/
OpenLog.ino
2273 lines (1906 loc) · 72.1 KB
/
OpenLog.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
/*
12-3-09
Nathan Seidle
SparkFun Electronics
OpenLog hardware and firmware are released under the Creative Commons Share Alike v3.0 license.
http://creativecommons.org/licenses/by-sa/3.0/
Feel free to use, distribute, and sell varients of OpenLog. All we ask is that you include attribution of 'Based on OpenLog by SparkFun'.
OpenLog is based on the work of Bill Greiman and sdfatlib: https://github.com/greiman/SdFat-beta
OpenLog is a simple serial logger based on the ATmega328 running at 16MHz. The whole purpose of this
logger was to create a logger that just powered up and worked. OpenLog ships with an Arduino/Optiboot
115200bps serial bootloader running at 16MHz so you can load new firmware with a simple serial
connection.
OpenLog has progressed significantly over the past three years. Please see CHANGELOG.md or GitHub commits
for a full trip down memory lane.
OpenLog automatically works with 512MB to 64GB micro/SD cards.
OpenLog runs at 9600bps by default. This is configurable to 1200, 2400, 4800, 9600, 19200, 38400, 57600, and 115200bps. You can alter
all settings including baud rate and escape characters by editing config.txt found on OpenLog.
Type '?' to get a list of supported commands.
During power up, you will see '12<'. '1' indicates the serial connection is established. '2' indicates
the SD card has been successfully initialized. '<' indicates OpenLog is ready to receive serial characters.
Recording constant 115200bps datastreams are supported. Throw it everything you've got! To acheive this maximum record rate, please use the
SD card formatter from : http://www.sdcard.org/consumers/formatter/. The fewer files on the card, the faster OpenLog is able to begin logging.
200 files is ok. 2GB worth of music and pictures is not.
To a lower dir, use 'cd ..' instead of 'cd..'.
Only standard 8.3 file names are supported. "12345678.123" is acceptable. "123456789.123" is not.
All file names are pushed to upper case. "NewLog.txt" will become "NEWLOG.TXT".
Type 'set' to enter baud rate configuration menu. Select the baud rate and press enter. You will then
see a message 'Going to 9600bps...' or some such message. You will need to power down OpenLog, change
your system UART settings to match the new OpenLog baud rate and then power OpenLog back up.
If you get OpenLog stuck into an unknown baudrate, there is a safety mechanism built-in. Tie the RX pin
to ground and power up OpenLog. You should see the LEDs blink back and forth for 2 seconds, then blink
in unison. Now power down OpenLog and remove the RX/GND jumper. OpenLog is now reset to 9600bps.
Please note: The preloaded Optiboot serial bootloader is 0.5k, and begins at 0x7E00 (32,256). If the code is
larger than 32,256 bytes, you will get verification errors during serial bootloading.
OpenLog regularly shuts down to conserve power. If after 0.5 seconds no characters are received, OpenLog will record
any unsaved characters and go to sleep. OpenLog will automatically wake up and continue logging the instant a
new character is received.
1.55mA idle
15mA actively writing
Input voltage on VCC can be 3.3 to 12V. Input voltage on RX-I pin must not exceed 6V. Output voltage on
TX-O pin will not be greater than 3.3V. This may cause problems with some systems - for example if your
attached microcontroller requires 4V minimum for serial communication (this is rare).
*/
#define __PROG_TYPES_COMPAT__ //Needed to get SerialPort.h to work in Arduino 1.6.x
#include <SPI.h>
#include <SdFat.h> //We do not use the built-in SD.h file because it calls Serial.print
#include <SerialPort.h> //This is a new/beta library written by Bill Greiman. You rock Bill!
#include <EEPROM.h>
#include <FreeStack.h> //Allows us to print the available stack/RAM size
SerialPort<0, 512, 0> NewSerial;
//<port #, RX buffer size, TX buffer size>
//We set the TX buffer to zero because we will be spending most of our
//time needing to buffer the incoming (RX) characters.
//This is the array within the append file routine
//We have to keep SerialPort buffer sizes reasonable so that when we drop to
//the command shell we have RAM available for the various commands like append
#define LOCAL_BUFF_SIZE 255 //Must not be larger than charsToRecord variable size which is currently a byte
//512/256 shell works, 5/5 logs passed
//800/128 shell works, 3/5 logs passed
//672/256 shell works, 2/5 logs passed
//722/256 shell fails
//850/128 shell fails
#include <avr/sleep.h> //Needed for sleep_mode
#include <avr/power.h> //Needed for powering down perihperals such as the ADC/TWI and Timers
#define SD_CHIP_SELECT 10 //On OpenLog this is pin 10
//Debug turns on (1) or off (0) a bunch of verbose debug statements. Normally use (0)
//#define DEBUG 1
#define DEBUG 0
void(* Reset_AVR) (void) = 0; //Way of resetting the ATmega
#define CFG_FILENAME "config.txt" //This is the name of the file that contains the unit settings
#define MAX_CFG "115200,103,214,0,1,1,0\0" //= 115200 bps, escape char of ASCII(103), 214 times, new log mode, verbose on, echo on, ignore RX false.
#define CFG_LENGTH (strlen(MAX_CFG) + 1) //Length of text found in config file. strlen ignores \0 so we have to add it back
#define SEQ_FILENAME "SEQLOG00.TXT" //This is the name for the file when you're in sequential mode
//Internal EEPROM locations for the user settings
#define LOCATION_SYSTEM_SETTING 0x02
#define LOCATION_FILE_NUMBER_LSB 0x03
#define LOCATION_FILE_NUMBER_MSB 0x04
#define LOCATION_ESCAPE_CHAR 0x05
#define LOCATION_MAX_ESCAPE_CHAR 0x06
#define LOCATION_VERBOSE 0x07
#define LOCATION_ECHO 0x08
#define LOCATION_BAUD_SETTING_HIGH 0x09
#define LOCATION_BAUD_SETTING_MID 0x0A
#define LOCATION_BAUD_SETTING_LOW 0x0B
#define LOCATION_IGNORE_RX 0x0C
#define BAUD_MIN 300
#define BAUD_MAX 1000000
#define MODE_NEWLOG 0
#define MODE_SEQLOG 1
#define MODE_COMMAND 2
const byte stat1 = 5; //This is the normal status LED
const byte stat2 = 13; //This is the SPI LED, indicating SD traffic
//Blinking LED error codes
#define ERROR_SD_INIT 3
#define ERROR_NEW_BAUD 5
#define ERROR_CARD_INIT 6
#define ERROR_VOLUME_INIT 7
#define ERROR_ROOT_INIT 8
#define ERROR_FILE_OPEN 9
#if !DEBUG
// Using OpenLog in an embedded environment only if not in debug mode. The reason for this
// is that we are out of space if the simple embedded is included
#define INCLUDE_SIMPLE_EMBEDDED
#endif
#ifdef INCLUDE_SIMPLE_EMBEDDED
#define EMBEDDED_END_MARKER 0x08
#endif
//These are bit locations used when testing for simple embedded commands.
#define ECHO 0x01
#define EXTENDED_INFO 0x02
#define OFF 0x00
#define ON 0x01
SdFat sd;
long setting_uart_speed; //This is the baud rate that the system runs at, default is 9600. Can be 1,200 to 1,000,000
byte setting_systemMode; //This is the mode the system runs in, default is MODE_NEWLOG
byte setting_escape_character; //This is the ASCII character we look for to break logging, default is ctrl+z
byte setting_max_escape_character; //Number of escape chars before break logging, default is 3
byte setting_verbose; //This controls the whether we get extended or simple responses.
byte setting_echo; //This turns on/off echoing at the command prompt
byte setting_ignore_RX; //This flag, when set to 1 will make OpenLog ignore the state of the RX pin when powering up
//The number of command line arguments
//Increase to support more arguments but be aware of the memory restrictions
//command <arg1> <arg2> <arg3> <arg4> <arg5>
#define MAX_COUNT_COMMAND_LINE_ARGS 5
//Used for wild card delete and search
struct commandArg
{
char* arg; //Points to first character in command line argument
byte arg_length; //Length of command line argument
};
static struct commandArg cmd_arg[MAX_COUNT_COMMAND_LINE_ARGS];
byte feedbackMode = (ECHO | EXTENDED_INFO);
//Handle errors by printing the error type and blinking LEDs in certain way
//The function will never exit - it loops forever inside blinkError
void systemError(byte errorType)
{
NewSerial.print(F("Error "));
switch (errorType)
{
case ERROR_CARD_INIT:
NewSerial.print(F("card.init"));
blinkError(ERROR_SD_INIT);
break;
case ERROR_VOLUME_INIT:
NewSerial.print(F("volume.init"));
blinkError(ERROR_SD_INIT);
break;
case ERROR_ROOT_INIT:
NewSerial.print(F("root.init"));
blinkError(ERROR_SD_INIT);
break;
case ERROR_FILE_OPEN:
NewSerial.print(F("file.open"));
blinkError(ERROR_SD_INIT);
break;
}
}
void setup(void)
{
pinMode(stat1, OUTPUT);
//Power down various bits of hardware to lower power usage
set_sleep_mode(SLEEP_MODE_IDLE);
sleep_enable();
//Shut off TWI, Timer2, Timer1, ADC
ADCSRA &= ~(1 << ADEN); //Disable ADC
ACSR = (1 << ACD); //Disable the analog comparator
DIDR0 = 0x3F; //Disable digital input buffers on all ADC0-ADC5 pins
DIDR1 = (1 << AIN1D) | (1 << AIN0D); //Disable digital input buffer on AIN1/0
power_twi_disable();
power_timer1_disable();
power_timer2_disable();
power_adc_disable();
readSystemSettings(); //Load all system settings from EEPROM
//Setup UART
NewSerial.begin(setting_uart_speed);
if (setting_uart_speed < 500) // check for slow baud rates
{
//There is an error in the Serial library for lower than 500bps.
//This fixes it. See issue 163: https://github.com/sparkfun/OpenLog/issues/163
// redo USART baud rate configuration
UBRR0 = (F_CPU / (16UL * setting_uart_speed)) - 1;
UCSR0A &= ~_BV(U2X0);
}
NewSerial.print(F("1"));
//Setup SD & FAT
if (!sd.begin(SD_CHIP_SELECT, SPI_FULL_SPEED)) systemError(ERROR_CARD_INIT);
if (!sd.chdir()) systemError(ERROR_ROOT_INIT); //Change to root directory. All new file creation will be in root.
NewSerial.print(F("2"));
//Search for a config file and load any settings found. This will over-ride previous EEPROM settings if found.
readConfigFile();
if (setting_ignore_RX == OFF) //If we are NOT ignoring RX, then
checkEmergencyReset(); //Look to see if the RX pin is being pulled low
#if DEBUG
NewSerial.print(F("FreeStack: "));
NewSerial.println(FreeStack());
#endif
}
void loop(void)
{
//If we are in new log mode, find a new file name to write to
if (setting_systemMode == MODE_NEWLOG)
appendFile(newLog()); //Append the file name that newLog() returns
//If we are in sequential log mode, determine if seqLog.txt has been created or not, and then open it for logging
if (setting_systemMode == MODE_SEQLOG)
seqLog();
//Once either one of these modes exits, go to normal command mode, which is called by returning to main()
commandShell();
}
//Log to a new file everytime the system boots
//Checks the spots in EEPROM for the next available LOG# file name
//Updates EEPROM and then appends to the new log file.
//Limited to 65535 files but this should not always be the case.
char* newLog(void)
{
byte msb, lsb;
unsigned int newFileNumber;
SdFile newFile; //This will contain the file for SD writing
//Combine two 8-bit EEPROM spots into one 16-bit number
lsb = EEPROM.read(LOCATION_FILE_NUMBER_LSB);
msb = EEPROM.read(LOCATION_FILE_NUMBER_MSB);
newFileNumber = msb;
newFileNumber = newFileNumber << 8;
newFileNumber |= lsb;
//If both EEPROM spots are 255 (0xFF), that means they are un-initialized (first time OpenLog has been turned on)
//Let's init them both to 0
if ((lsb == 255) && (msb == 255))
{
newFileNumber = 0; //By default, unit will start at file number zero
EEPROM.write(LOCATION_FILE_NUMBER_LSB, 0x00);
EEPROM.write(LOCATION_FILE_NUMBER_MSB, 0x00);
}
//The above code looks like it will forever loop if we ever create 65535 logs
//Let's quit if we ever get to 65534
//65534 logs is quite possible if you have a system with lots of power on/off cycles
if (newFileNumber == 65534)
{
//Gracefully drop out to command prompt with some error
NewSerial.print(F("!Too many logs:1!"));
return (0); //Bail!
}
//If we made it this far, everything looks good - let's start testing to see if our file number is the next available
#if DEBUG
NewSerial.print(F("Found file number: "));
NewSerial.println(newFileNumber);
#endif
//There is a weird EEPROM power-up glitch that causes the newFileNumber to advance
//arbitrarily. This fixes that problem.
if(newFileNumber > 0) newFileNumber--;
//Search for next available log spot
static char newFileName[13]; //Bug fix from ystark's pull request: https://github.com/sparkfun/OpenLog/pull/189
while (1)
{
sprintf_P(newFileName, PSTR("LOG%05u.TXT"), newFileNumber); //Splice the new file number into this file name
// O_CREAT - create the file if it does not exist
// O_APPEND - seek to the end of the file prior to each write
// O_WRITE - open for write
// O_EXCL - if O_CREAT and O_EXCEL are set, open() shall fail if file exists
//Try to open file, if it opens (file doesn't exist), then break
if (newFile.open(newFileName, O_CREAT | O_EXCL | O_WRITE)) break;
//Try to open file and see if it is empty. If so, use it.
if (newFile.open(newFileName, O_READ))
{
if (newFile.fileSize() == 0)
{
newFile.close(); // Close this existing file we just opened.
return (newFileName); // Use existing empty file.
}
newFile.close(); // Close this existing file we just opened.
}
//Try the next number
newFileNumber++;
if (newFileNumber > 65533) //There is a max of 65534 logs
{
NewSerial.print(F("!Too many logs:2!"));
return (0); //Bail!
}
}
newFile.close(); //Close this new file we just opened
newFileNumber++; //Increment so the next power up uses the next file #
//Record new_file number to EEPROM
lsb = (byte)(newFileNumber & 0x00FF);
msb = (byte)((newFileNumber & 0xFF00) >> 8);
EEPROM.write(LOCATION_FILE_NUMBER_LSB, lsb); // LSB
if (EEPROM.read(LOCATION_FILE_NUMBER_MSB) != msb)
EEPROM.write(LOCATION_FILE_NUMBER_MSB, msb); // MSB
#if DEBUG
NewSerial.print(F("Stored file number: "));
NewSerial.println(newFileNumber);
NewSerial.print(F("Created new file: "));
NewSerial.println(newFileName);
#endif
return (newFileName);
}
//Log to the same file every time the system boots, sequentially
//Checks to see if the file SEQLOG.txt is available
//If not, create it
//If yes, append to it
//Return 0 on error
//Return anything else on success
void seqLog(void)
{
SdFile seqFile;
char sequentialFileName[strlen(SEQ_FILENAME)]; //Limited to 8.3
strcpy_P(sequentialFileName, PSTR(SEQ_FILENAME)); //This is the name of the config file. 'config.sys' is probably a bad idea.
//Try to create sequential file
if (!seqFile.open(sequentialFileName, O_CREAT | O_WRITE))
{
NewSerial.println(F("Error creating SEQLOG"));
return;
}
seqFile.close(); //Close this new file we just opened
appendFile(sequentialFileName);
}
//This is the most important function of the device. These loops have been tweaked as much as possible.
//Modifying this loop may negatively affect how well the device can record at high baud rates.
//Appends a stream of serial data to a given file
//Does not exit until escape character is received the correct number of times
//Returns 0 on error
//Returns 1 on success
byte appendFile(char* fileName)
{
SdFile workingFile;
// O_CREAT - create the file if it does not exist
// O_APPEND - seek to the end of the file prior to each write
// O_WRITE - open for write
if (!workingFile.open(fileName, O_CREAT | O_APPEND | O_WRITE)) systemError(ERROR_FILE_OPEN);
if (workingFile.fileSize() == 0) {
//This is a trick to make sure first cluster is allocated - found in Bill's example/beta code
workingFile.rewind();
workingFile.sync();
}
//This is the 2nd buffer. It pulls from the larger Serial buffer as quickly as possible.
//The built-in Arduino serial buffer is 64 bytes: https://www.arduino.cc/en/Serial/Available
byte localBuffer[LOCAL_BUFF_SIZE];
byte checkedSpot;
byte escapeCharsReceived = 0;
const unsigned int MAX_IDLE_TIME_MSEC = 500; //The number of milliseconds before unit goes to sleep
unsigned long lastSyncTime = millis(); //Keeps track of the last time the file was synced
#if DEBUG
NewSerial.print(F("FreeStack: "));
NewSerial.println(FreeStack());
#endif
NewSerial.print(F("<")); //give a different prompt to indicate no echoing
digitalWrite(stat1, HIGH); //Turn on indicator LED
//Check if we should ignore escape characters
//If we are ignoring escape characters the recording loop is infinite and can be made shorter (less checking)
//This should allow for recording at higher incoming rates
if (setting_max_escape_character == 0)
{
//Start recording incoming characters
//With no escape characters, do this infinitely
while (1)
{
byte charsToRecord = NewSerial.read(localBuffer, sizeof(localBuffer)); //Read characters from global buffer into the local buffer
if (charsToRecord > 0) //If we have characters, check for escape characters
{
workingFile.write(localBuffer, charsToRecord); //Record the buffer to the card
toggleLED(stat1); //Toggle the STAT1 LED each time we record the buffer
}
//No characters received?
else if ( (millis() - lastSyncTime) > MAX_IDLE_TIME_MSEC) //If we haven't received any characters in 2s, goto sleep
{
workingFile.sync(); //Sync the card before we go to sleep
digitalWrite(stat1, LOW); //Turn off stat LED to save power
power_timer0_disable(); //Shut down peripherals we don't need
power_spi_disable();
sleep_mode(); //Stop everything and go to sleep. Wake up if serial character received
power_spi_enable(); //After wake up, power up peripherals
power_timer0_enable();
escapeCharsReceived = 0; // Clear the esc flag as it has timed out
lastSyncTime = millis(); //Reset the last sync time to now
}
}
}
//We only get this far if escape characters are more than zero
//Start recording incoming characters
while (escapeCharsReceived < setting_max_escape_character)
{
byte charsToRecord = NewSerial.read(localBuffer, sizeof(localBuffer)); //Read characters from global buffer into the local buffer
if (charsToRecord > 0) //If we have characters, check for escape characters
{
if (localBuffer[0] == setting_escape_character)
{
escapeCharsReceived++;
//Scan the local buffer for esacape characters
for (checkedSpot = 1 ; checkedSpot < charsToRecord ; checkedSpot++)
{
if (localBuffer[checkedSpot] == setting_escape_character) {
escapeCharsReceived++;
//If charsToRecord is greater than 3 there's a chance here where we receive three esc chars
// and then reset the variable: 26 26 26 A T + would not escape correctly
if (escapeCharsReceived == setting_max_escape_character) break;
}
else
escapeCharsReceived = 0;
}
}
else
escapeCharsReceived = 0;
workingFile.write(localBuffer, charsToRecord); //Record the buffer to the card
toggleLED(stat1); //Toggle the STAT1 LED each time we record the buffer
}
//No characters recevied?
else if ( (millis() - lastSyncTime) > MAX_IDLE_TIME_MSEC) //If we haven't received any characters in 2s, goto sleep
{
workingFile.sync(); //Sync the card before we go to sleep
digitalWrite(stat1, LOW); //Turn off stat LED to save power
power_timer0_disable(); //Shut down peripherals we don't need
power_spi_disable();
sleep_mode(); //Stop everything and go to sleep. Wake up if serial character received
power_spi_enable(); //After wake up, power up peripherals
power_timer0_enable();
escapeCharsReceived = 0; // Clear the esc flag as it has timed out
lastSyncTime = millis(); //Reset the last sync time to now
}
}
workingFile.sync();
//Remove the escape characters from the end of the file
if(setting_max_escape_character > 0)
workingFile.truncate(workingFile.fileSize() - setting_max_escape_character);
workingFile.close(); // Done recording, close out the file
digitalWrite(stat1, LOW); // Turn off indicator LED
NewSerial.print(F("~")); // Indicate a successful record
return (1); //Success!
}
//The following are system functions needed for basic operation
//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//Blinks the status LEDs to indicate a type of error
void blinkError(byte ERROR_TYPE) {
while (1) {
for (int x = 0 ; x < ERROR_TYPE ; x++) {
digitalWrite(stat1, HIGH);
delay(200);
digitalWrite(stat1, LOW);
delay(200);
}
delay(2000);
}
}
//Check to see if we need an emergency UART reset
//Scan the RX pin for 2 seconds
//If it's low the entire time, then return 1
void checkEmergencyReset(void)
{
pinMode(0, INPUT); //Turn the RX pin into an input
digitalWrite(0, HIGH); //Push a 1 onto RX pin to enable internal pull-up
//Quick pin check
if (digitalRead(0) == HIGH) return;
//Disable SPI so that we control the LEDs
SPI.end();
//Wait 2 seconds, blinking LEDs while we wait
pinMode(stat2, OUTPUT);
digitalWrite(stat2, HIGH); //Set the STAT2 LED
for (byte i = 0 ; i < 40 ; i++)
{
delay(25);
toggleLED(stat1); //Blink the stat LEDs
if (digitalRead(0) == HIGH) return; //Check to see if RX is not low anymore
delay(25);
toggleLED(stat2); //Blink the stat LEDs
if (digitalRead(0) == HIGH) return; //Check to see if RX is not low anymore
}
//If we make it here, then RX pin stayed low the whole time
setDefaultSettings(); //Reset baud, escape characters, escape number, system mode
//Try to setup the SD card so we can record these new settings
if (!sd.begin(SD_CHIP_SELECT, SPI_HALF_SPEED)) systemError(ERROR_CARD_INIT);
if (!sd.chdir()) systemError(ERROR_ROOT_INIT); //Change to root directory
recordConfigFile(); //Record new config settings
//Disable SPI so that we control the LEDs
SPI.end();
pinMode(stat1, OUTPUT);
pinMode(stat2, OUTPUT);
digitalWrite(stat1, HIGH);
digitalWrite(stat2, HIGH);
//Now sit in forever loop indicating system is now at 9600bps
while (1)
{
delay(500);
toggleLED(stat1); //Blink the stat LEDs
toggleLED(stat2); //Blink the stat LEDs
}
}
//Resets all the system settings to safe values
void setDefaultSettings(void)
{
//Reset UART to 9600bps
writeBaud(9600);
//Reset system to new log mode
EEPROM.write(LOCATION_SYSTEM_SETTING, MODE_NEWLOG);
//Reset escape character to ctrl+z
EEPROM.write(LOCATION_ESCAPE_CHAR, 26);
//Reset number of escape characters to 3
EEPROM.write(LOCATION_MAX_ESCAPE_CHAR, 3);
//Reset verbose responses to on
EEPROM.write(LOCATION_VERBOSE, ON);
//Reset echo to on
EEPROM.write(LOCATION_ECHO, ON);
//Reset the ignore RX to 'Pay attention to RX!'
EEPROM.write(LOCATION_IGNORE_RX, OFF);
//These settings are not recorded to the config file
//We can't do it here because we are not sure the FAT system is init'd
}
//Reads the current system settings from EEPROM
//If anything looks weird, reset setting to default value
void readSystemSettings(void)
{
//Read what the current UART speed is from EEPROM memory
//Default is 9600
setting_uart_speed = readBaud(); //Combine the three bytes
if (setting_uart_speed < BAUD_MIN || setting_uart_speed > BAUD_MAX)
{
setting_uart_speed = 9600; //Reset UART to 9600 if there is no speed stored
writeBaud(setting_uart_speed); //Record to EEPROM
}
//Determine the system mode we should be in
//Default is NEWLOG mode
setting_systemMode = EEPROM.read(LOCATION_SYSTEM_SETTING);
if (setting_systemMode > 5)
{
setting_systemMode = MODE_NEWLOG; //By default, unit will turn on and go to new file logging
EEPROM.write(LOCATION_SYSTEM_SETTING, setting_systemMode);
}
//Read the escape_character
//ASCII(26) is ctrl+z
setting_escape_character = EEPROM.read(LOCATION_ESCAPE_CHAR);
if (setting_escape_character == 0 || setting_escape_character == 255)
{
setting_escape_character = 26; //Reset escape character to ctrl+z
EEPROM.write(LOCATION_ESCAPE_CHAR, setting_escape_character);
}
//Read the number of escape_characters to look for
//Default is 3
setting_max_escape_character = EEPROM.read(LOCATION_MAX_ESCAPE_CHAR);
if (setting_max_escape_character == 255)
{
setting_max_escape_character = 3; //Reset number of escape characters to 3
EEPROM.write(LOCATION_MAX_ESCAPE_CHAR, setting_max_escape_character);
}
//Read whether we should use verbose responses or not
//Default is true
setting_verbose = EEPROM.read(LOCATION_VERBOSE);
if (setting_verbose != ON || setting_verbose != OFF)
{
setting_verbose = ON; //Reset verbose to true
EEPROM.write(LOCATION_VERBOSE, setting_verbose);
}
//Read whether we should echo characters or not
//Default is true
setting_echo = EEPROM.read(LOCATION_ECHO);
if (setting_echo != ON || setting_echo != OFF)
{
setting_echo = ON; //Reset to echo on
EEPROM.write(LOCATION_ECHO, setting_echo);
}
//Set flags for extended mode options
if (setting_verbose == ON)
feedbackMode |= EXTENDED_INFO;
else
feedbackMode &= ((byte)~EXTENDED_INFO);
if (setting_echo == ON)
feedbackMode |= ECHO;
else
feedbackMode &= ((byte)~ECHO);
//Read whether we should ignore RX at power up
//Some users need OpenLog to ignore the RX pin during power up
//Default is false or ignore
setting_ignore_RX = EEPROM.read(LOCATION_IGNORE_RX);
if (setting_ignore_RX > 1)
{
setting_ignore_RX = OFF; //By default we DO NOT ignore RX
EEPROM.write(LOCATION_IGNORE_RX, setting_ignore_RX);
}
}
void readConfigFile(void)
{
SdFile configFile;
if (!sd.chdir()) systemError(ERROR_ROOT_INIT); // open the root directory
char configFileName[strlen(CFG_FILENAME)]; //Limited to 8.3
strcpy_P(configFileName, PSTR(CFG_FILENAME)); //This is the name of the config file. 'config.sys' is probably a bad idea.
//Check to see if we have a config file
if (!configFile.open(configFileName, O_READ))
{
//If we don't have a config file already, then create config file and record the current system settings to the file
#if DEBUG
NewSerial.println(F("No config found - creating default:"));
#endif
configFile.close();
//Record the current eeprom settings to the config file
recordConfigFile();
return;
}
//If we found the config file then load settings from file and push them into EEPROM
#if DEBUG
NewSerial.println(F("Found config file!"));
#endif
//Read up to 22 characters from the file. There may be a better way of doing this...
char c;
int len;
byte settingsString[CFG_LENGTH]; //"115200,103,14,0,1,1,0\r" = 115200 bps, escape char of ASCII(103), 14 times, new log mode, verbose on, echo on, ignore RX false.
for (len = 0 ; len < CFG_LENGTH ; len++) {
c = configFile.read();
if (!(c == ',' || (c >= '0' && c <= '9'))) break; //Bail if we hit a non-numeric or non-comma character
settingsString[len] = c;
}
configFile.close();
#if DEBUG
//Print line for debugging
NewSerial.print(F("Text Settings: "));
for (int i = 0; i < len; i++)
NewSerial.write(settingsString[i]);
NewSerial.println();
NewSerial.print(F("Len: "));
NewSerial.println(len);
#endif
//Default the system settings in case things go horribly wrong
long new_system_baud = 9600;
byte new_systemMode = MODE_NEWLOG;
byte new_system_escape = 26;
byte new_system_max_escape = 3;
byte new_system_verbose = ON;
byte new_system_echo = ON;
byte new_system_ignore_RX = OFF;
//Parse the settings out
byte i = 0, j = 0, settingNumber = 0;
char newSettingString[8]; //Max length of a setting is 7, the bps setting = '1000000' plus '\0'
byte newSettingInt = 0;
for (i = 0 ; i < len; i++)
{
//Pick out one setting from the line of text
for (j = 0 ; settingsString[i] != ',' && i < len && j < 7 ; )
{
newSettingString[j] = settingsString[i];
i++;
j++;
}
newSettingString[j] = '\0'; //Terminate the string for array compare
newSettingInt = atoi(newSettingString); //Convert string to int
if (settingNumber == 0) //Baud rate
{
new_system_baud = strToLong(newSettingString);
//Basic error checking
if (new_system_baud < BAUD_MIN || new_system_baud > BAUD_MAX) new_system_baud = 9600; //Default to 9600
}
else if (settingNumber == 1) //Escape character
{
new_system_escape = newSettingInt;
if (new_system_escape == 0 || new_system_escape > 127) new_system_escape = 26; //Default is ctrl+z
}
else if (settingNumber == 2) //Max amount escape character
{
new_system_max_escape = newSettingInt;
if (new_system_max_escape > 254) new_system_max_escape = 3; //Default is 3
}
else if (settingNumber == 3) //System mode
{
new_systemMode = newSettingInt;
if (new_systemMode == 0 || new_systemMode > MODE_COMMAND) new_systemMode = MODE_NEWLOG; //Default is NEWLOG
}
else if (settingNumber == 4) //Verbose setting
{
new_system_verbose = newSettingInt;
if (new_system_verbose != ON && new_system_verbose != OFF) new_system_verbose = ON; //Default is on
}
else if (settingNumber == 5) //Echo setting
{
new_system_echo = newSettingInt;
if (new_system_echo != ON && new_system_echo != OFF) new_system_echo = ON; //Default is on
}
else if (settingNumber == 6) //Ignore RX setting
{
new_system_ignore_RX = newSettingInt;
if (new_system_ignore_RX != ON && new_system_ignore_RX != OFF) new_system_ignore_RX = OFF; //Default is to listen to RX
}
else
//We're done! Stop looking for settings
break;
settingNumber++;
}
//We now have the settings loaded into the global variables. Now check if they're different from EEPROM settings
boolean recordNewSettings = false;
if (new_system_baud != setting_uart_speed) {
//If the baud rate from the file is different from the current setting,
//Then update the setting to the file setting
//And re-init the UART
writeBaud(new_system_baud); //Write this baudrate to EEPROM
setting_uart_speed = new_system_baud;
NewSerial.begin(setting_uart_speed); //Move system to new uart speed
recordNewSettings = true;
}
if (new_systemMode != setting_systemMode) {
//Goto new system mode
setting_systemMode = new_systemMode;
EEPROM.write(LOCATION_SYSTEM_SETTING, setting_systemMode);
recordNewSettings = true;
}
if (new_system_escape != setting_escape_character) {
//Goto new system escape char
setting_escape_character = new_system_escape;
EEPROM.write(LOCATION_ESCAPE_CHAR, setting_escape_character);
recordNewSettings = true;
}
if (new_system_max_escape != setting_max_escape_character) {
//Goto new max escape
setting_max_escape_character = new_system_max_escape;
EEPROM.write(LOCATION_MAX_ESCAPE_CHAR, setting_max_escape_character);
recordNewSettings = true;
}
if (new_system_verbose != setting_verbose) {
//Goto new verbose setting
setting_verbose = new_system_verbose;
EEPROM.write(LOCATION_VERBOSE, setting_verbose);
recordNewSettings = true;
}
if (new_system_echo != setting_echo) {
//Goto new echo setting
setting_echo = new_system_echo;
EEPROM.write(LOCATION_ECHO, setting_echo);
recordNewSettings = true;
}
if (new_system_ignore_RX != setting_ignore_RX) {
//Goto new ignore setting
setting_ignore_RX = new_system_ignore_RX;
EEPROM.write(LOCATION_IGNORE_RX, setting_ignore_RX);
recordNewSettings = true;
}
//We don't want to constantly record a new config file on each power on. Only record when there is a change.
if (recordNewSettings == true)
recordConfigFile(); //If we corrected some values because the config file was corrupt, then overwrite any corruption
#if DEBUG
else
NewSerial.println(F("Config file matches system settings"));
#endif
//All done! New settings are loaded. System will now operate off new config settings found in file.
//Set flags for extended mode options
if (setting_verbose == ON)
feedbackMode |= EXTENDED_INFO;
else
feedbackMode &= ((byte)~EXTENDED_INFO);
if (setting_echo == ON)
feedbackMode |= ECHO;
else
feedbackMode &= ((byte)~ECHO);
}
//Records the current EEPROM settings to the config file
//If a config file exists, it is trashed and a new one is created
void recordConfigFile(void)
{
SdFile myFile;
if (!sd.chdir()) systemError(ERROR_ROOT_INIT); // open the root directory
char configFileName[strlen(CFG_FILENAME)];
strcpy_P(configFileName, PSTR(CFG_FILENAME)); //This is the name of the config file. 'config.sys' is probably a bad idea.
//If there is currently a config file, trash it
if (myFile.open(configFileName, O_WRITE)) {
if (!myFile.remove()) {
NewSerial.println(F("Remove config failed"));
myFile.close(); //Close this file
return;
}
}
//myFile.close(); //Not sure if we need to close the file before we try to reopen it
//Create config file
myFile.open(configFileName, O_CREAT | O_APPEND | O_WRITE);
//Config was successfully created, now record current system settings to the config file
char settingsString[CFG_LENGTH]; //"115200,103,14,0,1,1,0\0" = 115200 bps, escape char of ASCII(103), 14 times, new log mode, verbose on, echo on, ignore RX false.
//Before we read the EEPROM values, they've already been tested and defaulted in the readSystemSettings function
long current_system_baud = readBaud();
byte current_system_escape = EEPROM.read(LOCATION_ESCAPE_CHAR);
byte current_system_max_escape = EEPROM.read(LOCATION_MAX_ESCAPE_CHAR);
byte current_systemMode = EEPROM.read(LOCATION_SYSTEM_SETTING);
byte current_system_verbose = EEPROM.read(LOCATION_VERBOSE);
byte current_system_echo = EEPROM.read(LOCATION_ECHO);
byte current_system_ignore_RX = EEPROM.read(LOCATION_IGNORE_RX);
//Convert system settings to visible ASCII characters
sprintf_P(settingsString, PSTR("%ld,%d,%d,%d,%d,%d,%d\0"), current_system_baud, current_system_escape, current_system_max_escape, current_systemMode, current_system_verbose, current_system_echo, current_system_ignore_RX);
//Record current system settings to the config file
myFile.write(settingsString, strlen(settingsString));
myFile.println(); //Add a break between lines
//Add a decoder line to the file
#define HELP_STR "baud,escape,esc#,mode,verb,echo,ignoreRX\0"
char helperString[strlen(HELP_STR) + 1]; //strlen is preprocessed but returns one less because it ignores the \0
strcpy_P(helperString, PSTR(HELP_STR));
myFile.write(helperString); //Add this string to the file
myFile.sync(); //Sync all newly written data to card
myFile.close(); //Close this file
//Now that the new config file has the current system settings, nothing else to do!
}
//Given a baud rate (long number = four bytes but we only use three), record to EEPROM
void writeBaud(long uartRate)
{
EEPROM.write(LOCATION_BAUD_SETTING_HIGH, (byte)((uartRate & 0x00FF0000) >> 16));
EEPROM.write(LOCATION_BAUD_SETTING_MID, (byte)(uartRate >> 8));
EEPROM.write(LOCATION_BAUD_SETTING_LOW, (byte)uartRate);
}
//Look up the baud rate. This requires three bytes be combined into one long
long readBaud(void)
{
byte uartSpeedHigh = EEPROM.read(LOCATION_BAUD_SETTING_HIGH);
byte uartSpeedMid = EEPROM.read(LOCATION_BAUD_SETTING_MID);
byte uartSpeedLow = EEPROM.read(LOCATION_BAUD_SETTING_LOW);
long uartSpeed = 0x00FF0000 & ((long)uartSpeedHigh << 16) | ((long)uartSpeedMid << 8) | uartSpeedLow; //Combine the three bytes