Skip to content

Commit 02a0939

Browse files
committed
Fix PWM Support for the MIMXRT boards
There were two main issues with the PWM support. The first is they would fail to work properly if the board goes into low power mode, when you do things like: time.sleep(0.25) Can make partially work with this by turning on the proper flags in each of the FlexPWMTimer Timers/sub-timers, but this did not appear to work if for example you have both A and B channels enabled. Second main problem is that the code did not work with the X channel of each timer/sub-timer. It looks like someone had earlier started support for this, But was not sufficient. Needed to bypass the SDK code and get it closer to the PJRC code. That is we set the PWM_CTRL_FULL_MASK, which then uses base->SM[submodule].VAL1 to control when the timer is reset, so it sets up your cycle/frequency. But then this implies that X channel which uses 0, 1 has to be handled specially. So for the different channels: A - Uses VAL2 to turn on (0) and VAL3=duty to turn off B - Uses VAL4 to turn on (0) and VAL5 to turn off X - As mentioned above VAL1 turns off, but its set to the timing for freqency. so VAL0 turns on, so we set it to VAL1 - duty
1 parent 7be66a5 commit 02a0939

File tree

2 files changed

+117
-11
lines changed

2 files changed

+117
-11
lines changed

ports/mimxrt10xx/common-hal/pwmio/PWMOut.c

Lines changed: 106 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@
3838
#include "supervisor/shared/translate.h"
3939
#include "periph.h"
4040

