Skip to content

Commit e8ea072

Browse files
committed
stmhal, timer: Factor code to compute PWM percent; improve 32bit case.
Also do the same for teensy timer code.
1 parent 3fafe73 commit e8ea072

File tree

2 files changed

+106
-89
lines changed

2 files changed

+106
-89
lines changed

stmhal/timer.c

Lines changed: 60 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ typedef struct _pyb_timer_obj_t {
139139
// The following yields TIM_IT_UPDATE when channel is zero and
140140
// TIM_IT_CC1..TIM_IT_CC4 when channel is 1..4
141141
#define TIMER_IRQ_MASK(channel) (1 << (channel))
142-
#define TIMER_CNT_MASK(self) ((self)->is_32bit ? 0x3fffffff : 0xffff)
142+
#define TIMER_CNT_MASK(self) ((self)->is_32bit ? 0xffffffff : 0xffff)
143143
#define TIMER_CHANNEL(self) ((((self)->channel) - 1) << 2)
144144

145145
TIM_HandleTypeDef TIM3_Handle;
@@ -268,6 +268,37 @@ void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
268268

269269
STATIC const mp_obj_type_t pyb_timer_channel_type;
270270

271+
// 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) {
275+
uint32_t cmp;
276+
if (0) {
277+
#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;
280+
#endif
281+
} else {
282+
// For integer arithmetic, if period is large and 100*period will
283+
// overflow, then divide period before multiplying by cmp. Otherwise
284+
// 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);
290+
} else {
291+
cmp = (cmp * period) / 100;
292+
}
293+
}
294+
if (cmp < 0) {
295+
cmp = 0;
296+
} else if (cmp > period) {
297+
cmp = period;
298+
}
299+
return cmp;
300+
}
301+
271302
STATIC void pyb_timer_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
272303
pyb_timer_obj_t *self = self_in;
273304

