SAMD21 RTC millisecond timing

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.

Hi gtsi,

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?

Thanks
Adam

Hi Adam,

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:

0
99999
199999
299999
399999
499999
599999
699999
799999

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

Code part II:

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

Hi MartinL, thank you very much for your efforts. The code is very helpful!

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.