@@ -45,6 +45,18 @@ uint32_t slice_variable_frequency;
45
45
static uint32_t channel_use ;
46
46
static uint32_t never_reset_channel ;
47
47
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
+
48
60
static uint32_t _mask (uint8_t slice , uint8_t channel ) {
49
61
return 1 << (slice * CHANNELS_PER_SLICE + channel );
50
62
}
@@ -164,19 +176,28 @@ void common_hal_pwmio_pwmout_deinit(pwmio_pwmout_obj_t* self) {
164
176
165
177
extern void common_hal_pwmio_pwmout_set_duty_cycle (pwmio_pwmout_obj_t * self , uint16_t duty ) {
166
178
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 );
169
190
}
170
191
171
192
uint16_t common_hal_pwmio_pwmout_get_duty_cycle (pwmio_pwmout_obj_t * self ) {
172
193
return self -> duty_cycle ;
173
194
}
174
195
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 ) {
176
197
self -> actual_frequency = common_hal_mcu_processor_get_frequency () / top ;
177
198
self -> top = top ;
178
199
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 );
180
201
}
181
202
182
203
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
187
208
target_slice_frequencies [self -> slice ] = frequency ;
188
209
189
210
// 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 ))) {
191
212
// Compute the divisor. It's an 8 bit integer and 4 bit fraction. Therefore,
192
213
// we compute everything * 16 for the fractional part.
193
214
// 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
201
222
if (div16 >= (1 << 12 )) {
202
223
div16 = (1 << 12 ) - 1 ;
203
224
}
204
- self -> actual_frequency = frequency16 / div16 ;
205
- self -> top = 1 << 16 ;
225
+ self -> actual_frequency = ( frequency16 + ( div16 / 2 )) / div16 ;
226
+ self -> top = MAX_TOP ;
206
227
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 );
208
229
} else {
209
230
uint32_t top = common_hal_mcu_processor_get_frequency () / frequency ;
210
231
self -> actual_frequency = common_hal_mcu_processor_get_frequency () / top ;
211
- self -> top = top ;
232
+ self -> top = MIN ( MAX_TOP , top ) ;
212
233
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 );
214
236
}
215
237
common_hal_pwmio_pwmout_set_duty_cycle (self , self -> duty_cycle );
216
238
}
0 commit comments