@@ -568,7 +599,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_1(pyb_timer_deinit_obj, pyb_timer_deinit);
568599
STATIC const mp_arg_t pyb_timer_channel_args[] = {
569600
{ MP_QSTR_callback, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} },
570601
{ MP_QSTR_pin, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} },
571-
{ MP_QSTR_pulse_width, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0xffffffff} },
602+
{ MP_QSTR_pulse_width, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} },
572603
{ MP_QSTR_pulse_width_percent, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} },
573604
{ MP_QSTR_compare, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} },
574605
{ MP_QSTR_polarity, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0xffffffff} },
@@ -663,30 +694,19 @@ STATIC mp_obj_t pyb_timer_channel(mp_uint_t n_args, const mp_obj_t *args, mp_map
663694
case CHANNEL_MODE_PWM_INVERTED: {
664695
TIM_OC_InitTypeDef oc_config;
665696
oc_config.OCMode = channel_mode_info[chan->mode].oc_mode;
666-
if (vals[2].u_int != 0xffffffff) {
667-
// absolute pulse width value given
668-
oc_config.Pulse = vals[2].u_int;
669-
} else if (vals[3].u_obj != mp_const_none) {
697+
if (vals[3].u_obj != mp_const_none) {
670698
// pulse width percent given
671699
uint32_t period = (__HAL_TIM_GetAutoreload(&self->tim) & TIMER_CNT_MASK(self)) + 1;
672-
uint32_t cmp;
673-
#if MICROPY_PY_BUILTINS_FLOAT
674-
if (MP_OBJ_IS_TYPE(vals[3].u_obj, &mp_type_float)) {
675-
cmp = mp_obj_get_float(vals[3].u_obj) * period / 100.0;
676-
} else
677-
#endif
678-
{
679-
cmp = mp_obj_get_int(vals[3].u_obj) * period / 100;
680-
}
681-
if (cmp < 0) {
682-
cmp = 0;
683-
} else if (cmp > period) {
684-
cmp = period;
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;
685705
}
686-
oc_config.Pulse = cmp;
706+
oc_config.Pulse = compute_pwm_value_from_percent(period, vals[3].u_obj);
687707
} else {
688-
// nothing given, default to pulse width of 0
689-
oc_config.Pulse = 0;
708+
// use absolute pulse width value (defaults to 0 if nothing given)
709+
oc_config.Pulse = vals[2].u_int;
690710
}
691711
oc_config.OCPolarity = TIM_OCPOLARITY_HIGH;
692712
oc_config.OCNPolarity = TIM_OCNPOLARITY_HIGH;
@@ -910,38 +930,33 @@ STATIC mp_obj_t pyb_timer_channel_capture_compare(mp_uint_t n_args, const mp_obj
910930
}
911931
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(pyb_timer_channel_capture_compare_obj, 1, 2, pyb_timer_channel_capture_compare);
912932

913-
/// \method pulse_width_ratio([value])
914-
/// Get or set the pulse width ratio associated with a channel. The value is
915-
/// a floating-point number between 0.0 and 1.0, and is relative to the period
916-
/// of the timer associated with this channel. For example, a ratio of 0.5
917-
/// would be a 50% duty cycle.
933+
/// \method pulse_width_percent([value])
934+
/// Get or set the pulse width percentage associated with a channel. The value
935+
/// is a number between 0 and 100 and sets the percentage of the timer period
936+
/// for which the pulse is active. The value can be an integer or
937+
/// floating-point number for more accuracy. For example, a value of 25 gives
938+
/// a duty cycle of 25%.
918939
STATIC mp_obj_t pyb_timer_channel_pulse_width_percent(mp_uint_t n_args, const mp_obj_t *args) {
919940
pyb_timer_channel_obj_t *self = args[0];
920941
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+
}
921948
if (n_args == 1) {
922949
// get
923950
uint32_t cmp = __HAL_TIM_GetCompare(&self->timer->tim, TIMER_CHANNEL(self)) & TIMER_CNT_MASK(self->timer);
924-
#if MICROPY_PY_BUILTINS_FLOAT
925-
return mp_obj_new_float((float)cmp * 100.0 / (float)period);
926-
#else
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
927955
return mp_obj_new_int(cmp * 100 / period);
928-
#endif
956+
#endif
929957
} else {
930958
// set
931-
uint32_t cmp;
932-
#if MICROPY_PY_BUILTINS_FLOAT
933-
if (MP_OBJ_IS_TYPE(args[1], &mp_type_float)) {
934-
cmp = mp_obj_get_float(args[1]) * period / 100.0;
935-
} else
936-
#endif
937-
{
938-
cmp = mp_obj_get_int(args[1]) * period / 100;
939-
}
940-
if (cmp < 0) {
941-
cmp = 0;
942-
} else if (cmp > period) {
943-
cmp = period;
944-
}
959+
uint32_t cmp = compute_pwm_value_from_percent(period, args[1]);
945960
__HAL_TIM_SetCompare(&self->timer->tim, TIMER_CHANNEL(self), cmp & TIMER_CNT_MASK(self->timer));
946961
return mp_const_none;
947962
}

teensy/timer.c

Lines changed: 46 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,37 @@ 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 to compute PWM value from timer period and percent value.
132+
// 'val' can be an int or a float between 0 and 100 (out of range values are
133+
// clamped).
134+
STATIC uint32_t compute_pwm_value_from_percent(uint32_t period, mp_obj_t val) {
135+
uint32_t cmp;
136+
if (0) {
137+
#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;
140+
#endif
141+
} 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);
150+
} else {
151+
cmp = (cmp * period) / 100;
152+
}
153+
}
154+
if (cmp < 0) {
155+
cmp = 0;
156+
} else if (cmp > period) {
157+
cmp = period;
158+
}
159+
return cmp;
160+
}
161+
131162
STATIC void pyb_timer_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
132163
pyb_timer_obj_t *self = self_in;
133164

