Slow start SAMD21turboPWM

Hi

I am running this on a nano 33 IOT/ SAMD21.

Using SAMD21turboPWM.h to control PWM for two steppermotor drivers.

Its two steppermotor drivers running at independant frequencies.
(also start/stop and changing the frequency).

Problem is the PWM waveform does not start until about 18 sec. after start.
I can see the comments "Serial int..", "Debug".. right away.
After the slow start, its all working OK, start/stop, changing frequency.

#include <SAMD21turboPWM.h>

#define STEP 8

TurboPWM pwm;


void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);  

  for (int i= 0; i<= 5; i++) {
    delay(1000); 
    Serial.print("Serial init sec");
    Serial.println(i);
  }
 
  Serial.println("Debug");

  pinMode(STEP, OUTPUT);
  digitalWrite(STEP, LOW);

  pwm.setClockDivider(1, false); // Main clock 48 MHz divided by 1 => 48 MHz
  
  pwm.timer(0, 64, 14999, true); 

  pwm.analogWrite(STEP, 0); // 0/1000 dutycycle 
  pwm.analogWrite(STEP2, 0); // 0/1000 dutycycle
  

  // 
  analogReadResolution(12); 
    
  Serial.println("Debug after begin");

delay(1000);
  
}


void loop() {


  pwm.timer(0, 64, 14999, true); // 

  digitalWrite(DIR, LOW);
  digitalWrite(SLEEP, HIGH); // NOT sleep
  delay(2);
    
  pwm.analogWrite(STEP, 100); // start puls
    
  do {
    delay(1);       
    Serial.println(analogRead(A4)); // check what happens
} while (1);

Could the problem be related to this:

Changing Arduino Zero PWM Frequency - Hardware / Arduino Zero - Arduino Forum

” For some reason initialising the PWM using the buffered PERB/WAVEB registers causes a delay”

I can see PERB is being used in the library. But I am unfortunately missing the knowledge to understand this in deep?

Hi @NoX20

The TCC timers' buffered period or PERB register is used to change the output waveform's frequency during a timer update (overflow). This prevents changes in frequency from causing momentary glitches at the output, since the change always occurs at the beginning of the timer cycle.

Changes to the unbuffered PER register by contrast, occur at the output immediately.

If the use of unbuffered registers is acceptable, you could just change the PERB lines to PER (just removing the B) in the library and see if this improves your start-up time.

What I need is to control two steppermotors. Varying the PWM frequency independent from each other, from around 20 Hz up to around 320-640 Hz. Being able to stop them (DC= 0).

I tried using the turbo_PWM library, but was getting a little anoid by the long start up time of around 18 sec. Else the library seems to be working OK.

So I decided to shift from using the turbo_PWM library, and instead to set up things directly via the registers. Lot of coding ex. and info of doing so, in the forum, especially thanks to you MartinL.

But it seems the problem also accurse when using the registers directly. Only way to be sure it works is by using PERB (and CCB?). If not using PERB it will start right away, but making different errors after some sec/ stopping PWM (all 0/1 in). then sometimes start again.

The sketch is shifting between 3 frequencies, reading the result in on digitalread 14.

All the // is from the different things I tried when testing.

// declare variables **********************************************

#define DIR 5 // 
#define STEP 8 // 
#define SLEEP 6 // 
#define DIR2 2
#define STEP2 7 //
#define SLEEP2 3
#define SD_CS 10

unsigned long testTime;
int testState;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);  

  for (int i= 0; i<= 5; i++) {
    delay(1000); 
    Serial.print("Serial init sec");
    Serial.println(i);
  }
 
  Serial.println("Debug");

  
  pinMode(DIR2, OUTPUT);
  digitalWrite(DIR2, LOW);
  pinMode(STEP2, OUTPUT);
  digitalWrite(STEP2, LOW);
  pinMode(SLEEP2, OUTPUT);
  digitalWrite(SLEEP2, LOW); // sleep
  
  pinMode(DIR, OUTPUT);
  digitalWrite(DIR, LOW); // 
  pinMode(STEP, OUTPUT);
  digitalWrite(STEP, LOW);
  pinMode(SLEEP, OUTPUT);
  digitalWrite(SLEEP, LOW); // sleep
    
// Enable and configure generic clock generator 4
  GCLK->GENCTRL.reg = GCLK_GENCTRL_IDC |          // Improve duty cycle
                      GCLK_GENCTRL_GENEN |        // Enable generic clock gen
                      GCLK_GENCTRL_SRC_DFLL48M |  // Select 48MHz as source
                      GCLK_GENCTRL_ID(4);         // Select GCLK4
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

