#define d_VERSION_STRING "V,1.1.11" /* ****[Noble Logger]*** Data logging application * Version see d_VERSION_STRING * Written by: Stephan Barnard 12-Dec-2016, Modified by: Stephan Barnard 14 Feb 2017 * Modified by: David Hubbard 16 Feb 2017 * Copyright Noble Touch Limited 2016 - 2017 * More details can be found on: * www.noblemicro.com * This source code was written for the fourth kit build event in a series of talk and build events, organised by the BCS and held at the University of Hertfordshire. * This source code can be used as an inspiration for a new product but cannot be sold as is, or used as is, on a commercial basis. * This source code and the associated kit is designed to show micro-controller principles and act as an educational platform. * DISCLAIMER: * No responsibility is assumed or implied for anything that happens as a result of reading or using this source code. * PURPOSE: * Log readings from the analogue pin and store these in permanent (EEPROM) memory * Provide fast and slow record rates * Provide a play back ability * Provide a view on memory usage * The application has 4 modes of operation * MODE 1 = fast record of analogue data * MODE 2 = slow record of analogue data. (That is it has a longer interval between each data record) * MODE 3 = playback * MODE 4 = memory management * * The application has different states and runs a very basic state engine * STATE ACTIVE = Live interaction with user showing results to the 4 Bar LEDS. No sleep action no power saving action * STATE SLEEP PENDING = Live display to the user showing an LED pattern that indicates the unit is going to sleep soon * STATE SLEEP AND READ = A slow, power saving loop, where the unit is mostly asleep but takes a reading every so often * STATE SLEEP NO READ = The most power saving mode where it sleeps and only wakes up briefly to see if button A (Mode button) has been pressed. * * ATMEL ATTINY84A-PU * +-\/-+ * VCC 1| |14 GND * BUTTON GND - PIN10 2| |13 PIN0 - ACTIVITY INDICATOR LED * UART TX - PIN9 3| |12 PIN1 - BUTTON A MODE BUTTON * BUTTON B - RESET 4| |11 PIN2 - BAR LED1 * UART RX - PIN8 5| |10 PIN3 - BAR LED2 * AnalogIn - PIN7 6| |9 PIN4 - BAR LED3 * Sensor VCC - PIN6 7| |8 PIN5 - BAR LED4 * +----+ */ // EEPROM.h has functions that allow us to store data and keep this data even if the device is powered off #include // sleep.h has SLEEP instruction to allow this application to reduce its power consumption considerably. We draw .004 milli-amps when sleeping // AVR devices can be put into different sleep modes. Refer to the Atmel datasheet for the details. #include // ==SERIAL COMMS== // Universal asynchronous receiver/transmitter (UART) is used for serial communications with an attached computer. // This application uses software based serial rather than hardware based serial. // Hardware based UART provides better communication performance and buffering. Hardware serial is not an option on the ATTINY84A-PU. // To compensate for software serial it is recommended to keep the communication rate down. // 9600 baud worked well in our tests, which used both software serial and the internal clock. If you need higher performance communication using this chip it is recommended to use an external crystal oscillator rather than the internal clock. // UART is used here to send logged data and data captured in fast mode as well as mode changes and a status message. UART is also used to receive the keep awake command. #include #define d_UART_BAUD 9600 // UART transmission speed #define d_UART_RX_PIN 8 // Pin assigned as the UART receiver pin. Receives data from the connected computer. #define d_UART_TX_PIN 9 // Pin assigned as the UART transmitter pin. Transmits data to the connected computer. // SoftwareSerial is used for communication to an attached computer via UART // The line below creates an instance of a SoftwareSerial object and defines the Pin used for RX and Pin used for TX SoftwareSerial DataLogSerial(d_UART_RX_PIN, d_UART_TX_PIN); // Our instance of SoftwareSerial is called DataLogSerial and has module level scope. // To send a UART message we use the print member. e.g. DataLogSerial.print("Hello world\n"); //==LED Section== //There are 4 bar LEDs to show analogue read values and show we are about to enter sleep state //This is the user display #define d_LED_BAR1_PIN 2 #define d_LED_BAR2_PIN 3 #define d_LED_BAR3_PIN 4 #define d_LED_BAR4_PIN 5 #define d_BAR_LED_COUNT 4 #define d_VALUES_PER_SECTION 256 // Each led in the 4 bar display has 3 possible levels, 0 - LOW=off, LOW - HIGH=dim, HIGH - MAX=high #define d_LOW_LIGHT_LEVEL 85 // low level #define d_HIGH_LIGHT_LEVEL 170 // high level int m_LED_DisplayValue = 0; //Holds the value to display on the 4 LED bar. Values are shown as a bar view. //The read indicator pin is for diagnostics and is not a key part of the logger //#define d_LED_READ_INDICATOR_PIN 0 //Read indicator. Flashes each time an analogue read or a Playback read is done (not implemented in the basic kit) int m_LED_ActivityIndicator_State = LOW; #define d_ACTIVITY_INDICATOR_PIN 0 #define d_MODE_FLASH_COUNT 6 // the number of times the LEDs are flashed when displaying the current mode //The show patterns take the last 4 binary digits in SleepLEDShow and show this as Bar LEDs ons/offs //Here we defines an LED show that looks a bit like KITT from knight rider. We show this pattern to the user to show that the unit is about to go to sleep. #define d_MaxShowPatterns 10 #define d_PATTERN_HEADER_LEN 2 //The first two ints in the array are the header. Pos0=Pres frame Pos1=Number of frames in this pattern #define d_POS_PRES_FRAME_NUMBER 0 #define d_POS_NUMBER_OF_FRAMES 1 unsigned int SleepLEDShow[d_MaxShowPatterns] = {0, 8, 1, 3, 6, 12, 8, 12, 6, 3}; //LED pattern to show we are about to sleep. #define d_BIT_1 0x0001 #define d_BIT_2 0x0002 #define d_BIT_3 0x0004 #define d_BIT_4 0x0008 #define d_MODE_SHORT_DELAY 250 // used for a short wait #define d_MODE_LONG_DELAY 750 // used for a longer wait #define d_PENDSHOW_TOTAL_MILLIS 5000 // how long to wait before going to sleep #define d_PENDSHOW_CHANGE_MILLIS 150 // how long to delay between frames of the going to sleep display #define d_NO_FLASH 0 #define d_DIM_FLASH_RATE 3 int m_FlashPin = d_LED_BAR1_PIN;//Set as default but with Pin rate set to NO_FLASH this setting is ignored int m_FlashPinFlashRate = d_NO_FLASH; //No flash int m_LedFlashState = HIGH; // start with the flash state being on unsigned long m_MicrosLastPinFlash = 0; unsigned long m_StartPendShow = 0; // time of starting the sleep pending count unsigned long m_LastPendShowChange = 0;// monitors when we have got to the end of the sleep pending display //==Sensor Section== // This logger uses the chip's 10bit ADC to read analogue voltages on sensor pin7. This could be any sensor that provides a voltage between 0 and VCC. // For the BCS KIT4 an LED is used as a light detector. The LED is used in photoconductive mode. // In photoconductive mode, you place a positive voltage on the LED's cathode (i.e. the LED is reverse-biased, and will not emit light) but a very small current is allowed through. // The more incident light that falls on the LED, the more current flows into the cathode and out of the anode. // Since the current is very low we use a Darlington Pair to amplify it such that it can be read by the chip's ADC. // See http://www.noblemicro.com/Kit3_Build_Sheet.pdf // An LED's spectral sensitivity in this mode is related to its normal emitted wavelength. // Encapsulation material effects the sensitivity. Clear works well for general purpose as it does not not filter out colours. // Some LEDs contain a phosphor to enhance the light output of an LED. Such as the application of phosphor to make white LEDs. The phosphor prevents the light detection effect working. // So some LED will work and some will not. In the BCS KIT we have square blue LEDs which will not work as a detector but that is not to say that other blue LEDs will not work as a detector. // Tests show IR - UV LEDs work yet some in this range will not work. // Manufacturers do not characterise LEDs as photocells, so minor design changes that have minimal effect on their behaviour as an LED, can lead to major changes in their behaviour as a photocell. // This means you have to characterise an LED yourself if you plan to use it as a light detector. // To save power the external detector circuit is powered by the Sensor VCC pin. // The sensor VCC pin is only set HIGH to provide power to the sensor circuit when we want to take a reading and not when we are sleeping. #define d_SENSOR_VCC_PIN 6 //Power up external circuit. Delay for a short while to allow it to stabilise before we take a reading //There are two delays A & B //Delay A has LEDs on and External circuit on. This tells the user we are about to take a reading and it powers up the circuit allowing it to settle //Delay B follows delay A and has the LEDs off with the circuit on. This way light from the LEDs will not affect the light detector #define d_EXTERNAL_CIRCUIT_POWERUP_DELAY_A 8 //Delay while external circuit and LEDs are powered up #define d_EXTERNAL_CIRCUIT_POWERUP_DELAY_B 2 //Delay after DELAY_A and while only the external circuit is powered up (not the LEDs) #define d_ANALOG_READ_PIN 7 //This pin is set as an analogue to digital read pin to measure the analogue value applied to this pin. #define d_ANALOG_LOWER_LIMIT 190 // the lowest value expected from the A/D converter #define d_ANALOG_UPPER_LIMIT 1023 // the highest value expected from the A/D converter #define d_INTERNAL_DATA_UPPER_LIMIT 1023 // ==Basic state machine section== #define d_STATE_SLEEP_NO_READ 0 // asleep and not reading and no recording #define d_STATE_SLEEP_AND_READ 1 // Loop {sleep - read - record} #define d_STATE_SLEEP_PENDING 2 // waiting to sleep. Transition from active to sleeping #define d_STATE_ACTIVE 3 // reading and displaying in real time volatile int m_ProgramState = d_STATE_ACTIVE; // set the initial state to active // ==Modes section== //In addition to activity states the application runs in 4 different modes #define d_FAST_RECORD_MODE 0 // recording rapidly #define d_SLOW_RECORD_MODE 1 // recording slowly whilst asleep #define d_PLAY_BACK_MODE 2 // display values stored in memory #define d_MEMORY_STATUS_MODE 3 // showing state of usage of memory #define d_MODE_COUNT 4 // the total number of modes //==== #define d_MODE_BUTTON_PIN 1 //Button A The mode button is used to change the application mode #define d_RESET_BUTTON_GND_PIN 10 //Button B connects Reset to GND when pressed. We use this pin to provide a GND source for button B // Button B, Reset pin, is not defined as it is an inbuilt chip function. We provide GND to button B such that when it is pressed it connects GND to the reset. volatile byte m_PresMode = d_FAST_RECORD_MODE; // Note this initial value will be over written by the mode stored in EEPROM // ==EEProm section== // Non-Volatile Data Memory. Data stored in EEProm is not destroyed by powering the device Off. // EEProm Endurance: at least 100,000 Write/Erase Cycles // the term "EEPROM" is used for devices with per-byte erase capabilities and "flash" for devices which only support large-block erasure. // see http://electronics.stackexchange.com/questions/69234/what-is-the-difference-between-flash-memory-and-eeprom #define d_EEMEM_POS_MODE 0 // the position in EEprom which is used to save the current Mode #define d_EEMEM_NOT_USED_BYTE 1 // the position in EEprom #define d_EEMEM_POS_FREESPACE 2 // the position in EEprom which is used to save the pointer to the next available storage location #define d_START_EEMEM_POS 4 // the first position in EEprom that can be used for data storage #define d_END_EEMEM_POS 511 // the last position in EEprom that can be used for data storage #define d_ERROR_NO_DATA -1 // Error code used when retrieving EEPROM data. Normal range 0-255 #define d_NO_NEW_DATA 1 //When retrieving data to display if the timeout triggers or we have no new data, we need to know so we can avoid re-display of the same old data #define d_NEW_DATA 2 //When retrieving data to display if the timeout does not trigger and we have new data, we need to know so we display the new data volatile int m_PresEEMemPos = d_START_EEMEM_POS; //Points to next free space volatile int m_PlaybackEEMemPos = d_START_EEMEM_POS; //Start the playback from the start //==TIMER SECTION== #define d_TIMER_COUNT 65526 // used to set the counter so that it triggers the timer 100 times a second (Approx.) // 0=16ms, 1=32ms, 2=64ms, 3=128ms, 4=250ms, 5=500ms // 6=1sec, 7=2sec, 8=4sec, 9=8sec #define d_WATCHDOG_SLEEP_DURATION 8 // Wake up after 4 sec wake ups it does a read. So that is 4 seconds * d_WAKEUP_AND_READ_RATE = x seconds //every n wake ups it does a put. So that is 4 seconds * d_WAKEUP_AND_READ_RATE = x seconds Note there is an overhead on the 4 seconds as it does a read. // Approx 4.4 seconds. 39 puts = approx 170 seconds. 508 mem stores will cover 24 hours #define d_WAKEUP_AND_PUT_RATE 39 //Average every 39 readings and store it as a single value. 39 * 4.4 seconds * 508 stores = approx 24 hour cover for 508. Slow / averaging mode // Different timeout for different program modes. Based on below, after the unit has been awake / active for n milli-seconds then it will start the transition to sleeping. unsigned long int m_AwakeTimeout[d_MODE_COUNT] = {120000, 2000, 120000, 4000}; // m_MicrosLastWakeup is a variable that is used by both the loop function and the timer trigger // As such is is not safe to allow normal compiler action of using register values // We declare it volatile to specifically direct the compiler to load the variable from RAM and not from a storage register, // A storage register which is a temporary memory location where program variables are stored and manipulated. // But when loop and timing triggers access the same variable, the value for a variable stored in registers can be inaccurate. // Declaring it a volatile makes for slower code but keeps it accurate. volatile unsigned long m_MicrosLastWakeup = 0; // When true it allows a connected computer to continue to get data readings without the unit going to sleep. // Once set to true it stays true until there is a reset or power on/off cycle volatile bool m_KeepAwake = false; #define d_KEEP_AWAKE_CMD 'K' //UART char that acts as the keep awake command. unsigned int long m_SleepCount = 0; // used to control taking readings while asleep //==== #define d_FAST_READ_MILLIS 125 // Milli-seconds between sensor reads. (In fast mode it writes each read to EEProm). 125milli-seconds is 8 reads per second #define d_PLAYBACK_READ_MILLIS 125 // In playback mode this is Milli-seconds between each playback value. unsigned long m_LastLiveRead = 0; // time when latest read was taken unsigned long int m_TotalAnalogReadValue = 0;//When in slow mode we read more times than we store. We store the average over the period. unsigned long int m_AnalogReadCount = 0; //Use with the above to provide an average value //========MACROS====== // BIT Manipulation macros // Macros use for setting and clearing bits in a bit field. // cbi stands for Clear Bit // sbi stands for Set Bit. // cbi(sfr, bit) clears bit-number indicated by bit in the register sfr. // sbi(sfr, bit) sets bit-number indicated by bit in the register sfr. #ifndef cbi #define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit)) #endif #ifndef sbi #define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit)) #endif // // Watchdog Interrupt Service / is executed when watchdog timed out // This is required here to ensure the watchdog sleep works. It is required even if it does nothing. // ISR(WDT_vect) { // No action triggered // For more details on power save / sleep modes see: // http://www.surprisingedge.com/low-power-atmegatiny-with-watchdog-timer/ // https://www.sparkfun.com/tutorials/309 // http://jeelabs.org/tag/lowpower/ // http://interface.khm.de/index.php/lab/experiments/sleep_watchdog_battery/ } // // function to send the ATtiny into Power Save Mode // void goToSleep() { // Note do not disable all interrupts else it will not wake up again. It waits on the watchdog timer interrupt WriteAllLEDs(LOW); // turn off all LEDs // digitalWrite(d_LED_READ_INDICATOR_PIN, LOW); // turn off the read indicator DataLogSerial.end(); cbi(ADCSRA, ADEN); // switch analogue to Digital converter OFF setup_watchdog(d_WATCHDOG_SLEEP_DURATION); //There are a number of sleep modes. We have chosen the most power saving mode to get the power draw when asleep down to 4 micro amps // 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 set_sleep_mode(SLEEP_MODE_PWR_DOWN); // Set sleep mode. We only come out of sleep mode when our 4 second nap is over or reset is pressed sleep_enable(); //Puts the device ready for sleep mode. How the device is brought out of sleep mode depends on the specific mode selected with the set_sleep_mode() function. sleep_mode(); // Enter sleep mode. // After waking from watchdog interrupt the code continues to execute from this point. sleep_disable(); // Disable sleep mode after waking. // Re-enable the peripherals. sbi(ADCSRA, ADEN); // switch analogue to Digital converter ON DataLogSerial.begin(d_UART_BAUD); m_MicrosLastWakeup = millis(); } // // setup_watchdog // Takes timerPrescaler and maps it as follows: // 0=16ms, 1=32ms, 2=64ms, 3=128ms, 4=250ms, 5=500ms // 6=1sec, 7=2sec, 8=4sec, 9=8sec // void setup_watchdog(int timerPrescaler) { if (timerPrescaler > 9 ) { timerPrescaler = 9; // Correct incoming amount if need be } byte bb = timerPrescaler & 7; // the prescaler is ored with the least significant three bits if (timerPrescaler > 7) { bb |= (1 << 5); // set the special 5th bit if necessary } //This order of commands is important and cannot be combined MCUSR &= ~(1 << WDRF); // clear the watchdog reset WDTCSR |= (1 << WDCE) | (1 << WDE); // set WD_change enable, set WD enable WDTCSR = bb; // set new watchdog timeout value WDTCSR |= _BV(WDIE); // set the interrupt enable, this will keep unit from resetting after each int } // // GetDisplayValue gets the value we should show to the user // Based on the mode this function returns the appropriate value to display. (0 - 1023) // If no value is available such as Playback has run out of playback then it returns -1 (d_ERROR_NO_DATA) // int GetDisplayValue(int Mode, int *DisplayValue) { int GetValue_Status = d_NO_NEW_DATA; if ( Mode == d_FAST_RECORD_MODE) { // check whether timer has reached value if ( TimeOut( &m_LastLiveRead, d_FAST_READ_MILLIS ) ) { // period has timed out *DisplayValue = SensorRead(); // get the value from the sensor // save the value in EEprom. If the save fails then we return d_ERROR_NO_DATA,but if m_KeepAwake is true then we carry on. // The keep awake allows a connected computer to continue to get data readings without the unit going to sleep because its eeprom is full if( EEPush( *DisplayValue ) || m_KeepAwake ) { GetValue_Status = d_NEW_DATA;//We read and stored new data DataLogSerial.print("D,"); DataLogSerial.println(*DisplayValue); } else { // here if save fails (memory is full up) *DisplayValue = 0; GetValue_Status = d_ERROR_NO_DATA; // The reading is over, let's sleep } } } else if ( Mode == d_PLAY_BACK_MODE) { // check whether timer has reached value if ( TimeOut( &m_LastLiveRead, d_PLAYBACK_READ_MILLIS ) ) { // period has timed out *DisplayValue = GetPlayBackValue(); // get the next value to display from memory if (*DisplayValue == d_ERROR_NO_DATA) { *DisplayValue = 0; GetValue_Status = d_ERROR_NO_DATA; // The reading is over, let's sleep } else { GetValue_Status = d_NEW_DATA; DataLogSerial.print("D,"); DataLogSerial.println(*DisplayValue); } } } else { // memory status mode *DisplayValue = ((m_PresEEMemPos - d_START_EEMEM_POS) * 2); //maps mem usage to our range 1023 GetValue_Status = d_NEW_DATA; } return (GetValue_Status); } // // function to test whether a given timer has reached its timeout period // bool TimeOut(volatile unsigned long *LastMillis, unsigned long TimeOutPeriod ) { unsigned long PresMillis = millis(); // the function millis() is an Arduino function returning the time that the program has been running in milliseconds if (PresMillis < *LastMillis) // check for wrap around Happens once every 50 days (Approx.) { *LastMillis = PresMillis; //Correct a detected wrap around } // test whether we are still within the timer range if ((*LastMillis + TimeOutPeriod) < PresMillis) { // timed out so update time value *LastMillis = PresMillis; return (true); } else { // not timed out return (false); } } // // ISR - timer1 overflow interrupt service routine // useful values are: // 8000000 / 1024 = 7812.5 Hz // 7812.5 / 50 = 156.26 // 65536 - 156 = 65380 // where: // 8000000 is the main clock frequency in Hz // 1024 is the prescaler that we have set // 50 is the frequency that we want in Hz // 65536 is the maximum timer value // A good write up on timer functions can be found at // http://www.keychainino.com/how-timer-overflow-interrupt-works/ // ISR(TIM1_OVF_vect) { cli(); //disable interrupts TCNT1 = d_TIMER_COUNT; //This timer next triggers at about 25ms (Use of internal clock means we do not expect precise timing) //If we are active and its time to sleep then lets change from active mode to sleep pending if (m_ProgramState == d_STATE_ACTIVE) { if( TimeOut(&m_MicrosLastWakeup, m_AwakeTimeout[m_PresMode]) ) { //Normally we will sleep when timed out. But in fast record mode, when m_KeepAwake is set to true, then we do not sleep. if( ( ! m_KeepAwake)||( m_PresMode != d_FAST_RECORD_MODE)) { SetSleepPending(); // turn off the detector supply and set state to sleep pending. Get ready to sleep. } } } // check if we are going to sleep if ( m_ProgramState == d_STATE_SLEEP_PENDING ) { // have we reached the time to start to show that we are going to sleep? if ( TimeOut( &m_StartPendShow , d_PENDSHOW_TOTAL_MILLIS ) ) { // check mode if ( m_PresMode == d_SLOW_RECORD_MODE) //In Slow mode we only record in the sleep wake loop { // initialise variables for recording in our sleep m_ProgramState = d_STATE_SLEEP_AND_READ; m_TotalAnalogReadValue = 0; m_AnalogReadCount = 0; m_SleepCount = 0; } else { // otherwise turn off all LEDs m_ProgramState = d_STATE_SLEEP_NO_READ; WriteAllLEDs(LOW); // turn off all LEDs // digitalWrite(d_LED_READ_INDICATOR_PIN, LOW); } } else if ( TimeOut( &m_LastPendShowChange, d_PENDSHOW_CHANGE_MILLIS ) ) { // not yet asleep so show the next frame in our LED show LEDShow_NextFrame(SleepLEDShow); } } //If we are active then get a value and display that value if (m_ProgramState == d_STATE_ACTIVE) { //We are in active mode so we need to take action by doing a read and showing its value //The 4 bar LED has three states for each LED On, Dim, Off. We do the dim part here in the timer routine //For other projects consider using the PWM function to dim LEDS. This method is not done here as the //Position of the chip legs does not fit well with the layout of the kit on a breadboard if ((m_FlashPinFlashRate > 0) && (TimeOut(&m_MicrosLastPinFlash, m_FlashPinFlashRate)) ) { m_LedFlashState = !m_LedFlashState; // invert the flash state (on/off) digitalWrite(m_FlashPin, m_LedFlashState); // update the LEDs to the new state } //Get a value to display on the 4 LED bar. Different Modes get values from different sources but all are displayed using the same display int GetValue_Status = GetDisplayValue( m_PresMode, &m_LED_DisplayValue ); if (GetValue_Status == d_NEW_DATA) { //Show the value we have on our 4 LED display BarLED_ShowValue(m_LED_DisplayValue); } else if (GetValue_Status == d_ERROR_NO_DATA) { SetSleepPending(); // turn off the detector supply } } sei(); // in all cases, enable interrupts } // // function to show a pattern on the LED bar by dividing a value into four parts // void LEDShow_NextFrame(unsigned int ShowPattern[]) { // ShowPattern is an integer array of values like {0, 8, 1, 3, 6, 12, 8, 12, 6, 3} // increment the frame pointer, cycling to start when we reach the end ShowPattern[d_POS_PRES_FRAME_NUMBER] = (ShowPattern[d_POS_PRES_FRAME_NUMBER] + 1) % ShowPattern[d_POS_NUMBER_OF_FRAMES]; // get the frame to display int Frame = ShowPattern[0] + d_PATTERN_HEADER_LEN; // set the LEDs according to whether the corresponding bit is set in the pattern element digitalWrite(d_LED_BAR1_PIN, (ShowPattern[Frame] & d_BIT_1)); digitalWrite(d_LED_BAR2_PIN, (ShowPattern[Frame] & d_BIT_2)); digitalWrite(d_LED_BAR3_PIN, (ShowPattern[Frame] & d_BIT_3)); digitalWrite(d_LED_BAR4_PIN, (ShowPattern[Frame] & d_BIT_4)); } // // function to display a value on the LED Bar // void BarLED_ShowValue(int DisplayValue) { m_LED_ActivityIndicator_State = !m_LED_ActivityIndicator_State; // reverse the activity indicator state digitalWrite(d_ACTIVITY_INDICATOR_PIN, m_LED_ActivityIndicator_State); // write the new state to the ActivityIndicator // the value to be displayed can be between 0 and 1023. // First calculates how many of the four available LEDs should be turned on given that each LED represents a value range // of 256 units. Thus we have // value 0 to 255 - 0 LEDs // value 256 to 511 - 1 LED // value 512 to 767 - 2 LEDs // value 768 to 1023 - 3 LEDs int LEDsOn = DisplayValue / d_VALUES_PER_SECTION; // the number of LEDs that will be shown fully on obtained by integer division int RemainderValue = DisplayValue % d_VALUES_PER_SECTION; // get the remainder after division by 256 to find the part of the value for the final LED // turn on the required number of LEDs for (int Led = 0; Led < LEDsOn; Led++) { digitalWrite(d_LED_BAR1_PIN + Led, HIGH ); // turn on LED } // turn off the rest for (int Led = LEDsOn; Led < d_BAR_LED_COUNT; Led++) { digitalWrite(d_LED_BAR1_PIN + Led, LOW ); // turn off LED } m_FlashPin = d_LED_BAR1_PIN + LEDsOn; // the LED used to display the remainder is the one following the LEDs that are full on m_FlashPinFlashRate = d_NO_FLASH; // Initialise to show the pin will not be flashed // test how to set the remainder LED if (RemainderValue > d_HIGH_LIGHT_LEVEL) { // in this case the remainder is sufficiently high that we can turn this LED full on digitalWrite(m_FlashPin, HIGH); // turn on the remainder LED } else if (RemainderValue > d_LOW_LIGHT_LEVEL) // light level is between low and high { // in this case we set a flash rate so that the LED appears dim m_FlashPinFlashRate = d_DIM_FLASH_RATE; // Set the LED to dim as the intensity is low enough } } // // the setup function is called once when the program starts (after power up or reset) // it is used to initialise variables, pin modes, etc // void setup() { cli(); // disable global interrupts so the timer does not trigger until we have completed the setup GetHeaderData(); //Read Current Mode and Current Memory position(s) from EEPROM //=============SERIAL pinMode(d_UART_RX_PIN, INPUT); pinMode(d_UART_TX_PIN, OUTPUT); DataLogSerial.begin(d_UART_BAUD); DataLogSerial.print("\n\n"); DataLogSerial.println(d_VERSION_STRING); //================= // check for current mode if (m_PresMode != d_PLAY_BACK_MODE) { // not in play back mode so reset EEPROM memory pointers such that we can store fresh data EEResetMemory(); } // IO Pin SECTION // assign pins for Mode button (A) and reset button (B) GND pinMode(d_MODE_BUTTON_PIN, INPUT_PULLUP); // the mode button input uses the internal pullup resistor pinMode(d_RESET_BUTTON_GND_PIN, OUTPUT); // the reset button uses a digital output as its "ground" pin digitalWrite(d_RESET_BUTTON_GND_PIN, LOW); // the output is set low to provide a 0 volt pseudo ground pinMode(d_SENSOR_VCC_PIN, OUTPUT); // the sensor uses a digital output as power source digitalWrite(d_SENSOR_VCC_PIN, LOW); // the output is set low initially so that the sensor is pwoered off pinMode(d_ACTIVITY_INDICATOR_PIN, OUTPUT); // The activity indicator pin is designed to turn an LED On/Off dependent on how active the unit is digitalWrite(d_ACTIVITY_INDICATOR_PIN, LOW); // Set low to start with m_LED_ActivityIndicator_State = LOW; // the four pins for the LED Bar are defined as outputs pinMode(d_LED_BAR1_PIN, OUTPUT); pinMode(d_LED_BAR2_PIN, OUTPUT); pinMode(d_LED_BAR3_PIN, OUTPUT); pinMode(d_LED_BAR4_PIN, OUTPUT); // pinMode(d_LED_READ_INDICATOR_PIN, OUTPUT); // this pin can support an LED to show when reads are taken, it is not part of the basic kit // digitalWrite(d_LED_READ_INDICATOR_PIN, LOW); // the LED is turned off initially ShowMode(); // sets the LED Bar to show the current mode //====TIMER SECTION==== // initialise Timer1 TCCR1A = 0; // set entire TCCR1A register to 0 TCCR1B = 0; // set entire TCCR1B register to 0 // enable Timer1 overflow interrupt: TIMSK1 |= (1 << TOIE1); // preload timer 65536 - (8000000 / 1024 / 60) TCNT1 = d_TIMER_COUNT; // set 1024 prescaler bitSet(TCCR1B, CS12); bitSet(TCCR1B, CS10); //======WATCHDOG TIMER SECTION====== setup_watchdog(d_WATCHDOG_SLEEP_DURATION); // Wake up after n sec //===================== m_SleepCount = 0; m_MicrosLastWakeup = millis(); // record the time of the wake up (as milliseconds since the program started running) SendStatusMessage(); sei(); // enable global interrupts to start timer running } // After running the setup() function, which sets the initial values, // the loop() function, is constantly called from a timer. // This allows us to monitor, change and respond to events. // Used here to control sleeping status and mode change using the mode button // The rest of the program action is triggered by the timer event. (ISR Timer1) void loop() { // check to see if we need to change mode if (digitalRead(d_MODE_BUTTON_PIN) == LOW) // the mode button is low when pressed { cli(); // disable global interrupts to protect the following section of code m_PlaybackEEMemPos = d_START_EEMEM_POS; // Reset the playback pointer so we can replay the memory. (Designed for use in Playback mode) ShowMode(); // Show the user the current mode. //While the Mode button remains depressed the Mode continues to be changed while ( digitalRead(d_MODE_BUTTON_PIN) == LOW ) { ChangeMode(); // Changing to the next mode ShowMode(); // Show the user the current mode } m_MicrosLastWakeup = millis(); // record the wake up time/last user action m_LastLiveRead = m_MicrosLastWakeup; // set last read time as being the wake up/last user action time m_ProgramState = d_STATE_ACTIVE; //Wake up, get Active. Even if we are awake/active, no harm in setting it again SendStatusMessage();//Send a status message via UART to a connected computer. sei(); //re-enable global interrupts so that timer runs } //If we are in state active, then we monitor UART comms in case a connected computer sends us a command. if(m_ProgramState == d_STATE_ACTIVE) { char UartChar = DataLogSerial.read(); // If we receive a char and that char is d_KEEP_AWAKE_CMD, then set m_KeepAwake to true such that in fast mode we do not sleep, even when we are timed out or when eeprom is full // Keep awake allows a connected computer to get fast sensor data for as long as it wants. if( UartChar == d_KEEP_AWAKE_CMD ) { m_KeepAwake = true; DataLogSerial.println("A,K"); //Acknowledge that we have acted on the command } } if ((m_ProgramState == d_STATE_SLEEP_AND_READ) || (m_ProgramState == d_STATE_SLEEP_NO_READ)) { goToSleep(); // Enter a deep sleep only to awake ofter x time or a cpu reset if ( m_ProgramState == d_STATE_SLEEP_AND_READ ) { if ( EEMemoryFull() )//if we know the memory is full the do not bother taking any more readings as we will not be able to store them { m_ProgramState = d_STATE_SLEEP_NO_READ; } else { m_SleepCount++; digitalWrite(d_SENSOR_VCC_PIN, HIGH); // turn on the sensor vcc supply BarLED_ShowValue( ((m_PresEEMemPos - d_START_EEMEM_POS) * 2));//Show mem status on the bar leds delay(d_EXTERNAL_CIRCUIT_POWERUP_DELAY_A); //Small delay for the external circuit to stabilise and show LEDs so the user can see it is working WriteAllLEDs(LOW); // turn off all LEDs delay(d_EXTERNAL_CIRCUIT_POWERUP_DELAY_B); //Small delay for the external circuit to continue to stabilise and no light from LED so there it does not effect the read. m_LED_DisplayValue = SensorRead(); // get the current value from the analogue input digitalWrite(d_SENSOR_VCC_PIN, LOW); // turn off the sensor vcc supply m_TotalAnalogReadValue = m_TotalAnalogReadValue + m_LED_DisplayValue; // Add sensor value to the total such that we can get an average over time. m_AnalogReadCount++; // increment the number of samples in this batch //If its time to store the average of our readings if ((m_SleepCount % d_WAKEUP_AND_PUT_RATE) == 0) { //Since the preceding code does a m_AnalogReadCount++; and this in an unsigned int m_AnalogReadCount cannot be zero so we do not need to check for this condition int SensorPeriodAverage = m_TotalAnalogReadValue / m_AnalogReadCount; if ( ! EEPush(SensorPeriodAverage) ) // if the write to EEprom fails we assume it is because the memory is full. { // as memory must be full we move to a sleep state so that no more data storing is attempted m_ProgramState = d_STATE_SLEEP_NO_READ; } // reset our average over time m_TotalAnalogReadValue = 0; m_AnalogReadCount = 0; } } } } } // // function to read a byte from EEprom // int GetPlayBackValue() { // check validity of memory pointer if ( m_PlaybackEEMemPos < m_PresEEMemPos ) { // valid so read byte int RetVal = EEPeek(m_PlaybackEEMemPos); // and increment memory pointer m_PlaybackEEMemPos++; return RetVal; } else { // invalid call so return error condition return d_ERROR_NO_DATA; } } /* ************************************ * EEProm functions for treating EEprom as a data store using permanent memory * It is organized as a separate data space, in which single bytes can be read and written. * The EEPROM has an endurance of at least 100,000 write/erase cycles. ************************************ */ // // function to reset memory pointers // void EEResetMemory() { m_PresEEMemPos = d_START_EEMEM_POS; // point to start of available memory m_PlaybackEEMemPos = d_START_EEMEM_POS; // make current pointer also start of memory EEPROM.put(d_EEMEM_POS_FREESPACE, m_PresEEMemPos); // save pointer to EEprom } // // function to inform on memory full status // bool EEMemoryFull() { return (m_PresEEMemPos > d_END_EEMEM_POS); } // // function to inform the caller if the Data position is within the valid EE data store range // bool EEValidDataPos(int MemPos, int OverRun) { return ( (MemPos >= d_START_EEMEM_POS) && (MemPos <= (d_END_EEMEM_POS + OverRun)) ); } // // function to write a byte to EEProm // bool EEPush(int Value) { // check if there is room to store the data if ( EEMemoryFull() ) { // Memory Full return false; } else { // There is room for this data item byte ByteVal = Value >> 2; // shift right two bits to reduce 10 bit value to 8 bits for storing EEPROM.put(m_PresEEMemPos, ByteVal); // write to EEprom m_PresEEMemPos++; // increment memory pointer EEPROM.put(d_EEMEM_POS_FREESPACE, m_PresEEMemPos); // save the pointer in EEProm return true; } } // // function to read a byte from EEprom // int EEPeek(int PeekPos) { // check validity of memory address we are being asked to read if ( EEValidDataPos(PeekPos, 0) ) { // range is valid so get byte from EEprom byte RetVal = 0; EEPROM.get( PeekPos, RetVal); // convert to integer for return but shift two places left so that what was read as an 8 bit byte becomes a 10 bit value return ((int)RetVal << 2); } else { // invalid range, return error condition return d_ERROR_NO_DATA; } } // // function to read saved mode and memory pointer from EEprom // void GetHeaderData() { EEPROM.get(d_EEMEM_POS_MODE, m_PresMode); // get the saved mode from EEprom // check for valid value if ((m_PresMode < d_FAST_RECORD_MODE) || (m_PresMode > d_MEMORY_STATUS_MODE)) { // invalid so default to fast record m_PresMode = d_FAST_RECORD_MODE; // write the new mode back to EEprom EEPROM.put(d_EEMEM_POS_MODE, m_PresMode); } EEPROM.get(d_EEMEM_POS_FREESPACE, m_PresEEMemPos); // gets the next free memory position from EEprom // check for validity if ( ! EEValidDataPos(m_PresEEMemPos, 1) ) //We allow an overun of one byte as the FreePos can point one byte past EEProm memory { // invalid so reset to start of memory m_PresEEMemPos = d_START_EEMEM_POS; EEPROM.put(d_EEMEM_POS_FREESPACE, m_PresEEMemPos); } // update memory positions; m_PlaybackEEMemPos = d_START_EEMEM_POS; } //========================================= // // function to set all Leds to either all on, or all off, according to PinState (which can be HIGH or LOW) // void WriteAllLEDs(uint8_t PinState) { digitalWrite(d_ACTIVITY_INDICATOR_PIN, PinState); digitalWrite(d_LED_BAR1_PIN, PinState); digitalWrite(d_LED_BAR2_PIN, PinState); digitalWrite(d_LED_BAR3_PIN, PinState); digitalWrite(d_LED_BAR4_PIN, PinState); } // // function to respond to a mode button press // void ChangeMode() { m_PresMode = (m_PresMode + 1) % d_MODE_COUNT; // increment mode (cycle back to first mode if necessary) EEPROM.put(d_EEMEM_POS_MODE, m_PresMode); // save the new value in EEprom m_LED_DisplayValue = 0; // clear the value to display on the LEDs m_PlaybackEEMemPos = d_START_EEMEM_POS; // reset the storage pointer to the start of the EEprom range DataLogSerial.print("M,"); DataLogSerial.println(m_PresMode); } // // function to flash the LEDs to indicate the current mode // void ShowMode() { WriteAllLEDs(HIGH); // turn on all LEDs delay(d_MODE_LONG_DELAY); // wait WriteAllLEDs(LOW); // turn off all LEDs for (int i = 0; i < d_MODE_FLASH_COUNT; i++) // repeat for the defined number of flashes { digitalWrite(d_LED_BAR1_PIN + m_PresMode, HIGH); // turn on the LED for the current mode delay(d_MODE_LONG_DELAY); // wait digitalWrite(d_LED_BAR1_PIN + m_PresMode, LOW); // turn off the LED for the current mode delay(d_MODE_SHORT_DELAY); // wait again but only off for a short while. Cosmetic! This seems to look best for most users. } // check current mode to see if we need the Sensor on full time if (m_PresMode == d_FAST_RECORD_MODE) { digitalWrite(d_SENSOR_VCC_PIN, HIGH); // if the current mode is fast record then turn on the detector supply } else { digitalWrite(d_SENSOR_VCC_PIN, LOW); // otherwise turn it off } } // // function to set the detector supply ready for the process going to sleep // void SetSleepPending() { m_ProgramState = d_STATE_SLEEP_PENDING; // update the current state m_LED_DisplayValue = 0; //Reset this for later in case we are woken up during the sleep pending period. m_StartPendShow = millis(); // records the current time as milliseconds since the program started running digitalWrite(d_SENSOR_VCC_PIN, LOW); // turn off the sensor supply digitalWrite(d_ACTIVITY_INDICATOR_PIN, LOW); // turn off activity pin. We are becoming less active. SendStatusMessage(); //Send a status update to a connected computer. } // // function to obtain a value from the analogue input // int SensorRead() { int readValue = analogRead(d_ANALOG_READ_PIN); // get the 10 bit digital value // scale the value to match the range lower limit to upper limit // also invert the value by subtracting from 1023 // note that the light detector gives a low input (lower limit) for a bright light and a high input (upper limit) for no light // in the map function below the possible input range (0 to 1023) is mapped to the required output range (lower limit to upper limit) // and the returned value is readValue mapped into the output range readValue = d_INTERNAL_DATA_UPPER_LIMIT - map(readValue, d_ANALOG_LOWER_LIMIT, d_ANALOG_UPPER_LIMIT, 0, d_INTERNAL_DATA_UPPER_LIMIT); return (readValue); } // // Sends a status message via UART // S, PRES_MODE, PRES_STATE, PRES_EEMEM_POS // void SendStatusMessage() { DataLogSerial.print("S,"); //Going to sleep message DataLogSerial.print(m_PresMode); DataLogSerial.print(","); DataLogSerial.print(m_ProgramState); DataLogSerial.print(","); DataLogSerial.println(m_PresEEMemPos); }