-
-
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()); | |
} |
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...
Thanks for the example. It's a very concise starting point for custom project alterations.