REG_TCC0_CTRLA |= (1 << (12%32));//set PRESYNC[1:0] to 0x1 to Reload or reset the counter on next prescaler clock (PRESC).
REG_TCC0_CC0 = 468741;//Reload or reset the counter on next prescaler clock thus TOP(1Hz) = ((4810^6)/(11024))-1 = 468741 (TOP=f_clock/(f_PWM×Prescaler)-1).
REG_TCC0_INTENSET |= (1 << (0x00));//Enable Overflow/Underflow interrupt by writing bit 0 (OVF) to 1
NVIC_EnableIRQ(TCC0_IRQn);//Enable TCC0 Interrupt
REG_TCC0_CTRLA |= (1 << (1%32));//Enable and start the timer
while (1)
{
/wacht op een interrupt/
//REG_PORT_OUTTGL0 |= (1 << (17%32));//Toggle Pin 13
}
}
SAMD21 and SAM3X belongs to different families: even if both are CPU based on a Cortex-M core, the devices (PORT, TIMERS, SPI, UARTS, ecc.) are different and low-level code cannot be transported as is from SAM3X to SAMD21.
For example in your sketch:
REG_TCC0_CTRLA |= (1 << (3%32));//set MODE[1:0] to 0x2 for 32 bit counting (TOP = 468741, log2(468741) = 18.8384)
REG_TCC0_CTRLA |= (1 << (5%32));//set WAVEGEN[1:0] to 0x1 for matching the frequency operation
if you read carefully the datasheet of the SAMD21 (chapter 30.8.1) you'll never find the MODE[1:0] bits or the WAVEGEN[1:0] bits in the TCC0.CTRLA register.
That is the reason why it is always preferred to use the Arduino API for this kind of operation if you want to make portable code (or any other specialized library if the functions provided by the Arduino Core are not powerful enough) instead of writing directly on the registers of the CPU.
I'm curious about this as well. I'm versed in avr-gcc, and how to use avr-gcc code to set timers in Arduino for the boards based on the AVR platform, but I'm not certain what cmaglie is referring to if he does not mean using the CMSIS library. Thanks.
moresun:
Hello cmaglie
I would like to see how it looks like with the Arduino API.
Would it be possible for you to post a working piece of code?
I'm also trying to use a timer interrupt on the zero.
I have copied your code and when uploaded to the Zero the function SystemInit() causes the arduino to hang.
When I comment out SystemInit() the arduino doesn't hang, but ISR is never called.
Any chance you or anyone else could give us an example of a working Arduino Zero timer interrupt configuration?
After a vacation and some work I got it .
I have two versions one for the TC timer chapter 29 and an other one for the TCC chapter 30 of the SAMD21 handbook.
Markus
29. TC – Timer/Counter
/**
* @author Markus Bader
* @brief this program shows how to use the TC timer with interrupts on an Arduino Zero board
* @email markus.bader@tuwien.ac.at
*/
int pin_ovf_led = 13; // debug pin for overflow led
int pin_mc0_led = 5; // debug pin for compare led
unsigned int loop_count = 0;
unsigned int irq_ovf_count = 0;
void setup() {
pinMode(pin_ovf_led, OUTPUT); // for debug leds
digitalWrite(pin_ovf_led, LOW); // for debug leds
pinMode(pin_mc0_led, OUTPUT); // for debug leds
digitalWrite(pin_mc0_led, LOW); // for debug leds
// Enable clock for TC
REG_GCLK_CLKCTRL = (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID_TCC2_TC3) ;
while ( GCLK->STATUS.bit.SYNCBUSY == 1 ); // wait for sync
// The type cast must fit with the selected timer mode
TcCount16* TC = (TcCount16*) TC3; // get timer struct
TC->CTRLA.reg &= ~TC_CTRLA_ENABLE; // Disable TC
while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync
TC->CTRLA.reg |= TC_CTRLA_MODE_COUNT16; // Set Timer counter Mode to 16 bits
while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync
TC->CTRLA.reg |= TC_CTRLA_WAVEGEN_NFRQ; // Set TC as normal Normal Frq
while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync
TC->CTRLA.reg |= TC_CTRLA_PRESCALER_DIV256; // Set perscaler
while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync
// TC->PER.reg = 0xFF; // Set counter Top using the PER register but the 16/32 bit timer counts allway to max
// while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync
TC->CC[0].reg = 0xFFF;
while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync
// Interrupts
TC->INTENSET.reg = 0; // disable all interrupts
TC->INTENSET.bit.OVF = 1; // enable overfollow
TC->INTENSET.bit.MC0 = 1; // enable compare match to CC0
// Enable InterruptVector
NVIC_EnableIRQ(TC3_IRQn);
// Enable TC
TC->CTRLA.reg |= TC_CTRLA_ENABLE;
while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync
}
void loop() {
// dummy
delay(250);
}
void TC3_Handler()
{
TcCount16* TC = (TcCount16*) TC3; // get timer struct
if (TC->INTFLAG.bit.OVF == 1) { // A overflow caused the interrupt
digitalWrite(pin_ovf_led, irq_ovf_count % 2); // for debug leds
digitalWrite(pin_mc0_led, HIGH); // for debug leds
TC->INTFLAG.bit.OVF = 1; // writing a one clears the flag ovf flag
irq_ovf_count++; // for debug leds
}
if (TC->INTFLAG.bit.MC0 == 1) { // A compare to cc0 caused the interrupt
digitalWrite(pin_mc0_led, LOW); // for debug leds
TC->INTFLAG.bit.MC0 = 1; // writing a one clears the flag ovf flag
}
}
30. TCC – Timer/Counter for Control Applications
/**
* @author Markus Bader
* @brief this program shows how to use the TCC timer with interrupts on an Arduino Zero board
* @email markus.bader@tuwien.ac.at
*/
int pin_ovf_led = 13; // debug pin for overflow led
int pin_mc0_led = 5; // debug pin for compare led
unsigned int loop_count = 0;
unsigned int irq_ovf_count = 0;
void setup() {
pinMode(pin_ovf_led, OUTPUT); // for debug leds
digitalWrite(pin_ovf_led, LOW); // for debug leds
pinMode(pin_mc0_led, OUTPUT); // for debug leds
digitalWrite(pin_mc0_led, LOW); // for debug leds
// Enable clock for TC
REG_GCLK_CLKCTRL = (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID_TCC0_TCC1) ;
while ( GCLK->STATUS.bit.SYNCBUSY == 1 ); // wait for sync
// The type cast must fit with the selected timer
Tcc* TC = (Tcc*) TCC0; // get timer struct
TC->CTRLA.reg &= ~TCC_CTRLA_ENABLE; // Disable TC
while (TC->SYNCBUSY.bit.ENABLE == 1); // wait for sync
TC->CTRLA.reg |= TCC_CTRLA_PRESCALER_DIV256; // Set perscaler
TC->WAVE.reg |= TCC_WAVE_WAVEGEN_NFRQ; // Set wave form configuration
while (TC->SYNCBUSY.bit.WAVE == 1); // wait for sync
TC->PER.reg = 0xFFFF; // Set counter Top using the PER register
while (TC->SYNCBUSY.bit.PER == 1); // wait for sync
TC->CC[0].reg = 0xFFF;
while (TC->SYNCBUSY.bit.CC0 == 1); // wait for sync
// Interrupts
TC->INTENSET.reg = 0; // disable all interrupts
TC->INTENSET.bit.OVF = 1; // enable overfollow
TC->INTENSET.bit.MC0 = 1; // enable compare match to CC0
// Enable InterruptVector
NVIC_EnableIRQ(TCC0_IRQn);
// Enable TC
TC->CTRLA.reg |= TCC_CTRLA_ENABLE ;
while (TC->SYNCBUSY.bit.ENABLE == 1); // wait for sync
}
void loop() {
// dummy
delay(250);
}
void TCC0_Handler()
{
Tcc* TC = (Tcc*) TCC0; // get timer struct
if (TC->INTFLAG.bit.OVF == 1) { // A overflow caused the interrupt
digitalWrite(pin_ovf_led, irq_ovf_count % 2); // for debug leds
digitalWrite(pin_mc0_led, HIGH); // for debug leds
TC->INTFLAG.bit.OVF = 1; // writing a one clears the flag ovf flag
irq_ovf_count++; // for debug leds
}
if (TC->INTFLAG.bit.MC0 == 1) { // A compare to cc0 caused the interrupt
digitalWrite(pin_mc0_led, LOW); // for debug leds
TC->INTFLAG.bit.MC0 = 1; // writing a one clears the flag ovf flag
}
}
Your demo programs are great - any advice on how to set the timer frequency? I can change it manually by adjusting TC->PER.reg and TC->CC.reg but I'd love to know how to set the interval to a specific time duration like I can with the TimerOne library.
I don't know if this is what you need, but here is an example sketch that shows how the clock frequency for the timers can be set for an M0 Pro. (Note only TC3 and TC5 are available because TC4 is used internally for the micros() routine in the M0 Pro - I am not sure about the Zero). If you want to set the top-count and generate interrupts when this is reached you might be better of with using the TCC. This is because they are designed to be used in this mode for PWM control and H-bridge motor drivers.
For my part, I have been trying to read the TCC counter values for use as a 24-bit live-time clock. This will reduce latency due to interrupt servicing compared to 16 bit timer counters. Sadly, despite a months of evenings testing things and reading the manual, I did not manage to get this to work. It seems it only works using the event system.
I wish you success!
Harry J. Whitlow
/////////////////////////////////////////////////////////////////////////
//
//
// Real time clock on TC3
//
//
//
/////////////////////////////////////////////////////////////////////////
volatile uint32_t realTcount = 0x0 ; // Counter for superticks (overflow interrupts)
void setup() {
Serial.begin(9600);
setTC3clock();
}
/*
This is test code that counts to 10 s then resets and waits 3s to test
reset, start and stop functions on TC3.
*/
void loop() {
delay(250);
Serial.print(realTime(), 7);
Serial.println();
double val = realTime();
if( int(val) >= 10)
{
resetStartTC3(); // reset so never gets above 10 s
stopTC3();
delay(3000); //wait 3 sec
startTC3();
}
}
void TC3_Handler() // Interrupt on overflow
{
TcCount16* TC = (TcCount16*) TC3; // get timer struct
realTcount++; // Increment the supertick register
TC->INTFLAG.bit.OVF = 1; // writing a one clears the ovf flag
// }
}
/*
Get the real time in seconds.
*/
double realTime()
{
double realTime = (realTcount * 1.2288E-2) + (REG_TC3_COUNT16_COUNT * 1.875E-7) ;;
return realTime;
}
/*
Setup the Generic clock register
*/
void setTC3clock()
{
GCLK->CLKCTRL.reg = (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID( GCM_TCC2_TC3 ));
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
TcCount16* TC = (TcCount16*) TC3; // get timer struct
TC->CTRLA.reg &= ~TC_CTRLA_ENABLE; // Disable TC
while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync
TC->CTRLA.reg |= TC_CTRLA_MODE_COUNT16; // Set Timer counter Mode to 16 bits
while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync
TC->CTRLA.reg |= TC_CTRLA_WAVEGEN_NFRQ; // Set TC as normal Normal Frq
while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync
TC->CTRLA.reg |= TC_CTRLA_PRESCALER_DIV8; // Set perscaler
while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync
// Interrupts
TC->INTENSET.reg = 0; // disable all interrupts
TC->INTENSET.bit.OVF = 1; // enable overfollow interrup
// Enable InterruptVector
NVIC_EnableIRQ(TC3_IRQn);
// Enable TC
TC->CTRLA.reg |= TC_CTRLA_ENABLE;
while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync
}
void resetStartTC3()
{
TcCount16* TC = (TcCount16*) TC3; // get timer struct
realTcount = 0x00; // Zero superclicks
TC->CTRLBSET.reg |= TC_CTRLBCLR_CMD_RETRIGGER; // restart
}
void stopTC3()
{
TcCount16* TC = (TcCount16*) TC3; // get timer struct
TC->CTRLBSET.reg |= TC_CTRLBSET_CMD_STOP; // Stop counter
}
void startTC3()
{
TcCount16* TC = (TcCount16*) TC3; // get timer struct
TC->CTRLBSET.reg |= TC_CTRLBSET_CMD_RETRIGGER; // Start
}
OK, I think I have this figured out. I'm using the TCC0 example from moresun's site. In order to change the timer count in the middle of the program you have to invoke Tcc* TC = (Tcc*) TCC0;
Here's my function for changing the counter:
void setTimer(long period) {
// The type cast must fit with the selected timer
// must do this or you can't touch the timer
Tcc* TC = (Tcc*) TCC0; // get timer struct
TC->CTRLA.reg &= ~TCC_CTRLA_ENABLE; // Disable TC
while (TC->SYNCBUSY.bit.ENABLE == 1); // wait for sync
TC->PER.reg = period; // Set counter Top using the PER register
while (TC->SYNCBUSY.bit.PER == 1); // wait for sync
// Enable TC
TC->CTRLA.reg |= TCC_CTRLA_ENABLE ;
while (TC->SYNCBUSY.bit.ENABLE == 1); // wait for sync
}
Changing the prescaler in the middle of the program crashes my application, so I need to figure out what's up with that. There's also a weird spot in the timer top count: if I set the timer with a period of less than 4185 the speed goes DOWN instead of up, and over a certain point I have to feed it HALF the period to get the right output speed. I've got a hack in my application now to do this but it's odd.
But thanks to moresun for the examples, very helpful.
It's also possible to change the frequency of generic clock (GCLK) that feeds the timers. There are 8 generic clocks: 0..3 are used by the Zero's core, but 4..7 are free.
This is done by setting the Generic Clock Generator Division register (GENDIV). GCLKs 3..8 allow an 8 bit divisor from 1 to 255.
So for example to feed a timer TC3 with a 16MHz clock from generic clock 4:
// Set up the generic clock (GCLK4) used to clock timers
REG_GCLK_GENDIV = GCLK_GENDIV_DIV(3) | // Divide the 48MHz clock source by divisor 3: 48MHz/3=16MHz
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
// Feed GCLK4 to TCC2 (and TC3)
REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN | // Enable GCLK4 to TCC2 (and TC3)
GCLK_CLKCTRL_GEN_GCLK4 | // Select GCLK4
GCLK_CLKCTRL_ID_TCC2_TC3; // Feed GCLK4 to TCC2 (and TC3)
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
The timer's own prescaler (CTRLA register) can then be used to divide the frequency down further, for instance a prescaler divsor of 8 will set the timer at 2MHz.
Here's an example of using Timer Counter 4 (TC4) in 8-bit mode on the SAMD21:
void setup() {
// Set up the generic clock (GCLK4) used to clock timers
REG_GCLK_GENDIV = GCLK_GENDIV_DIV(3) | // Divide the 48MHz clock source by divisor 3: 48MHz/3=16MHz
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
// Feed GCLK4 to TC4 and TC5
REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN | // Enable GCLK4 to TC4 and TC5
GCLK_CLKCTRL_GEN_GCLK4 | // Select GCLK4
GCLK_CLKCTRL_ID_TC4_TC5; // Feed the GCLK4 to TC4 and TC5
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
REG_TC4_CTRLA |= TC_CTRLA_MODE_COUNT8; // Set the counter to 8-bit mode
while (TC4->COUNT8.STATUS.bit.SYNCBUSY); // Wait for synchronization
REG_TC4_COUNT8_CC0 = 0x55; // Set the TC4 CC0 register to some arbitary value
while (TC4->COUNT8.STATUS.bit.SYNCBUSY); // Wait for synchronization
REG_TC4_COUNT8_CC1 = 0xAA; // Set the TC4 CC1 register to some arbitary value
while (TC4->COUNT8.STATUS.bit.SYNCBUSY); // Wait for synchronization
REG_TC4_COUNT8_PER = 0xFF; // Set the PER (period) register to its maximum value
while (TC4->COUNT8.STATUS.bit.SYNCBUSY); // Wait for synchronization
//NVIC_DisableIRQ(TC4_IRQn);
//NVIC_ClearPendingIRQ(TC4_IRQn);
NVIC_SetPriority(TC4_IRQn, 0); // Set the Nested Vector Interrupt Controller (NVIC) priority for TC4 to 0 (highest)
NVIC_EnableIRQ(TC4_IRQn); // Connect TC4 to Nested Vector Interrupt Controller (NVIC)
REG_TC4_INTFLAG |= TC_INTFLAG_MC1 | TC_INTFLAG_MC0 | TC_INTFLAG_OVF; // Clear the interrupt flags
REG_TC4_INTENSET = TC_INTENSET_MC1 | TC_INTENSET_MC0 | TC_INTENSET_OVF; // Enable TC4 interrupts
// REG_TC4_INTENCLR = TC_INTENCLR_MC1 | TC_INTENCLR_MC0 | TC_INTENCLR_OVF; // Disable TC4 interrupts
REG_TC4_CTRLA |= TC_CTRLA_PRESCALER_DIV64 | // Set prescaler to 64, 16MHz/64 = 256kHz
TC_CTRLA_ENABLE; // Enable TC4
while (TC4->COUNT8.STATUS.bit.SYNCBUSY); // Wait for synchronization
}
void loop() {
// put your main code here, to run repeatedly:
}
void TC4_Handler() // Interrupt Service Routine (ISR) for timer TC4
{
// Check for overflow (OVF) interrupt
if (TC4->COUNT8.INTFLAG.bit.OVF && TC4->COUNT8.INTENSET.bit.OVF)
{
// Put your timer overflow (OVF) code here:
// ...
REG_TC4_INTFLAG = TC_INTFLAG_OVF; // Clear the OVF interrupt flag
}
// Check for match counter 0 (MC0) interrupt
if (TC4->COUNT8.INTFLAG.bit.MC0 && TC4->COUNT8.INTENSET.bit.MC0)
{
// Put your counter compare 0 (CC0) code here:
// ...
REG_TC4_INTFLAG = TC_INTFLAG_MC0; // Clear the MC0 interrupt flag
}
// Check for match counter 1 (MC1) interrupt
if (TC4->COUNT8.INTFLAG.bit.MC1 && TC4->COUNT8.INTENSET.bit.MC1)
{
// Put your counter compare 1 (CC1) code here:
// ...
REG_TC4_INTFLAG = TC_INTFLAG_MC1; // Clear the MC1 interrupt flag
}
}
Thanks moresun, MartinL and others for contributing here. You have got me moving in the right direction. I downloaded moresun's examples and have been playing around with them and want to add a few things I discovered.
First, setting the "top" value of the counter is different depending on if you get a pointer to a TcCount16 or TcCount8 struct. For TcCount16 (and I would assume TcCount32) you use the CC member:
// create a struct to help access TC3's registers for a 16 bit timer counter
TcCount16* TC = (TcCount16*) TC3;
(other setup details covered in examples above)
// set the top value to 32767
TC->CC[0].reg = 0x7FFF;
while (TC->STATUS.bit.SYNCBUSY == 1);
But for 8 bit counters where you get a pointer to a TcCount8 struct you use the PER member:
// create a struct to help access TC3's registers for a 16 bit timer counter
TcCount8* TC = (TcCount8*) TC3;
(other setup details covered in examples above)
// set the top value to 128
TC->PER.reg = 0x7F;
while (TC->STATUS.bit.SYNCBUSY == 1);
Full disclosure, I never got an 8 bit counter running while using a pointer to a TcCount8 struct. I probably missed something simple... I did get an 8 bit counter running using TcCount16:
// create a struct to help access TC3's registers for a 16 bit timer counter
TcCount16* TC = (TcCount16*) TC3;
TC->CTRLA.reg |= TC_CTRLA_MODE_COUNT8
while (TC->STATUS.bit.SYNCBUSY == 1);
(other setup details covered in examples above)
// set the top value to 128
TC->CC[0].reg = 0x7F;
The second point I would like to clarify is that you must sett the TC wave generation mode to TC_CTRLA_WAVEGEN_MFRQ if you want the TC to honor your top value. Setting it to TC_CTRLA_WAVEGEN_NFRQ will cause the counter to ignore the "top" value and always count to the maximum for that type. For example 65535 for a 16 bit counter. You can set CC[0] but it will be ignored.
Lastly, while I couldn't get it to compile since it couldn't find all of the required header files I did find the Adafruit_ZeroTimer which looks promising. Just looking at the code and the included ASF source files helped me.