// Set clock divider of 1 to generic clock generator 4
  GCLK->GENDIV.reg = GCLK_GENDIV_DIV(1) |         // Divide 48 MHz by 1
                     GCLK_GENDIV_ID(4);           // Apply to GCLK4 4
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

  // Enable GCLK4 and connect it to TCC0 and TCC1
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |        // Enable generic clock
                      GCLK_CLKCTRL_GEN_GCLK4 |    // Select GCLK4
                      GCLK_CLKCTRL_ID_TCC0_TCC1;  // Feed GCLK4 to TCC0/1
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

  // Divide counter by 64 giving ..
  TCC0->CTRLA.reg |= TCC_CTRLA_PRESCALER(TCC_CTRLA_PRESCALER_DIV64_Val);

  // Use "Normal PWM" (single-slope PWM): count up to PER, match on CC[n]
  TCC0->WAVE.reg = TCC_WAVE_WAVEGEN_NPWM;         // Select NPWM as waveform
  while (TCC0->SYNCBUSY.bit.WAVE);                // Wait for synchronization

  // Set the period (the number to count to (TOP) before resetting timer)
  TCC0->PER.reg = 6000;
  while (TCC0->SYNCBUSY.bit.PER);
  //TCC0->PERB.reg = 6000;
  //while (TCC0->SYNCBUSY.bit.PERB);
  
  // Set PWM signal to output 50% duty cycle
  // n for CC[n] is determined by n = x % 4 where x is from WO[x]
  //TCC0->CC[2].reg = 6000 / 2;
  //while (TCC0->SYNCBUSY.bit.CC2);
  TCC0->CCB[2].reg = 6000 / 2;
  while (TCC0->SYNCBUSY.bit.CCB2);

  // Configure PA18 ( ) to be output
  PORT->Group[PORTA].DIRSET.reg = PORT_PA18;      // Set pin as output
  PORT->Group[PORTA].OUTCLR.reg = PORT_PA18;      // Set pin to low

  // Enable the port multiplexer for PA18
  PORT->Group[PORTA].PINCFG[18].reg |= PORT_PINCFG_PMUXEN;

  // Connect TCC0 timer to PA18. Function F is TCC0/WO[2] for PA18.
  // Odd pin num (2*n + 1): use PMUXO
  // Even pin num (2*n): use PMUXE
  PORT->Group[PORTA].PMUX[9].reg = PORT_PMUX_PMUXE_F;

  // Enable output (start PWM)
  TCC0->CTRLA.reg |= (TCC_CTRLA_ENABLE);
  while (TCC0->SYNCBUSY.bit.ENABLE);        
  
    
  analogReadResolution(12);
    
  Serial.println("Debug  2");

delay(1000);

testState= 0;
testTime= millis()+500;

Serial.println("start state 0 f= 124");

}

