|
| 1 | +/* |
| 2 | + 12-3-09 |
| 3 | + Nathan Seidle |
| 4 | + SparkFun Electronics 2012 |
| 5 | +
|
| 6 | + OpenLog hardware and firmware are released under the Creative Commons Share Alike v3.0 license. |
| 7 | + http://creativecommons.org/licenses/by-sa/3.0/ |
| 8 | + Feel free to use, distribute, and sell varients of OpenLog. All we ask is that you include attribution of 'Based on OpenLog by SparkFun'. |
| 9 | +
|
| 10 | + OpenLog is based on the work of Bill Greiman and sdfatlib: https://github.com/greiman/SdFat-beta |
| 11 | +
|
| 12 | + This version has the command line interface and config file stripped out in order to simplify the overall |
| 13 | + program and increase the receive buffer (RAM). |
| 14 | +
|
| 15 | + The only option is the interface baud rate and it has to be set in code, compiled, and loaded onto OpenLog. To |
| 16 | + see how to do this please refer to https://github.com/sparkfun/OpenLog/wiki/Flashing-Firmware |
| 17 | +
|
| 18 | + This version |
| 19 | +
|
| 20 | + */ |
| 21 | + |
| 22 | +#define __PROG_TYPES_COMPAT__ //Needed to get SerialPort.h to work in 1.6x |
| 23 | + |
| 24 | +#include <SPI.h> |
| 25 | +#include <SdFat.h> //We do not use the built-in SD.h file because it calls Serial.print |
| 26 | +#include <EEPROM.h> |
| 27 | +#include <FreeStack.h> //Allows us to print the available stack/RAM size |
| 28 | + |
| 29 | +#include <avr/sleep.h> //Needed for sleep_mode |
| 30 | +#include <avr/power.h> //Needed for powering down perihperals such as the ADC/TWI and Timers |
| 31 | + |
| 32 | +#include <SerialPort.h> |
| 33 | +// port 0, 256 byte RX buffer, 0 byte TX buffer |
| 34 | +SerialPort<0, 1024, 0> NewSerial; |
| 35 | + |
| 36 | + |
| 37 | +//Debug turns on (1) or off (0) a bunch of verbose debug statements. Normally use (0) |
| 38 | +//#define DEBUG 1 |
| 39 | +#define DEBUG 0 |
| 40 | + |
| 41 | +#define SD_CHIP_SELECT 10 //On OpenLog this is pin 10 |
| 42 | + |
| 43 | +//Internal EEPROM locations for the user settings |
| 44 | +#define LOCATION_FILE_NUMBER_LSB 0x03 |
| 45 | +#define LOCATION_FILE_NUMBER_MSB 0x04 |
| 46 | + |
| 47 | +//STAT1 is a general LED and indicates serial traffic |
| 48 | +const byte statled1 = 5; //This is the normal status LED |
| 49 | +const byte statled2 = 13; //This is the SPI LED, indicating SD traffic |
| 50 | + |
| 51 | +//Blinking LED error codes |
| 52 | +#define ERROR_SD_INIT 3 |
| 53 | +#define ERROR_NEW_BAUD 5 |
| 54 | +#define ERROR_CARD_INIT 6 |
| 55 | +#define ERROR_VOLUME_INIT 7 |
| 56 | +#define ERROR_ROOT_INIT 8 |
| 57 | +#define ERROR_FILE_OPEN 9 |
| 58 | + |
| 59 | +SdFat sd; |
| 60 | +Sd2Card card; |
| 61 | + |
| 62 | +long setting_uart_speed = 115200; |
| 63 | + |
| 64 | +//Handle errors by printing the error type and blinking LEDs in certain way |
| 65 | +//The function will never exit - it loops forever inside blink_error |
| 66 | +void systemError(byte error_type) |
| 67 | +{ |
| 68 | + NewSerial.print(F("Error ")); |
| 69 | + switch (error_type) |
| 70 | + { |
| 71 | + case ERROR_CARD_INIT: |
| 72 | + NewSerial.print(F("card.init")); |
| 73 | + blink_error(ERROR_SD_INIT); |
| 74 | + break; |
| 75 | + case ERROR_VOLUME_INIT: |
| 76 | + NewSerial.print(F("volume.init")); |
| 77 | + blink_error(ERROR_SD_INIT); |
| 78 | + break; |
| 79 | + case ERROR_ROOT_INIT: |
| 80 | + NewSerial.print(F("root.init")); |
| 81 | + blink_error(ERROR_SD_INIT); |
| 82 | + break; |
| 83 | + case ERROR_FILE_OPEN: |
| 84 | + NewSerial.print(F("file.open")); |
| 85 | + blink_error(ERROR_SD_INIT); |
| 86 | + break; |
| 87 | + } |
| 88 | +} |
| 89 | + |
| 90 | +void setup(void) |
| 91 | +{ |
| 92 | + pinMode(statled1, OUTPUT); |
| 93 | + |
| 94 | + //Power down various bits of hardware to lower power usage |
| 95 | + set_sleep_mode(SLEEP_MODE_IDLE); |
| 96 | + sleep_enable(); |
| 97 | + |
| 98 | + //Shut off TWI, Timer2, Timer1, ADC |
| 99 | + ADCSRA &= ~(1 << ADEN); //Disable ADC |
| 100 | + ACSR = (1 << ACD); //Disable the analog comparator |
| 101 | + DIDR0 = 0x3F; //Disable digital input buffers on all ADC0-ADC5 pins |
| 102 | + DIDR1 = (1 << AIN1D) | (1 << AIN0D); //Disable digital input buffer on AIN1/0 |
| 103 | + |
| 104 | + power_twi_disable(); |
| 105 | + power_timer1_disable(); |
| 106 | + power_timer2_disable(); |
| 107 | + power_adc_disable(); |
| 108 | + |
| 109 | + //Setup UART |
| 110 | + NewSerial.begin(setting_uart_speed); |
| 111 | + NewSerial.print(F("1")); |
| 112 | + |
| 113 | + //Setup SD & FAT |
| 114 | + if (!sd.begin(SD_CHIP_SELECT, SPI_FULL_SPEED)) systemError(ERROR_CARD_INIT); |
| 115 | + |
| 116 | + NewSerial.print(F("2")); |
| 117 | + |
| 118 | +#if DEBUG |
| 119 | + NewSerial.print(F("FreeStack: ")); |
| 120 | + NewSerial.println(FreeStack()); |
| 121 | +#endif |
| 122 | + |
| 123 | +} |
| 124 | + |
| 125 | +void loop(void) |
| 126 | +{ |
| 127 | + append_file(newlog()); //Append the file name that newlog() returns |
| 128 | +} |
| 129 | + |
| 130 | +//Log to a new file everytime the system boots |
| 131 | +//Checks the spots in EEPROM for the next available LOG# file name |
| 132 | +//Updates EEPROM and then appends to the new log file. |
| 133 | +//Limited to 65535 files but this should not always be the case. |
| 134 | +char* newlog(void) |
| 135 | +{ |
| 136 | + byte msb, lsb; |
| 137 | + uint16_t new_file_number; |
| 138 | + |
| 139 | + SdFile newFile; //This will contain the file for SD writing |
| 140 | + |
| 141 | + //Combine two 8-bit EEPROM spots into one 16-bit number |
| 142 | + lsb = EEPROM.read(LOCATION_FILE_NUMBER_LSB); |
| 143 | + msb = EEPROM.read(LOCATION_FILE_NUMBER_MSB); |
| 144 | + |
| 145 | + new_file_number = msb; |
| 146 | + new_file_number = new_file_number << 8; |
| 147 | + new_file_number |= lsb; |
| 148 | + |
| 149 | + //If both EEPROM spots are 255 (0xFF), that means they are un-initialized (first time OpenLog has been turned on) |
| 150 | + //Let's init them both to 0 |
| 151 | + if ((lsb == 255) && (msb == 255)) |
| 152 | + { |
| 153 | + new_file_number = 0; //By default, unit will start at file number zero |
| 154 | + EEPROM.write(LOCATION_FILE_NUMBER_LSB, 0x00); |
| 155 | + EEPROM.write(LOCATION_FILE_NUMBER_MSB, 0x00); |
| 156 | + } |
| 157 | + |
| 158 | + //The above code looks like it will forever loop if we ever create 65535 logs |
| 159 | + //Let's quit if we ever get to 65534 |
| 160 | + //65534 logs is quite possible if you have a system with lots of power on/off cycles |
| 161 | + if (new_file_number == 65534) |
| 162 | + { |
| 163 | + //Gracefully drop out to command prompt with some error |
| 164 | + NewSerial.print(F("!Too many logs:1!")); |
| 165 | + return (0); //Bail! |
| 166 | + } |
| 167 | + |
| 168 | + //If we made it this far, everything looks good - let's start testing to see if our file number is the next available |
| 169 | + |
| 170 | + //Search for next available log spot |
| 171 | + static char new_file_name[13]; |
| 172 | + while(1) |
| 173 | + { |
| 174 | + sprintf_P(new_file_name, PSTR("LOG%05d.TXT"), new_file_number); //Splice the new file number into this file name |
| 175 | + |
| 176 | + //If we are able to create this file, then it didn't exist, we're good, break |
| 177 | + if (newFile.open(new_file_name, O_CREAT | O_EXCL | O_WRITE) == true) break; |
| 178 | + |
| 179 | + //If file exists, see if empty. If so, use it. |
| 180 | + if (newFile.open(new_file_name, O_READ)) |
| 181 | + { |
| 182 | + if (newFile.fileSize() == 0) |
| 183 | + { |
| 184 | + newFile.close(); // Close this existing file we just opened. |
| 185 | + return (new_file_name); // Use existing empty file. |
| 186 | + } |
| 187 | + newFile.close(); // Close this existing file we just opened. |
| 188 | + } |
| 189 | + |
| 190 | + //Try the next number |
| 191 | + new_file_number++; |
| 192 | + if (new_file_number > 65533) //There is a max of 65534 logs |
| 193 | + { |
| 194 | + NewSerial.print(F("!Too many logs:2!")); |
| 195 | + return (0); //Bail! |
| 196 | + } |
| 197 | + } |
| 198 | + |
| 199 | + new_file_number++; //Increment so the next power up uses the next file # |
| 200 | + |
| 201 | + //Record new_file number to EEPROM |
| 202 | + lsb = (byte)(new_file_number & 0x00FF); |
| 203 | + msb = (byte)((new_file_number & 0xFF00) >> 8); |
| 204 | + |
| 205 | + EEPROM.write(LOCATION_FILE_NUMBER_LSB, lsb); // LSB |
| 206 | + |
| 207 | + if (EEPROM.read(LOCATION_FILE_NUMBER_MSB) != msb) |
| 208 | + EEPROM.write(LOCATION_FILE_NUMBER_MSB, msb); // MSB |
| 209 | + |
| 210 | +#if DEBUG |
| 211 | + NewSerial.print(F("\nCreated new file: ")); |
| 212 | + NewSerial.println(new_file_name); |
| 213 | +#endif |
| 214 | + |
| 215 | + return (new_file_name); |
| 216 | +} |
| 217 | + |
| 218 | +//This is the most important function of the device. These loops have been tweaked as much as possible. |
| 219 | +//Modifying this loop may negatively affect how well the device can record at high baud rates. |
| 220 | +//Appends a stream of serial data to a given file |
| 221 | +//Assumes the currentDirectory variable has been set before entering the routine |
| 222 | +//Does not exit until Ctrl+z (ASCII 26) is received |
| 223 | +//Returns 0 on error |
| 224 | +//Returns 1 on success |
| 225 | +byte append_file(char* file_name) |
| 226 | +{ |
| 227 | + SdFile workingFile; |
| 228 | + |
| 229 | + // O_CREAT - create the file if it does not exist |
| 230 | + // O_APPEND - seek to the end of the file prior to each write |
| 231 | + // O_WRITE - open for write |
| 232 | + if (!workingFile.open(file_name, O_CREAT | O_APPEND | O_WRITE)) systemError(ERROR_FILE_OPEN); |
| 233 | + |
| 234 | + if (workingFile.fileSize() == 0) { |
| 235 | + //This is a trick to make sure first cluster is allocated - found in Bill's example/beta code |
| 236 | + workingFile.rewind(); |
| 237 | + workingFile.sync(); |
| 238 | + } |
| 239 | + |
| 240 | + const int LOCAL_BUFF_SIZE = 128; //This is the 2nd buffer. It pulls from the larger Serial buffer as quickly as possible. |
| 241 | + |
| 242 | + byte buff[LOCAL_BUFF_SIZE]; |
| 243 | + |
| 244 | + const unsigned int MAX_IDLE_TIME_MSEC = 500; //The number of milliseconds of inactivity before unit goes to sleep |
| 245 | + |
| 246 | +#if DEBUG |
| 247 | + NewSerial.print(F("FreeStack: ")); |
| 248 | + NewSerial.println(FreeStack()); |
| 249 | +#endif |
| 250 | + |
| 251 | + NewSerial.print(F("<")); //give a different prompt to indicate no echoing |
| 252 | + digitalWrite(statled1, HIGH); //Turn on indicator LED |
| 253 | + |
| 254 | + unsigned long lastSyncTime = millis(); //Keeps track of the last time the file was synced |
| 255 | + |
| 256 | + //Start recording incoming characters |
| 257 | + while (1) |
| 258 | + { |
| 259 | + byte charsToRecord = NewSerial.read(buff, sizeof(buff)); |
| 260 | + if (charsToRecord > 0) |
| 261 | + { |
| 262 | + toggleLED(statled1); //Toggle the STAT1 LED each time we record the buffer |
| 263 | + |
| 264 | + workingFile.write(buff, charsToRecord); |
| 265 | + } |
| 266 | + |
| 267 | + //No characters recevied? |
| 268 | + else if ( (unsigned long)(millis() - lastSyncTime) > MAX_IDLE_TIME_MSEC) //If we haven't received any characters in 2s, goto sleep |
| 269 | + { |
| 270 | + workingFile.sync(); //Sync the card before we go to sleep |
| 271 | + |
| 272 | + digitalWrite(statled1, LOW); //Turn off stat LED to save power |
| 273 | + |
| 274 | + power_timer0_disable(); //Shut down peripherals we don't need |
| 275 | + power_spi_disable(); |
| 276 | + sleep_mode(); //Stop everything and go to sleep. Wake up if serial character received |
| 277 | + |
| 278 | + power_spi_enable(); //After wake up, power up peripherals |
| 279 | + power_timer0_enable(); |
| 280 | + |
| 281 | + lastSyncTime = millis(); //Reset the last sync time to now |
| 282 | + } |
| 283 | + } |
| 284 | + |
| 285 | + return (1); //We should never get here! |
| 286 | +} |
| 287 | + |
| 288 | +//The following are system functions needed for basic operation |
| 289 | +//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= |
| 290 | + |
| 291 | +//Blinks the status LEDs to indicate a type of error |
| 292 | +void blink_error(byte ERROR_TYPE) { |
| 293 | + while (1) { |
| 294 | + for (int x = 0 ; x < ERROR_TYPE ; x++) { |
| 295 | + digitalWrite(statled1, HIGH); |
| 296 | + delay(200); |
| 297 | + digitalWrite(statled1, LOW); |
| 298 | + delay(200); |
| 299 | + } |
| 300 | + |
| 301 | + delay(2000); |
| 302 | + } |
| 303 | +} |
| 304 | + |
| 305 | +//Given a pin, it will toggle it from high to low or vice versa |
| 306 | +void toggleLED(byte pinNumber) |
| 307 | +{ |
| 308 | + if (digitalRead(pinNumber)) digitalWrite(pinNumber, LOW); |
| 309 | + else digitalWrite(pinNumber, HIGH); |
| 310 | +} |
| 311 | + |
| 312 | +//End core system functions |
| 313 | +//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= |
| 314 | + |
| 315 | +//A rudimentary way to convert a string to a long 32 bit integer |
| 316 | +//Used by the read command, in command shell and baud from the system menu |
| 317 | +uint32_t strtolong(const char* str) |
| 318 | +{ |
| 319 | + uint32_t l = 0; |
| 320 | + while (*str >= '0' && *str <= '9') |
| 321 | + l = l * 10 + (*str++ - '0'); |
| 322 | + |
| 323 | + return l; |
| 324 | +} |
0 commit comments