Real Time Clock
The Real-Time Clock (RTC) runs off a small backup battery or supercapacitor on the VBAT pin and has its own crystal, so it keeps ticking even when the main system is powered off. Use it to keep wall-clock time across power cycles, schedule wake-ups from Sleep/Shutdown, or write timestamped logs that survive reboots.
If the backup battery is drained or the RTC has never been initialized, the RTC's time will be invalid. Check rtc.IsValid and re-set the time if needed.
NuGet packages: GHIElectronics.TinyCLR.Devices.Rtc for the RTC API, and GHIElectronics.TinyCLR.Native for SystemTime.
Reading and setting the RTC
The example below initializes the RTC if needed, syncs the system clock from it, then prints both clocks side-by-side:
using GHIElectronics.TinyCLR.Devices.Rtc;
using GHIElectronics.TinyCLR.Native;
using System;
using System.Diagnostics;
using System.Threading;
var rtc = RtcController.GetDefault();
if (rtc.IsValid) {
Debug.WriteLine("RTC is valid");
SystemTime.SetTime(rtc.Now);
}
else {
Debug.WriteLine("RTC is invalid — initializing");
var initial = new DateTime(2026, 1, 1, 11, 11, 11);
rtc.Now = initial;
SystemTime.SetTime(initial);
}
while (true) {
Debug.WriteLine("System: " + DateTime.Now);
Debug.WriteLine("RTC: " + rtc.Now);
Thread.Sleep(1000);
}
Once SystemTime is synced from the RTC, DateTime.Now and rtc.Now should match within a few milliseconds.
Clock source
When the RTC initializes, it tries to use an external 32.768 kHz crystal first. That's the recommended setup — it gives accurate time with low drift. If no crystal is present, the RTC falls back to an internal RC oscillator, which is less accurate and drifts with temperature changes.
VBAT and charging
VBAT typically needs 1.2V to 3.6V for correct operation. The RTC controller can charge an attached supercapacitor in two charging modes, or leave the rail un-charged for a primary coin cell.
Set the charge mode correctly for your backup component.
- Coin cells (CR2032 and similar lithium primaries): use
BatteryChargeMode.None. Charging a non-rechargeable cell can damage it and cause it to leak. - Supercapacitors: use
BatteryChargeMode.SloworBatteryChargeMode.Fast. Without charging, a supercap won't retain time or battery-backed memory across power-off.
using GHIElectronics.TinyCLR.Devices.Rtc;
var rtc = RtcController.GetDefault();
rtc.SetChargeMode(BatteryChargeMode.None); // Lithium coin cell — no charging.
rtc.SetChargeMode(BatteryChargeMode.Slow); // Supercap, ~5 kΩ series resistor.
rtc.SetChargeMode(BatteryChargeMode.Fast); // Supercap, ~1.5 kΩ — used on SITCore Dev Boards.
System clock
DateTime.Now reads the system clock, which is a separate clock from the RTC. The system clock starts at zero on every power-up, so until you call SystemTime.SetTime(...) the time and date are wrong.
using GHIElectronics.TinyCLR.Native;
using System;
SystemTime.SetTime(rtc.Now); // From RTC.
SystemTime.SetTime(new DateTime(2026, 1, 1, 0, 0, 0)); // Or set explicitly.
For most applications the boot sequence is: read RTC → sync system clock from RTC → use DateTime.Now everywhere from then on.
SNTP
For Internet-connected devices, SNTP (Simple Network Time Protocol) gives you accurate time from an NTP server. Use it on startup to set the RTC, then let the RTC carry the time forward through power cycles.
The example below queries pool.ntp.org over UDP and returns the current UTC time (with optional offset to convert to local).
using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
public static DateTime GetNetworkTime(int hoursOffsetFromUtc = 0) {
const string ntpServer = "pool.ntp.org";
var ntpData = new byte[48];
// LeapIndicator = 0, VersionNum = 3, Mode = 3 (Client).
ntpData[0] = 0x1B;
var addresses = Dns.GetHostEntry(ntpServer).AddressList;
var ipEndPoint = new IPEndPoint(addresses[0], 123);
var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
socket.Connect(ipEndPoint);
Thread.Sleep(1); // Required pause on TinyCLR before send.
socket.Send(ntpData);
socket.Receive(ntpData);
socket.Close();
// Transmit Timestamp is at bytes 40-47: 4 bytes integer seconds, 4 bytes fractional.
ulong intPart = (ulong)ntpData[40] << 24 | (ulong)ntpData[41] << 16
| (ulong)ntpData[42] << 8 | (ulong)ntpData[43];
ulong fractPart = (ulong)ntpData[44] << 24 | (ulong)ntpData[45] << 16
| (ulong)ntpData[46] << 8 | (ulong)ntpData[47];
var milliseconds = (intPart * 1000) + ((fractPart * 1000) / 0x100000000L);
// NTP epoch is 1900-01-01.
var networkDateTime = new DateTime(1900, 1, 1).AddMilliseconds((long)milliseconds);
return networkDateTime.AddHours(hoursOffsetFromUtc);
}
Usage — the argument is the number of hours to add to UTC for your local timezone (e.g. -5 for US Eastern):
var localTime = GetNetworkTime(-5);
Battery-backed memory
SITCore devices include 4 KB of RAM that survives power-off as long as VBAT stays powered. It's exposed through the RTC controller — useful for storing a small amount of state across reboots without needing flash writes.
using GHIElectronics.TinyCLR.Devices.Rtc;
using System.Diagnostics;
var rtc = RtcController.GetDefault();
Debug.WriteLine("Size: " + rtc.BackupMemorySize); // 4096
var writeData = new byte[] { 1, 2, 3, 4, 5 };
rtc.WriteBackupMemory(writeData);
var readData = new byte[5];
rtc.ReadBackupMemory(readData);
foreach (var b in readData)
Debug.WriteLine(b.ToString()); // 1, 2, 3, 4, 5
Battery-backed memory is completely unmanaged — no allocation, disposal, or GC. The read/write methods just copy bytes. Data persists until you overwrite it or VBAT loses power.
The read and write methods come in three overloads each:
WriteBackupMemory(byte[] sourceData);
WriteBackupMemory(byte[] sourceData, uint destinationOffset);
WriteBackupMemory(byte[] sourceData, uint sourceOffset, uint destinationOffset, int count);
ReadBackupMemory(byte[] destinationData);
ReadBackupMemory(byte[] destinationData, uint sourceOffset);
ReadBackupMemory(byte[] destinationData, uint destinationOffset, uint sourceOffset, int count);
Calibration
If your RTC drifts noticeably over time (typical with the internal RC, less so with a crystal), use Calibrate to add or remove ticks every 32 seconds.
rtc.Calibrate(pulse);
pulse— ticks to add (positive) or remove (negative) every 32 seconds.0disables calibration.- Range —
±512, corresponding to roughly −487.1 ppm to +488.5 ppm of frequency adjustment.
A drift of one second per day is about 11.6 ppm — well within calibration range. Measure your actual drift over several hours, then dial in pulse to compensate.
API reference
| Namespace | Description |
|---|---|
| GHIElectronics.TinyCLR.Devices.Rtc | Real-time clock controller classes |