void CRemoteInterface::update() { if (motorControl.update) { updateMotorControl(); motorControl.update = false; } const uint32_t curtime = millis(); if (curtime > statusSendDelay) { statusSendDelay = curtime + 500; sendStatus(); } if (REM_SERIAL.available()) checkForCommands(); }
/** * \brief The main application * * This application logs visible and infrared irradiance levels to an SD card * * * \note * */ int main(void) { uint8_t ct, swDnUp, swBbIr; uint8_t errSD, cnt, r; uint16_t cntout = 0; strJSON[0] = '\0'; // "erase" the string DDRD &= ~(1<<5); // make the Bluetooth connection monitor pin an input PORTD &= ~(1<<5); // disable internal pull-up resistor DDRD |= (1<<4); // make Bluetooth power control an output DDRD |= (1<<7); // make Bluetooth baud rate control an output DDRB |= (1<<1); // make GPS power control an output DDRB |= (1<<0); // make GPS on/off an output GPS_power_off; // default to power off GPS_On_Off_Low; // brief (~200ms) high pulse turns GPS on/off BT_power_off(); // BT_baud_9600(); BT_baud_115k(); // cli(); // setupDiagnostics(); // need heartbeat timer to correctly turn SD power off (may work around this) // // also may help with power control tracking while testing dead battery re-charge // sei(); // turnSDCardPowerOff(); // force SD card power off, and pins in lowest power modes if (!(PRR0 & (1<<PRSPI))) // is SPI power on (Pwr Save bit clear)? { if (SPCR & (1<<SPE)) // is SPI enabled? { SPCR &= ~(1<<SPE); // disable SPI } PRR0 |= (1<<PRSPI); // turn off power to SPI module, stop its clock } DESELECT(); DDR_SPI &= ~((1<<DD_MOSI)|(1<<DD_SCK)); // change SPI output lines MOSI and SCK into inputs // pins might source current momentarily // set port bits to 0, disable any internal pull-ups; tri-state the pins SPI_PORT &= ~((1<<SPI_MOSI_BIT)|(1<<SPI_SCK_BIT)|(1<<SPI_MISO_BIT)); // MISO was already an input SD_CS_DD &= ~(1<<SD_CS_BIT); // change SS to an input, momentarily sources current through internal pull-up SD_CS_PORT &= ~(1<<SD_CS_BIT); // set port bit to zero, tri-state the input SD_PWR_DD |= (1<<SD_PWR_BIT); // Turns on PWR pin as output SD_PWR_PORT |= (1<<SD_PWR_BIT); // Drive PWR pin high; this will turn FET off SD_PWR_DD &= ~(1<<SD_PWR_BIT); // change PWR pin to an input // internal pull-up momentarily pulls high, along with external pull-up SD_PWR_PORT &= ~(1<<SD_PWR_BIT); // tri-state the pin; external pull-up keeps FET off // Stat |= STA_NOINIT; // Set STA_NOINIT // try allowing the following on first power-up, even if cell is barely charged cli(); setupDiagnostics(); uart0_init(); initFlags |= (1<<initUART0); uart1_init(); initFlags |= (1<<initUART1); sei(); // main osc is not tuned yet, can't do that until I2C is working commandBuffer[0] = '\0'; // "empty" the command buffer commandBufferPtr = commandBuffer; stateFlags1 |= (1<<writeDataHeaders); // write column headers at least once on startup intTmp1 = readCellVoltage(&cellVoltageReading); I2C_Init(); // enable I2C initFlags |= (1<<initI2C); if (initFlags & (1<<initUART0)) outputStringToUART0("\r\n UART0 Initialized\r\n"); if (initFlags & (1<<initUART1)) outputStringToUART1("\r\n UART1 Initialized\r\n"); if (initFlags & (1<<initI2C)) outputStringToUART0("\r\n I2C_Init completed\r\n"); // test the I2C bus, if it doesn't work nothing much else will intTmp1 = I2C_Start(); if (intTmp1 == 0) { // I2C start timed out // we are hung, go into SOS mode PRR0 |= (1<<PRTWI); // disable I2C, turn module power off (set Power Reduction bit) DDRC &= 0b00111111; // make SCL and SDA pins inputs so we can read them PORTC &= 0b00111111; // disable internal pull-up resistors // uart0 is about all we've got to talk on UBRR0 = 207; // 0.2% error BAUD_4800_2X_OSC_8MHZ, assume main osc untuned 8MHz Timer2 = 0; while (1) { // mostly, fast-blink the pilot light for (Timer1 = 10; Timer1; ); // Wait for 100ms PORTA |= (0b00000100); // set pilot light on for (Timer1 = 10; Timer1; ); // Wait for 100ms PORTA &= ~(0b00000100); // turn off bit 2, pilot light blinkey if (!Timer2) { // about every 3 seconds, send diagnostics cli(); // temporarily disable interrupts to prevent Timer3 from // changing the count partway through stateFlags1 |= (1<<isRoused); // enable uart output rouseCountdown = 100; // hold the Rouse flag on long enough for output sei(); len = sprintf(str, "\n\r I2C bus start failure: SCL = %i, SDA = %i \n\r", (PINC & 1), ((PINC & 2) ? 1 : 0)); outputStringToUART0(str); Timer2 = 1000; // ~3 sec to next message } } } else { // I2C bus started OK intTmp1 = I2C_Write(0); // send the generic device address so devices release the bus I2C_Stop(); // release I2C bus and continue } intTmp1 = rtc_readTime(&dt_RTC); strcat(strJSON, "\r\n{\"timechange\":{\"from\":\""); datetime_getstring(datetime_string, &dt_RTC); strcat(strJSON, datetime_string); strcat(strJSON, "\",\"to\":\""); if (dt_RTC.year) { // 0 on power up, otherwise must already have been set rtcStatus = rtcTimeRetained; strcat(strJSON, datetime_string); strcat(strJSON, "\",\"by\":\"retained\"}}\r\n"); } else { // RTC year = 0 on power up, clock needs to be set rtc_setdefault(); if (!rtc_setTime(&dt_RTC)) { rtcStatus = rtcTimeSetToDefault; datetime_getstring(datetime_string, &dt_RTC); strcat(strJSON, datetime_string); strcat(strJSON, "\",\"by\":\"default\"}}\r\n"); } else { rtcStatus = rtcTimeSetFailed; strcat(strJSON, datetime_string); strcat(strJSON, "\",\"by\":\"failure\"}}\r\n"); } } stateFlags1 |= (1<<writeJSONMsg); // log JSON message on next SD card write timeFlags &= ~(1<<nextAlarmSet); // alarm not set yet irradFlags |= (1<<isDark); // set the Dark flag, default till full-power initializations passed // tune uC osc down to 7.3728 MHz, implement 115200 baud // need I2C to do this tuneMainOsc(); r = initializeADXL345(); if (r) { len = sprintf(str, "\n\r ADXL345 initialize failed: %d\n\r\n\r", r); outputStringToUART0(str); } else { initFlags |= (1<<initAccelerometer); outputStringToUART0("\r\n ADXL345 initialized\r\n"); } switch (rtcStatus) { case rtcTimeRetained: outputStringToBothUARTs("\n\r time retained through uC reset\n\r"); break; case rtcTimeSetToDefault: outputStringToBothUARTs("\n\r time set to default, now elapsed to "); datetime_getstring(datetime_string, &dt_RTC); outputStringToBothUARTs(datetime_string); outputStringToBothUARTs("\n\r\n\r"); break; case rtcTimeSetFailed: outputStringToBothUARTs("\n\r could not set Real Time Clock \n\r"); break; } // attempt to read/write the time zone; will retry later if e.g. power too low syncTimeZone(); stateFlags1 |= (1<<isRoused); // force on for testing, enable UART output // for (Timer1 = 3; Timer1; ); // Wait for 30ms outputStringToUART0("\r\n test\r\n"); // for (Timer1 = 3; Timer1; ); // Wait for 30ms outputStringToUART0("\r\n test\r\n"); // for (Timer1 = 3; Timer1; ); // Wait for 30ms outputStringToUART0("\r\n test\r\n"); // for (Timer1 = 3; Timer1; ); // Wait for 30ms outputStringToBothUARTs("\n\r Power good \n\r\n\r"); /* // try to adjust the uC clock frequency // first step, measure the uC clock, relative to the RTC, which latter should be very accurate // eventually put this in a more reasonable place, but for now right here before main loop // go into uC clock adjust mode outputStringToUART0("\r\n going into uC adjust mode\r\n"); timeFlags &= ~(1<<nextAlarmSet); // clear flag disableRTCInterrupt(); intTmp1 = rtc_enableSqWave(); // PRTIM1 make sure power reduction register bit if off so timers run // go back into normal timekeeping mode outputStringToUART0("\r\n returning to timekeeping mode\r\n"); if (!(timeFlags & (1<<nextAlarmSet))) { intTmp1 = rtc_setupNextAlarm(&dt_CurAlarm); timeFlags |= (1<<nextAlarmSet); } */ while (1) { // main program loop // code that will only run once when/if cell voltage first goes above threshold, // sufficient to run initializations and modules that take more power if (!(stateFlags1 & (1<<reachedFullPower))) { if (cellVoltageReading.adcWholeWord > CELL_VOLTAGE_GOOD_FOR_STARTUP) { stateFlags1 |= (1<<reachedFullPower); // flag, so this loop does not happen again till next reset if (cellVoltageReading.adcWholeWord > CELL_VOLTAGE_GOOD_FOR_ALL_FUNCTIONS) { // probably, somebody has just popped a fresh battery in, and wants to set up this device stayRoused(18000); // keep system roused for 3 minutes (180 sec) for diagnostic output } else { // probably, battery has slowly charged from dead, and nobody is here watching // long diagnostics would just waste power for no good reason stayRoused(300); // only briefly, 3 seconds, then go into low power mode } if (cellVoltageReading.adcWholeWord > CELL_VOLTAGE_GOOD_FOR_ALL_FUNCTIONS) { // it's likely someone is setting up this device with a fresh battery tuneMainOsc(); // re-tune, in case clock was slow on first low-power startup keepBluetoothPowered(180); // start with Bluetooth power on for 3 minutes } } // end test CELL_VOLTAGE_GOOD_FOR_STARTUP } // end test reachedFullPower flag // end of segment that runs only once, when Full Power first achieved // beginning of loop that runs repeatedly // tests of normal operation checkCriticalPower(); if (stateFlags1 & (1<<reachedFullPower)) { // only run this after cell has charged to full power and modules initialized if (motionFlags & (1<<tapDetected)) { outputStringToUART0("\n\r Tap detected \n\r\n\r"); if (stateFlags1 & (1<<isRoused)) { // if tap detected while already roused stayRoused(12000); // 2 minutes (120 seconds) tuneMainOsc(); // re-tune, in case clock was slow on first low-power startup keepBluetoothPowered(120); // try for two minutes to get a Bluetooth connection } motionFlags &= ~(1<<tapDetected); } if (stateFlags1 & (1<<isRoused)) { // if roused irradFlags &= ~(1<<isDark); // clear the Dark flag timeFlags &= ~(1<<nextAlarmSet); // flag that the next alarm might not be correctly set // if (irradFlags & (1<<isDark)) // timeFlags &= ~(1<<nextAlarmSet); // flag that the next alarm might not be correctly set } if (BT_connected()) { // keep resetting this, so BT power will stay on for 2 minutes after connection lost // to allow easy reconnection keepBluetoothPowered(120); } else { // not connected if (btFlags & (1<<btWasConnected)) { // connection lost ; // action(s) when connection lost } btFlags &= ~(1<<btWasConnected); // clear the flag } } // end of this full power segment machineState = Idle; // beginning, or done with everything; return to Idle state while (machineState == Idle) { // RTC interrupt will break out of this checkCriticalPower(); // if (stateFlags1 & (1<<reachedFullPower)) { // another full-power-only segment intTmp1 = clearAnyADXL345TapInterrupt(); if (intTmp1) { len = sprintf(str, "\r\n could not clear ADXL345 Tap Interrupt: %d\r\n", intTmp1); outputStringToUART0(str); } enableAccelInterrupt(); checkForBTCommands(); checkForCommands(); // } // end of this full-power segment if (!(timeFlags & (1<<nextAlarmSet))) { // outputStringToUART0("\n\r about to call setupNextAlarm \n\r\n\r"); intTmp1 = rtc_setupNextAlarm(&dt_CurAlarm); timeFlags |= (1<<nextAlarmSet); } if (!(stateFlags1 & (1<<isRoused))) { // may add other conditions later // go to sleep PORTA &= ~(0b00000100); // turn off bit 2, pilot light blinkey // SE bit in SMCR must be written to logic one and a SLEEP // instruction must be executed. // When the SM2..0 bits are written to 010, the SLEEP instruction makes the MCU enter // Power-down mode // SM2 = bit 3 // SM1 = bit 2 // SM0 = bit 1 // SE = bit 0 // don't set SE yet SMCR = 0b00000100; // set SE (sleep enable) SMCR |= (1<<SE); // go intoPower-down mode SLEEP asm("sleep"); } } // end of (machState == Idle) // when (machState != Idle) execution passes on from this point // when RTCC alarm or Accelerometer tap occurs, changes machineState to WakedFromSleep checkCriticalPower(); // Tap interrupt will not be active until first time initialization, so // following flag should not be settable till then anyway // but put internal check in case code rearranged if (motionFlags & (1<<tapDetected)) { // if it was a tap, go into Roused state if (stateFlags1 & (1<<reachedFullPower)) { // only if had achieved full power and initialized stayRoused(3000); // 30 seconds } else { stayRoused(300); // 3 seconds } motionFlags &= ~(1<<tapDetected); // clear the flag } timeFlags &= ~(1<<nextAlarmSet); // flag that current alarm is no longer valid while (timeFlags & (1<<alarmDetected)) { // interrupt that woke from sleep was RTC alarm // use 'while' loop to allow various tests to break out timeFlags &= ~(1<<alarmDetected); // clear flag so this will only happen once in any case // monitor cell voltage, to decide whether there is enough power to proceed // remember previous voltage; very first read on intialize, so should be meaningful previousADCCellVoltageReading = cellVoltageReading.adcWholeWord; intTmp1 = readCellVoltage(&cellVoltageReading); if (!(stateFlags1 & (1<<reachedFullPower))) { // if not achieved full power and initialized, skip this data acquisition loop // for testing, set to 10-second interval, so don't have to wait an hour to see if battery charging worked irradFlags &= ~(1<<isDark); // remove this line when done testing dead battery re-charging break; // will test reachedFullPower at top of main program loop } if (cellVoltageReading.adcWholeWord < CELL_VOLTAGE_THRESHOLD_UART) { // power too low for any output, no need to even read sensors // remove comment-out of following line when done testing dead battery re-charge // irradFlags |= (1<<isDark); // act as if dark, to save power break; } // see if it's time to log data if ((!((dt_CurAlarm.minute) & 0x01) && (dt_CurAlarm.second == 0)) || (irradFlags & (1<<isDark))) { // if an even number of minutes, and zero seconds // or the once-per-hour wakeup while dark timeFlags |= (1<<timeToLogData); if (irradFlags & (1<<isDark)) { // store the voltage reading at this point refDarkVoltage = cellVoltageReading.adcWholeWord; } // outputStringToUART0("\n\r Time to log data \n\r"); } else { timeFlags &= ~(1<<timeToLogData); // outputStringToUART0("\n\r Not time to log data \n\r"); } // if not time to log data, and not roused if ((!(timeFlags & (1<<timeToLogData))) && (!((stateFlags1 & (1<<isRoused))))) { // won't do anything with results anyway, don't bother reading sensors, save power stayRoused(5); // rouse for 0.05 second to flash the pilot light break; } datetime_getstring(datetime_string, &dt_CurAlarm); outputStringToBothUARTs(datetime_string); if (cellVoltageReading.adcWholeWord < CELL_VOLTAGE_THRESHOLD_READ_DATA) { len = sprintf(str, "\t power too low to read sensors, %lumV\r\n", (unsigned long)(2.5 * (unsigned long)(cellVoltageReading.adcWholeWord))); outputStringToBothUARTs(str); break; } // attempt to assure time zone is synchronized syncTimeZone(); // internally tests if work is already done // read sensors for (ct = 0; ct < 4; ct++) { switch (ct) { case 0: swDnUp = TSL2561_DnLooking; swBbIr = TSL2561_CHANNEL_BROADBAND; break; case 1: swDnUp = TSL2561_DnLooking; swBbIr = TSL2561_CHANNEL_INFRARED; break; case 2: swDnUp = TSL2561_UpLooking; swBbIr = TSL2561_CHANNEL_BROADBAND; break; case 3: swDnUp = TSL2561_UpLooking; swBbIr = TSL2561_CHANNEL_INFRARED; break; } intTmp1 = getIrrReading(swDnUp, swBbIr, &irrReadings[ct]); if (!intTmp1) { len = sprintf(str, "\t%lu", (unsigned long)((unsigned long)irrReadings[ct].irrWholeWord * (unsigned long)irrReadings[ct].irrMultiplier)); } else if (intTmp1 == errNoI2CAddressAck) { // not present or not responding len = sprintf(str, "\t-"); } else { len = sprintf(str, "\n\r Could not get reading, err code: %x \n\r", intTmp1); } outputStringToBothUARTs(str); } if (!temperature_GetReading(&temperatureReading)) { len = sprintf(str, "\t%d", (int8_t)(temperatureReading.tmprHiByte)); outputStringToBothUARTs(str); } else outputStringToBothUARTs("\t-"); // calc cell voltage from ADC reading earlier // formula from datasheet: V(measured) = adcResult * (1024 / Vref) // using internal reference, Vref = 2.56V = 2560mV // V(measured) = adcResult * 2.5 (units are millivolts, so as to get whole numbers) len = sprintf(str, "\t%lu", (unsigned long)(2.5 * (unsigned long)(cellVoltageReading.adcWholeWord))); outputStringToBothUARTs(str); outputStringToBothUARTs("\r\n"); // begin to build log string while testing, even if we end up not logging data strcpy(strLog, "\n\r"); strcat(strLog, datetime_string); irradFlags &= ~((1<<isDarkBBDn) | (1<<isDarkIRDn) | (1<<isDarkBBUp) | (1<<isDarkIRUp)); // default clear for (ct = 0; ct < 4; ct++) { // generate irradiance readings if (!(irrReadings[ct].validation)) { lngTmp1 = (unsigned long)((unsigned long)irrReadings[ct].irrWholeWord * (unsigned long)irrReadings[ct].irrMultiplier); // len = sprintf(str, "\t%lu", (unsigned long)((unsigned long)irrReadings[ct].irrWholeWord * (unsigned long)irrReadings[ct].irrMultiplier)); len = sprintf(str, "\t%lu", lngTmp1); if ((ct == 0) || (ct == 3)) { // broadband lngTmp2 = darkCutOffBB; } else { // infrared lngTmp2 = darkCutoffIR; } if (lngTmp1 < lngTmp2) { switch (ct) { case 0: irradFlags |= (1<<isDarkBBDn); break; case 1: irradFlags |= (1<<isDarkIRDn); break; case 2: irradFlags |= (1<<isDarkBBUp); break; case 3: irradFlags |= (1<<isDarkIRUp); break; } } } else { // no valid data for this reading len = sprintf(str, "\t"); // treat invalid readings as if dark switch (ct) { case 0: irradFlags |= (1<<isDarkBBDn); break; case 1: irradFlags |= (1<<isDarkIRDn); break; case 2: irradFlags |= (1<<isDarkBBUp); break; case 3: irradFlags |= (1<<isDarkIRUp); break; } } strcat(strLog, str); } // end of irradiance sensor validity testing if (timeFlags & (1<<timeToLogData)) { // outputStringToUART0("\n\r Entered log data routine \n\r"); // (previously built irradiance part of log string) // log temperature if (!temperatureReading.verification) len = sprintf(str, "\t%d", (int8_t)(temperatureReading.tmprHiByte)); else len = sprintf(str, "\t"); strcat(strLog, str); // log cell voltage len = sprintf(str, "\t%lu\n\r", (unsigned long)(2.5 * (unsigned long)(cellVoltageReading.adcWholeWord))); strcat(strLog, str); len = strlen(strLog); errSD = writeCharsToSDCard(strLog, len); if (errSD) { tellFileError (errSD); } else { outputStringToBothUARTs(" Data written to SD card \n\r\n\r"); } } // end of test if time to log data // test if dark or not dark // if all sensors are less than thresholds, or missing; and system not in Roused state if ((irradFlags & (1<<isDarkBBDn)) && (irradFlags & (1<<isDarkIRDn)) && (irradFlags & (1<<isDarkBBUp)) && (irradFlags & (1<<isDarkIRUp)) && (!(stateFlags1 & (1<<isRoused)))) { // flag that it is dark irradFlags |= (1<<isDark); } else { // or not // try new algorithm: if (cellVoltageReading.adcWholeWord > CELL_VOLTAGE_THRESHOLD_SD_CARD) { // if cell is high irradFlags &= ~(1<<isDark); // always leave the Dark state } else { // if cell voltage is low, only leave the Dark state // if cell has charged somewhat since last reading // this should eliminate early morning drain, when data will not be good anyway // require a small increase in cell voltage to ignore random jitter if (cellVoltageReading.adcWholeWord > (refDarkVoltage + 3)) { irradFlags &= ~(1<<isDark); } } } // end of testing for dark or not dark // let main loop restore Idle state, after assuring timer interrupts are re-established break; // if did everything, break here } // end of data acquisition segment turnSDCardPowerOff(); if (stateFlags1 & (1<<reachedFullPower)) { // another full-power-only segment if (!BT_connected()) { // timeout diagnostics if no Bluetooth connection if (stateFlags1 & (1<<isRoused)) { len = sprintf(str, "\r\n sleep in %u seconds\r\n", (rouseCountdown/100)); outputStringToUART0(str); } if (BT_powered()) { len = sprintf(str, "\r\n BT off in %u seconds\r\n", (btCountdown/100)); outputStringToUART0(str); } } } // end of this full-power-only segment } // end of main program loop } // end of fn main