Skip to content

Commit 261b077

Browse files
authored
Merge pull request #4192 from dhalbert/pico-pwmout-top-fix-4189
RP2040: fix off-by-one PWM top issue
2 parents 2d23b79 + 0ec99b3 commit 261b077

File tree

3 files changed

+34
-13
lines changed

3 files changed

+34
-13
lines changed

ports/raspberrypi/common-hal/pwmio/PWMOut.c

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,18 @@ uint32_t slice_variable_frequency;
4545
static uint32_t channel_use;
4646
static uint32_t never_reset_channel;
4747

48+
// Per the RP2040 datasheet:
49+
//
50+
// "A CC value of 0 will produce a 0% output, i.e. the output signal
51+
// is always low. A CC value of TOP + 1 (i.e. equal to the period, in
52+
// non-phase-correct mode) will produce a 100% output. For example, if
53+
// TOP is programmed to 254, the counter will have a period of 255
54+
// cycles, and CC values in the range of 0 to 255 inclusive will
55+
// produce duty cycles in the range 0% to 100% inclusive."
56+
//
57+
// So 65534 should be the maximum top value, and we'll set CC to be TOP+1 as appropriate.
58+
#define MAX_TOP 65534
59+
4860
static uint32_t _mask(uint8_t slice, uint8_t channel) {
4961
return 1 << (slice * CHANNELS_PER_SLICE + channel);
5062
}
@@ -164,19 +176,28 @@ void common_hal_pwmio_pwmout_deinit(pwmio_pwmout_obj_t* self) {
164176

165177
extern void common_hal_pwmio_pwmout_set_duty_cycle(pwmio_pwmout_obj_t* self, uint16_t duty) {
166178
self->duty_cycle = duty;
167-
uint16_t actual_duty = duty * self->top / ((1 << 16) - 1);
168-
pwm_set_chan_level(self->slice, self->channel, actual_duty);
179+
// Do arithmetic in 32 bits to prevent overflow.
180+
uint16_t compare_count;
181+
if (duty == 65535) {
182+
// Ensure that 100% duty cycle is 100% full on and not rounded down,
183+
// but do MIN() to keep value in range, just in case.
184+
compare_count = MIN(UINT16_MAX, (uint32_t) self->top + 1);
185+
} else {
186+
compare_count= ((uint32_t) duty * self->top + MAX_TOP / 2) / MAX_TOP;
187+
}
188+
// compare_count is the CC register value, which should be TOP+1 for 100% duty cycle.
189+
pwm_set_chan_level(self->slice, self->channel, compare_count);
169190
}
170191

171192
uint16_t common_hal_pwmio_pwmout_get_duty_cycle(pwmio_pwmout_obj_t* self) {
172193
return self->duty_cycle;
173194
}
174195

175-
void pwmio_pwmout_set_top(pwmio_pwmout_obj_t* self, uint32_t top) {
196+
void pwmio_pwmout_set_top(pwmio_pwmout_obj_t* self, uint16_t top) {
176197
self->actual_frequency = common_hal_mcu_processor_get_frequency() / top;
177198
self->top = top;
178199
pwm_set_clkdiv_int_frac(self->slice, 1, 0);
179-
pwm_set_wrap(self->slice, self->top - 1);
200+
pwm_set_wrap(self->slice, self->top);
180201
}
181202

182203
void common_hal_pwmio_pwmout_set_frequency(pwmio_pwmout_obj_t* self, uint32_t frequency) {
@@ -187,7 +208,7 @@ void common_hal_pwmio_pwmout_set_frequency(pwmio_pwmout_obj_t* self, uint32_t fr
187208
target_slice_frequencies[self->slice] = frequency;
188209

189210
// For low frequencies use the divider to give us full resolution duty_cycle.
190-
if (frequency < (common_hal_mcu_processor_get_frequency() / (1 << 16))) {
211+
if (frequency <= (common_hal_mcu_processor_get_frequency() / (1 << 16))) {
191212
// Compute the divisor. It's an 8 bit integer and 4 bit fraction. Therefore,
192213
// we compute everything * 16 for the fractional part.
193214
// This is 1 << 12 because 4 bits are the * 16.
@@ -201,16 +222,17 @@ void common_hal_pwmio_pwmout_set_frequency(pwmio_pwmout_obj_t* self, uint32_t fr
201222
if (div16 >= (1 << 12)) {
202223
div16 = (1 << 12) - 1;
203224
}
204-
self->actual_frequency = frequency16 / div16;
205-
self->top = 1 << 16;
225+
self->actual_frequency = (frequency16 + (div16 / 2)) / div16;
226+
self->top = MAX_TOP;
206227
pwm_set_clkdiv_int_frac(self->slice, div16 / 16, div16 % 16);
207-
pwm_set_wrap(self->slice, self->top - 1);
228+
pwm_set_wrap(self->slice, self->top);
208229
} else {
209230
uint32_t top = common_hal_mcu_processor_get_frequency() / frequency;
210231
self->actual_frequency = common_hal_mcu_processor_get_frequency() / top;
211-
self->top = top;
232+
self->top = MIN(MAX_TOP, top);
212233
pwm_set_clkdiv_int_frac(self->slice, 1, 0);
213-
pwm_set_wrap(self->slice, self->top - 1);
234+
// Set TOP register. For 100% duty cycle, CC must be set to TOP+1.
235+
pwm_set_wrap(self->slice, self->top);
214236
}
215237
common_hal_pwmio_pwmout_set_duty_cycle(self, self->duty_cycle);
216238
}

ports/raspberrypi/common-hal/pwmio/PWMOut.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,11 @@ typedef struct {
3939
bool variable_frequency;
4040
uint16_t duty_cycle;
4141
uint32_t actual_frequency;
42-
uint32_t top;
42+
uint16_t top;
4343
} pwmio_pwmout_obj_t;
4444

4545
void pwmout_reset(void);
4646
// Private API for AudioPWMOut.
47-
void pwmio_pwmout_set_top(pwmio_pwmout_obj_t* self, uint32_t top);
47+
void pwmio_pwmout_set_top(pwmio_pwmout_obj_t* self, uint16_t top);
4848

4949
#endif // MICROPY_INCLUDED_ATMEL_SAMD_COMMON_HAL_PWMIO_PWMOUT_H

shared-bindings/adafruit_bus_device/SPIDevice.c

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@
3434

3535
#include "lib/utils/buffer_helper.h"
3636
#include "lib/utils/context_manager_helpers.h"
37-
#include "py/objproperty.h"
3837
#include "py/runtime.h"
3938
#include "supervisor/shared/translate.h"
4039

0 commit comments

Comments
 (0)