@@ -373,7 +404,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_1(pyb_timer_deinit_obj, pyb_timer_deinit);
373404
STATIC const mp_arg_t pyb_timer_channel_args[] = {
374405
{ MP_QSTR_callback, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} },
375406
{ MP_QSTR_pin, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} },
376-
{ MP_QSTR_pulse_width, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0xffffffff} },
407+
{ MP_QSTR_pulse_width, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} },
377408
{ MP_QSTR_pulse_width_percent, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} },
378409
{ MP_QSTR_compare, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} },
379410
{ MP_QSTR_polarity, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0xffffffff} },
@@ -468,30 +499,13 @@ STATIC mp_obj_t pyb_timer_channel(mp_uint_t n_args, const mp_obj_t *args, mp_map
468499
case CHANNEL_MODE_PWM_INVERTED: {
469500
FTM_OC_InitTypeDef oc_config;
470501
oc_config.OCMode = channel_mode_info[chan->mode].oc_mode;
471-
if (vals[2].u_int != 0xffffffff) {
472-
// absolute pulse width value given
473-
oc_config.Pulse = vals[2].u_int;
474-
} else if (vals[3].u_obj != mp_const_none) {
502+
if (vals[3].u_obj != mp_const_none) {
475503
// pulse width ratio given
476504
uint32_t period = (self->ftm.Instance->MOD & 0xffff) + 1;
477-
uint32_t cmp;
478-
#if MICROPY_PY_BUILTINS_FLOAT
479-
if (MP_OBJ_IS_TYPE(vals[3].u_obj, &mp_type_float)) {
480-
cmp = mp_obj_get_float(vals[3].u_obj) * period / 100.0;
481-
} else
482-
#endif
483-
{
484-
cmp = mp_obj_get_int(vals[3].u_obj) * period / 100;
485-
}
486-
if (cmp < 0) {
487-
cmp = 0;
488-
} else if (cmp > period) {
489-
cmp = period;
490-
}
491-
oc_config.Pulse = cmp;
505+
oc_config.Pulse = compute_pwm_value_from_percent(period, vals[3].u_obj);
492506
} else {
493-
// nothing given, default to pulse width of 0
494-
oc_config.Pulse = 0;
507+
// use absolute pulse width value (defaults to 0 if nothing given)
508+
oc_config.Pulse = vals[2].u_int;
495509
}
496510
oc_config.OCPolarity = FTM_OCPOLARITY_HIGH;
497511

@@ -748,38 +762,26 @@ STATIC mp_obj_t pyb_timer_channel_capture_compare(mp_uint_t n_args, const mp_obj
748762
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(pyb_timer_channel_capture_compare_obj, 1, 2, pyb_timer_channel_capture_compare);
749763

750764
/// \method pulse_width_percent([value])
751-
/// Get or set the pulse width ratio associated with a channel. The value is
752-
/// a floating-point number between 0.0 and 1.0, and is relative to the period
753-
/// of the timer associated with this channel. For example, a ratio of 0.5
754-
/// would be a 50% duty cycle.
765+
/// Get or set the pulse width percentage associated with a channel. The value
766+
/// is a number between 0 and 100 and sets the percentage of the timer period
767+
/// for which the pulse is active. The value can be an integer or
768+
/// floating-point number for more accuracy. For example, a value of 25 gives
769+
/// a duty cycle of 25%.
755770
STATIC mp_obj_t pyb_timer_channel_pulse_width_percent(mp_uint_t n_args, const mp_obj_t *args) {
756771
pyb_timer_channel_obj_t *self = args[0];
757772
FTM_TypeDef *FTMx = self->timer->ftm.Instance;
758773
uint32_t period = (FTMx->MOD & 0xffff) + 1;
759774
if (n_args == 1) {
760775
// get
761776
uint32_t cmp = FTMx->channel[self->channel].CV & 0xffff;
762-
#if MICROPY_PY_BUILTINS_FLOAT
763-
return mp_obj_new_float((float)cmp * 100.0 / (float)period);
764-
#else
777+
#if MICROPY_PY_BUILTINS_FLOAT
778+
return mp_obj_new_float((float)cmp / (float)period * 100.0);
779+
#else
765780
return mp_obj_new_int(cmp * 100 / period);
766-
#endif
781+
#endif
767782
} else {
768783
// set
769-
uint32_t cmp;
770-
#if MICROPY_PY_BUILTINS_FLOAT
771-
if (MP_OBJ_IS_TYPE(args[1], &mp_type_float)) {
772-
cmp = mp_obj_get_float(args[1]) * period / 100.0;
773-
} else
774-
#endif
775-
{
776-
cmp = mp_obj_get_int(args[1]) * period / 100;
777-
}
778-
if (cmp < 0) {
779-
cmp = 0;
780-
} else if (cmp > period) {
781-
cmp = period;
782-
}
784+
uint32_t cmp = compute_pwm_value_from_percent(period, args[1]);
783785
FTMx->channel[self->channel].CV = cmp & 0xffff;
784786
return mp_const_none;
785787
}

0 commit comments

Comments
 (0)