Skip to content

Commit 39296b4

Browse files
committed
Fix timer overflow code.
Teensy doesn't need to worry about overflows since all of its timers are only 16-bit. For PWM, the pulse width needs to be able to vary from 0..period+1 (pulse-width == period+1 corresponds to 100% PWM) I couldn't test the 0xffffffff cases since we can't currently get a period that big in python. With a prescaler of 0, that corresponds to a freq of 0.039 (i.e. cycle every 25.56 seconds), and we can't set that using freq or period. I also tested both stmhal and teensy with floats disabled, which required a few other code changes to compile.
1 parent b766e79 commit 39296b4

6 files changed

Lines changed: 145 additions & 68 deletions

File tree

extmod/moductypes.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,10 +309,12 @@ STATIC mp_obj_t get_aligned(uint val_type, void *p, mp_int_t index) {
309309
case UINT64:
310310
case INT64:
311311
return mp_obj_new_int_from_ll(((int64_t*)p)[index]);
312+
#if MICROPY_PY_BUILTINS_FLOAT
312313
case FLOAT32:
313314
return mp_obj_new_float(((float*)p)[index]);
314315
case FLOAT64:
315316
return mp_obj_new_float(((double*)p)[index]);
317+
#endif
316318
default:
317319
assert(0);
318320
return MP_OBJ_NULL;

stmhal/adc.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,7 @@ int adc_read_core_temp(ADC_HandleTypeDef *adcHandle) {
349349
return ((raw_value - CORE_TEMP_V25) / CORE_TEMP_AVG_SLOPE) + 25;
350350
}
351351

352+
#if MICROPY_PY_BUILTINS_FLOAT
352353
float adc_read_core_vbat(ADC_HandleTypeDef *adcHandle) {
353354
uint32_t raw_value = adc_config_and_read_channel(adcHandle, ADC_CHANNEL_VBAT);
354355

@@ -368,6 +369,7 @@ float adc_read_core_vref(ADC_HandleTypeDef *adcHandle) {
368369

369370
return raw_value * VBAT_DIV / 4096.0f * 3.3f;
370371
}
372+
#endif
371373

372374
/******************************************************************************/
373375
/* Micro Python bindings : adc_all object */
@@ -399,6 +401,7 @@ STATIC mp_obj_t adc_all_read_core_temp(mp_obj_t self_in) {
399401
}
400402
STATIC MP_DEFINE_CONST_FUN_OBJ_1(adc_all_read_core_temp_obj, adc_all_read_core_temp);
401403

404+
#if MICROPY_PY_BUILTINS_FLOAT
402405
STATIC mp_obj_t adc_all_read_core_vbat(mp_obj_t self_in) {
403406
pyb_adc_all_obj_t *self = self_in;
404407
float data = adc_read_core_vbat(&self->handle);
@@ -412,12 +415,15 @@ STATIC mp_obj_t adc_all_read_core_vref(mp_obj_t self_in) {
412415
return mp_obj_new_float(data);
413416
}
414417
STATIC MP_DEFINE_CONST_FUN_OBJ_1(adc_all_read_core_vref_obj, adc_all_read_core_vref);
418+
#endif
415419

416420
STATIC const mp_map_elem_t adc_all_locals_dict_table[] = {
417421
{ MP_OBJ_NEW_QSTR(MP_QSTR_read_channel), (mp_obj_t)&adc_all_read_channel_obj},
418422
{ MP_OBJ_NEW_QSTR(MP_QSTR_read_core_temp), (mp_obj_t)&adc_all_read_core_temp_obj},
423+
#if MICROPY_PY_BUILTINS_FLOAT
419424
{ MP_OBJ_NEW_QSTR(MP_QSTR_read_core_vbat), (mp_obj_t)&adc_all_read_core_vbat_obj},
420425
{ MP_OBJ_NEW_QSTR(MP_QSTR_read_core_vref), (mp_obj_t)&adc_all_read_core_vref_obj},
426+
#endif
421427
};
422428

423429
STATIC MP_DEFINE_CONST_DICT(adc_all_locals_dict, adc_all_locals_dict_table);

stmhal/modselect.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,10 +124,14 @@ STATIC mp_obj_t select_select(uint n_args, const mp_obj_t *args) {
124124
mp_uint_t timeout = -1;
125125
if (n_args == 4) {
126126
if (args[3] != mp_const_none) {
127+
#if MICROPY_PY_BUILTINS_FLOAT
127128
float timeout_f = mp_obj_get_float(args[3]);
128129
if (timeout_f >= 0) {
129130
timeout = (mp_uint_t)(timeout_f * 1000);
130131
}
132+
#else
133+
timeout = mp_obj_get_int(args[3]) * 1000;
134+
#endif
131135
}
132136
}
133137

stmhal/timer.c

Lines changed: 69 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -268,37 +268,84 @@ void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
268268

269269
STATIC const mp_obj_type_t pyb_timer_channel_type;
270270

271+
// This is the largest value that we can multiply by 100 and have the result
272+
// fit in a uint32_t.
273+
#define MAX_PERIOD_DIV_100 42949672
274+
275+
// Helper function for determining the period used for calculating percent
276+
STATIC uint32_t compute_period(pyb_timer_obj_t *self) {
277+
// In center mode, compare == period corresponds to 100%
278+
// In edge mode, compare == (period + 1) corresponds to 100%
279+
uint32_t period = (__HAL_TIM_GetAutoreload(&self->tim) & TIMER_CNT_MASK(self));
280+
if (period != 0xffffffff) {
281+
if (self->tim.Init.CounterMode == TIM_COUNTERMODE_UP ||
282+
self->tim.Init.CounterMode == TIM_COUNTERMODE_DOWN) {
283+
// Edge mode
284+
period++;
285+
}
286+
}
287+
return period;
288+
}
289+
271290
// Helper function to compute PWM value from timer period and percent value.
272-
// 'val' can be an int or a float between 0 and 100 (out of range values are
273-
// clamped).
274-
STATIC uint32_t compute_pwm_value_from_percent(uint32_t period, mp_obj_t val) {
291+
// 'percent_in' can be an int or a float between 0 and 100 (out of range
292+
// values are clamped).
293+
STATIC uint32_t compute_pwm_value_from_percent(uint32_t period, mp_obj_t percent_in) {
275294
uint32_t cmp;
276295
if (0) {
277296
#if MICROPY_PY_BUILTINS_FLOAT
278-
} else if (MP_OBJ_IS_TYPE(val, &mp_type_float)) {
279-
cmp = mp_obj_get_float(val) / 100.0 * period;
297+
} else if (MP_OBJ_IS_TYPE(percent_in, &mp_type_float)) {
298+
float percent = mp_obj_get_float(percent_in);
299+
if (percent <= 0.0) {
300+
cmp = 0;
301+
} else if (percent >= 100.0) {
302+
cmp = period;
303+
} else {
304+
cmp = percent / 100.0 * ((float)period);
305+
}
280306
#endif
281307
} else {
282308
// For integer arithmetic, if period is large and 100*period will
283309
// overflow, then divide period before multiplying by cmp. Otherwise
284310
// do it the other way round to retain precision.
285-
// TODO we really need an mp_obj_get_uint_clamped function here so
286-
// that we can get long-int values as large as 0xffffffff.
287-
cmp = mp_obj_get_int(val);
288-
if (period > (1 << 31) / 100) {
289-
cmp = cmp * (period / 100);
311+
mp_int_t percent = mp_obj_get_int(percent_in);
312+
if (percent <= 0) {
313+
cmp = 0;
314+
} else if (percent >= 100) {
315+
cmp = period;
316+
} else if (period > MAX_PERIOD_DIV_100) {
317+
cmp = (uint32_t)percent * (period / 100);
290318
} else {
291-
cmp = (cmp * period) / 100;
319+
cmp = ((uint32_t)percent * period) / 100;
292320
}
293321
}
294-
if (cmp < 0) {
295-
cmp = 0;
296-
} else if (cmp > period) {
297-
cmp = period;
298-
}
299322
return cmp;
300323
}
301324

325+
// Helper function to compute percentage from timer perion and PWM value.
326+
STATIC mp_obj_t compute_percent_from_pwm_value(uint32_t period, uint32_t cmp) {
327+
#if MICROPY_PY_BUILTINS_FLOAT
328+
float percent;
329+
if (cmp > period) {
330+
percent = 100.0;
331+
} else {
332+
percent = (float)cmp * 100.0 / ((float)period);
333+
}
334+
return mp_obj_new_float(percent);
335+
#else
336+
mp_int_t percent;
337+
if (cmp > period) {
338+
percent = 100;
339+
} else if (period > MAX_PERIOD_DIV_100) {
340+
// We divide the top and bottom by 128, and then do the math.
341+
percent = (cmp / 128) * 100 / (period / 128);
342+
} else {
343+
percent = cmp * 100 / period;
344+
}
345+
return mp_obj_new_int(percent);
346+
#endif
347+
}
348+
302349
STATIC void pyb_timer_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
303350
pyb_timer_obj_t *self = self_in;
304351

@@ -696,13 +743,7 @@ STATIC mp_obj_t pyb_timer_channel(mp_uint_t n_args, const mp_obj_t *args, mp_map
696743
oc_config.OCMode = channel_mode_info[chan->mode].oc_mode;
697744
if (vals[3].u_obj != mp_const_none) {
698745
// pulse width percent given
699-
uint32_t period = (__HAL_TIM_GetAutoreload(&self->tim) & TIMER_CNT_MASK(self)) + 1;
700-
// For 32-bit timer, maximum period + 1 will overflow. In that
701-
// case we set the period back to 0xffffffff which will give very
702-
// close to the correct result for the percentage calculation.
703-
if (period == 0) {
704-
period = 0xffffffff;
705-
}
746+
uint32_t period = compute_period(self);
706747
oc_config.Pulse = compute_pwm_value_from_percent(period, vals[3].u_obj);
707748
} else {
708749
// use absolute pulse width value (defaults to 0 if nothing given)
@@ -917,6 +958,9 @@ STATIC void pyb_timer_channel_print(void (*print)(void *env, const char *fmt, ..
917958
/// Get or set the pulse width value associated with a channel.
918959
/// capture, compare, and pulse_width are all aliases for the same function.
919960
/// pulse_width is the logical name to use when the channel is in PWM mode.
961+
///
962+
/// In edge aligned mode, a pulse_width of `period + 1` corresponds to a duty cycle of 100%
963+
/// In center aligned mode, a pulse width of `period` corresponds to a duty cycle of 100%
920964
STATIC mp_obj_t pyb_timer_channel_capture_compare(mp_uint_t n_args, const mp_obj_t *args) {
921965
pyb_timer_channel_obj_t *self = args[0];
922966
if (n_args == 1) {
@@ -938,22 +982,11 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(pyb_timer_channel_capture_compare_obj
938982
/// a duty cycle of 25%.
939983
STATIC mp_obj_t pyb_timer_channel_pulse_width_percent(mp_uint_t n_args, const mp_obj_t *args) {
940984
pyb_timer_channel_obj_t *self = args[0];
941-
uint32_t period = (__HAL_TIM_GetAutoreload(&self->timer->tim) & TIMER_CNT_MASK(self->timer)) + 1;
942-
// For 32-bit timer, maximum period + 1 will overflow. In that case we set
943-
// the period back to 0xffffffff which will give very close to the correct
944-
// result for the percentage calculation.
945-
if (period == 0) {
946-
period = 0xffffffff;
947-
}
985+
uint32_t period = compute_period(self->timer);
948986
if (n_args == 1) {
949987
// get
950988
uint32_t cmp = __HAL_TIM_GetCompare(&self->timer->tim, TIMER_CHANNEL(self)) & TIMER_CNT_MASK(self->timer);
951-
#if MICROPY_PY_BUILTINS_FLOAT
952-
return mp_obj_new_float((float)cmp / (float)period * 100.0);
953-
#else
954-
// TODO handle overflow of multiplication for 32-bit timer
955-
return mp_obj_new_int(cmp * 100 / period);
956-
#endif
989+
return compute_percent_from_pwm_value(period, cmp);
957990
} else {
958991
// set
959992
uint32_t cmp = compute_pwm_value_from_percent(period, args[1]);

teensy/mpconfigport.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
#define MICROPY_ENABLE_GC (1)
99
#define MICROPY_ENABLE_FINALISER (1)
1010
#define MICROPY_HELPER_REPL (1)
11-
#define MICROPY_PY_BUILTINS_FLOAT (1)
1211
#define MICROPY_ENABLE_SOURCE_LINE (1)
1312
#define MICROPY_LONGINT_IMPL (MICROPY_LONGINT_IMPL_MPZ)
1413
#define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_FLOAT)

teensy/timer.c

Lines changed: 64 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -128,37 +128,70 @@ mp_uint_t get_prescaler_shift(mp_int_t prescaler) {
128128

129129
STATIC const mp_obj_type_t pyb_timer_channel_type;
130130

131+
// Helper function for determining the period used for calculating percent
132+
STATIC uint32_t compute_period(pyb_timer_obj_t *self) {
133+
// In center mode, compare == period corresponds to 100%
134+
// In edge mode, compare == (period + 1) corresponds to 100%
135+
FTM_TypeDef *FTMx = self->ftm.Instance;
136+
uint32_t period = (FTMx->MOD & 0xffff);
137+
if ((FTMx->SC & FTM_SC_CPWMS) == 0) {
138+
// Edge mode
139+
period++;
140+
}
141+
return period;
142+
}
143+
131144
// Helper function to compute PWM value from timer period and percent value.
132145
// 'val' can be an int or a float between 0 and 100 (out of range values are
133146
// clamped).
134-
STATIC uint32_t compute_pwm_value_from_percent(uint32_t period, mp_obj_t val) {
147+
STATIC uint32_t compute_pwm_value_from_percent(uint32_t period, mp_obj_t percent_in) {
135148
uint32_t cmp;
136149
if (0) {
137150
#if MICROPY_PY_BUILTINS_FLOAT
138-
} else if (MP_OBJ_IS_TYPE(val, &mp_type_float)) {
139-
cmp = mp_obj_get_float(val) / 100.0 * period;
151+
} else if (MP_OBJ_IS_TYPE(percent_in, &mp_type_float)) {
152+
float percent = mp_obj_get_float(percent_in);
153+
if (percent <= 0.0) {
154+
cmp = 0;
155+
} else if (percent >= 100.0) {
156+
cmp = period;
157+
} else {
158+
cmp = percent / 100.0 * ((float)period);
159+
}
140160
#endif
141161
} else {
142-
// For integer arithmetic, if period is large and 100*period will
143-
// overflow, then divide period before multiplying by cmp. Otherwise
144-
// do it the other way round to retain precision.
145-
// TODO we really need an mp_obj_get_uint_clamped function here so
146-
// that we can get long-int values as large as 0xffffffff.
147-
cmp = mp_obj_get_int(val);
148-
if (period > (1 << 31) / 100) {
149-
cmp = cmp * (period / 100);
162+
mp_int_t percent = mp_obj_get_int(percent_in);
163+
if (percent <= 0) {
164+
cmp = 0;
165+
} else if (percent >= 100) {
166+
cmp = period;
150167
} else {
151-
cmp = (cmp * period) / 100;
168+
cmp = ((uint32_t)percent * period) / 100;
152169
}
153170
}
154-
if (cmp < 0) {
155-
cmp = 0;
156-
} else if (cmp > period) {
157-
cmp = period;
158-
}
159171
return cmp;
160172
}
161173

174+
// Helper function to compute percentage from timer perion and PWM value.
175+
STATIC mp_obj_t compute_percent_from_pwm_value(uint32_t period, uint32_t cmp) {
176+
#if MICROPY_PY_BUILTINS_FLOAT
177+
float percent = (float)cmp * 100.0 / (float)period;
178+
if (cmp > period) {
179+
percent = 100.0;
180+
} else {
181+
percent = (float)cmp * 100.0 / (float)period;
182+
}
183+
return mp_obj_new_float(percent);
184+
#else
185+
mp_int_t percent;
186+
if (cmp > period) {
187+
percent = 100;
188+
} else {
189+
percent = cmp * 100 / period;
190+
}
191+
return mp_obj_new_int(percent);
192+
#endif
193+
}
194+
162195
STATIC void pyb_timer_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
163196
pyb_timer_obj_t *self = self_in;
164197

@@ -169,7 +202,7 @@ STATIC void pyb_timer_print(void (*print)(void *env, const char *fmt, ...), void
169202
self->tim_id,
170203
1 << (self->ftm.Instance->SC & 7),
171204
self->ftm.Instance->MOD & 0xffff,
172-
self->ftm.Init.CounterMode == FTM_COUNTERMODE_UP ? "tUP" : "CENTER");
205+
self->ftm.Init.CounterMode == FTM_COUNTERMODE_UP ? "UP" : "CENTER");
173206
}
174207
}
175208

@@ -193,7 +226,8 @@ STATIC void pyb_timer_print(void (*print)(void *env, const char *fmt, ...), void
193226
/// - `period` [0-0xffff] - Specifies the value to be loaded into the timer's
194227
/// Modulo Register (MOD). This determines the period of the timer (i.e.
195228
/// when the counter cycles). The timer counter will roll-over after
196-
/// `period + 1` timer clock cycles.
229+
/// `period` timer clock cycles. In center mode, a compare register > 0x7fff
230+
/// doesn't seem to work properly, so keep this in mind.
197231
///
198232
/// - `mode` can be one of:
199233
/// - `Timer.UP` - configures the timer to count from 0 to MOD (default)
@@ -231,15 +265,15 @@ STATIC mp_obj_t pyb_timer_init_helper(pyb_timer_obj_t *self, uint n_args, const
231265

232266
uint32_t period = MAX(1, F_BUS / vals[0].u_int);
233267
uint32_t prescaler_shift = 0;
234-
while (period > 0x10000 && prescaler_shift < 7) {
268+
while (period > 0xffff && prescaler_shift < 7) {
235269
period >>= 1;
236270
prescaler_shift++;
237271
}
238-
if (period > 0x10000) {
239-
period = 0x10000;
272+
if (period > 0xffff) {
273+
period = 0xffff;
240274
}
241275
init->PrescalerShift = prescaler_shift;
242-
init->Period = period - 1;
276+
init->Period = period;
243277
} else if (vals[1].u_int != 0xffffffff && vals[2].u_int != 0xffffffff) {
244278
// set prescaler and period directly
245279
init->PrescalerShift = get_prescaler_shift(vals[1].u_int);
@@ -501,13 +535,13 @@ STATIC mp_obj_t pyb_timer_channel(mp_uint_t n_args, const mp_obj_t *args, mp_map
501535
oc_config.OCMode = channel_mode_info[chan->mode].oc_mode;
502536
if (vals[3].u_obj != mp_const_none) {
503537
// pulse width ratio given
504-
uint32_t period = (self->ftm.Instance->MOD & 0xffff) + 1;
538+
uint32_t period = compute_period(self);
505539
oc_config.Pulse = compute_pwm_value_from_percent(period, vals[3].u_obj);
506540
} else {
507541
// use absolute pulse width value (defaults to 0 if nothing given)
508542
oc_config.Pulse = vals[2].u_int;
509543
}
510-
oc_config.OCPolarity = FTM_OCPOLARITY_HIGH;
544+
oc_config.OCPolarity = FTM_OCPOLARITY_HIGH;
511545

512546
HAL_FTM_PWM_ConfigChannel(&self->ftm, &oc_config, channel);
513547
if (chan->callback == mp_const_none) {
@@ -745,6 +779,9 @@ STATIC void pyb_timer_channel_print(void (*print)(void *env, const char *fmt, ..
745779
/// Get or set the pulse width value associated with a channel.
746780
/// capture, compare, and pulse_width are all aliases for the same function.
747781
/// pulse_width is the logical name to use when the channel is in PWM mode.
782+
///
783+
/// In edge aligned mode, a pulse_width of `period + 1` corresponds to a duty cycle of 100%
784+
/// In center aligned mode, a pulse width of `period` corresponds to a duty cycle of 100%
748785
STATIC mp_obj_t pyb_timer_channel_capture_compare(mp_uint_t n_args, const mp_obj_t *args) {
749786
pyb_timer_channel_obj_t *self = args[0];
750787
FTM_TypeDef *FTMx = self->timer->ftm.Instance;
@@ -770,15 +807,11 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(pyb_timer_channel_capture_compare_obj
770807
STATIC mp_obj_t pyb_timer_channel_pulse_width_percent(mp_uint_t n_args, const mp_obj_t *args) {
771808
pyb_timer_channel_obj_t *self = args[0];
772809
FTM_TypeDef *FTMx = self->timer->ftm.Instance;
773-
uint32_t period = (FTMx->MOD & 0xffff) + 1;
810+
uint32_t period = compute_period(self->timer);
774811
if (n_args == 1) {
775812
// get
776813
uint32_t cmp = FTMx->channel[self->channel].CV & 0xffff;
777-
#if MICROPY_PY_BUILTINS_FLOAT
778-
return mp_obj_new_float((float)cmp / (float)period * 100.0);
779-
#else
780-
return mp_obj_new_int(cmp * 100 / period);
781-
#endif
814+
return compute_percent_from_pwm_value(period, cmp);
782815
} else {
783816
// set
784817
uint32_t cmp = compute_pwm_value_from_percent(period, args[1]);

0 commit comments

Comments
 (0)