Weird crash caused by PWM frequency change and Serial(?)

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();
}

You could also have a hardware problem as changing cables appears to trigger problems. Post an annotated schematic showing exactly how you have wired it. Include links to technical information on the hardware items.

I highly doubt that; I have repeated the above test more than a dozen times (with USB cable, without USB cable, with 20Hz and other divisors vs using regular analogWrite()), and it is very repeatable. However for what it's worth, here is the reference for the electronics and here is the pinout specification in the code.

What is the upper limit the units will take? 20KHz may be borderline. A slower frequency works, try something in between. You may also be at a resonate point and changing the frequency will reduce or eliminate the noise.

Thanks for posting the links, the assembly diagram looks nice but does not work for me.

According to the data sheet the DRV8871 h-bridge can handle frequencies up to 200kHz so 20kHz should be fine, however I've tried to go down to 5kHz. That defeats the purpose of making the whole thing inaudible, but leads to the same problem (with the same timing, I can discern no difference).

What sort of diagram do you need? Circuit diagram used in the PCB design? Can't upload unfortunately but I've put it here.

You should be able to drag an image file into a reply.

I can't - apparently not enough posts yet.

You can't attach but to my knowledge your should be able to drag-and-drop.

It needs to be an image file.

If you tried it, I'm probably mistaken.

Update - I've #ifdef'ed out my own code and replaced the functionality with GitHub - khoih-prog/SAMD_PWM: This library enables you to use Hardware-based PWM channels on SAMD21/SAMD51-based boards to create and output PWM to pins. Using the same functions as other FastPWM libraries to enable you to port PWM code easily between platforms.. Getting the same issue.

I don't know enough about it but it's POSSIBLE that the timers are shared by PWM and other timing/communication functions & libraries.

If everything works with the default PWM frequency that's very likely the problem.

They should not; according to the documentation and my understanding of grepping through the platform source code, the framework uses timers 0, 1, and 3. 4 and above should be free.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.