I need to measure with accuracy the simultaneity of two physical events. In order to do this I need 2 arduinos (MKR wifi 1010) to be able to make measurements at 1kHz and to timestamp the samples precisely (to the millisecond). The 2 arduinos cannot be connected directly, only a wireless link is possible.
The measurement takes about 5 seconds, then each arduino sends its data by wifi to a collecting system.
So I need my 2 arduinos clocks to be synchronized (no more than 5ms out of sync). I think that NTP synchronization will give me an acceptable accuracy.
Now I would like to use the RTC of the samd21 to give me the time to the millisecond, and here is my problem.
According to the datasheet, the RTC can either behave like a clock/calendar (which is accurate only to the second, not to the millisecond), or like a counter whose frequency is a sub-multiple of 32,768 Hz! So for example I can have a counter that increments to 1024Hz (with the correct prescaler setting), but how do I make a clock with that? Is there a simple trick?
PS: I've told you the whole story to avoid XY problems. If you have any other ideas on how to precisely synchronize 2 arduinos I'm also interested!
PS2 : English is not my mother tongue, please forgive my mistakes.
As you mention, the issue is that the SAMD21's RTC in MODE2 (calender mode) is only accurate to 1 second.
In order to measure to millisecond accuracy, it's necessary to use either the RTC timer in MODE0 (32-bit mode) and/or the TCC/TC timers.
Also, if you're clocking the RTC peripheral using 32.768kHz clock source, a single register read and write synchronization delay can be in the order of 5ms, as the whole peripheral (including registers) are being clocked at such a slow rate.
If there's line of sight between the boards, I'd consider using an IR transmitter and sensor for synchronization, as it avoids the latency over a wireless network.
The SAMD21's External Interrupt Controller (EIC) in conjuction with the event system, (a 12-channel highway that allows peripheral to peripheral communication without CPU intervention), can be used to automatically trigger timers from external input signals. The SAMD21 also has two dual analog comparators that can be employed if your physical events happen to be analog rather than digital signals.
MartinL:
In order to measure to millisecond accuracy, it's necessary to use either the RTC timer in MODE0 (32-bit mode) and/or the TCC/TC timers.
Hi MartinL,
interesting. I had the idea to add a log timestamp to my measurements, which requires an accuracy of 10 uS. I just got my hands on SAMD21, so still need to to study more. But, would you show an example for timer counter?
Here's a basic TC timer example that chains TC4 and TC5 to act as a 32-bit timer counter. The TC4 timer is clocked by the 48MHz Digital Frequency Locked Loop (DFLL48M) on generic clock 0 (GCLK0).
The loop() portion of the code outputs the multiples of 10us elapsed every second:
The TC4's COUNT register requires read register synchronization between the TC peripheral and the CPU's APB (Advanced Peripheral Bus). This is achieved either by a single or continous read request:
// Setup TC4/TC5 in 32-bit mode with 48MHz tick (20.83ns period)
void setup()
{
SerialUSB.begin(115200); // Open the native serial port
while(!SerialUSB); // Wait for the console to open
GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | // Enable the generic clock...
GCLK_CLKCTRL_GEN_GCLK0 | // ....on GCLK0 at 48MHz
GCLK_CLKCTRL_ID_TC4_TC5; // Feed the GCLK0 to TC4 and TC5
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
TC4->COUNT32.CTRLA.reg |= //TC_CTRLA_PRESCALER_DIV1 | // Set the timer prescaler
//TC_CTRLA_PRESCSYNC_PRESC | // Reload timer on next prescaler clock
TC_CTRLA_MODE_COUNT32; // Set the TC4 timer to 32-bit mode in conjuction with timer TC5
TC4->COUNT32.CTRLA.bit.ENABLE = 1; // Enable TC4
while (TC4->COUNT32.STATUS.bit.SYNCBUSY); // Wait for synchronization
TC4->COUNT32.READREQ.reg = TC_READREQ_RCONT | // Enable a continuous read request
TC_READREQ_ADDR(0x10); // Offset of the 32-bit COUNT register
while(TC4->COUNT32.STATUS.bit.SYNCBUSY); // Wait for (read) synchronization
}
void loop()
{
//TC4->COUNT32.READREQ.reg = TC_READREQ_RREQ | // Enable a basic read request
// TC_READREQ_ADDR(0x10); // Offset of the 32-bit COUNT register
//while (TC4->COUNT32.STATUS.bit.SYNCBUSY); // Wait for (read) synchronization
SerialUSB.println(TC4->COUNT32.COUNT.reg / 480); // Output the TC4 timer COUNT register in multiples of 10us
//TC4->COUNT32.CTRLBSET.reg = TC_CTRLBCLR_CMD_RETRIGGER; // Retrigger (reset) the timer
//while (TC4->COUNT32.STATUS.bit.SYNCBUSY); // Wait for CTRLBSET write synchronization
delay(1000); // Wait for 1 second
}
If you require an absolute timestamp, it should be possible generate an absolute calendar time using the RTC, then use the SAMD21's event sytem to trigger the TC timer every second. The TC timer can then measure the elapsed time down to the millisecond and microsecond level. The event system allows peripherals to communicate without any CPU intervention.
Here's an example that sets up the RTC in calendar mode (MODE2) to 21st May 2020 at 14:00 and gets it to generate both an ALARM0 interrupt and event every second.
The RTC's interrupt toggles the board's LED, while the its event retriggers timer TC4 running at 1MHz. The TC timer counts the number of elapsed microseconds for each second. The results are output on the console.
Code part I:
// Set-up RTC and timer TC4 to generate a timestamp down to microsecond accuracy
union { // Timestamp union/structure
struct {
uint32_t second : 6;
uint32_t minute : 6;
uint32_t hour : 5;
uint32_t day : 5;
uint32_t month : 4;
uint32_t year : 6;
} bit;
uint32_t reg;
} timeStamp;
void setup(){
// Initialise native serial port, event system and LED output //////////////////////
SerialUSB.begin(115200); // Initialise the native serial port
while (!SerialUSB); // Wait for the console to open
PM->APBCMASK.reg |= PM_APBCMASK_EVSYS; // Switch on the event system peripheral
PORT->Group[g_APinDescription[LED_BUILTIN].ulPort].DIRSET.reg = 1 << g_APinDescription[LED_BUILTIN].ulPin; // Set digital pin LED_BUILTIN to an OUTPUT
// RTC Generic Clock (GCLK) Configuration //////////////////////////////////////////
GCLK->GENDIV.reg = GCLK_GENDIV_DIV(4) | // Select clock divisor to divide by 32 (2 ^ (4 + 1))
GCLK_GENDIV_ID(4); // Select GLCK4
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
GCLK->GENCTRL.reg = //GCLK_GENCTRL_RUNSTDBY | // Enable run standby mode
GCLK_GENCTRL_IDC | // Set the duty cycle to 50/50 HIGH/LOW
GCLK_GENCTRL_GENEN | // Enable GCLK
GCLK_GENCTRL_DIVSEL | // Set GLCK divisor as 2 to the power of (divisor) value
GCLK_GENCTRL_SRC_XOSC32K | // Select GCLK source as external 32.768kHz crystal (XOSC32K)
GCLK_GENCTRL_ID(4); // Select GCLK4
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | // Enable GCLK
GCLK_CLKCTRL_ID_RTC | // Connect to the RTC at 1.024kHz (32.768kHz/32)
GCLK_CLKCTRL_GEN_GCLK4; // Select GCLK4
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
// Timer TC4 Generic Clock (GCLK) Configuration //////////////////////////////////////////
GCLK->GENDIV.reg = GCLK_GENDIV_DIV(3) | // Select clock divisor to divide by 3 48MHz/3=16MHz
GCLK_GENDIV_ID(5); // Select GLCK5
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
GCLK->GENCTRL.reg = //GCLK_GENCTRL_RUNSTDBY | // Enable run standby mode
GCLK_GENCTRL_IDC | // Set the duty cycle to 50/50 HIGH/LOW
GCLK_GENCTRL_GENEN | // Enable GCLK
GCLK_GENCTRL_SRC_DFLL48M | // Select GCLK source as the 48MHz DFLL (DFLL48M)
GCLK_GENCTRL_ID(5); // Select GCLK5
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | // Enable GCLK
GCLK_CLKCTRL_ID_TC4_TC5 | // Connect to timers TC4 an TC5
GCLK_CLKCTRL_GEN_GCLK5; // Select GCLK5
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
// Timer TC4 configuration ////////////////////////////////////////////////////
TC4->COUNT32.CTRLA.reg = TC_CTRLA_PRESCALER_DIV16 | // Set the timer prescaler to divide by 16: 16MHz/16=1MHz
TC_CTRLA_PRESCSYNC_PRESC | // Reload timer on next prescaler clock
TC_CTRLA_MODE_COUNT32; // Set the TC4 timer to 32-bit mode in conjuction with timer TC5
// RTC configuration //////////////////////////////////////////////////////////
RTC->MODE2.CTRL.bit.SWRST = 1; // Software reset the RTC
while (RTC->MODE2.STATUS.bit.SYNCBUSY); // Wait for synchronization ...
while(RTC->MODE2.CTRL.bit.ENABLE); // Wait for the RTC to become disabled
RTC->MODE2.CTRL.reg = RTC_MODE2_CTRL_PRESCALER_DIV1024 | // Set prescaler to 1024
RTC_MODE2_CTRL_MODE_CLOCK; // Set RTC to mode 2, clock/calendar mode
RTC->MODE2.Mode2Alarm[0].ALARM.bit.SECOND = RTC_MODE2_ALARM_SECOND(0); // Set the ALARM0 compare register
while (RTC->MODE2.STATUS.bit.SYNCBUSY) // Wait for synchronization
RTC->MODE2.Mode2Alarm[0].MASK.bit.SEL = RTC_MODE2_MASK_SEL_SS; // Set the RTC clock/calender interrupt mask to match seconds only
while (RTC->MODE2.STATUS.bit.SYNCBUSY) // Wait for synchronization
NVIC_SetPriority(RTC_IRQn, 0); // Set the Nested Vector Interrupt Controller (NVIC) priority for the RTC
NVIC_EnableIRQ(RTC_IRQn); // Connect Event System to Nested Vector Interrupt Controller (NVIC)
RTC->MODE2.INTENSET.reg = RTC_MODE2_INTENSET_ALARM0; // Set RTC ALARM0 interrupt
RTC->MODE2.CLOCK.reg = 0x516AE000; // Initialise the RTC: May 21st 2020, 14:00 hours, ref year: 2000
while (RTC->MODE2.STATUS.bit.SYNCBUSY); // Wait for synchronization
// Part II follows...
// Follows from part I....
// Event System configuration //////////////////////////////////////////////////
EVSYS->USER.reg = EVSYS_USER_CHANNEL(1) | // Attach the event user (receiver) to channel 0 (n + 1)
EVSYS_USER_USER(EVSYS_ID_USER_TC4_EVU); // Set the event user (receiver) as timer TC4
EVSYS->CHANNEL.reg = EVSYS_CHANNEL_EDGSEL_NO_EVT_OUTPUT | // No event edge detection
EVSYS_CHANNEL_PATH_ASYNCHRONOUS | // Set event path as asynchronous
EVSYS_CHANNEL_EVGEN(EVSYS_ID_GEN_RTC_CMP_0) | // Set event generator (sender) as external RTC compare 0
EVSYS_CHANNEL_CHANNEL(0); // Attach the generator (sender) to channel 0
RTC->MODE2.EVCTRL.reg = RTC_MODE2_EVCTRL_ALARMEO0; // Set RTC to generate an event output on ALARM0
TC4->COUNT32.EVCTRL.reg = TC_EVCTRL_TCEI | // Enable asynchronous input events
TC_EVCTRL_EVACT_RETRIGGER; // Retrigger the TC4 timer upon receiving an event
// Enable RTC and Timer TC4 ////////////////////////////////////////////////////////
TC4->COUNT32.CTRLA.bit.ENABLE = 1; // Enable TC4
while (TC4->COUNT32.STATUS.bit.SYNCBUSY); // Wait for synchronization
TC4->COUNT32.READREQ.reg = TC_READREQ_RCONT | // Enable a continuous read request
TC_READREQ_ADDR(0x10); // Offset of the 32-bit COUNT register
while(TC4->COUNT32.STATUS.bit.SYNCBUSY); // Wait for (read) synchronization
RTC->MODE2.CTRL.bit.ENABLE = 1; // Enable the RTC
while (RTC->MODE2.STATUS.bit.SYNCBUSY); // Wait for synchronization
}
void loop()
{
uint32_t microseconds = TC4->COUNT32.COUNT.reg; // Read the TC4 timer's COUNT register
uint32_t milliseconds = microseconds / 1000; // Calculate the elapsed milliseconds
microseconds %= 1000; // Calculate the reamaining elapsed microseconds
while (RTC->MODE2.STATUS.bit.SYNCBUSY); // Wait for read synchronization (6ms delay)
timeStamp.reg = RTC->MODE2.CLOCK.reg; // Read the RTC calendar clock
SerialUSB.print(F("Year: ")); // Display the timestamp results
SerialUSB.println(2000 + timeStamp.bit.year); // Use reference year: 2000
SerialUSB.print(F("Month: "));
SerialUSB.println(timeStamp.bit.month);
SerialUSB.print(F("Day: "));
SerialUSB.println(timeStamp.bit.day);
SerialUSB.print(F("Hour: "));
SerialUSB.println(timeStamp.bit.hour);
SerialUSB.print(F("Minute: "));
SerialUSB.println(timeStamp.bit.minute);
SerialUSB.print(F("Second: "));
SerialUSB.println(timeStamp.bit.second);
SerialUSB.print(F("Millisecond: "));
SerialUSB.println(milliseconds);
SerialUSB.print(F("Microsecond: "));
SerialUSB.println(microseconds);
SerialUSB.println();
delay(994); // Wait for 1 second (1000ms - 6ms from read synchronization)
}
void RTC_Handler(void) // Event System interrupt handler
{
if (RTC->MODE2.INTFLAG.bit.ALARM0) // Check if an ALARM0 interrupt has occured
{
RTC->MODE2.INTFLAG.bit.ALARM0 = 1; // Clear the interrupt flag
PORT->Group[g_APinDescription[LED_BUILTIN].ulPort].OUTTGL.reg = 1 << g_APinDescription[LED_BUILTIN].ulPin; // Toggle the LED_BUILTIN
if (!RTC->MODE2.STATUS.bit.SYNCBUSY) // Test if synchronization isn't in operation
{
RTC->MODE2.Mode2Alarm[0].ALARM.bit.SECOND = (RTC->MODE2.Mode2Alarm[0].ALARM.bit.SECOND + 1) % 60; // Increment the ALARM0 compare register
}
}
}
Interestingly, if the result output in the loop() is synchronized to the RTC ALARM0 interrupt, (in the code above it's asynchronous), the jitter is reduced to around 1 or 2 microseconds.
Slightly off topic but still a similar problem.
I am designing a remote solar powered weather station around the Feather/RFM69. It needs to be asleep for part of every measurement cycle to reduce power. I have been struggling with using the RTC in 32 bit counter mode to wake the device and read the counter to determine time between interrupts. An anemometer will cause a hardware interrupt each revolution so the tick count will be used to determine windspeed. In the even that there is no wind, the RTC will be set to wake up every few seconds and take some other measurements. Attached is just the timer code that should wake up periodically. It runs once and never wakes up again. I have been trying workarounds for days and haven't struck on a fix. The RTC is set to MODE0 with a 32768 clock source. It is set to wake up every 2 seconds and clear the count on match, flash an LED and print out the clock ticks since the last wakeup.
/*
* pg 8/5/20
* Using Adafruit Feather M0 (SAMD21G18A)
*
* Timer setup based on:
* https://community.atmel.com/forum/samd21-rtc-mode0-count-value-freezes-rcont-rreq-set
*
* Set up RTC clock to Mode0, 32 bit counter
* Set clock source to 32768 Hz (xtal controlled) and prescale of 1.
* Set RTC COMP0 reg to n * 32768 to interrupt every n seconds.
* Clear count on COMP0 to make it repeat.
* Clear CMP0 and OVF interrupts (both are set every time).
*
* Found in samd21g18a.h:
* RTC_IRQn = 3, // 3 SAMD21G18A Real-Time Counter (RTC)
*
* Found in component/rtc.h:
* #define RTC_MODE0_CTRL_MATCHCLR_Pos 7 //(RTC_MODE0_CTRL) Clear on Match
* #define RTC_MODE0_CTRL_MATCHCLR (0x1u << RTC_MODE0_CTRL_MATCHCLR_Pos)
*
*/
// storage for counter last time and counter now
uint32_t myCount0 = 0;
uint32_t myCount1 = 0;
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, LOW);
setupRTC();
Serial.begin(115200);
while(!Serial);
Serial.println("\nReady");
}
void loop() {
digitalWrite(LED_BUILTIN, HIGH);
delay(100);
digitalWrite(LED_BUILTIN, LOW);
// just read counter
myCount1 = RTC->MODE0.COUNT.reg - myCount0;
Serial.print("Counts:");
Serial.println(myCount1 - myCount0); // print the delta count between interrupts
uint32_t myCount0 = myCount1;
// go to sleep here
systemSleep();
} // end of main loop
/*************************************************/
// Set up RTC for counting
void setupRTC()
{
// configure the 32768 Hz oscillator
SYSCTRL->XOSC32K.reg = SYSCTRL_XOSC32K_ONDEMAND |
SYSCTRL_XOSC32K_RUNSTDBY |
SYSCTRL_XOSC32K_EN32K |
SYSCTRL_XOSC32K_XTALEN |
SYSCTRL_XOSC32K_STARTUP(6) |
SYSCTRL_XOSC32K_ENABLE;
// attach peripheral clock to 32768 Hz oscillator (1 tick = 1/32768 second)
GCLK->GENDIV.reg = GCLK_GENDIV_ID(2) | GCLK_GENDIV_DIV(0);
GCLK->GENCTRL.reg = (GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_XOSC32K | GCLK_GENCTRL_ID(2));
GCLK->CLKCTRL.reg = (uint32_t)((GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK2 | (RTC_GCLK_ID << GCLK_CLKCTRL_ID_Pos)));
// disable RTC if it is enabled, to allow reconfiguration
RTC->MODE0.CTRL.reg &= ~RTC_MODE0_CTRL_ENABLE;
// trigger RTC software reset
RTC->MODE0.CTRL.reg |= RTC_MODE0_CTRL_SWRST;
// configure RTC in mode 0 (32-bit)
RTC->MODE0.CTRL.reg |= RTC_MODE0_CTRL_PRESCALER_DIV1 | RTC_MODE0_CTRL_MODE_COUNT32;
// set match_clear bit(7) to clear counter for periodic interrupts
// this will probably screw up anything else using the RTC
// See Table 18-1. MODE0 - Mode Register Summary
RTC->MODE0.CTRL.reg |= RTC_MODE0_CTRL_MATCHCLR;
// enter freq correction here as sign (bit7) and magnitude (bits 6-0)
RTC_FREQCORR_VALUE(0x00); // adjust if necessary
// initialize counter & compare values
RTC->MODE0.COUNT.reg = 0;
RTC->MODE0.COMP[0].reg = 0x00010000; // 32768 * 2 seconds
// enable the CMP0 interrupt in the RTC
RTC->MODE0.INTENSET.reg |= RTC_MODE0_INTENSET_CMP0;
// re-enable RTC after reconfiguration and initial scheduling
RTC->MODE0.CTRL.reg |= RTC_MODE0_CTRL_ENABLE;
// enable RTC interrupt in controller
NVIC_SetPriority(RTC_IRQn, 0x00);
NVIC_EnableIRQ(RTC_IRQn);
// change USB/EIC priority to 0x01 to prevent preempting the RTC
//NVIC_SetPriority(USB_IRQn, 0x01);
//NVIC_SetPriority(EIC_IRQn, 0x01);
// enable continuous synchronization
while (RTC->MODE0.STATUS.bit.SYNCBUSY);
RTC->MODE0.READREQ.reg = RTC_READREQ_RREQ | RTC_READREQ_RCONT | 0x0010;
while (RTC->MODE0.STATUS.bit.SYNCBUSY);
} // end setupRTC
// Put SAMD to sleep
void systemSleep()
{
// Disable USB
//USBDevice.detach();
// Set the sleep mode
SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;
// SAMD sleep
__WFI();
//Wake up here
} // end systemSleep
/***** Service the RTC interrupt *****/
void RTC_Handler(void)
{
if(RTC->MODE0.INTFLAG.bit.CMP0)
{
//RTC->MODE0.INTFLAG.reg = 0xFF; // Clear all interrupts
RTC->MODE0.INTFLAG.reg |= 0x81; // Clear CMP0 and OVF interrupts only
}
}
It looks like I solved this problem by adding a bunch of wait for sync statements in the RTC setup. The serial USB port doesn't recover from sleep but I am only using it for debug so I don't know what the count is. I will have to send the tick count by RF link to the receiver while awake to see if, in fact, the count is right. Hoping the RF interface is not affected by the RTC settings.
/*
* pg 8/5/20
* Using Adafruit Feather M0 (SAMD21G18A)
*
* Timer setup based on:
* https://community.atmel.com/forum/samd21-rtc-mode0-count-value-freezes-rcont-rreq-set
*
* Set up RTC clock to Mode0, 32 bit counter
* Set clock source to 32768 Hz (xtal controlled) and prescale of 1.
* Set RTC COMP0 reg to n * 32768 to interrupt every n seconds.
* Clear count on COMP0 to make it repeat.
* Clear CMP0 and OVF interrupts (both are set every time).
*
* Found in samd21g18a.h:
* RTC_IRQn = 3, // 3 SAMD21G18A Real-Time Counter (RTC)
*
* Found in component/rtc.h:
* #define RTC_MODE0_CTRL_MATCHCLR_Pos 7 //(RTC_MODE0_CTRL) Clear on Match
* #define RTC_MODE0_CTRL_MATCHCLR (0x1u << RTC_MODE0_CTRL_MATCHCLR_Pos)
*
*/
// storage for counter last time and counter now
uint32_t myCount0 = 0;
uint32_t myCount1 = 0;
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, LOW);
setupRTC();
//Serial.begin(115200);
//while(!Serial);
//Serial.println("\nReady");
}
void loop() {
digitalWrite(LED_BUILTIN, HIGH);
delay(100);
digitalWrite(LED_BUILTIN, LOW);
delay(100);
// just read counter
myCount1 = RTC->MODE0.COUNT.reg - myCount0;
//Serial.print("Counts:");
//Serial.println(myCount1 - myCount0); // print the delta count between interrupts
uint32_t myCount0 = myCount1;
// go to sleep here
systemSleep();
} // end of main loop
/*************************************************/
// Set up RTC for counting
void setupRTC()
{
// configure the 32768 Hz oscillator
SYSCTRL->XOSC32K.reg = SYSCTRL_XOSC32K_ONDEMAND |
SYSCTRL_XOSC32K_RUNSTDBY |
SYSCTRL_XOSC32K_EN32K |
SYSCTRL_XOSC32K_XTALEN |
SYSCTRL_XOSC32K_STARTUP(6) |
SYSCTRL_XOSC32K_ENABLE;
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
// attach peripheral clock to 32768 Hz oscillator (1 tick = 1/32768 second)
GCLK->GENDIV.reg = GCLK_GENDIV_ID(2) | GCLK_GENDIV_DIV(0);
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
GCLK->GENCTRL.reg = (GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_XOSC32K | GCLK_GENCTRL_ID(2));
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
GCLK->CLKCTRL.reg = (uint32_t)((GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK2 | (RTC_GCLK_ID << GCLK_CLKCTRL_ID_Pos)));
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
// disable RTC if it is enabled, to allow reconfiguration
RTC->MODE0.CTRL.reg &= ~RTC_MODE0_CTRL_ENABLE;
while (RTC->MODE0.STATUS.bit.SYNCBUSY); // Wait for synchronization
// trigger RTC software reset
RTC->MODE0.CTRL.reg |= RTC_MODE0_CTRL_SWRST;
while (RTC->MODE0.STATUS.bit.SYNCBUSY); // Wait for synchronization
// configure RTC in mode 0 (32-bit)
RTC->MODE0.CTRL.reg |= RTC_MODE0_CTRL_PRESCALER_DIV1 | RTC_MODE0_CTRL_MODE_COUNT32;
while (RTC->MODE0.STATUS.bit.SYNCBUSY); // Wait for synchronization
// set match_clear bit(7) to clear counter for periodic interrupts
// this will probably screw up anything else using the RTC
// See Table 18-1. MODE0 - Mode Register Summary
RTC->MODE0.CTRL.reg |= RTC_MODE0_CTRL_MATCHCLR;
while (RTC->MODE0.STATUS.bit.SYNCBUSY); // Wait for synchronization
// enter freq correction here as sign (bit7) and magnitude (bits 6-0)
//RTC_FREQCORR_VALUE(0x00); // adjust if necessary
// initialize counter & compare values
RTC->MODE0.COUNT.reg = 0;
while (RTC->MODE0.STATUS.bit.SYNCBUSY); // Wait for synchronization
RTC->MODE0.COMP[0].reg = 0x00010000; // 32768 * 2 seconds
while (RTC->MODE0.STATUS.bit.SYNCBUSY); // Wait for synchronization
// enable the CMP0 interrupt in the RTC
RTC->MODE0.INTENSET.reg |= RTC_MODE0_INTENSET_CMP0;
while (RTC->MODE0.STATUS.bit.SYNCBUSY); // Wait for synchronization
// re-enable RTC after reconfiguration and initial scheduling
RTC->MODE0.CTRL.reg |= RTC_MODE0_CTRL_ENABLE;
while (RTC->MODE0.STATUS.bit.SYNCBUSY); // Wait for synchronization
// enable RTC interrupt in controller
NVIC_SetPriority(RTC_IRQn, 0x00);
NVIC_EnableIRQ(RTC_IRQn);
// change USB/EIC priority to 0x01 to prevent preempting the RTC
//NVIC_SetPriority(USB_IRQn, 0x01);
//NVIC_SetPriority(EIC_IRQn, 0x01);
// enable continuous synchronization
while (RTC->MODE0.STATUS.bit.SYNCBUSY);
RTC->MODE0.READREQ.reg = RTC_READREQ_RREQ | RTC_READREQ_RCONT | 0x0010;
while (RTC->MODE0.STATUS.bit.SYNCBUSY);
} // end setupRTC
// Put SAMD to sleep
void systemSleep()
{
// Disable USB
//USBDevice.detach();
// Set the sleep mode
SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;
// SAMD sleep
__WFI();
//Wake up here
//USBDevice.attach();
//delay(100);
} // end systemSleep
/***** Service the RTC interrupt *****/
void RTC_Handler(void)
{
if(RTC->MODE0.INTFLAG.bit.CMP0)
{
//RTC->MODE0.INTFLAG.reg = 0xFF; // Clear all interrupts
RTC->MODE0.INTFLAG.reg |= 0x81; // Clear CMP0 and OVF interrupts only
}
}
Well, the RF link still works. I'm getting real data every 2 seconds. However, after looking at the tick count I realize that I am reading the counter after it's been automatically set back to zero by the match (DUH!). So, back to the drawing board.