Go Down

Topic: Zero RTC - getting a 1Hz interrupt (Read 744 times) previous topic - next topic

Phil-D

Hi

After some fun setting up a smart clock that shows time and weather information using an LED matrix, which is working great, and I wrote some info here about NTP and setting the time accurately, I was struggling adding much more to it code wise as memory was short using an 328P, so thought I would upgrade the project to a SAM21.

This means I now have an option of using an external Dallas 3231 clock and implementing an interrupt from it's 1Hz output as before, or using the internal RTC clock of the SAM21 and reducing the component count.  The SAM21 will not be as accurate, but I can simply resync the clock a bit more often using NTP.

So I've tried the RTCZero library and the RTC is working, however I could do with a 1Hz interrupt from the RTC so I can tick up the seconds on the display.  I've tried the alarm option with a mask, however that only gets me a 1 minute event.  I think it should be possible to get the RTC to function in calendar mode and give a 1Hz output, but it's bit beyond me how to set it.

Anyone have any ideas?

I tried reading getSeconds in the loop to detect when the next second arrives, however that was quite an overhead in the loop. 

Regards

Phil

Phil-D

#1
Jun 17, 2018, 04:30 pm Last Edit: Jun 17, 2018, 04:33 pm by Phil-D
Hi

To answer my own post I found some info here https://forum.arduino.cc/index.php?topic=492670.0

This means the RTC isn't running in calendar/time mode, but I do get a 1Hz second, so can simply hold my own unix epoch counter and just increment on each second, then parse it to show the date/time.  Not ideal if wanting to sleep the microcontroller as would need to wake each second to keep count, so will have a play and see if I can implement an interrupt whilst still retaining calendar/clock mode.

Regards

Phil

MartinL

#2
Jun 18, 2018, 12:22 pm Last Edit: Jun 18, 2018, 12:39 pm by MartinL
Hi Phil-D,

The trick to getting the RTC interrupt to fire every second (1Hz) in clock/calendar mode (Mode 2), is to increment the ALARM0 compare register (SECOND bitfield) each time the RTC_Hander() interrupt service routine is called:

Code: [Select]
RTC->MODE2.Mode2Alarm[0].ALARM.bit.SECOND = (RTC->MODE2.Mode2Alarm[0].ALARM.bit.SECOND + 1) % 60;   // Increment the ALARM0 compare register
In this way, the ALARM0 compare register is set one second ahead of the clock/canlendar counter and the RTC interrupt is therefore called every second, each time the counter increments. The modulo 60 resets the compare register back to 0 after 60 seconds have elapsed.

Here's the code:

Code: [Select]
// Set-up RTC interrupt to fire every second (1Hz) in clock/calendar mode (Mode 2)
void setup(){
  SerialUSB.begin(115200);
  while (!SerialUSB);
 
  PORT->Group[PORTA].DIRSET.reg = PORT_PA17;  // Set digital pin D13 to an OUTPUT
 
// Configure clock source and clock generators (gclk.h)------------------------   
  GCLK->GENDIV.reg =  GCLK_GENDIV_ID(4) |   // Select GLCK4
                      GCLK_GENDIV_DIV(4);   // Select clock divisor to divide by 32 (2 ^ (4 + 1))
  while (GCLK->STATUS.bit.SYNCBUSY);        // Wait for synchronization
 
  GCLK->GENCTRL.reg = GCLK_GENCTRL_ID(4) |        // Select GCLK4
                      GCLK_GENCTRL_SRC_XOSC32K |  // Select GCLK source as the external 32.768kHz crystal                     
                      GCLK_GENCTRL_IDC |          // Improve duty cycle for odd div factors
                      GCLK_GENCTRL_RUNSTDBY |     // Enable run standby mode 
                      GCLK_GENCTRL_DIVSEL |       // Set GLCK divisor as 2 to the power of (divisor) value                       
                      GCLK_GENCTRL_GENEN;         // Enable GCLK4
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization
     
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_GEN_GCLK4 |  // Select GCLK4
                      GCLK_CLKCTRL_ID_RTC |     // Connect to the RTC
                      GCLK_CLKCTRL_CLKEN;       // Enable GCLK4
  while (GCLK->STATUS.bit.SYNCBUSY);            // Wait for synchronization

// RTC configuration (rtc.h)--------------------------------------------------                                             
  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

// Configure RTC interrupts ------------------------------------------
  NVIC_SetPriority(RTC_IRQn, 0);    // Set the Nested Vector Interrupt Controller (NVIC) priority for RTC
  NVIC_EnableIRQ(RTC_IRQn);         // Connect RTC to Nested Vector Interrupt Controller (NVIC)
 
  RTC->MODE2.INTENSET.reg = RTC_MODE2_INTENSET_ALARM0;            // Set Mode 2 alarm interrupt                           

// Enable RTC--------------------------------------------------------------
  RTC->MODE2.CLOCK.reg = 0x0000000;                               // Set the RTC counter to zero
  while (RTC->MODE2.STATUS.bit.SYNCBUSY);                         // Wait for synchronization
 
  RTC->MODE2.CTRL.reg |= RTC_MODE2_CTRL_ENABLE;                   // Turn on the RTC
  while (RTC->MODE2.STATUS.bit.SYNCBUSY);                         // Wait for synchronization
  SerialUSB.println(F("Setup complete..."));
}

