@@ -113,30 +113,31 @@ STATIC void mp_machine_pwm_freq_set(machine_pwm_obj_t *self, mp_int_t freq) {
113
113
// Maximum "top" is set at 65534 to be able to achieve 100% duty with 65535.
114
114
#define TOP_MAX 65534
115
115
uint32_t source_hz = clock_get_hz (clk_sys );
116
- uint32_t div16_top = 16 * source_hz / freq ;
117
- uint32_t top = 1 ;
118
- for (;;) {
119
- // Try a few small prime factors to get close to the desired frequency.
120
- if (div16_top >= 16 * 5 && div16_top % 5 == 0 && top * 5 <= TOP_MAX ) {
121
- div16_top /= 5 ;
122
- top *= 5 ;
123
- } else if (div16_top >= 16 * 3 && div16_top % 3 == 0 && top * 3 <= TOP_MAX ) {
124
- div16_top /= 3 ;
125
- top *= 3 ;
126
- } else if (div16_top >= 16 * 2 && top * 2 <= TOP_MAX ) {
127
- div16_top /= 2 ;
128
- top *= 2 ;
129
- } else {
130
- break ;
131
- }
116
+ uint32_t div16 ;
117
+
118
+ if ((source_hz + freq / 2 ) / freq < TOP_MAX ) {
119
+ // If possible (based on the formula for TOP below), use a DIV of 1.
120
+ // This also prevents overflow in the DIV calculation.
121
+ div16 = 16 ;
122
+ } else {
123
+ // Otherwise, choose the smallest possible DIV for maximum
124
+ // duty cycle resolution.
125
+ // Constraint: 16*F/(div16*freq) < TOP_MAX
126
+ // So: div16 = ceil(16*F/(TOP_MAX*freq))
127
+
128
+ div16 = (16 * source_hz + TOP_MAX * freq - 1 ) / (TOP_MAX * freq );
132
129
}
133
- if (div16_top < 16 ) {
130
+
131
+ // Set TOP as accurately as possible using rounding.
132
+ uint32_t top = (16 * source_hz + div16 * freq / 2 ) / (div16 * freq ) - 1 ;
133
+
134
+ if (div16 < 16 ) {
134
135
mp_raise_ValueError (MP_ERROR_TEXT ("freq too large" ));
135
- } else if (div16_top >= 256 * 16 ) {
136
+ } else if (div16 >= 256 * 16 ) {
136
137
mp_raise_ValueError (MP_ERROR_TEXT ("freq too small" ));
137
138
}
138
- pwm_hw -> slice [self -> slice ].div = div16_top ;
139
- pwm_hw -> slice [self -> slice ].top = top - 1 ;
139
+ pwm_hw -> slice [self -> slice ].div = div16 ;
140
+ pwm_hw -> slice [self -> slice ].top = top ;
140
141
if (self -> duty_type == DUTY_U16 ) {
141
142
mp_machine_pwm_duty_set_u16 (self , self -> duty );
142
143
} else if (self -> duty_type == DUTY_NS ) {
@@ -148,12 +149,17 @@ STATIC mp_obj_t mp_machine_pwm_duty_get_u16(machine_pwm_obj_t *self) {
148
149
uint32_t top = pwm_hw -> slice [self -> slice ].top ;
149
150
uint32_t cc = pwm_hw -> slice [self -> slice ].cc ;
150
151
cc = (cc >> (self -> channel ? PWM_CH0_CC_B_LSB : PWM_CH0_CC_A_LSB )) & 0xffff ;
151
- return MP_OBJ_NEW_SMALL_INT (cc * 65535 / (top + 1 ));
152
+
153
+ // Use rounding (instead of flooring) here to give as accurate an
154
+ // estimate as possible.
155
+ return MP_OBJ_NEW_SMALL_INT ((cc * 65535 + (top + 1 ) / 2 ) / (top + 1 ));
152
156
}
153
157
154
158
STATIC void mp_machine_pwm_duty_set_u16 (machine_pwm_obj_t * self , mp_int_t duty_u16 ) {
155
159
uint32_t top = pwm_hw -> slice [self -> slice ].top ;
156
- uint32_t cc = duty_u16 * (top + 1 ) / 65535 ;
160
+
161
+ // Use rounding here to set it as accurately as possible.
162
+ uint32_t cc = (duty_u16 * (top + 1 ) + 65535 / 2 ) / 65535 ;
157
163
pwm_set_chan_level (self -> slice , self -> channel , cc );
158
164
pwm_set_enabled (self -> slice , true);
159
165
self -> duty = duty_u16 ;
0 commit comments