41+
// Debug print support set to none zero to enable debug printing
42+
#define ENABLE_DEBUG_PRINTING 0
43+
44+
4145
static void config_periph_pin(const mcu_pwm_obj_t *periph) {
4246
IOMUXC_SetPinMux(
4347
periph->pin->mux_reg, periph->mux_mode,
@@ -83,6 +87,32 @@ static int calculate_pulse_count(uint32_t frequency, uint8_t *prescaler) {
8387
return 0;
8488
}
8589

90+
// ==========================================================
91+
// Debug code
92+
// ==========================================================
93+
#if ENABLE_DEBUG_PRINTING
94+
#define DBGPrintf mp_printf
95+
extern void debug_print_flexpwm_registers(PWM_Type *base);
96+
97+
void debug_print_flexpwm_registers(PWM_Type *base) {
98+
mp_printf(&mp_plat_print,
99+
"\t\tPWM OUTEN:%x MASK:%x SWCOUT:%x DTSRCSEL:%x MCTRL:%x MCTRL2:%x FCTRL:%x FSTS:%x FFILT:%x FTST:%x FCTRL2:%x\n",
100+
base->OUTEN, base->MASK, base->SWCOUT, base->DTSRCSEL, base->MCTRL, base->MCTRL2, base->FCTRL,
101+
base->FSTS, base->FFILT, base->FTST, base->FCTRL2);
102+
for (uint8_t i = 0; i < 4; i++) {
103+
mp_printf(&mp_plat_print,
104+
"\t\t(%u) INIT:%x CTRL2:%x CTRL:%x VAL0:%x VAL1:%x VAL2:%x VAL3:%x VAL4:%x VAL5:%x OCTRL:%x DTCNT0:%x DTCNT1:%x\n", i,
105+
base->SM[i].INIT, base->SM[i].CTRL2, base->SM[i].CTRL, base->SM[i].VAL0, base->SM[i].VAL1, base->SM[i].VAL2,
106+
base->SM[i].VAL3, base->SM[i].VAL4, base->SM[i].VAL5, base->SM[i].OCTRL, base->SM[i].DTCNT0, base->SM[i].DTCNT1);
107+
}
108+
109+
}
110+
#else
111+
#define DBGPrintf(p,...)
112+
inline void debug_print_flexpwm_registers(PWM_Type *base) {
113+
}
114+
#endif
115+
86116
pwmout_result_t common_hal_pwmio_pwmout_construct(pwmio_pwmout_obj_t *self,
87117
const mcu_pin_obj_t *pin,
88118
uint16_t duty,
@@ -93,6 +123,9 @@ pwmout_result_t common_hal_pwmio_pwmout_construct(pwmio_pwmout_obj_t *self,
93123

94124
const uint32_t pwm_count = sizeof(mcu_pwm_list) / sizeof(mcu_pwm_obj_t);
95125

126+
DBGPrintf(&mp_plat_print, ">>> common_hal_pwmio_pwmout_construct called: pin: %p %u freq:%u duty:%u var:%u\n",
127+
self->pin->gpio, self->pin->number, frequency, duty, variable_frequency);
128+
96129
for (uint32_t i = 0; i < pwm_count; ++i) {
97130
if (mcu_pwm_list[i].pin != pin) {
98131
continue;
@@ -107,6 +140,8 @@ pwmout_result_t common_hal_pwmio_pwmout_construct(pwmio_pwmout_obj_t *self,
107140
return PWMOUT_INVALID_PIN;
108141
}
109142

143+
DBGPrintf(&mp_plat_print, "\tFound in PWM List\n");
144+
110145
config_periph_pin(self->pwm);
111146

112147
pwm_config_t pwmConfig;
@@ -138,33 +173,81 @@ pwmout_result_t common_hal_pwmio_pwmout_construct(pwmio_pwmout_obj_t *self,
138173

139174
pwmConfig.prescale = self->prescaler;
140175

176+
DBGPrintf(&mp_plat_print, "\tCall PWM_Init\n");
141177
if (PWM_Init(self->pwm->pwm, self->pwm->submodule, &pwmConfig) == kStatus_Fail) {
142178
return PWMOUT_INVALID_PIN;
143179
}
144180

181+
// Disable all fault inputs
182+
self->pwm->pwm->SM[self->pwm->submodule].DISMAP[0] = 0;
183+
self->pwm->pwm->SM[self->pwm->submodule].DISMAP[1] = 0;
184+
185+
DBGPrintf(&mp_plat_print, "\tCall PWM_SetupPwm %p %x %u\n", self->pwm->pwm, self->pwm->submodule);
186+
#if 0
187+
// Not calling the PWM_SetupPwm as it was setup to only work for PWM output on chan A and B but not X
188+
// I have done some experimenting, probably could try others, but again they do not work with X.
189+
// Most of the code checks to see if A if not, then it assume B.
145190
pwm_signal_param_t pwmSignal = {
146191
.pwmChannel = self->pwm->channel,
147192
.level = kPWM_HighTrue,
148193
.dutyCyclePercent = 0, // avoid an initial transient
149194
.deadtimeValue = 0, // allow 100% duty cycle
150195
};
151-
152-
// Disable all fault inputs
153-
self->pwm->pwm->SM[self->pwm->submodule].DISMAP[0] = 0;
154-
self->pwm->pwm->SM[self->pwm->submodule].DISMAP[1] = 0;
155-
156196
status_t status = PWM_SetupPwm(self->pwm->pwm, self->pwm->submodule, &pwmSignal, 1, kPWM_EdgeAligned, frequency, PWM_SRC_CLK_FREQ);
157197

158198
if (status != kStatus_Success) {
159199
return PWMOUT_INITIALIZATION_ERROR;
160200
}
201+
#else
202+
// ========================================================================================================
203+
// Instead I set it up to work similar to what the Teensy 4.x code does.
204+
//
205+
// That is we set the PWM_CTRL_FULL_MASK, which then uses base->SM[submodule].VAL1 to control
206+
// when the timer is reset, so it sets up your cycle/frequency. But then this implies that X channel
207+
// which uses 0, 1 has to be handled specially. So for the different channels:
208+
// A - Uses VAL2 to turn on (0) and VAL3=duty to turn off
209+
// B - Uses VAL4 to turn on (0) and VAL5 to turn off
210+
// X - As mentioned above VAL1 turns off, but its set to the timing for freqency. so
211+
// VAL0 turns on, so we set it to VAL1 - duty
212+
//
213+
PWM_Type *base = self->pwm->pwm;
214+
uint8_t submodule = self->pwm->submodule;
215+
216+
uint32_t mask = 1 << submodule;
217+
uint32_t olddiv = base->SM[submodule].VAL1 + 1;
218+
if (self->pulse_count != olddiv) {
219+
base->MCTRL |= PWM_MCTRL_CLDOK(mask);
220+
base->SM[submodule].CTRL = PWM_CTRL_PRSC_MASK | PWM_CTRL_PRSC(self->prescaler);
221+
base->SM[submodule].VAL1 = self->pulse_count - 1;
222+
base->SM[submodule].CTRL2 = PWM_CTRL2_INDEP_MASK | PWM_CTRL2_WAITEN_MASK | PWM_CTRL2_DBGEN_MASK;
223+
224+
if (olddiv == 1) {
225+
base->SM[submodule].CTRL = PWM_CTRL_FULL_MASK;
226+
base->SM[submodule].VAL0 = 0;
227+
base->SM[submodule].VAL2 = 0;
228+
base->SM[submodule].VAL3 = 0;
229+
base->SM[submodule].VAL4 = 0;
230+
base->SM[submodule].VAL5 = 0;
231+
} else {
232+
base->SM[submodule].VAL0 = (base->SM[submodule].VAL0 * self->pulse_count) / olddiv;
233+
base->SM[submodule].VAL3 = (base->SM[submodule].VAL3 * self->pulse_count) / olddiv;
234+
base->SM[submodule].VAL5 = (base->SM[submodule].VAL5 * self->pulse_count) / olddiv;
235+
}
236+
base->MCTRL |= PWM_MCTRL_LDOK(mask);
237+
}
238+
debug_print_flexpwm_registers(self->pwm->pwm);
239+
240+
#endif
241+
161242
PWM_SetPwmLdok(self->pwm->pwm, 1 << self->pwm->submodule, true);
162243

163244
PWM_StartTimer(self->pwm->pwm, 1 << self->pwm->submodule);
164245

165246

247+
DBGPrintf(&mp_plat_print, "\tCall common_hal_pwmio_pwmout_set_duty_cycle\n");
166248
common_hal_pwmio_pwmout_set_duty_cycle(self, duty);
167249

250+
DBGPrintf(&mp_plat_print, "\tReturn OK\n");
168251
return PWMOUT_OK;
169252
}
170253

@@ -185,26 +268,38 @@ void common_hal_pwmio_pwmout_set_duty_cycle(pwmio_pwmout_obj_t *self, uint16_t d
185268
// we do not use PWM_UpdatePwmDutycycle because ...
186269
// * it works in integer percents
187270
// * it can't set the "X" duty cycle
271+
// As mentioned in the setting up of the frequency code
272+
// A - Uses VAL2 to turn on (0) and VAL3=duty to turn off
273+
// B - Uses VAL4 to turn on (0) and VAL5 to turn off
274+
// X - As mentioned above VAL1 turns off, but its set to the timing for freqency. so
275+
// VAL0 turns on, so we set it to VAL1 - duty
276+
277+
DBGPrintf(&mp_plat_print, "common_hal_pwmio_pwmout_set_duty_cycle %u\n", duty);
188278
self->duty_cycle = duty;
279+
PWM_Type *base = self->pwm->pwm;
280+
uint8_t mask = 1 << self->pwm->submodule;
189281
if (duty == 65535) {
190282
self->duty_scaled = self->pulse_count + 1;
191283
} else {
192284
self->duty_scaled = ((uint32_t)duty * self->pulse_count + self->pulse_count / 2) / 65535;
193285
}
194286
switch (self->pwm->channel) {
195287
case kPWM_PwmX:
196-
self->pwm->pwm->SM[self->pwm->submodule].VAL0 = 0;
197-
self->pwm->pwm->SM[self->pwm->submodule].VAL1 = self->duty_scaled;
288+
base->SM[self->pwm->submodule].VAL0 = self->pulse_count - self->duty_scaled;
289+
base->OUTEN |= PWM_OUTEN_PWMX_EN(mask);
198290
break;
199291
case kPWM_PwmA:
200-
self->pwm->pwm->SM[self->pwm->submodule].VAL2 = 0;
201-
self->pwm->pwm->SM[self->pwm->submodule].VAL3 = self->duty_scaled;
292+
base->SM[self->pwm->submodule].VAL3 = self->duty_scaled;
293+
base->OUTEN |= PWM_OUTEN_PWMA_EN(mask);
202294
break;
203295
case kPWM_PwmB:
204-
self->pwm->pwm->SM[self->pwm->submodule].VAL4 = 0;
205-
self->pwm->pwm->SM[self->pwm->submodule].VAL5 = self->duty_scaled;
296+
base->SM[self->pwm->submodule].VAL5 = self->duty_scaled;
297+
base->OUTEN |= PWM_OUTEN_PWMB_EN(mask);
206298
}
207299
PWM_SetPwmLdok(self->pwm->pwm, 1 << self->pwm->submodule, true);
300+
301+
debug_print_flexpwm_registers(self->pwm->pwm);
302+
208303
}
209304

210305
uint16_t common_hal_pwmio_pwmout_get_duty_cycle(pwmio_pwmout_obj_t *self) {

ports/mimxrt10xx/supervisor/port.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,13 @@ void port_interrupt_after_ticks(uint32_t ticks) {
398398

399399
void port_idle_until_interrupt(void) {
400400
// App note here: https://www.nxp.com/docs/en/application-note/AN12085.pdf
401+
// Currently I have disabled the setting into wait mode as this impacts lots of differnt
402+
// subsystems and it is unclear if you can or should set it generically without having
403+
// a better understanding of user intent. For example by default it will kill PWM
404+
// when in this mode, unless PWM_CTRL2_WAITEN_MASK is set, and even with this set
405+
// it may not work properly if the same timer/subtimer is trying to PWM on multiple channels.
406+
// Maybe at later date, revisit after we have a better understanding on things like which
407+
// timers it impacts and how each subsystem is configured.
401408

402409
// Clear the FPU interrupt because it can prevent us from sleeping.
403410
if (__get_FPSCR() & ~(0x9f)) {
@@ -410,11 +417,15 @@ void port_idle_until_interrupt(void) {
410417
NVIC_ClearPendingIRQ(SNVS_HP_WRAPPER_IRQn);
411418
// Don't down clock on debug builds because it prevents the DAP from
412419
// reading memory
420+
#if 0
413421
#if CIRCUITPY_DEBUG == 0
414422
CLOCK_SetMode(kCLOCK_ModeWait);
415423
#endif
416424
__WFI();
417425
CLOCK_SetMode(kCLOCK_ModeRun);
426+
#else
427+
__WFI();
428+
#endif
418429
}
419430
common_hal_mcu_enable_interrupts();
420431
}

0 commit comments

Comments
 (0)