void loop() {
  // put your main code here, to run repeatedly:

// 14999 = 50 Hz
// 7499 = 100 Hz

switch(testState) {
  case 0: // running 124 Hz, shifting to 62
    
    if (millis()> testTime) {
      testTime= millis()+500;
      testState++;
      Serial.println("skift til state 1, frekvens 62");
      
      TCC0->PER.reg = 12000;
      while (TCC0->SYNCBUSY.bit.PER);
      //TCC0->PERB.reg = 12000;
      //while (TCC0->SYNCBUSY.bit.PERB);
    
      // Set PWM signal to output 50% duty cycle
      // n for CC[n] is determined by n = x % 4 where x is from WO[x]
      //TCC0->CC[2].reg = 12000 / 2;
      //while (TCC0->SYNCBUSY.bit.CC2);
      TCC0->CCB[2].reg = 12000 / 2;
      while (TCC0->SYNCBUSY.bit.CCB2);
  
      // Enable output (start PWM)
      //TCC0->CTRLA.reg |= (TCC_CTRLA_ENABLE);
      //while (TCC0->SYNCBUSY.bit.ENABLE);        
      
    }

    
  break;
  case 1: // running 62 Hz, skift til 42

    if (millis()> testTime) {
      testTime= millis()+500;
      testState= 2;
      Serial.println("skift til state 2, frekevns 42");


//TCC0->CTRLBSET.reg = TCC_CTRLBSET_LUPD;         // Set the Lock Update bit
//  while (TCC0->SYNCBUSY.bit.CTRLB);               // Wait for synchronization

  
      TCC0->PER.reg = 18000;
      while (TCC0->SYNCBUSY.bit.PER);
      //TCC0->PERB.reg = 18000;
      //while (TCC0->SYNCBUSY.bit.PERB);
    
      // Set PWM signal to output 50% duty cycle
      // n for CC[n] is determined by n = x % 4 where x is from WO[x]
      //TCC0->CC[2].reg = 18000 / 2;
      //while (TCC0->SYNCBUSY.bit.CC2);
      TCC0->CCB[2].reg = 18000 / 2;
      while (TCC0->SYNCBUSY.bit.CCB2);
      
//TCC0->CTRLBCLR.reg = TCC_CTRLBCLR_LUPD;         // Clear the Lock Update bit
//  while (TCC0->SYNCBUSY.bit.CTRLB);               // Wait for synchronization
  
      // Enable output (start PWM)
      //TCC0->CTRLA.reg |= (TCC_CTRLA_ENABLE);
      //while (TCC0->SYNCBUSY.bit.ENABLE);        
      
    }

    
  break;
  case 2: // running 42 Hz, skift til 124

    if (millis()> testTime) {
      testTime= millis()+500;
      testState= 0;
      Serial.println("skift til state 0, frekevns 124");


//TCC0->CTRLBSET.reg = TCC_CTRLBSET_LUPD;         // Set the Lock Update bit
//  while (TCC0->SYNCBUSY.bit.CTRLB);               // Wait for synchronization

  
      TCC0->PER.reg = 6000;
      while (TCC0->SYNCBUSY.bit.PER);
      //TCC0->PERB.reg = 6000;
      //while (TCC0->SYNCBUSY.bit.PERB);
    
      // Set PWM signal to output 50% duty cycle
      // n for CC[n] is determined by n = x % 4 where x is from WO[x]
      //TCC0->CC[2].reg = 6000 / 2;
      //while (TCC0->SYNCBUSY.bit.CC2);
      TCC0->CCB[2].reg = 6000 / 2;
      while (TCC0->SYNCBUSY.bit.CCB2);
      
//TCC0->CTRLBCLR.reg = TCC_CTRLBCLR_LUPD;         // Clear the Lock Update bit
//  while (TCC0->SYNCBUSY.bit.CTRLB);               // Wait for synchronization
  
      // Enable output (start PWM)
      //TCC0->CTRLA.reg |= (TCC_CTRLA_ENABLE);
      //while (TCC0->SYNCBUSY.bit.ENABLE);        
      
    }

    
  break;
     
}

delay(2);
Serial.println(digitalRead(14)); // reading the PWM in again
  
}

Hi @NoX20

Here's an example of generating a 50% duty-cycle frequency sweep on the Nano 33 IoT's digital pin D6 from 20Hz to 640Hz then back again to 20Hz, followed by 0% duty-cycle for 1 second then repeat:

// Setup TCC0 and TCC1 for 50% duty-cycle, variable frequency (20Hz-640Hz) on digital pins D6
void setup() 
{ 
  GCLK->GENDIV.reg = GCLK_GENDIV_DIV(3) |          // Divide the 48MHz signal by 3: 48MHz/3 = 16MHz
                     GCLK_GENDIV_ID(4);            // Set division on Generic Clock Generator (GCLK) 4
  
  GCLK->GENCTRL.reg = //GCLK_GENCTRL_OE |            // Enable the GCLK output
                      GCLK_GENCTRL_IDC |           // Set the duty cycle to 50/50 HIGH/LOW
                      GCLK_GENCTRL_GENEN |         // Enable GCLK 4
                      GCLK_GENCTRL_SRC_DFLL48M |   // Set the clock source to 48MHz DFLL
                      GCLK_GENCTRL_ID(4);          // Set clock source on GCLK 4
  while (GCLK->STATUS.bit.SYNCBUSY);               // Wait for synchronization

  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |         // Route GCLK4 to TCC0 and TCC1
                      GCLK_CLKCTRL_GEN_GCLK4 |     
                      GCLK_CLKCTRL_ID_TCC0_TCC1;   
  
  // Enable the port multiplexer for pin D6
  PORT->Group[g_APinDescription[6].ulPort].PINCFG[g_APinDescription[6].ulPin].bit.PMUXEN = 1;
  //PORT->Group[g_APinDescription[A3].ulPort].PINCFG[g_APinDescription[A3].ulPin].bit.PMUXEN = 1;
  
  // D6 is on EVEN port pin PA04, both on multiplexer switch E
  PORT->Group[g_APinDescription[6].ulPort].PMUX[g_APinDescription[6].ulPin >> 1].reg |= /*PORT_PMUX_PMUXO_E |*/ PORT_PMUX_PMUXE_E;
  //PORT->Group[g_APinDescription[A4].ulPort].PMUX[g_APinDescription[A4].ulPin >> 1].reg |= /*PORT_PMUX_PMUXO_E |*/ PORT_PMUX_PMUXE_E;                   
  
  TCC0->WAVE.reg = TCC_WAVE_WAVEGEN_MFRQ;          // Enable match frequency MFRQ mode
  while (TCC0->SYNCBUSY.bit.WAVE);                 // Wait for synchronization
  
  TCC0->CTRLA.reg = TCC_CTRLA_PRESCSYNC_PRESC |    // Reload timer on the next prescaler clock
                    TCC_CTRLA_PRESCALER_DIV8;      // Set prescaler to 16, 16MHz/8 = 2Mhz
               
  TCC0->CTRLA.bit.ENABLE = 1;                      // Enable TCC0
  while (TCC0->SYNCBUSY.bit.ENABLE);               // Wait for synchronization
}

