-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathDCF77_Analyzer_Clock_1.72.ino
2034 lines (1767 loc) · 78.3 KB
/
DCF77_Analyzer_Clock_1.72.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
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
/*
================================================================================
DCF77 Analyzer / Clock
================================================================================
This sketch is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This sketch 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
The C++ code is far from optimized because I myself am an Arduino and C++ novice.
But even after learning some more now, I want to keep the code simpel and readable.
That is why I maybe over-documented the code to help understand what's going on.
HELP WANTED:
I'm not experienced enough to get to the point where the second-tick
of the Real Time Clock display is exactly in sync with the DCF pulse.
Now there's a small time lag.
If you have a solution, please let me know!
Erik de Ruiter
2014-2016
May 2014 First version
March 2016 - big overhaul...
Version 1.72
- Option: Use a cheap Ebay PIR detector to shut off selectable display's when no activity is detected.
The switch off delay can be set by the user to prevent the display shutting of if a person
is not moving but the display should be on.
- Now the display Night shut-down can be disabled by making both values 'POWERSAVINGOFFTIME'
and 'POWERSAVINGONTIME' zero.
- Fixed temperature display not shutting off at powersave mode.
- errorCounter display did not reset every hour so that's fixed
Version 1.71
- User option to reset temperature min/max memory at midnight
Version 1.7:
- The resolution of the temperature display is improved: from 0.5 to 0.1 degrees Celsius
Because of the time the DS18B20 sensor needs to convert the temperature and to keep the code clean,
the temperature display is updates once per minute.
- Parity check routine optimized.
- More reliable check for bad DCF data, preventing RTC update with invalid data.
- EoB error now clears inner LED ring as it should.
- The DCF OK LED now displays the condition of the DCF signal more reliably. Turns off immediately if an error occurs
and only turns ON when all 3 parity bits are OK.
Version 1.6:
- Changed temperature function to only calculate once per minute. Got strange errors before the change because
I used a delay of 100ms to give the DS18B20 sensor time to calculate the temperature. But the delay function is
a very bad idea in most c++ code so I finally got rid of it.
Version 1.5:
- Complete overhaul of the scanSignal function and the rest of the code! My first attempt worked but could be improved...
- The rPW and rPT led's did not work as I intended so that is corrected now.
- The End of Buffer error check routine does work now as it should.
- I incorporated a Parity check of the incoming DCF signal. In the signal 3 Parity bits are sent so now these are
checked and only if all three are OK, the received time information is accepted, the display is updated and the RTC synced.
if desired, you can attach 3 extra dual-color LED's (Common Cathode) to see if each of the 3 Parity bits are OK or Failed.
- I made wiring (or changing the wiring) much easier I think by putting all the PIN config in one easy to read table
- As long as you use 1 DS18B20 temp. sensor, I edited the code so you no longer need to figure out the address of the I2C device.
- Big clean-up of the code...
- Powersaving by shutting off the displays (the clock remains functioning as normal)
can now be configured somewhat easier by editing two variables POWERSAVINGONTIME and POWERSAVINGOFFTIME.
- changed some variable names:
- Maxim instances 'lc' and 'lc1' are now MaximCC and MaximCA
- Display description MaximDcfTime is now MaximTemperature
- DCF77SOUNDPIN is now BUZZERPIN
- LED/Display test after power up now build in
Short description:
Power On:
After power-on, first a LED test is performed. The LED's and displays lite up sequentially to keep the power consumption low.
Then the clock starts receiving DCF pulses and when a Minute Mark (2 seconds gap) is detected, the Minute Marker LED is lit
and the buffer counter is reset. The inner LED ring now will show the incoming DCF pulses which are also stored in the buffer.
At 3 moments during reception of data the parity DCF bits are checked to see if the data is valid.
Valid data received:
When, at the end of the minute, after the Minute Mark is detected (BF (Buffer Full) LED is lit), all three parity bits are OK
('DCF OK' LED is lit), the buffer information is used to extract time and date information.
Then the RTC clock is updated ('RTC Synced' LED is lit) and the inner LED ring information is copied to the outer LED ring.
The time, date and week display, day LED, summer/wintertime and leap year LED information is updated with the new time information.
No valid data:
When one or more of the parity bits are not OK because of a noisy signal, receiving of DCF information is continued but
will not be used to update the RTC, display's and LED's. The outer LED ring, 'RTC synced' and 'DCF OK' LED's will be reset.
Time, date, week, day LED, summer/wintertime LED and leap year LED are not affected and keep displaying the last received valid values.
The 'Period Time' and/or 'Period With' error LED's will indicate the error(s) and the error counter display is updated.
Every hour, the error display will bet set to zero.
The EoB, End of Buffer LED is lit when more DCF pulses are received before the Minute Mark is detected due to a noisy signal.
(When a minute Mark is detected we should have no more than 58 bits/pulses)
After the detection of the Minute Marker, a new cycle is started.
Temperature:
At the 30 second mark, the temperature display will show the High and Low values of the past period after the last reset.
Chime:
At the beginning of each hour, the Chime (if connected) will sound.
At night time, a time set by the user in the code itself, the chime is disabled.
Power saving, two options:
1. NIGHT SHUT OFF
At times set by the user, the displays are shutt off at night and turned on in the morning.
Look at the POWERSAVINGOFFTIME and POWERSAVINGONTIME variables.
Check the function <turnDisplaysOff> to select which displays you want to shut off at night.
2. PIR SENSOR
Connect a PIR sensor and activate the PIR option POWERSAVE_BY_PIR and the the delay at PIR_DELAY_TIME.
Every time the PIR detector senses movement, a minute counter is reset but if no movement is detected
longer than the PIR_DELAY_TIME, the displays are shut off.
When movement occurs, the displays immediately switch on.
Note: as said before, the clock will function normally while the displays are shut off.
The only thing is you can't see it... ;)
DCF beep:
With a switch, connected to pin BUZZERPIN, you can hear the received DCF bits coming in.
The tone duration is equivalent to pulse width of the DCF bits, so either 100 or 200 ms.
Miscelleanous:
When the RTC battery is empty or a connection fault is detected, the RTC Error LED is lit.
CREDITS:
I learned a lot from the work of Matthias Dalheimer and Thijs Elenbaas who made their own DCF77 decoders.
Without their work I would not have known where to start.
I ended up writing my own code (using bits and pieces of their ideas) so I could understand what is happening...
My code is far from efficient or advanced but it does work and I know what is going on.
* A big Thank You to Brett Oliver and Joop Tap for pointing out some errors!
Interesting websites:
- Brett Oliver : http://home.btconnect.com/brettoliver1/
- Joop Tap : http://www.jooptap.nl
- Thijs Ellenbaas : http://thijs.elenbaas.net/2012/04/arduino-dcf77-radio-clock-receiver-hardware-2/
- Mathias Dalheimer : https://github.com/roddi/DCF77-Arduino/blob/master/DCF77Servoclock/DCF77.h
- DCF77 wikipedia : https://en.wikipedia.org/wiki/DCF77
- Much more DCF77 info : http://www.picbasic.nl/indexes_uk.htm
- My Flickr website : https://www.flickr.com/photos/edr1924/albums
- My Github website : https://github.com/deruiter
- The Instructables website for this clock: soon!
*/
//----------------------------------------------------------------------------------------------------------
// Libraries
//----------------------------------------------------------------------------------------------------------
// Arduino (new) Time library .................................... http://www.pjrc.com/teensy/td_libs_Time.html
#include <Time.h>
// Enable this line if using Arduino Uno, Mega, etc.
#include <Wire.h>
// a basic DS1307 library that returns time as a time_t .......... http://www.pjrc.com/teensy/td_libs_DS1307RTC.html
#include <DS1307RTC.h>
// Maxim 7219 displays library ................................... http://playground.arduino.cc/Main/LEDMatrix
// !!! NOTE: you must use a special version of the Ledcontrol.h library to get Common Anode support
// because the Maxim chip is normally only suitable for common CATHODE displays!
#include <LedControl.h>
//SPI interface library .......................................... http://arduino.cc/en/Reference/SPI
#include <SPI.h>
// OneWire lets you access 1-wire devices made by Maxim/Dallas,
// such as DS18S20, DS18B20, DS1822 .............................. http://www.pjrc.com/teensy/td_libs_OneWire.html
// The DallasTemperature library can do all this work for you! ... http://milesburton.com/Dallas_Temperature_Control_Library
#include <OneWire.h>
//----------------------------------------------------------------------------------------------------------
// Arduino UNO Pin connections in an easy to read table
//
// Pin 0 - input - Rx - used for programming/communication with PC
// Pin 1 - output - Tx - used for programming/communication with PC
#define DCF77PIN 2 // Pin 2 - input - DCF signal from antenna pcb. Must be pin 2 or 3 because of interrupt!
#define PIRDETECTORPIN 3 // Pin 3 - input - PIR detector: check for activity in the room to activate displays
#define BUZZERPIN 4 // Pin 4 - input - SWITCH - turn on/off DCF77 'beep' piezo buzzer / ON = HIGH, OFF = LOW
#define MAXIMCALD 5 // Pin 5 - output - CS/LOAD - Maxim Common Cathode 7 segment displays
#define MAXIMCACLK 6 // Pin 6 - output - CLOCK - Maxim Common Cathode 7 segment displays
#define MAXIMCADATA 7 // Pin 7 - output - DATA - Maxim Common Cathode 7 segment displays
#define TEMPSENSORPIN 8 // Pin 8 - input - Dallas One Wire DS18B20 temperature sensor
#define TEMPRESETPIN 9 // Pin 9 - input - PUSH BUTTON - reset temperature min/max memory / HIGH = reset
#define MAXIMCCLD 10 // Pin 10 - output - CS/LOAD - Maxim Common Anode 7 segment displays
#define MAXIMCCCLK 11 // Pin 11 - output - CLOCK - Maxim Common Anode 7 segment displays
#define MAXIMCCDATA 12 // Pin 12 - output - DATA - Maxim Common Anode 7 segment displays
// Pin 13
// Pin A0
#define BUZZER A1 // Pin A1 - output - Piezo buzzer for DCF77 'beep' (to '+' of the buzzer)
#define SPEAKERVOLPIN A2 // Pin A2 - output - Sound Board volume - LOW = volume one notch lower. SPEAKERVOLUME determines how many times this output is activated after power on
#define CHIMEPIN A3 // Pin A3 - output - Chime Activate - OUTPUT LOW = Activate Chime on Adafruit Soundboard FX
// USED for DS1307 RTC // Pin A4 - I2C DATA - connect to Real Time Clock pcb
// USED for DS1307 RTC // Pin A5 - I2C CLOCK - connect to Real Time Clock pcb
//----------------------------------------------------------------------------------------------------------
// DS18B20 initialization
//----------------------------------------------------------------------------------------------------------
OneWire ds(TEMPSENSORPIN); // define Onewire instance DS on pin 8
//----------------------------------------------------------------------------------------------------------
// Maxim 7219 Matrix Display initialization
//----------------------------------------------------------------------------------------------------------
/*
clearDisplay(int addr) ........................................ clears the selected display
MaximCC.shutdown(int addr, boolean) ................................ wake up the MAX72XX from power-saving mode (true = sleep, false = awake)
MaximCC.setIntensity(int addr, value) .............................. set a medium brightness for the Leds (0=min - 15=max)
MaximCC.setLed(int addr, int row, int col, boolean state) .......... switch on the led in row, column. remember that indices start at 0!
MaximCC.setRow(int addr, int row, byte value) ...................... this function takes 3 arguments. example: MaximCC.setRow(0,2,B10110000);
MaximCC.setColumn(int addr, int col, byte value) ................... this function takes 3 arguments. example: MaximCC.setColumn(0,5,B00001111);
MaximCC.setDigit(int addr, int digit, byte value, boolean dp) ...... this function takes an argument of type byte and prints the corresponding digit on the specified column.
The range of valid values runs from 0..15. All values between 0..9 are printed as digits,
values between 10..15 are printed as their hexadecimal equivalent
MaximCC.setChar(int addr, int digit, char value, boolean dp) ....... will display: 0 1 2 3 4 5 6 7 8 9 A B C D E F H L P; - . , _ <SPACE> (the blank or space char)
***** Please set the number of devices you have *****
But the maximum default of 8 MAX72XX wil also work.
LedConrol(DATAIN, CLOCK, CS/LOAD, NUMBER OF MAXIM CHIPS)
*/
// lc is for the Maxim Common CATHODE displays
LedControl MaximCC = LedControl(MAXIMCCDATA, MAXIMCCCLK, MAXIMCCLD, 8, false); // Define pins for Maxim 72xx and how many 72xx we use
// lc1 is for the Maxim Common ANODE displays
// !!! NOTE: you must use a special version of the Ledcontrol.h library to get Common Anode support
// because the Maxim chip is normally only suitable for common CATHODE displays!
LedControl MaximCA = LedControl(MAXIMCADATA, MAXIMCACLK, MAXIMCALD, 1, true); // Define pins for Maxim 72xx and how many 72xx we use
//----------------------------------------------------------------------------------------------------------
// User settings, variable and array definitions
//----------------------------------------------------------------------------------------------------------
// The value below is not a PIN number but a value to set how many times the 'Lower volume' input on the sound board is activated
// so that way the volume of the sound board can be lowered after power up, if desired.
#define SPEAKERVOLUME 12
// Choose if you want a test of all LED's and Displays after a startup
// '1' = Yes, '0' = No
#define PERFORM_LED_TEST 1
// Delay between each 7 segment display in ms
#define LEDTEST_DELAY_DISPLAYS 600
// Delay between each LED in the LED ring and other LED's in ms
#define LEDTEST_DELAY_LED_RING 20
// Choose if you want to configure the DS18B20 temperature sensor ONCE to the highest resolution.
// this is needed after using the sensor for the first time. After running the software
// with this setting ON one time, shut it off.
// '1' = ON, '0' = OFF
#define CONFIGURE_DS18B20 0
// define power saving display OFF and ON time
// values are in 'Hour' format
// ONLY the displays are shut off at power saving time, the clock remains active.
// To disable this feature, make both values zero
#define POWERSAVINGOFFTIME 0 // displays are activated
#define POWERSAVINGONTIME 0 // displays are shutt off
// User option to reset temperature min/max memory at midnight
// '1' = Reset at midnight, '0' = Only manual reset
#define TEMPRESET_MIDNIGHT 1
// User option: activate the displays only when there is activity in the room
// '1' = ON, '0' = OFF
#define POWERSAVE_BY_PIR 1
// delay in MINUTES to wait after no detection before shutting off the displays
#define PIR_DELAY_TIME 5
//-------------------------------------------------------------------------------
// define miscellaneous parameters
#define DS1307_I2C_ADDRESS 0x68 // define the RTC I2C address
#define DCF_INTERRUPT 0 // Interrupt number associated with pin
// definition of Maxim 7219 display number wiring sequence
// first Maxim 7219 in wiring 'daisychain' must be '0', next '1' etc.
// COMMON CATHODE DISPLAYS
#define MaximLedRingInner 0
#define MaximLedRingOuter 1
#define MaximPeriodPulse 2
#define MaximBufferBitError 3
#define MaximWeek 4
#define MaximDate 5
#define MaximRtcTime 6
#define MaximLeds 7
// COMMON ANODE DISPLAYS
#define MaximTemperature 0
// definition of display brighness levels
#define BrightnessMaximLedRingOuter 1
#define BrightnessMaximLedRingInner 1
#define BrightnessMaximPeriodPulse 2
#define BrightnessMaximBufferBitError 15
#define BrightnessMaximWeek 3
#define BrightnessMaximDate 9
#define BrightnessMaximRtcTime 1
#define BrightnessMaximLeds 6
#define BrightnessMaximTemperature 4
// definition of Status/Error Led's. We use division and modulo to seperate Row/Col values
// to lit a LED you need a ROW and COLUMN value.
// so, for example: BFLed (with value 14) divided by 10 results in the value '1' (row)
// and BFLed % (Modulo) 10 results in the value '4' (column)
// Modulo explained: http://www.cprogramming.com/tutorial/modulus.html
/* Row/Col LED numbers of Maxim 7219 chip
LED Row Col
-------------- --- --- ----------------------------------------------------------------
Sunday 0 0
Monday 0 1
Tuesday 0 2
Wednesday 0 3
Thursday 0 4
Friday 0 5
Saturday 0 6
Synced 0 7 ON when RTC is set by the received DCF time
RTCError 1 0 ON when there is no response from the RTC (connection / battery empty)
SummerTime 1 1 ON when Central European Summer Time is set
WinterTime 1 2 ON when Central European Time is set
LeapYear 1 3 ON when we have a Leap Year... Duh!
BF 1 4 Flashes ON shortly when the DCF buffer if full and can be processed
EoM 1 5 Flashed ON shortly when the Minute Mark is detected BEFORE the DCF buffer is filled (after power up or other errors)
EoB 1 6 Flashes ON shortly when too many pulses are received before the Minute Marker (noisy signal)
rPW 1 7 Flashes ON shortly when the time between two pulse periods is out of set limits
rPT 2 0 Flashes ON shortly when the pulse time is out of set limits
DCFOk 2 1 ON when whe have a received a full minute of valid DCF data
Chime 2 2 ON when the Chime is enabled. If it's night time or wh switch the Chime off, this LED should be OFF
LEDP1Pass 3 0 ---
LEDP1Fail 3 1 Parity Check LED's, use if desired.
LEDP2Pass 3 2 WHILE receiving the incoming bits, at 3 moments, you can see if the Parity bits are OK or FAILED...
LEDP2Fail 3 3 I use 3 dual color 3mm LED's for this purpose but you can also only use the 'PASS' LED's)
LEDP3Pass 3 4
LEDP3Fail 3 5 ---
*/
#define SyncedLed 07
#define RTCError 10
#define SummerTimeLed 11
#define WinterTimeLed 12
#define LeapYearLed 13
#define BFLed 14
#define EoMLed 15
#define EoBLed 16
#define rPWLed 17
#define rPTLed 20
#define DCFOKLed 21
#define ChimeLed 22
#define LEDP1Pass 30
#define LEDP1Fail 31
#define LEDP2Pass 32
#define LEDP2Fail 33
#define LEDP3Pass 34
#define LEDP3Fail 35
// Pulse flanks
static unsigned long flankTime = 0;
static unsigned long leadingEdge = 0;
static unsigned long trailingEdge = 0;
unsigned long previousLeadingEdge = 0;
// used in <Int0handler>
volatile unsigned int DCFSignalState = 0; // interrupt variables ALWAYS need volatile qualifier!!
// used in <loop>
int previousSecond = 0;
int previousSignalState = 0;
// DCF Buffers and indicators
static int DCFbitBuffer[59]; // here, the received DCFbits are stored
const int bitValue[] = {1, 2, 4, 8, 10, 20, 40, 80}; // these are the decimal values of the received DCFbits
// only after start on a new minute, display received bits on inner LED ring
boolean MinuteMarkerFlag = false;
int bufferPosition = 0;
int previousMinute = 0;
int previousHour = 0;
// variables to check if DCF bits are vald
bool dcfValidSignal = false;
int dcfP1counter = 0;
int dcfP2counter = 0;
int dcfP3counter = 0;
int dcfParityCheckP1 = 0;
int dcfParityCheckP2 = 0;
int dcfParityCheckP3 = 0;
// dcf variables to store decoded DCF time in
int dcfMinute = 0;
int dcfHour = 0;
int dcfDay = 0;
int dcfWeekDay = 0;
int dcfMonth = 0;
int dcfYear = 0;
int dcfDST = 0;
int leapYear = 0;
// variables used to store weeknumber and daynumer values
int dayNumber;
int weekNumber;
// error counter variable
int errorCounter = 0;
boolean errorCondition = false;
// miscelleanous variables
boolean daytimeChange = true;
boolean dayTime = false;
int dcf77SoundSwitch = 0;
// temperature variables
byte present = 0;
byte DS18B20Data[12];
int maxTemp = 0;
int minTemp = 0;
int lowByte = 0;
int highByte = 0;
float tempReading = 0;
int tempCelsius = 0;
boolean tempResetButton = false;
// PIR detector variables
int pirActivity = 0;
int pirDisplaysState = 1;
unsigned int pirTimer = 0;
unsigned long previousTimePIR = 0;
//==============================================================================
// SETUP
//==============================================================================
void setup()
{
// initialize Serial communication
//Serial.begin(115200);
// initialize PIN connections
pinMode(DCF77PIN, INPUT);
pinMode(TEMPRESETPIN, INPUT);
pinMode(BUZZERPIN, INPUT);
pinMode(PIRDETECTORPIN,INPUT);
pinMode(CHIMEPIN, OUTPUT);
pinMode(SPEAKERVOLPIN, OUTPUT);
// Initialize variables and LED displays
initialize();
// Initialize DCF77 pulse interrupt on pin DCF_INTERRUPT, looking for a change of the signal,
// so either rising or falling edge pulses will trigger the interrupt handler and
// execute the int0handler function.
attachInterrupt(DCF_INTERRUPT, int0handler, CHANGE);
// Initialize RTC and set as SyncProvider.
// Later RTC will be synced with DCF time
setSyncProvider(RTC.get); // the function to get the time from the RTC
// check if RTC has set the system time
if (timeStatus() != timeSet)
{ // Unable to sync with the RTC - activate RTCError LED
MaximCC.setLed(MaximLeds, RTCError / 10, RTCError % 10, true);
}
else {
// RTC has set the system time - dim RTCError LED
MaximCC.setLed(MaximLeds, RTCError / 10, RTCError % 10, false);
}
// After power on, set the speaker volume of the Adafruit Audio Board
// initialize both pins to LOW which is the default output state
digitalWrite(SPEAKERVOLPIN, LOW);
digitalWrite(CHIMEPIN, LOW);
// lower volume with 'SPEAKERVOLUME' steps
for(int i = 0; i <= SPEAKERVOLUME; i++)
{
digitalWrite(SPEAKERVOLPIN, HIGH);
delay(100);
digitalWrite(SPEAKERVOLPIN, LOW);
delay(100);
}
// The following function should run only once.
// It is used to configure the temperature resolution of the DS18B20 sensor
if(CONFIGURE_DS18B20 == 1)
{
configureDS18B20();
}
// use for test purposes and/or setting the RTC time manually
// setTime(23, 59, 40, 31, 12, 13);
// RTC.set(now());
// Request the temperature conversion
calculateTemp();
// check if a LED test is needed
if(PERFORM_LED_TEST == 1)
{
// do a LED test
ledTest();
}
else
{
// if not doing a LED test, we need to wait a bit for the DS18B20 sensor to get ready
delay(750);
}
// Now get the temperature from the sensor and display it
displayTemp();
// activate errorCounter display after LED test
ledDisplay(MaximBufferBitError, "R", 0);
}
//==============================================================================
// LOOP
//==============================================================================
void loop()
{
// check first if pulse direction is changed (rising or falling)
// else we would keep evaluating the same pulse
if (DCFSignalState != previousSignalState)
{
// 'reset' state of variable
previousSignalState = DCFSignalState;
// evaluate incoming pulse
scanSignal();
}
// check if switches are changed and act upon it
checkSwitches();
// check for PIR movement
checkPIR();
// execute tasks that must happen only once every second, minute or hour
//----------------------------------------------------------------------------
tasksEverySecond();
tasksEveryMinute();
tasksEveryHour();
}
//================================================================================================================
//
// Function name : processDcfBit
// called from : <scanSignal>
//
// Purpose : Evaluates the signal as it is received. Decides whether we received a "1" or a "0"
// and perform checks to see if the pulse timing is within limits
// Parameters : none
// Return value : none
//
//================================================================================================================
/*
pulse pulse
width width
|- -| |-- --| |----- END OF MINUTE marker:2000ms -----|
___ _______ ___ ___ _______
| 0 | | 1 | | 0 | | 0 | | 1 |
| | | | | | | | | |
| | | | | | | | | |
______| |_______________| |___________| |___________________________________| |_______________| |__ _ _ _
^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^
1000 2100 2000 2200 3000 3100 NO PULSE 5000 5100 6000 6200 << example millis() value
= end of Minute indication
^ ^ ^ ^ ^
DCFbit# 56 DCFbit# 57 DCFbit# 58 DCFbit# 0 DCFbit# 1 etc... << DCF bit received
^ ^ ^
previous leading trailing
leading edge edge edge
^ ^
flanktime (rising or falling)
*/
void scanSignal()
{
//--------------------------------------------------------------------
// Check for Rising-Edge signal and perform checks
//--------------------------------------------------------------------
if (DCFSignalState == 1)
{
// store Rising-Edge Time to check later if the time between two pulses is valid
leadingEdge = millis();
// not much to do now so exit.
return;
}
//--------------------------------------------------------------------
// Check for Falling-Edge signal and perform checks
//--------------------------------------------------------------------
if (DCFSignalState == 0)
{
// store Trailing-Edge Time to check later if the Pulse Width is valid
trailingEdge = millis();
// display period width time on "L"eft side of the 8 digit Maxim 72xx LED display
ledDisplay(MaximPeriodPulse, "L", (leadingEdge - previousLeadingEdge));
// display pulse width time on the "R"ight side of the 8 digit Maxim 72xx LED display
ledDisplay(MaximPeriodPulse, "R", (trailingEdge - leadingEdge));
//--------------------------------------------------------------------------------
// Check PERIOD TIME
//--------------------------------------------------------------------------------
// If this flank UP is detected quickly after previous flank UP this is an incorrect
// Period Time (should be 1000ms -or 2000ms after second 58-) that we shall reject
if ((leadingEdge - previousLeadingEdge) < 900)
{
// rPW - ERROR: Periode Time (rising flank to rising flank) time is too short -> REJECTED
error(rPWLed);
errorCondition = true;
}
//--------------------------------------------------------------------------------
// CHECK PULSE TIME
//--------------------------------------------------------------------------------
// If the detected pulse is too short it will be an incorrect pulse that we shall reject
// should be 100 and 200 ms ideally
if (((trailingEdge - leadingEdge) < 70) || ((trailingEdge - leadingEdge) > 230))
{
//rPT - ERROR: Pulse Width too short or too long -> REJECTED
error(rPTLed);
errorCondition = true;
}
// if we had an error return and start over
if (errorCondition == true)
{
errorCondition = false;
// although we have an error, store current rising edge time to compare at the next Rising-Edge.
previousLeadingEdge = leadingEdge;
return;
}
//--------------------------------------------------------------------
// no errors found so now we can continue
//--------------------------------------------------------------------
// first we turn any error Led's OFF
MaximCC.setLed(MaximLeds, rPWLed / 10, rPWLed % 10, false);
MaximCC.setLed(MaximLeds, rPTLed / 10, rPTLed % 10, false);
MaximCC.setLed(MaximLeds, BFLed / 10, BFLed % 10, false);
MaximCC.setLed(MaximLeds, EoBLed / 10, EoBLed % 10, false);
MaximCC.setLed(MaximLeds, EoMLed / 10, EoMLed % 10, false);
// END OF MINUTE check, looking for a gap of approx. 2000ms
if ( leadingEdge - previousLeadingEdge > 1900 && leadingEdge - previousLeadingEdge < 2100)
{
// end of minute detected:
finalizeBuffer();
}
// refresh previousLeadingEdge time with the new leading edge time
previousLeadingEdge = leadingEdge;
//--------------------------------------------------------------------------------
// process DCF bits
//--------------------------------------------------------------------------------
// distinguish between long and short pulses
if (trailingEdge - leadingEdge < 170)
{
// call processDcfBit function and sent it the value '0'
processDcfBit(0);
// if switch is HIGH, the DCF pulses are audible
if (dcf77SoundSwitch == 1) buzzer(100);
}
else
{
// call processDcfBit function and sent it the value '1'
processDcfBit(1);
// if switch is HIGH, the DCF pulses are audible
if (dcf77SoundSwitch == 1) buzzer(200);
}
} // if (DCFSignalState == 0)
} // void scanSignal();
//================================================================================================================
//
// Function name : processDcfBit
// called from : <scanSignal>
//
// Purpose : after reception of one good DCF bit, do some checks and save it in the DCFbitBuffer array
// Parameters : none
// Return value : none
//
//================================================================================================================
void processDcfBit(int dcfBit)
{
//--------------------------------------------------------------------
// display values on the 7 segment displays
//--------------------------------------------------------------------
// display bufferPosition, digits 7,6
MaximCC.setChar(MaximBufferBitError, 7, bufferPosition / 10, false);
MaximCC.setChar(MaximBufferBitError, 6, bufferPosition % 10, false);
// display received DCFbit, digit 4
MaximCC.setChar(MaximBufferBitError, 4, dcfBit, false);
//--------------------------------------------------------------------
// display incoming DCF bits on inner LED ring
//--------------------------------------------------------------------
// only if we have valid DCF data or after an Minute Mark (EoM) signal
// activate the inner LED ring and diplay incoming data
if (dcfValidSignal == true || MinuteMarkerFlag == true)
{
// display received bits on inner LED ring
MaximCC.setLed(MaximLedRingInner, bufferPosition / 8, bufferPosition % 8, dcfBit);
}
//--------------------------------------------------------------------
// // Fill DCFbitBuffer array with DCFbit
//--------------------------------------------------------------------
DCFbitBuffer[bufferPosition] = dcfBit;
//--------------------------------------------------------------------
// Parity check
//--------------------------------------------------------------------
// DURING reception of the DCF bits, calculate and display the results of the DCF parity check.
//
// There is a Parity bit for the minutes, the hours and for the date.
// DCF77 works with EVEN parity, this works as follows:
// The hours for example have 6 bits plus a paritybit. The bits with value 1 are add up including the paritybit,
// the result must be an even number. If there is a bit wrong received, a 0 is as 1, or a 1 is as 0 received,
// then the result is uneven. source: http://www.picbasic.nl/frameload_uk.htm?http://www.picbasic.nl/info_dcf77_uk.htm
if (bufferPosition == 0)
{
// reset the parity LED's
MaximCC.setLed(MaximLeds, LEDP1Pass / 10, LEDP1Pass % 10, false);
MaximCC.setLed(MaximLeds, LEDP1Fail / 10, LEDP1Fail % 10, false);
MaximCC.setLed(MaximLeds, LEDP2Pass / 10, LEDP2Pass % 10, false);
MaximCC.setLed(MaximLeds, LEDP2Fail / 10, LEDP2Fail % 10, false);
MaximCC.setLed(MaximLeds, LEDP3Pass / 10, LEDP3Pass % 10, false);
MaximCC.setLed(MaximLeds, LEDP3Fail / 10, LEDP3Fail % 10, false);
// reset variables
dcfP1counter = 0;
dcfP2counter = 0;
dcfP3counter = 0;
dcfParityCheckP1 = 0;
dcfParityCheckP2 = 0;
dcfParityCheckP3 = 0;
}
// ----------------------------------------
// First parity check: minute bits
// ----------------------------------------
if (bufferPosition == 28)
{
for(int i = 21; i <= 27; i++)
{
// count the number of bits with the value '1'
dcfP1counter += DCFbitBuffer[i];
}
// perform P1 parity check. Parity is OK if the sum is an EVEN value
if((DCFbitBuffer[28] + dcfP1counter) % 2 == 0)
{
// Parity1 PASS LED ON
MaximCC.setLed(MaximLeds, LEDP1Pass / 10, LEDP1Pass % 10, true);
// Parity P1 PASS
dcfParityCheckP1 = 1;
}
else
{
// Parity1 FAIL LED ON
MaximCC.setLed(MaximLeds, LEDP1Fail / 10, LEDP1Fail % 10, true);
// we have no valid data!
dcfValidSignal = false;
// Turn DCF OK LED OFF
MaximCC.setLed(MaximLeds, DCFOKLed / 10, DCFOKLed % 10, false);
}
}
// ----------------------------------------
// Second parity check: hour bits
// ----------------------------------------
if (bufferPosition == 35)
{
for(int i = 29; i <= 34; i++)
{
dcfP2counter += DCFbitBuffer[i];
}
// perform P2 parity check. Parity is OK if the sum is an EVEN value
if((DCFbitBuffer[35] + dcfP2counter) % 2 == 0)
{
// Parity2 PASS LED ON
MaximCC.setLed(MaximLeds, LEDP2Pass / 10, LEDP2Pass % 10, true);
// Parity P2 PASS
dcfParityCheckP2 = 1;
}
else
{
// Parity2 FAIL LED ON
MaximCC.setLed(MaximLeds, LEDP2Fail / 10, LEDP2Fail % 10, true);
// we have no valid data!
dcfValidSignal = false;
// Turn DCF OK LED OFF
MaximCC.setLed(MaximLeds, DCFOKLed / 10, DCFOKLed % 10, false);
}
}
// ----------------------------------------
// Third parity check: date bits
// ----------------------------------------
if (bufferPosition == 58)
{
for(int i = 36; i <= 57; i++)
{
dcfP3counter += DCFbitBuffer[i];
}
// perform P3 parity check. Parity is OK if the sum is an EVEN value
(DCFbitBuffer[58] + dcfP3counter) % 2 == 0 ? dcfParityCheckP3 = 1 : dcfParityCheckP3 = 0;
// Turn Parity2 'PASS' or 'FAIL' LED ON
if(dcfParityCheckP3 == 1)
{
// Parity2 PASS LED ON
MaximCC.setLed(MaximLeds, LEDP3Pass / 10, LEDP3Pass % 10, true);
// Parity P3 PASS
dcfParityCheckP3 = 1;
}
else
{
// Parity2 FAIL LED ON
MaximCC.setLed(MaximLeds, LEDP3Fail / 10, LEDP3Fail % 10, true);
// we have no valid data!
dcfValidSignal = false;
// Turn DCF OK LED OFF
MaximCC.setLed(MaximLeds, DCFOKLed / 10, DCFOKLed % 10, false);
}
// ----------------------------------------
// finally, check all Parity bits
// ----------------------------------------
dcfParityCheckP1 + dcfParityCheckP2 + dcfParityCheckP3 == 3 ? dcfValidSignal = true : dcfValidSignal = false;
}
//--------------------------------------------------------------------
// before continuing with the next bit, increment counter
//--------------------------------------------------------------------
bufferPosition++;
//--------------------------------------------------------------------
// check if we have not received too many pulses?
//--------------------------------------------------------------------
if (bufferPosition > 59)
{
// End of Buffer (EoB) ERROR - we have received more pulses before reaching
// the 2 second 'gap' signalling the end of the minute.
//This error may be due to a noisy signal giving addition peaks/dcfBits
// So clear both DCFbit displays and start again.
// Reset buffer counter
bufferPosition = 0;
// clear inner LED ring
MaximCC.clearDisplay(MaximLedRingInner);
// turn EoB Error LED ON
error(EoBLed);
// exit
return;
}
//--------------------------------------------------------------------
// everything OK so we wait for next incoming DCFbit
//--------------------------------------------------------------------
}
//================================================================================================================
//
// Function name : finalizeBuffer
// called from : <scanSignal>
//
// Purpose : Process the succesfully received DCF data of one minute
// Parameters : none
// Return value : none
//
//================================================================================================================
void finalizeBuffer(void)
{
//--------------------------------------------------------------------
// We are here because of the detected 2 second 'gap'.
// Now check if it correspondends with the buffer counter
// 'bufferPosition' which should be value 59
//--------------------------------------------------------------------
if (bufferPosition == 59 && dcfValidSignal == true)
{
// bufferPosition == 59 so turn Buffer Full LED ON
MaximCC.setLed(MaximLeds, BFLed / 10, BFLed % 10, true);
// Turn DCF OK LED ON
MaximCC.setLed(MaximLeds, DCFOKLed / 10, DCFOKLed % 10, true);
// Reset inner LED ring (incoming time information)
MaximCC.clearDisplay(MaximLedRingInner);
// copy 'contents' of inner LED ring to the outer LED ring (current time information)
for (int i = 0; i < 59; i++)
{
MaximCC.setLed(MaximLedRingOuter, i / 8, i % 8, DCFbitBuffer[i]);
}
// process buffer and extract data sync the time with the RTC
decodeBufferContents();
// set Arduino time and after that set RTC time
setTime(dcfHour, dcfMinute, 0, dcfDay, dcfMonth, dcfYear);
RTC.set(now());
// activate Synced LED
MaximCC.setLed(MaximLeds, SyncedLed / 10, SyncedLed % 10, true);
// Reset running buffer
bufferPosition = 0;
// Reset DCFbitBuffer array, positions 0-58 (=59 bits)
for (int i = 0; i < 59; i++) {
DCFbitBuffer[i] = 0;
}
// reset flag
MinuteMarkerFlag = false;
} // if (bufferPosition == 59)
//--------------------------------------------------------------------
// The buffer is not yet filled although the 2 second 'gap' was detected.
// Can be result of a noisy signal, starting in middle of receiving data etc.
// Turn End of Message LED ON
//--------------------------------------------------------------------
else
{
MaximCC.setLed(MaximLeds, EoMLed / 10, EoMLed % 10, true);
// Clear displays
MaximCC.clearDisplay(MaximLedRingInner);
MaximCC.clearDisplay(MaximLedRingOuter);
// Reset running buffer and start afresh. Now we are in sync with the incoming data
bufferPosition = 0;
// Reset DCFbitBuffer array, positions 0-58 (=59 bits)
for (int i = 0; i < 59; i++)
{
DCFbitBuffer[i] = 0;
}
// set flag so we can display incoming pulsed on the inner LED ring.
MinuteMarkerFlag = true;
}
}
//================================================================================================================
//
// Function name : decodeBufferContents
// called from : <finalizeBuffer>
//
// Purpose : Evaluates the information stored in the buffer.
// This is where the DCF77 signal is decoded to time and date information
// Parameters : none
// Return value : none
//
//================================================================================================================
void decodeBufferContents(void)
{
// Buffer is full and ready to be decoded
dcfMinute = bitDecode(21, 27);
dcfHour = bitDecode(29, 34);
dcfDay = bitDecode(36, 41);