Go Down

Topic: Arduino ZERO timer interrupts (Read 26828 times) previous topic - next topic

MartinL

#15
Dec 27, 2015, 11:24 pm Last Edit: Dec 27, 2015, 11:33 pm by MartinL
Hi pharaohamps,

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:

Code: [Select]
// 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.

markab

Hello,

With an earlier Arduino I set up 2 timer interrupts like this...

   TCCR1A = 0;
   TCCR1B = (1<<WGM12)|(1<<CS12);
   OCR1A = 255;
   TCNT1 = 0;             

   TCCR2A = (1<<WGM21);
   TCCR2B = (1<<CS22)|(1<<CS21);
   OCR2A = 255;
   TCNT2 = 0;

   TIMSK1 |= (1<<OCIE1A);
   TIMSK2 |= (1<<OCIE2A);


And then these timers call functions on interrupt...

ISR(TIMER1_COMPA_vect)
ISR(TIMER2_COMPA_vect)

Can someone help me configure the same timers for the Zero.


Thanks

MartinL

#17
Mar 26, 2016, 06:01 pm Last Edit: May 02, 2016, 11:52 am by MartinL
Hi markab,

Here's an example of using Timer Counter 4 (TC4) in 8-bit mode on the SAMD21:

Code: [Select]
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
  }
}

markab


hank_scorpio

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:

Code: [Select]

//  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:


Code: [Select]

//  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:

Code: [Select]

//  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.

I hope this helps.

tartan-caber

I've tried to compile examples 29 & 30 above but get the error

sketch_jul07b.ino:22:80: error: 'GCLK_CLKCTRL_ID_TCC0_TCC1' was not declared in this scope
Error compiling.

I'm running IDE 1.7.9 on MAC OSX 10.11.5

It doesn't seem to matter which TCC numbers I use, they all fail.


Any ideas?

AloyseTech

IDE 1.7.9 is the Arduino.org IDE, so you probably use a .org arduino board. You should try with the .cc environment (board and IDE). There already are topics about changing the board bootloader to provide compatibility with .cc IDE.

tartan-caber

That's fixed it - thanks for your help! Politics, hey.

Rucus

Hi MartinL,

Quote
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.
Thanks for this info. I am successful in using GCLKs 5-7, but GLCK 4 seem used by Arduino too. When I used it to clock one of the TC modules, I always get a zero value when reading the COUNT register. If I change the clock source to GCLK 5 to 7, it seem to work fine. So, I am not sure now if GCLK4 is free?  :smiley-confuse:

Thanks,
Ruch

MartinL

#24
Jul 31, 2016, 08:56 pm Last Edit: Jul 31, 2016, 09:08 pm by MartinL
Hi Ruch,

You should be able to feed a single GCLK to any of the TCC or TC timers. In my firmware for my current project, I'm using GCLK4 as the source for all 6 timers.

