DS3231 - setting Aging register to optimum value

Here's the sketch. You'll need the Serial Monitor open. No libraries needed except Wire.

/*
DS3231 Aging from GPS

This sketch compares the SQW output of a DS3231 relative to the PPS output of a GPS module,
and adjusts the RTC's Aging register so the RTC clock is running at the same speed as GPS.
Comparisons are done every five minutes.  Results are valid only at the current temperature.

This runs on an ATMega328P Arduino (Uno, Nano, Pro Mini) with the PPS line of a GPS module
tied to D8, and the SQW output of a DS3231 RTC tied to D2.  VCC, GND, SDA and SCL as usual.
After bootup, wait for the GPS to begin one-second flashing, then press any key to begin.

This sketch will change the RTC time by up to one second.  It does not set the correct time
per GPS - it only optimizes the Aging register setting.  When Aging is where you want it,
enter 'Q' to quit, or 'T' to set the RTC to a new date and time you will enter, then quit.
Enter 'An' at any time to set Aging to n, then start over (-128 to 127 permitted).
*/

#include <Wire.h>
#define flagsREG EIFR                     // ATMega328P interrupt flags register

const byte RTCpin = 2;                    // D2
volatile bool Capture = false;            // Capture interrupt has occurred - GPS
volatile bool Started = false;            // Square wave interrupt has occurred - RTC
volatile byte MSBtimer;                   // MS byte of 24-bit timer
volatile int Square = 0;                  // square wave clocks - for SN vs M
bool isSN = true, Pending = false;        // true if SN, false if M; forced conv pending
bool fineFlag = false;                    // switch to max deltaAging of +/- 1
unsigned long GPTlow, GPThi;              // timer counts on GPS interrupt
long Diff, prevDiff, oldDiff, deltaDiff;  // clocks from RTC to GPS
int ppm;                                  // Aging +/- 1:  count difference over 5 minutes
int SNdivisor = 264;                      // ppm for SN parts
int Mdivisor = 576;                       // ppm for M parts
int Restart = 32;                         // initial 32-sec run-in
int Period = 300;                         // number of seconds between adjustments
int finePeriod = 200;                     // period when close to end
int Count = Restart, k;                   // down counter - seconds until next calc
int8_t Aging, deltaAging;                 // contents of RTC Aging register
byte Seconds, Control, Status, j, i = 0;  // other RTC registers' contents
long batch[16];                           // last 16 readings
char buff[20];                            // serial input buffer
byte buffSize;                            // length of input string
char in;                                  // serial input character

