Skip to content

Commit 56ac41f

Browse files
authored
Merge pull request #2240 from theacodes/fix-2086
Track unadjusted PWM duty cycle to avoid accumulating conversion errors
2 parents cc13fc3 + 6782948 commit 56ac41f

File tree

3 files changed

+21
-2
lines changed

3 files changed

+21
-2
lines changed

ports/atmel-samd/common-hal/pulseio/PWMOut.c

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ pwmout_result_t common_hal_pulseio_pwmout_construct(pulseio_pwmout_obj_t* self,
135135
bool variable_frequency) {
136136
self->pin = pin;
137137
self->variable_frequency = variable_frequency;
138+
self->duty_cycle = duty;
138139

139140
if (pin->timer[0].index >= TC_INST_NUM &&
140141
pin->timer[1].index >= TCC_INST_NUM
@@ -322,6 +323,13 @@ void common_hal_pulseio_pwmout_deinit(pulseio_pwmout_obj_t* self) {
322323
}
323324

324325
extern void common_hal_pulseio_pwmout_set_duty_cycle(pulseio_pwmout_obj_t* self, uint16_t duty) {
326+
// Store the unadjusted duty cycle. It turns out the the process of adjusting and calculating
327+
// the duty cycle here and reading it back is lossy - the value will decay over time.
328+
// Track it here so that if frequency is changed we can use this value to recalculate the
329+
// proper duty cycle.
330+
// See https://github.com/adafruit/circuitpython/issues/2086 for more details
331+
self->duty_cycle = duty;
332+
325333
const pin_timer_t* t = self->timer;
326334
if (t->is_tc) {
327335
uint16_t adjusted_duty = tc_periods[t->index] * duty / 0xffff;
@@ -415,7 +423,6 @@ void common_hal_pulseio_pwmout_set_frequency(pulseio_pwmout_obj_t* self,
415423
break;
416424
}
417425
}
418-
uint16_t old_duty = common_hal_pulseio_pwmout_get_duty_cycle(self);
419426
if (t->is_tc) {
420427
Tc* tc = tc_insts[t->index];
421428
uint8_t old_divisor = tc->COUNT16.CTRLA.bit.PRESCALER;
@@ -450,7 +457,7 @@ void common_hal_pulseio_pwmout_set_frequency(pulseio_pwmout_obj_t* self,
450457
#endif
451458
}
452459

453-
common_hal_pulseio_pwmout_set_duty_cycle(self, old_duty);
460+
common_hal_pulseio_pwmout_set_duty_cycle(self, self->duty_cycle);
454461
}
455462

456463
uint32_t common_hal_pulseio_pwmout_get_frequency(pulseio_pwmout_obj_t* self) {

ports/atmel-samd/common-hal/pulseio/PWMOut.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ typedef struct {
3636
const mcu_pin_obj_t *pin;
3737
const pin_timer_t* timer;
3838
bool variable_frequency;
39+
uint16_t duty_cycle;
3940
} pulseio_pwmout_obj_t;
4041

4142
void pwmout_reset(void);

shared-bindings/pulseio/PWMOut.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,11 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(pulseio_pwmout___exit___obj, 4, 4, pu
162162
//| 16 bit value that dictates how much of one cycle is high (1) versus low
163163
//| (0). 0xffff will always be high, 0 will always be low and 0x7fff will
164164
//| be half high and then half low.
165+
//|
166+
//| Depending on how PWM is implemented on a specific board, the internal
167+
//| representation for duty cycle might have less than 16 bits of resolution.
168+
//| Reading this property will return the value from the internal representation,
169+
//| so it may differ from the value set.
165170
STATIC mp_obj_t pulseio_pwmout_obj_get_duty_cycle(mp_obj_t self_in) {
166171
pulseio_pwmout_obj_t *self = MP_OBJ_TO_PTR(self_in);
167172
check_for_deinit(self);
@@ -193,6 +198,12 @@ const mp_obj_property_t pulseio_pwmout_duty_cycle_obj = {
193198
//| 32 bit value that dictates the PWM frequency in Hertz (cycles per
194199
//| second). Only writeable when constructed with ``variable_frequency=True``.
195200
//|
201+
//| Depending on how PWM is implemented on a specific board, the internal value
202+
//| for the PWM's duty cycle may need to be recalculated when the frequency
203+
//| changes. In these cases, the duty cycle is automatically recalculated
204+
//| from the original duty cycle value. This should happen without any need
205+
//| to manually re-set the duty cycle.
206+
//|
196207
STATIC mp_obj_t pulseio_pwmout_obj_get_frequency(mp_obj_t self_in) {
197208
pulseio_pwmout_obj_t *self = MP_OBJ_TO_PTR(self_in);
198209
check_for_deinit(self);

0 commit comments

Comments
 (0)