Electronics > Projects, Designs, and Technical Stuff

DS3231M - Improve accuracy with Aging register?

<< < (4/6) > >>

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.

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.

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

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) _
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: ---/*

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
  pinMode(aPIN, INPUT_PULLUP);

  Wire.beginTransmission(0x68);           // read Control and Status registers
  Wire.write(0x0E);                       //   and clear alarm enables and flags
  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

  flagsREG = 3;                           // clear any flags on both pins
  attachInterrupt(digitalPinToInterrupt(aPIN),rtcISR, FALLING);
  flagsREG = 3;

  buff_size = 0;
  buff[0] = 0;

void loop() {

  if(Alarm) {
    if (Seconds == 60) Seconds = 0;
    if(Seconds == 0) {
      Wire.beginTransmission(0x68);       // address DS3231
      Wire.write(1);                      // minutes register
      Wire.requestFrom(0x68, 1);
      Min = bcd2dec(Wire.read());

    Alarm = false;
    updateReg(0x0F);                      // clear alarm1 flag

    if (Count == 0) {
      Control &= 0xFE;                    // disable alarms

  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)) {
  // XmmhhWDDMMYY rest of timestamp
  else if((cmd[0]==88)&&(cmdsize==12)) {  // "X" rest of timestamp
    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
    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.requestFrom(0x68, 1);
    Aging = Wire.read();
    Serial.print("Aging = "); Serial.println(Aging);
  else if ((cmd[0]==84)||(cmd[0]==116)) { // "T" enable ticks

void Startup() {
  updateReg(0x0F);                        // clear alarm flags
  Control |= 1;
  updateReg(0x0E);                        // enable alarm1
  while (!Alarm);
  Alarm = false;
  updateReg(0x0F);                        // clear alarm flags

void RTCstamp() {                         // print current RTC timestamp
  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]);
  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) {
  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

void rtcISR() {
Alarm = true;

--- End code ---

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.

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.


[0] Message Index

[#] Next page

[*] Previous page

There was an error while thanking
Go to full version
Powered by SMFPacks Advanced Attachments Uploader Mod