Electronics > Projects, Designs, and Technical Stuff
DS3231M - Improve accuracy with Aging register?
Peabody:
I'm a hobbyist, and don't have any of that gear. I had thought about somehow multiplying the 1Hz ouput frequency with a PLL or whatever, and counting the higher frequency cycles. But the M does a temperature correction every 10 seconds, so I suspect the 1Hz wanders a good bit in the short run.
But if the Aging adjustment is nothing more than an adjustment to a down counter, then if you measure how far off it is over, say, a week, it should be possible to correct by the right amount, so one adjustment would get you pretty close. The datasheet says the least significant change to Aging is 0.12ppm, which would be 0.07 second over a week if my math is right. But it's never going to be really exact because the temperature adjustment isn't going to perfectly compensate for the temperature variability of the oscillator.
Georgy.Moshkin:
for measurements I've used OCXO module, price was around $20. It works from 5v and provides stable output in MHz range. This method requires good knowledge of some microcontroller to count pulses correctly. You metod will work too, I can imagine it may be pretty convenient if time from all sources can be photographed using smartphone. and fit on a single photo. You can use file creation time and time from photographed lcd screen(s). Or maybe there is some better approach.
Peabody:
I'm still waiting for my DS3231Ms from the Far East, but have finished the software I'll be using to calibrate them. It works on the SN module I already have, so I think it will also work for the Ms.
I was looking for a way to sync the RTC to my computer's time. And it turns out that VBScript in Windows has a SendKeys function that can be used to stuff the system timestamp into the keyboard input buffer of a running application - the Serial Monitor in this case. So I have a short script that first waits for the current second to change, then dumps the timestamp to the Serial Monitor, and the Arduino in turn writes that info to the RTC. It all happens very fast.
--- Code: ---' 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
--- End code ---
You would want to first sync the computer to some reliable NTP server. Then the Aduino sketch boots up and displays the current RTC timestamp, followed by 12 seconds of time ticks which can be compared with the PC's clock to see how out of sync they are. Then executing Timestamp.vbs will sync the RTC to the computer.
You can also display the current Aging register setting, or change it to a new value. The idea is that tweaking the Aging register over time could get the RTC as close as possible to perfect time. If you can get it to one second a week, that's 1.6ppm or about a minute a year, which is pretty good. But you can detect out-of-sync by as little as about 0.1 second, and the SN datasheet says changing Aging by 1 produces a 0.1ppm change, so in theory you could get it to be quite accurate.
Below is the Arduino sketch for an Uno or Nano. No library required except Wire.h. I'm hoping someone here will be able to play with it and post what you think.
--- Code: ---/*
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;
}
--- End code ---
tooki:
Why not use an ESP32 to sync NTP directly on the MCU? Then you could compare the times on the same device, no Windows involved, avoiding the potential of Windows not being synced. On the ESP32 you could trigger an NTP sync whenever you want.
Peabody:
Because I don't have an ESP32. And others who might want to do this might not have an ESP32. Of course they might not have an Arduino either, or Windows.
Navigation
[0] Message Index
[#] Next page
[*] Previous page
Go to full version