void loop(){}

void RTC_Handler(void)
{
  if (RTC->MODE2.INTFLAG.bit.ALARM0 && RTC->MODE2.INTENSET.bit.ALARM0)    // Check for ALARM0 interrupt
  { 
     SerialUSB.print(F("RTC Handler  "));
     SerialUSB.println(RTC->MODE2.Mode2Alarm[0].ALARM.bit.SECOND);
     PORT->Group[PORTA].OUTTGL.reg = PORT_PA17;                           // Toggle digital pin D13
     RTC->MODE2.INTFLAG.reg = RTC_MODE2_INTFLAG_ALARM0;                   // Reset interrupt flag     
     RTC->MODE2.Mode2Alarm[0].ALARM.bit.SECOND = (RTC->MODE2.Mode2Alarm[0].ALARM.bit.SECOND + 1) % 60;   // Increment the ALARM0 compare register
     while (RTC->MODE2.STATUS.bit.SYNCBUSY);                              // Wait for synchronization
  }
}

The code toggles the Zero's LED on digtial pin 13, so it'll go on for 1 second, off for 1 second and so on... The ALARM0 compare register value is also output on the console.

Phil-D

Hi MartinL

Many thanks for the code and examples, I will give that a go.  I did wonder if it would be viable to continually set an alarm for a second ahead so thank you for the code examples I will give that a go.

I since found some online documentation from Atmel and there is mention of a periodic event for mode 2 calendar mode, it appears it is the same as setting these events in any RTC mode, except they say

Quote
For the RTC to operate correctly in calendar mode, this frequency must be 1KHz, while the RTC's internal prescaler should be set to divide by 1024.
Which makes sense of course.  I did try setting a normal periodic event but it seemed to be running slower than a second, but I will look at the code again and check what I was setting. 

Regards

Phil

MartinL

#4
Jun 21, 2018, 09:38 am Last Edit: Jun 21, 2018, 09:40 am by MartinL
Hi Phil,

On the SAMD21 you've got the choice of three, 32.768kHz oscillators. Namely the internal OSC32K and ultra low power OSCULP32K oscillators, as well as the external crystal XOSC32K.

Of the three, the crystal clock XOSC32K is by far the most accurate at around 20 parts per million (ppm), which is what the example code above selects.

In the RTC clock/calendar mode the counter must be clocked at 1Hz, as specified in the datasheet:

Quote
The selected clock source and RTC prescaler must be configured to provide a 1Hz clock to the counter for correct operation in this mode.
To achieve this the cyrstal frequency of 32.768kHz is divided down by the generic clock divider by 32, this creates the 1.024kHz GCLK4 signal used to clock the RTC peripheral. The RTC prescaler then divides this down further by 1024, thereby generating the 1Hz clock for the clock/calendar counter.

Kind regards,
Martin

Phil-D

Hi MartinL

Many thanks, it was a quote out of context I added, that was in the data sheet about the RTC and setting interrupts and the warning when in Mode2 (Calendar mode) and setting periodic interrupts we had to work the timings/setup that way in order to keep the RTC running at 1KHz, which of course makes sense.  It was just it indicates periodic interrupts are possible in Mode 2.

I'm still trying to work out how to set it up.

Regards

Phil

MartinL

Hi Phil,

By the way, there's one small issue I'd thought I'd mention. As the RTC peripheral is clocked using generic clock 4 (GCLK4) at 1.024kHz, this leads to a large register synchronization delay while accessing some of the RTC's registers. In effect the whole RTC peripheral is only being clocked at 1.024kHz.

In the above example, the delay occurs after accessing the RTC's ALARM0 compare register in the RTC_Handler() function, while waiting for the SYNCBUSY bit in the RTC's STATUS register to clear:

Code: [Select]
RTC->MODE2.Mode2Alarm[0].ALARM.bit.SECOND = (RTC->MODE2.Mode2Alarm[0].ALARM.bit.SECOND + 1) % 60;   // Increment the ALARM0 compare register
while (RTC->MODE2.STATUS.bit.SYNCBUSY);

However, as the RTC_Handler() function is only being called every second, it's possible to instead just test that the SYNCYBUSY bit in the RTC's STATUS register has been cleared:

Code: [Select]
if (!RTC->MODE2.STATUS.bit.SYNCBUSY)                                 // Check that no RTC register is waiting for synchronization
{
   RTC->MODE2.Mode2Alarm[0].ALARM.bit.SECOND = (RTC->MODE2.Mode2Alarm[0].ALARM.bit.SECOND + 1) % 60;   // Increment the ALARM0 compare register
}

The prevents the synchronization delay from holding up your sketch and works provided you don't call on the RTC's registers directly after the RTC_Handler() function returns, or in other words while register synchronization is still taking place.

Kind regards,
Martin

Phil-D

Hi  MartinL

Many thanks.

I've got some code working based on your examples and getting a 1Hz tick.

I think I understand about the SYNCBUSY although in the change you've suggested if that evaluates to false then wouldn't it exit the RTC_Handler without having set the next alarm second, meaning no more alarms and the 1Hz event stops?

Regards

Phil




MartinL

Hi Phil,

The synchronization delay for the RTC peripheral being clocked at 1.024kHz is around 5 to 6ms.

After writing to the ALARM0 compare register, the SYNCBUSY bit in the RTC's STATUS register will be set to 1 for this 5 to 6ms duration, automatically being cleared to 0 once synchronization is complete.

Using the while loop your code will block for the duration of this synchronzation delay.

As the RTC_Handler() function is only called every second, the SYNCBUSY bit will have been cleared by the time you get round to reading the ALARM0 compare regsiter again. Therefore in this instance it's possible to forgo the blocking while loop, provided that the RTC's registers aren't accessed for 6ms afterwards.

In this case, the if statement isn't strictly necessary, it just checks that the SYNCBUSY bit has cleared before allowing access to the ALARM0 compare register. In more maginal timing instances however, it can be used as a failsafe to prevent access to a synchronizing register, as accessing a synchronizing register has the potential to stall the peripheral bus.

Kind regards,
Martin

Phil-D

Hi Martin

I understand now, learning more everyday, many thanks.

Another thing I've noticed is the 32.768kHz crystal on the Zero isn't used for the main clock, seems to be only used for the RTC.  I can short the crystal and see the RTC stops in its tracks but the code continues normally.

Is there a reason for this?  I assume the boot-loader is setting up the internal oscillator as the main clock.  Given the crystal is going to be more accurate than any internal oscillator I was surprised to see it not in use for the main clock.

Regards

Phil

david_prentice

As a general rule,   an embedded processor will run on a high  speed clock and go to sleep often.

You can wake up an RC with PLL quicker than you can start  a HF crystal.
So a low power app will run on RC,  sleep often.    Calibrate itself from the LF crystal.    The RTC can run off the LF crystal at very low power even when the main MCU is fast asleep.

My speculation.   Someone more knowledgable might clarify.

David.

Phil-D

Hi David

Now that was my thoughts exactly, however as the 32kHz crystal remains powered up during sleep (in order to provide wake interrupts), that makes it perfect for use, as tehre is no start-up time for the 32kHz clock as it's always running.

I've done some digging, in the startup.c file for the Zero boards is a #if defined(CRYSTALLESS) check, so the board can be configured to use internal clocks or the external crystal.  Now the only board I can see defining that in the boards.txt file for Samd is an Adafruit variant, the other boards should be using the external crystal.  So perhaps a bug somewhere in that initialisation or CRYSTALLESS is being defined somewhere else.

Regards

Phil

david_prentice

I have never looked.
I have an early Zero Pro and just bought  a Chinese M0 clone.

Perhaps I will have a play with the RTC.

Phil-D

Hi

I can confirm the startup.c file is compiling the correct sections, i.e. it isn't compiling as CRYSTALLESS.  I've been looking at Atmel Studio that has a graphical option for setting up the clocks but not made much sense of it.

At the moment either by design or bug, the Arduino Zero boards (and clones) have an external crystal that isn't being used, unless the RTC is enabled.

Regards

Phil

david_prentice

It is not uncommon for dev boards.   Probably for the low power reasons suggested earlier.
Especially when there are many runtime clock possibilities e.g. on Xmega or ARM.

Simple 8-bit MCUs tend to select clocks by programming fuses.  With little more than a prescaler.  Some have no runtime options at all.

David.

Go Up