I read the COUNT register for TC3, but as I don't need to write to the it, I'm setting the RCONT bit in the Read Request (READREQ) register to enable continuous synchronization. (I'm using the timer in 8-bit mode):

Code: [Select]
REG_TC3_READREQ = TC_READREQ_RCONT |            // Enable a continuous read request
                    TC_READREQ_ADDR(0x10);        // Offset of the 8 bit COUNT register
while (TC3->COUNT8.STATUS.bit.SYNCBUSY);        // Wait for (read) synchronization
...

This allows you to continuously read the COUNT register without having to synchronize each time, (it does it automatically).

If you write to the COUNT register at any time, but still want to read it, then you should just be able to set the RREQ bit to read synchronize as normal:

Code: [Select]
REG_TC3_READREQ = TC_READREQ_RREQ |           // Enable a read request
                      TC_READREQ_ADDR(0x10);      // Offset address of the COUNT register
while (TC3->COUNT8.STATUS.bit.SYNCBUSY);     // Wait for (read) synchronization
...

engineertype

Thanks for everyone who contributed to this thread!  You were a massive help.  As a way of giving back, I can describe how to set the overflow value for 16-bit TC counters.  The PER register is 8-bit only, but you can set the max count value in 16-bit counters using the method here.
Code: [Select]

...
TC->CC[0].reg = 0x1FFF; // Use CC0 to "overflow"
TC->CC[1].reg = 0x0FFF; // Use CC1 as a compare
...
...
TC->INTENSET.bit.MC0 = 1;  // Enable compare match to CC0
TC->INTENSET.bit.MC1 = 1;  // Enable compare match to CC1
...

void TC3_Handler()
{
  if (TC->INTFLAG.bit.OVF == 1) {  // A overflow caused the interrupt
    // This will never happen because CC0 will set the counter back to zero
  }
 if (TC->INTFLAG.bit.MC0 == 1) {  // A compare to cc0 caused the interrupt
   TC->INTFLAG.bit.MC0 = 1;    // Writing a one clears the cc0 interrupt flag
   TC->COUNT.reg = 0;  // Set the counter back to zero.  Yes, this can be done inside the interrupt      
 }
 if (TC->INTFLAG.bit.MC1 == 1) {  // A compare to cc1 caused the interrupt
   // Do whatever you need to do on compare here
   TC->INTFLAG.bit.MC1 = 1;    // Writing a one clears the cc1 interrupt flag
 }
}

Regards,

Peter

avandalen

#26
Jan 31, 2017, 11:38 am Last Edit: Jan 31, 2017, 11:49 am by avandalen
I have made a sophisticated timer library for the Arduino SAMD that makes life easier:
http://www.avdweb.nl/arduino/libraries/samd21-timer.html

The library is easy to use; the following code generates a square wave of 1Hz to pin 5 of the Arduino Zero:
SAMDtimer mytimer1 = SAMDtimer(3, TC_COUNTER_SIZE_16BIT, 5, 1e6);


bizzarosanta2

avandalen,

Thank you SO MUCH for taking the time to put this together! I have been trying to figure out how to do timer interrupts on my Adafruit Feather M0 for about a week!

Unfortunately I'm having a problem figuring out how to get the Adafruit_ZeroTimer library to compile properly. I get errors telling me that the library is trying to

#include "compiler.h"
#include "clock.h"
#include "gclk.h"
#include "pinmux.h"

but these files (and a couple of others) are no where to be found! After much researching I have come to realize they are part of the Atmel drivers for the SAMDs, however I have no idea where to find/put them? Is there somewhere I am supposed to have them installed on my computer? This seems odd as Adafruits documentation says there are no dependencies for this library. I know you are not adafruit support or anything but I'm desperate and you seem to have some idea about how this works.

Thanks again!

bizzarosanta2

After a LOT of digging around, I found out you need to install the following library to get Adafruit_ZeroTimer to compile:

https://github.com/adafruit/Adafruit_ASFcore

Really wish that was somewhere in Adafruits documentation!

cbob

Sorry for reopening this old thread, but:

I have some problems with my M0 Pro - and for your information: I am very new to the SAMD21

As long as I use the standard libraries and the standard programming, I am all in all satisfied with the M0 pro. But now I have to do the following: I have an incremental encoder (400 inches per revolution) connected to the Arduino and it counts its position with the help of an interrupt and that works as long as I'm not turning the encoder too fast - so far so good.
Now on top I have to supply a stepper controller with pulses and direction. The direction is done very simple as I have to pull a pin high or low for cw / ccw.
But the real problem is pulse generation. I know that using the 'digitalWrite(PulsePin, HIGH)' routine is not a good idea and so I was looking for other possibilities to switch a Pin faster by a timer. I stumbled upon many examples of timers, PWM and wave generators and nothing is working correctly.
Now I've tried a simple example from [url = https: //forum.arduino.cc/index.php?topic=332275.msg2355111#msg2355111] moresun [/ url] - but it gets stuck.
Then I tried the above from [url = https: //forum.arduino.cc/index.php?theme=332275.msg2682143 # msg2682143] MartinL [/ url] - and again it gets stuck. I kept it very simple by just letting the LED blink, but nothing  :(

So can someone help to get on the right track? (Attached you can find the examples)

Thanks in advance
Dan

Go Up