Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix off-by-one in RP2040 PWM frequency setting #9398

Merged
merged 3 commits into from
Jul 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions ports/raspberrypi/common-hal/pwmio/PWMOut.c
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,10 @@ extern void common_hal_pwmio_pwmout_set_duty_cycle(pwmio_pwmout_obj_t *self, uin
} else {
compare_count = ((uint32_t)duty * self->top + MAX_TOP / 2) / MAX_TOP;
}
// do not allow count to be 0 (due to rounding) unless duty 0 was requested
if (compare_count == 0 && duty != 0) {
compare_count = 1;
}
// compare_count is the CC register value, which should be TOP+1 for 100% duty cycle.
pwm_set_chan_level(self->slice, self->ab_channel, compare_count);
}
Expand Down Expand Up @@ -218,8 +222,8 @@ void common_hal_pwmio_pwmout_set_frequency(pwmio_pwmout_obj_t *self, uint32_t fr
pwm_set_clkdiv_int_frac(self->slice, div16 / 16, div16 % 16);
pwm_set_wrap(self->slice, self->top);
} else {
uint32_t top = common_hal_mcu_processor_get_frequency() / frequency;
self->actual_frequency = common_hal_mcu_processor_get_frequency() / top;
uint32_t top = common_hal_mcu_processor_get_frequency() / frequency - 1;
self->actual_frequency = common_hal_mcu_processor_get_frequency() / (top + 1);
self->top = MIN(MAX_TOP, top);
pwm_set_clkdiv_int_frac(self->slice, 1, 0);
// Set TOP register. For 100% duty cycle, CC must be set to TOP+1.
Expand Down
8 changes: 7 additions & 1 deletion tests/circuitpython-manual/pwmio/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

This directory contains tools for testing CircuitPython's PWM API. Running the tests involves three steps:

1. [CircuitPython PWM test code `code.py`](code.py) is run on the board to be tested.
1. [CircuitPython PWM test code `code_ramps.py`](code_ramps.py) is run on the board to be tested.
2. As the code runs, the state of the PWM signal is logged by a logic analyzer (I used a Saleae Logic Pro 8).
3. Data collected by the logic analyzer is analyzed and plotted into a PNG image by [CPython code `duty.py`](duty.py).

Expand Down Expand Up @@ -37,3 +37,9 @@ These tests can be used to assess how well the PWM API delivers expected behavio
The plot at the top of this page depicts data PWM gathered from a device with an API that displays all of the expected behavior listed above. The plots below show how the tools reveal flaws in the behavior of PWM APIs that are not as complete.

<img src="pwm_flaw_explainer.png">

## Testing always-off and always-on PWM extremes

The procedure described above does not test item 2 above, i.e. the ability of the API to support duty cycles of 0% and 100%. A different code file, (code_extremes.py) provides for this. This code cycles through PWM duty cycles of 32767, 65535, 1, 65534, and 0, repeating the sequence at six frequencies from 100 Hz to 10MHz. When viewed on a logic analyzer, the PWM output should look like the figure below. 100% and 0% PWM result from duty cycle settings of 65535 and 0, (and only from those settings, in accordance with item 3 above.)

<img src="pwm_extremes_explainer.png">
87 changes: 87 additions & 0 deletions tests/circuitpython-manual/pwmio/code_extremes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import board
import pwmio
import random
import time
import microcontroller
import os
import sys
import random

exponents = [
2,
3,
4,
5,
6,
7,
]

freqs = [int(10**f) for f in exponents]

top = 65536
den = 10
duties = [32767, 65535, 1, 65534, 0, 0]
freq_duration = 1.2
duty_duration = 0.2

print("\n\n")
board_name = sys.implementation[2]

pins = {
"Feather RP2040": (("D4", ""),),
"RP2040-Zero": (("GP15", ""),),
"Grand Central": (("D51", "TCC"), ("A15", "TC")),
"Metro M0": (("A2", "TC"), ("A3", "TCC")),
"ESP32-S3-DevKit": (("IO6", ""),), # marked D5 on board for XIAO-ESP32-s3
"Feather ESP32-S2": (("D9", ""),),
"XIAO nRF52840": (("D9", ""),),
}

for board_key in pins:
if board_key in board_name:
pins_to_use = pins[board_key]
break

while True:
for pin_name, pin_type in pins_to_use:
pin = getattr(board, pin_name)
print('title="', end="")
print(f"{board_name} at {microcontroller.cpu.frequency} Hz, pin {pin_name}", end="")
if len(pin_type) > 0:
print(f" ({pin_type})", end="")
print('",')
print(f'subtitle="{freq_duration:0.1f}s per frequency",')
print(f'version="{sys.version}",')
print("freq_calls=", end="")
pwm = pwmio.PWMOut(pin, variable_frequency=True)
t0 = time.monotonic()
duty_time = t0 + duty_duration
print("(", end="")
offset = 0
increment = 1
for freq in freqs:
i = 0
try:
pwm.frequency = freq
except ValueError:
break
freq_time = t0 + freq_duration
duty_time = t0 + duty_duration
j = 0
while time.monotonic() < freq_time:
duty = duties[j]
pwm.duty_cycle = duty
while time.monotonic() < duty_time and time.monotonic() < freq_time:
pass
j += 1
j = min(j, len(duties) - 1)
duty_time += duty_duration
i += 1
if time.monotonic() > freq_time:
break
t0 = freq_time
print(f"({freq}, {i/freq_duration:.0f}), ", end="")
print(")")
print("done.")
pwm.deinit()
time.sleep(5)
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
board_name = sys.implementation[2]

pins = {
"Feather RP2040": (("D4", ""),),
"RP2040-Zero": (("GP15", ""),),
"Grand Central": (("D51", "TCC"), ("A15", "TC")),
"Metro M0": (("A2", "TC"), ("A3", "TCC")),
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.