A data acquisition and telemetry program for model rockets running on the MAX32630FTHR platform.
Dependencies: BMI160 CommandDispatcher RadioHead SDFileSystem StringFifo USBDevice USBMSD_SD gps max31865 max32630fthr swspi
Revision 0:83f4079b2bac, committed 2017-06-11
- Comitter:
- danjulio
- Date:
- Sun Jun 11 04:26:22 2017 +0000
- Commit message:
- Initial commit of Rocket, a model rocket data acquisition and telemetry program for the MAX32630FTHR platform.
Changed in this revision
diff -r 000000000000 -r 83f4079b2bac BMI160.lib --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/BMI160.lib Sun Jun 11 04:26:22 2017 +0000 @@ -0,0 +1,1 @@ +https://developer.mbed.org/teams/MaximIntegrated/code/BMI160/#0ae99e97bcf5
diff -r 000000000000 -r 83f4079b2bac CommandDispatcher.lib --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/CommandDispatcher.lib Sun Jun 11 04:26:22 2017 +0000 @@ -0,0 +1,1 @@ +https://mbed.org/users/rominos2/code/CommandDispatcher/#ca5d575c03c8
diff -r 000000000000 -r 83f4079b2bac RadioHead.lib --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/RadioHead.lib Sun Jun 11 04:26:22 2017 +0000 @@ -0,0 +1,1 @@ +https://developer.mbed.org/users/danjulio/code/RadioHead/#e69d086cb053
diff -r 000000000000 -r 83f4079b2bac SDFileSystem.lib --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/SDFileSystem.lib Sun Jun 11 04:26:22 2017 +0000 @@ -0,0 +1,1 @@ +https://developer.mbed.org/teams/mbed/code/SDFileSystem/#8db0d3b02cec
diff -r 000000000000 -r 83f4079b2bac StringFifo.lib --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/StringFifo.lib Sun Jun 11 04:26:22 2017 +0000 @@ -0,0 +1,1 @@ +https://developer.mbed.org/users/danjulio/code/StringFifo/#093546398fcd
diff -r 000000000000 -r 83f4079b2bac USBDevice.lib --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/USBDevice.lib Sun Jun 11 04:26:22 2017 +0000 @@ -0,0 +1,1 @@ +https://developer.mbed.org/teams/MaximIntegrated/code/USBDevice/#c5e178adb138
diff -r 000000000000 -r 83f4079b2bac USBMSD_SD.lib --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/USBMSD_SD.lib Sun Jun 11 04:26:22 2017 +0000 @@ -0,0 +1,1 @@ +https://developer.mbed.org/users/danjulio/code/USBMSD_SD/#46888c9d5cd7
diff -r 000000000000 -r 83f4079b2bac gps.lib --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gps.lib Sun Jun 11 04:26:22 2017 +0000 @@ -0,0 +1,1 @@ +https://developer.mbed.org/users/danjulio/code/gps/#8fc18502886a
diff -r 000000000000 -r 83f4079b2bac main.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/main.cpp Sun Jun 11 04:26:22 2017 +0000 @@ -0,0 +1,1315 @@ +/* + * Model Rocket Data Acquisition and Telemetry system using the MAX32630FTHR. + * - Acceleration using on-board BMI160. + * - Rotation using ALS-PT19-315C Ambient Light Sensor with analog input + * - Temperature using PT-100 RTD and MAX31865 Amplifier/Digitizer + * - GPS location and altitude using external serial receiver. Receiver + * includes local battery for faster acquisition. + * - Battery voltage using on-board MAX14690 PMIC. + * - Radio communication for telemetry and command using a RFM95W LoRa module. + * + * Data is stored as text sentences to a file on a on-board Micro-SD Card. Data + * storage is initiated via a user command prior to launch and terminated after + * a configurable delay set to bound the flight-time. Highly dynamic information + * such as acceleration and temperature is stored at 100 samples/second. + * + * The Micro-SD Card may be made available as a USB Storage Device for data off- + * load. Data is formatted as follows. + * + * File Records - one per line - terminated with CTRL-N + * ACC,<BMI160_SECS>,<X>,<Y>,<Z>,*<CHECKSUM> + * TEMP,<SEQ NUM>,<TEMP>,*<CHECKSUM> + * BATT,<SEQ NUM>,<VOLTS>,*<CHECKSUM> + * LIGHT,<SEQ NUM>,<ADC VOLTS>,*<CHECKSUM> + * GPS,<FIX>,<LATITUDE>,<LAT ORIENTATION>,<LONGITUDE>,<LON ORIENTATION>, + * <ALTITUDE>,*<CHECKSUM> + * + * <CHECKSUM> is a 2-ASCII HEX value XOR of all bytes from the first character + * up to but not including the ',*' delimiter. + * + * GPS location (and/or other sensor information) may also be transmitted + * periodically via a LoRa radio to help with rocket retrieval. + * + * A simple command shell is available via the LoRa radio and diagnostic serial + * port supporting the following commands. Commands are terminated with a + * Newline character. Command output is echoed to the same interface that it + * was received from (either LoRa or diagnostic serial). Only one interface + * should ever be used at a time. + * + * "status" : Returns device status including recording state, battery voltage, + * temperature and current GPS coordinates. + * "file <filename>" : Set the name, <filename>, for the next recording session. + * The default filename is "flight.txt". + * "dump [<filename>" : Output the contents of the current or specified + * recording file. + * "dur <seconds>" : Set the duration of the recording session in seconds. + * The default value is 120 seconds. + * "msd [on/off]" : Enable or disable USB Mass Storage. USB must be detected + * connected. + * "send [accel | batt | gps | light | temp] [on/off]" : Enable or disable + * periodic sensor logging via the current output interface. + * "record [on/off]" : Enable or disable a recording session. Default is off. + * "shutdown" : Shut down power (via the PMIC). The MAX32630FTHR will + * automatically repower immediately if USB Power is connected. + * "help" : Output this list of commands. + * + * Status response takes the form + * STATUS,<RECORDING>,<BATT VOLTS>,<TEMP>,<FIX>,<LATITUDE>,<LAT ORIENTATION>, + * <LONGITUDE>,<LON ORIENTATION>,<ALTITUDE>,*<CHECKSUM> + * + * A sign-on message is generated on both daplink and LoRa interfaces containing + * the firmware revision. + * HELLO,<VERSION_MAJOR.VERSION_MINOR>,*<CHECKSUM> + * + * A typical flight scenerio might be: + * Preflight + * 1. Power-on system + * 2. Prepare rocket for launch + * 3. Use Status command to verify rocket has GPS acquisition + * 4. Configure flight recording filename + * 5. Enable GPS logging + * 6. Enable recording + * 7. Launch rocket + * During flight + * 1. Record rough position using telemetried GPS location + * Postflight + * 1. Use telemetried GPS location to plot rocket's landing position + * 2. Use either command or direct USB location to obtain flight data from + * recording file. + * 3. Power down system + * + * System Architecture + * 1. Individual threads for sensor data aquisition. + * 2. Main thread handling ougoing communication. + * 3. Event thread handling command processing. + * 4. File writing and reading threads for data to/from Micro-SD Card. + * 5. String FIFOs used for one-way data communication between sensor + * acquisition threads and logging thread as well as between command + * response, file dump, GPS acquisition thread and communication thread. + * 6. Atomically accessible variables used for current battery voltage, + * current temperature and recording state. + * 7. USBMSD object for handling Micro-SD as USB Mass Storage Device. + * + * Various libraries have been used or ported (thank you to the authors of those + * libraries). All application code is contained in this file even though it + * should be distributed in a proper system. Note that the mbed USBMSD_SD + * library was modified NOT to attempt to "connect" during the object constructor + * since this blocks until USB is connected. Connection is initiated only by + * command and only if USB power is detected. + * + * Communication Interfaces + * 1. USB UART Used for diagnostic interface (daplink). + * 2. UART2 used for GPS interface. + * 3. I2CM2 used for BMI160 and MAX14690. + * 4. Software (bit-banged) SPI used for LoRa radio and MAX31865. This is + * because the built-in hardware SPI seems to require use of hardware + * slave-select signals and only one of those is available if I2CM2 is + * also being used. + * 5. AIN0 connected to MAX14690 MON out (battery voltage). + * 6. AIN1 connected to ALS-PT19-315C output (light sensor). + * 7. Internal 1.2V ADC ref. + * 8. GPIO inputs for MAX31865 ready and LoRa interrupt. + * 9. GPIO outputs for LoRa reset, SPI slave select, MAX31865 SPI slave select. + * 10. On-board RGB LED for system operation state indication. + * 11. GPIO output (P4_0) used for profiling during development. + * + * RGB Status LED (lowest to highest priority) + * YELLOW : System Idle, No GPS Fix + * BLUE : System Idle, GPS Fix obtained + * GREEN : System Idle, USB Detected attached + * BLUE : USB Mass Storage Enabled (USB must be attached) + * MAGENTA : System Recording + * RED : System Failure (device initialization or file open failed) + * + * + * Copyright (c) 2017 by Dan Julio. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL DAN JULIO BE LIABLE FOR ANY CLAIM, DAMAGES + * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + */ +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <math.h> +#include "mbed.h" +#include "max32630fthr.h" +#include "bmi160.h" +#include "CommandDispatcher.h" +#include "gps.h" +#include "Adafruit_MAX31865.h" +#include "RH_RF95.h" +#include "SDFileSystem.h" +#include "StringFifo.h" +#include "swspi.h" +#include "USBMSD_SD.h" + + +// +// Version +// +#define VER_MAJOR 0 +#define VER_MINOR 8 + + +// ============================================================================= +// Configuration Constants +// ============================================================================= + +// +// Length (in characters) of the FIFO used to store sensor data sentences to be +// logged in the Micro-SD card. This should be long enough to prevent any +// sensor pushing data into the FIFO from hanging until there is room and ideally +// determined by analyzing the time required to log data, the maximum flight time +// and computing the maximum data rate. +// +#define LOG_FIFO_LENGTH 16384 + +// +// Length (in characters) for the FIFO used to store GPS data sentences, command +// responses and file dump sentences to be transmitted via the LoRa radio (which +// is slow when working in the most reliable configuration) or the diagnostic +// serial interface. +// +#define RSP_FIFO_LENGTH 16384 + +// +// Time (in seconds) for default recording session. +// +#define DEF_RECORD_SECONDS 120 + +// +// Default recording file name and maximum length +// +#define DEF_RECORD_FILE "flight.txt" +#define MAX_RECORD_FN_LEN 32 + +// +// Command and sentence limits (number of chars/line) +// +#define MAX_CMD_LEN 64 +#define MAX_CMD_RSP_LEN 128 +#define MAX_SENTENCE_LEN 80 + +// +// Low-battery auto-shutdown voltage. +// +#define SHUTDOWN_VOLTS 3.0f + +// +// ADC Vref volts (this is fixed for MAX32630) +// +#define VREF 1.2f + +// +// Status LED set/clear masks (fixed) +// +#define ST_STARTUP 0x01 +#define ST_GPS_FIX 0x02 +#define ST_USB_PWR 0x04 +#define ST_RECORD 0x10 +#define ST_MSD 0x20 +#define ST_ERROR 0x80 + + + +// ============================================================================= +// Objects +// ============================================================================= + +// +// Hardware-related objects +// +MAX32630FTHR pegasus(MAX32630FTHR::VIO_3V3); + +Serial daplink(P2_1, P2_0); + +Serial gps_serial(P3_1, P3_0); +Adafruit_GPS gps(&gps_serial); + +I2C i2cBus(P5_7, P6_0); +BMI160_I2C imu(i2cBus, BMI160_I2C::I2C_ADRS_SDO_LO); + +swspi sw_spi(P5_1, P5_2, P5_0, P3_4, P5_3); // mosi, miso, sclk, ss1, ss2 +Adafruit_MAX31865 tempSense(sw_spi, 1, P3_5); // spi, ss<N>, rdyPin +RH_RF95 radio(sw_spi, 2); // spi, ss<N> +InterruptIn radioInt(P5_4); +DigitalOut radioRstN(P5_5); + +AnalogIn battSensor(AIN_0); +AnalogIn lightSensor(AIN_1); + +SDFileSystem sd(P0_5, P0_6, P0_4, P0_7, "sd"); // mosi, miso, sclk, cs +USBMSD_SD ms_sd(P0_5, P0_6, P0_4, P0_7); // mosi, miso, sclk, cs + +DigitalOut rLED(LED1); +DigitalOut gLED(LED2); +DigitalOut bLED(LED3); + +DigitalOut profOut(P4_0); + + +// +// Data objects +// +StringFifo sensorFifo(LOG_FIFO_LENGTH); +StringFifo rspFifo(RSP_FIFO_LENGTH); + +CommandDispatcher cmdDispatcher; + +EventQueue mQueue(16*EVENTS_EVENT_SIZE); + + +// +// Threads +// +Thread mQueueThread; +Thread daplinkRxThread; +Thread accelThread; +Thread battThread; +Thread gpsRxThread; +Thread gpsLogThread; +Thread lightThread; +Thread tempThread; +Thread fileWriteThread; +Thread fileReadThread; + + +// +// Sensor trigger timers +// +Ticker accelTicker; +Ticker battTicker; +Ticker gpsTicker; +Ticker lightTicker; +Ticker tempTicker; + + + +// ============================================================================= +// Global Variables +// ============================================================================= + +// +// RGB Status LED colors +// +enum LedColors +{ + BLACK, + RED, + YELLOW, + GREEN, + CYAN, + BLUE, + MAGENTA, + WHITE +}; + + +// +// Indicies into sensor-related control arrays +// +enum SensorSelect +{ + ACCEL = 0, + BATT, + GPS, + LIGHT, + TEMP +}; + + +// +// System state +// +bool enSnsLogging[5]; // Set to enable transmitting various sensor values +bool enSnsTick[5]; // Set when time for a sensor to log or record info + +bool trigRecording = false; // {command} --> {file writer} +bool trigEndRecording = false; // {command, timer} --> {file writer} +bool enRecording = false; // Set by command to note recording, cleared by writer +char curRecFileName[MAX_RECORD_FN_LEN + 1]; +float recTimeoutSecs = DEF_RECORD_SECONDS; +FILE* fp; // Use for recording or dumping file + +bool trigFileDump = false; // {command} --> {file reader} +bool enFileDump = false; // Set by command to note dumping, cleared by reader +char curReadFileName[MAX_RECORD_FN_LEN + 1]; + +bool enMassStorage = false; // Set by command when mass storage is enabled + +// +// Command and output processing +// +volatile bool cmdFromRadio = true; +char cmdString[MAX_CMD_LEN+1]; + +// +// System status +// +float curBattV = 0.0f; +float curTempC = 0.0f; +Mutex gpsMutex; +float gpsLat = 0.0f; +char gpsLatOrient = ' '; +float gpsLon = 0.0f; +char gpsLonOrient = ' '; +float gpsAlt = 0.0f; +bool gpsUpdated = false; // {GpsRx} -> {GpsLog} +bool gpsFix = false; + +bool usbPower = false; // Set while the battery thread detects ext pwr + +uint8_t ledStatus = 0x00; + + +// ============================================================================= +// Necessary forward declarations +// ============================================================================= + +void CommandEvent(); +void RecordEndEvent(); +void ShutdownEvent(); +bool InitRadio(); +void SetStatusLed(uint8_t f); +void ClearStatusLed(uint8_t f); +void UpdateStatusLed(); +float GpsNMEAtoFrac(float c); +void AddChecksum(char* s); + + + +// ============================================================================= +// Thread code +// ============================================================================= + +void daplinkRxProcess() +{ + int rxIndex = 0; + char c; + + while (1) { + // Wait for and then process incoming data + while (!daplink.readable()) { + Thread::yield(); + } + + // Process a character + c = daplink.getc(); + if (c == '\n') { + cmdString[rxIndex] = 0; + rxIndex = 0; + cmdFromRadio = false; + mQueue.call(CommandEvent); + } else if ((c != '\r') && (rxIndex < MAX_CMD_LEN)) { + cmdString[rxIndex++] = c; + } + } +} + + +void AccelProcess() +{ + BMI160::AccConfig accConfig; + BMI160::SensorData accData; + BMI160::SensorTime sensorTime; + char accelString[MAX_SENTENCE_LEN]; + uint32_t seqNum = 0; + bool initSuccess = true; // Will be set false if necessary + + // Setup accelerometer configuration + accConfig.range = BMI160::SENS_16G; + accConfig.us = BMI160::ACC_US_OFF; + accConfig.bwp = BMI160::ACC_BWP_2; + accConfig.odr = BMI160::ACC_ODR_8; + + // Initialize + if (imu.setSensorPowerMode(BMI160::GYRO, BMI160::NORMAL) != BMI160::RTN_NO_ERROR) { + daplink.printf("Failed to set gyro power mode\n\r"); + initSuccess = false; + } + wait_ms(100); + if (imu.setSensorPowerMode(BMI160::ACC, BMI160::NORMAL) != BMI160::RTN_NO_ERROR) { + daplink.printf("Failed to set accelerometer power mode\n\r"); + initSuccess = false; + } + wait_ms(100); + if (initSuccess){ + if (imu.setSensorConfig(accConfig) != BMI160::RTN_NO_ERROR) { + daplink.printf("Failed to reconfigure accelerometer\n\r"); + initSuccess = false; + } + } + + if (!initSuccess) { + SetStatusLed(ST_ERROR); + } else { + // Main loop + while (1) { + // Wait to be triggered + while (!enSnsTick[ACCEL]) { + Thread::yield(); + } + enSnsTick[ACCEL] = false; + + // Read accelerometer + (void) imu.getSensorXYZandSensorTime(accData, sensorTime, accConfig.range); + + // Send for logging + sprintf(accelString, "ACC,%f,%f,%f,%f", sensorTime.seconds, + accData.xAxis.scaled, accData.yAxis.scaled, accData.zAxis.scaled); + AddChecksum(accelString); + + if (enRecording) { + sensorFifo.PushString(accelString); + } + + if ((enSnsLogging[ACCEL] && (seqNum++ % 100) == 0)) { + // Only send to our user occasionally + rspFifo.PushString(accelString); + } + } + } +} + + +void BattProcess() +{ + char battString[MAX_SENTENCE_LEN]; + uint32_t seqNum = 0; + char regVal; + + // Initialize - setup the monitor input for 1:4 scaling to fit our + // ADC's fullscale range + pegasus.max14690.monSet(pegasus.max14690.MON_BAT, pegasus.max14690.MON_DIV4); + + while (1) { + // Wait to be triggered + while (!enSnsTick[BATT]) { + Thread::yield(); + } + enSnsTick[BATT] = false; + + // Read battery voltage and check for shutdown condition + curBattV = battSensor.read()*4.0f*VREF; + + if (curBattV < SHUTDOWN_VOLTS) { + // Attempt to tell the world we're shutting down + rspFifo.PushString("Low Battery Shutdown"); + + // Schedule shutdown + mQueue.call(ShutdownEvent); + } + + // Send for logging + sprintf(battString, "BATT,%d,%1.2f", seqNum++, curBattV); + AddChecksum(battString); + + + if (enRecording) { + sensorFifo.PushString(battString); + } + + if (enSnsLogging[BATT]) { + rspFifo.PushString(battString); + } + + // Read the MAX14690 StatusB register bit[3] UsbOk to determine + // if we are connected to a USB system or not + if (pegasus.max14690.readReg(MAX14690::REG_STATUS_B, ®Val) == MAX14690_NO_ERROR) { + if (regVal & 0x08) { + if (!usbPower) { + usbPower = true; + SetStatusLed(ST_USB_PWR); + } + } else { + if (usbPower) { + usbPower = false; + ClearStatusLed(ST_USB_PWR); + } + } + } + } +} + + +void GpsRxProcess() +{ + // Initialize + gps.begin(9600); + + while (1) { + // Read complete lines from the GPS receiver (blocks until data avail) + gps.readNMEA(); + + // Process any received NMEA sentences + if (gps.newNMEAreceived()) { + if (gps.parse(gps.lastNMEA())) { + if (gps.fix) { + // Update our status + gpsMutex.lock(); + gpsLat = GpsNMEAtoFrac(gps.latitude); + gpsLatOrient = gps.lat; + gpsLon = GpsNMEAtoFrac(gps.longitude); + gpsLonOrient = gps.lon; + gpsAlt = gps.altitude; + gpsUpdated = true; + gpsMutex.unlock(); + } + } + } + } +} + + +void GpsLogProcess() +{ + char gpsString[MAX_SENTENCE_LEN]; + int gpsMissedUpdateCount = 0; + + while (1) { + // Wait to be triggered + while (!enSnsTick[GPS]) { + Thread::yield(); + } + enSnsTick[GPS] = false; + + // Evaluate fix detecton logic + if (gpsUpdated) { + gpsUpdated = false; + gpsMissedUpdateCount = 0; + if (!gpsFix) { + gpsFix = true; + SetStatusLed(ST_GPS_FIX); + } + } else { + if (++gpsMissedUpdateCount > 5) { + if (gpsFix) { + gpsFix = false; + ClearStatusLed(ST_GPS_FIX); + } + } + } + + // Send for logging + gpsMutex.lock(); + sprintf(gpsString, "GPS,%d,%1.6f,%c,%1.6f,%c,%1.1f", + gpsFix, gpsLat, gpsLatOrient, gpsLon, gpsLonOrient, gps.altitude); + gpsMutex.unlock(); + AddChecksum(gpsString); + + if (enRecording) { + sensorFifo.PushString(gpsString); + } + + if (enSnsLogging[GPS]) { + rspFifo.PushString(gpsString); + } + } +} + + +void LightProcess() +{ + char lightString[MAX_SENTENCE_LEN]; + uint32_t seqNum = 0; + float lightV; + + while (1) { + // Wait to be triggered + while (!enSnsTick[LIGHT]) { + Thread::yield(); + } + enSnsTick[LIGHT] = false; + + // Read light sensor voltage + lightV = lightSensor.read()*VREF; + + // Send for logging + sprintf(lightString, "LIGHT,%d,%1.2f", seqNum++, lightV); + AddChecksum(lightString); + + if (enRecording) { + sensorFifo.PushString(lightString); + } + + if ((enSnsLogging[LIGHT] && (seqNum % 100) == 0)) { + // Only send to our user occasionally + rspFifo.PushString(lightString); + } + } +} + + +void TempProcess() +{ + char tempString[MAX_SENTENCE_LEN]; + uint32_t seqNum = 0; + uint16_t rtd; + + // Initialize temperature sensor for continuous sensing + tempSense.begin(); + tempSense.enableBias(true); + tempSense.autoConvert(true); + + while (1) { + // Wait to be triggered + while (!enSnsTick[TEMP]) { + Thread::yield(); + } + enSnsTick[TEMP] = false; + + // Get the temperature if available + if (tempSense.isReady(&rtd)) { + // Using 100 ohm RTD and 430 ohm reference resistor + curTempC = tempSense.temperature(100.0f, 430.0f, rtd); + + // Send for logging + sprintf(tempString, "TEMP,%d,%1.2f", seqNum++, curTempC); + AddChecksum(tempString); + + if (enRecording) { + sensorFifo.PushString(tempString); + } + + if ((enSnsLogging[TEMP] && (seqNum % 10) == 0)) { + // Only send to our user occasionally + rspFifo.PushString(tempString); + } + } + } +} + + +void FileWriteProcess() +{ + char fullRecFileName[MAX_RECORD_FN_LEN + 5]; + char logString[MAX_SENTENCE_LEN]; + + while (1) { + // Each time through before possibly blocking on the fifo, check + // if we need to close the file after recording finishes + if (trigEndRecording && enRecording) { + enRecording = false; + trigEndRecording = false; + fclose(fp); + ClearStatusLed(ST_RECORD); + } + + // Look for sensor data + sensorFifo.PopString(logString); + + // See if we need to start recording + if (trigRecording) { + trigRecording = false; + // Attempt to open the file for writing + strcpy(fullRecFileName, "/sd/"); + strcpy(&fullRecFileName[4], curRecFileName); + fp = fopen(fullRecFileName, "w"); + if (fp == 0) { + enRecording = false; + sprintf(logString, "Cannot open %s, errno = %d", fullRecFileName, errno); + rspFifo.PushString(logString); + SetStatusLed(ST_ERROR); + } else { + mQueue.call_in(recTimeoutSecs*1000, RecordEndEvent); + ClearStatusLed(ST_ERROR); // Just in case they screwed up the filename earlier + SetStatusLed(ST_RECORD); + } + } + + // Write to file if we are logging (Unix style) + if (enRecording) { + profOut = 1; + fprintf(fp, "%s\n", logString); + profOut = 0; + Thread::yield(); // Allow other threads to run too + } + } +} + + +void FileReadProcess() +{ + char fullReadFileName[MAX_RECORD_FN_LEN + 5]; + char curLine[MAX_CMD_RSP_LEN + 1]; + char c; + int i; + + while (1) { + // Wait for a trigger to start dumping a file + while (!trigFileDump) { + Thread::yield(); + } + trigFileDump = false; + + // Attempt to open the file for reading + strcpy(fullReadFileName, "/sd/"); + strcpy(&fullReadFileName[4], curReadFileName); + fp = fopen(fullReadFileName, "r"); + if (fp == 0) { + sprintf(curLine, "Cannot open %s: errno %d", fullReadFileName, errno); + rspFifo.PushString(curLine); + } else { + // Dump file one line at a time, assuming Unix <LF> line endings + // but skipping <CR> if they occur. They can stop us by clearing + // enFileDump (although we may have buffered a lot of data in + // rspFifo). + i = 0; + while (!feof(fp) && enFileDump) { + c = getc(fp); + if (c == '\n') { + // End of line + curLine[i] = 0; + i = 0; + rspFifo.PushString(curLine); + Thread::yield(); // Allow other activity while dumping + } else if (c != '\r') { + if (i < MAX_CMD_RSP_LEN) { + curLine[i++] = c; + } + } + } + if (i != 0) { + // Push any final, unterminated lines + rspFifo.PushString(curLine); + } + rspFifo.PushString("END"); + fclose(fp); + } + enFileDump = false; + } +} + + + +// ============================================================================= +// Command handlers +// ============================================================================= + +void statusCommand(unsigned int argc, char* argv[], char* result) +{ + gpsMutex.lock(); + sprintf(result, "STATUS,%d,%1.2f,%1.2f,%d,%1.6f,%c,%1.6f,%c,%1.1f", + enRecording, curBattV, curTempC, gpsFix, gpsLat, gpsLatOrient, gpsLon, + gpsLonOrient, gpsAlt); + gpsMutex.unlock(); + AddChecksum(result); +} + + +void fileCommand(unsigned int argc, char* argv[], char* result) +{ + if (argc >= 2) { + strcpy(curRecFileName, argv[1]); + sprintf(result, "Filename = %s", curRecFileName); + } else { + sprintf(result, "Must also specify filename"); + } +} + + +void dumpCommand(unsigned int argc, char* argv[], char* result) +{ + if (enRecording == true) { + sprintf(result, "Cannot dump while recording!"); + } else { + if (!enFileDump) { + if (argc >= 2) { + // They provided a specific file to read + strcpy(curReadFileName, argv[1]); + } else { + // Use the existing record file name + strcpy(curReadFileName, curRecFileName); + } + enFileDump = true; // Allow sensors to start filling sensorFifo + trigFileDump = true; + } + sprintf(result, ""); + } +} + + +void durCommand(unsigned int argc, char* argv[], char* result) +{ + float f; + + if (argc >= 2) { + if ((f = atof(argv[1])) != 0.0f) { + recTimeoutSecs = f; + } + sprintf(result, "Recording timeout = %f seconds", recTimeoutSecs); + } else { + sprintf(result, "Must also specify timeout"); + } +} + + +void massStorageCommand(unsigned int argc, char* argv[], char* result) +{ + if (argc >= 2) { + if (strcmp(argv[1], "on") == 0) { + if (!enMassStorage) { + if (!usbPower) { + sprintf(result, "Cannot enable USB Mass storage with no USB"); + } else { + if (ms_sd.connect(true)) { + enMassStorage = true; + sprintf(result, "Mass Storage Enabled"); + SetStatusLed(ST_MSD); + } else { + sprintf(result, "Mass Storage Not Enabled!"); + } + } + } else { + sprintf(result, "Mass Storage already Enabled!"); + } + } else { + if (enMassStorage) { + ms_sd.disconnect(); + enMassStorage = false; + ClearStatusLed(ST_MSD); + sprintf(result, "Mass Storage Disabled"); + } else { + sprintf(result, "Mass Storage already Disabled"); + } + } + } else { + sprintf(result, "Must also specify on/off"); + } +} + + +void sendCommand(unsigned int argc, char* argv[], char* result) +{ + bool en; + bool valid = true; + + if (argc >= 3) { + if (strcmp(argv[2], "on") == 0) { + en = true; + } else { + en = false; + } + + if (strcmp(argv[1], "accel") == 0) { + enSnsLogging[ACCEL] = en; + } else if (strcmp(argv[1], "batt") == 0) { + enSnsLogging[BATT] = en; + } else if (strcmp(argv[1], "gps") == 0) { + enSnsLogging[GPS] = en; + } else if (strcmp(argv[1], "light") == 0) { + enSnsLogging[LIGHT] = en; + } else if (strcmp(argv[1], "temp") == 0) { + enSnsLogging[TEMP] = en; + } else { + valid = false; + sprintf(result, "Unknown sensor: %s", argv[1]); + } + + if (valid) { + sprintf(result, "%s Sending = %d", argv[1], en); + } + } else { + sprintf(result, "Must specify sensor and on/off"); + } +} + + +void recordCommand(unsigned int argc, char* argv[], char* result) +{ + if (enFileDump) { + sprintf(result, ""); // Silently ignore command while dumping + } else if (argc >= 2) { + if (strcmp(argv[1], "on") == 0) { + if (!enRecording) { + enRecording = true; // Allow sensors to start feeding writer + trigRecording = true; + sprintf(result, "Setup to record to %s", curRecFileName); + } else { + sprintf(result, "Already recording"); + } + } else { + if (enRecording) { + trigEndRecording = true; + sprintf(result, "Stopping recording"); + } else { + sprintf(result, "Recording already stopped"); + } + } + } else { + sprintf(result, "Must also specify on/off"); + } +} + + +void shutdownCommand(unsigned int argc, char* argv[], char* result) +{ + // Schedule shutdown + mQueue.call(ShutdownEvent); + + sprintf(result, "Shutdown"); +} + + +void helpCommand(unsigned int argc, char* argv[], char* result) +{ + // Empty command response + sprintf(result, ""); + + rspFifo.PushString("Commands: "); + rspFifo.PushString(" status"); + rspFifo.PushString(" file <filename>"); + rspFifo.PushString(" dump [<filename>"); + rspFifo.PushString(" dur <seconds>"); + rspFifo.PushString(" msd [on/off]"); + rspFifo.PushString(" send [accel | batt | gps | light | temp] [on/off]"); + rspFifo.PushString(" record [on/off]"); + rspFifo.PushString(" shutdown"); + rspFifo.PushString(" help"); +} + + + +// ============================================================================= +// Event Handlers +// ============================================================================= + +/* +// NOTE: This function not used because attempting to catch daplink RX interrupts +// hangs the system --> So I'm falling back to polling in another thread... +void DaplinkRxIsr() +{ + static int rxIndex = 0; + bool sawTerm = false; + char c; + + // Process all incoming received data and see if we can process a command + while (daplink.readable() && !sawTerm) { + c = daplink.getc(); + if (c == '\n') { + cmdString[rxIndex] = 0; + sawTerm = true; + rxIndex = 0; + } else if (rxIndex < MAX_CMD_LEN) { + cmdString[rxIndex++] = c; + } + } + + if (sawTerm) { + cmdFromRadio = false; + mQueue.call(CommandEvent); + } + return; +} +*/ + + +void RadioIsrEvent() +{ + uint8_t rxLen; + + // Process the Radio's Interrupt Handler - we do it in an event instead + // of an ISR because it may access the swspi and the current mbed + // environment won't correctly execute the mutex's protecting the swspi + // access routines (which are shared with code in other threads) in an ISR. + radio.handleInterrupt(); + + // See if we received a packet and process it if necessary + if (radio.available()) { + rxLen = MAX_CMD_LEN; + if (radio.recv((uint8_t *) cmdString, &rxLen)) { + cmdString[rxLen+1] = 0; + cmdFromRadio = true; + mQueue.call(CommandEvent); + } + } +} + + +void CommandEvent() +{ + char rspString[MAX_CMD_RSP_LEN+1]; + + // Process the received command + if (cmdDispatcher.executeCommand(cmdString, rspString)) { + // Push response + rspFifo.PushString(rspString); + } +} + + +void RecordEndEvent() +{ + if (enRecording) { + trigEndRecording = true; + rspFifo.PushString("Finishing Recording"); + } +} + + +void ShutdownEvent() +{ + // Stop recording if necessary + if (enRecording) { + trigEndRecording = true; + } + + // Wait a moment to let any other threads clean up + wait_ms(100); + + // Power down + pegasus.max14690.shutdown(); +} + + +void AccelTick() +{ + enSnsTick[ACCEL] = true; +} + +void BattTick() +{ + enSnsTick[BATT] = true; +} + +void GpsTick() +{ + enSnsTick[GPS] = true; +} + +void LightTick() +{ + enSnsTick[LIGHT] = true; +} + +void TempTick() +{ + enSnsTick[TEMP] = true; +} + + + +// ============================================================================= +// Support subroutines +// ============================================================================= + +void InitSystem() +{ + // Indicate startup + SetStatusLed(ST_STARTUP); + + // Misc global object initialization + daplink.baud(115200); + sw_spi.init(); + i2cBus.frequency(400000); + + // Setup the Radio + if (!InitRadio()) { + daplink.printf("Radio Init failed\n\r"); + SetStatusLed(ST_ERROR); + } + + // Misc global variable initialization + strcpy(curRecFileName, DEF_RECORD_FILE); + for (int i=0; i<5; i++) { + enSnsLogging[i] = false; + enSnsTick[i] = false; + } + + // Commands + cmdDispatcher.addCommand("status", statusCommand); + cmdDispatcher.addCommand("file", fileCommand); + cmdDispatcher.addCommand("dump", dumpCommand); + cmdDispatcher.addCommand("dur", durCommand); + cmdDispatcher.addCommand("msd", massStorageCommand); + cmdDispatcher.addCommand("send", sendCommand); + cmdDispatcher.addCommand("record", recordCommand); + cmdDispatcher.addCommand("shutdown", shutdownCommand); + cmdDispatcher.addCommand("help", helpCommand); + + // Setup events + //daplink.attach(&DaplinkRxIsr, Serial::RxIrq); // NOTE: This hangs the system! + radioInt.rise(mQueue.event(RadioIsrEvent)); + + // Start threads + daplinkRxThread.start(callback(daplinkRxProcess)); + accelThread.start(callback(AccelProcess)); + battThread.start(callback(BattProcess)); + gpsRxThread.start(callback(GpsRxProcess)); + gpsLogThread.start(callback(GpsLogProcess)); + lightThread.start(callback(LightProcess)); + tempThread.start(callback(TempProcess)); + fileWriteThread.start(callback(FileWriteProcess)); + fileReadThread.start(callback(FileReadProcess)); + mQueueThread.start(callback(&mQueue, &EventQueue::dispatch_forever)); + + // Start sensor trigger timers (this sets the frequency for each sensor's data + // collection) + accelTicker.attach(&AccelTick, 0.01f); + battTicker.attach(&BattTick, 5.0f); + gpsTicker.attach(&GpsTick, 1.0f); + lightTicker.attach(&LightTick, 0.01f); + tempTicker.attach(&TempTick, 0.1f); +} + + +bool InitRadio() +{ + // Reset radio + radioRstN = 0; + wait_ms(10); + radioRstN = 1; + + // Attempt to initialize it + if (!radio.init()) { + return(false); + } + + // Configure + radio.setFrequency(915.0); + radio.setTxPower(20); + + return(true); +} + + +void SetStatusLed(uint8_t f) +{ + ledStatus |= f; + + UpdateStatusLed(); +} + + +void ClearStatusLed(uint8_t f) +{ + ledStatus &= ~f; + + UpdateStatusLed(); +} + + +void UpdateStatusLed() +{ + LedColors c; + + // Priority Encoder + if (ledStatus & ST_ERROR) c = RED; + else if (ledStatus & ST_MSD) c = BLUE; + else if (ledStatus & ST_RECORD) c = MAGENTA; + else if (ledStatus & ST_USB_PWR) c = GREEN; + else if (ledStatus & ST_GPS_FIX) c = CYAN; + else if (ledStatus & ST_STARTUP) c = YELLOW; + else c = BLACK; + + // Hardware control + switch (c) { + case BLACK: + rLED = 1; gLED = 1; bLED = 1; + break; + case RED: + rLED = 0; gLED = 1; bLED = 1; + break; + case YELLOW: + rLED = 0; gLED = 0; bLED = 1; + break; + case GREEN: + rLED = 1; gLED = 0; bLED = 1; + break; + case CYAN: + rLED = 1; gLED = 0; bLED = 0; + break; + case BLUE: + rLED = 1; gLED = 1; bLED = 0; + break; + case MAGENTA: + rLED = 0; gLED = 1; bLED = 0; + break; + case WHITE: + rLED = 0; gLED = 0; bLED = 0; + break; + } +} + + +void Signon() +{ + char msg[20]; + + sprintf(msg, "HELLO,%d.%d", VER_MAJOR, VER_MINOR); + AddChecksum(msg); + daplink.printf("%s\n\r", msg); + radio.send((uint8_t *) msg, sizeof(msg)); +} + + +float GpsNMEAtoFrac(float c) +{ + float intPart; + float fracPart; + + // Get the integer part of the coordinate + intPart = floor(c / 100.0f); + + // Convert the fractional part into 60ths + fracPart = c - (intPart * 100.0f); // Get rid of the integer part + fracPart = fracPart / 60.0f; + + return(intPart + fracPart); +} + + +void AddChecksum(char* s) +{ + uint8_t c = 0; + int i = 0; + + while (*(s+i) != 0) { + c ^= *(s+i++); + } + + sprintf(s+i, ",*%X", c); +} + + + +// ============================================================================= +// Main +// ============================================================================= +int main() +{ + char outString[MAX_CMD_RSP_LEN+1]; + + InitSystem(); + Signon(); + + while (1) { + // Look for data to pass back to our user + rspFifo.PopString(outString); + + if (cmdFromRadio) { + radio.send((uint8_t *) outString, strlen(outString)); + } else { + while (!daplink.writeable()) { + Thread::yield(); + } + daplink.printf("%s\n", outString); + } + } +}
diff -r 000000000000 -r 83f4079b2bac max31865.lib --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/max31865.lib Sun Jun 11 04:26:22 2017 +0000 @@ -0,0 +1,1 @@ +https://developer.mbed.org/users/danjulio/code/max31865/#9294dd756a0c
diff -r 000000000000 -r 83f4079b2bac max32630fthr.lib --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/max32630fthr.lib Sun Jun 11 04:26:22 2017 +0000 @@ -0,0 +1,1 @@ +http://developer.mbed.org/teams/MaximIntegrated/code/max32630fthr/#60997adf01a2
diff -r 000000000000 -r 83f4079b2bac mbed-os.lib --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mbed-os.lib Sun Jun 11 04:26:22 2017 +0000 @@ -0,0 +1,1 @@ +https://github.com/ARMmbed/mbed-os/#269f58d75b752a4e67a6a2d8c5c698635ffd6752
diff -r 000000000000 -r 83f4079b2bac swspi.lib --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/swspi.lib Sun Jun 11 04:26:22 2017 +0000 @@ -0,0 +1,1 @@ +https://developer.mbed.org/users/danjulio/code/swspi/#8a4db0f083fc