forked from NorthernWidget/ALog
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathLogger.cpp
More file actions
1675 lines (1456 loc) · 51.3 KB
/
Logger.cpp
File metadata and controls
1675 lines (1456 loc) · 51.3 KB
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
/*
Data logger library
Written by Andy Wickert
September-October 2011
# LICENSE: GNU GPL v3
Logger.cpp is part of Logger, an Arduino library written by Andrew D. Wickert.
Copyright (C) 2011-2013, Andrew D. Wickert
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////
// INCLUDE HEADER FILE AND ITS INCLUDED LIBRARIES //
////////////////////////////////////////////////////
#include <Logger.h>
// Breaking things up at first, but will try to just put all of the initialize /
// setup stuff in the constructor eventually (or maybe just lump "initialize"
// and "setup")
/////////////////////////////////////////////////////
// STATIC MEMBERS OF CLASS, SO ACCESSIBLE ANYWHERE //
/////////////////////////////////////////////////////
// MAYBE PUT UNDERSCORES BEFORE ALL OF THESE VARS, IF I THINK THERE IS RISK OF
// RE-DEFINING THEM IN SKETCH
// DEFINE BOARD BASED ON MCU TYPE
// First, give integer values to the different board types
const int bottle_logger=0;
const int big_log=1;
const int log_mega=2; // In development
// Then define _model
#if defined(__AVR_ATmega168__) || defined(__AVR_ATmega328P__) || defined(__AVR_ATmega8__) || defined(__AVR_ATmega88__)
const int _model = bottle_logger;
const char _model_name[] = "bottle_logger";
#elif defined(__AVR_ATmega644__) || defined(__AVR_ATmega644P__)
const int _model = big_log;
const char _model_name[] = "big_log";
#elif defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
const int _model = log_mega;
const char _model_name[] = "log_mega";
#endif
// DECLARE PINS
// Should do the full declaration here with some "if's" so I can do "const int"
/////////////////
// ASSIGN PINS //
/////////////////
#if(_model == bottle_logger)
// SD card: CSpin and protected pins
const int CLKpin = 13;
const int MISOpin = 12;
const int MOSIpin = 11;
const int CSpin = 10;
// Protected I2C pins
const int SDApin = A4;
const int SCLpin = A5;
// Digital pins
const int SensorPin = 4; // Activates voltage regulator to give power to sensors
const int SDpin = 8; // Turns on voltage source to SD card
const int LEDpin = 9; // LED to tell user if logger is working properly
const int wakePin = 2; // interrupt pin used for waking up via the alarm
const int interruptNum = wakePin-2; // =0 for pin 2, 1 for pin 3
const int manualWakePin = 5; // Wakes the logger with a manual button - overrides the "wait for right minute" commands
#elif(_model == big_log)
// SD card: CSpin and protected pins
const int CLKpin = 7;
const int MISOpin = 6;
const int MOSIpin = 5;
const int CSpin = 4;
// Protected I2C pins
const int SDApin = 23;
const int SCLpin = 22;
// Digital pins
const int SensorPin = 21; // Activates voltage regulator to give power to sensors
const int SDpin = 22; // Turns on voltage source to SD card
const int LEDpin = 23; // LED to tell user if logger is working properly
const int wakePin = 10; // interrupt pin used for waking up via the alarm
const int interruptNum = 0; // =0 for pin 2, 1 for pin 3
#endif
/////////////////////////////////////////////////
// GLOBAL VARIABLES DEFINED IN INITIALIZE STEP //
/////////////////////////////////////////////////
// Logging interval - wake when minutes == this
int log_minutes;
bool CAMERA_IS_ON = false; // for a video camera
// IS_LOGGING tells the logger if it is awake and actively logging
// Prevents being put back to sleep by an event (e.g., rain gage bucket tip)
// if it is in the middle of logging, so it will return to logging instead.
bool IS_LOGGING = false;
// Filename and logger name
// Filename is set up as 8.3 filename:
//char filename[12];
char* filename;
char* logger_name;
// For interrupt from sensor
bool extInt;
bool NEW_RAIN_BUCKET_TIP = false; // flag
bool LOG_ON_BUCKET_TIP; // Defaults to False, true if you should log every
// time an event (e.g., rain gage bucket tip) happens
/////////////////////////
// INSTANTIATE CLASSES //
/////////////////////////
// Both for same clock, but need to create instances of both
// classes in library (due to my glomming of two libs together)
RTClib RTC;
DS3231 Clock;
// SD CLASSES
SdFat sd;
SdFile datafile;
SdFile otherfile; // for rain gage, camera timing, and anything else that
// doesn't follow the standard logging cycle / regular timing
// Datetime
DateTime now;
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!//
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!//
//!! LOGGER LIBRARY COMPONENTS !!//
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!//
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!//
// Constructor
Logger::Logger(){}
void Logger::initialize(char* _logger_name, char* _filename, int _log_minutes, bool _ext_int, bool _LOG_ON_BUCKET_TIP){
/*
Model automatically determined from the MCU type and is used to modify
pinout-dependent functions. There may be a need to add a board version in the
future, and I don't see a way around passing that via a human-coded variable.
log_minutes is the number of minutes between logging intervals
The logger will wake up, decrement this value, check and check if it is <=0.
If so, it will log; otherwise, it will go back to sleep.
*/
///////////////////
// SLEEP COUNTER //
///////////////////
// Assign the global variables (not intended to change) to the input values
logger_name = _logger_name;
filename = _filename;
log_minutes = _log_minutes;
// Assign the global and changable variables to input values
LOG_ON_BUCKET_TIP = _LOG_ON_BUCKET_TIP;
//////////////////////////////////////////
// EXTERNAL INTERRUPT (E.G., RAIN GAGE) //
//////////////////////////////////////////
// Specific for the bottle logger!
extInt = _ext_int;
if (extInt){
pinMode(extInt, INPUT);
digitalWrite(3, HIGH); // enable internal 20K pull-up
//pinMode(6, INPUT);
//digitalWrite(6, LOW);
}
////////////
// SERIAL //
////////////
Serial.begin(57600);
/////////////////////////////
// Logger models and setup //
/////////////////////////////
if (_model == 0 || _model == 1 || _model == 2){
Serial.print("Logger model = ");
Serial.println(_model_name);
}
else{
Serial.println(F("Error: model name must be ""bottle"" or ""big""."));
Serial.println(F("Stopping execution."));
LEDwarn(100); // 100 quick flashes of the LED
// Do nothing until reset - maybe change this to sleep function so it doesn't drain its own batteries
while(1){}
}
// From weather station code
// For power savings
// http://jeelabs.net/projects/11/wiki/Weather_station_code
#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif
//////////////////////
// LOGGER FILE NAME //
//////////////////////
delay(20);
Serial.print("Filename: ");
Serial.println(filename);
Serial.println(F("Logger done initializing."));
}
void Logger::setupLogger(){
Serial.println(F("Beginning logger setup."));
// We use a 3.3V regulator that we can switch on and off (to conserve power)
// to power the instruments. Therefore, we set the analog reference to
// "EXTERNAL". Do NOT set it to the internal 1.1V reference voltage or to
// "DEFAULT" (VCC), UNLESS you are absolutely sure that you need to and the
// 3.3V regulator connected to the AREF pin is off. Otherwise, you will short
// 1.1V (or VCC) against 3.3V and likely damage the MCU / fry the ADC(?)
analogReference(EXTERNAL); // Commented out in other code - check on how to make this all work
///////////////////////////////////
// CHECK IF LOG_MINUTES IS VALID //
///////////////////////////////////
// Warn if log_minutes is bad.
// This only works for intervals of < 1 hour
if (log_minutes > 59){
Serial.println(F("CANNOT LOG AT INTERVALS OF >= 1 HOUR"));
Serial.println(F("PLEASE CHANGE <log_minutes> PASSED TO FUNCTION <sleep> TO AN INTEGER <= 59"));
LEDwarn(300); // 300 quick flashes of the LED - serious badness!
}
else if (log_minutes == -1){
Serial.println(F("Disabling time-lapse data logging (log_minutes = -1 is the call for this)"));
Serial.println(F("This also disables the ""Log Now"" button."));
}
// 0 will always log - so need to figure out what to do about that. maybe just decide hourly ok default?
//////////////
// SET PINS //
//////////////
pinMode(wakePin,INPUT); // Interrupt to wake up
digitalWrite(wakePin,HIGH); // enable internal 20K pull-up
// Set the rest of the pins: this is my pinModeRunning() function in other code,
// but really is just as good to plop in here
pinMode(CSpin,OUTPUT);
pinMode(SensorPin,OUTPUT);
pinMode(LEDpin,OUTPUT);
pinMode(SDpin,OUTPUT);
// Manual wake pin - only on the new bottle loggers
if (_model == bottle_logger){
Serial.println(F("Setting manualWakePin"));
pinMode(manualWakePin,INPUT);
digitalWrite(manualWakePin,HIGH); // enable internal 20K pull-up
// FIX FIX FIX - PUT ALL PINS INTO "LOW" MODE, BASED ON LOGGER MODEL! COMPLETE COMPLETE!
//pinMode(6, OUTPUT);
//digitalWrite(6, LOW);
}
//Start out with SD, Sensor pins set LOW
digitalWrite(SDpin,LOW);
digitalWrite(SensorPin,LOW);
////////////
// SERIAL //
////////////
Serial.begin(57600);
// Announce start
announce_start();
///////////////////
// WIRE: I2C RTC //
///////////////////
Wire.begin();
/////////////////
// CHECK CLOCK //
/////////////////
// Includes check whether you are talking to Python terminal
startup_sequence();
////////////////
// SD CARD ON //
////////////////
digitalWrite(SDpin,HIGH);
/////////////////////////////////////////////////////////////////////
// Set alarm to go off every time seconds==00 (i.e. once a minute) //
/////////////////////////////////////////////////////////////////////
alarm2_1min();
///////////////////
// SD CARD SETUP //
///////////////////
// Initialize SdFat or print a detailed error message and halt
// Use half speed like the native library.
// change to SPI_FULL_SPEED for more performance.
delay(1000);
name();
Serial.print(F("Initializing SD card..."));
if (!sd.begin(CSpin, SPI_HALF_SPEED)){
Serial.println(F("Card failed, or not present"));
LEDwarn(20); // 20 quick flashes of the LED
sd.initErrorHalt();
}
Serial.println(F("card initialized."));
Serial.println();
LEDgood(); // LED flashes peppy happy pattern, indicating that all is well
delay(50);
name();
Serial.println(F("Logger initialization complete! Ciao bellos."));
delay(50);
digitalWrite(SDpin,LOW);
}
/////////////////////////////////////////////////////
// PRIVATE FUNCTIONS: UTILITIES FOR LOGGER LIBRARY //
/////////////////////////////////////////////////////
void Logger::pinUnavailable(int pin){
int _errorFlag = 0;
char* _pinNameList[9] = {"MISOpin", "MOSIpin", "CSpin", "SensorPin", "SDpin", "LEDpin", "wakePin", "SDApin", "SCLpin"};
int _pinList[9] = {MISOpin, MOSIpin, CSpin, SensorPin, SDpin, LEDpin, wakePin, SDApin, SCLpin};
for (int i=0; i<9; i++){
if (pin == _pinList[i]){
_errorFlag++;
Serial.print("Error: trying to alter the state of Pin ");
Serial.println(_pinList[i]);
Serial.print("This pin is assigned in a system-critical role as: ");
Serial.println(_pinNameList[i]);
// Note: numbers >13 in standard Arduino are analog pins
}
}
if (_errorFlag){
Serial.println(F("Error encountered."));
Serial.println(F("Stalling program: cannot reassign critical pins to sensors, etc."));
LEDwarn(50); // 50 quick flashes of the LED
// Do nothing until reset
while(1){}
}
}
void Logger::sleepNow() // here we put the arduino to sleep
{
IS_LOGGING = false; // Definitely not logging anymore
alarm2reset(); // Turns alarm 2 off and then turns it back
// on so it will go off again next minute
// NOT BACK ON ANYMORE
/* Now is the time to set the sleep mode. In the Atmega8 datasheet
* http://www.atmel.com/dyn/resources/prod_documents/doc2486.pdf on page 35
* there is a list of sleep modes which explains which clocks and
* wake up sources are available in which sleep mode.
*
* In the avr/sleep.h file, the call names of these sleep modes are to be found:
*
* The 5 different modes are:
* SLEEP_MODE_IDLE -the least power savings
* SLEEP_MODE_ADC
* SLEEP_MODE_PWR_SAVE
* SLEEP_MODE_STANDBY
* SLEEP_MODE_PWR_DOWN -the most power savings
*
* For now, we want as much power savings as possible, so we
* choose the according
* sleep mode: SLEEP_MODE_PWR_DOWN
*
*/
set_sleep_mode(SLEEP_MODE_PWR_DOWN); // sleep mode is set here
// setPrescaler(6); // Clock prescaler of 64, slows down to conserve power
cbi(ADCSRA,ADEN); // switch Analog to Digitalconverter OFF
sleep_enable(); // enables the sleep bit in the mcucr register
// so sleep is possible. just a safety pin
/* Now it is time to enable an interrupt. We do it here so an
* accidentally pushed interrupt button doesn't interrupt
* our running program. if you want to be able to run
* interrupt code besides the sleep function, place it in
* setup() for example.
*
* In the function call attachInterrupt(A, B, C)
* A can be either 0 or 1 for interrupts on pin 2 or 3.
*
* B Name of a function you want to execute at interrupt for A.
*
* C Trigger mode of the interrupt pin. can be:
* LOW a low level triggers
* CHANGE a change in level triggers
* RISING a rising edge of a level triggers
* FALLING a falling edge of a level triggers
*
* In all but the IDLE sleep modes only LOW can be used.
*/
if (log_minutes == -1 && extInt == false){
Serial.println(F("All inputs to wake from sleep disabled! Reprogram, please!"));
}
if (log_minutes != -1){
attachInterrupt(interruptNum, wakeUpNow, LOW); // wakeUpNow when wakePin gets LOW
}
if (extInt){
attachInterrupt(1, wakeUpNow_tip, LOW);
}
// Copied from http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1243973204
//power_adc_disable();
//power_spi_disable();
//power_timer0_disable();
//power_timer1_disable();
//power_timer2_disable(); // uncommented because unlike forum poster, I don't rely
// on an internal timer
//power_twi_disable();
// Clearing the Serial buffer: http://forum.arduino.cc/index.php?topic=134764.0
/*
delayMicroseconds(10000);
Serial.flush();
while (!(UCSR0A & (1 << UDRE0))) // Wait for empty transmit buffer
UCSR0A |= 1 << TXC0; // mark transmission not complete
while (!(UCSR0A & (1 << TXC0))); // Wait for the transmission to complete
*/
// End Serial
//Serial.end();
sleep_mode(); // here the device is actually put to sleep!!
// THE PROGRAM CONTINUES FROM HERE AFTER WAKING UP
// After waking, run sleep mode function, and then remainder of this function (below)
sleep_disable(); // first thing after waking from sleep:
// disable sleep...
// detachInterrupt(1); // crude, but keeps interrupts from clashing. Need to improve this to allow both measurements types!
// 06-11-2015: The above line commented to allow the rain gage to be read
// at the same time as other readings
// Maybe move this to specific post-wakeup code?
detachInterrupt(interruptNum); // disables interrupt so the
// wakeUpNow code will not be executed
// during normal running time.
//delay(3); // Slight delay before I feel OK taking readings
// Copied from http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1243973204
//power_all_enable();
/*
Serial.begin(57600);
Serial.flush();
while (!(UCSR0A & (1 << UDRE0))) // Wait for empty transmit buffer
UCSR0A |= 1 << TXC0; // mark transmission not complete
while (!(UCSR0A & (1 << TXC0))); // Wait for the transmission to complete
*/
}
// Must be defined outside of Logger class
void wakeUpNow() // here the interrupt is handled after wakeup
{
// execute code here after wake-up before returning to the loop() function
// timers and code using timers (serial.print and more...) will not work here.
// we don't really need to execute any special functions here, since we
// just want the thing to wake up
IS_LOGGING = true; // Currently logging
// this will allow the logging
// to happen even if the logger is
// already awake to deal with a
// rain gauge bucket tip
sbi(ADCSRA,ADEN); // switch Analog to Digitalconverter ON
}
void wakeUpNow_tip() // here the interrupt is handled after wakeup
{
// execute code here after wake-up before returning to the loop() function
// timers and code using timers (serial.print and more...) will not work here.
// we don't really need to execute any special functions here, since we
// just want the thing to wake up
//sleep_disable(); // first thing after waking from sleep:
// disable sleep...
NEW_RAIN_BUCKET_TIP = true;
// If the logger is already logging, run
if (IS_LOGGING){
}
}
void Logger::alarm2reset()
{
// Reset alarm
Clock.turnOffAlarm(2);
Clock.turnOnAlarm(2);
// Not sure why, but have to use these "checking" functions, or else the clock
// won't realize that it's been reset.
// Here I'm just using them all; they're quick.
// But I could probably ignore the Alarm 1 ones
// Clock.checkAlarmEnabled(1);
Clock.checkAlarmEnabled(2);
// Clock.checkIfAlarm(1);
Clock.checkIfAlarm(2);
}
void Logger::alarm2_1min()
{
// Sets an alarm that will go off once a minute
// for intermittent data logging
// (This will use the AVR interrupt)
Clock.turnOffAlarm(1);
Clock.turnOffAlarm(2);
Clock.setA2Time(1, 0, 0, 0b01110000, false, false, false); // just min mask
Clock.turnOnAlarm(2);
}
void Logger::LEDwarn(int nflash)
{
// Flash LED quickly to say that the SD card (and therefore the logger)
// has not properly initialized upon restart
for(int i=0;i<=nflash;i++){
digitalWrite(LEDpin,HIGH);
delay(50);
digitalWrite(LEDpin,LOW);
delay(50);
}
}
void Logger::LEDgood()
{
// Peppy blinky pattern to show that the logger has successfully initialized
digitalWrite(LEDpin,HIGH);
delay(1000);
digitalWrite(LEDpin,LOW);
delay(300);
digitalWrite(LEDpin,HIGH);
delay(100);
digitalWrite(LEDpin,LOW);
delay(100);
digitalWrite(LEDpin,HIGH);
delay(100);
digitalWrite(LEDpin,LOW);
}
void Logger::LEDtimeWrong(int ncycles)
{
// Syncopated pattern to show that the clock has probably reset to January
// 1st, 2000
for(int i=0;i<=ncycles;i++)
{
digitalWrite(LEDpin,HIGH);
delay(250);
digitalWrite(LEDpin,LOW);
delay(100);
digitalWrite(LEDpin,HIGH);
delay(100);
digitalWrite(LEDpin,LOW);
delay(100);
}
}
void Logger::unixDatestamp(){
now = RTC.now();
// SD
datafile.print(now.unixtime());
datafile.print(",");
// Echo to serial
Serial.print(now.unixtime());
Serial.print(F(","));
}
void Logger::endLine(){
// Ends the line in the file; do this at end of recording instance
// before going back to sleep
datafile.println();
Serial.println();
}
float Logger::_vdivR(int pin,float Rref){
// Same as public vidvR code, but returns value instead of
// saving it to a file
int _ADC;
_ADC = analogRead(pin);
float _ADCnorm = _ADC/1023.0; // Normalize to 0-1
float _R = Rref/_ADCnorm - Rref; // R1 = (R2-Vin)/Vout - R2
return _R;
}
////////////////////////////////////////////////////////////
// PUBLIC UTILITY FUNCTIONS TO IMPLEMENT LOGGER IN SKETCH //
////////////////////////////////////////////////////////////
void Logger::sleep(){
// Maintain backwards compatibility with code that requires "log_minutes"
// to be defined separately from initialize step. (A bad idea, right? Will
// remove this compatibility once it seems to no longer be a problem.)
sleep(log_minutes);
}
void Logger::sleep(int log_minutes){
// Go to sleep
backtosleep:
IS_LOGGING = false; // not logging when sleeping!
sleepNow();
// Wake up
delay(100); // Is such a long delay necessary?
// First, check if there was a bucket tip from the rain gage, if present
if (NEW_RAIN_BUCKET_TIP){
TippingBucketRainGage();
if (LOG_ON_BUCKET_TIP){
// If we want data recorded when the bucket tips, we don't want it to
// interrupt a current logging step.
// And IS_LOGGING will just stay true if we're interrupting that.
IS_LOGGING = true; // First switch the IS_LOGGING flag because we're
// doing it now.
}
}
// Then check if a logging event is supposed to occur
// that is not part of a new bucket tip
else if (IS_LOGGING){
// Check if the logger has been awakend by someone pushing the button
// If so, bypass everything else
if (_model == bottle_logger && (digitalRead(manualWakePin) == LOW)){
}
// FIND OUT HOW TO TELL PROGRAM TO LOG IF TIPPED AND NOT ON TIME
// (done, but hack-ey)
// AND ALSO HOW TO NOT TO GO BACK TO SLEEP IF A CONDITION IS MET
// THAT WILL TELL IT TO DO CONTINUOUS LOGGING FOR SOME TIME
// AND HOW TO PASS THE FLAGS FOR THIS TO THE MAIN CODE FROM OUTSIDE
else{
int minute = Clock.getMinute();
// Only wake if you really have to
if (minute % log_minutes == 0){
Serial.println(F("Logging!"));
}
else {
Serial.print(F("Going back to sleep for "));
Serial.print(minute % log_minutes);
if (minute % log_minutes == 1){
Serial.println(F(" more minute"));
}
else{
Serial.println(F(" more minutes"));
}
// Check right before going back to sleep if there has been a rain
// gauge bucket tip while it has been on
// This is a temporary solution!
if (NEW_RAIN_BUCKET_TIP){
TippingBucketRainGage();
}
goto backtosleep;
}
}
}
}
void Logger::startLogging(){
pinMode(SDpin,OUTPUT); // Seemed to have forgotten between loops... ?
// Initialize logger
digitalWrite(SDpin,HIGH); // Turn on SD card before writing to it
// Delay required after this??
delay(10);
if (!sd.begin(CSpin, SPI_HALF_SPEED)) {
// Just use Serial.println: don't kill batteries by aborting code
// on error
Serial.println(F("Error initializing SD card for writing"));
}
delay(10);
// open the file for write at end like the Native SD library
if (!datafile.open(filename, O_WRITE | O_CREAT | O_AT_END)) {
// Just use Serial.println: don't kill batteries by aborting code
// on error
Serial.print(F("Opening "));
Serial.print(filename);
Serial.println(F(" for write failed"));
delay(10);
}
// Datestamp the start of the line
unixDatestamp();
}
void Logger::endLogging(){
// Ends line, turns of SD card, and resets alarm: ready to sleep
endLine();
// close the file: (This does the actual sync() step too - writes buffer)
datafile.close();
delay(2);
digitalWrite(SDpin,LOW); // Turns off SD card
alarm2reset();
delay(10); // need time to reset alarms?
// Check right before going back to sleep if there has been a rain
// gauge bucket tip while it has been on
// This is a temporary solution!
if (NEW_RAIN_BUCKET_TIP){
TippingBucketRainGage();
}
// After this step, since everything is in the loop() part of the Arduino
// sketch, the sketch will cycle back back to sleep(...)
}
void Logger::startAnalog(){
// Turn on power to analog sensors
digitalWrite(SensorPin,HIGH);
delay(2);
}
void Logger::endAnalog(){
// Turn off power to analog sensors
digitalWrite(SensorPin,LOW);
delay(2);
}
////////////////////////////////
// SENSOR INTERFACE FUNCTIONS //
////////////////////////////////
// Thermistor - with b-value
//////////////////////////////
float Logger::thermistorB(float R0,float B,float Rref,float T0degC,int thermPin){
// R0 and T0 are thermistor calibrations
//
// EXAMPLES:
// thermistorB(10000,3950,30000,25,tempPin); // Cantherm from DigiKey
// thermistorB(10000,3988,13320,25,tempPin); // EPCOS, DigiKey # 495-2153-ND
// Voltage divider
float Rtherm = _vdivR(thermPin,Rref);
//float Rtherm = 10000;
// B-value thermistor equations
float T0 = T0degC + 273.15;
float Rinf = R0*exp(-B/T0);
float T = B / log(Rtherm/Rinf);
// Convert to celsius
T = T - 273.15;
///////////////
// SAVE DATA //
///////////////
// SD
datafile.print(T);
datafile.print(",");
// Echo to serial
Serial.print(T);
Serial.print(F(","));
return T;
}
// MaxBotix ruggedized standard size ultrasonic rangefinder:
// 1 cm = 1 10-bit ADC interval
//////////////////////////////////////////////////////////////
void Logger::ultrasonicMB_analog_1cm(int nping, int EX, int sonicPin, bool writeAll){
// Returns distance in cm
// set EX=99 if you don't need it
float range; // The most recent returned range
float ranges[nping]; // Array of returned ranges
float sumRange = 0; // The sum of the ranges measured
float meanRange; // The average range over all the pings
// Serial.println();
// Get range measurements
// Get rid of any trash; Serial.flush() unnecessary; main thing that is important is
// getting the 2 pings of junk out of the way
Serial.flush();
for (int i=1; i<=2; i++){
if(EX != 99){
digitalWrite(EX,HIGH);
delay(1);
digitalWrite(EX,LOW);
}
delay(100);
}
for(int i=1;i<=nping;i++){
if(EX != 99){
digitalWrite(EX,HIGH);
delay(1);
digitalWrite(EX,LOW);
}
delay(100);
range = analogRead(sonicPin);
ranges[i-1] = range; // 10-bit ADC value = range in cm
// C is 0-indexed, hence the "-1"
if (writeAll){
Serial.print(range);
Serial.print(F(","));
datafile.print(range);
datafile.print(",");
}
sumRange += range;
}
// Find mean of range measurements from sumRange and nping
meanRange = sumRange/nping;
// Find standard deviation
float sumsquares = 0;
float sigma;
for(int i=0;i<nping;i++){
// Sum the squares of the differences from the mean
sumsquares += square(ranges[i]-meanRange);
}
// Calculate stdev
sigma = sqrt(sumsquares/nping);
///////////////
// SAVE DATA //
///////////////
datafile.print(meanRange);
datafile.print(",");
datafile.print(sigma);
datafile.print(",");
// Echo to serial
Serial.print(meanRange);
Serial.print(F(","));
Serial.print(sigma);
Serial.print(F(","));
}
void Logger::maxbotixHRXL_WR_analog(int nping, int sonicPin, int EX, bool writeAll){
// Returns distance in mm, +/- 5 mm
// Each 10-bit ADC increment corresponds to 5 mm.
// set EX=99 if you don't need it (or leave it clear -- this is default)
// Default nping=10 and sonicPin=A0
// Basically only differs from older MB sensor function in its range scaling
// and its added defaults.
float range; // The most recent returned range
float ranges[nping]; // Array of returned ranges
float sumRange = 0; // The sum of the ranges measured
float meanRange; // The average range over all the pings
int sp; // analog reading of sonic pin; probably unnecessary, but Arduino warns against having too many fcns w/ artihmetic, I think
// Get range measurements
// Get rid of any trash; Serial.flush() unnecessary; main thing that is important is
// getting the 2 pings of junk out of the way
Serial.flush();
for (int i=1; i<=2; i++){
if(EX != 99){
digitalWrite(EX,HIGH);
delay(1);
digitalWrite(EX,LOW);
}
delay(100);
}
for(int i=1;i<=nping;i++){
if(EX != 99){
digitalWrite(EX,HIGH);
delay(1);
digitalWrite(EX,LOW);
}
delay(100);
sp = analogRead(sonicPin);
range = (sp + 1) * 5;
ranges[i-1] = range; // 10-bit ADC value (1--1024) * 5 = range in mm
// C is 0-indexed, hence the "-1"
if (writeAll){
Serial.print(range);
Serial.print(F(","));
datafile.print(range);
datafile.print(",");
}
sumRange += range;
}
// Find mean of range measurements from sumRange and nping
meanRange = sumRange/nping;
// Find standard deviation
float sumsquares = 0;
float sigma;
for(int i=0;i<nping;i++){
// Sum the squares of the differences from the mean
sumsquares += square(ranges[i]-meanRange);
}
// Calculate stdev
sigma = sqrt(sumsquares/nping);
///////////////
// SAVE DATA //
///////////////
datafile.print(meanRange);
datafile.print(",");
datafile.print(sigma);
datafile.print(",");
// Echo to serial
Serial.print(meanRange);
Serial.print(F(","));
Serial.print(sigma);
Serial.print(F(","));
}
float Logger::maxbotixHRXL_WR_Serial(int Ex, int Rx, int npings, bool writeAll, int maxRange, bool RS232){
// Stores the ranging output from the MaxBotix sensor
int myranges[npings]; // Declare an array to store the ranges [mm] // Should be int, but float for passing to fcns
// Get nodata value - 5000 or 9999 based on logger max range (in meters)
// I have also made 0 a nodata value, because it appears sometimes and shouldn't
// (minimum range = 300 mm)
int nodata_value;
if (maxRange == 5){
nodata_value = 5000;
}
else if (maxRange == 10){
nodata_value = 9999;
}
// Put all of the range values in the array
for (int i=0; i<npings; i++){
myranges[i] = maxbotix_Serial_parse(Ex, Rx, RS232);
}
// Then get the mean and standard deviation of all of the data
int npings_with_nodata_returns = 0;
unsigned long sum_of_good_ranges = 0;
int good_ranges[npings];
int j=0;
for (int i=0; i<npings; i++){
if (myranges[i] != nodata_value && myranges[i] != 0){
sum_of_good_ranges += myranges[i];
good_ranges[j] = myranges[i];
j++;
}
else{
npings_with_nodata_returns ++;
}
}
float npings_with_real_returns = npings - npings_with_nodata_returns;
float mean_range;
float standard_deviation;
// Avoid div0 errors
if (npings_with_real_returns > 0){
mean_range = sum_of_good_ranges / npings_with_real_returns;
standard_deviation = standard_deviation_from_array(good_ranges, npings_with_real_returns, mean_range);
}
else {
mean_range = -9999;
standard_deviation = -9999;
}
// Write all values if so desired
if (writeAll){