Read Synchronization and Atomic References

Today I’m going to revisit the Real Time Counter (RTC) in the SAMD21 microcontroller. When running in real time clock mode (mode 2) a single register holds the date/time value with separate bit fields for year, month, day, hour, and seconds. The value can be printed out with this code:

   char dtstamp[20];
   sprintf(dtstamp, "%.2d/%.2d/%.2d %.2d:%.2d:%.2d",
          RTC->MODE2.CLOCK.bit.MONTH,
          RTC->MODE2.CLOCK.bit.DAY,
          RTC->MODE2.CLOCK.bit.YEAR,
          RTC->MODE2.CLOCK.bit.HOUR,
          RTC->MODE2.CLOCK.bit.MINUTE,
          RTC->MODE2.CLOCK.bit.SECOND);
  Serial.println(dtstamp);

But there are some issues with this code. First, consider the case where minutes are just incrementing. If SECOND is read before the minutes increment and MINUTE is read afterwards the time value will be wrong (59 seconds high) however if SECOND is read after the minutes increment and MINUTE is read before the time value will also be wrong (59 seconds low). Now it is possible as a result of optimization that the CLOCK value will only be fetched once, but there is no guarantee. For that reason it is better to force the register to be read only once by using a temporary variable to hold the value:

  char dtstamp[20];
  // Do atomic read of the time.
  RTC_MODE2_CLOCK_Type time; 
  time.reg =  RTC->MODE2.CLOCK.reg;
  sprintf(dtstamp, "%.2d/%.2d/%.2d %.2d:%.2d:%.2d",
          time.bit.MONTH,
          time.bit.DAY,
          time.bit.YEAR,
          time.bit.HOUR,
          time.bit.MINUTE,
          time.bit.SECOND);
  Serial.println(dtstamp);

But we aren’t done. The CLOCK register requires read synchronization. Because the register is in a different clock domain (much slower) than the CPU, the read is held up until the contents can be synchronized across the clock domains. I’ve measured this to be more than five milliseconds. During this time the processor is stalled. If there are any interrupts requested, they will be blocked until the instruction executes. A delay this long can easily cause interrupts to be missed or handled too slowly.

To solve this issue, there is a register that starts the read request but doesn’t block when it is written. A second register can be polled to see if the synchronization is still in progress. Once the data has been synchronized the CLOCK register can be read without delay. Interrupts are never blocked with this technique. The code is now:

  REG_RTC_READREQ |= RTC_READREQ_RREQ;           // start read request
  while (REG_RTC_STATUS & RTC_STATUS_SYNCBUSY) { ; }  // synchronize

  // do a nicely formatted display of the date and time
  char dtstamp[20];
  // local variable so we can do atomic read of all fields.
  RTC_MODE2_CLOCK_Type time; 
  time.reg =  RTC->MODE2.CLOCK.reg;
  sprintf(dtstamp, "%.2d/%.2d/%.2d %.2d:%.2d:%.2d",
          time.bit.MONTH,
          time.bit.DAY,
          time.bit.YEAR,
          time.bit.HOUR,
          time.bit.MINUTE,
          time.bit.SECOND);
  Serial.println(dtstamp);

Note that the read request is necessary to prevent blocking for each time the CLOCK is read. If each field was read from the register in separate statements, read requests would be necessary prior to reading each field!

If blocking is not an issue, the read request is not needed. The microcontroller work correctly, just that the reading of the CLOCK will be slow.