-
Notifications
You must be signed in to change notification settings - Fork 4
/
synchro_RTC_MINI.ino
413 lines (376 loc) · 15.6 KB
/
synchro_RTC_MINI.ino
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
//------------------------------------------------------------------------------
// Home Office
// Nürnberg, Germany
// E-Mail: sergej1@email.ua
// Version 2.0.0 / is compatible with SynchroTimeApp version 2.x.x
// Copyright (C) 2022 free Project SynchroTime. All rights reserved.
//------------------------------------------------------------------------------
/*
This sketch performs as a server on an arduino controller for connecting the PC with an RTC DS3231 MINI module via a serial port.
Built-in server functions allow you to:
- adjust the RTC DS3231 time in accordance with the reference time of your computer;
- correct the frequency drift of the RTC DS3231 oscillator;
- evaluate the accuracy and reliability of the RTC oscillator for a specific sample,
as well as the chances of a successful correction in the event of a significant frequency drift;
- save parameters and calibration data to the Alarm1 Registers (DS3231);
- read value from the Aging register;
- write value to the Aging register.
The settings are:
- Set your local time zone in the settings of the application synchroTimeApp.
time zone = Difference of the local time and UTC-time, a value from { -12, .., -2, -1, 0, +1, +2, +3, .., +12, +13, +14 }.
For example, time zone for Central Europe = +1/+2, depending on which season is winter (+1) or summer time (+2).
- MIN_TIME_SPAN the minimum time required for a stable calculation of the frequency drift.
Dependencies:
- Arduino IDE version >= 1.8.13 (!Replace compilation flags from -Os to -O2);
- Adafruit RTC library for Arduino RTClib version >= 1.13 (https://github.com/adafruit/RTClib).
Connecting DS3231 MINI module to arduino board:
- VCC and GND of RTC DS3231 module should be connected to some power source +5V
- SDA, SCL of RTC DS3231 module should be connected to SDA - data line, SCL - clock line of arduino (for arduino Nano this are A4 and A5)
- SQW should be connected to INTERRUPT_PIN
- INTERRUPT_PIN needs to work with interrupts
*/
#include <avr/io.h>
#include <avr/interrupt.h>
#include <Wire.h>
#include "RTClib.h"
#define INTERRUPT_PIN 2 // Interrupt pin (for Arduino Uno = 2 or 3)
#define STARTBYTE 0x40 // The starting byte of the data set from the communication protocol.
#define DS3231_ADDRESS 0x68 // I2C address for DS3231
#define DS3231_ALARM1 0x07 // Alarm 1 register
#define DS3231_AGINGREG 0x10 // Aging register address
#define DS3231_TEMPERATUREREG 0x11 // Temperature register (high byte - low byte is at 0x12), 10-bit temperature value
#define MIN_TIME_SPAN 100000 // The minimum time required for a stable calculation of the frequency drift [in secs]. Default value 200000.
enum task_t : uint8_t { TASK_IDLE, TASK_ADJUST, TASK_INFO, TASK_CALIBR, TASK_RESET, TASK_SETREG, TASK_STATUS, TASK_WRONG };
struct time_t {
uint32_t utc; // This is a Coordinated Universal Time (UTC-time) + time-zone offset
uint16_t milliSecs;
};
RTC_DS3231 rtc;
static uint8_t buff[4]; // temporary buffer
static uint8_t byteBuffer[18];
// Function Prototypes
int8_t readFromAgingReg( void ); // read from Aging register
bool writeToAgingReg( const int8_t value ); // write to Aging register
static inline void memcpy_byte( void *__restrict__ dstp, const void *__restrict__ srcp, uint16_t len );
inline void intToHex( uint8_t* const buff, const uint32_t value );
inline void floatToHex( uint8_t* const buff, const float value );
uint32_t hexToInt( const uint8_t* const buff );
static float calculateDrift_ppm( const time_t* const ref, const time_t* const t );
static int8_t roundUpDrift( float drift_in_ppm );
static uint8_t sumOfBytes( const uint8_t* const bbuffer, const uint8_t blength );
static int16_t read_Temperature( void );
static time_t getTime( void );
void setup () {
Serial.begin( 115200 ); // initialization serial port with 115200 baud (_standard_)
while ( !Serial ); // wait for serial port to connect. Needed for native USB
if ( !rtc.begin() ) {
Serial.print( F( "Couldn't find DS3231 modul" ) );
Serial.flush();
abort();
}
Ds3231SqwPinMode mode = rtc.readSqwPinMode();
if ( mode != DS3231_SquareWave1Hz ) {
rtc.writeSqwPinMode( DS3231_SquareWave1Hz );
}
rtc.disable32K(); //we don't need the 32K Pin, so disable it
if ( rtc.lostPower() ) {
// If the RTC have lost power it will sets the RTC to the date & time this sketch was compiled in the following line
const uint32_t newtime = DateTime( F(__DATE__), F(__TIME__) ).unixtime();
rtc.adjust( newtime );
i2c_write_value( newtime );
}
// set alarm 1, 2 flag to false (so alarm 1, 2 didn't happen so far)
// if not done, this easily leads to problems, as both register aren't reset on reboot/recompile
rtc.clearAlarm(1);
rtc.clearAlarm(2);
// turn off alarm (in case it isn't off already)
// again, this isn't done at reboot, so a previously set alarm could easily go overlooked
rtc.disableAlarm(1);
rtc.disableAlarm(2);
PORTD |= 0x01 << INTERRUPT_PIN; // set INPUT_PULLUP to INTERRUPT_PIN port
EICRA &= ~( bit(ISC00) | bit(ISC01) ); // Reset ISC00
EICRA |= bit(ISC01); // Set ISC01 - tracks FALLING at INT0
EIMSK |= bit(INT0); // Enable interrupt INT0
Serial.print( F( "Boot.. Ok, T[°С]=" ));
const int16_t temp = read_Temperature();
Serial.print( float( temp >> 8 ) + ( (temp & 0xFF) >> 6 ) * 0.25f );
}
volatile uint32_t tickCounter;
ISR( INT0_vect ) {
tickCounter = micros();
}
inline void reset( void ) {
tickCounter = micros();
}
void loop () {
task_t task = TASK_IDLE;
bool ok = false;
uint8_t byteCounter = 0U;
uint8_t numberOfBytes = 0U;
float drift_in_ppm = .0f;
int8_t drift_val;
time_t t, ref;
if ( Serial.available() > 1 && Serial.read() == STARTBYTE ) { // if there is data available
t = getTime();
// Command Parser
char thisChar = Serial.read(); // read the byte of request
switch ( thisChar )
{
case 'a': // time adjustment request
numberOfBytes = Serial.readBytes( byteBuffer, 7 );
task = TASK_ADJUST;
break;
case 'i': // information request
numberOfBytes = Serial.readBytes( byteBuffer, 7 );
task = TASK_INFO;
break;
case 'c': // calibrating request
numberOfBytes = Serial.readBytes( byteBuffer, 7 );
task = TASK_CALIBR;
break;
case 'r': // reset request
numberOfBytes = Serial.readBytes( byteBuffer, 1 );
task = TASK_RESET;
break;
case 's': // set Aging reg. request
numberOfBytes = Serial.readBytes( byteBuffer, 5 );
task = TASK_SETREG;
break;
case 't': // status request
numberOfBytes = Serial.readBytes( byteBuffer, 1 );
task = TASK_STATUS;
break;
default: // unknown request
task = TASK_IDLE;
Serial.print( F("Unknown Request ") );
Serial.print( thisChar );
}
// Data Parser
uint8_t crc = 0U;
uint8_t sum = uint8_t( thisChar );
if ( numberOfBytes > sizeof( ref ) ) {
// reading reference time if data is available. in the form [sec|ms] = 4+2 bytes
memcpy_byte( &ref, byteBuffer, sizeof( ref ));
crc = byteBuffer[ sizeof( ref )];
sum += sumOfBytes( byteBuffer, sizeof( ref ));
}
else if ( numberOfBytes > sizeof( drift_in_ppm ) ) {
// reading new value for the Aging reg. in the form [float] = 4 bytes
memcpy_byte( &drift_in_ppm, byteBuffer, sizeof( drift_in_ppm ));
crc = byteBuffer[ sizeof( drift_in_ppm )];
sum += sumOfBytes( byteBuffer, sizeof( drift_in_ppm ));
}
else {
crc = byteBuffer[0];
}
// checksum verification
if ( crc != sum ) {
task = TASK_IDLE;
Serial.print( F("Invalid Data") );
}
else if ( task != TASK_IDLE ) {
byteBuffer[byteCounter] = STARTBYTE;
byteCounter++;
}
}
switch ( task )
{
case TASK_ADJUST: // adjust time
reset(); // reset milliseconds
rtc.adjust( DateTime( ref.utc ) );
ok = i2c_write_value( ref.utc ); // write the time to the Alarm1 Registers
byteBuffer[byteCounter] = ok;
byteCounter++;
task = TASK_IDLE;
break;
case TASK_INFO: // information
memcpy_byte( byteBuffer + byteCounter, &t, sizeof( t ) ); // write time to buffer bytes
byteCounter += sizeof( t );
byteBuffer[byteCounter] = readFromAgingReg(); // reading Aging value
byteCounter++;
drift_in_ppm = calculateDrift_ppm( &ref, &t ); // calculate drift time
floatToHex( byteBuffer + byteCounter, drift_in_ppm );
byteCounter += sizeof( drift_in_ppm );
if ( i2c_read_buffer( byteBuffer + byteCounter, sizeof( uint32_t )) ) {
byteCounter += sizeof( uint32_t );
}
task = TASK_IDLE;
break;
case TASK_CALIBR: // calibrating
byteBuffer[byteCounter] = readFromAgingReg(); // read last value from the Aging register
byteCounter++;
drift_in_ppm = calculateDrift_ppm( &ref, &t ); // calculate drift time
floatToHex( byteBuffer + byteCounter, drift_in_ppm ); // read drift as float value
byteCounter += sizeof(drift_in_ppm);
drift_val = roundUpDrift( drift_in_ppm );
if ( drift_val != 0 ) {
ok = writeToAgingReg( drift_val ); // write drift value to Aging Reg. of DS3231
if ( ok ) {
reset(); // reset milliseconds
rtc.adjust( DateTime( ref.utc ) ); // adjust time
ok &= i2c_write_value( ref.utc ); // write last_set_time to the Alarm1 Registers
}
byteBuffer[byteCounter] = drift_val; // read new value from the Aging register
byteCounter++;
}
else {
ok = true;
byteBuffer[byteCounter] = byteBuffer[1]; // read value from the Aging register
byteCounter++;
}
byteBuffer[byteCounter] = ok;
byteCounter++;
task = TASK_IDLE;
break;
case TASK_RESET: // reset
ok = writeToAgingReg( 0 );
if ( ok ) {
ok &= i2c_write_value( 0xFFFFFFFF ); // write 0xFFFFFFFF to the Alarm1 Registers
}
byteBuffer[byteCounter] = ok;
byteCounter++;
task = TASK_IDLE;
break;
case TASK_SETREG: // set register
drift_in_ppm *= 10;
drift_val = (drift_in_ppm > .0f) ? ( drift_in_ppm + 0.5f ) : ( drift_in_ppm - 0.5f );
ok = writeToAgingReg( drift_val ); // write drift value to Aging Reg. of DS3231
byteBuffer[byteCounter] = ok;
byteCounter++;
task = TASK_IDLE;
break;
case TASK_STATUS: // get status
byteBuffer[byteCounter] = 0x00;
byteCounter++;
task = TASK_IDLE;
break;
case TASK_IDLE: // idle task
break;
default:
Serial.print( F("Unknown Task ") );
Serial.print( task, HEX );
task = TASK_IDLE;
}
// Response to the request
if ( byteCounter > 0 ) {
byteBuffer[byteCounter] = sumOfBytes( byteBuffer, byteCounter );
Serial.write( byteBuffer, ++byteCounter );
Serial.flush();
}
}
int8_t readFromAgingReg( void ) {
Wire.beginTransmission( DS3231_ADDRESS ); // Sets the DS3231 RTC module address
Wire.write( uint8_t( DS3231_AGINGREG ) ); // sets the aging register address
Wire.endTransmission();
int8_t aging_val = 0;
Wire.requestFrom( uint8_t( DS3231_ADDRESS ), 1U ); // Read a byte from register
aging_val = int8_t( Wire.read() );
return aging_val;
}
bool writeToAgingReg( const int8_t value ) {
Wire.beginTransmission( DS3231_ADDRESS ); // Sets the DS3231 RTC module address
Wire.write( uint8_t( DS3231_AGINGREG ) ); // sets the aging register address
Wire.write( value ); // Write value to register
return ( Wire.endTransmission() == 0 );
}
inline void intToHex( uint8_t* const buff, const uint32_t value ) {
memcpy_byte( buff, &value, sizeof(value) );
}
inline void floatToHex( uint8_t* const buff, const float value ) {
memcpy_byte( buff, &value, sizeof(value) );
}
uint32_t hexToInt( const uint8_t* const buff ) {
uint32_t *y = (uint32_t *)buff;
return y[0];
}
// the result is rounded to the maximum possible values of type int8_t
static int8_t roundUpDrift( float drift_in_ppm ) {
drift_in_ppm *= 10;
int32_t offset = (drift_in_ppm > .0f) ? ( drift_in_ppm + 0.5f ) : ( drift_in_ppm - 0.5f );
if ( offset == 0 ) {
return offset; // if offset is 0, nothing needs to be done
}
const int8_t last_aging_reg = readFromAgingReg();
drift_in_ppm += last_aging_reg;
offset = (drift_in_ppm > .0f) ? ( drift_in_ppm + 0.5f ) : ( drift_in_ppm - 0.5f );
return (offset > 127) ? 127 : (offset < -128) ? -128 : offset;
}
/*
"drift in ppm unit" - this is the ratio of the clock drift from the reference time,
which is expressed in terms of one million control seconds.
For example, reference_time = 1597590292 sec, clock_time = 1597590276 sec, last_set_time = 1596628800 sec,
time_drift = clock_time - reference_time = -16 sec
number_of_control_seconds = reference_time - last_set_time = 961492 sec, i.e 0.961492*10^6 sec
drift_in_ppm = time_drift * 10^6 / number_of_control_seconds = -16*10^6 /(0.961492*10^6) = -16.64 ppm
*/
static float calculateDrift_ppm( const time_t* const referenceTime, const time_t* const clockTime ) {
if ( !i2c_read_buffer( buff, sizeof(buff)) ) {
return 0;
}
const uint32_t last_set_timeUTC = hexToInt( buff );
const int32_t diff = referenceTime->utc - last_set_timeUTC;
// verification is needed because the var. last_set_timeSecs can reach the overflow value
if ( referenceTime->utc < last_set_timeUTC || diff < MIN_TIME_SPAN ) {
return 0;
}
const int32_t time_driftSecs = clockTime->utc - referenceTime->utc;
const int16_t time_driftMs = clockTime->milliSecs - referenceTime->milliSecs;
const float time_drift = time_driftSecs * 1000 + time_driftMs;
return time_drift * 1000 / diff;
}
static uint8_t sumOfBytes( const uint8_t* const bbuffer, const uint8_t blength ) {
uint8_t sum = 0U;
for ( uint8_t idx = 0U; idx < blength; idx++ ) {
sum += bbuffer[idx];
}
return sum;
}
static bool i2c_write_value( const uint32_t utc ) {
memcpy( buff, &utc, sizeof(utc) ); // write time to temporary buffer
Wire.beginTransmission( DS3231_ADDRESS );
Wire.write( DS3231_ALARM1 );
uint8_t i;
for ( i = 0; i < sizeof(utc); i++ ) {
Wire.write( buff[i] );
}
return ( Wire.endTransmission() == 0 );
}
static bool i2c_read_buffer( uint8_t* const buffer, const uint8_t length ) {
Wire.beginTransmission( DS3231_ADDRESS );
Wire.write( DS3231_ALARM1 );
bool ret_val = ( Wire.endTransmission() == 0 );
Wire.requestFrom( (uint8_t)DS3231_ADDRESS, (uint8_t)length );
uint8_t i;
for ( i = 0; i < length; i++ ) {
if ( Wire.available() ) {
buffer[i] = Wire.read();
}
}
return ret_val;
}
static inline void memcpy_byte( void *__restrict__ dstp, const void *__restrict__ srcp, uint16_t len ) {
uint8_t *dst = ( uint8_t *) dstp;
const uint8_t *src = ( uint8_t *) srcp;
uint16_t idx;
for( idx = 0U; idx < len; idx++ )
*(dst++) = *(src++);
}
static int16_t read_Temperature() {
int16_t temp = 0;
Wire.beginTransmission( DS3231_ADDRESS );
Wire.write( DS3231_TEMPERATUREREG );
Wire.endTransmission();
Wire.requestFrom( DS3231_ADDRESS, 2 );
temp = Wire.read();
temp = (temp << 8) | Wire.read();
return temp;
}
static time_t getTime() {
time_t t;
while ( micros() - tickCounter > 999980UL );
const uint32_t difference = micros() - tickCounter;
t.milliSecs = (difference + difference % 1000U)/ 1000U;
DateTime dt = rtc.now(); // reading clock time
t.utc = dt.unixtime(); // reading clock time as UTC-time + TimeZone
return t;
}