void loop() 
{
  for (uint32_t frequency = 20; frequency < 641; frequency++)       // Sweep PWM frequency up
  {
    setMotorFrequency(frequency);
    delay(10);
  }
  for (uint32_t frequency = 639; frequency >= 20; frequency--)      // Sweep PWM frequency down
  {
    setMotorFrequency(frequency);
    delay(10);
  }
  stopMotor();
  delay(1000);
  startMotor();
}

void setMotorFrequency(uint32_t frequency)
{
  TCC0->CCB[0].reg = 2000000ul / (2 * frequency) - 1;     // Set the PWM frequency
  while (TCC0->SYNCBUSY.bit.CCB0);                        // Wait for synchronization
}

void startMotor()
{
  TCC0->PATTB.bit.PGEB0 = 0;                              // Set the output to the stepper frequency on the next timer cycle
  while (TCC0->SYNCBUSY.bit.PATTB);                       // Wait for synchronization
}

void stopMotor()
{
  TCC0->PATTB.bit.PGEB0 = 1;                              // Set the output to 0V on the next timer cycle
  while (TCC0->SYNCBUSY.bit.PATTB);                       // Wait for synchronization
}

The code uses the TCC0 timer in Match Frequency MFRQ mode, where the frequency is set by the CC0 register. The output simply toggles the output each time the timer's COUNT reaches the CC0 register's value, thereby producing a signal with a 50% duty-cycle.

The sketch employs the buffered CCB0 register during operation, which updates CC0 at the start of the timer cycle, to prevent glitches from occuring on the output whenever it's updated.

The duty-cycle is set to 0% using the buffered patter register PATTB, which again updates the output only at the start of the next timer cycle.

A second stepper motor will require the same code for timer TCC1.

Thanks Martin, my ” Serial.println(digitalRead(14));” –oscilloscope shows its starting right away, and keeps running correct.

Only issue, I had to change to PORT_PMUX_PMUXE_F. Is it as simply as this (after looking at some of your other post);

TCC0 is PORT_PMUX_PMUXE_F; (or PORT_PMUX_PMUXO_F)

TCC1 is PORT_PMUX_PMUXE_E; (or PORT_PMUX_PMUXO_E)

TCC2

Hi @NoX20

To determine which SAMD21 pin uses which timer, I use the Nano 33 IoT schematic to convert from Arduino pin nomenclature (D0, D1, D2, etc...), to the microcontroller's port pins (PA00, PA01, PA02, etc...):

Then reference the SAMD21 datasheet in section 7 that contains the I/O Multiplexing table with port pin functions for multiplex switch positions: A through to H.

Each adjacent port pin odd and even pair (PA00 and PA01, PA02 and PA03, etc...) share a PMUX register that sets the multiplexer switches for each pin, hence the fact that they can be both be set using a single line of code, specifying odd or even (O or E) and the multiplexer switch (A to H), for example:

PORT->Group[g_APinDescription[6].ulPort].PMUX[g_APinDescription[6].ulPin >> 1].reg = PORT_PMUX_PMUXO_E | PORT_PMUX_PMUXE_E;

Note that in the table the TCC0 timer's four channels: waveform outputs WO[0] to WO[3], are repeated on WO[4] to WO[7] respectively.

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