-
-
Save nonsintetic/ad13e70f164801325f5f552f84306d6f to your computer and use it in GitHub Desktop.
/* | |
* This sketch illustrates how to set a timer on an SAMD21 based board in Arduino (Feather M0, Arduino Zero should work) | |
* It should generate a 1Hz square wave as it is (thanks richdrich for the suggestion) | |
* Some more info about Timer Counter works can be found in this article: | |
* http://www.lucadavidian.com/2017/08/08/arduino-m0-pro-il-sistema-di-clock/ | |
* and in the datasheet: http://ww1.microchip.com/downloads/en/DeviceDoc/SAM_D21_DA1_Family_DataSheet_DS40001882F.pdf | |
*/ | |
uint32_t sampleRate = 1000; //sample rate in milliseconds, determines how often TC5_Handler is called | |
#define LED_PIN 13 //just for an example | |
bool state = 0; //just for an example | |
void setup() { | |
pinMode(LED_PIN,OUTPUT); //this configures the LED pin, you can remove this it's just example code | |
tcConfigure(sampleRate); //configure the timer to run at <sampleRate>Hertz | |
tcStartCounter(); //starts the timer | |
} | |
void loop() { | |
//tcDisable(); //This function can be used anywhere if you need to stop/pause the timer | |
//tcReset(); //This function should be called everytime you stop the timer | |
} | |
//this function gets called by the interrupt at <sampleRate>Hertz | |
void TC5_Handler (void) { | |
//YOUR CODE HERE | |
if(state == true) { | |
digitalWrite(LED_PIN,HIGH); | |
} else { | |
digitalWrite(LED_PIN,LOW); | |
} | |
state = !state; | |
// END OF YOUR CODE | |
TC5->COUNT16.INTFLAG.bit.MC0 = 1; //Writing a 1 to INTFLAG.bit.MC0 clears the interrupt so that it will run again | |
} | |
/* | |
* TIMER SPECIFIC FUNCTIONS FOLLOW | |
* you shouldn't change these unless you know what you're doing | |
*/ | |
//Configures the TC to generate output events at the sample frequency. | |
//Configures the TC in Frequency Generation mode, with an event output once | |
//each time the audio sample frequency period expires. | |
void tcConfigure(int sampleRate) | |
{ | |
// select the generic clock generator used as source to the generic clock multiplexer | |
GCLK->CLKCTRL.reg = (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID(GCM_TC4_TC5)) ; | |
while (GCLK->STATUS.bit.SYNCBUSY); | |
tcReset(); //reset TC5 | |
// Set Timer counter 5 Mode to 16 bits, it will become a 16bit counter ('mode1' in the datasheet) | |
TC5->COUNT16.CTRLA.reg |= TC_CTRLA_MODE_COUNT16; | |
// Set TC5 waveform generation mode to 'match frequency' | |
TC5->COUNT16.CTRLA.reg |= TC_CTRLA_WAVEGEN_MFRQ; | |
//set prescaler | |
//the clock normally counts at the GCLK_TC frequency, but we can set it to divide that frequency to slow it down | |
//you can use different prescaler divisons here like TC_CTRLA_PRESCALER_DIV1 to get a different range | |
TC5->COUNT16.CTRLA.reg |= TC_CTRLA_PRESCALER_DIV1024 | TC_CTRLA_ENABLE; //it will divide GCLK_TC frequency by 1024 | |
//set the compare-capture register. | |
//The counter will count up to this value (it's a 16bit counter so we use uint16_t) | |
//this is how we fine-tune the frequency, make it count to a lower or higher value | |
//system clock should be 1MHz (8MHz/8) at Reset by default | |
TC5->COUNT16.CC[0].reg = (uint16_t) (SystemCoreClock / sampleRate); | |
while (tcIsSyncing()); | |
// Configure interrupt request | |
NVIC_DisableIRQ(TC5_IRQn); | |
NVIC_ClearPendingIRQ(TC5_IRQn); | |
NVIC_SetPriority(TC5_IRQn, 0); | |
NVIC_EnableIRQ(TC5_IRQn); | |
// Enable the TC5 interrupt request | |
TC5->COUNT16.INTENSET.bit.MC0 = 1; | |
while (tcIsSyncing()); //wait until TC5 is done syncing | |
} | |
//Function that is used to check if TC5 is done syncing | |
//returns true when it is done syncing | |
bool tcIsSyncing() | |
{ | |
return TC5->COUNT16.STATUS.reg & TC_STATUS_SYNCBUSY; | |
} | |
//This function enables TC5 and waits for it to be ready | |
void tcStartCounter() | |
{ | |
TC5->COUNT16.CTRLA.reg |= TC_CTRLA_ENABLE; //set the CTRLA register | |
while (tcIsSyncing()); //wait until snyc'd | |
} | |
//Reset TC5 | |
void tcReset() | |
{ | |
TC5->COUNT16.CTRLA.reg = TC_CTRLA_SWRST; | |
while (tcIsSyncing()); | |
while (TC5->COUNT16.CTRLA.bit.SWRST); | |
} | |
//disable TC5 | |
void tcDisable() | |
{ | |
TC5->COUNT16.CTRLA.reg &= ~TC_CTRLA_ENABLE; | |
while (tcIsSyncing()); | |
} |
thanks for the post, this is helpful.
Compiling for a Feather M0, I get a clean compile on your code AS IS but when I try to add it to my own code, the compiler gives me:
'TC5' was not declared in this scope
I moved everything after the loop() function to tc5timer.cpp and created tc5timer.h to predefine the needed functions. tc5timer.h was included in the main sketch. Not sure where I messed this one up.
@funwithbots
Make sure that your code before the TC5 deceleration has semi colons. Same thing happened to me.
Thank you for this example, helped a lot. Implemented something similar in a timing routine for feedback control, see the AutomationShield project.
As posted you are setting the counter to e.g. 64M / 10 which is >65535 and hence basically random.
If you set e.g. SampleRate=1000 and TC_CTRLA_PRESCALER_DIV1024 it will (on a Zero) blink at about 1Hz, which is more usable.
Very well observed!
It took me quite a while to realize what the actual blinking frequency would be in the example. This idea helps a lot to understand and make the example usable.
I compiled it as an independent module on my MKRWAN1300, and its working fine, so far!
FWIW: Writing a 1 to INTFLAG.bit.MC0 clears the interrupt so that it will run again. This is what is being done in this line:
TC5->COUNT16.INTFLAG.bit.MC0 = 1;
It would be helpful if the comment was changed to indicate this.
done
FWIW: Writing a 1 to INTFLAG.bit.MC0 clears the interrupt so that it will run again. This is what is being done in this line:
TC5->COUNT16.INTFLAG.bit.MC0 = 1;
It would be helpful if the comment was changed to indicate this.
done
Thank you for this, it is very well done. I am curious though, that when I change the sampleRate either up or down (say to 2000 or 500) my initial thought was it would either half or double the time the light blinks, however I am not finding that to be the case. How exactly could that be accomplished?
Thanks very much. I have been looking to port some code from a Nano to the Nano IoT 33 and want to know if the code could be expected to work on the Nano IoT 33 which is SAMD21G18A microcontroller.
Actually
uint32_t sampleRate = 1024;
Will give you exactly a 1 sec interrupt interval generating a 0.5 Hz square wave according to my, admittedly cheap, Chinese CRO.
Not sure why, but 2^10 does have a ring of truth about it - happy for any feedback explanation.
Thanks for a nice piece of code, avoiding the need for an RTC to control accurate 1 Hz sampling.
Just found this example code - very helpful -thank you.
To comment above - yes 1024 generates exactly a 1 hz interrupt - timer is counting at a rate of systemclock/1024(prescaler) counts/sec and interrupt will be generated every 1024 counts - so systemclock counts will trigger the interrupt - to reach systemclock counts takes one second - by definition.
Question - the code uses loads of predefined 'labels', e.g. TC5->COUNT16.CTRLA.reg |= TC_CTRLA_MODE_COUNT16;
Where are all these definitions documented? I'm still finding my way around everything arduino - there's lots - would appreciate being pointed in the right direction. Thanks
Question - the code uses loads of predefined 'labels', e.g. TC5->COUNT16.CTRLA.reg |= TC_CTRLA_MODE_COUNT16;
Where are all these definitions documented? I'm still finding my way around everything arduino - there's lots - would appreciate being pointed in the right direction. Thanks
The defines are from the Arduino Core written for the SAMD21 (https://github.com/arduino/ArduinoCore-samd). They 'translate' the registers and various combinations of register settings from the datasheet into a more human readable format for programming. Go to that repository and use the search function to find where they are defined, some extra comments might be present.
To see what they actually do you have to check the datasheet for the SAMD21G18. You won't find the EXACT tags most of the time because some are combinations of settings (TC_CTRLA_MODE_COUNT16 means something Timer Counter, for CTRLA (a register), set MODE to COUNT16), check around section 19.8.2 Control - MODE1 (part of the chapter on the real time counter) of the SAMD21 datasheet to see more.
Another SAMD21 Quirk
As I commented above a sample rate of 1024 generates exactly a 1 sec tick - When the SAMD21 is powered from the micro USB connector with a data connection, eg to a PC.
However, when powered with 5V into the BAT pin, or even 5V into the micro USB from a power supply a lower frequency 0.970 Hz [1.031 sec] tic is generated.
Both the USB & BAT connections feed into the same 3V3 voltage regulator with rock solid 3V3 output - so voltage differences do not appear to be the issue - leaving the USB data connection as the issue, as discussed above.
Can anyone confirm my observations above & shed any light on what is causing this timing issue on the SAMD21 between micro USB/with a data connection vs 5V into either the micro USB without a data connection or 5V into the BAT pin?
Note: there is so Serial connection being set up in the user code.
at when I change the sampleRate either up or down (say to 2000 or 500) my in
TC5->COUNT16.CTRLA.reg |= TC_CTRLA_PRESCALER_DIV1024 | TC_CTRLA_ENABLE; //it will divide GCLK_TC frequency by 1024
TC5->COUNT16.CC[0].reg = (uint16_t) (SystemCoreClock / sampleRate);
In the first line, the GCLK is prescaled by 1024. So say that you have a 48MHz system core clock, it's now counts 48MHz/1024 = 46,875 times per second.
To have this thing trigger every second, you set sampleRate to 1024 so it will count to 46,875, which is the counts per second in the previous sentence.
To trigger every 100ms, you'd want to trigger every 4,688 times, or 48MHz/ (1024 * 10).
So ms wanted to trigger the interrupt is:
(X * SystemCoreClock) / (1,024 * 1000) where
X is the ms wanted
1024 is due to the prescaler in this code
1000 is for 1000ms in a second
Thanks for the example. It's a very concise starting point for custom project alterations.
You need to be a little careful with these timing changes.
A sample rate of 1024 certainly gives you a 1Hz tick - As long as you have a USB data connection back to your PC - see my earlier post relating to this SAMD21 quirk.
I agree with voloved's logic above, the clock count appears to be set to SystemCoreClock/SampleRate. You would imagine that if you halved the sample rate then this would double the count and give a 2 sec tick??
BUT NO My CRO shows a 0.6 sec tick.
THE REASON - 48,000,000/512 = 93,750 causing an overflow in the uint_16 TC5 register.
With the max size of uint_16 at 65,535 the lowest SampleRate you can have is 48,000,000/65,535=733 giving a maximum 1.4 sec tick.
Moving in the other direction, with SampleRate defined as uint_32, there is no problem with higher frequencies - easily works down to 100 uS tick - you will probably run into the digital write speed in your Arduino code.
For those wanting to calculate the required SampleRate [with the specified 1024 pre scaler] the following formula may be handy
SampleRate = (1000 * 1024)/mS
Thanks for the example! Is this a revised version of this one on svn.larosterna.com with better comments? Just curious.
May I suggest this edit for the first line of code comment: "sample rate in millihertz" rather than "sample rate in milliseconds" .
Agree, the variable "sampleRate" is actually a frequency, but not quite exactly in millihertz.
If you want accurate timing in millihertz then you need to account for the 1024 pre scaling factor in GCLK.
The actual frequency in millihertz generated by "sampleRate" is
Freq = sampleRate/ (1000 * 1024) mHz
My suggestion to try and avoid all this pre scaler confusion would be simple modify the first couple of code lines as
=============================================================================
uint32_t samplePeriod = 1000; //Sample period in milliseconds, determines how often TC5_Handler is called
//Note: The maximum samplePeriod = 1400 mS beyond which the TC5 register
//will overflow causing erroneous timing. Shorter sample periods (high frequencies)
//will not cause any problems.
uint32_t sampleRate = (1000 * 1024)/samplePeriod;
=============================================================================
Don't forget that the SAMD21 clock needs to see a USB data connection to give accurate timing. If you start it up without a USB connection, say on battery power, then you will need to find the required sampleRate parameter for your required timing using a CRO.
Those points taken into account, this is a really nice piece of useful code for those of us using the SAMD21
Thanks for the example! Is this a revised version of this one on svn.larosterna.com with better comments? Just curious.
May I suggest this edit for the first line of code comment: "sample rate in millihertz" rather than "sample rate in milliseconds" .
Think it's more like the other way round. I put this together from some comments on the Arduino forum (I think) ages ago while trying to make a RFM95 walkie talkie :) It's pieced together from several suggestions, but I don't remember what post it was.
Thank you for this piece of code, its been really helpful to me to understand the timers on my Nano 33 IoT.
However, I am trying to find out of I can implement an interrupt frequency faster than 1kHz (period less than 1ms) and would really appreciate it if somebody could clarify what the fastest interrupt frequency is that one can implement on the 48MHz Cortex-M0 32-bit SAMD21 used by the Nano 33 IoT. Are there limitations to going faster than 1kHz?
For context, I am sampling AC signals and would like to be able to sample a bit faster than 1kHz.
Thank you for this piece of code, its been really helpful to me to understand the timers on my Nano 33 IoT.
However, I am trying to find out of I can implement an interrupt frequency faster than 1kHz (period less than 1ms) and would really appreciate it if somebody could clarify what the fastest interrupt frequency is that one can implement on the 48MHz Cortex-M0 32-bit SAMD21 used by the Nano 33 IoT. Are there limitations to going faster than 1kHz?
For context, I am sampling AC signals and would like to be able to sample a bit faster than 1kHz.
On a variant with a different system clock frequency the calculations will be different. This code was devised for an 8Mhz clock. This is the line that would be affected by a different clock speed:
//system clock should be 1MHz (8MHz/8) at Reset by default
TC5->COUNT16.CC[0].reg = (uint16_t) (SystemCoreClock / sampleRate);
With a faster clock the counter would count faster, so you'd get a bigger maximum frequency. All this does is set a counter to increase by one every clock cycle and set a number as the target. When the counter gets to the set target, the interrupt gets called and the counter starts from zero.
You can also change line 62 to divide the clock by a different number. Dividing by less will count faster. As it is in the gist it divides by 1024.
Thanks for the fast reply! I'll do some testing.
Hello there
Actually , there is this more accurate view of the TC5->COUNT16.CC[0].reg setting, i hope more accurate. In the SAMD21 data sheet you can read in the TC section 30.10.13 Channel x Compare/Capture Value, 16-bit Mode, the following sentence: In Match frequency (MFRQ) or Match PWM (MPWM) waveform operation (CTRLA.WAVEGEN), the CC0 register is used as a period register. This is here the case, that means : it counts period units. The default clock generator for the TC peripheral is the XOSC oscillator, i 'm not sure which is that, but it definitely drives the bus with 4MHz (i measured its actual value on a Zero). Let's assign to TC the more accurate oscillator XOSC32K and divide by a DIV1 prescaler, and we have a clock frequency for the TC5 of f = 32768/4 = 8.192 kHz (is divided by 4 by default). The period for that frequency is 1s/32768 = 122.1 us, that means every single count lasts 122.1 us. Now the CC[0] register is a 16bit value, at maximum 65535. Then if we clock pin11 instead of the the LED for 1 count x 122.1 we get a pulsed signal of 50% duty cycle with f=8192 Hz. The declaration of the quotient SystemCoreClock / sampleRate has in my opinion therefore no physical meaning, either the SystemCoreClock value has anything to do with the physical state of the driving clock with the TC5 timer configured in this way. SystemCoreClock is just the constant number 48e6 which is the core clock frequency indeed and the value sampleRate has no possible meaning at all. You can put in the CC[0] register instead any 16bit value between 1 and 65535 and expect theoretically a time interval of 1x 122.1 us up to 65535 x 122.1 us = 8 s. I don't know why the actual period measured is exactly the half of it 4s, i suppose the interrupt mode is CHANGING, not RISING or FALLING. Anyway the code behaves well and solves the service of calling an Interrupt Service Routine ISR by an externally delivered pulse.
I changed the code, corrected it at some placed and added (hopefully) useful comments.
You can fetch the file from my account.
Thanks
Doesn't seems to work with a ATSAMD21J18A, strange...
As posted you are setting the counter to e.g. 64M / 10 which is >65535 and hence basically random.
If you set e.g. SampleRate=1000 and TC_CTRLA_PRESCALER_DIV1024 it will (on a Zero) blink at about 1Hz, which is more usable.