In embedded Linux deployments with intermittent connectivity, maintaining accurate system time becomes particularly challenging. The standard NTP synchronization works perfectly when online, but fails during extended offline periods where system clocks drift unpredictably. Meanwhile, hardware RTCs demonstrate consistent drift patterns that could theoretically be compensated for if properly characterized.
The Linux timekeeping ecosystem offers several components that partially address this need:
# Standard NTP configuration (partial)
server pool.ntp.org iburst
driftfile /var/lib/ntp/ntp.drift
However, as noted in the question, neither stock ntpd nor chrony properly handle the RTC drift compensation scenario. The kernel's 11-minute mode actively works against our requirements by overwriting the RTC with system time.
Here's a solution combining several components:
#!/bin/bash
# /usr/local/bin/timesync-manager
# Configuration
RTC_DRIFT_RATE=0.00042 # ppm, measured during calibration
LAST_NTP_SYNC=/var/run/last_ntp_sync
SYNC_THRESHOLD=300 # seconds between NTP attempts
# Check network connectivity
if ping -c1 -W2 pool.ntp.org &>/dev/null; then
# Online mode - use NTP
if [ ! -f "$LAST_NTP_SYNC" ] || \
[ $(($(date +%s) - $(stat -c %Y "$LAST_NTP_SYNC"))) -gt $SYNC_THRESHOLD ]; then
if ntpdate -u pool.ntp.org; then
touch "$LAST_NTP_SYNC"
# Disable 11-minute mode
echo 0 > /sys/class/rtc/rtc0/since_epoch
fi
fi
else
# Offline mode - use compensated RTC
RTC_TIME=$(hwclock --utc --get | awk '{print $1}')
DRIFT_COMPENSATED=$((RTC_TIME + (RTC_TIME * RTC_DRIFT_RATE)))
date -s @$DRIFT_COMPENSATED
fi
For production deployment, consider these integration points:
# Systemd service unit
[Unit]
Description=Hybrid Time Synchronization
After=network.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/timesync-manager
[Install]
WantedBy=multi-user.target
# Add to crontab
*/5 * * * * /usr/local/bin/timesync-manager
Measuring RTC drift requires a controlled environment:
# Calibration script (run during initial setup)
#!/bin/bash
START=$(date +%s)
hwclock --hctosys --utc
sleep 86400 # 24 hours
END=$(date +%s)
RTC_END=$(hwclock --utc --get | awk '{print $1}')
DRIFT_RATE=$(echo "scale=8; ($RTC_END - $END) / ($END - $START)" | bc)
echo "Calculated drift rate: $DRIFT_RATE ppm" > /etc/rtc-drift.conf
For environments where neither NTP nor RTC provide sufficient accuracy:
- MSF/DCF77 receivers (though indoor reception can be problematic)
- Temperature-compensated RTC modules (like DS3231)
- LoRaWAN-based time synchronization
Implement logging to track synchronization performance:
# Enhanced timesync-manager with logging
LOG_FILE=/var/log/timesync.log
log() {
echo "$(date -u '+%Y-%m-%d %H:%M:%S UTC') - $1" >> $LOG_FILE
}
if ping -c1 -W2 pool.ntp.org &>/dev/null; then
log "Network available, attempting NTP sync"
# ... rest of online logic ...
else
log "Offline mode, applying RTC drift compensation"
# ... rest of offline logic ...
fi
Maintaining accurate timekeeping in embedded Linux systems presents unique challenges when network connectivity is unreliable. Traditional NTP solutions work perfectly when online, but become ineffective during extended offline periods. Meanwhile, hardware RTCs (Real-Time Clocks) exhibit consistent drift patterns that vary between devices.
Standard timekeeping approaches have several shortcomings:
# Typical NTP configuration (doesn't address offline scenarios)
server 0.pool.ntp.org iburst
server 1.pool.ntp.org iburst
server 2.pool.ntp.org iburst
The kernel's 11-minute mode (rtc sync) actively prevents long-term RTC drift tracking by overwriting RTC values with system time. This behavior is hard-coded in current Linux kernels.
Here's a solution combining several components:
#!/bin/bash
# RTC calibration script (run during initial deployment)
START=$(date +%s)
sleep 86400 # Wait 24 hours
END=$(date +%s)
RTC_END=$(hwclock --utc --getepoch)
DRIFT_RATE=$(( ($END - $START) - ($RTC_END - $RTC_START) ))
echo $DRIFT_RATE > /etc/rtc_drift.conf
Chrony can be configured to handle both online and offline scenarios:
# /etc/chrony/chrony.conf
server ntp.example.com iburst
rtcdevice /dev/rtc0
rtcautotrim 0 # Disable automatic RTC adjustment
rtcsync # Enable RTC synchronization
makestep 1.0 3 # Allow large time jumps initially
A Python-based solution to handle offline compensation:
import time
import subprocess
from datetime import datetime, timedelta
def get_rtc_drift():
try:
with open('/etc/rtc_drift.conf', 'r') as f:
return float(f.read().strip())
except:
return 0.0
def sync_with_rtc():
drift = get_rtc_drift()
rtc_time = subprocess.check_output(['hwclock', '--utc', '--getepoch'])
adjusted_time = datetime.fromtimestamp(float(rtc_time)) +
timedelta(seconds=drift*(time.time()-last_sync))
subprocess.call(['date', '-s', adjusted_time.isoformat()])
while True:
try:
# Check NTP connectivity
subprocess.check_call(['ntpdate', '-q', 'pool.ntp.org'])
time.sleep(300) # Normal NTP sync interval
except subprocess.CalledProcessError:
sync_with_rtc()
time.sleep(60) # More frequent checks when offline
Key points for successful implementation:
- Calibrate RTC drift for at least 24 hours before deployment
- Store drift rates in non-volatile storage
- Consider adding watchdog timers to detect daemon failures
- For critical systems, implement redundant time sources (DCF77/MSF receivers)
When absolute precision is required:
# Sample configuration for DCF77 receiver
ln -s /dev/ttyUSB0 /dev/dcf77
ntpd -p /dev/dcf77 -D 2 -f /etc/ntp.drift -c /etc/ntp.conf