-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathWWVB_PPS.ino
467 lines (373 loc) · 13.5 KB
/
WWVB_PPS.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
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
/*
* WWVB experiments
* WWVB as fake gps for U3S, how much will pps cause the tx frequency to be incorrect
*/
#include <LCD5110_Basic.h>
LCD5110 LCD( 2,3,4,6,5 );
extern uint8_t SmallFont[];
extern uint8_t MediumNumbers[];
extern uint8_t BigNumbers[];
#define WWVB_IN 7
#define PPS_OUT 8
int gmon = 1,gday = 1,gyr = 1,ghr,gmin;
int tot_days = 1;
uint16_t leap = 1;
uint8_t DST; // daylight savings bit
uint8_t gsec;
uint8_t msg_que = 1;
uint64_t wwvb_data, wwvb_sync, wwvb_errors;
// long term time adjustment
uint16_t time_adjust; // counter
uint16_t tm_correct_count = 3000; // test if really fast clock //50000; // adjust one ms in this many ms
int8_t tm_correction = 1; // +1 fast, -1 slow
// lost second detection when wwvb signal is weak
int phase;
int tot_phase;
volatile int psec; // que prints outside of the timekeeping function
uint8_t pmin;
uint8_t perrors;
int pwwvb_tmp;
void setup() {
int i;
pinMode(WWVB_IN, INPUT_PULLUP); // sample wwvb receiver signal
pinMode(PPS_OUT, OUTPUT);
Serial.begin(9600);
LCD.InitLCD();
LCD.setFont(SmallFont);
LCD.print("WWVB PPS TEST",CENTER,8*0);
delay( 5000 );
LCD.clrRow(0);
// timer0 millis timer
OCR0A = 0x40;
TIMSK0 |= _BV(OCIE0A);
}
ISR(TIMER0_COMPA_vect){ // millis timer interrupt
wwvb_sample2();
}
void loop() {
static uint8_t msg;
if( pwwvb_tmp != -1 ) pbin( );
if( gsec == msg_que && Serial.availableForWrite() > 60 ){ // send all each second
switch( msg ){
case 0: send_gga(); ++msg; break; // order sent must be just so, alphabetical I guess
case 2: send_gsv(); ++msg; break;
case 1: send_gsa(); ++msg; break;
case 3: send_rmc(); ++msg; break;
case 4: ++msg_que;
if( msg_que >= 60 ) msg_que = 0;
msg = 0;
break;
}
}
if( psec != -1 ) psecs();
if( pmin ){
switch( pmin++ ){
case 1:
keep_time();
LCD.printNumI( tm_correct_count,LEFT,1*8,6,' ');
LCD.printNumI( tm_correction,8*6,1*8,2,' ');
LCD.printNumI(perrors,RIGHT,1*8,2,' ');
break;
case 2:
LCD.printNumI( tot_phase,RIGHT,5*8,4,' ' );
break;
case 3:
LCD.setFont(MediumNumbers);
LCD.printNumI(ghr,LEFT,2*8,2,'0');
LCD.printNumI(gmin,CENTER,2*8,2,'0');
LCD.setFont(SmallFont);
break;
case 4:
LCD.printNumI(gmon,0,5*8,2,'0');
LCD.print("/",2*6,5*8);
break;
case 5:
LCD.printNumI(gday,3*6,5*8,2,'0');
LCD.print("/",5*6,5*8);
break;
case 6:
LCD.printNumI(gyr,6*6,5*8,2,'0');
break;
default: pmin = 0; break;
}
}
}
void send_gga(){
gps_puts( "$GPGGA," );
send_num( ghr ); send_num( gmin ); send_num( gsec );
gps_puts( ".20," );
gps_puts( "4426.8053,N,"); // lat
gps_puts( "06931.4612,W,"); // long
gps_puts( "1,"); // fix
gps_puts( "04,"); // num sats
gps_puts( "1.5,"); // horizontal something
gps_puts( "96.8,M," ); // altitude
gps_puts( "-34.0,M," );
gps_puts( ",*" );
}
void send_gsv(){
gps_puts( "$GPGSV," );
gps_puts( "1,1,04," ); // messages, sats in view
gps_puts( "04,44,104,24," ); // prn,elevation,azimuth,snr
gps_puts( "05,45,105,25," ); // prn,elevation,azimuth,snr
gps_puts( "06,46,106,26," ); // prn,elevation,azimuth,snr
gps_puts( "07,47,107,27*" ); // prn,elevation,azimuth,snr
}
void send_gsa(){
gps_puts( "$GPGSA," );
gps_puts( "A,3," ); // 3d fix
gps_puts( "04,05,06,07," ); // ID's
gps_puts( ",,,,,,,," ); // unused slots
gps_puts( "1.7,1.1,1.3*" ); // pdop,hdop,vdop
}
void send_rmc(){
gps_puts( "$GPRMC," );
send_num( ghr ); send_num( gmin ); send_num( gsec );
gps_puts( ".20," ); // some references say this is needed, others say not needed
gps_puts( "A," );
gps_puts( "4426.8053,N,"); // lat
gps_puts( "06931.4612,W,"); // long
gps_puts( "000.5,054.7,"); // speed,course
send_num( gday ); send_num(gmon); send_num( gyr ); // date
gps_puts( ",018.1,W,A*" ); // mag declination, A or D?
}
void send_num( int val ){
char buf[30];
if( val < 10 ) gps_putch('0');
itoa( val, buf, 10 );
gps_puts( buf );
}
void gps_putch( unsigned char c ){
static uint8_t crc;
if( c == '$' ) crc = 0;
else if( c != '*' ) crc ^= c;
Serial.write( c );
if( c == '*' ){
send_hex( crc );
Serial.println();
}
}
void gps_puts( char * p ){
char c;
while( ( c = *p++ ) ) gps_putch( c );
}
void send_hex( uint8_t d ){
char buf[30];
itoa( d, buf, 16 );
buf[0] = toupper( buf[0] );
buf[1] = toupper( buf[1] );
if( buf[1] == 0 ){
buf[1] = buf[0];
buf[0] = '0';
}
Serial.write(buf[0]); Serial.write(buf[1]);
}
void calc_date(){ // from total days and leap flag
const int cal[2][12] =
{ 31,28,31,30,31,30,31,31,30,31,30,31,
31,29,31,30,31,30,31,31,30,31,30,31 };
int i,d;
d = tot_days;
for( i = 0; i < 12; ++i ){
if( d <= cal[leap][i] ) break;
d -= cal[leap][i];
}
gmon = i + 1;
gday = d;
}
// WWVB receiver in a fringe area - integrate the signal to remove noise
// Although it probably makes more sense to dump the integrator 10 times per second, here we use 8.
// sample each millisecond, sum 100 or 150 samples , decide if low or high, shift into temp variable
// at end of 1 second( 8 bits), decide if temp has a 1, 0, or sync. Shift into 64 bit data and sync variables.
// when the sync variable contains the magic number, decode the 64 bit data.
// each second starts with a low signal and ends with a high signal
// much like software sampling rs232 start and stop bits.
// this routine runs fast by design until it locks on the wwvb signal( or slow depending upon point of view )
void wwvb_sample2(){
uint8_t b,s,e;
static uint8_t wwvb_clk, wwvb_sum, wwvb_tmp, wwvb_count; // data decoding
const uint8_t counts[8] = { 100,100,150,150,150,150,100,100 }; // total of 1000 ms
static uint8_t early,late;
static int8_t secs;
static uint8_t dither = 4; // quick sync, adjusts to 1 when signal is good
static uint8_t errors;
static uint8_t fract;
// adjust for 16mhz freq error, add or sub one millisecond
if( ++time_adjust >= tm_correct_count && wwvb_clk > 50 ){
time_adjust -= tm_correct_count;
wwvb_clk += tm_correction;
}
// duplicate the timer 0 - millis adjustment as we don't see it now as a timer0 interrupt
// timer 0 runs at 1.024 millisecond per interrupt
fract += 3;
if( fract >= 125 && wwvb_clk > 3 ){
--wwvb_clk;
++time_adjust;
fract -= 125;
}
if( digitalRead(WWVB_IN) == LOW ) ++wwvb_sum;
if( --wwvb_clk == 0 ){ // end of period, dump integrator
b = ( wwvb_sum > (counts[wwvb_count] >> 1) ) ? 0 : 128;
wwvb_tmp >>= 1;
wwvb_tmp |= b;
wwvb_sum = 0;
wwvb_count++;
wwvb_count &= 7;
if( wwvb_count == 1 ) digitalWrite( PPS_OUT,HIGH); // pps not accurate, use 00 10 for calibrate in U3S
if( wwvb_count == 2 ) digitalWrite( PPS_OUT,LOW) , gsec = secs; // que serial messages
wwvb_clk = counts[wwvb_count]; // 100 100 150 150 150 150 100 100
// decode 0 1 sync stop should be high
// 8 dumps of the integrator is one second, decode this bit
if( wwvb_count == 0 ){ // start of next second, decode time
// clocks late or early, just dither them back and forth across the falling edge
// when not in sync, more 1's than 0's are detected and this slips in time.
if( wwvb_tmp != 0xff && wwvb_tmp != 0x00 ){
if( digitalRead(WWVB_IN) == 0 ){
++late; // sampling late
wwvb_clk -= dither; // adjust sample to earlier
}
else{
++early; // need to sample later
wwvb_clk += dither; // longer clock
}
}
// decode
// 11111100 is a zero, 11110000 is a one, 11000000 is a sync
b = 0; s = 0; e = 1; // assume it is an error
if( wwvb_tmp == 0xfc ) e = 0, b = 0;
if( wwvb_tmp == 0xf0 ) e = 0, b = 1;
if( wwvb_tmp == 0xc0 ) e = 0, s = 1;
if( e ) ++errors;
wwvb_data <<= 1; wwvb_data |= b; // shift 64 bits data
wwvb_sync <<= 1; wwvb_sync |= s; // sync
wwvb_errors <<= 1; wwvb_errors |= e; // errors
pwwvb_tmp = wwvb_tmp;
// magic 64 bits of sync ( looking at 60 seconds of data with 4 seconds of the past minute )
// xxxx1000000001 0000000001 0000000001 0000000001 0000000001 0000000001
// wwvb_sync &= 0x0fffffffffffffff; // mask off the old bits from previous minute
// instead of masking, use the old bits to see the double sync bits at 0 of this minute
// and 59 seconds of the previous minute. This decodes at zero time.
if( wwvb_sync == 0b0001100000000100000000010000000001000000000100000000010000000001 ){
if( wwvb_errors == 0 ){ // decode if no bit errors
wwvb_decode();
secs = 59; // secs incremented below
}
}
if( ++secs >= 60 ){ // adjust dither each minute
secs -= 60;
dither = ( errors >> 4 ) + 1;
pmin = 1; // que prints
phase = early-late;
if( errors <= 10 ){ // a signal with accurate timing, adjust the clock adjustment
// will this work for both slow and fast 16 mhz clock?
// adjust correction for the 16 mhz nano clock
tm_correct_count += tm_correction * (late - early); // ? which is correct ?
//tm_correct_count += tm_correction * (early - late);
if( tm_correct_count > 60000 ){
tm_correct_count = 59000;
tm_correction *= -1; // slow or fast correction
}
}
early = late = 0; // reset the stats for the next minute
secs += save_phase_hist(phase,errors);
perrors = errors;
errors = 0;
phase = 0;
}
psec = secs;
} // end decode time
} // end integration timer
}
void psecs(){
int ps;
noInterrupts();
ps = psec;
psec = -1;
interrupts();
LCD.setFont(MediumNumbers);
LCD.printNumI(ps,RIGHT,2*8,2,'0');
LCD.setFont(SmallFont);
}
// save correction history, adjust seconds if think lost one
int8_t save_phase_hist(int phase, int errors){
int i;
if( errors <= 40 ){ // assume in phase if receiving some good data
if( tot_phase < 0 ) ++tot_phase;
if( tot_phase > 0 ) --tot_phase;
}
tot_phase += phase;
if( tot_phase > 600 ){
tot_phase -= 1000;
return 1; // lost a second?
}
if( tot_phase < -600 ){ // gained a second
tot_phase += 1000;
return -1;
}
return 0;
}
void wwvb_decode(){ // WWVB transmits the data for the previous minute just ended
uint16_t tmp;
uint16_t tmp2;
uint16_t yr;
uint16_t hr;
uint16_t mn;
uint16_t dy;
uint8_t i;
yr = wwvb_decode2( 53, 0x1ff ); // year is 0 to 99
dy = wwvb_decode2( 33, 0xfff ); // day is 0 to 365/366
hr = wwvb_decode2( 18, 0x7f );
mn = wwvb_decode2( 8, 0xff );
leap = wwvb_decode2( 55, 0x1 );
DST = wwvb_decode2( 57, 0x1 ); // in effect bit ( using bit 58 gave wrong time for one day )
ghr = hr;
gmin = mn;
gyr = yr;
tot_days = dy;
calc_date();
}
// wwvb fields all decode about the same way
uint16_t wwvb_decode2( uint8_t pos, uint16_t mask ){
uint16_t tmp;
uint16_t val;
tmp = ( wwvb_data >> ( 59 - pos ) ) & mask;
val = 0;
if( tmp & 0x800 ) val += 200;
if( tmp & 0x400 ) val += 100;
if( tmp & 0x100 ) val += 80;
if( tmp & 0x80 ) val += 40;
if( tmp & 0x40 ) val += 20;
if( tmp & 0x20 ) val += 10;
val += (tmp & 0xf);
return val;
}
void keep_time(){
if( ++gmin >= 60 ){
gmin = 0;
if( ++ghr >= 24 ){
ghr = 0;
++tot_days;
if( tot_days > 365 + leap ) ++gyr, tot_days = 1;
noInterrupts();
calc_date();
interrupts();
}
}
}
void pbin( ){
int v;
char buf[30];
char ch[2];
noInterrupts();
ch[0] = 'e';
if( pwwvb_tmp == 0xc0 ) ch[0] = 'S';
if( pwwvb_tmp == 0xf0 ) ch[0] = '1';
if( pwwvb_tmp == 0xfc ) ch[0] = '0';
ch[1] = 0;
v = pwwvb_tmp + 256; // add a leading 1
pwwvb_tmp = -1;
interrupts();
LCD.print( ch, LEFT,0*8 );
itoa( v,buf,2 );
LCD.print(&buf[1],RIGHT,0*8); // remove leading 1
}