I'm working on linear actuator using a stepper motor, i found some code on here from a previous post allowing me to create a accurate PWM by setting a time TCC0. The motor control is all handled by a motor driver so all i have to send it is the PWM signal.
REG_GCLK_GENDIV = GCLK_GENDIV_DIV(1) | // Divide the 48MHz clock source by divisor 1: 48MHz/1=48MHz
GCLK_GENDIV_ID(4); // Select Generic Clock (GCLK) 4
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
REG_GCLK_GENCTRL = GCLK_GENCTRL_IDC | // Set the duty cycle to 50/50 HIGH/LOW
GCLK_GENCTRL_GENEN | // Enable GCLK4
GCLK_GENCTRL_SRC_DFLL48M | // Set the 48MHz clock source
GCLK_GENCTRL_ID(4); // Select GCLK4
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
// Enable the port multiplexer for the digital pin D7
PORT->Group[g_APinDescription[7].ulPort].PINCFG[g_APinDescription[7].ulPin].bit.PMUXEN = 1;
// Connect the TCC0 timer to digital output D7 - port pins are paired odd PMUO and even PMUXE
// F & E specify the timers: TCC0, TCC1 and TCC2
PORT->Group[g_APinDescription[6].ulPort].PMUX[g_APinDescription[6].ulPin >> 1].reg = PORT_PMUX_PMUXO_F;
// Wait for synchronization
// Feed GCLK4 to TCC0 and TCC1
REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN | // Enable GCLK4 to TCC0 and TCC1
GCLK_CLKCTRL_GEN_GCLK4 | // Select GCLK4
GCLK_CLKCTRL_ID_TCC0_TCC1; // Feed GCLK4 to TCC0 and TCC1
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
// Dual slope PWM operation: timers countinuously count up to PER register value then down 0
REG_TCC0_WAVE |= TCC_WAVE_POL(0xF) | // Reverse the output polarity on all TCC0 outputs
TCC_WAVE_WAVEGEN_DSBOTH; // Setup dual slope PWM on TCC0
while (TCC0->SYNCBUSY.bit.WAVE); // Wait for synchronization
// Each timer counts up to a maximum or TOP value set by the PER register,
// this determines the frequency of the PWM operation: (96)
REG_TCC0_PER = 96; // Set the frequency of the PWM on TCC0 to 250kHz
while (TCC0->SYNCBUSY.bit.PER); // Wait for synchronization
// Set the PWM signal to output 50% duty cycle
REG_TCC0_CC3 = 48; // TCC0 CC3 - on D7
while (TCC0->SYNCBUSY.bit.CC3); // Wait for synchronization
// Divide the 48MHz signal by 1 giving 48MHz (20.83ns) TCC0 timer tick and enable the outputs
REG_TCC0_CTRLA |= TCC_CTRLA_PRESCALER_DIV1 | // Divide GCLK4 by 1
TCC_CTRLA_ENABLE; // Enable the TCC0 output
while (TCC0->SYNCBUSY.bit.ENABLE); // Wait for synchronization
On my motor, 1 pulse correlates to 1.8 degrees so 360/1.8 = 200 steps for a full rotation.
My question is what would be the best way to count the pulses sent from the TCC0?
Should i be using interrupts or is there another timer i could step up ?
What PWM frequency are you looking to drive the motor driver? The code example provided is runnng at 250kHz, which is high with respect to the microcontroller's 48MHz clock, this lowers the PWM's resolution, in this case as low as 96 steps, which isn't enough for your 200 steps requirement. A lower PWM frequency would allow you to have a higher resolution.
In order to count the number of PWM pulses it's probably easier to use single slope, rather than dual slope PWM. It's possible to trigger an interrupt using dual slope PWM, but as the timer cycle counts up to PER (TOP) then down to ZERO, an interrupt will occur when the timer COUNT matches the CCx register. This occurs both when the timer's counting up and on the way down. That means you'll get two interrupts every timer cycle, on the rising and falling edge of the output pulse.
Using single slope PWM the timer only counts up, then returns to ZERO when the timer reaches PER (TOP). As the output pulse starts at the beginning of every timer cycle, this means you can trigger an interrupt to count the number of PWM pulses every time the counter overflows, (every time it reaches the PER value and is reset to ZERO). Here's some example code:
// Output 1kHz single slope PWM on timer TCC0
volatile unsigned long count;
void setup()
{
REG_GCLK_GENDIV = GCLK_GENDIV_DIV(1) | // Divide the 48MHz clock source by divisor 1: 48MHz/1=48MHz
GCLK_GENDIV_ID(4); // Select Generic Clock (GCLK) 4
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
REG_GCLK_GENCTRL = GCLK_GENCTRL_IDC | // Set the duty cycle to 50/50 HIGH/LOW
GCLK_GENCTRL_GENEN | // Enable GCLK4
GCLK_GENCTRL_SRC_DFLL48M | // Set the 48MHz clock source
GCLK_GENCTRL_ID(4); // Select GCLK4
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
// Enable the port multiplexer for the digital pins D7
PORT->Group[g_APinDescription[7].ulPort].PINCFG[g_APinDescription[7].ulPin].bit.PMUXEN = 1;
// Connect the TCC0 timer to the port output D7 - port pins are paired odd PMUXO and even PMUXE
// F & E specify the timers: TCC0, TCC1 and TCC2
PORT->Group[g_APinDescription[6].ulPort].PMUX[g_APinDescription[6].ulPin >> 1].reg = PORT_PMUX_PMUXO_F; //| PORT_PMUX_PMUXE_F;
// Feed GCLK4 to TCC0 and TCC1
REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN | // Enable GCLK4 to TCC0 and TCC1
GCLK_CLKCTRL_GEN_GCLK4 | // Select GCLK4
GCLK_CLKCTRL_ID_TCC0_TCC1; // Feed GCLK4 to TCC0 and TCC1
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
// Normal (single slope) PWM operation: timers countinuously count up to PER register value and then is reset to 0
REG_TCC0_WAVE |= TCC_WAVE_WAVEGEN_NPWM; // Setup single slope PWM on TCC0
while (TCC0->SYNCBUSY.bit.WAVE); // Wait for synchronization
// Each timer counts up to a maximum or TOP value set by the PER register,
// this determines the frequency of the PWM operation:
REG_TCC0_PER = 45714; // Set the frequency of the PWM on TCC0 to 1kHz
while(TCC0->SYNCBUSY.bit.PER); // Wait for synchronization
// Set the PWM signal to output 50% duty cycle on D7
REG_TCC0_CCB3 = 22857; // TCC0 CCB3 - on output on D7 50% duty cycle
while(TCC0->SYNCBUSY.bit.CCB3); // Wait for synchronization
//NVIC_DisableIRQ(TCC0_IRQn);
//NVIC_ClearPendingIRQ(TCC0_IRQn);
NVIC_SetPriority(TCC0_IRQn, 0); // Set the Nested Vector Interrupt Controller (NVIC) priority for TCC0 to 0 (highest)
NVIC_EnableIRQ(TCC0_IRQn); // Connect TCC0 to Nested Vector Interrupt Controller (NVIC)
REG_TCC0_INTFLAG |= TC_INTFLAG_OVF; // Clear the overflow interrupt flag
REG_TCC0_INTENSET = TC_INTENSET_OVF; // Enable TCC0 overflow interrupt
// Divide the 48MHz signal by 1 giving 48MHz (20.83ns) TCC0 timer tick and enable the outputs
REG_TCC0_CTRLA |= TCC_CTRLA_PRESCALER_DIV1 | // Divide GCLK4 by 1
TCC_CTRLA_ENABLE; // Enable the TCC0 output
while (TCC0->SYNCBUSY.bit.ENABLE); // Wait for synchronization
}
void loop() {}
// Ineterrupt handler called every time the TCC0 timer overflows
void TCC0_Handler() // Interrupt Service Routine (ISR) for timer TCC0
{
// Check for overflow (OVF) interrupt
if (TCC0->INTFLAG.bit.OVF && TCC0->INTENSET.bit.OVF)
{
// Put your timer overflow (OVF) code here:
// ...
count++;
REG_TCC0_INTFLAG = TC_INTFLAG_OVF; // Clear the OVF interrupt flag
}
}
Note that in order to read the count variable in the loop() section, you'll need to temporarily disable interrupts using the Arduino interrupts() and noInterrupts() functions.
void loop() {
noInterrupts(); // Switch off interrupts
unsigned long readCount = count; // Read the count value
interrupts(); // Switch on interrupts
}
Also note that using single slope PWM means doubling the values of the PER and CCB3 registers, in order to maintain the PWM frequency of 1kHz.
if i have to rewrite the Frequency and duty cycle somewhere else in the code, (for example increasing speed or stopping the motor). Am i right in thinking i should just directly rewrite like this ?
You may want to look at the Nano Zero Stepper, it has motor drive plus feedback for stepper motor angle allowing the stepper motor to become more like servo. The design is all open source and can be found here: Nano Zero Stepper
if i have to rewrite the Frequency and duty cycle somewhere else in the code, (for example increasing speed or stopping the motor). Am i right in thinking i should just directly rewrite like this ?
Yes, although for the period it's also possible to use the buffered period or PERB register. The buffered PERB register loads the new period into the PER register at the beginning of a new timer cycle, that way the changes to the PWM frequency won't cause glitches on your output waveform: