This started when I wanted a way to sync the RTC's time to my PC's time after the PC has been synced to NTP. I found that VBScript has a SendKeys function that will stuff the current timestamp into another program's keyboard buffer, which in this case is the IDE's Serial Monitor. Then the Arduino sketch parses that input and sets the RTC time accordingly. I'll post the VBS script and Arduino sketch below in case anyone is interested.
But then it occurred to me that the same method could be used to tweak the Aging register to the value that makes the RTC most accurate. The script would send the timestamp at the beginning of the PC's second, and the RTC will generate an interrupt at the beginning of its second. With the two seconds values and the millis() values when each occurs, that would give a pretty accurate differential which could be used to adjust Aging. Doing this every 15 minutes, it might even be possible to use some kind of successive approximation to get pretty quickly to the optimum point.
However, I wonder if NTP is consistent enough for this to work. As I understand it, NTP assumes that half of the total round trip delay occurs on the return packet, and I don't see any reason why that would be the case. If the actual return packet delay varies a lot from the theoretical, then that would limit the usefulness of my method. Does anyone have experience with this aspect of NTP that could advise me? An alternative would possibly be a WWVB receiver, but there again propagation delays could vary a lot.
The idea is to get the DS3231 as accurate as possible before deploying it into the field where there will be no communications available to keep the time correct. Well, I guess other than WWVB, if that actually works, and doesn't use to much power.
' Timestamp.vbs
' VBScript for Windows
' This script sends the current system date/time to the keyboard input of the
' Arduino Serial Monitor. If more than one Serial Monitor is open, change the
' "COM" entry below to the specific COM port used for the RTC sketch (i.e. "COM3").
' The Serial Monitor line-end setting must be set to Newline or Carriage Return.
' The "Weekday(dDate,1)" entry is for Sunday being day 1.
' Change to "Weekday(dDate,2)" to make Monday day 1.
Set WshShell = WScript.CreateObject("WScript.Shell")
WshShell.AppActivate "COM"
oldTime = Time
While oldTime = Time
Wend
WshShell.SendKeys TimeStamp(Now)
Function TimeStamp(dDate)
TimeStamp = "S"&right("0"&second(dDate),2)&"{ENTER}"&"X"&right("0"&Minute(dDate),2) _
&right("0"&Hour(dDate),2)&Weekday(dDate,1)&right("0"&Day(dDate),2) _
&right("0"&Month(dDate),2)&right(Year(dDate),2)&"{ENTER}"
End Function
/*
DS3231TimeCheck.ino
Connect the DS3231 RTC to an Uno or Nano, with the INT/SQW
pin connected to D2. On boot the RTC's time stamp is
displayed on the Serial Monitor, followed by a 12-second
count, each tick triggered by an RTC interrupt, which can
be compared to the PC's clock.
Execute the script Timestamp.vbs to transmit the Windows
PC's time to the RTC. The PC should have first been synced
to an NTP server.
Type "T" to display another 12 RTC clock ticks.
Type "A" to display the current value of the Aging register,
or "An", to assign the value of n to the Aging register,
which must be between -128 and +127.
*/
#include <Wire.h>
#define flagsREG EIFR // Atmega328P flags register
byte Seconds, Min, Control, Status, Count, buff_size;
int8_t Aging; // signed byte
char buff[40];
byte r[13]; // registers read
const byte aPIN = 2; // D2
const byte ticks = 12;
volatile bool Alarm = false;
void setup() {
Serial.begin(57600); // all development was done at this speed
delay(2000);
Wire.begin();
delay(10);
pinMode(aPIN, INPUT_PULLUP);
Wire.beginTransmission(0x68); // read Control and Status registers
Wire.write(0x0E); // and clear alarm enables and flags
Wire.endTransmission();
Wire.requestFrom(0x68, 2);
// Clear /EOSC, A2E, AE1. Set BBSQW, INTCN
Control = (Wire.read() & 0b01111100) | 0b01000100;
// Clear OSF, EN32k, A2F, A1F
Status = Wire.read() & 0b01110100;
updateReg(0x0E); // update Control
updateReg(0x0F); // update Status
Wire.beginTransmission(0x68); // address of DS3231
Wire.write(7); // select register = Alarm1 seconds
Wire.write(0x80); // alarm on each second
Wire.write(0x80);
Wire.write(0x80);
Wire.write(0x80);
Wire.endTransmission();
noInterrupts();
flagsREG = 3; // clear any flags on both pins
attachInterrupt(digitalPinToInterrupt(aPIN),rtcISR, FALLING);
flagsREG = 3;
interrupts();
Startup();
buff_size = 0;
buff[0] = 0;
}
void loop() {
if(Alarm) {
Seconds++;
if (Seconds == 60) Seconds = 0;
if(Seconds == 0) {
Wire.beginTransmission(0x68); // address DS3231
Wire.write(1); // minutes register
Wire.endTransmission();
Wire.requestFrom(0x68, 1);
Min = bcd2dec(Wire.read());
Serial.print(Min);Serial.print(":");
}
Serial.println(Seconds);
Alarm = false;
updateReg(0x0F); // clear alarm1 flag
Count--;
if (Count == 0) {
Control &= 0xFE; // disable alarms
updateReg(0x0E);
}
}
if(Serial.available()) { // process input from Serial Monitor
char in = Serial.read(); // set end-line option to Newline or CR
if ((in == 13) || (in == 10)) {
buff[buff_size] = 0;
parse_cmd(buff, buff_size);
buff_size = 0;
buff[0] = 0;
}
else {
buff[buff_size] = in;
buff_size += 1;
}
}
}
void parse_cmd(char *cmd, byte cmdsize) {
// Sss seconds // "S" seconds
if ((cmd[0] == 83) && (cmdsize == 3)) {
Wire.beginTransmission(0x68);
Wire.write(0);
Wire.write(inp2bcd(cmd,1));
Wire.endTransmission();
}
// XmmhhWDDMMYY rest of timestamp
else if((cmd[0]==88)&&(cmdsize==12)) { // "X" rest of timestamp
Wire.beginTransmission(0x68);
Wire.write(1);
Wire.write(inp2bcd(cmd,1)); // minutes
Wire.write(inp2bcd(cmd,3)); // hours
Wire.write(cmd[5] - 48); // day of the week
Wire.write(inp2bcd(cmd,6)); // date of the month
Wire.write(inp2bcd(cmd,8) | 0x80); // month & century
Wire.write(inp2bcd(cmd,10)); // short year
Wire.endTransmission();
RTCstamp();
updateReg(0x0F); // clear alarm flags
Control |= 1;
updateReg(0x0E); // enable alarm1
}
else if ((cmd[0]==65)||(cmd[0]==97)){ // "A" Aging
if (cmdsize > 1) {
int k = atoi(&cmd[1]); // get value of string
if ((k < 128) && (k > -129)) { // check for legit value
Aging = k; // convert to signed byte
updateReg(0x10); // write to Aging register
}
else Serial.println ("Invalid Aging Value");
}
Wire.beginTransmission(0x68); // "A" alone prints current value
Wire.write(0x10);
Wire.endTransmission();
Wire.requestFrom(0x68, 1);
Aging = Wire.read();
Serial.print("Aging = "); Serial.println(Aging);
}
else if ((cmd[0]==84)||(cmd[0]==116)) { // "T" enable ticks
Startup();
}
}
void Startup() {
updateReg(0x0F); // clear alarm flags
Control |= 1;
updateReg(0x0E); // enable alarm1
while (!Alarm);
Alarm = false;
updateReg(0x0F); // clear alarm flags
RTCstamp();
}
void RTCstamp() { // print current RTC timestamp
Wire.beginTransmission(0x68);
Wire.write(0);
Wire.endTransmission();
Wire.requestFrom(0x68, 7);
for (byte i = 0; i<7; i++) {
r[i] = bcd2dec(Wire.read());
}
snprintf(buff,40,"%d/%02d/%02d Day%1d %02d:%02d:%02d",r[6]+2000,r[5],r[4],r[3],r[2],r[1],r[0]);
Serial.println(buff);
Seconds = r[0];
Count = ticks;
}
byte bcd2dec(byte n){
n &= 0x7F; // mask out Century bit
return n - 6 * (n >> 4);
}
byte dec2bcd(byte n){
return ((n / 10 * 16) + (n % 10));
}
byte inp2bcd(char *inp, byte seek) {
return (((inp[seek]-48)<<4) + (inp[seek+1] - 48));
}
void updateReg(byte addr) {
Wire.beginTransmission(0x68);
Wire.write(addr);
if(addr == 0x0E) Wire.write(Control); // enable alarm1
else if(addr == 0x0F) Wire.write(Status); // clear alarm flags
else if(addr == 0x10) Wire.write(Aging); // update Aging register
Wire.endTransmission();
}
void rtcISR() {
Alarm = true;
}