All, I need your help, this is driving me nuts.
I've set up a differential-driven robot with 20kHz PWM to get rid of the 735Hz whine, mostly following info on this forum by @MartinL and this excellent blog post by pointsinfocus.com. The problem I have is as follows:
- If I start the robot with the USB cable connected, even if only very shortly and still disconnect during startup, everything works as it should.
- If I start the robot with the USB cable disconnected, it runs through the selftest routine, even including running the motors using the 20kHz PWM, but as soon as I start giving drive commands the MKR Wifi crashes, to the point of not being recognized on USB anymore until a reset.
- This only happens with the custom timer setup code in place. If I leave everything at its default 735Hz, nothing crashes ever.
My first idea was a brownout (USB providing voltage), but it's happening even if the USB cable is removed at the very beginning, and also oscilloscope says 3.3V is rock solid stable all the time. Now my idea is that if the USB cable is connected while the Serial port is initialized, the Serial code does something which makes the timer setup work / not crash, and if it isn't it doesn't.
My full code is here and around it. Unfortunately it's quite involved by now, so I'm replicating the setup and duty cycle part here (P_LEFT_PWMA is pin 18, P_LEFT_PWMB 19, P_RIGHT_PWMA 3, P_RIGHT_PWMB 2):
static uint16_t pwmPeriod;
static void configureTimers() {
REG_GCLK_GENDIV = GCLK_GENDIV_DIV(1) | GCLK_GENDIV_ID(4);
while(GCLK->STATUS.bit.SYNCBUSY);
// Set the clock source, duty cycle, and enable GCLK4
REG_GCLK_GENCTRL = GCLK_GENCTRL_SRC_DFLL48M | // Set 48MHz source
GCLK_GENCTRL_IDC | // Improve Duty Cycle
GCLK_GENCTRL_GENEN | // Enable GCLK
GCLK_GENCTRL_ID(4); // For GLCK4
while(GCLK->STATUS.bit.SYNCBUSY);
// Route GLCK5 to TCC0 & TCC1, and enable the clock.
REG_GCLK_CLKCTRL = GCLK_CLKCTRL_ID_TCC0_TCC1 | // Route GCLK5 to TCC0 & 1
GCLK_CLKCTRL_CLKEN | // Enable the clock
GCLK_CLKCTRL_GEN_GCLK4; // Select GCLK4
while(GCLK->STATUS.bit.SYNCBUSY);
// Route to multiplexer...
PORT->Group[g_APinDescription[P_LEFT_PWMA].ulPort].PINCFG[g_APinDescription[P_LEFT_PWMA].ulPin].bit.PMUXEN = 1;
PORT->Group[g_APinDescription[P_LEFT_PWMB].ulPort].PINCFG[g_APinDescription[P_LEFT_PWMB].ulPin].bit.PMUXEN = 1;
PORT->Group[g_APinDescription[P_RIGHT_PWMA].ulPort].PINCFG[g_APinDescription[P_RIGHT_PWMA].ulPin].bit.PMUXEN = 1;
PORT->Group[g_APinDescription[P_RIGHT_PWMB].ulPort].PINCFG[g_APinDescription[P_RIGHT_PWMB].ulPin].bit.PMUXEN = 1;
// ...and in the MUX to peripheral function F. Here comes the messy bit (data sheet page 30, table 7.1)
// LEFT_PWMA is Pin 18 -> PA04, which gets TCC0/WO[0] in port function E
PORT->Group[g_APinDescription[P_LEFT_PWMA].ulPort].PMUX[g_APinDescription[P_LEFT_PWMA].ulPin >> 1].reg |= PORT_PMUX_PMUXE_E;
// LEFT_PWMB is Pin 19 -> PA05, which gets TCC0/WO[1] in port function E
PORT->Group[g_APinDescription[P_LEFT_PWMB].ulPort].PMUX[g_APinDescription[P_LEFT_PWMB].ulPin >> 1].reg |= PORT_PMUX_PMUXO_E;
// RIGHT_PWMA is Pin 3 -> PA11, which gets TCC0/WO[3] in port function F
PORT->Group[g_APinDescription[P_RIGHT_PWMA].ulPort].PMUX[g_APinDescription[P_RIGHT_PWMA].ulPin >> 1].reg |= PORT_PMUX_PMUXE_F;
// RIGHT_PWMB is Pin 2 -> PA10, which gets TCC0/WO[2] in port function F
PORT->Group[g_APinDescription[P_RIGHT_PWMB].ulPort].PMUX[g_APinDescription[P_RIGHT_PWMB].ulPin >> 1].reg |= PORT_PMUX_PMUXO_F;
int prescaler = 1;
double fPWM = 20000;
double fBus = 48000000;
pwmPeriod = int(fBus / (prescaler*fPWM))-1;
REG_TCC0_WAVE |= TCC_WAVE_WAVEGEN_NPWM;
REG_TCC0_PER = pwmPeriod;
REG_TCC0_CTRLA |= TCC_CTRLA_PRESCALER_DIV1 | TCC_CTRLA_ENABLE; // Requires SYNC on CTRLA
while(TCC0->SYNCBUSY.bit.ENABLE || TCC0->SYNCBUSY.bit.WAVE || TCC0->SYNCBUSY.bit.PER);
}
static void customAnalogWrite(uint8_t pin, uint8_t dutycycle) {
uint16_t dc = map(dutycycle, 0, 255, 0, pwmPeriod);
noInterrupts();
switch(pin) {
case P_LEFT_PWMA: // WO[0] -> CCB0
REG_TCC0_CCB0 = dc;
while(TCC0->SYNCBUSY.bit.CCB0);
break;
case P_LEFT_PWMB: // WO[1] -> CCB1
REG_TCC0_CCB1 = dc;
while(TCC0->SYNCBUSY.bit.CCB1);
break;
case P_RIGHT_PWMA: // WO[3] -> CCB3
REG_TCC0_CCB3 = dc;
while(TCC0->SYNCBUSY.bit.CCB3);
break;
case P_RIGHT_PWMB: // WO[2] -> CCB2
REG_TCC0_CCB2 = dc;
while(TCC0->SYNCBUSY.bit.CCB2);
break;
default:
//bb::printf("ERROR - Unknown pin %d in customAnalogWrite()\n", pin);
break;
}
interrupts();
}