-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy path2-Part_ProMiniFalconTubeLogger.ino
2977 lines (2526 loc) · 180 KB
/
2-Part_ProMiniFalconTubeLogger.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
// 2-Part logger code by Edward Mallon - modified 2023 for e360 course at Northwestern University
// https://thecavepearlproject.org/2022/03/09/powering-a-promini-logger-for-one-year-on-a-coin-cell/
/*
This program supports an ongoing series of DIY 'Classroom Logger' tutorials from the Cave Pearl Project.
The goal is to provide a starting point for self-built student projects in environmental monitoring.
This low power 2-module iteration runs from a CR2032 coin cell and uses EEprom memory to store sensor readings.
Data download & logger control are managed through the IDE's serial monitor window at 500000 baud.
The logger WILL NOT START until those serial handshakes are completed via a UART connection.
The most important rule to follow when adding new sensors is that this code can only accept 1, 2, 4, 8 or 16 sensorBytesPerRecord.
These 'powers of 2' fit in the I2C buffer AND divide evenly into the EEproms hardware page size to prevent page wrap-arounds.
*/
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// STEP0 :
// Create SENSOR definitions HERE to match the sensors you added to the logger
// Use these as global controls to enable/disable sensor code with #ifdef & #endif
// POWERS OF TWO rule: sensorBytesPerRecord for the enabled sensor combination MUST TOTAL 1,2,4,8 or 16
// IF you see a repeating error at download time it's probably because you violated that rule
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// LowestBattery & RTC_Temperature are the 2 byte 'base values' which are generally recorded with every sensor reading (as they require no extra sensor hardware beyond the logger itself)
#define logLowestBattery // 1-byte (compressed): saves LowestBattery recorded during operation
#define logRTC_Temperature // 1-byte: the RTC's internal 0.25°C resolution temperature sensor
//#define logCurrentBattery // 2-byte: RARELY USED - not 1byte compressed like LowestBattery, primarily included as a powers-of-2 balancing option
//#define readNTC_D6refD7ntc // 2-bytes: ohms // for explanation of the method for reading analog resistance with digital pins see
//#define readLDR_onD9 // 2-bytes: ohms // https://thecavepearlproject.org/2019/03/25/using-arduinos-input-capture-unit-for-high-resolution-sensor-readings/
// 2022 note: 10k refference resistor on D6, NTC on D7, 300Ω on D8, LDR on D9 - does not match newer 2023 e360 NTC connections
//#define readSi7051_Temperature // 2-bytes: often used for NTC calibration - does not require a library, functions for si7051 at end of program
//#define readBh1750_LUX // 2-bytes: raw sensor output: gets converted to Lux during download
// IF you enable all three BME or BMP outputs, you will need two more bytes for an 8-byte record: try adding logLowestBattery & logRTC_Temperature
//#define readBMP280_Temperature // 2-bytes
//#define readBMP280_Pressure // 2-bytes
//#define recordBMP280_Altitude // 2-bytes: calculated by library
//#define recordBMEtemp_2byteInt // 2-byte NOTE: works with BMP & BME
//#define recordBMEpressure_2byteInt // 2-byte NOTE: works with BMP & BME
//#define recordBMEhumidity_2byteInt // 2-byte ONLY if BME 280 connected!
//#define OLED_64x32_SSD1306 // not a sensor, but enabled with define to include needed library - Generates noise on rails, requires 1000uF rail capacitor!-
#define LED_GndGB_A0_A2 // (DEFAULT) note Red on d13 left in place, A0gnd Green A1, blue A2
//#define LED_r9_b10_g11_gnd12 installed // 360 logger config: enables code for RGB indicator LED //1k limit resistor on shared GND line! // Red LED on D13 gets used as indicator if this #define is commented out
//#define countPIReventsPerSampleInterval // 2-bytes: saves # of PIR HIGH events in a specified sample interval. Do not enable this with PIRtriggersSensorReadings - choose one or the other
//#define PIRtriggersSensorReadings // 4-bytes: Still in beta! Do not enable this with PIRcountTriggerEvent - choose one or the other
// does NOT use the regular RTC-alarm based sampling interval but instead records the seconds elapsed between EVERY PIR trigger event in a uint32_t long variable [uint16_t would overflow at ~18 hours]
// WARNING this can use alot of memory very quickly! - recomend use with larger eeprom memory
// PIRtriggersSensorReadings could be enabled with four other bytes of sensor data [for a total of 8 bytes per record] OR with another 12 bytes of sensor data for a total of 16 bytes per record.
#include <Wire.h> // I2C bus coms library: RTC, EEprom & Sensors
#include <EEPROM.h> // note: requires default promini bootloader (ie NOT optiboot)
#include <avr/power.h> // library for shutting down 328p chip peripherals to lower runtime current
#include <avr/sleep.h> // provides SLEEP_MODE_ADC to lower current during ADC readings in readBattery() function
#include <LowPower.h> // NEEDS to be INSTALLED via the Library Manager: for interval & battery recovery sleeps
// Ref, Interval & Echo are reset via serial monitor input - so the values here don't matter
//-------------------------------------------------------------------------------------------------
int32_t InternalReferenceConstant = 1126400; // default = 1126400L = 1100mV internal vref * 1024 // gets changed in setup via serial menu input option later
// adding/subtracting 400 from the constant raises/lowers the 'calculated' result from readBattery() by ~1 millivolt,
// simply read the rail with a DVM while running on UART power and change the constant until the calculation is accurate
int8_t RTCagingOffset = 0; // stores -127 to +128 in the same two complement format that the register in the RTC uses
uint8_t SampleIntervalMinutes = 15; // Allowed values: 1,2,3,5,10,15,20,30 for both - must divide equally into 60!
uint8_t SampleIntervalSeconds = 0; // minutes must be zero for intervalseconds, seconds must be zero for intervalMinutes
// NOTE: Make sure your sensor readings don't take longer than your sample interval!
// If you over-run your alarm because the sensor took too long you will have to wait 24hours for next wakeup
bool ECHO_TO_SERIAL = false; // true enables multiple print statements throughout the code via if(ECHO_TO_SERIAL){} // also starts the run with no interval sync delay so timestamps are misaligned
// most VARIABLES below this point stay the SAME on all machines:
//---------------------------------------------------------------------------
#define fileNAMEonly (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : strrchr(__FILE__, '\\') ? strrchr(__FILE__, '\\') + 1 : __FILE__) //from: https://stackoverflow.com/questions/8487986/file-macro-shows-full-path
const char compileDate[] PROGMEM = __DATE__; // built-in function in C++ makes text string: Jun 29 2023
const char compileTime[] PROGMEM = __TIME__; // built-in function in C++ makes text string: 10:04:18
uint8_t sensorBytesPerRecord = 0; // INCREMENTED at the beginning of setup to match #defined sensors. MUST divide evenly into EEprom Page buffer AND fit inside I2C buffer
uint32_t EEmemPointer = 64; // counter that advances through the EEprom memory locations by sensorBytesPerRecord at each pass through the main loop
#define EEpromI2Caddr 0x57 // Run a bus scan to check where your eeproms are https://github.com/RobTillaart/MultiSpeedI2CScanner
#define totalBytesOfEEpromStorage 4096 // Default: 0x57 / 4096 bytes for 4k // 32k Module: 0x50 & 32768
// OR AT24c512 (via chip swap) 64k: 0x50 & 65536
//defines & variables for ADC & readbattery() function
//------------------------------------------------------------------------------
uint16_t CurrentBattery = 0;
uint16_t LowestBattery = 5764;
uint16_t systemShutdownVoltage = 2795; // MUST be > BrownOutDetect default of 2775mv (which is also the EEprom voltage limit)
byte default_ADCSRA,default_ADMUX; // stores default ADC controll register settings for peripheral shut down
byte set_ADCSRA_2readRailVoltage, set_ADMUX_2readRailVoltage; // stores custom settings for readbattery() via 1.1 internal band gap reference
volatile uint8_t adc_interrupt_counter; // incremented in readADCLowNoise ISR to calculate average of multiple ADC readings
#define NOP __asm__ __volatile__ ("nop\n\t") //https://forum.arduino.cc/index.php?topic=43333.0
//defines & variables for DS3231 RTC
//------------------------------------------------------------------------------
#define rtcAlarmInputPin 2 // DS3231's SQW output is connected to interrupt0 pin D2 on the ProMini
#define DS3231_ADDRESS 0x68 // this is the I2C bus address of our RTC chip
#define DS3231_STATUS_REG 0x0F // reflects status of internal operations
#define DS3231_CONTROL_REG 0x0E // enables or disables clock functions
#define DS3231_TMP_UP_REG 0x11 // temperature registers (upper byte 0x11 & lower 0x12) gets updated every 64sec
#define DS3231_AGING_OFFSET_REG 0x10 // Aging offset register
uint8_t AlarmSelectBits; // sets which parts of time to use or ignore for nxt alarm // ALRM1_SET bits and ALRM2_SET are 0b1000 and 0b0111 respectively.
//uint32_t loggerStartTime; // uint32_t is large enough to hold the 10-digit unixtime number
char CycleTimeStamp[] = "0000/00/00,00:00:00"; //16 character array to store human readble time (with seconds)
uint8_t t_second,t_minute,t_hour,t_day,t_month; // current time variables populated by calling RTC_DS3231_getTime()
uint16_t t_year; //current year //note: yOff = raw year to which you need to add 2000
uint8_t Alarmday,Alarmhour,Alarmminute,Alarmsecond; // calculated variables for setting next alarm
volatile boolean rtc_INT0_Flag = false; // used in startup time sync delay //volatile because it's changed in an ISR // rtc_d2_alarm_ISR() sets this boolean flag=true when RTC alarm wakes the logger
float rtc_TEMP_degC = 0.0;
bool stopRTCoscillator = false;
bool DS3231_PowerLossFlag = false;
// temporary 'buffer' variables only used during calculations
//------------------------------------------------------------------------------
bool booleanBuffer; // boolean for functions that return a true/false or 1/0
uint8_t byteBuffer1 = 9; // 1-byte (8 bit) type = unsigned number from 0 to 255
uint8_t byteBuffer2 = 9; // note: uint8_t is the same as byte variable type
int16_t int16_Buffer = 9999; // 2-byte from -32,768 to 32,767
uint16_t uint16_Buffer= 9999; // 2-byte from 0 to 65535
int32_t int32_Buffer = 9999; // 4-byte from -2,147,483,648 to 2,147,483,647
uint32_t uint32_Buffer= 9999; // 4-byte from 0 to 4,294,967,295 //used for millis() timing
float floatBuffer = 9999.99; // for float calculations
uint8_t hiByte,loByte; // for splitting 16-byte integers during EEprom save
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// STEP1 : #include sensor libraries, create GLOBAL variables, #defines etc. HERE
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
#ifdef countPIReventsPerSampleInterval
//--------------------------
uint16_t d3_INT1_eventCounter = 0;
volatile boolean d3_INT1_Flag = false;
#endif
#ifdef PIRtriggersSensorReadings
//--------------------------------
uint32_t currentPIRtriggerTime;
uint32_t previousPIRtriggerTime;
uint32_t d3_INT1_elapsedSeconds = 0;
uint16_t d3_INT1_eventCounter = 0; //not used in this case but left in for compatiblity with countPIReventsPerSampleInterval
volatile boolean d3_INT1_Flag = false;
#endif
#ifdef readNTC_D6refD7ntc
//------------------
uint16_t NTC_NewReading; // max of 65535 limits our ability to measure 10kNTC at temps below zero C!
#endif
#ifdef readLDR_onD9
//------------------
uint16_t LDR_NewReading; // 65535 limit
#endif
#if defined(readNTC_D6refD7ntc) || defined(readLDR_onD9)
//------------------------------------------------
#define referenceResistorValue 10000UL // ARBITRARY value that should be 'close' to actual but precise value is not required //https://hackingmajenkoblog.wordpress.com/2016/08/12/measuring-arduino-internal-pull-up-resistors/
volatile boolean triggered; // volatiles needed for all digital pin reading of resistance:
volatile uint16_t timer1CounterValue; // prepareForInterrupts(), ISR (TIMER1_OVF_vect), ISR (TIMER1_CAPT_vect),ReadD6riseTimeOnD8
#endif
#ifdef readBh1750_LUX
//------------------------------------------------------------------------------
#include <hp_BH1750.h> // from https://github.com/Starmbi/hp_BH1750 returns the sensor to sleep automatically after each read & supports auto-ranging.
hp_BH1750 bh1750; // Instantiate a BH1750FVI library object
uint16_t lux_BH1750_RawInt; // raw reading before conversion to lux // 2-byte, 0 to 65535
#define Bh1750_Address 0x23
#endif
#if defined(readBMP280_Temperature) || defined(readBMP280_Pressure) || defined(recordBMP280_Altitude)
//-----------------------------------------------------------------------------------
#include <BMP280_DEV.h> // Include the BMP280_DEV.h library // NOTE: this library has disappeared from github?
BMP280_DEV bmp280; // Instantiate (create) a BMP280_DEV object and set-up for I2C operation
#define BMP280_Address 0x76
float Bmp280_Temp_degC, Bmp280_Pr_mBar, Bmp280_altitude_m; // Variables for sensor output
#endif
#if defined(recordBMEtemp_2byteInt) || defined(recordBMEpressure_2byteInt) || defined(recordBMEhumidity_2byteInt)
//-----------------------------------------------------------------------------------
#include <forcedBMX280.h> // install this through the library manager in the IDE
// from https://github.com/soylentOrange/Forced-BMX280/blob/master/examples/BME280_full_example/BME280_full_example.ino
// ctrl_meas - see datasheet section 5.4.5 // forced mode: ctrl_meas[0..1] 0b01 (0b11 for normal and 0b00 for sleep mode)
// pressure oversampling x 1: ctrl_meas[4..2] 0b001
// temperature oversampling x 1: ctrl_meas[7..5] 0b001
// humidity does not have oversampling
ForcedBME280 climateSensor = ForcedBME280();
int32_t g_temperature; // current temperature with 2 decimal places embedded in the integer
uint32_t g_pressure; // current pressure "
uint32_t g_humidity; // current humidity "
#endif
#ifdef readSi7051_Temperature
//------------------------------------------------------------------------------
// we are not using a library - init and read functions for si7051 at end of program
uint16_t TEMP_si7051=0; // NOTE sensors output overruns this uint16_t at 40C!
#define Si7051_Resolution 0b00000000 // 0b00000000 = 14-bit (0.01 C rez), 13 bit = 0b10000000 (0.02 C rez), 12 bit = 0b00000001 (0.04 C rez),11 bit = 0b10000001 (0.08 C rez)
#define Si7051_Address 0x40
#endif
#ifdef OLED_64x32_SSD1306
//------------------------------------------------------------------------------
#include <SSD1306Ascii.h> // https://github.com/greiman/SSD1306Ascii
#include <SSD1306AsciiWire.h> // tells SSD1306Ascii.h to use default wire library
#define oled_I2C_Address 0x3C // 0X3C+SA0 can be 0x3C or 0x3D
SSD1306AsciiWire oled; // declare oled object
#endif
//======================================================================================================================
//======================================================================================================================
//======================================================================================================================
//* * * * * * * * * * * SETUP * * * * * * * * * * * * * * * * *
//======================================================================================================================
//======================================================================================================================
//======================================================================================================================
// NOTE: problems in setup usually call the error_shutdown() function which shuts down the logger
void setup () {
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// STEP2 : Adjust the sensorBytesPerRecord variable to match the #bytes in your
// sensor variables that will be saved to EEprom at each sampling interval
// sensorBytesPerRecord must be 1,2,4,8 or 16 bytes because of EEprom page boundaries
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
#ifdef logLowestBattery
sensorBytesPerRecord = sensorBytesPerRecord + 1; // NOW INDEX-compressed to 1-Byte [with slight loss of resolution]
#endif
#ifdef logRTC_Temperature
sensorBytesPerRecord = sensorBytesPerRecord + 1; // NOW INDEX-compressed to 1-Byte of eeprom storage
#endif
#ifdef countPIReventsPerSampleInterval
sensorBytesPerRecord = sensorBytesPerRecord + 2; // two-byte integer counts Rising of output channel of PIR sensor
#endif
#ifdef PIRtriggersSensorReadings
sensorBytesPerRecord = sensorBytesPerRecord + 4; // 4-byte integer: d3_INT1_elapsedSeconds
#endif
#ifdef readNTC_D6refD7ntc
sensorBytesPerRecord = sensorBytesPerRecord + 2; // two-byte integer: NTC ohms
#endif
#ifdef readLDR_onD9
sensorBytesPerRecord = sensorBytesPerRecord + 2; // two-byte integer: LDR ohms
#endif
#ifdef readBh1750_LUX
sensorBytesPerRecord = sensorBytesPerRecord + 2; // two-byte integer for RAW reading before conversion to lux
#endif
#ifdef readBMP280_Temperature
sensorBytesPerRecord = sensorBytesPerRecord + 2; // two-byte integer
#endif
#ifdef readBMP280_Pressure
sensorBytesPerRecord = sensorBytesPerRecord + 2; // two-byte integer
#endif
#ifdef recordBMP280_Altitude
sensorBytesPerRecord = sensorBytesPerRecord + 2; // two-byte integer
#endif
#ifdef recordBMEtemp_2byteInt
sensorBytesPerRecord = sensorBytesPerRecord + 2; // two-byte integer
#endif
#ifdef recordBMEpressure_2byteInt
sensorBytesPerRecord = sensorBytesPerRecord + 2; // two-byte integer
#endif
#ifdef recordBMEhumidity_2byteInt
sensorBytesPerRecord = sensorBytesPerRecord + 2; // two-byte integer
#endif
#ifdef logCurrentBattery
sensorBytesPerRecord = sensorBytesPerRecord + 2; // two-byte integer
#endif
#ifdef readSi7051_Temperature
sensorBytesPerRecord = sensorBytesPerRecord + 2; // two-byte integer
#endif
// General Startup housekeeping: Set UNUSED digital pins to a known state at startup to reduce current & noise
//------------------------------------------------------------------------------------------------------------
pinMode(13, INPUT); // turn of D13 onboard red LED by setting D13 to INPUT & LOW
#ifdef LED_r9_b10_g11_gnd12 // we will use INPUT & PULLUP resistor to PIP the leds to reduce current
for (int i = 9; i <=12; i++) { pinMode(i, INPUT); digitalWrite(i, LOW); }
pinMode(12, OUTPUT); //the common ground line on our RGB led must OUTPUT to allow current
digitalWrite(A0,LOW);digitalWrite(A1,LOW);digitalWrite(A2,LOW);
#endif
#ifdef LED_GndGB_A0_A2
bitClear(PORTC,1); bitClear(DDRC,1); // A1 [green] LED LOW & INPUT
bitClear(PORTC,2); bitClear(DDRC,2); // A2 [Blue] LED LOW & INPUT
bitClear(PORTC,0); bitSet(DDRC,0); // A0 GND pin LOW & OUTPUT
#endif //LED_GndGB_A0_A2
// set UNUSED digital pins to LOW & OUTPUT so EMI noise does not toggle the pin & draw current
for (int i = 3; i <=8; i++) { digitalWrite(i, LOW); pinMode(i, OUTPUT); } //Note: if an interrupt source connected to D3 then the pin must be reset to INPUT
//Disable UNUSED Peripherals to save power - restart them later when needed - At 3V @ 25°C, the ADC consumes ~87µA so we disable it to save power
SPCR = 0; power_spi_disable(); // stop the peripheral clock with SPCR = 0 BEFORE calling power_spi_disable(); -same applies to the ADC
power_timer1_disable(); power_timer2_disable(); // NOTE: DON'T mess with timer0! - other peripherals like the I2C bus require Timer0 operating
bitSet(ACSR,ACD); // Disables the analog comparator on AIN0(PD6) and AIN1(PD7)by setting the ACD bit (bit 7) of the ACSR register to one. analog comparator draws ~51µA.
// ADC Configuration: default & modified control register settings saved into storage variables
//------------------------------------------------------------------------------------------------------------
#ifndef LED_GndGB_A0_A2 // if NOT defined!
bitSet (DIDR0, ADC0D); // disable digital input on A0
bitSet (DIDR0, ADC1D); // disable digital input on A1
bitSet (DIDR0, ADC2D); // disable digital input on A2 // Digital input circuits can 'leak' a relatively high amount of current if the analog input is approximately half-Vcc
#endif
digitalWrite(A3,LOW); bitSet (DIDR0, ADC3D); // disable digital input on A3
analogReference(DEFAULT); analogRead(A3); // sets the ADC channel to A3 input pin
default_ADCSRA = ADCSRA; default_ADMUX = ADMUX; // Saves the DEFAULT ADC control registers into byte variables so we can restore those ADC control settings later
// Set the ADC system clock prescalar to 32 so it operates at 2x the normal speed note: readings at 2x are nearly identical to 1x speed readings
bitWrite(ADCSRA,ADPS2,1);bitWrite(ADCSRA,ADPS1,0);bitWrite(ADCSRA,ADPS0,1); // https://www.arduino.cc/reference/en/language/functions/bits-and-bytes/bitwrite/
set_ADCSRA_2readRailVoltage = ADCSRA; // store the modified ADCSRA register values for use in readBattery() function & while saving eeprom data
// modify ADC settings to reading the battery/rail voltage using the internal 1.1v reference inside the 328p chip
ADMUX = (0 << REFS1) | (1 << REFS0) | (0 << ADLAR) | (1 << MUX3) | (1 << MUX2) | (1 << MUX1) | (0 << MUX0); // from https://provideyourown.com/2012/secret-arduino-voltmeter-measure-battery-voltage/
set_ADMUX_2readRailVoltage = ADMUX; // store modified ADMUX register values in a variable for use in readBattery()
ADMUX = default_ADMUX; //restore the default
ADCSRA = 0; power_adc_disable(); //turn off the ADC to save power At 3V @ 25°C, the ADC consumes ~87µA
// Configure the DS3231 Real Time Clock control registers for coincell powered operation
//------------------------------------------------------------------------------------------------------------
Wire.begin(); // Start the I2C bus // enables internal 30-50k pull-up resistors on SDA & SCL by default
DS3231_PowerLossFlag = i2c_readRegisterByte(DS3231_ADDRESS, DS3231_STATUS_REG) >> 7; //0Fh BIT 7 is OSF: Oscillator Stopped Flag
// i2c_setRegisterBit function requires: (deviceAddress, registerAddress, bitPosition, 1 or 0)
i2c_setRegisterBit(DS3231_ADDRESS, DS3231_STATUS_REG, 3, 0); // disable the 32khz output pg14-17 of datasheet // This does not reduce the sleep current but can't run because we cut VCC
i2c_setRegisterBit(DS3231_ADDRESS, DS3231_CONTROL_REG, 6, 1); // 0Eh Bit 6 (Battery power ALARM Enable) - MUST set to 1 for wake-up alarms when running from the coincell bkup battery
i2c_setRegisterBit(DS3231_ADDRESS, DS3231_CONTROL_REG, 7, 0); // Enable Oscillator (EOSC). This bit is clear (logic 0) when power is first applied. EOSC = 0 IS REQUIRED with OUR LOGGER DESIGN:
// when EOSC (bit7) is 0, the RTC oscillator continues running during battery powered operation. Otherwise it would stop.
RTC_DS3231_turnOffBothAlarms(); // stops RTC from holding the D2 interrupt line low if system reset just occured
digitalWrite(2, LOW); pinMode(2, INPUT); // D2 INPUT & D2 pullup off because it is not requried with 4k7 hardware pullups on the RTC module
bitSet(EIFR,INTF0); bitSet(EIFR,INTF1); // clears any previous trigger-flags inside the 328p processor for interrupt 0 (D2) & interrupt 1 (D3)
//DS3231 does not have any non-volatile memory so all of the internal registers reset to default if power is lost - so we store this in the 328p eeprom
RTCagingOffset = EEPROM.read(10); // int8_t can store from -127 to +128
if((RTCagingOffset<-128) || (RTCagingOffset>127)){ // if value stored in eeprom is outside normal operating parameters
RTCagingOffset=0; // then resets it to the 0 default
EEPROM.update(10,0); // and store that default back in the eeprom
}
i2c_writeRegisterByte(DS3231_ADDRESS,DS3231_AGING_OFFSET_REG,RTCagingOffset); delay(15);
RTCagingOffset = i2c_readRegisterByte(DS3231_ADDRESS,DS3231_AGING_OFFSET_REG); //RTCagingOffset variable in matching twos complement format as register
// NOTE the RTCagingOffset value gets displayed as part of the serial menu
// Check Previous run Parameters stored in 328p eeprom [ update happens only on 1st run of a brand new logger]
//------------------------------------------------------------------------------------------------------------
EEPROM.get(4,InternalReferenceConstant); //in this case .get is reading 4 consecutive bytes into the long (32bit) integer InternalReferenceConstant
if((InternalReferenceConstant<1000000) || (InternalReferenceConstant>1228800)){ //if value stored in eeprom is outside normal operating parameters
InternalReferenceConstant=1126400; // then re-sets it to the default 1126400
EEPROM.put(4,InternalReferenceConstant);} // and store that default back in the eeprom
SampleIntervalMinutes = EEPROM.read(8); // retrieve 'previous' sampling interval data stored in the CPU's internal 1024 bytes of eeprom space
SampleIntervalSeconds = EEPROM.read(9); // these numbers will be random the first time the logger is run because the EEprom memory locations are empty
if((SampleIntervalMinutes>60) || (SampleIntervalSeconds>30)) //if values read from eeprom are outside allowed maximums then reset to 'safe' default values
{ SampleIntervalMinutes=15;SampleIntervalSeconds=0;
EEPROM.update(8,SampleIntervalMinutes);
EEPROM.update(9,SampleIntervalSeconds); } // .update is the same as .put, except that it only writes the data if there is a change - eeprom has a limited # of write cycles
//===============================================================================================
// Logger Configuration Menu via Serial Monitor [loops for 8 minutes or until START is selected]
//===============================================================================================
// NOTE: Opening the serial monitor always "restarts" the Arduino
Serial.begin(500000); // 500000 baud is max possible with 8Mhz processor http://wormfood.net/avrbaudcalc.php
setup_sendboilerplate2serialMonitor(); // send currently running code fileNAME, deployment details to monitor
//----------------------------------------------------------------------------------------
setup_displayStartMenu(); // see this function & linked sub functions after end of MAIN loop
//----------------------------------------------------------------------------------------
// check if ECHO is on & confirm with user that's OK
if(ECHO_TO_SERIAL){ //confirmation check so we don't waste battery power
Serial.println(F("Serial Output ON! - NOT compatible w bat. powered operation. Disable? y/n"));
//DO NOT ENABLE ECHO_TO_SERIAL during battery powered logger operation or system wastes a lot of power waiting for a serial handshake that never arrives
booleanBuffer = true; byteBuffer1 = 0;
while (booleanBuffer) {
if (Serial.available()) {byteBuffer1 = Serial.read();} //.read captures only character at a time from the serial monitor window
switch (byteBuffer1) {
case 'y':
ECHO_TO_SERIAL = !ECHO_TO_SERIAL; booleanBuffer = false; break;
case 'n':
booleanBuffer = false; break;
default: break;
} // terminates switch case
} // terminates while(booleanBuffer)
} // terminates if(ECHO_TO_SERIAL)
Serial.print(F("Serial "));Serial.println(ECHO_TO_SERIAL ? "ON" : "OFF"); // ? here is short-form of if..else statement which checks the boolean and prints first text if true, second option if false
Serial.println();
//Final confirmation check before erasing EEprom & starting the logger
Serial.println(F("Type 'start' (& enter) to clear the EEprom & begin logging"));
//Serial.println(F("Any other input will shut down the logger"));
Serial.println(F("PROCEEDING at **this** point will ERASE ALL previous DATA!"));
//------------------------------------------------------------------------------------------------------------
Serial.println();
String command=""; // any variables delcared in the setup function get deleted at end of setup function
boolean goFlagReceived = false; // so these variables will 'disapear' when we reach the main loop
Serial.setTimeout(100000); // need to set timeout or .readStringUntil would wait for input forever...
unsigned long startMillis = millis();
do { command = Serial.readStringUntil('\n'); // read serial monitor data into the string until carridge return = \n character
// here we are using if statements to check the input instead of the switch / case method used above, goFlagReceived only becomes 'true' with valid input
if(command == "start"){
Serial.println(F("Erasing EEprom: Stay on UART power until done"));
//------------------------------------------------------------------------------
for (uint32_t memoryLocation=64; memoryLocation<totalBytesOfEEpromStorage; memoryLocation+=16){ // loop writes 16-bytes at a time into the I2C buffer
Wire.beginTransmission(EEpromI2Caddr);
Wire.write(highByte(memoryLocation)); // sends only the HiByte of the 2-byte integer address
Wire.write(lowByte(memoryLocation)); // send only the LowByte of the address
for (byte k=0; k<16; k++) { Wire.write(0);} // we are loading the I2C buffer with 16 'zeros' each time
Wire.endTransmission(); // only at this command does the I2C bus transmission actually occcur
// here we use modulo to print a progress dots in the serial monitor as the memory erase proceeds // also generates a return when memoryLocation=0
if ((memoryLocation % 64) == 0){Serial.print(F("."));} // progress bar dot every 64 memory locations // %(modulo) = reminder after division
if ((memoryLocation % 4096) == 0){Serial.println();} // serial printing takes approximately 10 seconds divided by the baud rate, per character
// while loop which polls the eeprom to see if its ready for the next bytes to be written - can't progress past this point until EEprom says Yes
do{ Wire.beginTransmission(EEpromI2Caddr); }while (Wire.endTransmission() != 0x00); // endTransmission returns ZERO for successfully ACKnowledgement ONLY when EEprom is READ for more data
} //terminates: for (int memoryLocation=0; memoryLocation<totalBytesOfEEpromStorage; memoryLocation+=16){
Serial.println(); goFlagReceived=true;
} else if(command == "test") // 'test' is a HIDDEN option not on the displayed menu
{goFlagReceived=true;} // 'test' simply skips erasing eeprom: I only use this to make debugging faster
if(goFlagReceived) break; // breaks out of the while loop when goFlagReceived=true;
}while ((millis() - startMillis) < 200000); // terminates the do-while after 200 seconds BEFORE loop times out with goFlagReceived=false which leads to logger shutdown
if (!goFlagReceived){ // if goflag=false then the loop timed out so shut down the logger
Serial.println(F("Timeout with NO command recieved -> logger shutting down"));
Serial.flush();error_shutdown(); // shut down the logger
}
Serial.print(F("Initializing sensors: "));Serial.flush();
//------------------------------------------------------------------------------------------------------------
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// STEP3 : Initialize your sensor (if needed)
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// this is where you could configure your control registers & usually take a first reading
// often wrapped with #ifdef Sensor_Address ... #endif statements
// RTC is already initialized in setup!
// no initialization needed for readNTC_D6refD7ntc or readLDR_onD9
// BH1750 initialization
//-----------------------
#ifdef readBh1750_LUX //using library: https://github.com/Starmbi/hp_BH1750
bh1750.begin(Bh1750_Address); // set address & initialize
//bh1750.calibrateTiming(); // NOTE: ambient light must be at least 140 lux when calibrateTiming runs!
//Serial.println(F("BH1750 light sensor MUST be exposed to >150 lux @ startup"));
//Serial.println(F("for self-cal or it may freeze randomly. 15 sec = minimum interval for this sensor"));Serial.println();Serial.flush();
bh1750.start(BH1750_QUALITY_LOW, BH1750_MTREG_LOW); // Quality LOW = fastest readings
// QUALITY_HIGH -Resolution Mode Measurement Time 120-180 ms depending on light levels
// QUALITY_LOW -Resolution Mode Measurement Time 16-24 msec //LOW MTreg:31 resolution lux:7.4, 121557 is highest lux
Serial.print(F("BH1750 started,"));Serial.flush();
#endif // terminates bh1750 init.
// BMP280 initialization
//-----------------------
#if defined(readBMP280_Temperature) || defined(readBMP280_Pressure) || defined(recordBMP280_Altitude)
bmp280.begin(BMP280_Address); // or bmp280.begin(BMP280_I2C_ALT_ADDR); for sensors at 0x76
//Options are OVERSAMPLING_SKIP, _X1, _X2, _X4, _X8, _X16 // pg 15 datasheet One millibar = 100 Pa
bmp280.setPresOversampling(OVERSAMPLING_X4);
//X2 Low power 17 bit / 1.31 Pa 7.5-8.7msec 3-4µA @ 1 Hz forced mode
//X4 Standard resolution 18 bit / 0.66 Pa 11.5-13.3ms 7-10µA @ 1 Hz forced mode
//X8 High resolution 19 bit / 0.33 Pa 19.5-22.5ms 12-18µA @ 1 Hz forced mode
//X16 Ultra high resolution 20 bit / 0.16 Pa 37.5-43.2ms 24-37µA @ 1 Hz forced mode
bmp280.setTempOversampling (OVERSAMPLING_X2);
//X2 Low power 17 bit / 0.0025 °C // NOTE: Higher levels of Temperature oversampling
//X4 Standard resolution 18 bit / 0.0012 °C // provides NO improvement to ACCURACY of pressure readings
//X8 High resolution 19 bit / 0.0006 °C
//X16 Ultra high resolution 20 bit / 0.0003 °C
bmp280.setSeaLevelPressure (1013.25f); // default value for altitude calculations
bmp280.setIIRFilter(IIR_FILTER_OFF);
//take the first reading (just a throw away reading to load the output registers)
bmp280.startForcedConversion(); // time needed here depends on oversampling settings
LowPower.powerDown(SLEEP_30MS, ADC_OFF, BOD_OFF); // 60MSEC = long enough for max rezolution settings
bmp280.getCurrentMeasurements(Bmp280_Temp_degC,Bmp280_Pr_mBar,Bmp280_altitude_m);
Serial.print(F("BMP280 started,"));Serial.flush();
#endif // terminates BMP280 init.
// BME280 initialization
//-----------------------
#if defined(recordBMEtemp_2byteInt) || defined(recordBMEpressure_2byteInt) || defined(recordBMEhumidity_2byteInt)
// NOTE this library defaults to lowest resoulution to save power
// the NOISE depends on the oversampling and, for pressure and temperature on the filter setting used
// noise in temp at x1 oversampling: 0.005 deg C at 25C [see chapter 3.6]
// noise in pressure at x1 oversampling: 3.3mbar @x1 but 1.3mbar at x16 at 25C
// IIR filtering can't be used with one-shot because it requires you to take multiple readings in succession
while (climateSensor.begin()) {Serial.println(F("Waiting for sensor..."));delay(1000);}
Serial.print("BmE280 ID:0x");
Serial.print(climateSensor.getChipID(), HEX);
Serial.println();Serial.flush();
#endif // terminates BME280 init.
#ifdef readSi7051_Temperature
//--------------------
initializeSI7051(); // we are not using a library so you can scroll down to read this function at the end of this program
// if that function was in an #included library it would usually have an object .prefix something like: si7051.initialize()
Serial.print(F("Si7051 started"));Serial.flush(); // if you see this message in the serial monitor you know the sensor did not hang the machine...
#endif
#ifdef OLED_64x32_SSD1306 // using library: https://github.com/greiman/SSD1306Ascii/blob/master/src/SSD1306Ascii.cpp
oled.begin(&Adafruit128x64,oled_I2C_Address);
oled.setFont(System5x7); // list of other availiable fonts: https://github.com/greiman/SSD1306Ascii/tree/master/src/fonts
oled.setContrast(64); //The contrast level -range 0 to 255. // LOWER CONTRAST = DIMMER SCREEN = LESS CURRENT
oled.clear();
oled.ssd1306WriteCmd(SSD1306_DISPLAYOFF); // switch display off until it is needed
// NOTE 0.49 inch OLED Display is only 64x32 pixels which ONLY OCCUPIES THE CENTER SQUARE of the SSD1306's 128x64 memory locations
#endif
Serial.println(F("& Starting the logger:"));Serial.flush();
//========================================================================================
// FINAL STARTUP PROCEDURE: is to DELAY the logger until 1st sampling alarm is synchronized
// otherwise you might get a "clipped interval" at the first hour rollover
//========================================================================================
// ALSO saves logger startup time to 328p internal EEprom WHILE tethered to UART for power
// the saved startup time is used later when startMenu_sendData2Serial reconstructs TimeStamps during data download
//========================================================================================
RTC_DS3231_getTime(); // populates the global variables t_day, t_hour, etc.
Alarmday = t_day; Alarmhour = t_hour; Alarmminute = t_minute;
//AlarmSelectBits = 0b00001000; // A1 Alarm when hours, minutes and seconds match
//AlarmSelectBits = 0b00000000; // A1 Alarm when day of month, hours, minutes and seconds match
if(SampleIntervalSeconds>0){ // NOTE: if the sample interval is in seconds this delay can be VERY short
Alarmsecond = t_second + 1;
do{ Alarmsecond = Alarmsecond + 1;
}while(Alarmsecond % SampleIntervalSeconds); // all non-zero results considered true // forces alignment
//bytebuffer1 = delay minutes, bytebuffer2 = delay seconds
//the expected delays sent to serial - not actual alarm times
byteBuffer2 = Alarmsecond - t_second; byteBuffer1 = 0;
if(Alarmsecond>59){
Alarmsecond = 0;
Alarmminute=Alarmminute+1;
byteBuffer2 =60-t_second;
byteBuffer1 = Alarmminute - t_minute;
}
}else{ // for minute alarms
Alarmsecond = 0; byteBuffer2 = 0;
if(t_second>58){Alarmminute = Alarmminute + 1;}
// and extra buffer if we are too close to rollover
do{
Alarmminute = Alarmminute + 1;
}while(Alarmminute % SampleIntervalMinutes); // all non-zero results considered true // forces alignment
byteBuffer1 = Alarmminute-t_minute;
}
Serial.print(F("Start-up Sync Delay: "));
if(SampleIntervalMinutes>0){Serial.print(byteBuffer1);Serial.print(F("min"));}
if(SampleIntervalSeconds>0){Serial.print(byteBuffer2);Serial.print(F("sec"));}
Serial.println();Serial.flush();
if (Alarmminute > 59 ){ Alarmhour = Alarmhour+1; Alarmminute = 0;} // alt Alarmminute = SampleIntervalMinutes? for longer delay if started at rollover
if (Alarmhour > 23) { Alarmday = Alarmday+1; Alarmhour = 0;} // but day not used here
// NOW set the 1st -ALIGNED- wakeup alarm:
AlarmSelectBits = 0b00001100; // A1 Alarm when minutes AND seconds match, ignores days, hours
RTC_DS3231_setA1Time(0, 0, Alarmminute, Alarmsecond, AlarmSelectBits, 0, 0, 0);
RTC_DS3231_turnOnAlarm(1); // alarm will break the logger out of flashing RED&BLUE light sync delay that follows
noInterrupts (); // make sure we don't get interrupted before we sleep
bitSet(EIFR,INTF0); // clear any previous flags for interrupt 0 (D2) see https://gammon.com.au/interrupts
rtc_INT0_Flag=false; // Flag gets set TRUE only inside rtc_d2_alarm_ISR ISR
attachInterrupt(0,rtc_d2_alarm_ISR, LOW); // RTC SQW alarms LOW and is connected to pin D2 which is interupt channel 0
interrupts ();
//----------------------------------------------------------------------------------------------
// calculate and save UNIXtime for the wakup alarm we just set // same calculation used in function: RTC_DS3231_unixtime():
// we are doing it here because UART is still connected to supply power for the internal eeprom saving which draws alot of current (8mA for EEprom + 5mA for ProMini)
uint16_Buffer = rtc_date2days(t_year, t_month, Alarmday); // the days calculation // don't start your logger at the midnight rollover!
uint32_Buffer = rtc_time2long(uint16_Buffer, Alarmhour, Alarmminute, Alarmsecond); // convert that to seconds
uint32_Buffer += 946684800; // this will be the unixtime when we wake AFTER the sync delay is over // add # seconds from 1970 to 2000 = delta between Unixtime start & our RTC's internal start time
EEPROM.put(0,uint32_Buffer); // store loggerStartTime so it can be used reconstructing each records timestamp in the startMenu_sendData2Serial() later during download
#ifdef PIRtriggersSensorReadings
previousPIRtriggerTime = uint32_Buffer;
#endif
//------------------------------------------------------------------------------
// Transfer BACKUP COPY of starting parameters in bytes 0-64 internal 328p eeprom into 0-64 bytes of external eeprom
// done in four steps because wire buffer can only transfer 16 bytes at a time
Wire.beginTransmission(EEpromI2Caddr); // physicalEEpromAddr = block[0];
Wire.write(0); Wire.write(0); // two bytes to specify the external eeprom address
for (uint8_t p = 0; p < 16; p++) { byteBuffer1 = EEPROM.read(p); Wire.write(byteBuffer1);}
Wire.endTransmission(); delay(12); // AT24c32 Self-Timed Write Cycle (10 ms max) // LowPower.powerDown(SLEEP_15MS, ADC_OFF, BOD_OFF);
Wire.beginTransmission(EEpromI2Caddr);
Wire.write(0); Wire.write(16); // two bytes to specify the external eeprom address
for (uint8_t q = 16; q < 32; q++) { byteBuffer1 = EEPROM.read(q); Wire.write(byteBuffer1);}
Wire.endTransmission(); delay(12);
Wire.beginTransmission(EEpromI2Caddr);
Wire.write(0); Wire.write(32); // two bytes to specify the external eeprom address
for (uint8_t r = 32; r < 48; r++) { byteBuffer1 = EEPROM.read(r); Wire.write(byteBuffer1);}
Wire.endTransmission(); delay(12);
Wire.beginTransmission(EEpromI2Caddr);
Wire.write(0); Wire.write(48); // two bytes to specify the external eeprom address
for (uint8_t s = 48; s < 64; s++) { byteBuffer1 = EEPROM.read(s); Wire.write(byteBuffer1);}
Wire.endTransmission(); delay(12);
//------------------------------------------------------------------------------
Serial.println(F("LEDs will flicker rapidly until logger takes 1st reading."));
Serial.println(F("After that Green LED pips will show when each sensor reading is collected."));
Serial.flush();
if(!ECHO_TO_SERIAL){ // if it's not being used, shut down the UART peripheral now to save power
Serial.println(F("Disconnect UART now - NO additional messages will be sent over serial.")); Serial.flush();
// power_usart0_disable(); // we waited until this point because the startup input menu requires serial input via the UART
// NOTE: TOO many students were forgetting to wrap the Serial.print statements in the main loop with if(ECHO_TO_SERIAL){ } so we commented usart0_disable out
// digital pins 0(RX) and 1(TX) are connected to the UART peripheral inside the 328p chip
// Connecting anything to these pins may interfere with serial communication, including causing failed uploads
}
//------------------------------------------------------------------------------
// FIRST sampling wakeup alarm is already set But instead of simply going to sleep we will use LowPower.powerDown SLEEP_500MS
// to wake the logger once per second to toggle the LEDs ON/Off so user can tell we are in the sync-delay period before logging starts
// RED & BLUE leds start in opposite states so they ALTERNATE when toggled by the PIN register
#if defined(LED_r9_b10_g11_gnd12) || defined(LED_GndGB_A0_A2)
turnOnBlueLED();
#else
pinMode(13,INPUT); // D13 onboard red LED
#endif
byteBuffer1 = 2;
do{ // see a ProMini pin map to understand why we are using PINB here for the LED controls https://images.theengineeringprojects.com/image/webp/2018/06/introduction-to-arduino-pro-mini-2.png.webp?ssl=1
#if defined(LED_r9_b10_g11_gnd12)
toggleRedandBlueLEDs(); // toggles [Blue] & [Red] led channels inside the 5mm common cathode LED
#elif defined(LED_GndGB_A0_A2)
toggleBlueAndGreenLEDs(); // This configuration does not have a RED led channel inside the 5mm common cathode LED
#else
PINB = B00100000; // this toggles ONLY the D13 led // ~50uA to light RED onboard led through D13s internal pullup resistor
#endif
LowPower.powerDown(SLEEP_30MS, ADC_OFF, BOD_OFF); //ADC_ON preserves the existing ADC state - if its already off it stays off
}while(!rtc_INT0_Flag); // sends you back to do{ unless the RTC alarm has triggered
RTC_DS3231_turnOffBothAlarms(); // Note: detachInterrupt(0); already done inside the rtc_d2_alarm_ISR
rtc_INT0_Flag=false;
//terminates synchronization time delay -we are now ready to start logging!
//------------------------------------------------------------------------------------------------------------
turnOffAllindicatorLEDs();
#ifndef logCurrentBattery
LowestBattery = readBattery(); //sets starting value for LowBat, but only needed if not logging CurrentBat
#endif
} // terminator for void setup()
//==========================================================================================
//==========================================================================================
//==========================================================================================
//========================== START OF MAIN loop()===========================================
//==========================================================================================
//==========================================================================================
//================================================== START OF MAIN loop()===================
//==========================================================================================
//==========================================================================================
void loop(){
//----------------------------------------------------------------------------------
// HEARTBEAT LED pip by leaving LED on during sleep delays in next RTC Alarm setting
//----------------------------------------------------------------------------------
turnOnGreenLED(); // the brightest flash we can make with Pullup resistors limiting
//-------------------------------------------------------------------------------
// * * * * Set the next RTC wakeup alarm * * * * * *
//-------------------------------------------------------------------------------
RTC_DS3231_getTime(); // populates global t_minute,t_second variables
#ifdef PIRtriggersSensorReadings
currentPIRtriggerTime = RTC_DS3231_unixtime(); // special case where we preload a variable for a delta calculation
d3_INT1_elapsedSeconds = currentPIRtriggerTime - previousPIRtriggerTime;
previousPIRtriggerTime = currentPIRtriggerTime;
#else
if (SampleIntervalSeconds > 0){ // then our alarm is in (SampleInterval) seconds
Alarmsecond = (t_second + SampleIntervalSeconds) %60; // gives seconds from 0-59 sec e.g. 50s+15 = 65s 65%60 = 5s
// if (Alarmsecond < SampleIntervalSeconds){Alarmsecond=0;} // would force alignment at first minute rollover
Alarmminute = 0;
AlarmSelectBits = 0b00001110; // A1 Alarm when seconds match, ignores others
//if (SampleIntervalSeconds == 1){ // A1 Alarm once per second = Special case
// Alarmminute = 0;
// AlarmSelectBits = 0b00001111; // A1 Alarm once per second = Special case
//}
RTC_DS3231_setA1Time(0, 0, 0, Alarmsecond, AlarmSelectBits, 0, 0, 0);
} else { // use SampleIntervalMinutes
Alarmsecond = 0; // forces matching on even min
Alarmminute = (t_minute + SampleIntervalMinutes) % 60; // gives from 0-59
// if (Alarmminute< SampleIntervalMinutes){Alarmminute=0;} // would force alignment at first hour rollover
AlarmSelectBits = 0b00001100; // A1 Alarm when minutes and seconds match, ignore days, hours
RTC_DS3231_setA1Time(0, 0, Alarmminute, Alarmsecond, AlarmSelectBits, 0, 0, 0);
}
LowPower.powerDown(SLEEP_30MS, ADC_OFF, BOD_OFF); // RTC memory register WRITING time & battery recovery time
RTC_DS3231_turnOnAlarm(1);
LowPower.powerDown(SLEEP_30MS, ADC_OFF, BOD_OFF); // RTC memory register WRITING time & battery recovery time
#endif //terminates #ifdef PIRtriggersSensorReadings
if(ECHO_TO_SERIAL){
Serial.println();Serial.println();Serial.print(F(">Wake: "));
Serial.print(t_year,DEC);Serial.print(F("/"));Serial.print(t_month,DEC);Serial.print(F("/"));Serial.print(t_day,DEC);
Serial.print(F(" "));Serial.print(t_hour,DEC);Serial.print(F(":"));Serial.print(t_minute,DEC);Serial.print(F(":"));Serial.print(t_second,DEC);
#ifndef PIRtriggersSensorReadings
Serial.print(F(" Next Alarm Set in: "));
if (SampleIntervalSeconds > 0){ Serial.print(SampleIntervalSeconds); Serial.print(F("sec"));
}else{ Serial.print(SampleIntervalMinutes);Serial.print(F("min")); }
#endif //#ifndef PIRtriggersSensorReadings
Serial.println();Serial.flush();
}
toggleBlueAndGreenLEDs();
#ifdef logRTC_Temperature
//------------------------------------------------------------------------------
rtc_TEMP_degC = RTC_DS3231_getTemp(); // moved this code into its own function
if(ECHO_TO_SERIAL){
Serial.print(F(", RTC temp[°C]:"));Serial.print(rtc_TEMP_degC,2);Serial.flush();
}
#endif
#ifdef logCurrentBattery // ADC reads use significant power - only read CurrentBat if saving the data
CurrentBattery = readBattery(); // Note: a SLEEP_15MS is embedded in the readBattery function, processor draws about 1mA in sleep mode ADC
if(ECHO_TO_SERIAL){
Serial.print(F(", Current Bat[mV]:"));Serial.print(CurrentBattery);Serial.flush();
}
#endif
#ifdef logLowestBattery
if(ECHO_TO_SERIAL){
Serial.print(F(", Lowest Bat[mV]:"));Serial.print(LowestBattery);Serial.flush();
}
#endif
// if you are only logging LowestBattery, it might be useful to record the unloaded battery voltage once per day (?)
//if(t_hour==0 && t_minute==0 && t_second==0){ // midnight reset prevents 'occasional' low readings from permanently resetting the lobat record
// LowestBattery = readBattery(); // no-load readBattery() calls are usually 20-100mv higher than Lobat reads during high drain EEsave events
// LowPower.powerDown(SLEEP_15MS, ADC_OFF, BOD_OFF);
// }
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// STEP4 : READ your sensors here
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
#if defined(readLDR_onD9) || defined(readNTC_D6refD7ntc)
//------------------------------------------------------
if(ECHO_TO_SERIAL){ Serial.println();Serial.flush();}
ConditionCapacitorOnD8(); // ConditionCapacitor only needs to be called ONCE before other ICU resistor readings
//uint32_Buffer = ReadD8riseTimeOnD8(); // charge cycles the cap through D8 to standardize condition // AND it sets all pins with resistor/sensors connected to that common capacitor to input
uint32_Buffer = ReadD6riseTimeOnD8(); // loads elapsedTime for 10k ohm resistor
#endif
#ifdef readNTC_D6refD7ntc
//------------------
NTC_NewReading = ReadD7riseTimeOnD8(); // a complex function at the end of this program
NTC_NewReading = (referenceResistorValue * (uint32_t)NTC_NewReading) / uint32_Buffer;
if(ECHO_TO_SERIAL){
Serial.print(F(", NTC[Ω]:"));Serial.print(NTC_NewReading);Serial.flush();
}
#endif
#ifdef readLDR_onD9
//-------------------
//LDR_NewReading = ReadD6riseTimeOnD8();
LDR_NewReading = ReadD9riseTimeOnD8();
LDR_NewReading = (referenceResistorValue * (uint32_t)LDR_NewReading) / uint32_Buffer;
if(ECHO_TO_SERIAL){
Serial.print(F(", LDR[Ω]:"));Serial.println(LDR_NewReading);Serial.flush();
}
#endif
#ifdef readBh1750_LUX
//-------------------
bh1750.start(BH1750_QUALITY_LOW, BH1750_MTREG_LOW); // triggers a new sensor reading
// LOW MTreg:31 resolution lux:7.4, 121557 is highest lux
LowPower.powerDown(SLEEP_30MS, ADC_OFF, BOD_OFF); // L-Resolution Mode Measurement Time 16-24 msec
//LowPower.powerDown(SLEEP_30MS, ADC_ON, BOD_OFF); // H-Resolution Mode Measurement Time is much longer: 120-180 ms
lux_BH1750_RawInt =bh1750.getRaw(); // reading can reach 120,000
LowPower.powerDown(SLEEP_30MS, ADC_OFF, BOD_OFF); // battery recovery after I2C - not really needed with this low current sensor
if(ECHO_TO_SERIAL){
Serial.println();Serial.print(F(", Bh1750(rawInt): "));Serial.print(lux_BH1750_RawInt);
Serial.print(F(", Lux(calc): "));Serial.print(bh1750.calcLux(lux_BH1750_RawInt,BH1750_QUALITY_LOW,BH1750_MTREG_LOW),2);Serial.flush();
//if you call calcLux() without Quality & MTreg, the parameters from the last measurement are used for the calculation
}
#endif //readBh1750_LUX
// read bmp280 sensor
//-------------------
#if defined(readBMP280_Temperature) || defined(readBMP280_Pressure) || defined(recordBMP280_Altitude) // '||' means 'OR'
bmp280.startForcedConversion();
LowPower.powerDown(SLEEP_30MS, ADC_OFF, BOD_OFF); //NOTE: sleep time needed here depends on your oversampling settings
if(ECHO_TO_SERIAL){ Serial.println();}
#endif
#ifdef readBMP280_Temperature
bmp280.getCurrentTemperature(Bmp280_Temp_degC);
LowPower.powerDown(SLEEP_15MS, ADC_OFF, BOD_OFF);
if(ECHO_TO_SERIAL){ Serial.print(F(", b280 Temp: ")); Serial.print(Bmp280_Temp_degC,2); Serial.print(F(" °C")); }
#endif
#ifdef readBMP280_Pressure
bmp280.getCurrentPressure(Bmp280_Pr_mBar);
LowPower.powerDown(SLEEP_15MS, ADC_OFF, BOD_OFF);
if(ECHO_TO_SERIAL){ Serial.print(F(", b280 Pr. "));Serial.print(Bmp280_Pr_mBar,2); Serial.print(F(" hPa")); }
#endif
#ifdef recordBMP280_Altitude
bmp280.getCurrentAltitude(Bmp280_altitude_m);
if(ECHO_TO_SERIAL){ Serial.print(F(", b280 Alt. ")); Serial.print(Bmp280_altitude_m,2); Serial.print(F(" m,")); }
#endif
// to read all three at the same time: bmp280.getCurrentMeasurements(Bmp280_Temp_degC, Bmp280_Pr_mBar, Bmp280_altitude_m); //function returns 1 if readings OK
#if defined(readBMP280_Temperature) || defined(readBMP280_Pressure) || defined(recordBMP280_Altitude) // '||' means 'OR'
if(ECHO_TO_SERIAL){ Serial.flush();}
#endif
// read BME280 sensor
//-------------------
#if defined(recordBMEtemp_2byteInt) || defined(recordBMEpressure_2byteInt) || defined(recordBMEhumidity_2byteInt)
climateSensor.takeForcedMeasurement();
LowPower.powerDown(SLEEP_30MS, ADC_OFF, BOD_OFF);//At lowest resoluton this should be plenty of time
#if defined(recordBMEtemp_2byteInt)
g_temperature = climateSensor.getTemperatureCelsius();
if(ECHO_TO_SERIAL){Serial.print(F(" BmeT: "));Serial.print(g_temperature/100);Serial.print(F("."));Serial.print(g_temperature%100);Serial.print(F("°C"));}
#endif
#if defined(recordBMEpressure_2byteInt)
g_pressure = climateSensor.getPressure();
if(ECHO_TO_SERIAL){Serial.print(F(" BmePr: "));Serial.print(g_pressure/100);Serial.print(F("."));Serial.print(g_pressure%100);Serial.print(F("hPa"));}
#endif
#if defined(recordBMEhumidity_2byteInt)
g_humidity = climateSensor.getRelativeHumidity();
if(ECHO_TO_SERIAL){Serial.print(F(" BmeRh: "));Serial.print(g_humidity/100);Serial.print(F("."));Serial.print(g_humidity%100);Serial.print(F("%"));}
#endif
if(ECHO_TO_SERIAL){Serial.println();Serial.flush();}
#endif
#ifdef readSi7051_Temperature
//------------------------------------------------------------------------------
TEMP_si7051 = readSI7051(); // see functions at end of this program
if(ECHO_TO_SERIAL){
Serial.print(F(", SI7051 temp: "));Serial.print(((175.26*TEMP_si7051)/65536.0)-46.85 ,3);Serial.flush();//print 3 decimals
}
LowPower.powerDown(SLEEP_30MS, ADC_OFF, BOD_OFF); // battery recovery time
#endif
//-----------------------------------------------------
// Sensor readings finished: turn off the indicator LED
//-----------------------------------------------------
turnOffAllindicatorLEDs();
//---------------------------------------------------------------------------------
// Setup ADC to read the coincell voltage DURING the EEprom data save:
//---------------------------------------------------------------------------------
bitClear(DDRB,5);bitSet(PORTB,5);
// pip the red d13 LED INPUT_PULLUP to indicate EEprom memory save event(optional)
bitSet(ACSR,ACD); //disable analog comparator ~51µA.
SPCR = 0; ADCSRA =0; power_all_disable();
power_adc_enable(); power_twi_enable(); power_timer0_enable();
ADMUX = set_ADMUX_2readRailVoltage; ADCSRA = set_ADCSRA_2readRailVoltage; // NOTE: ADC @2x normal in setup 32 ADC prescalar
bitWrite(ADCSRA,ADPS2,1);bitWrite(ADCSRA,ADPS1,1);bitWrite(ADCSRA,ADPS0,0); // 64 (default) prescalar @ 8MHz/64 = 125 kHz, =~104uS/ADC reading
bitSet(ADCSRA,ADSC); // triggers a 1st throw-away ADC reading to engauge the Aref cap //1st read takes 20 ADC clock cycles instead of usual 13
LowPower.powerDown(SLEEP_15MS, ADC_ON, BOD_OFF);
// NOTE: Aref capacitor Rise time can take 5-10 milliseconds after starting ADC so 15ms of ADC_ON powerDown sleep works
//---------------------------------------------------------------------------------
// SAVE NEW SENSOR READINGS into EEprom & READ battery after
//---------------------------------------------------------------------------------
// the number of bytes you transfer here must match the number in sensorBytesPerRecord
// AND bytes written to per cycle MUST divide evenly into the eeproms pagesize
// So each cycle can only add 1,2,4,8 or 16 bytes - not 3 , not 5, not 7 etc.
// or you bork the 'powers of 2' math required by page boundaries in the EEprom
//---------------------------------------------------------------------------------
// the general pattern when sending bytes to store in an I2C eeprom:
// Wire.beginTransmission(EEpromAddressonI2Cbus); // first byte in I2C buffer
// Wire.write(highByte(memoryAddress)); // MSB is second byte
// Wire.write(lowByte(memoryAddress)); // LSB is third byte
// Wire.write(byte); // adds 1st byte of SAVED data to buffer
// Wire.write(byte); // adds 2nd byte of SAVED data to buffer
// -more wire.writes- // CONTINUE adding up to 16 DATA bytes per record - divide the sensor variables up into individual bytes for sending
// Wire.endTransmission(); // Only when this command executes does the I2C memory buffer get sent over the I2C bus
// beginTransmission() and write() are slightly misleading terms, they do NOT send commands/packets to the I2C device.
// They are simply queuing commands, which means they are adding bytes to an internal buffer in the Wire/TWI library.
// This internal buffer is not sent to the I2C device on the bus until end.Transmission() is called
// estimate about 100us per byte at 100khz bus = 0.7milliseconds for 3(adr)+4(payload) bytes
//---------------------------------------------------------------------------------
//---------------------------------------------------------------------------------
Wire.beginTransmission(EEpromI2Caddr); // STARTS filling the I2C transmission buffer with the eeprom I2C bus address
Wire.write(highByte(EEmemPointer)); // send the HighByte of the EEprom memory location we want to write to
Wire.write(lowByte(EEmemPointer)); // send the LowByte of the EEprom memory location // Note: we add 'bytes per record' to EEmemPointer at the end of the main loop
//---------------------------------------------------------------------------------
// NOW load the sensor variables - one byte at a time - into the I2C transmission buffer with Wire.write statements
// ORDER of loading here MUST EXACTLY MATCH the order of the bytes retrieved in the startMenu_sendData2Serial function
// ALSO NOTE we are using ZEROs as our END OF FILE marker to stop the download ( this is why we filled the eeprom with zeros at startup)
// So we must implement a 'zero trap' but ONLY ON THE FIRST byte of each record, bumping it to '1' if it actualy was zero - this occasionally causes a data error
#ifdef PIRtriggersSensorReadings
//how to slice a 4-byte LONG uint32_t into individual bytes: & 0b11111111 extracts only the lowest eight bits
d3_INT1_elapsedSeconds++; //++ as a zero-trap for first byte of the record
loByte = d3_INT1_elapsedSeconds & 0b11111111;
Wire.write(loByte); // byte 1 (the lowest byte)
byteBuffer1 = (d3_INT1_elapsedSeconds >> 8) & 0b11111111;
Wire.write(byteBuffer1); // byte 3
byteBuffer2 = (d3_INT1_elapsedSeconds >> 16) & 0b11111111;
Wire.write(byteBuffer2); // byte 2
hiByte = (d3_INT1_elapsedSeconds >> 24) & 0b11111111;
Wire.write(hiByte); // byte 4 (the highest byte)
#endif
#ifdef logLowestBattery // INDEX compression converts lowBattery reading to # less than 255 which can be stored in one byte eeprom memory location
//-------------------------------------------------------------------------------------------------------
int16_Buffer = (LowestBattery-1700)/16; // index compression looses some data due to rounding error - so record is reduced to only 16mv/bit resolution
byteBuffer1 = lowByte(int16_Buffer);
if(byteBuffer1<1){byteBuffer1=1;} // ONLY THE FIRST data byte in each record must have a ZERO TRAP to preserve End Of Data indicator in EEprom
Wire.write(byteBuffer1); // write that single compressed byte to eeprom (we will have to expand it back later !)
#endif //logLowestBattery
#ifdef logRTC_Temperature // NOTE: this 1-byte COMPRESSED encoding limits our temperature range to 0-63 degrees
//--------------------------------------------------------------------------------------------------------
floatBuffer = (rtc_TEMP_degC + 10.0)*4; // *4 converts the RTC temperature into a small integer (63*4= 252 - within the range of one byte!)
int16_Buffer = (int)(floatBuffer); // adding 10 shifts the 63 degree range, so we can record -10C to +53C // there is no loss of information because the resolution is only 0.25C
if(int16_Buffer>255){int16_Buffer=255;} // temps above 53 C get clipped because a byte cant represent them
if(int16_Buffer<1){int16_Buffer=1;} // Zero Trap to preserve End of Data check, this sets lower cutoff temp to -9.75C (but our coincell is dead before that)
byteBuffer1 = lowByte(int16_Buffer);
//if(byteBuffer1<1){byteBuffer1=1;} // ONLY THE FIRST data byte saved in each record must have a ZERO TRAP to preserve zero EOF indicators in EEprom
Wire.write(byteBuffer1);
#endif //logRTC_Temperature
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// STEP5 : add more sensor data bytes to the I2C buffer HERE as required ++++++++
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
#ifdef countPIReventsPerSampleInterval
d3_INT1_eventCounter = d3_INT1_eventCounter+1; // we 'add one' so that the 1st stored byte is never zero - even when the count actually is zero- this is our zero trap
loByte = lowByte(d3_INT1_eventCounter);
Wire.write(loByte);
hiByte = highByte(d3_INT1_eventCounter);
Wire.write(hiByte);
d3_INT1_eventCounter = 0; // after saving the data we can reset our event counter to zero
#endif
#ifdef readNTC_D6refD7ntc
//------------------
loByte = lowByte(NTC_NewReading); // first byte of record gets checked by