Skip to content

Commit 0ec99b3

Browse files
committed
handle 100% duty cycle; improve actual_frequency calc
1 parent 3e43281 commit 0ec99b3

File tree

1 file changed

+26
-5
lines changed
  • ports/raspberrypi/common-hal/pwmio

1 file changed

+26
-5
lines changed

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

Lines changed: 26 additions & 5 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
}
@@ -165,8 +177,16 @@ void common_hal_pwmio_pwmout_deinit(pwmio_pwmout_obj_t* self) {
165177
extern void common_hal_pwmio_pwmout_set_duty_cycle(pwmio_pwmout_obj_t* self, uint16_t duty) {
166178
self->duty_cycle = duty;
167179
// Do arithmetic in 32 bits to prevent overflow.
168-
uint16_t actual_duty = (uint32_t) duty * self->top / ((1 << 16) - 1);
169-
pwm_set_chan_level(self->slice, self->channel, actual_duty);
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);
170190
}
171191

172192
uint16_t common_hal_pwmio_pwmout_get_duty_cycle(pwmio_pwmout_obj_t* self) {
@@ -202,15 +222,16 @@ void common_hal_pwmio_pwmout_set_frequency(pwmio_pwmout_obj_t* self, uint32_t fr
202222
if (div16 >= (1 << 12)) {
203223
div16 = (1 << 12) - 1;
204224
}
205-
self->actual_frequency = frequency16 / div16;
206-
self->top = (1 << 16) - 1;
225+
self->actual_frequency = (frequency16 + (div16 / 2)) / div16;
226+
self->top = MAX_TOP;
207227
pwm_set_clkdiv_int_frac(self->slice, div16 / 16, div16 % 16);
208228
pwm_set_wrap(self->slice, self->top);
209229
} else {
210230
uint32_t top = common_hal_mcu_processor_get_frequency() / frequency;
211231
self->actual_frequency = common_hal_mcu_processor_get_frequency() / top;
212-
self->top = MIN(UINT16_MAX, top - 1);
232+
self->top = MIN(MAX_TOP, top);
213233
pwm_set_clkdiv_int_frac(self->slice, 1, 0);
234+
// Set TOP register. For 100% duty cycle, CC must be set to TOP+1.
214235
pwm_set_wrap(self->slice, self->top);
215236
}
216237
common_hal_pwmio_pwmout_set_duty_cycle(self, self->duty_cycle);

0 commit comments

Comments
 (0)