void setup() {
  Serial.begin(57600);
  delay(2000);
  Wire.begin();
  pinMode(8,INPUT);                       // Timer1 capture input - from GPS (ICP1)
  pinMode(RTCpin,INPUT_PULLUP);           // Hardware interrupt on D2 - from RTC

  Serial.println("Enter any key to begin"); // Wait for GPS to begin 1-sec flashes
  Serial.println();
  int key1 = 0;
  while ((key1 != 10) && (key1 != 13)) {
    key1 = Serial.read();
    delay (100);
  }
  key1 = Serial.read();                   // in case CR/LF

  // Clear /EOSC, CONV, RS2, INTCN, A2E, A1E.   Set BBSQW, RS1. (SQW freq = 1KHz)
  Control = 0b01001000;
  Status = 0;

  updateReg(0x0E);                        // update Control
  updateReg(0x0F);                        // update Status

  Wire.beginTransmission(0x68);           // read Aging and Seconds registers
  Wire.write(0x10);
  Wire.endTransmission();
  Wire.requestFrom(0x68, 4);

  Aging = Wire.read();                    // starting value of the Aging register
  Seconds = Wire.read();                  // read past temp registers
  Seconds = Wire.read();
  Seconds = Wire.read();                  // pointer wraps to zero

  cli();
  flagsREG = 3;                           // clear any flags on both pins
  attachInterrupt(digitalPinToInterrupt(RTCpin),SNvsM, FALLING);
  flagsREG = 3;
  sei();
  delay(2000);
  Control &= 0b11110111;                  // change squarewave to 1Hz
  updateReg(0x0E);
  detachInterrupt(digitalPinToInterrupt(RTCpin));  // will assign new ISR for D2
  flagsREG = 3;
  Serial.print ("1KHz squarewave makes "); Serial.print(Square);
  Serial.println (" cycles over 2 seconds.");
  if (Square < 500) {
    isSN = false;
    ppm = Mdivisor;                       // expected change from Aging +/- 1
    Serial.println ("So this is a DS3231M");
  }
  else {
    ppm = SNdivisor;                      // expected change from Aging +/- 1
    Serial.println ("So this is a DS3231SN");
  }
  Serial.println();
  if (F_CPU == 8000000) ppm /= 2;         // if 8MHz Pro Mini

  while(digitalRead(8));                  // wait for GPS low, then
  while(!digitalRead(8));                 // wait for GPS high - beginning of second
  delay(500);                             // wait half a second
  updateReg(0);                           // reinitialize RTC clock - now 1/2 sec apart

  cli();
  TIMSK0 = 0;                             // Disable Timer0 interrupts (millis)
  TCCR0A = 0;
  TCCR0B = 0;
  TIFR0  = 0xFF;

  TCCR1A = 0;                             // set up Timer1
  TCCR1B = 0;
  TCCR1C = 0;
  TCNT0  = 0;                             // clear Timer1
  TIFR1  = 0xFF;                          // clear flags
  TIMSK1 = 0b00100001;                    // enable capture and overflow interrupt (GPS)
  TIFR1  = 0xFF;                          // clear flags
  TCCR1A = 0b00000000;                    // Normal mode, no output, WGM #0
  TCCR1B = 0b01000001;                    // rising edge capture, timer1 on, no prescale

  flagsREG = 3;                           // new ISR for D2
  attachInterrupt(digitalPinToInterrupt(RTCpin),rtcISR, FALLING);
  flagsREG = 3;
  sei();
  Serial.println ("Enter 'An' to change Aging to n (-128 to 127)");
  Serial.println ("Enter 'Q' to quit, or 'T' to enter new date/time"); Serial.println();
}

void loop() {
  if (Capture) {                          // GPS PPS has gone high
    Capture = false;
    GPTlow = ICR1;                        // read timer values
    GPThi = MSBtimer;
    cli();
    TCNT1 = 0;                            // clear timer1
    MSBtimer = 0;
    TIFR1 = 0xFF;                         // clear flags
    sei();

    Diff = (GPThi << 16) + GPTlow;        // combine timer counts to one long value
    if (abs(Diff - prevDiff) < 1000) {    // normal values only
      batch[i] = Diff;                    // collect last 16 values into array
      i = (i + 1) & 15;
    }
    prevDiff = Diff;

    Count--;
    if (!Count) {                         // do calculation every five minutes
      Diff = 0;
      for (j = 0; j < 16; j++) {
        Diff += batch[j];
      }
      Diff = (Diff + 8) / 16;             // average over last 16 seconds
      if (Restart == 32) oldDiff = Diff;
      deltaDiff = Diff - oldDiff;         // calculate new Aging
      deltaAging = deltaDiff / ppm;
      if ((Restart != 32) && (!deltaAging)) fineFlag = true;
      if (deltaAging) {                   // if any change
        if (fineFlag && (deltaAging > 1)) deltaAging = 1;
        if (fineFlag && (deltaAging < -1)) deltaAging = -1;
        Aging += deltaAging;
        updateReg(0x10);
        oldDiff = Diff;
        if (isSN) Pending = true;         // force conversion if SN
      }
      Serial.print ("Diff "); Serial.println(Diff);   // print results
      Serial.print ("deltaDiff ");
      if (deltaAging == 0) {
        Serial.print("[");
        Serial.print(deltaDiff);
        Serial.println("]");
      }
      else Serial.println(deltaDiff);
      Serial.print ("deltaAging "); Serial.println(deltaAging);
      Serial.print ("Aging "); Serial.println(Aging); Serial.println();

      if (Restart==32) Restart = Period;  // switch to 5 minutes after run-in
      if (fineFlag) Restart = finePeriod; // switch to 3.33 minutes in fine mode
      Count =  Restart;
    }
  }
  if (Started) {                          // beginning of second
    if (Pending) {
      Control |= 0b00100000;              // force conversion
      updateReg(0x0E);
      Control &= 0b11011111;
      Pending = false;
    }
    Started = false;
  }
  if(Serial.available()) {                // process input from Serial Monitor
    in = Serial.read();                   // set end-line option to Newline or CR
    if ((in == 13) || (in == 10)) {
      buff[buffSize] = 0;
      parse_cmd(buff, buffSize);
      buffSize = 0;
      buff[0] = 0;
    }
    else {
      buff[buffSize] = in;
      buffSize++;
    }
  }
}

