-
Notifications
You must be signed in to change notification settings - Fork 28
/
dcf77.h
2241 lines (1855 loc) · 91.9 KB
/
dcf77.h
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
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
//
// www.blinkenlight.net
//
// Copyright 2016 Udo Klein
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses/
#ifndef dcf77_h
#define dcf77_h
#define DCF77_MAJOR_VERSION 3
#define DCF77_MINOR_VERSION 1
#define DCF77_PATCH_VERSION 4
#include <stdint.h>
#if defined(ARDUINO)
#include "Arduino.h"
#endif
struct Configuration {
// The Configuration holds the configuration of the clock library.
// The resolution defines if the clock will average 10 milli second ticks and
// then sample or if it will directly sample each millisecond.
// The impact of this is:
// - If resolution is set to centi seconds:
// -the RAM footprint will be significantly lower
// - clock jitter occurs less often but in 10 ms leaps
// - auto tune will settle 10 times slower
// - filter bandwidth of the demodulator stage is reduced by 10 times
// - noise tolerance of this stage is higher
// - If resolution is set to milliseconds:
// - the RAM footprint will be significantly higher
// - clock jitter occurs ten times more often but in 1 ms leaps
// - auto tune will settle 10 times faster
// - filter bandwidth of the demodulator stage is increased by 10 times
// - noise tolerance of this stage is lower
// As a consequency on Atmega 328 (e.g. "Arduino Uno") the resolution must
// be centi_seconds. On ARM (e.g. "Arduino Due") the resolution may be set
// to milli_seconds.
// If your application does not need milli second accuracy then the low
// memory footprint (centi_seconds) option is recommended.
// If you are in a (electrically) noisy environment the low memory footprint
// option is recommended.
// If you are on AVR low memory will be selected and milli second
// accuracy will not be achieved.
// If you have a resonator instead of a crystal oscillator milli
// second accuracy will also never be achieved. Pretending to
// have a crystal even though you have a resonator will result
// in phase lock code that will not be able to lock to the phase.
// With other words: if the library fails to lock to DCF77 please
// double check if you really have a crystal. In particular the
// Arduino UNO has a crystal for USB but uses a resonator for the
// controller. Hence has_crystal = false must be used for the UNO.
// 10 ms vs 1 ms sample resolution
// setting this to true will change the sample resolution to 1 ms for ARM
// for AVR there is not enough memory, so for AVR it will default to false
// the constant(s) below are assumed to be configured by the user of the library
static const bool want_high_phase_lock_resolution = true;
//const bool want_high_phase_lock_resolution = false;
// end of configuration section, the stuff below
// will compute the implications of the desired configuration,
// ready for the compiler to consume
#if defined(__arm__)
static const bool has_lots_of_memory = true;
#else
static const bool has_lots_of_memory = false;
#endif
static const bool high_phase_lock_resolution = want_high_phase_lock_resolution &&
has_lots_of_memory;
enum ticks_per_second_t : uint16_t { centi_seconds = 100, milli_seconds = 1000 };
// this is the actuall sample rate
static const ticks_per_second_t phase_lock_resolution = high_phase_lock_resolution ? milli_seconds
: centi_seconds;
enum quality_factor_sync_threshold_t : uint8_t { aggressive = 1, standard = 2, conservative = 3 };
static const uint8_t quality_factor_sync_threshold = aggressive;
// Set to true if the library is deployed in a device runnning at room temperature.
// Set to false if the library is deployed in a device operating outdoors.
// The implications are as follows:
// Outdoors there are larger temperature deviations than indoors. In particular there are
// significant differences between night and day temperatures. As a consequence there will
// significant frequency deviations between night and daytime operation. It follows
// that it is pointless to tune the crystal over periods that exceed several hours.
// Hence setting this to false will limit the measurement period to several hours.
// If the clock will be deployed outdoors and this is set to true then it may
// happen that it initially works as it should be loses sync once the crystal is tuned.
// This is a nasty issue that will only show up if the clock is running for several
// days AND the temperature swings are high.
// If you are "indoors" this should never be an issue but for outdoor use set this to false.
// The price to pay is that during DCF77 outage beyond several hours resync will take
// longer. It will also imply that the crystal will never be tuned better than 1 ppm as
// this is completely pointless in the presence of huge changes in ambient temperature.
static const bool has_stable_ambient_temperature = true; // indoor deployment
// static const bool has_stable_ambient_temperature = false; // outdoor deployment
};
// https://gcc.gnu.org/onlinedocs/cpp/Stringification.html
#define EXPAND_THEN_STRINGIFY(s) STRINGIFY(s)
#define STRINGIFY(s) #s
#define DCF77_VERSION_STRING (EXPAND_THEN_STRINGIFY(DCF77_MAJOR_VERSION) "." \
EXPAND_THEN_STRINGIFY(DCF77_MINOR_VERSION) "." \
EXPAND_THEN_STRINGIFY(DCF77_PATCH_VERSION))
#define GCC_VERSION (__GNUC__ * 10000 \
+ __GNUC_MINOR__ * 100 \
+ __GNUC_PATCHLEVEL__)
#if defined(__arm__)
#define GCC_ARCHITECTURE "ARM"
#elif defined(__AVR__)
#define GCC_ARCHITECTURE "AVR"
#elif defined(__unix__)
#define GCC_ARCHITECTURE "UNIX"
#else
#define GCC_ARCHITECTURE "unknown"
#endif
#define ERROR_MESSAGE(major, minor, patchlevel) compiler_version__GCC_ ## major ## _ ## minor ## _ ## patchlevel ## __ ;
#define OUTDATED_COMPILER_ERROR(major, minor, patchlevel) ERROR_MESSAGE(major, minor, patchlevel)
#if GCC_VERSION < 40503
// Arduino 1.0.0 - 1.0.6 come with an outdated version of avr-gcc.
// Arduino 1.5.8 comes with a ***much*** better avr-gcc. The library
// will compile but fail to execute properly if compiled with an
// outdated avr-gcc. So here we stop here if the compiler is outdated.
//
// You may find out your compiler version by executing 'avr-gcc --version'
// Visit the compatibility section here:
// http://blog.blinkenlight.net/experiments/dcf77/dcf77-library/
// for more details.
#error Outdated compiler version < 4.5.3
#error Absolute minimum recommended version is avr-gcc 4.5.3.
#error Use 'avr-gcc --version' from the command line to verify your compiler version.
#error Arduino 1.0.0 - 1.0.6 ship with outdated compilers.
#error Arduino 1.5.8 (avr-gcc 4.8.1) and above are recommended.
OUTDATED_COMPILER_ERROR(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__)
#endif
namespace BCD {
typedef union {
struct {
uint8_t lo:4;
uint8_t hi:4;
} digit;
struct {
uint8_t b0:1;
uint8_t b1:1;
uint8_t b2:1;
uint8_t b3:1;
uint8_t b4:1;
uint8_t b5:1;
uint8_t b6:1;
uint8_t b7:1;
} bit;
uint8_t val;
} bcd_t;
bool operator == (const bcd_t a, const bcd_t b);
bool operator != (const bcd_t a, const bcd_t b);
bool operator >= (const bcd_t a, const bcd_t b);
bool operator <= (const bcd_t a, const bcd_t b);
bool operator > (const bcd_t a, const bcd_t b);
bool operator < (const bcd_t a, const bcd_t b);
void increment(bcd_t &value);
bcd_t int_to_bcd(const uint8_t value);
uint8_t bcd_to_int(const bcd_t value);
}
namespace Clock {
typedef struct {
BCD::bcd_t year; // 0..99
BCD::bcd_t month; // 1..12
BCD::bcd_t day; // 1..31
BCD::bcd_t weekday; // Mo = 1, So = 7
BCD::bcd_t hour; // 0..23
BCD::bcd_t minute; // 0..59
BCD::bcd_t second; // 0..60
bool uses_summertime;
bool timezone_change_scheduled;
bool leap_second_scheduled;
} time_t;
// Once the clock has locked to the DCF77 signal
// and has decoded a reliable time signal it will
// call output handler once per second
typedef void (*output_handler_t)(const time_t &decoded_time);
// input provider will be called each millisecond and must
// provide the input of the raw DCF77 signal
typedef uint8_t (*input_provider_t)(void);
typedef enum {
useless = 0, // waiting for good enough signal
dirty = 1, // time data available but unreliable
free = 2, // clock was once synced but now may deviate more than 200 ms, must not re-lock if valid phase is detected
unlocked = 3, // lock was once synced, inaccuracy below 200 ms, may re-lock if a valid phase is detected
locked = 4, // clock driven by accurate phase, time is accurate but not all decoder stages have sufficient quality for sync
synced = 5 // best possible quality, clock is 100% synced
} clock_state_t;
}
namespace DCF77_Clock {
void setup();
void setup(const Clock::input_provider_t input_provider, const Clock::output_handler_t output_handler);
void set_input_provider(const Clock::input_provider_t);
void set_output_handler(const Clock::output_handler_t output_handler);
// blocking till start of next second
void get_current_time(Clock::time_t &now);
// non-blocking, reads current second
void read_current_time(Clock::time_t &now);
// non-blocking, reads current second+1
void read_future_time(Clock::time_t &now_plus_1s);
#if defined(__AVR__)
void auto_persist(); // this is slow and messes with the interrupt flag, do not call during interrupt handling
#endif
void print(Clock::time_t time);
void debug();
// determine quality of the DCF77 signal lock
uint8_t get_overall_quality_factor();
// determine the internal clock state
Clock::clock_state_t get_clock_state();
// determine the short term signal quality
// 0xff = not available
// 0..25 = extraordinary poor
// 25.5 would be the expected value for 100% noise
// 26 = very poor
// 50 = best possible, every signal bit matches with the local clock
uint8_t get_prediction_match();
}
//////////////////////////////////////////////////////////////////////////
// Everything below this comment are internals of the library.
// The interfaces are public to ease debugging.
// There is no guarantee that it will be kept stable.
// I did not separate this into several header files due to
// Arduino's poor build system.
// Unless you know what you are doing and unless you accept that
// future release of the library may break your code do not use
// anything below in your code. You have been warned.
//////////////////////////////////////////////////////////////////////////
namespace Internal {
namespace Debug {
void debug_helper(char data);
void bcddigit(uint8_t data);
void bcddigits(uint8_t data);
void hexdump(uint8_t data);
}
namespace DCF77 {
typedef enum {
long_tick = 3,
short_tick = 2,
undefined = 1,
sync_mark = 0
} tick_t;
typedef struct {
uint8_t byte_0; // bit 16-20 // flags
uint8_t byte_1; // bit 21-28 // minutes
uint8_t byte_2; // bit 29-36 // hours, bit 0 of day
uint8_t byte_3; // bit 37-44 // day + weekday
uint8_t byte_4; // bit 45-52 // month + bit 0-2 of year
uint8_t byte_5; // bit 52-58 // year + parity
} serialized_clock_stream;
}
namespace TMP {
// determine type depending on number of bits
template <uint8_t typesize> struct uint_t {};
template <> struct uint_t<8> { typedef uint8_t type; };
template <> struct uint_t<16> { typedef uint16_t type; };
template <> struct uint_t<32> { typedef uint32_t type; };
// determine type depending on limits
template <uint32_t max> struct uval_t {
typedef typename uint_t< max < 256? 8:
max < 65536? 16:
32>::type type;
};
// template to compare types
template <typename x, typename y> struct equal { enum { val = false }; };
template <typename x> struct equal<x, x> { enum { val = true }; };
template <typename T> struct limits { };
template <> struct limits<uint8_t> { enum { min = 0, max = 0xff}; };
template <> struct limits<uint16_t> { enum { min = 0, max = 0xffff}; };
template <> struct limits<uint32_t> { enum { min = 0, max = 0xffffffff}; };
template <bool condition, typename x, typename y> struct if_t { typedef y type; };
template < typename x, typename y> struct if_t<true, x, y> { typedef x type; };
}
namespace Arithmetic_Tools {
template <uint8_t N> inline void bounded_increment(uint8_t &value) __attribute__((always_inline));
template <uint8_t N>
void bounded_increment(uint8_t &value) {
const uint8_t bound = TMP::limits<uint8_t>::max;
if (value >= bound - N) { value = bound; } else { value += N; }
}
template <uint8_t N> inline void bounded_decrement(uint8_t &value) __attribute__((always_inline));
template <uint8_t N>
void bounded_decrement(uint8_t &value) {
const uint8_t bound = TMP::limits<uint8_t>::min;
if (value <= bound + N) { value = bound; } else { value -= N; }
}
template <typename t>
void minimize(t& minimum, t const value) {
if (value < minimum) {
minimum = value;
}
}
template <typename t>
void maximize(t& maximum, t const value) {
if (value > maximum) {
maximum = value;
}
}
void bounded_add(uint8_t &value, const uint8_t amount);
void bounded_sub(uint8_t &value, const uint8_t amount);
uint8_t bit_count(const uint8_t value);
uint8_t parity(const uint8_t value);
uint8_t set_bit(const uint8_t data, const uint8_t number, const uint8_t value);
}
struct DCF77_Encoder {
BCD::bcd_t year; // 0..99
BCD::bcd_t month; // 1..12
BCD::bcd_t day; // 1..31
BCD::bcd_t weekday; // Mo = 1, So = 7
BCD::bcd_t hour; // 0..23
BCD::bcd_t minute; // 0..59
uint8_t second; // 0..60
bool uses_summertime : 1; // false -> wintertime, true -> summertime
bool abnormal_transmitter_operation : 1; // typically false
bool timezone_change_scheduled : 1;
bool leap_second_scheduled : 1;
bool undefined_minute_output : 1;
bool undefined_uses_summertime_output : 1;
bool undefined_abnormal_transmitter_operation_output: 1;
bool undefined_timezone_change_scheduled_output : 1;
// What *** exactly *** is the semantics of the "Encoder"?
// It only *** encodes *** whatever time is set
// It does never attempt to verify the data
uint8_t days_per_month() const;
void reset();
uint8_t get_weekday() const;
BCD::bcd_t get_bcd_weekday() const;
// This will set the weekday by evaluating the date.
void autoset_weekday();
void autoset_timezone();
void autoset_timezone_change_scheduled();
bool verify_leap_second_scheduled(const bool assume_leap_second) const;
void autoset_control_bits();
// This will advance the minute. It will consider the control
// bits while doing so. It will NOT try to properly set the
// control bits. If this is desired "autoset" must be called in
// advance.
void advance_minute();
// This will advance the second. It will consider the control
// bits while doing so. It will NOT try to properly set the
// control bits. If this is desired "autoset" must be called in
// advance.
void advance_second();
DCF77::tick_t get_current_signal() const;
void get_serialized_clock_stream(DCF77::serialized_clock_stream &data) const;
void debug() const;
void debug(const uint16_t cycles) const;
// Bit Bezeichnung Wert Pegel Bedeutung
// 0 M 0 Minutenanfang (
// 1..14 n/a reserviert
// 15 R Reserveantenne aktiv (0 inaktiv, 1 aktiv)
// 16 A1 Ankündigung Zeitzonenwechsel (1 Stunde vor dem Wechsel für 1 Stunde, d.h ab Minute 1)
// 17 Z1 2 Zeitzonenbit Sommerzeit (MEZ = 0, MESZ = 1); also Zeitzone = UTC + 2*Z1 + Z2
// 18 Z2 1 Zeitzonenbit Winterzeit (MEZ = 1, MESZ = 0); also Zeitzone = UTC + 2*Z1 + Z2
// 19 A2 Ankündigung einer Schaltsekunde (1 Stunde vor der Schaltsekunde für 1 Stunde, d.h. ab Minute 1)
// 20 S 1 Startbit für Zeitinformation
// 21 1 Minuten 1er
// 22 2 Minuten 2er
// 23 4 Minuten 4er
// 24 8 Minuten 8er
// 25 10 Minuten 10er
// 26 20 Minuten 20er
// 27 40 Minuten 40er
// 28 P1 Prüfbit 1 (gerade Parität)
// 29 1 Stunden 1er
// 30 2 Stunden 2er
// 31 4 Stunden 4er
// 32 8 Stunden 8er
// 33 10 Stunden 10er
// 34 20 Stunden 20er
// 35 P2 Prüfbit 2 (gerade Parität)
// 36 1 Tag 1er
// 37 2 Tag 2er
// 38 4 Tag 4er
// 39 8 Tag 8er
// 40 10 Tag 10er
// 41 20 Tag 20er
// 42 1 Wochentag 1er (Mo = 1, Di = 2, Mi = 3,
// 43 2 Wochentag 2er (Do = 4, Fr = 5, Sa = 6,
// 44 4 Wochentag 4er (So = 7)
// 45 1 Monat 1er
// 46 2 Monat 2er
// 47 4 Monat 4er
// 48 8 Monat 8er
// 49 10 Monat 10er
// 50 1 Jahr 1er
// 51 2 Jahr 2er
// 52 4 Jahr 4er
// 53 8 Jahr 8er
// 54 10 Jahr 10er
// 55 20 Jahr 20er
// 56 40 Jahr 40er
// 57 80 Jahr 80er
// 58 P3 Prüftbit 3 (gerade Parität)
// 59 sync Sync Marke, kein Impuls (übliches Minutenende)
// 59 0 Schaltsekunde (sehr selten, nur nach Ankündigung)
// 60 sync Sync Marke, kein Impuls (nur nach Schaltsekunde)
// Falls eine Schaltsekunde eingefügt wird, wird bei Bit 59 eine Sekundenmarke gesendet.
// Der Syncimpuls erfolgt dann in Sekunde 60 statt 59. Üblicherweise wird eine 0 als Bit 59 gesendet
// Üblicherweise springt die Uhr beim Wechsel Winterzeit nach Sommerzeit von 1:59:59 auf 3:00:00
// beim Wechsel Sommerzeit nach Winterzeit von 2:59:59 auf 2:00:00
// Die Zeitinformation wird immer 1 Minute im Vorraus übertragen. D.h. nach der Syncmarke hat
// man die aktuelle Zeit
// http://www.dcf77logs.de/SpecialFiles.aspx
// Schaltsekunden werden in Deutschland von der Physikalisch-Technischen Bundesanstalt festgelegt,
// die allerdings dazu nur die international vom International Earth Rotation and Reference Systems
// Service (IERS) festgelegten Schaltsekunden übernimmt. Im Mittel sind Schaltsekunden etwa alle 18
// Monate nötig und werden vorrangig am 31. Dezember oder 30. Juni, nachrangig am 31. März oder
// 30. September nach 23:59:59 UTC (also vor 1:00 MEZ bzw. 2:00 MESZ) eingefügt. Seit der Einführung
// des Systems 1972 wurden ausschließlich die Zeitpunkte im Dezember und Juni benutzt.
};
#if defined(__AVR__)
#include <avr/eeprom.h>
#include <util/atomic.h>
#define CRITICAL_SECTION ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
#elif defined(__arm__)
// Workaround as suggested by Stackoverflow user "Notlikethat"
// http://stackoverflow.com/questions/27998059/atomic-block-for-reading-vs-arm-systicks
static inline int __int_disable_irq(void) {
int primask;
asm volatile("mrs %0, PRIMASK\n" : "=r"(primask));
asm volatile("cpsid i\n");
return primask & 1;
}
static inline void __int_restore_irq(int *primask) {
if (!(*primask)) {
asm volatile ("" ::: "memory");
asm volatile("cpsie i\n");
}
}
// This critical section macro borrows heavily from
// avr-libc util/atomic.h
// --> http://www.nongnu.org/avr-libc/user-manual/atomic_8h_source.html
#define CRITICAL_SECTION for (int primask_save __attribute__((__cleanup__(__int_restore_irq))) = __int_disable_irq(), __n = 1; __n; __n = 0)
#elif defined(__unix__) && defined(__unit_test__)
#warning Compiling for Linux target only supported for unit test purposes. Only fake support for atomic sections. Please take care.
#define CRITICAL_SECTION for (int __n = 1; __n; __n = 0)
#else
#error Unsupported controller architecture
#endif
#define sprint(...) Serial.print(__VA_ARGS__)
#define sprintln(...) Serial.println(__VA_ARGS__)
namespace Binning {
template <typename uint_t>
struct lock_quality_tt {
uint_t lock_max;
uint_t noise_max;
};
template <uint8_t significant_bits>
void score (uint8_t& data, const BCD::bcd_t input, const BCD::bcd_t candidate) {
using namespace Arithmetic_Tools;
const uint8_t the_score = significant_bits - bit_count(input.val ^ candidate.val);
bounded_add(data, the_score);
}
template <typename data_t, typename signal_t, signal_t signal_max>
void score(data_t &value, const signal_t signal, const bool bit) {
// it is assumed that the caller takes care of potential overflow condition
// before calling the score function
if (bit) {
value += signal;
} else {
value += signal_max - signal;
}
}
template <typename data_type, typename noise_type, uint32_t number_of_bins>
struct bins_t {
typedef data_type data_t;
typedef typename TMP::uval_t<number_of_bins>::type index_t;
typedef lock_quality_tt<noise_type> lock_quality_t;
data_t data[number_of_bins];
index_t tick;
noise_type noise_max;
noise_type signal_max;
index_t signal_max_index;
void setup() {
for (index_t index = 0; index < number_of_bins; ++index) {
data[index] = 0;
}
tick = 0;
signal_max = 0;
signal_max_index = number_of_bins + 1;
noise_max = 0;
}
void advance_tick() {
if (tick < number_of_bins - 1) {
++tick;
} else {
tick = 0;
}
}
void get_quality(lock_quality_t & lock_quality) {
CRITICAL_SECTION {
lock_quality.lock_max = signal_max;
lock_quality.noise_max = noise_max;
}
}
uint8_t get_quality_factor() {
noise_type signal_max;
noise_type delta;
CRITICAL_SECTION {
signal_max = this->signal_max;
if (signal_max <= this->noise_max) {
return 0;
}
delta = signal_max - this->noise_max;
}
if (TMP::equal<noise_type, uint32_t>::val) {
// noise_type equals uint32_t --> typically convolution and other integration style stuff
uint8_t log2_plus_1 = 0;
while (signal_max) {
signal_max >>= 1;
++log2_plus_1;
}
// crude approximation for delta/log2(max)
while (log2_plus_1) {
log2_plus_1 >>= 1;
delta >>= 1;
}
return delta<256? delta: 255;
}
if (TMP::equal<noise_type, uint8_t>::val) {
// noise_type equals uint8_t --> typically standard binners
// we define the quality factor as
// (delta) / ld (max + 3)
// unfortunately this is prohibitive expensive to compute
// --> we need some shortcuts
// --> we will cheat a lot
// lookup for ld(n):
// 4 --> 2, 6 --> 2.5, 8 --> 3, 12 --> 3.5
// above 16 --> only count the position of the leading digit
uint8_t quality_factor;
if (signal_max >= 32-3) {
// delta / ld(max+3) ~ delta / ld(max)
uint8_t log2 = 0;
while (signal_max > 0) {
signal_max >>= 1;
++log2;
}
log2 -= 1;
// now 15 >= log2 >= 5
// multiply by 256/log2 and divide by 256
const uint16_t multiplier =
log2 > 12? log2 > 13? log2 > 14? 256/15
: 256/14
: 256/13
: log2 > 8 ? log2 > 10? log2 > 11? 256/12
: 256/11
: log2 > 9? 256/10
: 256/ 9
: log2 > 6? log2 > 7? 256/ 8
: 256/ 7
: log2 > 5? 256/ 6
: 256/ 5;
quality_factor = ((uint16_t)delta * multiplier) >> 8;
} else if (signal_max >= 16-3) {
// delta / 4
quality_factor = delta >> 2;
} else if (signal_max >= 12-3) {
// delta / 3.5
// we know delta <= max < 16-3 = 13 --> delta <= 12
quality_factor = delta >= 11? 3:
delta >= 7? 2:
delta >= 4? 1:
0;
} else if (signal_max >= 8-3) {
// delta / 3
// we know delta <= max < 12-3 = 9 --> delta <= 8
quality_factor = delta >= 6? 2:
delta >= 3? 1:
0;
} else if (signal_max >= 6-3) {
// delta / 2.5
// we know delta <= max < 8-3 = 5 --> delta <= 4
quality_factor = delta >= 3? 1: 0;
} else { // if (max >= 4-3) {
// delta / 2
quality_factor = delta >> 1;
}
return quality_factor;
}
// create compile time error if we encounter an unexpectedt type
typedef bool assert_type_is_known[TMP::equal<noise_type, uint8_t>::val ||
TMP::equal<noise_type, uint32_t>::val ? 0: -1];
}
void debug() {
sprint(this->get_time_value().val, HEX);
sprint(F(" Tick: "));
sprint(this->tick);
sprint(F(" Quality: "));
sprint(this->signal_max, DEC);
sprint('-');
sprint(this->noise_max, DEC);
sprint(F(" Max Index: "));
sprint(this->max_index, DEC);
sprint(F(" Quality Factor: "));
sprintln(this->get_quality_factor(), DEC);
sprint('>');
}
void dump() {
for (index_t index = 0; index < number_of_bins-1; ++index) {
sprint(data[index]);
sprint(',');
}
sprintln(data[number_of_bins-1]);
}
};
template <typename data_type, uint32_t number_of_bins>
struct Decoder : bins_t<data_type, data_type, number_of_bins> {
typedef typename bins_t<data_type, data_type, number_of_bins>::index_t index_t;
typedef data_type data_t;
void compute_max_index() {
this->noise_max = 0;
this->signal_max = 0;
this->signal_max_index = number_of_bins + 1;
for (index_t index = 0; index < number_of_bins; ++index) {
const data_t bin_data = this->data[index];
if (bin_data >= this->signal_max) {
this->noise_max = this->signal_max;
this->signal_max = bin_data;
this->signal_max_index = index;
} else if (bin_data > this->noise_max) {
this->noise_max = bin_data;
}
}
}
BCD::bcd_t get_time_value() {
// there is a trade off involved here:
// low threshold --> lock will be detected earlier
// low threshold --> if lock is not clean output will be garbled
// a proper lock will fix the issue
// the question is: which start up behaviour do we prefer?
const uint8_t threshold = 2;
const index_t offset = (number_of_bins == 60 ||
number_of_bins == 24 ||
number_of_bins == 10)? 0x00: 0x01;
if (this->signal_max - this->noise_max >= threshold) {
return BCD::int_to_bcd((this->signal_max_index + this->tick + 1) % number_of_bins + offset);
} else {
BCD::bcd_t undefined;
undefined.val = 0xff;
return undefined;
}
}
void debug() {
this->debug();
for (index_t index = 0; index < number_of_bins; ++index) {
sprint((index == this->signal_max_index ||
index == ((this->signal_max_index+1) % number_of_bins))
? '|': ',');
sprint(this->data[index], HEX);
}
sprintln();
}
template <typename signal_t, signal_t signal_max, uint8_t signal_bitno_offset, uint8_t significant_bits, bool with_parity>
void BCD_binning(const uint8_t bitno_with_offset, const signal_t signal) {
using namespace Arithmetic_Tools;
using namespace BCD;
// If bit positions are outside of the bit positions relevant for this decoder,
// then stop processing right at the start
if (bitno_with_offset < signal_bitno_offset) { return; }
const uint8_t bitno = bitno_with_offset - signal_bitno_offset;
const uint8_t number_of_bits = significant_bits + with_parity;
if (bitno > number_of_bits) { return; }
if (bitno == number_of_bits) { compute_max_index(); return; }
const data_t upper_bin_bound = TMP::equal<data_t, uint8_t>::val? 255 : (60 * number_of_bits * signal_max) / 2;
// for minutes, hours have parity and start counting at 0
// for days, weeks, month we have no parity and start counting at 1
// for years and decades we have no parity and start counting at 0
// TODO: more explicit offset handling
bcd_t candidate;
candidate.val = (with_parity || number_of_bins == 10)? 0x00: 0x01;
data_t min = upper_bin_bound;
data_t max = 0;
const index_t offset = number_of_bins-1-this->tick;
index_t bin_index = offset;
for (index_t pass=0; pass < number_of_bins; ++pass) {
data_t& current_bin = this->data[bin_index];
if (bitno < significant_bits) {
// score vs. bcd value determined by pass
score<data_t, signal_t, signal_max>(current_bin, signal, (candidate.val >> bitno) & 1);
} else
if (with_parity) {
if (bitno == significant_bits) {
// score vs. parity bit
score<data_t, signal_t, signal_max>(current_bin, signal, parity(candidate.val));
}
}
maximize(max, current_bin);
minimize(min, current_bin);
bin_index = bin_index < number_of_bins-1? bin_index+1: 0;
increment(candidate);
}
if (max - min <= upper_bin_bound) {
// enforce min == 0, upper bound will be fine anyway
for (index_t pass=0; pass < number_of_bins; ++pass) {
this->data[pass] -= min;
}
}
if (max >= upper_bin_bound - number_of_bits) {
// runs at most once per minute
// enforce max <= upper_bin_bound
for (index_t pass=0; pass < number_of_bins; ++pass) {
data_t & current_bin = this->data[pass];
bounded_decrement<number_of_bits>(current_bin);
maximize(current_bin, (data_t) 0);
}
}
}
};
template <typename data_type, uint32_t number_of_bins>
struct Convoluter : bins_t<data_type, uint32_t, number_of_bins> {
typedef typename bins_t<data_type, data_type, number_of_bins>::index_t index_t;
typedef data_type data_t;
void debug() {
for (index_t index = 0; index < number_of_bins; ++index) {
sprint((
// hard coded to the implicit knowledge of the phase detection convolution kernel
index == this->signal_max_index ||
index == ((this->signal_max_index+1*(number_of_bins/10)) % number_of_bins) ||
index == ((this->signal_max_index+2*(number_of_bins/10)) % number_of_bins))
? '|': ',');
sprint(this->data[index], HEX);
}
sprintln();
}
};
template <typename data_t, uint32_t number_of_bins>
struct Binner {
typedef typename TMP::uval_t<number_of_bins>::type index_t;
typedef uint8_t noise_type;
typedef lock_quality_tt<uint8_t> lock_quality_t;
struct bins_t {
data_t data[number_of_bins];
index_t tick;
data_t noise_max;
data_t max;
index_t max_index;
};
bins_t bins;
void setup();
void advance_tick();
void get_quality(lock_quality_tt<uint8_t> &lock_quality);
uint8_t get_quality_factor();
void compute_max_index();
BCD::bcd_t get_time_value();
void debug();
template <typename signal_t, signal_t signal_max, uint8_t significant_bits, bool with_parity>
void BCD_binning(const uint8_t bitno, const signal_t signal);
};
}
template <typename Clock_Controller>
struct DCF77_Demodulator : Binning::Convoluter<uint16_t, Clock_Controller::Configuration::phase_lock_resolution> {
typedef typename Binning::Convoluter<uint16_t, Clock_Controller::Configuration::phase_lock_resolution>::index_t index_t;
typedef typename Binning::Convoluter<uint16_t, Clock_Controller::Configuration::phase_lock_resolution>::data_t data_t;
static const index_t bin_count = Clock_Controller::Configuration::phase_lock_resolution;
static const uint16_t samples_per_second = 1000;
static const uint16_t samples_per_bin = samples_per_second / bin_count;
static const uint16_t bins_per_10ms = bin_count / 100;
static const uint16_t bins_per_50ms = 5 * bins_per_10ms;
static const uint16_t bins_per_60ms = 6 * bins_per_10ms;
static const uint16_t bins_per_100ms = 10 * bins_per_10ms;
static const uint16_t bins_per_200ms = 20 * bins_per_10ms;
static const uint16_t bins_per_400ms = 40 * bins_per_10ms;
static const uint16_t bins_per_500ms = 50 * bins_per_10ms;
static const uint16_t bins_per_600ms = 60 * bins_per_10ms;
static uint16_t wrap(const uint16_t value) {
// faster modulo function which avoids division
uint16_t result = value;
while (result >= bin_count) {
result-= bin_count;
}
return result;
}
static const uint32_t ticks_to_drift_one_tick = 30000UL;
static const uint32_t tuned_ticks_to_drift_one_tick = 300000UL;
// N times the clock precision shall be smaller than 1/resolution
// how many seconds may be cummulated
// this controls how slow the filter may be to follow a phase drift
// N times the clock precision shall be smaller 1/bin_count
// untuned clock ~30 ppm, 100 bins => N < 300
// tuned clock ~2 ppm, 100 bins => N < 3600
// For resonators we will allow slightly higher phase drift thresholds.
// This is because otherwise we will not be able to deal with noise
// in any reasonable way.
uint16_t N = ticks_to_drift_one_tick / bin_count;
void set_has_tuned_clock() {
// will be called once crystal is tuned to better than 1 ppm.
N = tuned_ticks_to_drift_one_tick / bin_count;
}
int32_t integral = 0;
int32_t running_max = 0;
index_t running_max_index = 0;
int32_t running_noise_max = 0;
void setup() {
Binning::Convoluter<uint16_t, Clock_Controller::Configuration::phase_lock_resolution>::setup();
integral = 0;
running_max = 0;
running_max_index = 0;
running_noise_max = 0;
N = ticks_to_drift_one_tick / bin_count;
}
void phase_binning(const uint8_t input)
__attribute__((always_inline)) {
Binning::Convoluter<uint16_t, Clock_Controller::Configuration::phase_lock_resolution>::advance_tick();
const index_t tick = this->tick;
data_t & data = this->data[tick];
if (data > N) {
data = N;
}
if (input) {
if (data < N) {
++data;
}
} else {
if (data > 0) {
--data;
}
}
{
// ck = convolution_kernel