void parse_cmd(char *cmd, byte cmdsize) {

  // YYYYMMDDWhhmmss
  if ((cmd[0]=='2')&&(cmdsize==15)) {     // "2" new date/time
    Wire.beginTransmission(0x68);
    Wire.write(0);
    Wire.write(inp2bcd(cmd,13));          // seconds
    Wire.write(inp2bcd(cmd,11));          // minutes
    Wire.write(inp2bcd(cmd,9));           // hours
    Wire.write(cmd[8] - 48);              // day of the week
    Wire.write(inp2bcd(cmd,6));           // date of the month
    Wire.write(inp2bcd(cmd,4) | 0x80);    // month & century
    Wire.write(inp2bcd(cmd,2));           // year
    Wire.endTransmission();
    Serial.println ("Data entered");
    shutdown();
  }

  else if ((cmd[0]&0xDF)=='T') {          // "T" Time set
    Serial.println ("Enter new date/time for RTC.  (w = day of week (1-7))");
    Serial.println ("YYYYMMDDwhhmmss");
  }

  else if ((cmd[0]&0xDF)=='A') {          // "A" Aging
    if (cmdsize > 1) {
      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
        Count = 32; Restart = 32; fineFlag = false;
        if (isSN) Pending = true;
      }
      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); Serial.println();
  } 

  else if ((cmd[0]&0xDF)=='Q') {          // "Q" Quit
    shutdown();
  }
}

byte inp2bcd(char *inp, byte seek) {
  return (((inp[seek]-48)<<4) + (inp[seek+1] - 48));
}

void shutdown() {
  Control |= 0b00000100;                  // disable square wave
  updateReg(0x0E);
  cli();
  detachInterrupt(digitalPinToInterrupt(RTCpin));
  flagsREG = 3;
  TCCR1B = 0;
  TIMSK1 = 0;
  TIFR1 = 0;
  sei();
  Serial.println("Squarewave disabled");
  Serial.println("Shutting down");
  while (1);
}

void updateReg(byte addr) {
  Wire.beginTransmission(0x68);
  Wire.write(addr);
  if(addr == 0x0E) Wire.write(Control);
  else if(addr == 0x0F) Wire.write(Status);
  else if(addr == 0x10) Wire.write(Aging);
  else if(addr == 0) Wire.write(Seconds);
  Wire.endTransmission();
}

ISR(TIMER1_CAPT_vect) {
  TCCR1B &= 0xFE;                         // stop Timer1 clock
  Capture = true;
}

ISR(TIMER1_OVF_vect) {
  MSBtimer++;                             // increment MSB on overflow
}

void rtcISR() {
  TCCR1B |= 1;                            // start Timer1 clock
  Started = true;
}

void SNvsM() {                            // only used for SN vs M test
  Square++;
}