diff --git a/Binaries/OpenLog_Artemis-V10-v111.bin b/Binaries/OpenLog_Artemis-V10-v111.bin new file mode 100644 index 0000000..d4726b6 Binary files /dev/null and b/Binaries/OpenLog_Artemis-V10-v111.bin differ diff --git a/Binaries/OpenLog_Artemis-X04-v111.bin b/Binaries/OpenLog_Artemis-X04-v111.bin new file mode 100644 index 0000000..8216c37 Binary files /dev/null and b/Binaries/OpenLog_Artemis-X04-v111.bin differ diff --git a/CHANGELOG.md b/CHANGELOG.md index 41c2d1c..02027e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,25 @@ Change Log ====================== +v1.11 +--------- + +* Adds support for the ICM-20948 Digital Motion Processor [47](https://github.com/sparkfun/OpenLog_Artemis/issues/47) + * The OLA's orientation can be logged as a 6-axis or 9-axis Quaternion. Note: only Quat9 provides absolute orientation - using the magnetometer. Quat6 uses the accel and gyro only + * Open Menu 3 and select option 12 to enable the DMP +* Adds support for exFAT microSD cards [34](https://github.com/sparkfun/OpenLog_Artemis/issues/34) + * Based on v2.0.6 of Bill Greiman's SdFat library +* Adds a minimum awake time, making it easier to open the Serial menu when the OLA is sleeping between measurements [83](https://github.com/sparkfun/OpenLog_Artemis/issues/83) +* Adds support for the Bio Sensor Hub Pulse Oximeter and Heart Rate Sensor [81](https://github.com/sparkfun/OpenLog_Artemis/issues/81) + * Requires exclusive use of pins 32 and 11 + * Open Menu 6 (Detect / Configure Attached Devices) to enable Oximeter detection +* Adds stand-alone examples for: + * ICM-20948 DMP (orientation in Quat6 and Quat9) + * GNSS RAWX logging + * GNSS TIM-TM2 logging +* Does not add support for the Qwiic Button [81](https://github.com/sparkfun/OpenLog_Artemis/issues/81) + * We tried to add support for the QB, but it uses I2C clock stretching and causes all kinds of badness with the Artemis + v1.10 --------- diff --git a/Firmware/OpenLog_Artemis/OpenLog_Artemis.ino b/Firmware/OpenLog_Artemis/OpenLog_Artemis.ino index 42cb8de..1248e70 100644 --- a/Firmware/OpenLog_Artemis/OpenLog_Artemis.ino +++ b/Firmware/OpenLog_Artemis/OpenLog_Artemis.ino @@ -55,7 +55,7 @@ (done) Use the WDT to reset the Artemis when power is reconnected (previously the Artemis would have stayed in deep sleep) Add a callback function to the u-blox library so we can abort waiting for UBX data if the power goes low (done) Add support for the ADS122C04 ADC (Qwiic PT100) - Investigate why usBetweenReadings appears to be ~0.8s longer than expected + (done) Investigate why usBetweenReadings appears to be longer than expected. We needed to read millis _before_ enabling the lower power clock! (done) Correct u-blox pull-ups (done) Add an olaIdentifier to prevent problems when using two code variants that have the same sizeOfSettings (done) Add a fix for the IMU wake-up issue identified in https://github.com/sparkfun/OpenLog_Artemis/issues/18 @@ -88,10 +88,16 @@ (done) Add support for the MS5837 - as used in the BlueRobotics BAR02 and BAR30 water pressure sensors (done) Correct an issue which was causing the OLA to crash when waking from sleep and outputting serial data https://github.com/sparkfun/OpenLog_Artemis/issues/79 (done) Correct low-power code as per https://github.com/sparkfun/OpenLog_Artemis/issues/78 + (done) Correct a bug in menuAttachedDevices when useTxRxPinsForTerminal is enabled https://github.com/sparkfun/OpenLog_Artemis/issues/82 + (done) Add ICM-20948 DMP support. Requires v1.2.6 of the ICM-20948 library. DMP logging is limited to: Quat6 or Quat9, plus raw accel, gyro and compass. https://github.com/sparkfun/OpenLog_Artemis/issues/47 + (done) Add support for exFAT. Requires v2.0.6 of Bill Greiman's SdFat library. https://github.com/sparkfun/OpenLog_Artemis/issues/34 + (done) Add minimum awake time: https://github.com/sparkfun/OpenLog_Artemis/issues/83 + (done) Add support for the Pulse Oximeter: https://github.com/sparkfun/OpenLog_Artemis/issues/81 + (won't do?) Add support for the Qwiic Button. The QB uses clock-stretching and the Artemis really doesn't enjoy that... */ const int FIRMWARE_VERSION_MAJOR = 1; -const int FIRMWARE_VERSION_MINOR = 10; +const int FIRMWARE_VERSION_MINOR = 11; //Define the OLA board identifier: // This is an int which is unique to this variant of the OLA and which allows us @@ -101,7 +107,7 @@ const int FIRMWARE_VERSION_MINOR = 10; // the variant * 0x100 (OLA = 1; GNSS_LOGGER = 2; GEOPHONE_LOGGER = 3) // the major firmware version * 0x10 // the minor firmware version -#define OLA_IDENTIFIER 0x11A // Stored as 282 decimal in OLA_settings.txt +#define OLA_IDENTIFIER 0x11B // Stored as 283 decimal in OLA_settings.txt #include "settings.h" @@ -163,10 +169,30 @@ TwoWire qwiic(1); //Will use pads 8/9 //microSD Interface //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- #include -#include //SdFat (FAT32) by Bill Greiman: http://librarymanager/All#SdFat + +#include //SdFat v2.0.6 by Bill Greiman: http://librarymanager/All#SdFat_exFAT + +#define SD_FAT_TYPE 3 // SD_FAT_TYPE = 0 for SdFat/File, 1 for FAT16/FAT32, 2 for exFAT, 3 for FAT16/FAT32 and exFAT. +#define SD_CONFIG SdSpiConfig(PIN_MICROSD_CHIP_SELECT, SHARED_SPI, SD_SCK_MHZ(24)) // 24MHz + +#if SD_FAT_TYPE == 1 +SdFat32 sd; +File32 sensorDataFile; //File that all sensor data is written to +File32 serialDataFile; //File that all incoming serial data is written to +#elif SD_FAT_TYPE == 2 +SdExFat sd; +ExFile sensorDataFile; //File that all sensor data is written to +ExFile serialDataFile; //File that all incoming serial data is written to +#elif SD_FAT_TYPE == 3 +SdFs sd; +FsFile sensorDataFile; //File that all sensor data is written to +FsFile serialDataFile; //File that all incoming serial data is written to +#else // SD_FAT_TYPE == 0 SdFat sd; -SdFile sensorDataFile; //File that all sensor data is written to -SdFile serialDataFile; //File that all incoming serial data is written to +File sensorDataFile; //File that all sensor data is written to +File serialDataFile; //File that all incoming serial data is written to +#endif // SD_FAT_TYPE + //#define PRINT_LAST_WRITE_TIME // Uncomment this line to enable the 'measure the time between writes' diagnostic char sensorDataFileName[30] = ""; //We keep a record of this file name so that we can re-open it upon wakeup from sleep @@ -195,6 +221,7 @@ int charsReceived = 0; //Used for verifying/debugging serial reception //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- #include "ICM_20948.h" // Click here to get the library: http://librarymanager/All#SparkFun_ICM_20948_IMU ICM_20948_SPI myICM; +icm_20948_DMP_data_t dmpData; // Global storage for the DMP data - extracted from the FIFO //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- //Header files for all compatible Qwiic sensors @@ -223,6 +250,8 @@ ICM_20948_SPI myICM; #include "SparkFun_SGP40_Arduino_Library.h" // Click here to get the library: http://librarymanager/All#SparkFun_SGP40 #include "SparkFun_SDP3x_Arduino_Library.h" // Click here to get the library: http://librarymanager/All#SparkFun_SDP3x #include "MS5837.h" // Click here to download the library: https://github.com/sparkfunX/BlueRobotics_MS5837_Library +//#include "SparkFun_Qwiic_Button.h" // Click here to get the library: http://librarymanager/All#SparkFun_Qwiic_Button_Switch +#include "SparkFun_Bio_Sensor_Hub_Library.h" // Click here to get the library: http://librarymanager/All#SparkFun_Bio_Sensor //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- @@ -237,6 +266,7 @@ unsigned long lastReadTime = 0; //Used to delay until user wants to record a new unsigned long lastDataLogSyncTime = 0; //Used to record to SD every half second unsigned int totalCharactersPrinted = 0; //Limit output rate based on baud rate and number of characters to print bool takeReading = true; //Goes true when enough time has passed between readings or we've woken from sleep +bool sleepAfterRead = false; //Used to keep the code awake for at least minimumAwakeTimeMillis const uint64_t maxUsBeforeSleep = 2000000ULL; //Number of us between readings before sleep is activated. const byte menuTimeout = 15; //Menus will exit/timeout after this number of seconds volatile static bool stopLoggingSeen = false; //Flag to indicate if we should stop logging @@ -639,12 +669,27 @@ void loop() { triggerEdgeSeen = false; // Clear the trigger seen flag here - just in case another trigger was received while we were logging data to SD card - // Code changes here are based on suggestions by @ryanneve in Issue #46 and PR #64 + // Code changes here are based on suggestions by @ryanneve in Issue #46, PR #64 and Issue #83 if (checkIfItIsTimeToSleep()) { - goToSleep(howLongToSleepFor()); + sleepAfterRead = true; } } + + if (sleepAfterRead == true) + { + // Check if we should stay awake because settings.minimumAwakeTimeMillis is non-zero + if ((settings.usBetweenReadings >= maxUsBeforeSleep) && (settings.minimumAwakeTimeMillis > 0)) + { + // Check if we have been awake long enough (millis is reset to zero when waking from sleep) + // goToSleep will automatically compensate for how long we have been awake + if (millis() < settings.minimumAwakeTimeMillis) + return; // Too early to sleep - leave sleepAfterRead set true + } + + sleepAfterRead = false; + goToSleep(howLongToSleepFor()); + } } uint32_t howLongToSleepFor(void) @@ -653,6 +698,8 @@ uint32_t howLongToSleepFor(void) //Calculate how many 32768Hz system ticks we need to sleep for: //sysTicksToSleep = msToSleep * 32768L / 1000 //We need to be careful with the multiply as we will overflow uint32_t if msToSleep is > 131072 + + //goToSleep will automatically compensate for how long we have been awake uint32_t msToSleep; @@ -680,7 +727,9 @@ uint32_t howLongToSleepFor(void) msToSleep = (secondsUntilStop + 1) * 1000UL; // Adjust msToSleep, adding one extra second to make sure the next wake is > slowLoggingStop } else // checkSleepOnUsBetweenReadings - msToSleep = (uint32_t)(settings.usBetweenReadings / 1000ULL); + { + msToSleep = (uint32_t)(settings.usBetweenReadings / 1000ULL); // Sleep for usBetweenReadings + } uint32_t sysTicksToSleep; if (msToSleep < 131000) @@ -784,7 +833,7 @@ void beginSD() delay(1); } - if (sd.begin(PIN_MICROSD_CHIP_SELECT, SD_SCK_MHZ(24)) == false) //Standard SdFat + if (sd.begin(SD_CONFIG) == false) // Try to begin the SD card using the correct chip select { printDebug(F("SD init failed (first attempt). Trying again...\r\n")); for (int i = 0; i < 250; i++) //Give SD more time to power up, then try again @@ -792,9 +841,10 @@ void beginSD() checkBattery(); delay(1); } - if (sd.begin(PIN_MICROSD_CHIP_SELECT, SD_SCK_MHZ(24)) == false) //Standard SdFat + if (sd.begin(SD_CONFIG) == false) // Try to begin the SD card using the correct chip select { SerialPrintln(F("SD init failed (second attempt). Is card present? Formatted?")); + SerialPrintln(F("Please ensure the SD card is formatted correctly using https://www.sdcard.org/downloads/formatter/")); digitalWrite(PIN_MICROSD_CHIP_SELECT, HIGH); //Be sure SD is deselected online.microSD = false; return; @@ -896,35 +946,183 @@ void beginIMU() delay(1); } - //Update the full scale and DLPF settings - ICM_20948_Status_e retval = myICM.enableDLPF(ICM_20948_Internal_Acc, settings.imuAccDLPF); - if (retval != ICM_20948_Stat_Ok) + bool success = true; + + //Check if we are using the DMP + if (settings.imuUseDMP == false) { - SerialPrintln(F("Error: Could not configure the IMU Accelerometer DLPF!")); + //Perform a full startup (not minimal) for non-DMP mode + ICM_20948_Status_e retval = myICM.startupDefault(false); + if (retval != ICM_20948_Stat_Ok) + { + SerialPrintln(F("Error: Could not startup the IMU in non-DMP mode!")); + success = false; + } + //Update the full scale and DLPF settings + retval = myICM.enableDLPF(ICM_20948_Internal_Acc, settings.imuAccDLPF); + if (retval != ICM_20948_Stat_Ok) + { + SerialPrintln(F("Error: Could not configure the IMU Accelerometer DLPF!")); + success = false; + } + retval = myICM.enableDLPF(ICM_20948_Internal_Gyr, settings.imuGyroDLPF); + if (retval != ICM_20948_Stat_Ok) + { + SerialPrintln(F("Error: Could not configure the IMU Gyro DLPF!")); + success = false; + } + ICM_20948_dlpcfg_t dlpcfg; + dlpcfg.a = settings.imuAccDLPFBW; + dlpcfg.g = settings.imuGyroDLPFBW; + retval = myICM.setDLPFcfg((ICM_20948_Internal_Acc | ICM_20948_Internal_Gyr), dlpcfg); + if (retval != ICM_20948_Stat_Ok) + { + SerialPrintln(F("Error: Could not configure the IMU DLPF BW!")); + success = false; + } + ICM_20948_fss_t FSS; + FSS.a = settings.imuAccFSS; + FSS.g = settings.imuGyroFSS; + retval = myICM.setFullScale((ICM_20948_Internal_Acc | ICM_20948_Internal_Gyr), FSS); + if (retval != ICM_20948_Stat_Ok) + { + SerialPrintln(F("Error: Could not configure the IMU Full Scale!")); + success = false; + } } - retval = myICM.enableDLPF(ICM_20948_Internal_Gyr, settings.imuGyroDLPF); - if (retval != ICM_20948_Stat_Ok) + else { - SerialPrintln(F("Error: Could not configure the IMU Gyro DLPF!")); + // Initialize the DMP + ICM_20948_Status_e retval = myICM.initializeDMP(); + if (retval != ICM_20948_Stat_Ok) + { + SerialPrintln(F("Error: Could not startup the IMU in DMP mode!")); + success = false; + } + if (settings.imuLogDMPQuat6) + { + retval = myICM.enableDMPSensor(INV_ICM20948_SENSOR_GAME_ROTATION_VECTOR); + if (retval != ICM_20948_Stat_Ok) + { + SerialPrintln(F("Error: Could not enable the Game Rotation Vector (Quat6)!")); + success = false; + } + retval = myICM.setDMPODRrate(DMP_ODR_Reg_Quat6, 0); // Set ODR to 55Hz + if (retval != ICM_20948_Stat_Ok) + { + SerialPrintln(F("Error: Could not set the Quat6 ODR!")); + success = false; + } + } + if (settings.imuLogDMPQuat9) + { + retval = myICM.enableDMPSensor(INV_ICM20948_SENSOR_ROTATION_VECTOR); + if (retval != ICM_20948_Stat_Ok) + { + SerialPrintln(F("Error: Could not enable the Rotation Vector (Quat9)!")); + success = false; + } + retval = myICM.setDMPODRrate(DMP_ODR_Reg_Quat9, 0); // Set ODR to 55Hz + if (retval != ICM_20948_Stat_Ok) + { + SerialPrintln(F("Error: Could not set the Quat9 ODR!")); + success = false; + } + } + if (settings.imuLogDMPAccel) + { + retval = myICM.enableDMPSensor(INV_ICM20948_SENSOR_RAW_ACCELEROMETER); + if (retval != ICM_20948_Stat_Ok) + { + SerialPrintln(F("Error: Could not enable the DMP Accelerometer!")); + success = false; + } + retval = myICM.setDMPODRrate(DMP_ODR_Reg_Accel, 0); // Set ODR to 55Hz + if (retval != ICM_20948_Stat_Ok) + { + SerialPrintln(F("Error: Could not set the Accel ODR!")); + success = false; + } + } + if (settings.imuLogDMPGyro) + { + retval = myICM.enableDMPSensor(INV_ICM20948_SENSOR_RAW_GYROSCOPE); + if (retval != ICM_20948_Stat_Ok) + { + SerialPrintln(F("Error: Could not enable the DMP Gyroscope!")); + success = false; + } + retval = myICM.setDMPODRrate(DMP_ODR_Reg_Gyro, 0); // Set ODR to 55Hz + if (retval != ICM_20948_Stat_Ok) + { + SerialPrintln(F("Error: Could not set the Gyro ODR!")); + success = false; + } + retval = myICM.setDMPODRrate(DMP_ODR_Reg_Gyro_Calibr, 0); // Set ODR to 55Hz + if (retval != ICM_20948_Stat_Ok) + { + SerialPrintln(F("Error: Could not set the Gyro Calibr ODR!")); + success = false; + } + } + if (settings.imuLogDMPCpass) + { + retval = myICM.enableDMPSensor(INV_ICM20948_SENSOR_MAGNETIC_FIELD_UNCALIBRATED); + if (retval != ICM_20948_Stat_Ok) + { + SerialPrintln(F("Error: Could not enable the DMP Compass!")); + success = false; + } + retval = myICM.setDMPODRrate(DMP_ODR_Reg_Cpass, 0); // Set ODR to 55Hz + if (retval != ICM_20948_Stat_Ok) + { + SerialPrintln(F("Error: Could not set the Compass ODR!")); + success = false; + } + retval = myICM.setDMPODRrate(DMP_ODR_Reg_Cpass_Calibr, 0); // Set ODR to 55Hz + if (retval != ICM_20948_Stat_Ok) + { + SerialPrintln(F("Error: Could not set the Compass Calibr ODR!")); + success = false; + } + } + retval = myICM.enableFIFO(); // Enable the FIFO + if (retval != ICM_20948_Stat_Ok) + { + SerialPrintln(F("Error: Could not enable the FIFO!")); + success = false; + } + retval = myICM.enableDMP(); // Enable the DMP + if (retval != ICM_20948_Stat_Ok) + { + SerialPrintln(F("Error: Could not enable the DMP!")); + success = false; + } + retval = myICM.resetDMP(); // Reset the DMP + if (retval != ICM_20948_Stat_Ok) + { + SerialPrintln(F("Error: Could not reset the DMP!")); + success = false; + } + retval = myICM.resetFIFO(); // Reset the FIFO + if (retval != ICM_20948_Stat_Ok) + { + SerialPrintln(F("Error: Could not reset the FIFO!")); + success = false; + } } - ICM_20948_dlpcfg_t dlpcfg; - dlpcfg.a = settings.imuAccDLPFBW; - dlpcfg.g = settings.imuGyroDLPFBW; - retval = myICM.setDLPFcfg((ICM_20948_Internal_Acc | ICM_20948_Internal_Gyr), dlpcfg); - if (retval != ICM_20948_Stat_Ok) + + if (success) { - SerialPrintln(F("Error: Could not configure the IMU DLPF BW!")); + online.IMU = true; + delay(50); // Give the IMU time to get its first measurement ready } - ICM_20948_fss_t FSS; - FSS.a = settings.imuAccFSS; - FSS.g = settings.imuGyroFSS; - retval = myICM.setFullScale((ICM_20948_Internal_Acc | ICM_20948_Internal_Gyr), FSS); - if (retval != ICM_20948_Stat_Ok) + else { - SerialPrintln(F("Error: Could not configure the IMU Full Scale!")); + //Power down IMU + imuPowerOff(); + online.IMU = false; } - - online.IMU = true; } else { @@ -997,14 +1195,30 @@ void beginSerialOutput() online.serialOutput = false; } -void updateDataFileCreate(SdFile *dataFile) +#if SD_FAT_TYPE == 1 +void updateDataFileCreate(File32 *dataFile) +#elif SD_FAT_TYPE == 2 +void updateDataFileCreate(ExFile *dataFile) +#elif SD_FAT_TYPE == 3 +void updateDataFileCreate(FsFile *dataFile) +#else // SD_FAT_TYPE == 0 +void updateDataFileCreate(File *dataFile) +#endif // SD_FAT_TYPE { myRTC.getTime(); //Get the RTC time so we can use it to update the create time //Update the file create time dataFile->timestamp(T_CREATE, (myRTC.year + 2000), myRTC.month, myRTC.dayOfMonth, myRTC.hour, myRTC.minute, myRTC.seconds); } -void updateDataFileAccess(SdFile *dataFile) +#if SD_FAT_TYPE == 1 +void updateDataFileAccess(File32 *dataFile) +#elif SD_FAT_TYPE == 2 +void updateDataFileAccess(ExFile *dataFile) +#elif SD_FAT_TYPE == 3 +void updateDataFileAccess(FsFile *dataFile) +#else // SD_FAT_TYPE == 0 +void updateDataFileAccess(File *dataFile) +#endif // SD_FAT_TYPE { myRTC.getTime(); //Get the RTC time so we can use it to update the last modified time //Update the file access time diff --git a/Firmware/OpenLog_Artemis/Sensors.ino b/Firmware/OpenLog_Artemis/Sensors.ino index 891cfe6..63fb4ae 100644 --- a/Firmware/OpenLog_Artemis/Sensors.ino +++ b/Firmware/OpenLog_Artemis/Sensors.ino @@ -86,37 +86,75 @@ void getData() { //printDebug("getData: online.IMU = " + (String)online.IMU + "\r\n"); - if (myICM.dataReady()) + if (settings.imuUseDMP == false) { - //printDebug("getData: myICM.dataReady = " + (String)myICM.dataReady() + "\r\n"); - - myICM.getAGMT(); //Update values - - if (settings.logIMUAccel) + if (myICM.dataReady()) + { + //printDebug("getData: myICM.dataReady = " + (String)myICM.dataReady() + "\r\n"); + + myICM.getAGMT(); //Update values + + if (settings.logIMUAccel) + { + sprintf(tempData, "%.2f,%.2f,%.2f,", myICM.accX(), myICM.accY(), myICM.accZ()); + strcat(outputData, tempData); + } + if (settings.logIMUGyro) + { + sprintf(tempData, "%.2f,%.2f,%.2f,", myICM.gyrX(), myICM.gyrY(), myICM.gyrZ()); + strcat(outputData, tempData); + } + if (settings.logIMUMag) + { + sprintf(tempData, "%.2f,%.2f,%.2f,", myICM.magX(), myICM.magY(), myICM.magZ()); + strcat(outputData, tempData); + } + if (settings.logIMUTemp) + { + sprintf(tempData, "%.2f,", myICM.temp()); + strcat(outputData, tempData); + } + } + //else + //{ + // printDebug("getData: myICM.dataReady = " + (String)myICM.dataReady() + "\r\n"); + //} + } + else + { + myICM.readDMPdataFromFIFO(&dmpData); + while (myICM.status == ICM_20948_Stat_FIFOMoreDataAvail) { - sprintf(tempData, "%.2f,%.2f,%.2f,", myICM.accX(), myICM.accY(), myICM.accZ()); + myICM.readDMPdataFromFIFO(&dmpData); // Empty the FIFO - make sure data contains the most recent data + } + if (settings.imuLogDMPQuat6) + { + sprintf(tempData, "%.3f,%.3f,%.3f,", ((double)dmpData.Quat6.Data.Q1) / 1073741824.0, + ((double)dmpData.Quat6.Data.Q2) / 1073741824.0, ((double)dmpData.Quat6.Data.Q3) / 1073741824.0); strcat(outputData, tempData); } - if (settings.logIMUGyro) + if (settings.imuLogDMPQuat9) { - sprintf(tempData, "%.2f,%.2f,%.2f,", myICM.gyrX(), myICM.gyrY(), myICM.gyrZ()); + sprintf(tempData, "%.3f,%.3f,%.3f,%d,", ((double)dmpData.Quat9.Data.Q1) / 1073741824.0, + ((double)dmpData.Quat9.Data.Q2) / 1073741824.0, ((double)dmpData.Quat9.Data.Q3) / 1073741824.0, dmpData.Quat9.Data.Accuracy); strcat(outputData, tempData); } - if (settings.logIMUMag) + if (settings.imuLogDMPAccel) { - sprintf(tempData, "%.2f,%.2f,%.2f,", myICM.magX(), myICM.magY(), myICM.magZ()); + sprintf(tempData, "%d,%d,%d,", dmpData.Raw_Accel.Data.X, dmpData.Raw_Accel.Data.Y, dmpData.Raw_Accel.Data.Z); strcat(outputData, tempData); } - if (settings.logIMUTemp) + if (settings.imuLogDMPGyro) + { + sprintf(tempData, "%d,%d,%d,", dmpData.Raw_Gyro.Data.X, dmpData.Raw_Gyro.Data.Y, dmpData.Raw_Gyro.Data.Z); + strcat(outputData, tempData); + } + if (settings.imuLogDMPCpass) { - sprintf(tempData, "%.2f,", myICM.temp()); + sprintf(tempData, "%d,%d,%d,", dmpData.Compass.Data.X, dmpData.Compass.Data.Y, dmpData.Compass.Data.Z); strcat(outputData, tempData); } } - //else - //{ - // printDebug("getData: myICM.dataReady = " + (String)myICM.dataReady() + "\r\n"); - //} } //Append all external sensor data on linked list to outputData @@ -844,6 +882,91 @@ void gatherDeviceValues() } } break; +// case DEVICE_QWIIC_BUTTON: +// { +// QwiicButton *nodeDevice = (QwiicButton *)temp->classPtr; +// struct_QWIIC_BUTTON *nodeSetting = (struct_QWIIC_BUTTON *)temp->configPtr; +// if (nodeSetting->log == true) +// { +// long pressedPopped = 0; +// while (nodeDevice->isPressedQueueEmpty() == false) +// { +// pressedPopped = nodeDevice->popPressedQueue(); +// } +// if (nodeSetting->logPressed) +// { +// sprintf(tempData, "%.03f,", ((float)pressedPopped) / 1000.0); // Record only the most recent press - that's the best we can do +// strcat(outputData, tempData); +// } +// +// long clickedPopped = 0; +// while (nodeDevice->isClickedQueueEmpty() == false) +// { +// clickedPopped = nodeDevice->popClickedQueue(); +// nodeSetting->ledState ^= 1; // Toggle nodeSetting->ledState on _every_ click (not just the most recent) +// } +// if (nodeSetting->logClicked) +// { +// sprintf(tempData, "%.03f,", ((float)clickedPopped) / 1000.0); // Record only the most recent click - that's the best we can do +// strcat(outputData, tempData); +// } +// +// if (nodeSetting->toggleLEDOnClick) +// { +// if (nodeSetting->ledState) +// nodeDevice->LEDon(nodeSetting->ledBrightness); +// else +// nodeDevice->LEDoff(); +// sprintf(tempData, "%d,", nodeSetting->ledState); +// strcat(outputData, tempData); +// } +// } +// } +// break; + case DEVICE_BIO_SENSOR_HUB: + { + SparkFun_Bio_Sensor_Hub *nodeDevice = (SparkFun_Bio_Sensor_Hub *)temp->classPtr; + struct_BIO_SENSOR_HUB *nodeSetting = (struct_BIO_SENSOR_HUB *)temp->configPtr; + if (nodeSetting->log == true) + { + bioData body; + if ((nodeSetting->logHeartrate) || (nodeSetting->logConfidence) || (nodeSetting->logOxygen) || (nodeSetting->logStatus) || (nodeSetting->logExtendedStatus) || (nodeSetting->logRValue)) + { + body = nodeDevice->readBpm(); + } + if (nodeSetting->logHeartrate) + { + sprintf(tempData, "%d,", body.heartRate); + strcat(outputData, tempData); + } + if (nodeSetting->logConfidence) + { + sprintf(tempData, "%d,", body.confidence); + strcat(outputData, tempData); + } + if (nodeSetting->logOxygen) + { + sprintf(tempData, "%d,", body.oxygen); + strcat(outputData, tempData); + } + if (nodeSetting->logStatus) + { + sprintf(tempData, "%d,", body.status); + strcat(outputData, tempData); + } + if (nodeSetting->logExtendedStatus) + { + sprintf(tempData, "%d,", body.extStatus); + strcat(outputData, tempData); + } + if (nodeSetting->logRValue) + { + sprintf(tempData, "%.01f,", body.rValue); + strcat(outputData, tempData); + } + } + } + break; default: SerialPrintf2("printDeviceValue unknown device type: %s\r\n", getDeviceName(temp->deviceType)); break; @@ -887,14 +1010,30 @@ void printHelperText(bool terminalOnly) if (online.IMU) { - if (settings.logIMUAccel) - strcat(helperText, "aX,aY,aZ,"); - if (settings.logIMUGyro) - strcat(helperText, "gX,gY,gZ,"); - if (settings.logIMUMag) - strcat(helperText, "mX,mY,mZ,"); - if (settings.logIMUTemp) - strcat(helperText, "imu_degC,"); + if (settings.imuUseDMP == false) + { + if (settings.logIMUAccel) + strcat(helperText, "aX,aY,aZ,"); + if (settings.logIMUGyro) + strcat(helperText, "gX,gY,gZ,"); + if (settings.logIMUMag) + strcat(helperText, "mX,mY,mZ,"); + if (settings.logIMUTemp) + strcat(helperText, "imu_degC,"); + } + else + { + if (settings.imuLogDMPQuat6) + strcat(helperText, "Q6_1,Q6_2,Q6_3,"); + if (settings.imuLogDMPQuat9) + strcat(helperText, "Q9_1,Q9_2,Q9_3,HeadAcc,"); + if (settings.imuLogDMPAccel) + strcat(helperText, "RawAX,RawAY,RawAZ,"); + if (settings.imuLogDMPGyro) + strcat(helperText, "RawGX,RawGY,RawGZ,"); + if (settings.imuLogDMPCpass) + strcat(helperText, "RawMX,RawMY,RawMZ,"); + } } //Step through list, printing values as we go @@ -1244,6 +1383,40 @@ void printHelperText(bool terminalOnly) } } break; +// case DEVICE_QWIIC_BUTTON: +// { +// struct_QWIIC_BUTTON *nodeSetting = (struct_QWIIC_BUTTON *)temp->configPtr; +// if (nodeSetting->log) +// { +// if (nodeSetting->logPressed) +// strcat(helperText, "pressS,"); +// if (nodeSetting->logClicked) +// strcat(helperText, "clickS,"); +// if (nodeSetting->toggleLEDOnClick) +// strcat(helperText, "LED,"); +// } +// } +// break; + case DEVICE_BIO_SENSOR_HUB: + { + struct_BIO_SENSOR_HUB *nodeSetting = (struct_BIO_SENSOR_HUB *)temp->configPtr; + if (nodeSetting->log) + { + if (nodeSetting->logHeartrate) + strcat(helperText, "bpm,"); + if (nodeSetting->logConfidence) + strcat(helperText, "conf%,"); + if (nodeSetting->logOxygen) + strcat(helperText, "O2%,"); + if (nodeSetting->logStatus) + strcat(helperText, "stat,"); + if (nodeSetting->logExtendedStatus) + strcat(helperText, "eStat,"); + if (nodeSetting->logRValue) + strcat(helperText, "O2R,"); + } + } + break; default: SerialPrintf2("\nprinterHelperText device not found: %d\r\n", temp->deviceType); break; diff --git a/Firmware/OpenLog_Artemis/autoDetect.ino b/Firmware/OpenLog_Artemis/autoDetect.ino index ec233cc..9dd451b 100644 --- a/Firmware/OpenLog_Artemis/autoDetect.ino +++ b/Firmware/OpenLog_Artemis/autoDetect.ino @@ -255,6 +255,18 @@ bool addDevice(deviceType_e deviceType, uint8_t address, uint8_t muxAddress, uin temp->configPtr = new struct_MS5837; } break; +// case DEVICE_QWIIC_BUTTON: +// { +// temp->classPtr = new QwiicButton; +// temp->configPtr = new struct_QWIIC_BUTTON; +// } +// break; + case DEVICE_BIO_SENSOR_HUB: + { + temp->classPtr = new SparkFun_Bio_Sensor_Hub(32, 11, address); // Reset pin is 32, MFIO pin is 11 + temp->configPtr = new struct_BIO_SENSOR_HUB; + } + break; default: SerialPrintf2("addDevice Device type not found: %d\r\n", deviceType); break; @@ -515,6 +527,24 @@ bool beginQwiicDevices() temp->online = true; } break; +// case DEVICE_QWIIC_BUTTON: +// { +// QwiicButton *tempDevice = (QwiicButton *)temp->classPtr; +// struct_QWIIC_BUTTON *nodeSetting = (struct_QWIIC_BUTTON *)temp->configPtr; //Create a local pointer that points to same spot as node does +// if (nodeSetting->powerOnDelayMillis > qwiicPowerOnDelayMillis) qwiicPowerOnDelayMillis = nodeSetting->powerOnDelayMillis; // Increase qwiicPowerOnDelayMillis if required +// if (tempDevice->begin(temp->address, qwiic) == true) //Address, Wire port. Returns true on success. +// temp->online = true; +// } +// break; + case DEVICE_BIO_SENSOR_HUB: + { + SparkFun_Bio_Sensor_Hub *tempDevice = (SparkFun_Bio_Sensor_Hub *)temp->classPtr; + struct_BIO_SENSOR_HUB *nodeSetting = (struct_BIO_SENSOR_HUB *)temp->configPtr; //Create a local pointer that points to same spot as node does + if (nodeSetting->powerOnDelayMillis > qwiicPowerOnDelayMillis) qwiicPowerOnDelayMillis = nodeSetting->powerOnDelayMillis; // Increase qwiicPowerOnDelayMillis if required + if (tempDevice->begin(qwiic) == 0x00) //Wire port. Returns 0x00 on success. + temp->online = true; + } + break; default: SerialPrintf2("beginQwiicDevices: device type not found: %d\r\n", temp->deviceType); break; @@ -788,6 +818,25 @@ void configureDevice(node * temp) sensor->setFluidDensity(sensorSetting->fluidDensity); } break; +// case DEVICE_QWIIC_BUTTON: +// { +// QwiicButton *sensor = (QwiicButton *)temp->classPtr; +// struct_QWIIC_BUTTON *sensorSetting = (struct_QWIIC_BUTTON *)temp->configPtr; +// +// if (sensorSetting->ledState) +// sensor->LEDon(sensorSetting->ledBrightness); +// else +// sensor->LEDoff(); +// } +// break + case DEVICE_BIO_SENSOR_HUB: + { + SparkFun_Bio_Sensor_Hub *sensor = (SparkFun_Bio_Sensor_Hub *)temp->classPtr; + struct_BIO_SENSOR_HUB *sensorSetting = (struct_BIO_SENSOR_HUB *)temp->configPtr; + + sensor->configBpm(MODE_TWO); // MODE_TWO provides the oxygen R value + } + break; default: SerialPrintf3("configureDevice: Unknown device type %d: %s\r\n", deviceType, getDeviceName((deviceType_e)deviceType)); break; @@ -887,6 +936,12 @@ FunctionPointer getConfigFunctionPtr(uint8_t nodeNumber) case DEVICE_PRESSURE_MS5837: ptr = (FunctionPointer)menuConfigure_MS5837; break; +// case DEVICE_QWIIC_BUTTON: +// ptr = (FunctionPointer)menuConfigure_QWIIC_BUTTON; +// break; + case DEVICE_BIO_SENSOR_HUB: + ptr = (FunctionPointer)menuConfigure_BIO_SENSOR_HUB; + break; default: SerialPrintln(F("getConfigFunctionPtr: Unknown device type")); SerialFlush(); @@ -1025,6 +1080,7 @@ void swap(struct node * a, struct node * b) #define ADR_UBLOX 0x42 //But can be set to any address #define ADR_ADS122C04 0x45 //Alternates: 0x44, 0x41 and 0x40 #define ADR_TMP117 0x48 //Alternates: 0x49, 0x4A, and 0x4B +#define ADR_BIO_SENSOR_HUB 0x55 #define ADR_SGP30 0x58 #define ADR_SGP40 0x59 #define ADR_CCS811 0x5B //Alternates: 0x5A @@ -1032,6 +1088,7 @@ void swap(struct node * a, struct node * b) #define ADR_VCNL4040 0x60 #define ADR_SCD30 0x61 #define ADR_MCP9600 0x60 //0x60 to 0x67 +//#define ADR_QWIIC_BUTTON 0x6F //But can be any address... Limit the range to 0x68-0x6F #define ADR_MULTIPLEXER 0x70 //0x70 to 0x77 #define ADR_SHTC3 0x70 #define ADR_MS5637 0x76 @@ -1199,6 +1256,16 @@ deviceType_e testDevice(uint8_t i2cAddress, uint8_t muxAddress, uint8_t portNumb return (DEVICE_TEMPERATURE_TMP117); } break; + case 0x55: + { + if (settings.identifyBioSensorHubs == true) + { + SparkFun_Bio_Sensor_Hub sensor(32, 11, i2cAddress); // Reset pin is 32, MFIO pin is 11 + if (sensor.begin(qwiic) == 0x00) //Wire port + return (DEVICE_BIO_SENSOR_HUB); + } + } + break; case 0x58: { SGP30 sensor; @@ -1313,6 +1380,20 @@ deviceType_e testDevice(uint8_t i2cAddress, uint8_t muxAddress, uint8_t portNumb return (DEVICE_TEMPERATURE_MCP9600); } break; +// case 0x68: +// case 0x69: +// case 0x6A: +// case 0x6B: +// case 0x6C: +// case 0x6D: +// case 0x6E: +// case 0x6F: +// { +// QwiicButton sensor; +// if (sensor.begin(i2cAddress, qwiic) == true) //Address, Wire port +// return (DEVICE_QWIIC_BUTTON); +// } +// break; case 0x70: { //Ignore devices we've already recorded. This was causing the mux to get tested, a begin() would happen, and the mux would be reset. @@ -1625,6 +1706,12 @@ const char* getDeviceName(deviceType_e deviceNumber) case DEVICE_PRESSURE_MS5837: return "Pressure-MS5837"; break; +// case DEVICE_QWIIC_BUTTON: +// return "Qwiic_Button"; +// break; + case DEVICE_BIO_SENSOR_HUB: + return "Bio-Sensor-Oximeter"; + break; case DEVICE_UNKNOWN_DEVICE: return "Unknown device"; diff --git a/Firmware/OpenLog_Artemis/lowerPower.ino b/Firmware/OpenLog_Artemis/lowerPower.ino index 0b313ea..292e664 100644 --- a/Firmware/OpenLog_Artemis/lowerPower.ino +++ b/Firmware/OpenLog_Artemis/lowerPower.ino @@ -318,6 +318,11 @@ void goToSleep(uint32_t sysTicksToSleep) //else // powerLEDOff(); + //Adjust sysTicks down by the amount we've be at 48MHz + //Read millis _before_ we switch to the lower power clock! + uint32_t msBeenAwake = millis(); + uint32_t sysTicksAwake = msBeenAwake * 32768L / 1000L; //Convert to 32kHz systicks + //Power down cache, flash, SRAM am_hal_pwrctrl_memory_deepsleep_powerdown(AM_HAL_PWRCTRL_MEM_ALL); // Power down all flash and cache am_hal_pwrctrl_memory_deepsleep_retain(AM_HAL_PWRCTRL_MEM_SRAM_384K); // Retain all SRAM @@ -326,10 +331,6 @@ void goToSleep(uint32_t sysTicksToSleep) am_hal_stimer_config(AM_HAL_STIMER_CFG_CLEAR | AM_HAL_STIMER_CFG_FREEZE); am_hal_stimer_config(AM_HAL_STIMER_XTAL_32KHZ | AM_HAL_STIMER_CFG_COMPARE_G_ENABLE); - //Adjust sysTicks down by the amount we've be at 48MHz - uint32_t msBeenAwake = millis(); - uint32_t sysTicksAwake = msBeenAwake * 32768L / 1000L; //Convert to 32kHz systicks - //Check that sysTicksToSleep is >> sysTicksAwake if (sysTicksToSleep > (sysTicksAwake + 3277)) // Abort if we are trying to sleep for < 100ms { diff --git a/Firmware/OpenLog_Artemis/menuAnalogLogging.ino b/Firmware/OpenLog_Artemis/menuAnalogLogging.ino index f8298f8..17326f3 100644 --- a/Firmware/OpenLog_Artemis/menuAnalogLogging.ino +++ b/Firmware/OpenLog_Artemis/menuAnalogLogging.ino @@ -9,9 +9,12 @@ void menuAnalogLogging() SerialPrintln(F("")); SerialPrintln(F("Menu: Configure Analog Logging")); - SerialPrint(F("1) Log analog pin 11 (2V Max): ")); - if (settings.logA11 == true) SerialPrintln(F("Enabled. (Triggering is disabled)")); - else SerialPrintln(F("Disabled")); + if (settings.identifyBioSensorHubs == false) + { + SerialPrint(F("1) Log analog pin 11 (2V Max): ")); + if (settings.logA11 == true) SerialPrintln(F("Enabled. (Triggering is disabled)")); + else SerialPrintln(F("Disabled")); + } SerialPrint(F("2) Log analog pin 12 (TX) (2V Max): ")); if (settings.useTxRxPinsForTerminal == true) SerialPrintln(F("Disabled. (TX and RX pins are being used for the Terminal)")); @@ -23,9 +26,12 @@ void menuAnalogLogging() else if (settings.logA13 == true) SerialPrintln(F("Enabled. (Serial logging is disabled)")); else SerialPrintln(F("Disabled")); - SerialPrint(F("4) Log analog pin 32 (2V Max): ")); - if (settings.logA32 == true) SerialPrintln(F("Enabled. (Stop logging is disabled)")); - else SerialPrintln(F("Disabled")); + if (settings.identifyBioSensorHubs == false) + { + SerialPrint(F("4) Log analog pin 32 (2V Max): ")); + if (settings.logA32 == true) SerialPrintln(F("Enabled. (Stop logging is disabled)")); + else SerialPrintln(F("Disabled")); + } SerialPrint(F("5) Log output type: ")); if (settings.logAnalogVoltages == true) SerialPrintln(F("Calculated Voltage")); @@ -41,17 +47,26 @@ void menuAnalogLogging() if (incoming == '1') { - if(settings.logA11 == false) + if (settings.identifyBioSensorHubs == false) { - settings.logA11 = true; - // Disable triggering - settings.useGPIO11ForTrigger = false; - detachInterrupt(digitalPinToInterrupt(PIN_TRIGGER)); // Disable the interrupt - pinMode(PIN_TRIGGER, INPUT); // Remove the pull-up - triggerEdgeSeen = false; // Make sure the flag is clear + if(settings.logA11 == false) + { + settings.logA11 = true; + // Disable triggering + settings.useGPIO11ForTrigger = false; + detachInterrupt(digitalPinToInterrupt(PIN_TRIGGER)); // Disable the interrupt + pinMode(PIN_TRIGGER, INPUT); // Remove the pull-up + triggerEdgeSeen = false; // Make sure the flag is clear + } + else + settings.logA11 = false; } else - settings.logA11 = false; + { + SerialPrintln(F("")); + SerialPrintln(F("Analog logging on pin 11 is not possible. Bio Sensor Hubs (Pulse Oximeters) are enabled.")); + SerialPrintln(F("")); + } } else if (incoming == '2') { @@ -95,16 +110,25 @@ void menuAnalogLogging() } else if (incoming == '4') { - if(settings.logA32 == false) + if (settings.identifyBioSensorHubs == false) { - settings.logA32 = true; - // Disable stop logging - settings.useGPIO32ForStopLogging = false; - detachInterrupt(digitalPinToInterrupt(PIN_STOP_LOGGING)); // Disable the interrupt - pinMode(PIN_STOP_LOGGING, INPUT); // Remove the pull-up + if(settings.logA32 == false) + { + settings.logA32 = true; + // Disable stop logging + settings.useGPIO32ForStopLogging = false; + detachInterrupt(digitalPinToInterrupt(PIN_STOP_LOGGING)); // Disable the interrupt + pinMode(PIN_STOP_LOGGING, INPUT); // Remove the pull-up + } + else + settings.logA32 = false; } else - settings.logA32 = false; + { + SerialPrintln(F("")); + SerialPrintln(F("Analog logging on pin 32 is not possible. Bio Sensor Hubs (Pulse Oximeters) are enabled.")); + SerialPrintln(F("")); + } } else if (incoming == '5') settings.logAnalogVoltages ^= 1; diff --git a/Firmware/OpenLog_Artemis/menuAttachedDevices.ino b/Firmware/OpenLog_Artemis/menuAttachedDevices.ino index 2e2dd23..e82cf10 100644 --- a/Firmware/OpenLog_Artemis/menuAttachedDevices.ino +++ b/Firmware/OpenLog_Artemis/menuAttachedDevices.ino @@ -336,6 +336,12 @@ void menuAttachedDevices() case DEVICE_PRESSURE_MS5837: SerialPrintf3("%s MS5837 (BAR30 / BAR02) Pressure Sensor %s\r\n", strDeviceMenu, strAddress); break; +// case DEVICE_QWIIC_BUTTON: +// SerialPrintf3("%s Qwiic Button %s\r\n", strDeviceMenu, strAddress); +// break; + case DEVICE_BIO_SENSOR_HUB: + SerialPrintf3("%s Bio Sensor Pulse Oximeter %s\r\n", strDeviceMenu, strAddress); + break; default: SerialPrintf2("Unknown device type %d in menuAttachedDevices\r\n", temp->deviceType); break; @@ -345,12 +351,22 @@ void menuAttachedDevices() temp = temp->next; } - SerialPrintf2("%d) Configure Qwiic Settings\r\n", availableDevices++ + 1); + availableDevices++; + SerialPrintf2("%d) Configure Qwiic Settings\r\n", availableDevices); + availableDevices++; + if (settings.identifyBioSensorHubs == true) + { + SerialPrintf2("%d) Detect Bio Sensor Pulse Oximeter: Enabled\r\n", availableDevices); + } + else + { + SerialPrintf2("%d) Detect Bio Sensor Pulse Oximeter: Disabled\r\n", availableDevices); + } SerialPrintln(F("x) Exit")); int nodeNumber = getNumber(menuTimeout); //Timeout after x seconds - if (nodeNumber > 0 && nodeNumber < availableDevices) + if (nodeNumber > 0 && nodeNumber < availableDevices - 1) { //Lookup the function we need to call based the node number FunctionPointer functionPointer = getConfigFunctionPtr(nodeNumber - 1); @@ -361,10 +377,59 @@ void menuAttachedDevices() configureDevice(nodeNumber - 1); //Reconfigure this device with the new settings } - else if (nodeNumber == availableDevices) + else if (nodeNumber == availableDevices - 1) { menuConfigure_QwiicBus(); } + else if (nodeNumber == availableDevices) + { + if (settings.identifyBioSensorHubs) + { + SerialPrintln(F("")); + SerialPrintln(F("\"Detect Bio Sensor Pulse Oximeter\" can only be disabled by \"Reset all settings to default\"")); + SerialPrintln(F("")); + } + else + { + SerialPrintln(F("")); + SerialPrintln(F("\"Detect Bio Sensor Pulse Oximeter\" requires exclusive use of pins 32 and 11")); + SerialPrintln(F("Once enabled, \"Detect Bio Sensor Pulse Oximeter\" can only be disabled by \"Reset all settings to default\"")); + SerialPrintln(F("Pin 32 must be connected to the sensor RST pin")); + SerialPrintln(F("Pin 11 must be connected to the sensor MFIO pin")); + SerialPrintln(F("This means you cannot use pins 32 and 11 for: analog logging; triggering; fast/slow logging; stop logging; etc.")); + SerialPrintln(F("Are you sure? Press 'y' to confirm: ")); + byte bContinue = getByteChoice(menuTimeout); + if (bContinue == 'y') + { + settings.identifyBioSensorHubs = true; + settings.logA11 = false; + settings.logA32 = false; + if (settings.useGPIO11ForTrigger == true) // If interrupts are enabled, we need to disable and then re-enable + { + detachInterrupt(digitalPinToInterrupt(PIN_TRIGGER)); // Disable the interrupt + settings.useGPIO11ForTrigger = false; + } + settings.useGPIO11ForFastSlowLogging = false; + if (settings.useGPIO32ForStopLogging == true) + { + // Disable stop logging + settings.useGPIO32ForStopLogging = false; + detachInterrupt(digitalPinToInterrupt(PIN_STOP_LOGGING)); // Disable the interrupt + } + + recordSystemSettings(); //Record the new settings to EEPROM and config file now in case the user resets before exiting the menus + + if (detectQwiicDevices() == true) //Detect the oximeter + { + beginQwiicDevices(); //Begin() each device in the node list + configureQwiicDevices(); //Apply config settings to each device in the node list + recordDeviceSettingsToFile(); //Record the current devices settings to device config file now in case the user resets before exiting the menus + } + } + else + SerialPrintln(F("\"Detect Bio Sensor Pulse Oximeter\" aborted")); + } + } else if (nodeNumber == STATUS_PRESSED_X) break; else if (nodeNumber == STATUS_GETNUMBER_TIMEOUT) @@ -2389,3 +2454,146 @@ void menuConfigure_MS5837(void *configPtr) printUnknown(incoming); } } + +//void menuConfigure_QWIIC_BUTTON(void *configPtr) +//{ +// struct_QWIIC_BUTTON *sensorSetting = (struct_QWIIC_BUTTON*)configPtr; +// +// while (1) +// { +// SerialPrintln(F("")); +// SerialPrintln(F("Menu: Configure Qwiic Button")); +// +// SerialPrint(F("1) Sensor Logging: ")); +// if (sensorSetting->log == true) SerialPrintln(F("Enabled")); +// else SerialPrintln(F("Disabled")); +// +// if (sensorSetting->log == true) +// { +// SerialPrint(F("2) Log Button Presses: ")); +// if (sensorSetting->logPressed == true) SerialPrintln(F("Enabled")); +// else SerialPrintln(F("Disabled")); +// +// SerialPrint(F("3) Log Button Clicks: ")); +// if (sensorSetting->logClicked == true) SerialPrintln(F("Enabled")); +// else SerialPrintln(F("Disabled")); +// +// SerialPrint(F("4) Toggle LED on each click (and log the LED state): ")); +// if (sensorSetting->toggleLEDOnClick == true) SerialPrintln(F("Enabled")); +// else SerialPrintln(F("Disabled")); +// +// SerialPrintf2("5) LED Brightness: %d\r\n", sensorSetting->ledBrightness); +// } +// SerialPrintln(F("x) Exit")); +// +// int incoming = getNumber(menuTimeout); //Timeout after x seconds +// +// if (incoming == 1) +// sensorSetting->log ^= 1; +// else if (sensorSetting->log == true) +// { +// if (incoming == 2) +// sensorSetting->logPressed ^= 1; +// else if (incoming == 3) +// sensorSetting->logClicked ^= 1; +// else if (incoming == 4) +// sensorSetting->toggleLEDOnClick ^= 1; +// else if (incoming == 5) +// { +// SerialPrint(F("Enter the LED brightness (0 to 255): ")); +// int bright = getNumber(menuTimeout); //x second timeout +// if (bright < 0 || bright > 255) +// SerialPrintln(F("Error: Out of range")); +// else +// sensorSetting->ledBrightness = bright; +// } +// else if (incoming == STATUS_PRESSED_X) +// break; +// else if (incoming == STATUS_GETNUMBER_TIMEOUT) +// break; +// else +// printUnknown(incoming); +// } +// else if (incoming == STATUS_PRESSED_X) +// break; +// else if (incoming == STATUS_GETNUMBER_TIMEOUT) +// break; +// else +// printUnknown(incoming); +// } +//} + +void menuConfigure_BIO_SENSOR_HUB(void *configPtr) +{ + struct_BIO_SENSOR_HUB *sensorSetting = (struct_BIO_SENSOR_HUB*)configPtr; + + while (1) + { + SerialPrintln(F("")); + SerialPrintln(F("Menu: Configure Bio Sensor Hub (Pulse Oximeter)")); + + SerialPrint(F("1) Sensor Logging: ")); + if (sensorSetting->log == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); + + if (sensorSetting->log == true) + { + SerialPrint(F("2) Log Heart Rate: ")); + if (sensorSetting->logHeartrate == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); + + SerialPrint(F("3) Log Confidence %: ")); + if (sensorSetting->logConfidence == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); + + SerialPrint(F("4) Log Oxygen %: ")); + if (sensorSetting->logOxygen == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); + + SerialPrint(F("5) Log Status: ")); + if (sensorSetting->logStatus == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); + + SerialPrint(F("6) Log Extended Status: ")); + if (sensorSetting->logExtendedStatus == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); + + SerialPrint(F("7) Log Oxygen R Value: ")); + if (sensorSetting->logRValue == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); + } + SerialPrintln(F("x) Exit")); + + int incoming = getNumber(menuTimeout); //Timeout after x seconds + + if (incoming == 1) + sensorSetting->log ^= 1; + else if (sensorSetting->log == true) + { + if (incoming == 2) + sensorSetting->logHeartrate ^= 1; + else if (incoming == 3) + sensorSetting->logConfidence ^= 1; + else if (incoming == 4) + sensorSetting->logOxygen ^= 1; + else if (incoming == 5) + sensorSetting->logStatus ^= 1; + else if (incoming == 6) + sensorSetting->logExtendedStatus ^= 1; + else if (incoming == 7) + sensorSetting->logRValue ^= 1; + else if (incoming == STATUS_PRESSED_X) + break; + else if (incoming == STATUS_GETNUMBER_TIMEOUT) + break; + else + printUnknown(incoming); + } + else if (incoming == STATUS_PRESSED_X) + break; + else if (incoming == STATUS_GETNUMBER_TIMEOUT) + break; + else + printUnknown(incoming); + } +} diff --git a/Firmware/OpenLog_Artemis/menuIMU.ino b/Firmware/OpenLog_Artemis/menuIMU.ino index c91af34..c0330fa 100644 --- a/Firmware/OpenLog_Artemis/menuIMU.ino +++ b/Firmware/OpenLog_Artemis/menuIMU.ino @@ -1,5 +1,7 @@ -void menuIMU() +// Return true if IMU requires a restart +bool menuIMU() { + bool restartIMU = false; while (1) { SerialPrintln(F("")); @@ -11,145 +13,171 @@ void menuIMU() if (settings.enableIMU == true) { - SerialPrint(F("2) Accelerometer Logging: ")); - if (settings.logIMUAccel) SerialPrintln(F("Enabled")); - else SerialPrintln(F("Disabled")); - - SerialPrint(F("3) Gyro Logging: ")); - if (settings.logIMUGyro) SerialPrintln(F("Enabled")); - else SerialPrintln(F("Disabled")); - - SerialPrint(F("4) Magnetometer Logging: ")); - if (settings.logIMUMag) SerialPrintln(F("Enabled")); - else SerialPrintln(F("Disabled")); - - SerialPrint(F("5) Temperature Logging: ")); - if (settings.logIMUTemp) SerialPrintln(F("Enabled")); - else SerialPrintln(F("Disabled")); - - if (online.IMU == true) + if (settings.imuUseDMP == false) { - SerialPrint(F("6) Accelerometer Full Scale: +/- ")); - switch (settings.imuAccFSS) - { - case 0: - SerialPrintln(F("2g")); - break; - case 1: - SerialPrintln(F("4g")); - break; - case 2: - SerialPrintln(F("8g")); - break; - case 3: - SerialPrintln(F("16g")); - break; - default: - SerialPrintln(F("UNKNOWN")); - break; - } - - SerialPrint(F("7) Accelerometer Digital Low Pass Filter: ")); - if (settings.imuAccDLPF) + SerialPrint(F("2) Accelerometer Logging: ")); + if (settings.logIMUAccel) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); + + SerialPrint(F("3) Gyro Logging: ")); + if (settings.logIMUGyro) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); + + SerialPrint(F("4) Magnetometer Logging: ")); + if (settings.logIMUMag) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); + + SerialPrint(F("5) Temperature Logging: ")); + if (settings.logIMUTemp) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); + + if (online.IMU == true) { - SerialPrintln(F("Enabled")); - SerialPrint(F("8) Accelerometer DLPF Bandwidth (Hz): ")); - switch (settings.imuAccDLPFBW) + SerialPrint(F("6) Accelerometer Full Scale: +/- ")); + switch (settings.imuAccFSS) { case 0: - SerialPrintln(F("246.0 (3dB) 265.0 (Nyquist)")); + SerialPrintln(F("2g")); break; case 1: - SerialPrintln(F("246.0 (3dB) 265.0 (Nyquist)")); + SerialPrintln(F("4g")); break; case 2: - SerialPrintln(F("111.4 (3dB) 136.0 (Nyquist)")); + SerialPrintln(F("8g")); break; case 3: - SerialPrintln(F("50.4 (3dB) 68.8 (Nyquist)")); - break; - case 4: - SerialPrintln(F("23.9 (3dB) 34.4 (Nyquist)")); - break; - case 5: - SerialPrintln(F("11.5 (3dB) 17.0 (Nyquist)")); - break; - case 6: - SerialPrintln(F("5.7 (3dB) 8.3 (Nyquist)")); - break; - case 7: - SerialPrintln(F("473 (3dB) 499 (Nyquist)")); + SerialPrintln(F("16g")); break; default: SerialPrintln(F("UNKNOWN")); break; } - } - else - { - SerialPrintln(F("Disabled (Bandwidth is 1209 Hz (3dB) 1248 Hz (Nyquist))")); - } - - SerialPrint(F("9) Gyro Full Scale: +/- ")); - switch (settings.imuGyroFSS) - { - case 0: - SerialPrintln(F("250dps")); - break; - case 1: - SerialPrintln(F("500dps")); - break; - case 2: - SerialPrintln(F("1000dps")); - break; - case 3: - SerialPrintln(F("2000dps")); - break; - default: - SerialPrintln(F("UNKNOWN")); - break; - } - - SerialPrint(F("10) Gyro Digital Low Pass Filter: ")); - if (settings.imuGyroDLPF) - { - SerialPrintln(F("Enabled")); - SerialPrint(F("11) Gyro DLPF Bandwidth (Hz): ")); - switch (settings.imuGyroDLPFBW) + + SerialPrint(F("7) Accelerometer Digital Low Pass Filter: ")); + if (settings.imuAccDLPF) + { + SerialPrintln(F("Enabled")); + SerialPrint(F("8) Accelerometer DLPF Bandwidth (Hz): ")); + switch (settings.imuAccDLPFBW) + { + case 0: + SerialPrintln(F("246.0 (3dB) 265.0 (Nyquist)")); + break; + case 1: + SerialPrintln(F("246.0 (3dB) 265.0 (Nyquist)")); + break; + case 2: + SerialPrintln(F("111.4 (3dB) 136.0 (Nyquist)")); + break; + case 3: + SerialPrintln(F("50.4 (3dB) 68.8 (Nyquist)")); + break; + case 4: + SerialPrintln(F("23.9 (3dB) 34.4 (Nyquist)")); + break; + case 5: + SerialPrintln(F("11.5 (3dB) 17.0 (Nyquist)")); + break; + case 6: + SerialPrintln(F("5.7 (3dB) 8.3 (Nyquist)")); + break; + case 7: + SerialPrintln(F("473 (3dB) 499 (Nyquist)")); + break; + default: + SerialPrintln(F("UNKNOWN")); + break; + } + } + else + { + SerialPrintln(F("Disabled (Bandwidth is 1209 Hz (3dB) 1248 Hz (Nyquist))")); + } + + SerialPrint(F("9) Gyro Full Scale: +/- ")); + switch (settings.imuGyroFSS) { case 0: - SerialPrintln(F("196.6 (3dB) 229.8 (Nyquist)")); + SerialPrintln(F("250dps")); break; case 1: - SerialPrintln(F("151.8 (3dB) 187.6 (Nyquist)")); + SerialPrintln(F("500dps")); break; case 2: - SerialPrintln(F("119.5 (3dB) 154.3 (Nyquist)")); + SerialPrintln(F("1000dps")); break; case 3: - SerialPrintln(F("51.2 (3dB) 73.3 (Nyquist)")); - break; - case 4: - SerialPrintln(F("23.9 (3dB) 35.9 (Nyquist)")); - break; - case 5: - SerialPrintln(F("11.6 (3dB) 17.8 (Nyquist)")); - break; - case 6: - SerialPrintln(F("5.7 (3dB) 8.9 (Nyquist)")); - break; - case 7: - SerialPrintln(F("361.4 (3dB) 376.5 (Nyquist)")); + SerialPrintln(F("2000dps")); break; default: SerialPrintln(F("UNKNOWN")); break; } + + SerialPrint(F("10) Gyro Digital Low Pass Filter: ")); + if (settings.imuGyroDLPF) + { + SerialPrintln(F("Enabled")); + SerialPrint(F("11) Gyro DLPF Bandwidth (Hz): ")); + switch (settings.imuGyroDLPFBW) + { + case 0: + SerialPrintln(F("196.6 (3dB) 229.8 (Nyquist)")); + break; + case 1: + SerialPrintln(F("151.8 (3dB) 187.6 (Nyquist)")); + break; + case 2: + SerialPrintln(F("119.5 (3dB) 154.3 (Nyquist)")); + break; + case 3: + SerialPrintln(F("51.2 (3dB) 73.3 (Nyquist)")); + break; + case 4: + SerialPrintln(F("23.9 (3dB) 35.9 (Nyquist)")); + break; + case 5: + SerialPrintln(F("11.6 (3dB) 17.8 (Nyquist)")); + break; + case 6: + SerialPrintln(F("5.7 (3dB) 8.9 (Nyquist)")); + break; + case 7: + SerialPrintln(F("361.4 (3dB) 376.5 (Nyquist)")); + break; + default: + SerialPrintln(F("UNKNOWN")); + break; + } + } + else + { + SerialPrintln(F("Disabled (Bandwidth is 12106 Hz (3dB) 12316 Hz (Nyquist))")); + } } - else - { - SerialPrintln(F("Disabled (Bandwidth is 12106 Hz (3dB) 12316 Hz (Nyquist))")); - } + } + + SerialPrint(F("12) Digital Motion Processor (DMP): ")); + if (settings.imuUseDMP) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); + + if (settings.imuUseDMP == true) + { + SerialPrint(F("13) Game Rotation Vector (Quat6) Logging: ")); + if (settings.imuLogDMPQuat6) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); + SerialPrint(F("14) Rotation Vector (Quat9) Logging: ")); + if (settings.imuLogDMPQuat9) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); + SerialPrint(F("15) Accelerometer Logging: ")); + if (settings.imuLogDMPAccel) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); + SerialPrint(F("16) Gyro Logging: ")); + if (settings.imuLogDMPGyro) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); + SerialPrint(F("17) Compass Logging: ")); + if (settings.imuLogDMPCpass) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); } } @@ -165,131 +193,200 @@ void menuIMU() } else if (settings.enableIMU == true) { - if (incoming == 2) - settings.logIMUAccel ^= 1; - else if (incoming == 3) - settings.logIMUGyro ^= 1; - else if (incoming == 4) - settings.logIMUMag ^= 1; - else if (incoming == 5) - settings.logIMUTemp ^= 1; - else if ((incoming == 6) && (online.IMU == true)) + if (settings.imuUseDMP == false) { - SerialPrintln(F("Enter Accelerometer Full Scale (0 to 3): ")); - SerialPrintln(F("0: +/- 2g")); - SerialPrintln(F("1: +/- 4g")); - SerialPrintln(F("2: +/- 8g")); - SerialPrintln(F("3: +/- 16g")); - int afs = getNumber(menuTimeout); //x second timeout - if (afs < 0 || afs > 3) - SerialPrintln(F("Error: Out of range")); - else + if (incoming == 2) + settings.logIMUAccel ^= 1; + else if (incoming == 3) + settings.logIMUGyro ^= 1; + else if (incoming == 4) + settings.logIMUMag ^= 1; + else if (incoming == 5) + settings.logIMUTemp ^= 1; + else if ((incoming == 6) && (online.IMU == true)) + { + SerialPrintln(F("Enter Accelerometer Full Scale (0 to 3): ")); + SerialPrintln(F("0: +/- 2g")); + SerialPrintln(F("1: +/- 4g")); + SerialPrintln(F("2: +/- 8g")); + SerialPrintln(F("3: +/- 16g")); + int afs = getNumber(menuTimeout); //x second timeout + if (afs < 0 || afs > 3) + SerialPrintln(F("Error: Out of range")); + else + { + settings.imuAccFSS = afs; + ICM_20948_fss_t FSS; + FSS.a = settings.imuAccFSS; + FSS.g = settings.imuGyroFSS; + ICM_20948_Status_e retval = myICM.setFullScale((ICM_20948_Internal_Acc | ICM_20948_Internal_Gyr), FSS); + if (retval != ICM_20948_Stat_Ok) + { + SerialPrintln(F("Error: Could not configure the IMU!")); + } + } + } + else if ((incoming == 7) && (online.IMU == true)) { - settings.imuAccFSS = afs; - ICM_20948_fss_t FSS; - FSS.a = settings.imuAccFSS; - FSS.g = settings.imuGyroFSS; - ICM_20948_Status_e retval = myICM.setFullScale((ICM_20948_Internal_Acc | ICM_20948_Internal_Gyr), FSS); + settings.imuAccDLPF ^= 1; + ICM_20948_Status_e retval = myICM.enableDLPF(ICM_20948_Internal_Acc, settings.imuAccDLPF); if (retval != ICM_20948_Stat_Ok) { SerialPrintln(F("Error: Could not configure the IMU!")); } } - } - else if ((incoming == 7) && (online.IMU == true)) - { - settings.imuAccDLPF ^= 1; - ICM_20948_Status_e retval = myICM.enableDLPF(ICM_20948_Internal_Acc, settings.imuAccDLPF); - if (retval != ICM_20948_Stat_Ok) + else if ((incoming == 8) && (online.IMU == true) && (settings.imuAccDLPF == true)) { - SerialPrintln(F("Error: Could not configure the IMU!")); + SerialPrintln(F("Enter Accelerometer DLPF Bandwidth (0 to 7): ")); + SerialPrintln(F("0: 246.0 (3dB) 265.0 (Nyquist) (Hz)")); + SerialPrintln(F("1: 246.0 (3dB) 265.0 (Nyquist) (Hz)")); + SerialPrintln(F("2: 111.4 (3dB) 136.0 (Nyquist) (Hz)")); + SerialPrintln(F("3: 50.4 (3dB) 68.8 (Nyquist) (Hz)")); + SerialPrintln(F("4: 23.9 (3dB) 34.4 (Nyquist) (Hz)")); + SerialPrintln(F("5: 11.5 (3dB) 17.0 (Nyquist) (Hz)")); + SerialPrintln(F("6: 5.7 (3dB) 8.3 (Nyquist) (Hz)")); + SerialPrintln(F("7: 473 (3dB) 499 (Nyquist) (Hz)")); + int afbw = getNumber(menuTimeout); //x second timeout + if (afbw < 0 || afbw > 7) + SerialPrintln(F("Error: Out of range")); + else + { + settings.imuAccDLPFBW = afbw; + ICM_20948_dlpcfg_t dlpcfg; + dlpcfg.a = settings.imuAccDLPFBW; + dlpcfg.g = settings.imuGyroDLPFBW; + ICM_20948_Status_e retval = myICM.setDLPFcfg((ICM_20948_Internal_Acc | ICM_20948_Internal_Gyr), dlpcfg); + if (retval != ICM_20948_Stat_Ok) + { + SerialPrintln(F("Error: Could not configure the IMU!")); + } + } } - } - else if ((incoming == 8) && (online.IMU == true) && (settings.imuAccDLPF == true)) - { - SerialPrintln(F("Enter Accelerometer DLPF Bandwidth (0 to 7): ")); - SerialPrintln(F("0: 246.0 (3dB) 265.0 (Nyquist) (Hz)")); - SerialPrintln(F("1: 246.0 (3dB) 265.0 (Nyquist) (Hz)")); - SerialPrintln(F("2: 111.4 (3dB) 136.0 (Nyquist) (Hz)")); - SerialPrintln(F("3: 50.4 (3dB) 68.8 (Nyquist) (Hz)")); - SerialPrintln(F("4: 23.9 (3dB) 34.4 (Nyquist) (Hz)")); - SerialPrintln(F("5: 11.5 (3dB) 17.0 (Nyquist) (Hz)")); - SerialPrintln(F("6: 5.7 (3dB) 8.3 (Nyquist) (Hz)")); - SerialPrintln(F("7: 473 (3dB) 499 (Nyquist) (Hz)")); - int afbw = getNumber(menuTimeout); //x second timeout - if (afbw < 0 || afbw > 7) - SerialPrintln(F("Error: Out of range")); - else + else if ((incoming == 9) && (online.IMU == true)) { - settings.imuAccDLPFBW = afbw; - ICM_20948_dlpcfg_t dlpcfg; - dlpcfg.a = settings.imuAccDLPFBW; - dlpcfg.g = settings.imuGyroDLPFBW; - ICM_20948_Status_e retval = myICM.setDLPFcfg((ICM_20948_Internal_Acc | ICM_20948_Internal_Gyr), dlpcfg); - if (retval != ICM_20948_Stat_Ok) + SerialPrintln(F("Enter Gyro Full Scale (0 to 3): ")); + SerialPrintln(F("0: +/- 250dps")); + SerialPrintln(F("1: +/- 500dps")); + SerialPrintln(F("2: +/- 1000dps")); + SerialPrintln(F("3: +/- 2000dps")); + int gfs = getNumber(menuTimeout); //x second timeout + if (gfs < 0 || gfs > 3) + SerialPrintln(F("Error: Out of range")); + else { - SerialPrintln(F("Error: Could not configure the IMU!")); + settings.imuGyroFSS = gfs; + ICM_20948_fss_t FSS; + FSS.a = settings.imuAccFSS; + FSS.g = settings.imuGyroFSS; + ICM_20948_Status_e retval = myICM.setFullScale((ICM_20948_Internal_Acc | ICM_20948_Internal_Gyr), FSS); + if (retval != ICM_20948_Stat_Ok) + { + SerialPrintln(F("Error: Could not configure the IMU!")); + } } } - } - else if ((incoming == 9) && (online.IMU == true)) - { - SerialPrintln(F("Enter Gyro Full Scale (0 to 3): ")); - SerialPrintln(F("0: +/- 250dps")); - SerialPrintln(F("1: +/- 500dps")); - SerialPrintln(F("2: +/- 1000dps")); - SerialPrintln(F("3: +/- 2000dps")); - int gfs = getNumber(menuTimeout); //x second timeout - if (gfs < 0 || gfs > 3) - SerialPrintln(F("Error: Out of range")); - else + else if ((incoming == 10) && (online.IMU == true)) { - settings.imuGyroFSS = gfs; - ICM_20948_fss_t FSS; - FSS.a = settings.imuAccFSS; - FSS.g = settings.imuGyroFSS; - ICM_20948_Status_e retval = myICM.setFullScale((ICM_20948_Internal_Acc | ICM_20948_Internal_Gyr), FSS); + settings.imuGyroDLPF ^= 1; + ICM_20948_Status_e retval = myICM.enableDLPF(ICM_20948_Internal_Gyr, settings.imuGyroDLPF); if (retval != ICM_20948_Stat_Ok) { SerialPrintln(F("Error: Could not configure the IMU!")); } } - } - else if ((incoming == 10) && (online.IMU == true)) - { - settings.imuGyroDLPF ^= 1; - ICM_20948_Status_e retval = myICM.enableDLPF(ICM_20948_Internal_Gyr, settings.imuGyroDLPF); - if (retval != ICM_20948_Stat_Ok) + else if ((incoming == 11) && (online.IMU == true) && (settings.imuGyroDLPF == true)) + { + SerialPrintln(F("Enter Gyro DLPF Bandwidth (0 to 7): ")); + SerialPrintln(F("0: 196.6 (3dB) 229.8 (Nyquist) (Hz)")); + SerialPrintln(F("1: 151.8 (3dB) 187.6 (Nyquist) (Hz)")); + SerialPrintln(F("2: 119.5 (3dB) 154.3 (Nyquist) (Hz)")); + SerialPrintln(F("3: 51.2 (3dB) 73.3 (Nyquist) (Hz)")); + SerialPrintln(F("4: 23.9 (3dB) 35.9 (Nyquist) (Hz)")); + SerialPrintln(F("5: 11.6 (3dB) 17.8 (Nyquist) (Hz)")); + SerialPrintln(F("6: 5.7 (3dB) 8.9 (Nyquist) (Hz)")); + SerialPrintln(F("7: 361.4 (3dB) 376.5 (Nyquist) (Hz)")); + int gfbw = getNumber(menuTimeout); //x second timeout + if (gfbw < 0 || gfbw > 7) + SerialPrintln(F("Error: Out of range")); + else + { + settings.imuGyroDLPFBW = gfbw; + ICM_20948_dlpcfg_t dlpcfg; + dlpcfg.a = settings.imuAccDLPFBW; + dlpcfg.g = settings.imuGyroDLPFBW; + ICM_20948_Status_e retval = myICM.setDLPFcfg((ICM_20948_Internal_Acc | ICM_20948_Internal_Gyr), dlpcfg); + if (retval != ICM_20948_Stat_Ok) + { + SerialPrintln(F("Error: Could not configure the IMU!")); + } + } + } + else if (incoming == 12) { - SerialPrintln(F("Error: Could not configure the IMU!")); + settings.imuUseDMP ^= 1; + restartIMU = true; } + else if (incoming == STATUS_PRESSED_X) + break; + else if (incoming == STATUS_GETNUMBER_TIMEOUT) + break; + else + printUnknown(incoming); } - else if ((incoming == 11) && (online.IMU == true) && (settings.imuGyroDLPF == true)) + else if (settings.imuUseDMP == true) { - SerialPrintln(F("Enter Gyro DLPF Bandwidth (0 to 7): ")); - SerialPrintln(F("0: 196.6 (3dB) 229.8 (Nyquist) (Hz)")); - SerialPrintln(F("1: 151.8 (3dB) 187.6 (Nyquist) (Hz)")); - SerialPrintln(F("2: 119.5 (3dB) 154.3 (Nyquist) (Hz)")); - SerialPrintln(F("3: 51.2 (3dB) 73.3 (Nyquist) (Hz)")); - SerialPrintln(F("4: 23.9 (3dB) 35.9 (Nyquist) (Hz)")); - SerialPrintln(F("5: 11.6 (3dB) 17.8 (Nyquist) (Hz)")); - SerialPrintln(F("6: 5.7 (3dB) 8.9 (Nyquist) (Hz)")); - SerialPrintln(F("7: 361.4 (3dB) 376.5 (Nyquist) (Hz)")); - int gfbw = getNumber(menuTimeout); //x second timeout - if (gfbw < 0 || gfbw > 7) - SerialPrintln(F("Error: Out of range")); - else + if (incoming == 12) { - settings.imuGyroDLPFBW = gfbw; - ICM_20948_dlpcfg_t dlpcfg; - dlpcfg.a = settings.imuAccDLPFBW; - dlpcfg.g = settings.imuGyroDLPFBW; - ICM_20948_Status_e retval = myICM.setDLPFcfg((ICM_20948_Internal_Acc | ICM_20948_Internal_Gyr), dlpcfg); - if (retval != ICM_20948_Stat_Ok) + settings.imuUseDMP ^= 1; + restartIMU = true; + } + else if (incoming == 13) + { + if (settings.imuLogDMPQuat6 == true) { - SerialPrintln(F("Error: Could not configure the IMU!")); + settings.imuLogDMPQuat6 = false; + } + else + { + settings.imuLogDMPQuat6 = true; + settings.imuLogDMPQuat9 = false; + } + restartIMU = true; + } + else if (incoming == 14) + { + if (settings.imuLogDMPQuat9 == true) + { + settings.imuLogDMPQuat9 = false; + } + else + { + settings.imuLogDMPQuat9 = true; + settings.imuLogDMPQuat6 = false; } + restartIMU = true; + } + else if (incoming == 15) + { + settings.imuLogDMPAccel ^= 1; + restartIMU = true; } + else if (incoming == 16) + { + settings.imuLogDMPGyro ^= 1; + restartIMU = true; + } + else if (incoming == 17) + { + settings.imuLogDMPCpass ^= 1; + restartIMU = true; + } + else if (incoming == STATUS_PRESSED_X) + break; + else if (incoming == STATUS_GETNUMBER_TIMEOUT) + break; + else + printUnknown(incoming); } else if (incoming == STATUS_PRESSED_X) break; @@ -305,4 +402,5 @@ void menuIMU() else printUnknown(incoming); } + return (restartIMU); } diff --git a/Firmware/OpenLog_Artemis/menuMain.ino b/Firmware/OpenLog_Artemis/menuMain.ino index 0cc2202..5809998 100644 --- a/Firmware/OpenLog_Artemis/menuMain.ino +++ b/Firmware/OpenLog_Artemis/menuMain.ino @@ -2,6 +2,7 @@ //If user doesn't respond within a few seconds, return to main loop void menuMain() { + bool restartIMU = false; while (1) { SerialPrintln(F("")); @@ -42,7 +43,7 @@ void menuMain() else if (incoming == '2') menuTimeStamp(); else if (incoming == '3') - menuIMU(); + restartIMU = menuIMU(); else if ((incoming == '4') && (settings.useTxRxPinsForTerminal == false)) menuSerialLogging(); else if (incoming == '5') @@ -161,6 +162,9 @@ void menuMain() configureQwiicDevices(); //Reconfigure the qwiic devices in case any settings have changed + if (restartIMU == true) + beginIMU(); // Restart the IMU if required + while (Serial.available()) Serial.read(); //Empty buffer of any newline chars if (settings.useTxRxPinsForTerminal == true) diff --git a/Firmware/OpenLog_Artemis/menuPower.ino b/Firmware/OpenLog_Artemis/menuPower.ino index f3721da..3f5c8d3 100644 --- a/Firmware/OpenLog_Artemis/menuPower.ino +++ b/Firmware/OpenLog_Artemis/menuPower.ino @@ -39,24 +39,33 @@ void menuPower() } else if (incoming == '2') { - if (settings.useGPIO32ForStopLogging == true) + if (settings.identifyBioSensorHubs == false) { - // Disable stop logging - settings.useGPIO32ForStopLogging = false; - detachInterrupt(digitalPinToInterrupt(PIN_STOP_LOGGING)); // Disable the interrupt - pinMode(PIN_STOP_LOGGING, INPUT); // Remove the pull-up - stopLoggingSeen = false; // Make sure the flag is clear + if (settings.useGPIO32ForStopLogging == true) + { + // Disable stop logging + settings.useGPIO32ForStopLogging = false; + detachInterrupt(digitalPinToInterrupt(PIN_STOP_LOGGING)); // Disable the interrupt + pinMode(PIN_STOP_LOGGING, INPUT); // Remove the pull-up + stopLoggingSeen = false; // Make sure the flag is clear + } + else + { + // Enable stop logging + settings.useGPIO32ForStopLogging = true; + pinMode(PIN_STOP_LOGGING, INPUT_PULLUP); + delay(1); // Let the pin stabilize + attachInterrupt(digitalPinToInterrupt(PIN_STOP_LOGGING), stopLoggingISR, FALLING); // Enable the interrupt + stopLoggingSeen = false; // Make sure the flag is clear + settings.logA32 = false; // Disable analog logging on pin 32 + } } else { - // Enable stop logging - settings.useGPIO32ForStopLogging = true; - pinMode(PIN_STOP_LOGGING, INPUT_PULLUP); - delay(1); // Let the pin stabilize - attachInterrupt(digitalPinToInterrupt(PIN_STOP_LOGGING), stopLoggingISR, FALLING); // Enable the interrupt - stopLoggingSeen = false; // Make sure the flag is clear - settings.logA32 = false; // Disable analog logging on pin 32 - } + SerialPrintln(F("")); + SerialPrintln(F("Stop logging via pin 32 is not possible. \"Detect Bio Sensor Pulse Oximeter\" is enabled.")); + SerialPrintln(F("")); + } } #if(HARDWARE_VERSION_MAJOR >= 1) else if (incoming == '3') diff --git a/Firmware/OpenLog_Artemis/menuTerminal.ino b/Firmware/OpenLog_Artemis/menuTerminal.ino index da2059e..d8fe4d6 100644 --- a/Firmware/OpenLog_Artemis/menuTerminal.ino +++ b/Firmware/OpenLog_Artemis/menuTerminal.ino @@ -132,6 +132,12 @@ void menuLogRate() SerialPrintf3("%02d:%02d\r\n", slowHour, slowMin); } + if ((settings.useGPIO11ForTrigger == false) && (settings.usBetweenReadings >= maxUsBeforeSleep)) + { + SerialPrint(F("21) Minimum awake time between sleeps: ")); + SerialPrintf2("%dms\r\n", settings.minimumAwakeTimeMillis); + } + SerialPrintln(F("x) Exit")); int incoming = getNumber(menuTimeout); //Timeout after x seconds @@ -186,7 +192,16 @@ void menuLogRate() if (tempSeconds < 1 || tempSeconds > 129600) SerialPrintln(F("Error: logging interval out of range")); else + { settings.usBetweenReadings = 1000000ULL * ((uint64_t)tempSeconds); + + if (settings.usBetweenReadings >= maxUsBeforeSleep) // Check if minimumAwakeTimeMillis needs to be reduced + { + // Limit minimumAwakeTimeMillis to usBetweenReadings minus one second + if (settings.minimumAwakeTimeMillis > ((settings.usBetweenReadings / 1000ULL) - 1000ULL)) + settings.minimumAwakeTimeMillis = (unsigned long)((settings.usBetweenReadings / 1000ULL) - 1000ULL); + } + } } } else if (incoming == 6) @@ -243,45 +258,63 @@ void menuLogRate() settings.frequentFileAccessTimestamps ^= 1; else if (incoming == 12) { - if (settings.useGPIO11ForTrigger == true) + if (settings.identifyBioSensorHubs == false) { - // Disable triggering - settings.useGPIO11ForTrigger = false; - detachInterrupt(digitalPinToInterrupt(PIN_TRIGGER)); // Disable the interrupt - pinMode(PIN_TRIGGER, INPUT); // Remove the pull-up - triggerEdgeSeen = false; // Make sure the flag is clear + if (settings.useGPIO11ForTrigger == true) + { + // Disable triggering + settings.useGPIO11ForTrigger = false; + detachInterrupt(digitalPinToInterrupt(PIN_TRIGGER)); // Disable the interrupt + pinMode(PIN_TRIGGER, INPUT); // Remove the pull-up + triggerEdgeSeen = false; // Make sure the flag is clear + } + else + { + // Enable triggering + settings.useGPIO11ForTrigger = true; + pinMode(PIN_TRIGGER, INPUT_PULLUP); + delay(1); // Let the pin stabilize + if (settings.fallingEdgeTrigger == true) + attachInterrupt(digitalPinToInterrupt(PIN_TRIGGER), triggerPinISR, FALLING); // Enable the interrupt + else + attachInterrupt(digitalPinToInterrupt(PIN_TRIGGER), triggerPinISR, RISING); // Enable the interrupt + triggerEdgeSeen = false; // Make sure the flag is clear + settings.logA11 = false; // Disable analog logging on pin 11 + settings.logMaxRate = false; // Disable max rate logging + settings.useGPIO11ForFastSlowLogging = false; + settings.useRTCForFastSlowLogging = false; + } } else { - // Enable triggering - settings.useGPIO11ForTrigger = true; - pinMode(PIN_TRIGGER, INPUT_PULLUP); - delay(1); // Let the pin stabilize - if (settings.fallingEdgeTrigger == true) - attachInterrupt(digitalPinToInterrupt(PIN_TRIGGER), triggerPinISR, FALLING); // Enable the interrupt - else - attachInterrupt(digitalPinToInterrupt(PIN_TRIGGER), triggerPinISR, RISING); // Enable the interrupt - triggerEdgeSeen = false; // Make sure the flag is clear - settings.logA11 = false; // Disable analog logging on pin 11 - settings.logMaxRate = false; // Disable max rate logging - settings.useGPIO11ForFastSlowLogging = false; - settings.useRTCForFastSlowLogging = false; - } + SerialPrintln(F("")); + SerialPrintln(F("Triggering on pin 11 is not possible. \"Detect Bio Sensor Pulse Oximeter\" is enabled.")); + SerialPrintln(F("")); + } } else if (incoming == 13) { - if (settings.useGPIO11ForTrigger == true) // If interrupts are enabled, we need to disable and then re-enable + if (settings.identifyBioSensorHubs == false) { - detachInterrupt(digitalPinToInterrupt(PIN_TRIGGER)); // Disable the interrupt - settings.fallingEdgeTrigger ^= 1; // Invert the flag - if (settings.fallingEdgeTrigger == true) - attachInterrupt(digitalPinToInterrupt(PIN_TRIGGER), triggerPinISR, FALLING); // Enable the interrupt + if (settings.useGPIO11ForTrigger == true) // If interrupts are enabled, we need to disable and then re-enable + { + detachInterrupt(digitalPinToInterrupt(PIN_TRIGGER)); // Disable the interrupt + settings.fallingEdgeTrigger ^= 1; // Invert the flag + if (settings.fallingEdgeTrigger == true) + attachInterrupt(digitalPinToInterrupt(PIN_TRIGGER), triggerPinISR, FALLING); // Enable the interrupt + else + attachInterrupt(digitalPinToInterrupt(PIN_TRIGGER), triggerPinISR, RISING); // Enable the interrupt + triggerEdgeSeen = false; // Make sure the flag is clear + } else - attachInterrupt(digitalPinToInterrupt(PIN_TRIGGER), triggerPinISR, RISING); // Enable the interrupt - triggerEdgeSeen = false; // Make sure the flag is clear + settings.fallingEdgeTrigger ^= 1; // Interrupt is not currently enabled so simply invert the flag } else - settings.fallingEdgeTrigger ^= 1; // Interrupt is not currently enabled so simply invert the flag + { + SerialPrintln(F("")); + SerialPrintln(F("Triggering on pin 11 is not possible. \"Detect Bio Sensor Pulse Oximeter\" is enabled.")); + SerialPrintln(F("")); + } } else if (incoming == 14) { @@ -323,32 +356,49 @@ void menuLogRate() } else if (incoming == 15) { - if (settings.useGPIO11ForFastSlowLogging == false) // If the user is trying to enable Pin 11 fast / slow logging + if (settings.identifyBioSensorHubs == false) { - settings.useGPIO11ForFastSlowLogging = true; - settings.useRTCForFastSlowLogging = false; - settings.logA11 = false; // Disable analog logging on pin 11 - pinMode(PIN_TRIGGER, INPUT_PULLUP); - delay(1); // Let the pin stabilize - // Disable triggering - if (settings.useGPIO11ForTrigger == true) + if (settings.useGPIO11ForFastSlowLogging == false) // If the user is trying to enable Pin 11 fast / slow logging { - detachInterrupt(digitalPinToInterrupt(PIN_TRIGGER)); // Disable the interrupt - triggerEdgeSeen = false; // Make sure the flag is clear + settings.useGPIO11ForFastSlowLogging = true; + settings.useRTCForFastSlowLogging = false; + settings.logA11 = false; // Disable analog logging on pin 11 + pinMode(PIN_TRIGGER, INPUT_PULLUP); + delay(1); // Let the pin stabilize + // Disable triggering + if (settings.useGPIO11ForTrigger == true) + { + detachInterrupt(digitalPinToInterrupt(PIN_TRIGGER)); // Disable the interrupt + triggerEdgeSeen = false; // Make sure the flag is clear + } + settings.useGPIO11ForTrigger = false; + } + else // If the user is trying to disable Pin 11 fast / slow logging + { + settings.useGPIO11ForFastSlowLogging = false; + pinMode(PIN_TRIGGER, INPUT); // Remove the pull-up } - settings.useGPIO11ForTrigger = false; } - else // If the user is trying to disable Pin 11 fast / slow logging + else { - settings.useGPIO11ForFastSlowLogging = false; - pinMode(PIN_TRIGGER, INPUT); // Remove the pull-up - } + SerialPrintln(F("")); + SerialPrintln(F("Fast / slow logging via pin 11 is not possible. \"Detect Bio Sensor Pulse Oximeter\" is enabled.")); + SerialPrintln(F("")); + } } else if (incoming == 16) { - if (settings.useGPIO11ForFastSlowLogging == true) + if (settings.identifyBioSensorHubs == false) + { + if (settings.useGPIO11ForFastSlowLogging == true) + { + settings.slowLoggingWhenPin11Is ^= 1; + } + } + else // If the user is trying to disable Pin 11 fast / slow logging { - settings.slowLoggingWhenPin11Is ^= 1; + settings.useGPIO11ForFastSlowLogging = false; + pinMode(PIN_TRIGGER, INPUT); // Remove the pull-up } } else if (incoming == 17) @@ -431,6 +481,27 @@ void menuLogRate() } } } + else if (incoming == 21) + { + if (settings.useGPIO11ForTrigger == false) + { + if (settings.usBetweenReadings >= maxUsBeforeSleep) + { + // Limit minimumAwakeTimeMillis to usBetweenReadings minus one second + unsigned long maxAwakeMillis = (unsigned long)((settings.usBetweenReadings / 1000ULL) - 1000ULL); + SerialPrintf2("Enter minimum awake time (ms: 0 to %d): : ", maxAwakeMillis); + int newAwake = getNumber(menuTimeout); //Timeout after x seconds + if (newAwake < 0 || newAwake > maxAwakeMillis) + { + SerialPrintln(F("Error: awake time out of range")); + } + else + { + settings.minimumAwakeTimeMillis = newAwake; + } + } + } + } else if (incoming == STATUS_PRESSED_X) return; else if (incoming == STATUS_GETNUMBER_TIMEOUT) diff --git a/Firmware/OpenLog_Artemis/nvm.ino b/Firmware/OpenLog_Artemis/nvm.ino index 1d54337..09c7c0a 100644 --- a/Firmware/OpenLog_Artemis/nvm.ino +++ b/Firmware/OpenLog_Artemis/nvm.ino @@ -57,7 +57,15 @@ void recordSystemSettingsToFile() if (sd.exists("OLA_settings.txt")) sd.remove("OLA_settings.txt"); - SdFile settingsFile; //FAT32 + #if SD_FAT_TYPE == 1 + File32 settingsFile; + #elif SD_FAT_TYPE == 2 + ExFile settingsFile; + #elif SD_FAT_TYPE == 3 + FsFile settingsFile; + #else // SD_FAT_TYPE == 0 + File settingsFile; + #endif // SD_FAT_TYPE if (settingsFile.open("OLA_settings.txt", O_CREAT | O_APPEND | O_WRITE) == false) { SerialPrintln(F("Failed to create settings file")); @@ -164,6 +172,14 @@ void recordSystemSettingsToFile() settingsFile.println("slowLoggingStartMOD=" + (String)settings.slowLoggingStartMOD); settingsFile.println("slowLoggingStopMOD=" + (String)settings.slowLoggingStopMOD); settingsFile.println("resetOnZeroDeviceCount=" + (String)settings.resetOnZeroDeviceCount); + settingsFile.println("imuUseDMP=" + (String)settings.imuUseDMP); + settingsFile.println("imuLogDMPQuat6=" + (String)settings.imuLogDMPQuat6); + settingsFile.println("imuLogDMPQuat9=" + (String)settings.imuLogDMPQuat9); + settingsFile.println("imuLogDMPAccel=" + (String)settings.imuLogDMPAccel); + settingsFile.println("imuLogDMPGyro=" + (String)settings.imuLogDMPGyro); + settingsFile.println("imuLogDMPCpass=" + (String)settings.imuLogDMPCpass); + settingsFile.println("minimumAwakeTimeMillis=" + (String)settings.minimumAwakeTimeMillis); + settingsFile.println("identifyBioSensorHubs=" + (String)settings.identifyBioSensorHubs); updateDataFileAccess(&settingsFile); // Update the file access time & date settingsFile.close(); } @@ -424,6 +440,22 @@ bool parseLine(char* str) { settings.slowLoggingStopMOD = d; else if (strcmp(settingName, "resetOnZeroDeviceCount") == 0) settings.resetOnZeroDeviceCount = d; + else if (strcmp(settingName, "imuUseDMP") == 0) + settings.imuUseDMP = d; + else if (strcmp(settingName, "imuLogDMPQuat6") == 0) + settings.imuLogDMPQuat6 = d; + else if (strcmp(settingName, "imuLogDMPQuat9") == 0) + settings.imuLogDMPQuat9 = d; + else if (strcmp(settingName, "imuLogDMPAccel") == 0) + settings.imuLogDMPAccel = d; + else if (strcmp(settingName, "imuLogDMPGyro") == 0) + settings.imuLogDMPGyro = d; + else if (strcmp(settingName, "imuLogDMPCpass") == 0) + settings.imuLogDMPCpass = d; + else if (strcmp(settingName, "minimumAwakeTimeMillis") == 0) + settings.minimumAwakeTimeMillis = d; + else if (strcmp(settingName, "identifyBioSensorHubs") == 0) + settings.identifyBioSensorHubs = d; else { SerialPrintf2("Unknown setting %s. Ignoring...\r\n", settingName); @@ -441,7 +473,15 @@ void recordDeviceSettingsToFile() if (sd.exists("OLA_deviceSettings.txt")) sd.remove("OLA_deviceSettings.txt"); - SdFile settingsFile; //FAT32 + #if SD_FAT_TYPE == 1 + File32 settingsFile; + #elif SD_FAT_TYPE == 2 + ExFile settingsFile; + #elif SD_FAT_TYPE == 3 + FsFile settingsFile; + #else // SD_FAT_TYPE == 0 + File settingsFile; + #endif // SD_FAT_TYPE if (settingsFile.open("OLA_deviceSettings.txt", O_CREAT | O_APPEND | O_WRITE) == false) { SerialPrintln(F("Failed to create device settings file")); @@ -691,6 +731,7 @@ void recordDeviceSettingsToFile() case DEVICE_PRESSURE_SDP3X: { struct_SDP3X *nodeSetting = (struct_SDP3X *)temp->configPtr; + settingsFile.println((String)base + "log=" + nodeSetting->log); settingsFile.println((String)base + "logPressure=" + nodeSetting->logPressure); settingsFile.println((String)base + "logTemperature=" + nodeSetting->logTemperature); settingsFile.println((String)base + "massFlow=" + nodeSetting->massFlow); @@ -710,6 +751,28 @@ void recordDeviceSettingsToFile() settingsFile.println((String)base + "conversion=" + nodeSetting->conversion); } break; +// case DEVICE_QWIIC_BUTTON: +// { +// struct_QWIIC_BUTTON *nodeSetting = (struct_QWIIC_BUTTON *)temp->configPtr; +// settingsFile.println((String)base + "log=" + nodeSetting->log); +// settingsFile.println((String)base + "logPressed=" + nodeSetting->logPressed); +// settingsFile.println((String)base + "logClicked=" + nodeSetting->logClicked); +// settingsFile.println((String)base + "toggleLEDOnClick=" + nodeSetting->toggleLEDOnClick); +// settingsFile.println((String)base + "ledBrightness=" + nodeSetting->ledBrightness); +// } +// break; + case DEVICE_BIO_SENSOR_HUB: + { + struct_BIO_SENSOR_HUB *nodeSetting = (struct_BIO_SENSOR_HUB *)temp->configPtr; + settingsFile.println((String)base + "log=" + nodeSetting->log); + settingsFile.println((String)base + "logHeartrate=" + nodeSetting->logHeartrate); + settingsFile.println((String)base + "logConfidence=" + nodeSetting->logConfidence); + settingsFile.println((String)base + "logOxygen=" + nodeSetting->logOxygen); + settingsFile.println((String)base + "logStatus=" + nodeSetting->logStatus); + settingsFile.println((String)base + "logExtendedStatus=" + nodeSetting->logExtendedStatus); + settingsFile.println((String)base + "logRValue=" + nodeSetting->logRValue); + } + break; default: SerialPrintf2("recordSettingsToFile Unknown device: %s\r\n", base); //settingsFile.println((String)base + "=UnknownDeviceSettings"); @@ -732,7 +795,16 @@ bool loadDeviceSettingsFromFile() { if (sd.exists("OLA_deviceSettings.txt")) { - SdFile settingsFile; //FAT32 + #if SD_FAT_TYPE == 1 + File32 settingsFile; + #elif SD_FAT_TYPE == 2 + ExFile settingsFile; + #elif SD_FAT_TYPE == 3 + FsFile settingsFile; + #else // SD_FAT_TYPE == 0 + File settingsFile; + #endif // SD_FAT_TYPE + if (settingsFile.open("OLA_deviceSettings.txt", O_READ) == false) { SerialPrintln(F("Failed to open device settings file")); @@ -1288,6 +1360,44 @@ bool parseDeviceLine(char* str) { SerialPrintf2("Unknown device setting: %s\r\n", deviceSettingName); } break; +// case DEVICE_QWIIC_BUTTON: +// { +// struct_QWIIC_BUTTON *nodeSetting = (struct_QWIIC_BUTTON *)deviceConfigPtr; //Create a local pointer that points to same spot as node does +// if (strcmp(deviceSettingName, "log") == 0) +// nodeSetting->log = d; +// else if (strcmp(deviceSettingName, "logPressed") == 0) +// nodeSetting->logPressed = d; +// else if (strcmp(deviceSettingName, "logClicked") == 0) +// nodeSetting->logClicked = d; +// else if (strcmp(deviceSettingName, "toggleLEDOnClick") == 0) +// nodeSetting->toggleLEDOnClick = d; +// else if (strcmp(deviceSettingName, "ledBrightness") == 0) +// nodeSetting->ledBrightness = d; +// else +// SerialPrintf2("Unknown device setting: %s\r\n", deviceSettingName); +// } +// break; + case DEVICE_BIO_SENSOR_HUB: + { + struct_BIO_SENSOR_HUB *nodeSetting = (struct_BIO_SENSOR_HUB *)deviceConfigPtr; //Create a local pointer that points to same spot as node does + if (strcmp(deviceSettingName, "log") == 0) + nodeSetting->log = d; + else if (strcmp(deviceSettingName, "logHeartrate") == 0) + nodeSetting->logHeartrate = d; + else if (strcmp(deviceSettingName, "logConfidence") == 0) + nodeSetting->logConfidence = d; + else if (strcmp(deviceSettingName, "logOxygen") == 0) + nodeSetting->logOxygen = d; + else if (strcmp(deviceSettingName, "logStatus") == 0) + nodeSetting->logStatus = d; + else if (strcmp(deviceSettingName, "logExtendedStatus") == 0) + nodeSetting->logExtendedStatus = d; + else if (strcmp(deviceSettingName, "logRValue") == 0) + nodeSetting->logRValue = d; + else + SerialPrintf2("Unknown device setting: %s\r\n", deviceSettingName); + } + break; default: SerialPrintf2("Unknown device type: %d\r\n", deviceType); SerialFlush(); diff --git a/Firmware/OpenLog_Artemis/settings.h b/Firmware/OpenLog_Artemis/settings.h index 6f7f5a6..b60b726 100644 --- a/Firmware/OpenLog_Artemis/settings.h +++ b/Firmware/OpenLog_Artemis/settings.h @@ -26,6 +26,8 @@ typedef enum DEVICE_VOC_SGP40, DEVICE_PRESSURE_SDP3X, DEVICE_PRESSURE_MS5837, +// DEVICE_QWIIC_BUTTON, + DEVICE_BIO_SENSOR_HUB, DEVICE_TOTAL_DEVICES, //Marks the end, used to iterate loops DEVICE_UNKNOWN_DEVICE, @@ -313,6 +315,27 @@ struct struct_MS5837 { unsigned long powerOnDelayMillis = minimumQwiicPowerOnDelay; // Wait for at least this many millis before communicating with this device. Increase if required! }; +//struct struct_QWIIC_BUTTON { +// bool log = true; +// bool logPressed = true; +// bool logClicked = true; +// bool toggleLEDOnClick = true; +// bool ledState = false; // Do not store in NVM +// uint8_t ledBrightness = 255; +// unsigned long powerOnDelayMillis = 3000; // Wait for at least this many millis before communicating with this device. Increase if required! +//}; + +struct struct_BIO_SENSOR_HUB { + bool log = true; + bool logHeartrate = true; + bool logConfidence = true; + bool logOxygen = true; + bool logStatus = true; + bool logExtendedStatus = true; + bool logRValue = true; + unsigned long powerOnDelayMillis = minimumQwiicPowerOnDelay; // Wait for at least this many millis before communicating with this device. Increase if required! +}; + //This is all the settings that can be set on OpenLog. It's recorded to NVM and the config file. struct struct_settings { @@ -392,6 +415,14 @@ struct struct_settings { int slowLoggingStartMOD = 1260; // Start slow logging at this many Minutes Of Day. Default to 21:00 int slowLoggingStopMOD = 420; // Stop slow logging at this many Minutes Of Day. Default to 07:00 bool resetOnZeroDeviceCount = false; // A work-around for I2C bus crashes. Enable this via the debug menu. + bool imuUseDMP = false; // If true, enable the DMP + bool imuLogDMPQuat6 = true; // If true, log INV_ICM20948_SENSOR_GAME_ROTATION_VECTOR (Quat6) + bool imuLogDMPQuat9 = false; // If true, log INV_ICM20948_SENSOR_ROTATION_VECTOR (Quat9 + Heading Accuracy) + bool imuLogDMPAccel = false; // If true, log INV_ICM20948_SENSOR_RAW_ACCELEROMETER + bool imuLogDMPGyro = false; // If true, log INV_ICM20948_SENSOR_RAW_GYROSCOPE + bool imuLogDMPCpass = false; // If true, log INV_ICM20948_SENSOR_MAGNETIC_FIELD_UNCALIBRATED + unsigned long minimumAwakeTimeMillis = 0; // Set to greater than zero to keep the Artemis awake for this long between sleeps + bool identifyBioSensorHubs = false; // If true, Bio Sensor Hubs (Pulse Oximeters) will be included in autoDetect (requires exclusive use of pins 32 and 11) } settings; //These are the devices on board OpenLog that may be on or offline. diff --git a/Firmware/Test Sketches/GNSS_Data_Logging/GNSS_Data_Logging.ino b/Firmware/Test Sketches/GNSS_Data_Logging/GNSS_Data_Logging.ino new file mode 100644 index 0000000..780661c --- /dev/null +++ b/Firmware/Test Sketches/GNSS_Data_Logging/GNSS_Data_Logging.ino @@ -0,0 +1,551 @@ +/* + This is an example for OpenLog Artemis - based on a datalogging example from the SparkFun u-blox library: + + Configuring the GNSS to automatically send RXM SFRBX and RAWX reports over I2C and log them to file on SD card + + By: Paul Clark + SparkFun Electronics + Date: April 23rd, 2021 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + This example shows how to configure the u-blox GNSS to send RXM SFRBX and RAWX reports automatically + and log the data to SD card in UBX format + + ** Please note: this example will only work with u-blox ADR or High Precision GNSS or Time Sync products ** + + Data is logged in u-blox UBX format. Please see the u-blox protocol specification for more details. + You can replay and analyze the data using u-center: + https://www.u-blox.com/en/product/u-center + Or you can use (e.g.) RTKLIB to analyze the data and extract your precise location or produce + Post-Processed Kinematic data: + https://rtklibexplorer.wordpress.com/ + http://rtkexplorer.com/downloads/rtklib-code/ + + Hardware Connections: + Connect an antenna to your GNSS board if required. + Connect your GNSS board to OpenLog Artemis using a Qwiic cable. + Insert a formatted micro-SD card into the socket on OpenLog Artemis. + Connect OpenLog Artemis to your computer using a USB-C cable. + Ensure you have the SparkFun Apollo3 boards installed: http://boardsmanager/All#SparkFun_Apollo3 + This code has been tested using version 1.2.1 of the Apollo3 boards on Arduino IDE 1.8.13. + Select "SparkFun Artemis ATP" as the board type. + Press upload to upload the code onto the Artemis. + Open the Serial Monitor at 115200 baud to see the output. + + Pull pin 32 low to stop logging. Logging will also stop on low battery. + + To minimise I2C bus errors, it is a good idea to open the I2C pull-up split pad links on + the u-blox module breakout. + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 +*/ + +#include + +#include //Click here to get the library: http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GNSS myGNSS; + +#include //SdFat v2.0.6 by Bill Greiman. Click here to get the library: http://librarymanager/All#SdFat_exFAT + +#define SD_FAT_TYPE 3 // SD_FAT_TYPE = 0 for SdFat/File, 1 for FAT16/FAT32, 2 for exFAT, 3 for FAT16/FAT32 and exFAT. +#define SD_CONFIG SdSpiConfig(PIN_MICROSD_CHIP_SELECT, SHARED_SPI, SD_SCK_MHZ(24)) // 24MHz + +#if SD_FAT_TYPE == 1 +SdFat32 sd; +File32 myFile; //File that all GNSS data is written to +#elif SD_FAT_TYPE == 2 +SdExFat sd; +ExFile myFile; //File that all GNSS data is written to +#elif SD_FAT_TYPE == 3 +SdFs sd; +FsFile myFile; //File that all GNSS data is written to +#else // SD_FAT_TYPE == 0 +SdFat sd; +File myFile; //File that all GNSS data is written to +#endif // SD_FAT_TYPE + +// OLA Specifics: + +//Setup Qwiic Port +#include +TwoWire qwiic(1); //Will use pads 8/9 + +//Define the pin functions +//Depends on hardware version. This can be found as a marking on the PCB. +//x04 was the SparkX 'black' version. +//v10 was the first red version. +#define HARDWARE_VERSION_MAJOR 1 +#define HARDWARE_VERSION_MINOR 0 + +#if(HARDWARE_VERSION_MAJOR == 0 && HARDWARE_VERSION_MINOR == 4) +const byte PIN_MICROSD_CHIP_SELECT = 10; +const byte PIN_IMU_POWER = 22; +#elif(HARDWARE_VERSION_MAJOR == 1 && HARDWARE_VERSION_MINOR == 0) +const byte PIN_MICROSD_CHIP_SELECT = 23; +const byte PIN_IMU_POWER = 27; +const byte PIN_PWR_LED = 29; +const byte PIN_VREG_ENABLE = 25; +const byte PIN_VIN_MONITOR = 34; // VIN/3 (1M/2M - will require a correction factor) +#endif + +const byte PIN_POWER_LOSS = 3; +const byte PIN_MICROSD_POWER = 15; +const byte PIN_QWIIC_POWER = 18; +const byte PIN_STAT_LED = 19; +const byte PIN_IMU_INT = 37; +const byte PIN_IMU_CHIP_SELECT = 44; +const byte PIN_STOP_LOGGING = 32; +const byte BREAKOUT_PIN_32 = 32; +const byte BREAKOUT_PIN_TX = 12; +const byte BREAKOUT_PIN_RX = 13; +const byte BREAKOUT_PIN_11 = 11; +const byte PIN_QWIIC_SCL = 8; +const byte PIN_QWIIC_SDA = 9; + +// Globals and Consts + +float vinCorrectionFactor = 1.47; //Correction factor for the VIN measurement; to compensate for the divider impedance +float lowBatteryThreshold = 3.4; // Low battery voltage threshold (Volts) +int lowBatteryReadings = 0; // Count how many times the battery voltage has read low +const int lowBatteryReadingsLimit = 10; // Don't declare the battery voltage low until we have had this many consecutive low readings (to reject sampling noise) +const int sdPowerDownDelay = 100; //Delay for this many ms before turning off the SD card power +bool powerLossSeen = false; //Interrupt flag for power loss detection +bool stopLoggingSeen = false; //Interrupt flag for stop logging detection + +// Data Logging Specifics: + +#define sdWriteSize 512 // Write data to the SD card in blocks of 512 bytes +#define fileBufferSize 16384 // Allocate 16KBytes of RAM for UBX message storage + +unsigned long lastPrint; // Record when the last Serial print took place +unsigned long bytesWritten = 0; // Record how many bytes have been written to SD card + +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +void setup() +{ + //If 3.3V rail drops below 3V, system will power down and maintain RTC + pinMode(PIN_POWER_LOSS, INPUT); // BD49K30G-TL has CMOS output and does not need a pull-up + + delay(1); // Let PIN_POWER_LOSS stabilize + + if (digitalRead(PIN_POWER_LOSS) == LOW) powerLossISR(); //Check PIN_POWER_LOSS just in case we missed the falling edge + attachInterrupt(digitalPinToInterrupt(PIN_POWER_LOSS), powerLossISR, FALLING); + + powerLEDOn(); // Turn the power LED on - if the hardware supports it + + pinMode(PIN_STAT_LED, OUTPUT); // Flash the STAT LED during SD writes + digitalWrite(PIN_STAT_LED, LOW); + + Serial.begin(115200); + + SPI.begin(); + + //while (!Serial); //Wait for user to open terminal + Serial.println("SparkFun OpenLog Artemis Example"); + Serial.println("u-blox GNSS RXM RAWX and SFRBS Logging"); + + analogReadResolution(14); //Increase ADC resolution from default of 10-bit + + beginQwiic(); // Turn the qwiic power on as early as possible + + beginSD(); // Enable power for the SD card + + enableCIPOpullUp(); // Enable CIPO pull-up after beginSD + + imuPowerOff(); // We're not using the IMU so turn it off + + delay(2500); // Give the GNSS time to power up + + pinMode(PIN_STOP_LOGGING, INPUT_PULLUP); + delay(1); // Let the pin stabilize + attachInterrupt(digitalPinToInterrupt(PIN_STOP_LOGGING), stopLoggingISR, FALLING); // Enable the stop logging interrupt + + Serial.println("Initializing SD card..."); + + // See if the card is present and can be initialized: + if (!sd.begin(SD_CONFIG)) + { + Serial.println("Card failed, or not present. Freezing..."); + // don't do anything more: + while (1); + } + Serial.println("SD card initialized."); + + // Create or open a file called "RXM_RAWX.ubx" on the SD card. + // If the file already exists, the new data is appended to the end of the file. + myFile.open("RXM_RAWX.ubx", FILE_WRITE); + if(!myFile) + { + Serial.println(F("Failed to create UBX data file! Freezing...")); + while (1); + } + + //myGNSS.enableDebugging(); // Uncomment this line to enable lots of helpful GNSS debug messages on Serial + //myGNSS.enableDebugging(Serial, true); // Or, uncomment this line to enable only the important GNSS debug messages on Serial + + myGNSS.disableUBX7Fcheck(); // RAWX data can legitimately contain 0x7F, so we need to disable the "7F" check in checkUbloxI2C + + // RAWX messages can be over 2KBytes in size, so we need to make sure we allocate enough RAM to hold all the data. + // SD cards can occasionally 'hiccup' and a write takes much longer than usual. The buffer needs to be big enough + // to hold the backlog of data if/when this happens. + // getMaxFileBufferAvail will tell us the maximum number of bytes which the file buffer has contained. + myGNSS.setFileBufferSize(fileBufferSize); // setFileBufferSize must be called _before_ .begin + + if (myGNSS.begin(qwiic) == false) //Connect to the u-blox module using Qwiic port + { + Serial.println(F("u-blox GNSS not detected at default I2C address. Please check wiring. Freezing...")); + while (1); + } + + // Uncomment the next line if you want to reset your module back to the default settings with 1Hz navigation rate + // (This will also disable any "auto" messages that were enabled and saved by other examples and reduce the load on the I2C bus) + //myGNSS.factoryDefault(); delay(5000); + + myGNSS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + myGNSS.saveConfigSelective(VAL_CFG_SUBSEC_IOPORT); //Save (only) the communications port settings to flash and BBR + + myGNSS.setNavigationFrequency(1); //Produce one navigation solution per second (that's plenty for Precise Point Positioning) + + + myGNSS.setAutoRXMSFRBX(true, false); // Enable automatic RXM SFRBX messages: without callback; without implicit update + myGNSS.logRXMSFRBX(); // Enable RXM SFRBX data logging + + + myGNSS.setAutoRXMRAWX(true, false); // Enable automatic RXM RAWX messages: without callback; without implicit update + myGNSS.logRXMRAWX(); // Enable RXM RAWX data logging + + + // If you want to log additional data, call the associated setAuto and log functions here + + + while (Serial.available()) // Make sure the Serial buffer is empty + { + Serial.read(); + } + + Serial.println(F("Press any key to stop logging.")); + + lastPrint = millis(); // Initialize lastPrint +} + +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +void loop() +{ + checkBattery(); // Check for low battery + + // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + + if ((powerLossSeen) || (stopLoggingSeen) || (Serial.available() > 0)) // Check for power loss or stop logging interrupts + { + stopLogging(); + } + + // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + + myGNSS.checkUblox(); // Check for the arrival of new data and process it. + + // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + + while (myGNSS.fileBufferAvailable() >= sdWriteSize) // Check to see if we have at least sdWriteSize waiting in the buffer + { + digitalWrite(PIN_STAT_LED, HIGH); // Flash PIN_STAT_LED each time we write to the SD card + + uint8_t myBuffer[sdWriteSize]; // Create our own buffer to hold the data while we write it to SD card + + myGNSS.extractFileBufferData((uint8_t *)&myBuffer, sdWriteSize); // Extract exactly sdWriteSize bytes from the UBX file buffer and put them into myBuffer + + myFile.write(myBuffer, sdWriteSize); // Write exactly sdWriteSize bytes from myBuffer to the ubxDataFile on the SD card + + bytesWritten += sdWriteSize; // Update bytesWritten + + // In case the SD writing is slow or there is a lot of data to write, keep checking for the arrival of new data + myGNSS.checkUblox(); // Check for the arrival of new data and process it. + + digitalWrite(PIN_STAT_LED, LOW); // Turn PIN_STAT_LED off again + } + + // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + + if (millis() > (lastPrint + 1000)) // Print bytesWritten once per second + { + Serial.print(F("The number of bytes written to SD card is ")); // Print how many bytes have been written to SD card + Serial.println(bytesWritten); + + uint16_t maxBufferBytes = myGNSS.getMaxFileBufferAvail(); // Get how full the file buffer has been (not how full it is now) + + //Serial.print(F("The maximum number of bytes which the file buffer has contained is: ")); // It is a fun thing to watch how full the buffer gets + //Serial.println(maxBufferBytes); + + if (maxBufferBytes > ((fileBufferSize / 5) * 4)) // Warn the user if fileBufferSize was more than 80% full + { + Serial.println(F("Warning: the file buffer has been over 80% full. Some data may have been lost.")); + } + + lastPrint = millis(); // Update lastPrint + } +} + +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +//Stop Logging ISR +void stopLoggingISR(void) +{ + Serial.println(F("Stop Logging Seen!")); + detachInterrupt(digitalPinToInterrupt(PIN_STOP_LOGGING)); //Prevent multiple interrupts + stopLoggingSeen = true; +} + +//Power Loss ISR +void powerLossISR(void) +{ + Serial.println(F("Power Loss Detected!")); + detachInterrupt(digitalPinToInterrupt(PIN_POWER_LOSS)); //Prevent multiple interrupts + powerLossSeen = true; +} + +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +void stopLogging(void) // Stop logging; close the SD file; go into deep sleep +{ + Serial.println(F("stopLogging:")); + + uint16_t remainingBytes = myGNSS.fileBufferAvailable(); // Check if there are any bytes remaining in the file buffer + + while (remainingBytes > 0) // While there is still data in the file buffer + { + digitalWrite(PIN_STAT_LED, HIGH); // Flash PIN_STAT_LED while we write to the SD card + + uint8_t myBuffer[sdWriteSize]; // Create our own buffer to hold the data while we write it to SD card + + uint16_t bytesToWrite = remainingBytes; // Write the remaining bytes to SD card sdWriteSize bytes at a time + if (bytesToWrite > sdWriteSize) + { + bytesToWrite = sdWriteSize; + } + + myGNSS.extractFileBufferData((uint8_t *)&myBuffer, bytesToWrite); // Extract bytesToWrite bytes from the UBX file buffer and put them into myBuffer + + myFile.write(myBuffer, bytesToWrite); // Write bytesToWrite bytes from myBuffer to the ubxDataFile on the SD card + + bytesWritten += bytesToWrite; // Update bytesWritten + + remainingBytes -= bytesToWrite; // Decrement remainingBytes + } + + digitalWrite(PIN_STAT_LED, LOW); // Turn PIN_STAT_LED off + + Serial.print(F("The total number of bytes written to SD card is ")); // Print how many bytes have been written to SD card + Serial.println(bytesWritten); + + myFile.close(); // Close the data file + + Serial.println(F("Logging stopped. Freezing...")); + Serial.flush(); // Make sure final message is printed + + delay(sdPowerDownDelay); // Give the SD card time to finish writing ***** THIS IS CRITICAL ***** + + detachInterrupt(digitalPinToInterrupt(PIN_STOP_LOGGING)); //Prevent stop logging button from waking us from sleep + detachInterrupt(digitalPinToInterrupt(PIN_POWER_LOSS)); //Prevent voltage supervisor from waking us from sleep + + qwiic.end(); //Power down I2C + + SPI.end(); //Power down SPI + + power_adc_disable(); //Power down ADC. It it started by default before setup(). + + Serial.end(); //Power down UART + + powerLEDOff(); // Turn the power LED off - if the hardware supports it + + imuPowerOff(); + + microSDPowerOff(); + + qwiicPowerOff(); // Power off the Qwiic bus + +//Force the peripherals off + am_hal_pwrctrl_periph_disable(AM_HAL_PWRCTRL_PERIPH_IOM0); + am_hal_pwrctrl_periph_disable(AM_HAL_PWRCTRL_PERIPH_IOM1); + am_hal_pwrctrl_periph_disable(AM_HAL_PWRCTRL_PERIPH_IOM2); + am_hal_pwrctrl_periph_disable(AM_HAL_PWRCTRL_PERIPH_IOM3); + am_hal_pwrctrl_periph_disable(AM_HAL_PWRCTRL_PERIPH_IOM4); + am_hal_pwrctrl_periph_disable(AM_HAL_PWRCTRL_PERIPH_IOM5); + am_hal_pwrctrl_periph_disable(AM_HAL_PWRCTRL_PERIPH_ADC); + am_hal_pwrctrl_periph_disable(AM_HAL_PWRCTRL_PERIPH_UART0); + am_hal_pwrctrl_periph_disable(AM_HAL_PWRCTRL_PERIPH_UART1); + + //Disable pads + for (int x = 0; x < 50; x++) + { + if ((x != ap3_gpio_pin2pad(PIN_MICROSD_POWER)) && + (x != ap3_gpio_pin2pad(PIN_QWIIC_POWER)) && + (x != ap3_gpio_pin2pad(PIN_IMU_POWER))) + { + am_hal_gpio_pinconfig(x, g_AM_HAL_GPIO_DISABLE); + } + } + + //We can't leave these power control pins floating + imuPowerOff(); + microSDPowerOff(); + qwiicPowerOff(); // Power off the Qwiic bus + + //Power down cache, flash, SRAM + am_hal_pwrctrl_memory_deepsleep_powerdown(AM_HAL_PWRCTRL_MEM_ALL); // Power down all flash and cache + am_hal_pwrctrl_memory_deepsleep_retain(AM_HAL_PWRCTRL_MEM_SRAM_384K); // Retain all SRAM + + //Keep the 32kHz clock running for RTC + am_hal_stimer_config(AM_HAL_STIMER_CFG_CLEAR | AM_HAL_STIMER_CFG_FREEZE); + am_hal_stimer_config(AM_HAL_STIMER_XTAL_32KHZ); + + while (1) // Stay in deep sleep until we get reset + { + am_hal_sysctrl_sleep(AM_HAL_SYSCTRL_SLEEP_DEEP); //Sleep + } +} + +// OLA Specifics + +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +void powerLEDOn() +{ +#if(HARDWARE_VERSION_MAJOR >= 1) + pinMode(PIN_PWR_LED, OUTPUT); + digitalWrite(PIN_PWR_LED, HIGH); // Turn the Power LED on +#endif +} +void powerLEDOff() +{ +#if(HARDWARE_VERSION_MAJOR >= 1) + pinMode(PIN_PWR_LED, OUTPUT); + digitalWrite(PIN_PWR_LED, LOW); // Turn the Power LED off +#endif +} + +void qwiicPowerOn() +{ + pinMode(PIN_QWIIC_POWER, OUTPUT); +#if(HARDWARE_VERSION_MAJOR == 0) + digitalWrite(PIN_QWIIC_POWER, LOW); +#else + digitalWrite(PIN_QWIIC_POWER, HIGH); +#endif +} +void qwiicPowerOff() +{ + pinMode(PIN_QWIIC_POWER, OUTPUT); +#if(HARDWARE_VERSION_MAJOR == 0) + digitalWrite(PIN_QWIIC_POWER, HIGH); +#else + digitalWrite(PIN_QWIIC_POWER, LOW); +#endif +} + +void microSDPowerOn() +{ + pinMode(PIN_MICROSD_POWER, OUTPUT); + digitalWrite(PIN_MICROSD_POWER, LOW); +} +void microSDPowerOff() +{ + pinMode(PIN_MICROSD_POWER, OUTPUT); + digitalWrite(PIN_MICROSD_POWER, HIGH); +} + +void imuPowerOn() +{ + pinMode(PIN_IMU_POWER, OUTPUT); + digitalWrite(PIN_IMU_POWER, HIGH); +} +void imuPowerOff() +{ + pinMode(PIN_IMU_POWER, OUTPUT); + digitalWrite(PIN_IMU_POWER, LOW); +} + +void beginQwiic() +{ + pinMode(PIN_QWIIC_POWER, OUTPUT); + qwiicPowerOn(); + qwiic.begin(); + qwiic.setPullups(0); //Just to make it really clear what pull-ups are being used, set pullups here. +} + +void beginSD() +{ + pinMode(PIN_MICROSD_POWER, OUTPUT); + pinMode(PIN_MICROSD_CHIP_SELECT, OUTPUT); + digitalWrite(PIN_MICROSD_CHIP_SELECT, HIGH); //Be sure SD is deselected + + // For reasons I don't understand, we seem to have to wait for at least 1ms after SPI.begin before we call microSDPowerOn. + // If you comment the next line, the Artemis resets at microSDPowerOn when beginSD is called from wakeFromSleep... + // But only on one of my V10 red boards. The second one I have doesn't seem to need the delay!? + delay(1); + + microSDPowerOn(); +} + +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +bool enableCIPOpullUp() +{ + //Add CIPO pull-up + ap3_err_t retval = AP3_OK; + am_hal_gpio_pincfg_t cipoPinCfg = AP3_GPIO_DEFAULT_PINCFG; + cipoPinCfg.uFuncSel = AM_HAL_PIN_6_M0MISO; + cipoPinCfg.eDriveStrength = AM_HAL_GPIO_PIN_DRIVESTRENGTH_12MA; + cipoPinCfg.eGPOutcfg = AM_HAL_GPIO_PIN_OUTCFG_PUSHPULL; + cipoPinCfg.uIOMnum = AP3_SPI_IOM; + cipoPinCfg.ePullup = AM_HAL_GPIO_PIN_PULLUP_1_5K; + padMode(MISO, cipoPinCfg, &retval); + return (retval == AP3_OK); +} + +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +//Read the VIN voltage +float readVIN() +{ + // Only supported on >= V10 hardware +#if(HARDWARE_VERSION_MAJOR == 0) + return(0.0); // Return 0.0V on old hardware +#else + int div3 = analogRead(PIN_VIN_MONITOR); //Read VIN across a 1/3 resistor divider + float vin = (float)div3 * 3.0 * 2.0 / 16384.0; //Convert 1/3 VIN to VIN (14-bit resolution) + vin = vin * vinCorrectionFactor; //Correct for divider impedance (determined experimentally) + //Serial.print(F("VIN (Volts): ")); + //Serial.println(vin, 2); + return (vin); +#endif +} + +// Read the battery voltage +// If it is low, increment lowBatteryReadings +// If lowBatteryReadings exceeds lowBatteryReadingsLimit then powerDown +void checkBattery(void) +{ +#if(HARDWARE_VERSION_MAJOR >= 1) + float voltage = readVIN(); // Read the battery voltage + if (voltage < lowBatteryThreshold) // Is the voltage low? + { + lowBatteryReadings++; // Increment the low battery count + if (lowBatteryReadings > lowBatteryReadingsLimit) // Have we exceeded the low battery count limit? + { + // Gracefully powerDown + Serial.println(F("Low battery detected!")); + stopLogging(); + } + } + else + { + lowBatteryReadings = 0; // Reset the low battery count + } +#endif +} diff --git a/Firmware/Test Sketches/IMU_DMP_MultipleSensors/IMU_DMP_MultipleSensors.ino b/Firmware/Test Sketches/IMU_DMP_MultipleSensors/IMU_DMP_MultipleSensors.ino new file mode 100644 index 0000000..fb174ee --- /dev/null +++ b/Firmware/Test Sketches/IMU_DMP_MultipleSensors/IMU_DMP_MultipleSensors.ino @@ -0,0 +1,280 @@ +/**************************************************************** + * This is an example for OpenLog Artemis - based on a DMP example from the ICM-20948 Library: + * + * Example9_DMP_MultipleSensors.ino + * ICM 20948 Arduino Library Demo + * Initialize the DMP based on the TDK InvenSense ICM20948_eMD_nucleo_1.0 example-icm20948 + * Paul Clark, February 15th 2021 + * Based on original code by: + * Owen Lyke @ SparkFun Electronics + * Original Creation Date: April 17 2019 + * + * ** This example is based on InvenSense's _confidential_ Application Note "Programming Sequence for DMP Hardware Functions". + * ** We are grateful to InvenSense for sharing this with us. + * + * ** Important note: by default the DMP functionality is disabled in the library + * ** as the DMP firmware takes up 14301 Bytes of program memory. + * ** To use the DMP, you will need to: + * ** Edit ICM_20948_C.h + * ** Uncomment line 29: #define ICM_20948_USE_DMP + * ** Save changes + * ** If you are using Windows, you can find ICM_20948_C.h in: + * ** Documents\Arduino\libraries\SparkFun_ICM-20948_ArduinoLibrary\src\util + * + * Please see License.md for the license information. + * + * Distributed as-is; no warranty is given. + ***************************************************************/ + +// OLA Specifics: +const byte PIN_IMU_POWER = 27; // The Red SparkFun version of the OLA (V10) uses pin 27 +//const byte PIN_IMU_POWER = 22; // The Black SparkX version of the OLA (X04) uses pin 22 +const byte PIN_IMU_INT = 37; +const byte PIN_IMU_CHIP_SELECT = 44; + +#include "ICM_20948.h" // Click here to get the library: http://librarymanager/All#SparkFun_ICM_20948_IMU + +#define SERIAL_PORT Serial + +#define SPI_PORT SPI // Your desired SPI port. OLA uses SPI. +#define CS_PIN PIN_IMU_CHIP_SELECT // Which pin you connect CS to. OLA uses pin 44. + +ICM_20948_SPI myICM; // Create an ICM_20948_SPI object + +void setup() { + + SPI_PORT.begin(); + + enableCIPOpullUp(); // Enable CIPO pull-up on the OLA + + pinMode(PIN_IMU_CHIP_SELECT, OUTPUT); + digitalWrite(PIN_IMU_CHIP_SELECT, HIGH); //Be sure IMU is deselected + pinMode(PIN_IMU_INT, INPUT_PULLUP); + + //Reset ICM by power cycling it + imuPowerOff(); + + delay(10); + + imuPowerOn(); // Enable power for the OLA IMU + + delay(100); // Wait for the IMU to power up + + SERIAL_PORT.begin(115200); // Start the serial console + SERIAL_PORT.println(F("OpenLog Artemis ICM-20948 Example")); + + delay(100); + + while (SERIAL_PORT.available()) // Make sure the serial RX buffer is empty + SERIAL_PORT.read(); + + SERIAL_PORT.println(F("Press any key to continue...")); + + while (!SERIAL_PORT.available()) // Wait for the user to press a key (send any serial character) + ; + + //myICM.enableDebugging(); // Uncomment this line to enable helpful debug messages on Serial + + bool initialized = false; + while( !initialized ){ + + // Initialize the ICM-20948 + // If the DMP is enabled, .begin performs a minimal startup. We need to configure the sample mode etc. manually. + myICM.begin( CS_PIN, SPI_PORT ); + + SERIAL_PORT.print( F("Initialization of the sensor returned: ") ); + SERIAL_PORT.println( myICM.statusString() ); + if( myICM.status != ICM_20948_Stat_Ok ){ + SERIAL_PORT.println( F("Trying again...") ); + delay(500); + }else{ + initialized = true; + } + } + + SERIAL_PORT.println(F("Device connected!")); + + bool success = true; // Use success to show if the DMP configuration was successful + + // Initialize the DMP. initializeDMP is a weak function. You can overwrite it if you want to e.g. to change the sample rate + success &= (myICM.initializeDMP() == ICM_20948_Stat_Ok); + + // DMP sensor options are defined in ICM_20948_DMP.h + // INV_ICM20948_SENSOR_ACCELEROMETER (16-bit accel) + // INV_ICM20948_SENSOR_GYROSCOPE (16-bit gyro + 32-bit calibrated gyro) + // INV_ICM20948_SENSOR_RAW_ACCELEROMETER (16-bit accel) + // INV_ICM20948_SENSOR_RAW_GYROSCOPE (16-bit gyro + 32-bit calibrated gyro) + // INV_ICM20948_SENSOR_MAGNETIC_FIELD_UNCALIBRATED (16-bit compass) + // INV_ICM20948_SENSOR_GYROSCOPE_UNCALIBRATED (16-bit gyro) + // INV_ICM20948_SENSOR_STEP_DETECTOR (Pedometer Step Detector) + // INV_ICM20948_SENSOR_STEP_COUNTER (Pedometer Step Detector) + // INV_ICM20948_SENSOR_GAME_ROTATION_VECTOR (32-bit 6-axis quaternion) + // INV_ICM20948_SENSOR_ROTATION_VECTOR (32-bit 9-axis quaternion + heading accuracy) + // INV_ICM20948_SENSOR_GEOMAGNETIC_ROTATION_VECTOR (32-bit Geomag RV + heading accuracy) + // INV_ICM20948_SENSOR_GEOMAGNETIC_FIELD (32-bit calibrated compass) + // INV_ICM20948_SENSOR_GRAVITY (32-bit 6-axis quaternion) + // INV_ICM20948_SENSOR_LINEAR_ACCELERATION (16-bit accel + 32-bit 6-axis quaternion) + // INV_ICM20948_SENSOR_ORIENTATION (32-bit 9-axis quaternion + heading accuracy) + + // Enable the DMP Game Rotation Vector sensor (Quat6) + success &= (myICM.enableDMPSensor(INV_ICM20948_SENSOR_GAME_ROTATION_VECTOR) == ICM_20948_Stat_Ok); + + // Enable additional sensors / features + success &= (myICM.enableDMPSensor(INV_ICM20948_SENSOR_RAW_GYROSCOPE) == ICM_20948_Stat_Ok); + success &= (myICM.enableDMPSensor(INV_ICM20948_SENSOR_RAW_ACCELEROMETER) == ICM_20948_Stat_Ok); + success &= (myICM.enableDMPSensor(INV_ICM20948_SENSOR_MAGNETIC_FIELD_UNCALIBRATED) == ICM_20948_Stat_Ok); + + // Configuring DMP to output data at multiple ODRs: + // DMP is capable of outputting multiple sensor data at different rates to FIFO. + // Setting value can be calculated as follows: + // Value = (DMP running rate / ODR ) - 1 + // E.g. For a 5Hz ODR rate when DMP is running at 55Hz, value = (55/5) - 1 = 10. + success &= (myICM.setDMPODRrate(DMP_ODR_Reg_Quat6, 10) == ICM_20948_Stat_Ok); // Set to 5Hz + success &= (myICM.setDMPODRrate(DMP_ODR_Reg_Accel, 54) == ICM_20948_Stat_Ok); // Set to 1Hz + success &= (myICM.setDMPODRrate(DMP_ODR_Reg_Gyro, 54) == ICM_20948_Stat_Ok); // Set to 1Hz + success &= (myICM.setDMPODRrate(DMP_ODR_Reg_Gyro_Calibr, 54) == ICM_20948_Stat_Ok); // Set to 1Hz + success &= (myICM.setDMPODRrate(DMP_ODR_Reg_Cpass, 54) == ICM_20948_Stat_Ok); // Set to 1Hz + success &= (myICM.setDMPODRrate(DMP_ODR_Reg_Cpass_Calibr, 54) == ICM_20948_Stat_Ok); // Set to 1Hz + + // Enable the FIFO + success &= (myICM.enableFIFO() == ICM_20948_Stat_Ok); + + // Enable the DMP + success &= (myICM.enableDMP() == ICM_20948_Stat_Ok); + + // Reset DMP + success &= (myICM.resetDMP() == ICM_20948_Stat_Ok); + + // Reset FIFO + success &= (myICM.resetFIFO() == ICM_20948_Stat_Ok); + + // Check success + if (success) + { + SERIAL_PORT.println(F("DMP enabled!")); + } + else + { + SERIAL_PORT.println(F("Enable DMP failed!")); + SERIAL_PORT.println(F("Please check that you have uncommented line 29 (#define ICM_20948_USE_DMP) in ICM_20948_C.h...")); + while (1) + ; // Do nothing more + } +} + +void loop() +{ + // Read any DMP data waiting in the FIFO + // Note: + // readDMPdataFromFIFO will return ICM_20948_Stat_FIFONoDataAvail if no data is available. + // If data is available, readDMPdataFromFIFO will attempt to read _one_ frame of DMP data. + // readDMPdataFromFIFO will return ICM_20948_Stat_FIFOIncompleteData if a frame was present but was incomplete + // readDMPdataFromFIFO will return ICM_20948_Stat_Ok if a valid frame was read. + // readDMPdataFromFIFO will return ICM_20948_Stat_FIFOMoreDataAvail if a valid frame was read _and_ the FIFO contains more (unread) data. + icm_20948_DMP_data_t data; + myICM.readDMPdataFromFIFO(&data); + + if ((myICM.status == ICM_20948_Stat_Ok) || (myICM.status == ICM_20948_Stat_FIFOMoreDataAvail)) // Was valid data available? + { + //SERIAL_PORT.print(F("Received data! Header: 0x")); // Print the header in HEX so we can see what data is arriving in the FIFO + //if ( data.header < 0x1000) SERIAL_PORT.print( "0" ); // Pad the zeros + //if ( data.header < 0x100) SERIAL_PORT.print( "0" ); + //if ( data.header < 0x10) SERIAL_PORT.print( "0" ); + //SERIAL_PORT.println( data.header, HEX ); + + if ((data.header & DMP_header_bitmap_Quat6) > 0) // Check for orientation data (Quat9) + { + // Q0 value is computed from this equation: Q0^2 + Q1^2 + Q2^2 + Q3^2 = 1. + // In case of drift, the sum will not add to 1, therefore, quaternion data need to be corrected with right bias values. + // The quaternion data is scaled by 2^30. + + //SERIAL_PORT.printf("Quat6 data is: Q1:%ld Q2:%ld Q3:%ld\r\n", data.Quat6.Data.Q1, data.Quat6.Data.Q2, data.Quat6.Data.Q3); + + // Scale to +/- 1 + double q1 = ((double)data.Quat6.Data.Q1) / 1073741824.0; // Convert to double. Divide by 2^30 + double q2 = ((double)data.Quat6.Data.Q2) / 1073741824.0; // Convert to double. Divide by 2^30 + double q3 = ((double)data.Quat6.Data.Q3) / 1073741824.0; // Convert to double. Divide by 2^30 + + SERIAL_PORT.print(F("Q1:")); + SERIAL_PORT.print(q1, 3); + SERIAL_PORT.print(F(" Q2:")); + SERIAL_PORT.print(q2, 3); + SERIAL_PORT.print(F(" Q3:")); + SERIAL_PORT.println(q3, 3); + } + + if ((data.header & DMP_header_bitmap_Accel) > 0) // Check for Accel + { + float acc_x = (float)data.Raw_Accel.Data.X; // Extract the raw accelerometer data + float acc_y = (float)data.Raw_Accel.Data.Y; + float acc_z = (float)data.Raw_Accel.Data.Z; + + SERIAL_PORT.print(F("Accel: X:")); + SERIAL_PORT.print(acc_x); + SERIAL_PORT.print(F(" Y:")); + SERIAL_PORT.print(acc_y); + SERIAL_PORT.print(F(" Z:")); + SERIAL_PORT.println(acc_z); + } + + if ((data.header & DMP_header_bitmap_Gyro) > 0) // Check for Gyro + { + float x = (float)data.Raw_Gyro.Data.X; // Extract the raw gyro data + float y = (float)data.Raw_Gyro.Data.Y; + float z = (float)data.Raw_Gyro.Data.Z; + + SERIAL_PORT.print(F("Gyro: X:")); + SERIAL_PORT.print(x); + SERIAL_PORT.print(F(" Y:")); + SERIAL_PORT.print(y); + SERIAL_PORT.print(F(" Z:")); + SERIAL_PORT.println(z); + } + + if ((data.header & DMP_header_bitmap_Compass) > 0) // Check for Compass + { + float x = (float)data.Compass.Data.X; // Extract the compass data + float y = (float)data.Compass.Data.Y; + float z = (float)data.Compass.Data.Z; + + SERIAL_PORT.print(F("Compass: X:")); + SERIAL_PORT.print(x); + SERIAL_PORT.print(F(" Y:")); + SERIAL_PORT.print(y); + SERIAL_PORT.print(F(" Z:")); + SERIAL_PORT.println(z); + } + } + + if (myICM.status != ICM_20948_Stat_FIFOMoreDataAvail) // If more data is available then we should read it right away - and not delay + { + delay(10); + } +} + +// OLA Specifics + +void imuPowerOn() +{ + pinMode(PIN_IMU_POWER, OUTPUT); + digitalWrite(PIN_IMU_POWER, HIGH); +} +void imuPowerOff() +{ + pinMode(PIN_IMU_POWER, OUTPUT); + digitalWrite(PIN_IMU_POWER, LOW); +} + +bool enableCIPOpullUp() +{ + //Add CIPO pull-up + ap3_err_t retval = AP3_OK; + am_hal_gpio_pincfg_t cipoPinCfg = AP3_GPIO_DEFAULT_PINCFG; + cipoPinCfg.uFuncSel = AM_HAL_PIN_6_M0MISO; + cipoPinCfg.eDriveStrength = AM_HAL_GPIO_PIN_DRIVESTRENGTH_12MA; + cipoPinCfg.eGPOutcfg = AM_HAL_GPIO_PIN_OUTCFG_PUSHPULL; + cipoPinCfg.uIOMnum = AP3_SPI_IOM; + cipoPinCfg.ePullup = AM_HAL_GPIO_PIN_PULLUP_1_5K; + padMode(MISO, cipoPinCfg, &retval); + return (retval == AP3_OK); +} diff --git a/Firmware/Test Sketches/IMU_DMP_Quat6/IMU_DMP_Quat6.ino b/Firmware/Test Sketches/IMU_DMP_Quat6/IMU_DMP_Quat6.ino index f1592fd..b6e4ff0 100644 --- a/Firmware/Test Sketches/IMU_DMP_Quat6/IMU_DMP_Quat6.ino +++ b/Firmware/Test Sketches/IMU_DMP_Quat6/IMU_DMP_Quat6.ino @@ -106,176 +106,10 @@ void setup() { SERIAL_PORT.println(F("Device connected!")); #endif - // The ICM-20948 is awake and ready but hasn't been configured. Let's step through the configuration - // sequence from InvenSense's _confidential_ Application Note "Programming Sequence for DMP Hardware Functions". - - bool success = true; // Use success to show if the configuration was successful - - // Configure clock source through PWR_MGMT_1 - // ICM_20948_Clock_Auto selects the best available clock source – PLL if ready, else use the Internal oscillator - success &= (myICM.setClockSource(ICM_20948_Clock_Auto) == ICM_20948_Stat_Ok); // This is shorthand: success will be set to false if setClockSource fails - - // Enable accel and gyro sensors through PWR_MGMT_2 - // Enable Accelerometer (all axes) and Gyroscope (all axes) by writing zero to PWR_MGMT_2 - // Set the reserved bit 6 - success &= (myICM.setBank(0) == ICM_20948_Stat_Ok); // Select Bank 0 - uint8_t pwrMgmt2 = 0x40; - success &= (myICM.write(AGB0_REG_PWR_MGMT_2, &pwrMgmt2, 1) == ICM_20948_Stat_Ok); // Write one byte to the PWR_MGMT_2 register - - // Configure I2C_Master/Gyro/Accel in Low Power Mode (cycled) with LP_CONFIG - success &= (myICM.setSampleMode( (ICM_20948_Internal_Mst | ICM_20948_Internal_Acc | ICM_20948_Internal_Gyr), ICM_20948_Sample_Mode_Cycled ) == ICM_20948_Stat_Ok); - - // Disable the FIFO - success &= (myICM.enableFIFO(false) == ICM_20948_Stat_Ok); - - // Disable the DMP - success &= (myICM.enableDMP(false) == ICM_20948_Stat_Ok); - - // Set Gyro FSR (Full scale range) to 2000dps through GYRO_CONFIG_1 - // Set Accel FSR (Full scale range) to 4g through ACCEL_CONFIG - ICM_20948_fss_t myFSS; // This uses a "Full Scale Settings" structure that can contain values for all configurable sensors - myFSS.a = gpm4; // (ICM_20948_ACCEL_CONFIG_FS_SEL_e) - // gpm2 - // gpm4 - // gpm8 - // gpm16 - myFSS.g = dps2000; // (ICM_20948_GYRO_CONFIG_1_FS_SEL_e) - // dps250 - // dps500 - // dps1000 - // dps2000 - success &= (myICM.setFullScale( (ICM_20948_Internal_Acc | ICM_20948_Internal_Gyr), myFSS ) == ICM_20948_Stat_Ok); - - // Enable interrupt for FIFO overflow from FIFOs through INT_ENABLE_2 - // If we see this interrupt, we'll need to reset the FIFO - //success &= (myICM.intEnableOverflowFIFO( 0x1F ) == ICM_20948_Stat_Ok); // Enable the interrupt on all FIFOs - - // Turn off what goes into the FIFO through FIFO_EN_1, FIFO_EN_2 - // Stop the peripheral data from being written to the FIFO by writing zero to FIFO_EN_1 - success &= (myICM.setBank(0) == ICM_20948_Stat_Ok); // Select Bank 0 - uint8_t zero = 0; - success &= (myICM.write(AGB0_REG_FIFO_EN_1, &zero, 1) == ICM_20948_Stat_Ok); - // Stop the accelerometer, gyro and temperature data from being written to the FIFO by writing zero to FIFO_EN_2 - success &= (myICM.write(AGB0_REG_FIFO_EN_2, &zero, 1) == ICM_20948_Stat_Ok); - - // Turn off data ready interrupt through INT_ENABLE_1 - success &= (myICM.intEnableRawDataReady(false) == ICM_20948_Stat_Ok); - - // Reset FIFO through FIFO_RST - success &= (myICM.resetFIFO() == ICM_20948_Stat_Ok); - - // Set gyro sample rate divider with GYRO_SMPLRT_DIV - // Set accel sample rate divider with ACCEL_SMPLRT_DIV_2 - ICM_20948_smplrt_t mySmplrt; - mySmplrt.g = 19; // ODR is computed as follows: 1.1 kHz/(1+GYRO_SMPLRT_DIV[7:0]). 19 = 55Hz. InvenSense Nucleo example uses 19 (0x13). - mySmplrt.a = 19; // ODR is computed as follows: 1.125 kHz/(1+ACCEL_SMPLRT_DIV[11:0]). 19 = 56.25Hz. InvenSense Nucleo example uses 19 (0x13). - myICM.setSampleRate( (ICM_20948_Internal_Acc | ICM_20948_Internal_Gyr), mySmplrt ); // ** Note: comment this line to leave the sample rates at the maximum ** - - // Setup DMP start address through PRGM_STRT_ADDRH/PRGM_STRT_ADDRL - success &= (myICM.setDMPstartAddress() == ICM_20948_Stat_Ok); // Defaults to DMP_START_ADDRESS + bool success = true; // Use success to show if the DMP configuration was successful - // Now load the DMP firmware - success &= (myICM.loadDMPFirmware() == ICM_20948_Stat_Ok); - - // Write the 2 byte Firmware Start Value to ICM PRGM_STRT_ADDRH/PRGM_STRT_ADDRL - success &= (myICM.setDMPstartAddress() == ICM_20948_Stat_Ok); // Defaults to DMP_START_ADDRESS - - // Set the Hardware Fix Disable register to 0x48 - success &= (myICM.setBank(0) == ICM_20948_Stat_Ok); // Select Bank 0 - uint8_t fix = 0x48; - success &= (myICM.write(AGB0_REG_HW_FIX_DISABLE, &fix, 1) == ICM_20948_Stat_Ok); - - // Set the Single FIFO Priority Select register to 0xE4 - success &= (myICM.setBank(0) == ICM_20948_Stat_Ok); // Select Bank 0 - uint8_t fifoPrio = 0xE4; - success &= (myICM.write(AGB0_REG_SINGLE_FIFO_PRIORITY_SEL, &fifoPrio, 1) == ICM_20948_Stat_Ok); - - // Configure Accel scaling to DMP - // The DMP scales accel raw data internally to align 1g as 2^25 - // In order to align internal accel raw data 2^25 = 1g write 0x04000000 when FSR is 4g - const unsigned char accScale[4] = {0x04, 0x00, 0x00, 0x00}; - success &= (myICM.writeDMPmems(ACC_SCALE, 4, &accScale[0]) == ICM_20948_Stat_Ok); // Write accScale to ACC_SCALE DMP register - // In order to output hardware unit data as configured FSR write 0x00040000 when FSR is 4g - const unsigned char accScale2[4] = {0x00, 0x04, 0x00, 0x00}; - success &= (myICM.writeDMPmems(ACC_SCALE2, 4, &accScale2[0]) == ICM_20948_Stat_Ok); // Write accScale2 to ACC_SCALE2 DMP register - - // Configure Compass mount matrix and scale to DMP - // The mount matrix write to DMP register is used to align the compass axes with accel/gyro. - // This mechanism is also used to convert hardware unit to uT. The value is expressed as 1uT = 2^30. - // Each compass axis will be converted as below: - // X = raw_x * CPASS_MTX_00 + raw_y * CPASS_MTX_01 + raw_z * CPASS_MTX_02 - // Y = raw_x * CPASS_MTX_10 + raw_y * CPASS_MTX_11 + raw_z * CPASS_MTX_12 - // Z = raw_x * CPASS_MTX_20 + raw_y * CPASS_MTX_21 + raw_z * CPASS_MTX_22 - // The AK09916 produces a 16-bit signed output in the range +/-32752 corresponding to +/-4912uT. 1uT = 6.66 ADU. - // 2^30 / 6.66666 = 161061273 = 0x9999999 - const unsigned char mountMultiplierZero[4] = {0x00, 0x00, 0x00, 0x00}; - const unsigned char mountMultiplierPlus[4] = {0x09, 0x99, 0x99, 0x99}; // Value taken from InvenSense Nucleo example - const unsigned char mountMultiplierMinus[4] = {0xF6, 0x66, 0x66, 0x67}; // Value taken from InvenSense Nucleo example - success &= (myICM.writeDMPmems(CPASS_MTX_00, 4, &mountMultiplierPlus[0]) == ICM_20948_Stat_Ok); - success &= (myICM.writeDMPmems(CPASS_MTX_01, 4, &mountMultiplierZero[0]) == ICM_20948_Stat_Ok); - success &= (myICM.writeDMPmems(CPASS_MTX_02, 4, &mountMultiplierZero[0]) == ICM_20948_Stat_Ok); - success &= (myICM.writeDMPmems(CPASS_MTX_10, 4, &mountMultiplierZero[0]) == ICM_20948_Stat_Ok); - success &= (myICM.writeDMPmems(CPASS_MTX_11, 4, &mountMultiplierMinus[0]) == ICM_20948_Stat_Ok); - success &= (myICM.writeDMPmems(CPASS_MTX_12, 4, &mountMultiplierZero[0]) == ICM_20948_Stat_Ok); - success &= (myICM.writeDMPmems(CPASS_MTX_20, 4, &mountMultiplierZero[0]) == ICM_20948_Stat_Ok); - success &= (myICM.writeDMPmems(CPASS_MTX_21, 4, &mountMultiplierZero[0]) == ICM_20948_Stat_Ok); - success &= (myICM.writeDMPmems(CPASS_MTX_22, 4, &mountMultiplierMinus[0]) == ICM_20948_Stat_Ok); - - // Configure the B2S Mounting Matrix - const unsigned char b2sMountMultiplierZero[4] = {0x00, 0x00, 0x00, 0x00}; - const unsigned char b2sMountMultiplierPlus[4] = {0x40, 0x00, 0x00, 0x00}; // Value taken from InvenSense Nucleo example - success &= (myICM.writeDMPmems(B2S_MTX_00, 4, &b2sMountMultiplierPlus[0]) == ICM_20948_Stat_Ok); - success &= (myICM.writeDMPmems(B2S_MTX_01, 4, &b2sMountMultiplierZero[0]) == ICM_20948_Stat_Ok); - success &= (myICM.writeDMPmems(B2S_MTX_02, 4, &b2sMountMultiplierZero[0]) == ICM_20948_Stat_Ok); - success &= (myICM.writeDMPmems(B2S_MTX_10, 4, &b2sMountMultiplierZero[0]) == ICM_20948_Stat_Ok); - success &= (myICM.writeDMPmems(B2S_MTX_11, 4, &b2sMountMultiplierPlus[0]) == ICM_20948_Stat_Ok); - success &= (myICM.writeDMPmems(B2S_MTX_12, 4, &b2sMountMultiplierZero[0]) == ICM_20948_Stat_Ok); - success &= (myICM.writeDMPmems(B2S_MTX_20, 4, &b2sMountMultiplierZero[0]) == ICM_20948_Stat_Ok); - success &= (myICM.writeDMPmems(B2S_MTX_21, 4, &b2sMountMultiplierZero[0]) == ICM_20948_Stat_Ok); - success &= (myICM.writeDMPmems(B2S_MTX_22, 4, &b2sMountMultiplierPlus[0]) == ICM_20948_Stat_Ok); - - // Configure the DMP Gyro Scaling Factor - // @param[in] gyro_div Value written to GYRO_SMPLRT_DIV register, where - // 0=1125Hz sample rate, 1=562.5Hz sample rate, ... 4=225Hz sample rate, ... - // 10=102.2727Hz sample rate, ... etc. - // @param[in] gyro_level 0=250 dps, 1=500 dps, 2=1000 dps, 3=2000 dps - success &= (myICM.setGyroSF(19, 3) == ICM_20948_Stat_Ok); // 19 = 55Hz (see above), 3 = 2000dps (see above) - - // Configure the Gyro full scale - // 2000dps : 2^28 - // 1000dps : 2^27 - // 500dps : 2^26 - // 250dps : 2^25 - const unsigned char gyroFullScale[4] = {0x10, 0x00, 0x00, 0x00}; // 2000dps : 2^28 - success &= (myICM.writeDMPmems(GYRO_FULLSCALE, 4, &gyroFullScale[0]) == ICM_20948_Stat_Ok); - - // Configure the Accel Only Gain: 15252014 (225Hz) 30504029 (112Hz) 61117001 (56Hz) - const unsigned char accelOnlyGain[4] = {0x03, 0xA4, 0x92, 0x49}; // 56Hz - //const unsigned char accelOnlyGain[4] = {0x00, 0xE8, 0xBA, 0x2E}; // InvenSense Nucleo example uses 225Hz - success &= (myICM.writeDMPmems(ACCEL_ONLY_GAIN, 4, &accelOnlyGain[0]) == ICM_20948_Stat_Ok); - - // Configure the Accel Alpha Var: 1026019965 (225Hz) 977872018 (112Hz) 882002213 (56Hz) - const unsigned char accelAlphaVar[4] = {0x34, 0x92, 0x49, 0x25}; // 56Hz - //const unsigned char accelAlphaVar[4] = {0x06, 0x66, 0x66, 0x66}; // Value taken from InvenSense Nucleo example - success &= (myICM.writeDMPmems(ACCEL_ALPHA_VAR, 4, &accelAlphaVar[0]) == ICM_20948_Stat_Ok); - - // Configure the Accel A Var: 47721859 (225Hz) 95869806 (112Hz) 191739611 (56Hz) - const unsigned char accelAVar[4] = {0x0B, 0x6D, 0xB6, 0xDB}; // 56Hz - //const unsigned char accelAVar[4] = {0x39, 0x99, 0x99, 0x9A}; // Value taken from InvenSense Nucleo example - success &= (myICM.writeDMPmems(ACCEL_A_VAR, 4, &accelAVar[0]) == ICM_20948_Stat_Ok); - - // Configure the Accel Cal Rate - const unsigned char accelCalRate[4] = {0x00, 0x00}; // Value taken from InvenSense Nucleo example - success &= (myICM.writeDMPmems(ACCEL_CAL_RATE, 2, &accelCalRate[0]) == ICM_20948_Stat_Ok); - - // Configure the Compass Time Buffer. The compass (magnetometer) is set to 100Hz (AK09916_mode_cont_100hz) - // in startupMagnetometer. We need to set CPASS_TIME_BUFFER to 100 too. - const unsigned char compassRate[2] = {0x00, 0x64}; // 100Hz - success &= (myICM.writeDMPmems(CPASS_TIME_BUFFER, 2, &compassRate[0]) == ICM_20948_Stat_Ok); - - // Enable DMP interrupt - // This would be the most efficient way of getting the DMP data, instead of polling the FIFO - //success &= (myICM.intEnableDMP(true) == ICM_20948_Stat_Ok); + // Initialize the DMP. initializeDMP is a weak function. You can overwrite it if you want to e.g. to change the sample rate + success &= (myICM.initializeDMP() == ICM_20948_Stat_Ok); // DMP sensor options are defined in ICM_20948_DMP.h // INV_ICM20948_SENSOR_ACCELEROMETER (16-bit accel) @@ -403,8 +237,6 @@ void loop() SERIAL_PORT.print(q2, 3); SERIAL_PORT.print(F(" Q3:")); SERIAL_PORT.print(q3, 3); - SERIAL_PORT.print(F(" Accuracy:")); - SERIAL_PORT.print(data.Quat9.Data.Accuracy); SERIAL_PORT.print(F(" Roll:")); SERIAL_PORT.print(roll, 1); SERIAL_PORT.print(F(" Pitch:")); diff --git a/Firmware/Test Sketches/IMU_DMP_Quat9/IMU_DMP_Quat9.ino b/Firmware/Test Sketches/IMU_DMP_Quat9/IMU_DMP_Quat9.ino index 60b4df6..757d2d0 100644 --- a/Firmware/Test Sketches/IMU_DMP_Quat9/IMU_DMP_Quat9.ino +++ b/Firmware/Test Sketches/IMU_DMP_Quat9/IMU_DMP_Quat9.ino @@ -106,176 +106,10 @@ void setup() { SERIAL_PORT.println(F("Device connected!")); #endif - // The ICM-20948 is awake and ready but hasn't been configured. Let's step through the configuration - // sequence from InvenSense's _confidential_ Application Note "Programming Sequence for DMP Hardware Functions". - - bool success = true; // Use success to show if the configuration was successful - - // Configure clock source through PWR_MGMT_1 - // ICM_20948_Clock_Auto selects the best available clock source – PLL if ready, else use the Internal oscillator - success &= (myICM.setClockSource(ICM_20948_Clock_Auto) == ICM_20948_Stat_Ok); // This is shorthand: success will be set to false if setClockSource fails - - // Enable accel and gyro sensors through PWR_MGMT_2 - // Enable Accelerometer (all axes) and Gyroscope (all axes) by writing zero to PWR_MGMT_2 - // Set the reserved bit 6 - success &= (myICM.setBank(0) == ICM_20948_Stat_Ok); // Select Bank 0 - uint8_t pwrMgmt2 = 0x40; - success &= (myICM.write(AGB0_REG_PWR_MGMT_2, &pwrMgmt2, 1) == ICM_20948_Stat_Ok); // Write one byte to the PWR_MGMT_2 register - - // Configure I2C_Master/Gyro/Accel in Low Power Mode (cycled) with LP_CONFIG - success &= (myICM.setSampleMode( (ICM_20948_Internal_Mst | ICM_20948_Internal_Acc | ICM_20948_Internal_Gyr), ICM_20948_Sample_Mode_Cycled ) == ICM_20948_Stat_Ok); - - // Disable the FIFO - success &= (myICM.enableFIFO(false) == ICM_20948_Stat_Ok); - - // Disable the DMP - success &= (myICM.enableDMP(false) == ICM_20948_Stat_Ok); - - // Set Gyro FSR (Full scale range) to 2000dps through GYRO_CONFIG_1 - // Set Accel FSR (Full scale range) to 4g through ACCEL_CONFIG - ICM_20948_fss_t myFSS; // This uses a "Full Scale Settings" structure that can contain values for all configurable sensors - myFSS.a = gpm4; // (ICM_20948_ACCEL_CONFIG_FS_SEL_e) - // gpm2 - // gpm4 - // gpm8 - // gpm16 - myFSS.g = dps2000; // (ICM_20948_GYRO_CONFIG_1_FS_SEL_e) - // dps250 - // dps500 - // dps1000 - // dps2000 - success &= (myICM.setFullScale( (ICM_20948_Internal_Acc | ICM_20948_Internal_Gyr), myFSS ) == ICM_20948_Stat_Ok); - - // Enable interrupt for FIFO overflow from FIFOs through INT_ENABLE_2 - // If we see this interrupt, we'll need to reset the FIFO - //success &= (myICM.intEnableOverflowFIFO( 0x1F ) == ICM_20948_Stat_Ok); // Enable the interrupt on all FIFOs - - // Turn off what goes into the FIFO through FIFO_EN_1, FIFO_EN_2 - // Stop the peripheral data from being written to the FIFO by writing zero to FIFO_EN_1 - success &= (myICM.setBank(0) == ICM_20948_Stat_Ok); // Select Bank 0 - uint8_t zero = 0; - success &= (myICM.write(AGB0_REG_FIFO_EN_1, &zero, 1) == ICM_20948_Stat_Ok); - // Stop the accelerometer, gyro and temperature data from being written to the FIFO by writing zero to FIFO_EN_2 - success &= (myICM.write(AGB0_REG_FIFO_EN_2, &zero, 1) == ICM_20948_Stat_Ok); - - // Turn off data ready interrupt through INT_ENABLE_1 - success &= (myICM.intEnableRawDataReady(false) == ICM_20948_Stat_Ok); - - // Reset FIFO through FIFO_RST - success &= (myICM.resetFIFO() == ICM_20948_Stat_Ok); - - // Set gyro sample rate divider with GYRO_SMPLRT_DIV - // Set accel sample rate divider with ACCEL_SMPLRT_DIV_2 - ICM_20948_smplrt_t mySmplrt; - mySmplrt.g = 19; // ODR is computed as follows: 1.1 kHz/(1+GYRO_SMPLRT_DIV[7:0]). 19 = 55Hz. InvenSense Nucleo example uses 19 (0x13). - mySmplrt.a = 19; // ODR is computed as follows: 1.125 kHz/(1+ACCEL_SMPLRT_DIV[11:0]). 19 = 56.25Hz. InvenSense Nucleo example uses 19 (0x13). - myICM.setSampleRate( (ICM_20948_Internal_Acc | ICM_20948_Internal_Gyr), mySmplrt ); // ** Note: comment this line to leave the sample rates at the maximum ** - - // Setup DMP start address through PRGM_STRT_ADDRH/PRGM_STRT_ADDRL - success &= (myICM.setDMPstartAddress() == ICM_20948_Stat_Ok); // Defaults to DMP_START_ADDRESS + bool success = true; // Use success to show if the DMP configuration was successful - // Now load the DMP firmware - success &= (myICM.loadDMPFirmware() == ICM_20948_Stat_Ok); - - // Write the 2 byte Firmware Start Value to ICM PRGM_STRT_ADDRH/PRGM_STRT_ADDRL - success &= (myICM.setDMPstartAddress() == ICM_20948_Stat_Ok); // Defaults to DMP_START_ADDRESS - - // Set the Hardware Fix Disable register to 0x48 - success &= (myICM.setBank(0) == ICM_20948_Stat_Ok); // Select Bank 0 - uint8_t fix = 0x48; - success &= (myICM.write(AGB0_REG_HW_FIX_DISABLE, &fix, 1) == ICM_20948_Stat_Ok); - - // Set the Single FIFO Priority Select register to 0xE4 - success &= (myICM.setBank(0) == ICM_20948_Stat_Ok); // Select Bank 0 - uint8_t fifoPrio = 0xE4; - success &= (myICM.write(AGB0_REG_SINGLE_FIFO_PRIORITY_SEL, &fifoPrio, 1) == ICM_20948_Stat_Ok); - - // Configure Accel scaling to DMP - // The DMP scales accel raw data internally to align 1g as 2^25 - // In order to align internal accel raw data 2^25 = 1g write 0x04000000 when FSR is 4g - const unsigned char accScale[4] = {0x04, 0x00, 0x00, 0x00}; - success &= (myICM.writeDMPmems(ACC_SCALE, 4, &accScale[0]) == ICM_20948_Stat_Ok); // Write accScale to ACC_SCALE DMP register - // In order to output hardware unit data as configured FSR write 0x00040000 when FSR is 4g - const unsigned char accScale2[4] = {0x00, 0x04, 0x00, 0x00}; - success &= (myICM.writeDMPmems(ACC_SCALE2, 4, &accScale2[0]) == ICM_20948_Stat_Ok); // Write accScale2 to ACC_SCALE2 DMP register - - // Configure Compass mount matrix and scale to DMP - // The mount matrix write to DMP register is used to align the compass axes with accel/gyro. - // This mechanism is also used to convert hardware unit to uT. The value is expressed as 1uT = 2^30. - // Each compass axis will be converted as below: - // X = raw_x * CPASS_MTX_00 + raw_y * CPASS_MTX_01 + raw_z * CPASS_MTX_02 - // Y = raw_x * CPASS_MTX_10 + raw_y * CPASS_MTX_11 + raw_z * CPASS_MTX_12 - // Z = raw_x * CPASS_MTX_20 + raw_y * CPASS_MTX_21 + raw_z * CPASS_MTX_22 - // The AK09916 produces a 16-bit signed output in the range +/-32752 corresponding to +/-4912uT. 1uT = 6.66 ADU. - // 2^30 / 6.66666 = 161061273 = 0x9999999 - const unsigned char mountMultiplierZero[4] = {0x00, 0x00, 0x00, 0x00}; - const unsigned char mountMultiplierPlus[4] = {0x09, 0x99, 0x99, 0x99}; // Value taken from InvenSense Nucleo example - const unsigned char mountMultiplierMinus[4] = {0xF6, 0x66, 0x66, 0x67}; // Value taken from InvenSense Nucleo example - success &= (myICM.writeDMPmems(CPASS_MTX_00, 4, &mountMultiplierPlus[0]) == ICM_20948_Stat_Ok); - success &= (myICM.writeDMPmems(CPASS_MTX_01, 4, &mountMultiplierZero[0]) == ICM_20948_Stat_Ok); - success &= (myICM.writeDMPmems(CPASS_MTX_02, 4, &mountMultiplierZero[0]) == ICM_20948_Stat_Ok); - success &= (myICM.writeDMPmems(CPASS_MTX_10, 4, &mountMultiplierZero[0]) == ICM_20948_Stat_Ok); - success &= (myICM.writeDMPmems(CPASS_MTX_11, 4, &mountMultiplierMinus[0]) == ICM_20948_Stat_Ok); - success &= (myICM.writeDMPmems(CPASS_MTX_12, 4, &mountMultiplierZero[0]) == ICM_20948_Stat_Ok); - success &= (myICM.writeDMPmems(CPASS_MTX_20, 4, &mountMultiplierZero[0]) == ICM_20948_Stat_Ok); - success &= (myICM.writeDMPmems(CPASS_MTX_21, 4, &mountMultiplierZero[0]) == ICM_20948_Stat_Ok); - success &= (myICM.writeDMPmems(CPASS_MTX_22, 4, &mountMultiplierMinus[0]) == ICM_20948_Stat_Ok); - - // Configure the B2S Mounting Matrix - const unsigned char b2sMountMultiplierZero[4] = {0x00, 0x00, 0x00, 0x00}; - const unsigned char b2sMountMultiplierPlus[4] = {0x40, 0x00, 0x00, 0x00}; // Value taken from InvenSense Nucleo example - success &= (myICM.writeDMPmems(B2S_MTX_00, 4, &b2sMountMultiplierPlus[0]) == ICM_20948_Stat_Ok); - success &= (myICM.writeDMPmems(B2S_MTX_01, 4, &b2sMountMultiplierZero[0]) == ICM_20948_Stat_Ok); - success &= (myICM.writeDMPmems(B2S_MTX_02, 4, &b2sMountMultiplierZero[0]) == ICM_20948_Stat_Ok); - success &= (myICM.writeDMPmems(B2S_MTX_10, 4, &b2sMountMultiplierZero[0]) == ICM_20948_Stat_Ok); - success &= (myICM.writeDMPmems(B2S_MTX_11, 4, &b2sMountMultiplierPlus[0]) == ICM_20948_Stat_Ok); - success &= (myICM.writeDMPmems(B2S_MTX_12, 4, &b2sMountMultiplierZero[0]) == ICM_20948_Stat_Ok); - success &= (myICM.writeDMPmems(B2S_MTX_20, 4, &b2sMountMultiplierZero[0]) == ICM_20948_Stat_Ok); - success &= (myICM.writeDMPmems(B2S_MTX_21, 4, &b2sMountMultiplierZero[0]) == ICM_20948_Stat_Ok); - success &= (myICM.writeDMPmems(B2S_MTX_22, 4, &b2sMountMultiplierPlus[0]) == ICM_20948_Stat_Ok); - - // Configure the DMP Gyro Scaling Factor - // @param[in] gyro_div Value written to GYRO_SMPLRT_DIV register, where - // 0=1125Hz sample rate, 1=562.5Hz sample rate, ... 4=225Hz sample rate, ... - // 10=102.2727Hz sample rate, ... etc. - // @param[in] gyro_level 0=250 dps, 1=500 dps, 2=1000 dps, 3=2000 dps - success &= (myICM.setGyroSF(19, 3) == ICM_20948_Stat_Ok); // 19 = 55Hz (see above), 3 = 2000dps (see above) - - // Configure the Gyro full scale - // 2000dps : 2^28 - // 1000dps : 2^27 - // 500dps : 2^26 - // 250dps : 2^25 - const unsigned char gyroFullScale[4] = {0x10, 0x00, 0x00, 0x00}; // 2000dps : 2^28 - success &= (myICM.writeDMPmems(GYRO_FULLSCALE, 4, &gyroFullScale[0]) == ICM_20948_Stat_Ok); - - // Configure the Accel Only Gain: 15252014 (225Hz) 30504029 (112Hz) 61117001 (56Hz) - const unsigned char accelOnlyGain[4] = {0x03, 0xA4, 0x92, 0x49}; // 56Hz - //const unsigned char accelOnlyGain[4] = {0x00, 0xE8, 0xBA, 0x2E}; // InvenSense Nucleo example uses 225Hz - success &= (myICM.writeDMPmems(ACCEL_ONLY_GAIN, 4, &accelOnlyGain[0]) == ICM_20948_Stat_Ok); - - // Configure the Accel Alpha Var: 1026019965 (225Hz) 977872018 (112Hz) 882002213 (56Hz) - const unsigned char accelAlphaVar[4] = {0x34, 0x92, 0x49, 0x25}; // 56Hz - //const unsigned char accelAlphaVar[4] = {0x06, 0x66, 0x66, 0x66}; // Value taken from InvenSense Nucleo example - success &= (myICM.writeDMPmems(ACCEL_ALPHA_VAR, 4, &accelAlphaVar[0]) == ICM_20948_Stat_Ok); - - // Configure the Accel A Var: 47721859 (225Hz) 95869806 (112Hz) 191739611 (56Hz) - const unsigned char accelAVar[4] = {0x0B, 0x6D, 0xB6, 0xDB}; // 56Hz - //const unsigned char accelAVar[4] = {0x39, 0x99, 0x99, 0x9A}; // Value taken from InvenSense Nucleo example - success &= (myICM.writeDMPmems(ACCEL_A_VAR, 4, &accelAVar[0]) == ICM_20948_Stat_Ok); - - // Configure the Accel Cal Rate - const unsigned char accelCalRate[4] = {0x00, 0x00}; // Value taken from InvenSense Nucleo example - success &= (myICM.writeDMPmems(ACCEL_CAL_RATE, 2, &accelCalRate[0]) == ICM_20948_Stat_Ok); - - // Configure the Compass Time Buffer. The compass (magnetometer) is set to 100Hz (AK09916_mode_cont_100hz) - // in startupMagnetometer. We need to set CPASS_TIME_BUFFER to 100 too. - const unsigned char compassRate[2] = {0x00, 0x64}; // 100Hz - success &= (myICM.writeDMPmems(CPASS_TIME_BUFFER, 2, &compassRate[0]) == ICM_20948_Stat_Ok); - - // Enable DMP interrupt - // This would be the most efficient way of getting the DMP data, instead of polling the FIFO - //success &= (myICM.intEnableDMP(true) == ICM_20948_Stat_Ok); + // Initialize the DMP. initializeDMP is a weak function. You can overwrite it if you want to e.g. to change the sample rate + success &= (myICM.initializeDMP() == ICM_20948_Stat_Ok); // DMP sensor options are defined in ICM_20948_DMP.h // INV_ICM20948_SENSOR_ACCELEROMETER (16-bit accel) diff --git a/Firmware/Test Sketches/TIM_TM2_Data_Logging/TIM_TM2_Data_Logging.ino b/Firmware/Test Sketches/TIM_TM2_Data_Logging/TIM_TM2_Data_Logging.ino new file mode 100644 index 0000000..081f343 --- /dev/null +++ b/Firmware/Test Sketches/TIM_TM2_Data_Logging/TIM_TM2_Data_Logging.ino @@ -0,0 +1,620 @@ +/* + This is an example for OpenLog Artemis - based on an example from the Qwiic Sound Trigger repo: + + Qwiic Sound Trigger (VM1010) Example: accurate sound event logging with the u-blox ZED-F9P and UBX TIM_TM2 messages + + Qwiic Sound Trigger: https://www.sparkfun.com/products/17979 + + By: Paul Clark + SparkFun Electronics + Date: April 23rd, 2021 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + Based on the u-blox GNSS library example DataLoggingExample2_TIM_TM2: + Configuring the ZED-F9P GNSS to automatically send TIM TM2 reports over I2C and log them to file on SD card + + This example shows how to configure the u-blox ZED-F9P GNSS to send TIM TM2 reports when a sound event is detected + and automatically log the data to SD card in UBX format. + + ** Please note: this example will only work with u-blox ADR or High Precision GNSS or Time Sync products ** + + Hardware Connections: + Solder a 6-way row of header pins to the Qwiic Sound Trigger so you can access the TRIG pin. + Connect your ZED-F9P GNSS breakout to the OLA using a Qwiic cable. + Connect an antenna to your GNSS board. + Insert a formatted micro-SD card into the socket on the OLA. + Connect the Qwiic Sound Trigger to the ZED-F9P using a second Qwiic cable. + Use a jumper cable to connect the TRIG pin on the Qwiic Sound Trigger to the INT pin on the ZED-F9P breakout. + + Ensure you have the SparkFun Apollo3 boards installed: http://boardsmanager/All#SparkFun_Apollo3 + This code has been tested using version 1.2.1 of the Apollo3 boards on Arduino IDE 1.8.13. + Select "SparkFun Artemis ATP" as the board type. + Press upload to upload the code onto the Artemis. + Open the Serial Monitor at 115200 baud to see the output. + + Pull pin 32 low to stop logging. Logging will also stop on low battery. + + To minimise I2C bus errors, it is a good idea to open the I2C pull-up split pad links on + the u-blox module breakout. + + Each time the Qwiic Sound Trigger detects a sound, it pulls its TRIG pin high. The ZED-F9P will + detect this on its INT pin and generate a TIM TM2 message. The OLA will log the TIM TM2 message + to SD card. You can then study the timing of the sound pulse with nanosecond resolution! + The code will "debounce" the sound event and reset the VM1010 for the next sound event after 250ms. + + Note: TIM TM2 can only capture the timing of one rising edge and one falling edge per + navigation solution. So with setNavigationFrequency set to 4Hz, we can only see the timing + of one rising edge every 250ms. The code "debounces" each sound event to make sure there will + only be one rising edge event per navigation solution. + + TIM TM2 messages are only produced when a rising or falling edge is detected on the INT pin. + If you disconnect your TRIG to INT jumper wire, the messages will stop. + + Data is logged in u-blox UBX format. Please see the u-blox protocol specification for more details. + You can replay and analyze the data using u-center: + https://www.u-blox.com/en/product/u-center + + Feel like supporting open source hardware? + Buy a board from SparkFun! + ZED-F9P RTK2: https://www.sparkfun.com/products/15136 + NEO-M8P RTK: https://www.sparkfun.com/products/15005 + Qwiic Sound Trigger: https://www.sparkfun.com/products/17979 +*/ + +#include // Click here to get the library: http://librarymanager/All#SparkFun_PCA9536 +PCA9536 myTrigger; + +#include +#include + +#include //Click here to get the library: http://librarymanager/All#SparkFun_u-blox_GNSS +SFE_UBLOX_GNSS myGNSS; + +File myFile; //File that all GNSS data is written to + +// OLA Specifics: + +//Setup Qwiic Port +#include +TwoWire qwiic(1); //Will use pads 8/9 + +//Define the pin functions +//Depends on hardware version. This can be found as a marking on the PCB. +//x04 was the SparkX 'black' version. +//v10 was the first red version. +#define HARDWARE_VERSION_MAJOR 1 +#define HARDWARE_VERSION_MINOR 0 + +#if(HARDWARE_VERSION_MAJOR == 0 && HARDWARE_VERSION_MINOR == 4) +const byte PIN_MICROSD_CHIP_SELECT = 10; +const byte PIN_IMU_POWER = 22; +#elif(HARDWARE_VERSION_MAJOR == 1 && HARDWARE_VERSION_MINOR == 0) +const byte PIN_MICROSD_CHIP_SELECT = 23; +const byte PIN_IMU_POWER = 27; +const byte PIN_PWR_LED = 29; +const byte PIN_VREG_ENABLE = 25; +const byte PIN_VIN_MONITOR = 34; // VIN/3 (1M/2M - will require a correction factor) +#endif + +const byte PIN_POWER_LOSS = 3; +const byte PIN_MICROSD_POWER = 15; +const byte PIN_QWIIC_POWER = 18; +const byte PIN_STAT_LED = 19; +const byte PIN_IMU_INT = 37; +const byte PIN_IMU_CHIP_SELECT = 44; +const byte PIN_STOP_LOGGING = 32; +const byte BREAKOUT_PIN_32 = 32; +const byte BREAKOUT_PIN_TX = 12; +const byte BREAKOUT_PIN_RX = 13; +const byte BREAKOUT_PIN_11 = 11; +const byte PIN_QWIIC_SCL = 8; +const byte PIN_QWIIC_SDA = 9; + +// Globals and Consts + +float vinCorrectionFactor = 1.47; //Correction factor for the VIN measurement; to compensate for the divider impedance +float lowBatteryThreshold = 3.4; // Low battery voltage threshold (Volts) +int lowBatteryReadings = 0; // Count how many times the battery voltage has read low +const int lowBatteryReadingsLimit = 10; // Don't declare the battery voltage low until we have had this many consecutive low readings (to reject sampling noise) +const int sdPowerDownDelay = 100; //Delay for this many ms before turning off the SD card power +bool powerLossSeen = false; //Interrupt flag for power loss detection +bool stopLoggingSeen = false; //Interrupt flag for stop logging detection + +// Data Logging Specifics: + +#define packetLength 36 // TIM TM2 is 28 + 8 bytes in length (including the sync chars, class, id, length and checksum bytes) +#define VM1010_MODE 0 // The VM1010 mode pin is connected to GPIO0 on the PCA9536 +#define VM1010_TRIG 1 // The VM1010 trigger pin (Dout) is connected to GPIO1 on the PCA9536 + +// Uncomment the next line to keep the SD file open between writes: +// This will result in much faster writes, but there is a risk of the log file being left open when the power is disconnected +// and the data being lost +//#define KEEP_FILE_OPEN // Uncomment this line to keep the SD file open between writes + +int dotsPrinted = 0; // Print dots in rows of 50 while waiting for a TIM TM2 message + +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +// Callback: printTIMTM2data will be called when new TIM TM2 data arrives +// See u-blox_structs.h for the full definition of UBX_TIM_TM2_data_t +// _____ You can use any name you like for the callback. Use the same name when you call setAutoTIMTM2callback +// / _____ This _must_ be UBX_TIM_TM2_data_t +// | / _____ You can use any name you like for the struct +// | | / +// | | | +void printTIMTM2data(UBX_TIM_TM2_data_t ubxDataStruct) +{ + // It is the rising edge of the sound event (TRIG) which is important + // The falling edge is less useful, as it will be "debounced" by the loop code + + if (ubxDataStruct.flags.bits.newRisingEdge) // 1 if a new rising edge was detected + { + Serial.println(); + Serial.print(F("Sound Event Detected!")); + + Serial.print(F(" Rising Edge Counter: ")); // Rising edge counter + Serial.print(ubxDataStruct.count); + + Serial.print(F(" Time Of Week: ")); + Serial.print(ubxDataStruct.towMsR); // Time Of Week of rising edge (ms) + Serial.print(F(" ms + ")); + Serial.print(ubxDataStruct.towSubMsR); // Millisecond fraction of Time Of Week of rising edge in nanoseconds + Serial.println(F(" ns")); + + dotsPrinted = 0; // Reset dotsPrinted + } +} + +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +void setup() +{ + //If 3.3V rail drops below 3V, system will power down and maintain RTC + pinMode(PIN_POWER_LOSS, INPUT); // BD49K30G-TL has CMOS output and does not need a pull-up + + delay(1); // Let PIN_POWER_LOSS stabilize + + if (digitalRead(PIN_POWER_LOSS) == LOW) powerLossISR(); //Check PIN_POWER_LOSS just in case we missed the falling edge + attachInterrupt(digitalPinToInterrupt(PIN_POWER_LOSS), powerLossISR, FALLING); + + powerLEDOn(); // Turn the power LED on - if the hardware supports it + + pinMode(PIN_STAT_LED, OUTPUT); // Flash the STAT LED during SD writes + digitalWrite(PIN_STAT_LED, LOW); + + Serial.begin(115200); + + SPI.begin(); + + //while (!Serial); //Wait for user to open terminal + Serial.println("SparkFun OpenLog Artemis Example"); + Serial.println("u-blox GNSS TIM_TM2 Sound Event Logging"); + + analogReadResolution(14); //Increase ADC resolution from default of 10-bit + + beginQwiic(); // Turn the qwiic power on as early as possible + + beginSD(); // Enable power for the SD card + + enableCIPOpullUp(); // Enable CIPO pull-up after beginSD + + imuPowerOff(); // We're not using the IMU so turn it off + + // Initialize the Sound Trigger PCA9536 with a begin function + if (myTrigger.begin(qwiic) == false) + { + Serial.println(F("Sound Trigger (PCA9536) not detected. Please check wiring. Freezing...")); + while (1) + ; + } + + // Configure VM1010_TRIG (GPIO1) as an input for the VM1010 trigger signal (Dout) + myTrigger.pinMode(VM1010_TRIG, INPUT); + + // Configure VM1010_MODE (GPIO0) as an input for now. + // The pull-up resistor on the sound trigger will hold the VM1010 in "Wake On Sound" mode. + // We will configure VM1010_MODE as an output when we want to pull the MODE pin low to clear the wake-up event. + myTrigger.pinMode(VM1010_MODE, INPUT); + + delay(2500); // Give the GNSS time to power up + + pinMode(PIN_STOP_LOGGING, INPUT_PULLUP); + delay(1); // Let the pin stabilize + attachInterrupt(digitalPinToInterrupt(PIN_STOP_LOGGING), stopLoggingISR, FALLING); // Enable the stop logging interrupt + + Serial.println("Initializing SD card..."); + + // See if the card is present and can be initialized: + if (!SD.begin(PIN_MICROSD_CHIP_SELECT)) + { + Serial.println("Card failed, or not present. Freezing..."); + // don't do anything more: + while (1); + } + Serial.println("SD card initialized."); + + // Create or open a file called "TIM_TM2.ubx" on the SD card. + // If the file already exists, the new data is appended to the end of the file. + myFile = SD.open("TIM_TM2.ubx", FILE_WRITE); + if(!myFile) + { + Serial.println(F("Failed to create UBX data file! Freezing...")); + while (1); + } + #ifndef KEEP_FILE_OPEN + myFile.close(); // Close the log file. It will be reopened when a sound event is detected. + #endif + + //myGNSS.enableDebugging(); // Uncomment this line to enable lots of helpful GNSS debug messages on Serial + //myGNSS.enableDebugging(Serial, true); // Or, uncomment this line to enable only the important GNSS debug messages on Serial + + myGNSS.disableUBX7Fcheck(); // RAWX data can legitimately contain 0x7F, so we need to disable the "7F" check in checkUbloxI2C + + // TIM TM2 messages are 36 bytes long. + // In this example, the data will arrive no faster than four messages per second. + // So, setting the file buffer size to 361 bytes should be more than adequate. + // I.e. room for ten messages plus an empty tail byte. + myGNSS.setFileBufferSize(361); // setFileBufferSize must be called _before_ .begin + + if (myGNSS.begin(qwiic) == false) //Connect to the u-blox module using Qwiic port + { + Serial.println(F("u-blox GNSS not detected at default I2C address. Please check wiring. Freezing...")); + while (1); + } + + // Uncomment the next line if you want to reset your module back to the default settings with 1Hz navigation rate + // (This will also disable any "auto" messages that were enabled and saved by other examples and reduce the load on the I2C bus) + //myGNSS.factoryDefault(); delay(5000); + + myGNSS.setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) + myGNSS.saveConfigSelective(VAL_CFG_SUBSEC_IOPORT); //Save (only) the communications port settings to flash and BBR + + myGNSS.setNavigationFrequency(4); //Produce four navigation solutions per second + + myGNSS.setAutoTIMTM2callback(&printTIMTM2data); // Enable automatic TIM TM2 messages with callback to printTIMTM2data + + myGNSS.logTIMTM2(); // Enable TIM TM2 data logging + + while (Serial.available()) // Make sure the Serial buffer is empty + { + Serial.read(); + } + + Serial.println(F("Press any key to stop logging.")); + + resetSoundTrigger(); // Reset the sound trigger - in case it has already been triggered +} + +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +void resetSoundTrigger() +{ + // Reset the sound event by: + myTrigger.digitalWrite(VM1010_MODE, LOW); // Get ready to pull the VM1010 MODE pin low + myTrigger.pinMode(VM1010_MODE, OUTPUT); // Change the PCA9536 GPIO0 pin to an output. It will pull the VM1010 MODE pin low + myTrigger.pinMode(VM1010_MODE, INPUT); // Change the PCA9536 GPIO0 pin back to an input (with pull-up), so it will not 'fight' the mode button +} + +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +void loop() +{ + checkBattery(); // Check for low battery + + // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + + if ((powerLossSeen) || (stopLoggingSeen) || (Serial.available() > 0)) // Check for power loss or stop logging interrupts + { + stopLogging(); + } + + // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + + myGNSS.checkUblox(); // Check for the arrival of new data and process it. + myGNSS.checkCallbacks(); // Check if any callbacks are waiting to be processed. + + // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + + // Check to see if a new packetLength-byte TIM TM2 message has been stored + // This indicates if a sound event was detected + if (myGNSS.fileBufferAvailable() >= packetLength) + { + uint8_t myBuffer[packetLength]; // Create our own buffer to hold the data while we write it to SD card + + myGNSS.extractFileBufferData((uint8_t *)&myBuffer, packetLength); // Extract exactly packetLength bytes from the UBX file buffer and put them into myBuffer + + digitalWrite(PIN_STAT_LED, HIGH); // We will use PIN_STAT_LED to indicate when data is being written to the SD card + + #ifndef KEEP_FILE_OPEN + myFile = SD.open("TIM_TM2.ubx", FILE_WRITE); // Reopen the file + #endif + + myFile.write(myBuffer, packetLength); // Write exactly packetLength bytes from myBuffer to the ubxDataFile on the SD card + + #ifndef KEEP_FILE_OPEN + myFile.close(); // Close the log file after the write. Slower but safer... + #endif + + digitalWrite(PIN_STAT_LED, LOW); // Turn the LED off + + //printBuffer(myBuffer); // Uncomment this line to print the data + + // "debounce" the sound event by 250ms so we only capture one rising edge per navigation solution + for (int i = 0; i < 250; i++) + { + if (powerLossSeen) // Check for power loss + { + break; // Stop the debounce if the battery is low + } + delay(1); // Wait 250 * 1ms + } + + resetSoundTrigger(); // Reset the sound trigger. This will generate a falling edge TIM_TM2 message + } + + Serial.print("."); // Print dots in rows of 50 while we wait for a sound event + delay(50); + if (++dotsPrinted > 50) + { + Serial.println(); + dotsPrinted = 0; + } +} + +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +// Print the buffer contents as Hexadecimal +// You should see: +// SYNC CHAR 1: 0xB5 +// SYNC CHAR 2: 0x62 +// CLASS: 0x0D for TIM +// ID: 0x03 for TM2 +// LENGTH: 2-bytes Little Endian (0x1C00 = 28 bytes for TIM TM2) +// PAYLOAD: LENGTH bytes +// CHECKSUM_A +// CHECKSUM_B +// Please see the u-blox protocol specification for more details +void printBuffer(uint8_t *ptr) +{ + for (int i = 0; i < packetLength; i++) + { + if (ptr[i] < 16) Serial.print("0"); // Print a leading zero if required + Serial.print(ptr[i], HEX); // Print the byte as Hexadecimal + Serial.print(" "); + } + Serial.println(); +} + +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +//Stop Logging ISR +void stopLoggingISR(void) +{ + Serial.println(F("\nStop Logging Seen!")); + detachInterrupt(digitalPinToInterrupt(PIN_STOP_LOGGING)); //Prevent multiple interrupts + stopLoggingSeen = true; +} + +//Power Loss ISR +void powerLossISR(void) +{ + Serial.println(F("\nPower Loss Detected!")); + detachInterrupt(digitalPinToInterrupt(PIN_POWER_LOSS)); //Prevent multiple interrupts + powerLossSeen = true; +} + +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +void stopLogging(void) // Stop logging; close the SD file; go into deep sleep +{ + Serial.println(F("\nstopLogging:")); + + #ifdef KEEP_FILE_OPEN + myFile.close(); // Close the data file + #endif + + Serial.println(F("Logging stopped. Freezing...")); + Serial.flush(); // Make sure final message is printed + + delay(sdPowerDownDelay); // Give the SD card time to finish writing ***** THIS IS CRITICAL ***** + + detachInterrupt(digitalPinToInterrupt(PIN_STOP_LOGGING)); //Prevent stop logging button from waking us from sleep + detachInterrupt(digitalPinToInterrupt(PIN_POWER_LOSS)); //Prevent voltage supervisor from waking us from sleep + + qwiic.end(); //Power down I2C + + SPI.end(); //Power down SPI + + power_adc_disable(); //Power down ADC. It it started by default before setup(). + + Serial.end(); //Power down UART + + powerLEDOff(); // Turn the power LED off - if the hardware supports it + + imuPowerOff(); + + microSDPowerOff(); + + qwiicPowerOff(); // Power off the Qwiic bus + +//Force the peripherals off + am_hal_pwrctrl_periph_disable(AM_HAL_PWRCTRL_PERIPH_IOM0); + am_hal_pwrctrl_periph_disable(AM_HAL_PWRCTRL_PERIPH_IOM1); + am_hal_pwrctrl_periph_disable(AM_HAL_PWRCTRL_PERIPH_IOM2); + am_hal_pwrctrl_periph_disable(AM_HAL_PWRCTRL_PERIPH_IOM3); + am_hal_pwrctrl_periph_disable(AM_HAL_PWRCTRL_PERIPH_IOM4); + am_hal_pwrctrl_periph_disable(AM_HAL_PWRCTRL_PERIPH_IOM5); + am_hal_pwrctrl_periph_disable(AM_HAL_PWRCTRL_PERIPH_ADC); + am_hal_pwrctrl_periph_disable(AM_HAL_PWRCTRL_PERIPH_UART0); + am_hal_pwrctrl_periph_disable(AM_HAL_PWRCTRL_PERIPH_UART1); + + //Disable pads + for (int x = 0; x < 50; x++) + { + if ((x != ap3_gpio_pin2pad(PIN_MICROSD_POWER)) && + (x != ap3_gpio_pin2pad(PIN_QWIIC_POWER)) && + (x != ap3_gpio_pin2pad(PIN_IMU_POWER))) + { + am_hal_gpio_pinconfig(x, g_AM_HAL_GPIO_DISABLE); + } + } + + //We can't leave these power control pins floating + imuPowerOff(); + microSDPowerOff(); + qwiicPowerOff(); // Power off the Qwiic bus + + //Power down cache, flash, SRAM + am_hal_pwrctrl_memory_deepsleep_powerdown(AM_HAL_PWRCTRL_MEM_ALL); // Power down all flash and cache + am_hal_pwrctrl_memory_deepsleep_retain(AM_HAL_PWRCTRL_MEM_SRAM_384K); // Retain all SRAM + + //Keep the 32kHz clock running for RTC + am_hal_stimer_config(AM_HAL_STIMER_CFG_CLEAR | AM_HAL_STIMER_CFG_FREEZE); + am_hal_stimer_config(AM_HAL_STIMER_XTAL_32KHZ); + + while (1) // Stay in deep sleep until we get reset + { + am_hal_sysctrl_sleep(AM_HAL_SYSCTRL_SLEEP_DEEP); //Sleep + } +} + +// OLA Specifics + +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +void powerLEDOn() +{ +#if(HARDWARE_VERSION_MAJOR >= 1) + pinMode(PIN_PWR_LED, OUTPUT); + digitalWrite(PIN_PWR_LED, HIGH); // Turn the Power LED on +#endif +} +void powerLEDOff() +{ +#if(HARDWARE_VERSION_MAJOR >= 1) + pinMode(PIN_PWR_LED, OUTPUT); + digitalWrite(PIN_PWR_LED, LOW); // Turn the Power LED off +#endif +} + +void qwiicPowerOn() +{ + pinMode(PIN_QWIIC_POWER, OUTPUT); +#if(HARDWARE_VERSION_MAJOR == 0) + digitalWrite(PIN_QWIIC_POWER, LOW); +#else + digitalWrite(PIN_QWIIC_POWER, HIGH); +#endif +} +void qwiicPowerOff() +{ + pinMode(PIN_QWIIC_POWER, OUTPUT); +#if(HARDWARE_VERSION_MAJOR == 0) + digitalWrite(PIN_QWIIC_POWER, HIGH); +#else + digitalWrite(PIN_QWIIC_POWER, LOW); +#endif +} + +void microSDPowerOn() +{ + pinMode(PIN_MICROSD_POWER, OUTPUT); + digitalWrite(PIN_MICROSD_POWER, LOW); +} +void microSDPowerOff() +{ + pinMode(PIN_MICROSD_POWER, OUTPUT); + digitalWrite(PIN_MICROSD_POWER, HIGH); +} + +void imuPowerOn() +{ + pinMode(PIN_IMU_POWER, OUTPUT); + digitalWrite(PIN_IMU_POWER, HIGH); +} +void imuPowerOff() +{ + pinMode(PIN_IMU_POWER, OUTPUT); + digitalWrite(PIN_IMU_POWER, LOW); +} + +void beginQwiic() +{ + pinMode(PIN_QWIIC_POWER, OUTPUT); + qwiicPowerOn(); + qwiic.begin(); + qwiic.setPullups(0); //Just to make it really clear what pull-ups are being used, set pullups here. +} + +void beginSD() +{ + pinMode(PIN_MICROSD_POWER, OUTPUT); + pinMode(PIN_MICROSD_CHIP_SELECT, OUTPUT); + digitalWrite(PIN_MICROSD_CHIP_SELECT, HIGH); //Be sure SD is deselected + + // For reasons I don't understand, we seem to have to wait for at least 1ms after SPI.begin before we call microSDPowerOn. + // If you comment the next line, the Artemis resets at microSDPowerOn when beginSD is called from wakeFromSleep... + // But only on one of my V10 red boards. The second one I have doesn't seem to need the delay!? + delay(1); + + microSDPowerOn(); +} + +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +bool enableCIPOpullUp() +{ + //Add CIPO pull-up + ap3_err_t retval = AP3_OK; + am_hal_gpio_pincfg_t cipoPinCfg = AP3_GPIO_DEFAULT_PINCFG; + cipoPinCfg.uFuncSel = AM_HAL_PIN_6_M0MISO; + cipoPinCfg.eDriveStrength = AM_HAL_GPIO_PIN_DRIVESTRENGTH_12MA; + cipoPinCfg.eGPOutcfg = AM_HAL_GPIO_PIN_OUTCFG_PUSHPULL; + cipoPinCfg.uIOMnum = AP3_SPI_IOM; + cipoPinCfg.ePullup = AM_HAL_GPIO_PIN_PULLUP_1_5K; + padMode(MISO, cipoPinCfg, &retval); + return (retval == AP3_OK); +} + +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +//Read the VIN voltage +float readVIN() +{ + // Only supported on >= V10 hardware +#if(HARDWARE_VERSION_MAJOR == 0) + return(0.0); // Return 0.0V on old hardware +#else + int div3 = analogRead(PIN_VIN_MONITOR); //Read VIN across a 1/3 resistor divider + float vin = (float)div3 * 3.0 * 2.0 / 16384.0; //Convert 1/3 VIN to VIN (14-bit resolution) + vin = vin * vinCorrectionFactor; //Correct for divider impedance (determined experimentally) + //Serial.print(F("VIN (Volts): ")); + //Serial.println(vin, 2); + return (vin); +#endif +} + +// Read the battery voltage +// If it is low, increment lowBatteryReadings +// If lowBatteryReadings exceeds lowBatteryReadingsLimit then powerDown +void checkBattery(void) +{ +#if(HARDWARE_VERSION_MAJOR >= 1) + float voltage = readVIN(); // Read the battery voltage + if (voltage < lowBatteryThreshold) // Is the voltage low? + { + lowBatteryReadings++; // Increment the low battery count + if (lowBatteryReadings > lowBatteryReadingsLimit) // Have we exceeded the low battery count limit? + { + // Gracefully powerDown + Serial.println(F("Low battery detected!")); + stopLogging(); + } + } + else + { + lowBatteryReadings = 0; // Reset the low battery count + } +#endif +} diff --git a/README.md b/README.md index 6aaa4f3..6f981ec 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,7 @@ The OpenLog Artemis automatically scans, detects, configures, and logs various Q * [VL53L1X LIDAR Distance Sensor](https://www.sparkfun.com/products/14722) * [ADS122C04 ADC PT100 Sensor](https://www.sparkfun.com/products/16770) * [Qwiic Mux](https://www.sparkfun.com/products/16784) allowing for the chaining of up to 64 unique buses! +* [Pulse Oximeter and Heart Rate Sensor](https://www.sparkfun.com/products/15219) (requires exclusive use of pins 32 and 11) * More boards are being added all the time! Very low power logging is supported. OpenLog Artemis can be configured to take readings at 500 times a second, or as slow as 1 reading every 24 hours. You choose! When there is more than 2 seconds between readings OLA will automatically power down itself and the sensors on the bus resulting in a sleep current of approximately 18uA. This means a normal [2Ah battery](https://www.sparkfun.com/products/13855) will enable logging for more than 4,000 days! OpenLog Artemis has built-in LiPo charging set at 450mA/hr. diff --git a/SENSOR_UNITS.md b/SENSOR_UNITS.md index a47e854..24f584d 100644 --- a/SENSOR_UNITS.md +++ b/SENSOR_UNITS.md @@ -1,329 +1,362 @@ -# OpenLog Artemis : Sensor Units - -This document summarizes the units used for each sensor measurement. - ---- -## Index - -### Built-in Inertial Measurement Unit: - -- [ICM-20948 IMU](#ICM-20948-IMU) - -### Global Navigation Satellite System (GNSS) navigation data: - -- [u-blox GNSS boards](#u-blox-GNSS-boards) - -### Pressure, Altitude, Humidity and Temperature Data: - -- [BME280 atmospheric sensor](#BME280-atmospheric-sensor) -- [LPS25HB absolute pressure sensor](#LPS25HB-absolute-pressure-sensor) -- [MS8607 PHT sensor](#MS8607-PHT-sensor) -- [MPR0025PA MicroPressure sensor](#MPR0025PA-MicroPressure-sensor) -- [MS5637 barometric pressure sensor](#MS5637-barometric-pressure-sensor) -- [MS5837 depth and pressure sensor](#MS5837-depth-pressure-sensor) -- [AHT20 humidity and temperature sensor](#AHT20-humidity-and-temperature-sensor) -- [SHTC3 humidity and temperature sensor](#SHTC3-humidity-and-temperature-sensor) - -### Differential Pressure: - -- [SDP3X differential pressure sensor](#SDP3X-differential-pressure-sensor) - -### Air Quality and Environmental Sensors: - -- [CCS811 air quality sensor](#CCS811-air-quality-sensor) -- [VEML6075 UV light sensor](#VEML6075-UV-light-sensor) -- [SGP30 air quality and Volatile Organic Compound (VOC) sensor](#SGP30-air-quality-and-VOC-sensor) -- [SGP40 air quality (VOC index) sensor](#SGP40-air-quality-sensor) -- [SCD30 CO2 humidity and temperature sensor](#SCD30-CO2-humidity-and-temperature-sensor) -- [SN-GCJA5 Particle Sensor](#SN-GCJA5-Particle-Sensor) - -### Distance: - -- [VL53L1X laser Time of Flight (ToF) sensor](#VL53L1X-laser-ToF-sensor) -- [VCNL4040 proximity sensor](#VCNL4040-proximity-sensor) - -### Precision Temperature Sensors: - -- [MCP9600 thermocouple amplifier](#MCP9600-thermocouple-amplifier) -- [Qwiic PT100 ADS122C04 platinum resistance sensor](#Qwiic-PT100-ADS122C04-platinum-resistance-sensor) -- [TMP117 precision temperature sensor](#TMP117-precision-temperature-sensor) - -### Weight: - -- [NAU7802 load cell sensor](#NAU7802-load-cell-sensor) - -### ADC: - -- [Qwiic PT100 ADS122C04 platinum resistance sensor](#Qwiic-PT100-ADS122C04-platinum-resistance-sensor) - ---- -## Sensor Units - ---- -## ICM-20948 IMU - -| []() | | | -|---|---|---| -| Accelerometer | aX,aY,aZ | milli g | -| Gyro | gX,gY,gZ | Degrees per Second | -| Magnetometer | mX,mY,mZ | micro Tesla | -| Temperature | imu_degC | Degrees Centigrade | - ---- -## u-blox GNSS boards - -| []() | | | -|---|---|---| -| Date | gps_Date | MM/DD/YYYY or DD/MM/YYYY | -| Time | gps_Time | HH:MM:SS.SSS | -| Lat & Lon | gps_Lat,gps_Long | Degrees-7 | -| Altitude | gps_Alt | mm | -| Altitude MSL | gps_AltMSL | mm | -| SIV | gps_SIV | Count | -| Fix Type | gps_FixType | 0-5 | -| Carrier Soln. | gps_CarrierSolution | 0-2 | -| Ground Speed | gps_GroundSpeed | mm/s | -| Heading | gps_Heading | Degrees-5 | -| PDOP | gps_pDOP | 10-2 (dimensionless) | -| Time Of Week | gps_iTOW | ms | - -Lat = Latitude -Lon = Longitude -MSL = Metres above Sea Level -SIV = Satellites In View -PDOP = Positional Dilution Of Precision - -Fix Type: -0: No -1: Dead Reckoning Only -2: 2D -3: 3D -4: GNSS + Dead Reckoning -5: Time Only - -Carrier Solution: -0: No -1: Float Solution -2: Fixed Solution - ---- -## BME280 atmospheric sensor - -| []() | | | -|---|---|---| -| Pressure | pressure_Pa | Pascals | -| Humidity | humidity_% | Percent | -| Altitude | altitude_m | m | -| Temperature | temp_degC | Degrees Centigrade | - ---- -## LPS25HB absolute pressure sensor - -| []() | | | -|---|---|---| -| Pressure | pressure_hPa | hectoPascals | -| Temperature | pressure_degC | Degrees Centigrade | - ---- -## MS8607 PHT sensor - -| []() | | | -|---|---|---| -| Humidity | humidity_% | Percent | -| Pressure | hPa | hectoPascals | -| Temperature | degC | Degrees Centigrade | - ---- -## MPR0025PA MicroPressure sensor - -| []() | | | -|---|---|---| -| Pressure (PSI) | PSI | Pounds per Square Inch | -| Pressure (Pa) | Pa | Pascals | -| Pressure (kPa) | kPa | kiloPascals | -| Pressure (torr) | torr | torr | -| Pressure (inHg) | inHg | inches of Mercury | -| Pressure (atm) | atm | atmospheres | -| Pressure (bar) | bar | barometric pressure | - ---- -## MS5637 barometric pressure sensor - -| []() | | | -|---|---|---| -| Pressure | pressure_hPa | hectoPascals | -| Temperature | temperature_degC | Degrees Centigrade | - ---- -## MS5837 depth pressure sensor - -| []() | | | -|---|---|---| -| Pressure | mbar | millibar | -| Temperature | degC | Degrees Centigrade | -| Depth | depth_m | Metres | -| Altitude | alt_m | Metres | - ---- -## SDP3X differential pressure sensor - -| []() | | | -|---|---|---| -| Pressure | Pa | Pascals | -| Temperature | degC | Degrees Centigrade | - ---- -## AHT20 humidity and temperature sensor - -| []() | | | -|---|---|---| -| Humidity | humidity_% | Percent | -| Temperature | degC | Degrees Centigrade | - ---- -## SHTC3 humidity and temperature sensor - -| []() | | | -|---|---|---| -| Humidity | humidity_% | Percent | -| Temperature | degC | Degrees Centigrade | - ---- -## CCS811 air quality sensor - -| []() | | | -|---|---|---| -| VOC | tvoc_ppb | Parts Per Billion | -| CO2 | co2_ppm | Parts Per Million | - -VOC = Volatile Organic Compounds - ---- -## VEML6075 UV light sensor - -| []() | | | -|---|---|---| -| UVA | uva | | -| UVB | uvb | | -| UV Index | uvIndex | | - ---- -## SGP30 air quality and VOC sensor - -| []() | | | -|---|---|---| -| Total VOC | tvoc_ppb | Parts Per Billion | -| CO2 | co2_ppm | Parts Per Million | -| H2 | H2 | none | -| Ethanol | ethanol | none | - ---- -## SGP40 air quality sensor - -| []() | | | -|---|---|---| -| VOC Index | VOCindex | none | - ---- -## SCD30 CO2 humidity and temperature sensor - -| []() | | | -|---|---|---| -| CO2 | co2_ppm | Parts Per Million | -| Humidity | humidity_% | Percent | -| Temperature | degC | Degrees Centigrade | - ---- -## SN-GCJA5 Particle Sensor - -| []() | | | -|---|---|---| -| Particle Density (1.0µm) | PM1_0 | µg/m3 | -| Particle Density (2.5µm) | PM2_5 | µg/m3 | -| Particle Density (10µm) | PM10 | µg/m3 | -| Particle Count (0.5µm) | PC0_5 | Count | -| Particle Count (1.0µm) | PC1_0 | Count | -| Particle Count (2.5µm) | PC2_5 | Count | -| Particle Count (5.0µm) | PC5_0 | Count | -| Particle Count (7.5µm) | PC7_5 | Count | -| Particle Count (10µm) | PC10 | Count | -| Sensor Status | Sensors | | -| Photodiode Status | PD | 0-3 | -| Laser Diode Status | LD | 0-3 | -| Fan Status | Fan | 0-3 | - -Sensor status: -| []() | | -|---|---| -| | PD LD Fan | -| 0 | 0 0 0 | -| 1 | Any 1, nor 2 & 3 | -| 2 | Any 2 | -| 3 | Any 3 nor 2 | - -PD status: -0: Normal status -1: Normal status (within -80% against initial value), with S/W correction -2: Abnormal (below -90% against initial value), loss of function -3: Abnormal (below -80% against initial value), with S/W correction - -LD operational status: -0: Normal status -1: Normal status (within -70% against initial LOP), with S/W correction -2: Abnormal (below -90% against initial LOP) or no LOP, loss of function -3: Abnormal (below -70% against initial LOP), with S/W correction - -Fan operational status: -0: Normal status -1: Normal status (1,000rpm or more), with S/W correction -2: In initial calibration -3: Abnormal (below 1,000rpm), out of control - ---- -## VL53L1X laser ToF sensor - -| []() | | | -|---|---|---| -| Distance | distance_mm | mm | -| Range Status | distance_rangeStatus(0=good) | | -| Signal Rate | distance_signalRate | | - ---- -## VCNL4040 proximity sensor - -| []() | | | -|---|---|---| -| Proximity | prox(no unit) | none | -| Ambient Light | ambient_lux | | - ---- -## MCP9600 thermocouple amplifier - -| []() | | | -|---|---|---| -| Temperature | thermo_degC | Degrees Centigrade | -| Ambient Temperature | thermo_ambientDegC | Degrees Centigrade | - ---- -## Qwiic PT100 ADS122C04 platinum resistance sensor - -| []() | | | -|---|---|---| -| Temperature (C) | degC | Degrees Centigrade | -| Temperature (F) | degF | Degrees Fahrenheit | -| Temperature Internal | degC | Degrees Centigrade | -| Raw Voltage | V*2.048/2^23 | Volts * 2.048 / 223 | - ---- -## TMP117 precision temperature sensor - -| []() | | | -|---|---|---| -| Temperature | degC | Degrees Centigrade | - ---- -## NAU7802 load cell sensor - -| []() | | | -|---|---|---| -| Weight | weight(no unit) | none | +# OpenLog Artemis : Sensor Units + +This document summarizes the units used for each sensor measurement. + +--- +## Index + +### Built-in Inertial Measurement Unit: + +- [ICM-20948 IMU](#ICM-20948-IMU) + +### Global Navigation Satellite System (GNSS) navigation data: + +- [u-blox GNSS boards](#u-blox-GNSS-boards) + +### Pressure, Altitude, Humidity and Temperature Data: + +- [BME280 atmospheric sensor](#BME280-atmospheric-sensor) +- [LPS25HB absolute pressure sensor](#LPS25HB-absolute-pressure-sensor) +- [MS8607 PHT sensor](#MS8607-PHT-sensor) +- [MPR0025PA MicroPressure sensor](#MPR0025PA-MicroPressure-sensor) +- [MS5637 barometric pressure sensor](#MS5637-barometric-pressure-sensor) +- [MS5837 depth and pressure sensor](#MS5837-depth-pressure-sensor) +- [AHT20 humidity and temperature sensor](#AHT20-humidity-and-temperature-sensor) +- [SHTC3 humidity and temperature sensor](#SHTC3-humidity-and-temperature-sensor) + +### Differential Pressure: + +- [SDP3X differential pressure sensor](#SDP3X-differential-pressure-sensor) + +### Air Quality and Environmental Sensors: + +- [CCS811 air quality sensor](#CCS811-air-quality-sensor) +- [VEML6075 UV light sensor](#VEML6075-UV-light-sensor) +- [SGP30 air quality and Volatile Organic Compound (VOC) sensor](#SGP30-air-quality-and-VOC-sensor) +- [SGP40 air quality (VOC index) sensor](#SGP40-air-quality-sensor) +- [SCD30 CO2 humidity and temperature sensor](#SCD30-CO2-humidity-and-temperature-sensor) +- [SN-GCJA5 Particle Sensor](#SN-GCJA5-Particle-Sensor) + +### Distance: + +- [VL53L1X laser Time of Flight (ToF) sensor](#VL53L1X-laser-ToF-sensor) +- [VCNL4040 proximity sensor](#VCNL4040-proximity-sensor) + +### Precision Temperature Sensors: + +- [MCP9600 thermocouple amplifier](#MCP9600-thermocouple-amplifier) +- [Qwiic PT100 ADS122C04 platinum resistance sensor](#Qwiic-PT100-ADS122C04-platinum-resistance-sensor) +- [TMP117 precision temperature sensor](#TMP117-precision-temperature-sensor) + +### Weight: + +- [NAU7802 load cell sensor](#NAU7802-load-cell-sensor) + +### ADC: + +- [Qwiic PT100 ADS122C04 platinum resistance sensor](#Qwiic-PT100-ADS122C04-platinum-resistance-sensor) + +### Biometric Sensors: + +- [Pulse Oximeter and Heart Rate Sensor](#Pulse-Oximeter) + +--- +## Sensor Units + +--- +## ICM-20948 IMU + +| []() | | | +|---|---|---| +| Accelerometer | aX,aY,aZ | milli g | +| Gyro | gX,gY,gZ | Degrees per Second | +| Magnetometer | mX,mY,mZ | micro Tesla | +| Temperature | imu_degC | Degrees Centigrade | + +--- +## u-blox GNSS boards + +| []() | | | +|---|---|---| +| Date | gps_Date | MM/DD/YYYY or DD/MM/YYYY | +| Time | gps_Time | HH:MM:SS.SSS | +| Lat & Lon | gps_Lat,gps_Long | Degrees-7 | +| Altitude | gps_Alt | mm | +| Altitude MSL | gps_AltMSL | mm | +| SIV | gps_SIV | Count | +| Fix Type | gps_FixType | 0-5 | +| Carrier Soln. | gps_CarrierSolution | 0-2 | +| Ground Speed | gps_GroundSpeed | mm/s | +| Heading | gps_Heading | Degrees-5 | +| PDOP | gps_pDOP | 10-2 (dimensionless) | +| Time Of Week | gps_iTOW | ms | + +Lat = Latitude +Lon = Longitude +MSL = Metres above Sea Level +SIV = Satellites In View +PDOP = Positional Dilution Of Precision + +Fix Type: +0: No +1: Dead Reckoning Only +2: 2D +3: 3D +4: GNSS + Dead Reckoning +5: Time Only + +Carrier Solution: +0: No +1: Float Solution +2: Fixed Solution + +--- +## BME280 atmospheric sensor + +| []() | | | +|---|---|---| +| Pressure | pressure_Pa | Pascals | +| Humidity | humidity_% | Percent | +| Altitude | altitude_m | m | +| Temperature | temp_degC | Degrees Centigrade | + +--- +## LPS25HB absolute pressure sensor + +| []() | | | +|---|---|---| +| Pressure | pressure_hPa | hectoPascals | +| Temperature | pressure_degC | Degrees Centigrade | + +--- +## MS8607 PHT sensor + +| []() | | | +|---|---|---| +| Humidity | humidity_% | Percent | +| Pressure | hPa | hectoPascals | +| Temperature | degC | Degrees Centigrade | + +--- +## MPR0025PA MicroPressure sensor + +| []() | | | +|---|---|---| +| Pressure (PSI) | PSI | Pounds per Square Inch | +| Pressure (Pa) | Pa | Pascals | +| Pressure (kPa) | kPa | kiloPascals | +| Pressure (torr) | torr | torr | +| Pressure (inHg) | inHg | inches of Mercury | +| Pressure (atm) | atm | atmospheres | +| Pressure (bar) | bar | barometric pressure | + +--- +## MS5637 barometric pressure sensor + +| []() | | | +|---|---|---| +| Pressure | pressure_hPa | hectoPascals | +| Temperature | temperature_degC | Degrees Centigrade | + +--- +## MS5837 depth pressure sensor + +| []() | | | +|---|---|---| +| Pressure | mbar | millibar | +| Temperature | degC | Degrees Centigrade | +| Depth | depth_m | Metres | +| Altitude | alt_m | Metres | + +--- +## SDP3X differential pressure sensor + +| []() | | | +|---|---|---| +| Pressure | Pa | Pascals | +| Temperature | degC | Degrees Centigrade | + +--- +## AHT20 humidity and temperature sensor + +| []() | | | +|---|---|---| +| Humidity | humidity_% | Percent | +| Temperature | degC | Degrees Centigrade | + +--- +## SHTC3 humidity and temperature sensor + +| []() | | | +|---|---|---| +| Humidity | humidity_% | Percent | +| Temperature | degC | Degrees Centigrade | + +--- +## CCS811 air quality sensor + +| []() | | | +|---|---|---| +| VOC | tvoc_ppb | Parts Per Billion | +| CO2 | co2_ppm | Parts Per Million | + +VOC = Volatile Organic Compounds + +--- +## VEML6075 UV light sensor + +| []() | | | +|---|---|---| +| UVA | uva | | +| UVB | uvb | | +| UV Index | uvIndex | | + +--- +## SGP30 air quality and VOC sensor + +| []() | | | +|---|---|---| +| Total VOC | tvoc_ppb | Parts Per Billion | +| CO2 | co2_ppm | Parts Per Million | +| H2 | H2 | none | +| Ethanol | ethanol | none | + +--- +## SGP40 air quality sensor + +| []() | | | +|---|---|---| +| VOC Index | VOCindex | none | + +--- +## SCD30 CO2 humidity and temperature sensor + +| []() | | | +|---|---|---| +| CO2 | co2_ppm | Parts Per Million | +| Humidity | humidity_% | Percent | +| Temperature | degC | Degrees Centigrade | + +--- +## SN-GCJA5 Particle Sensor + +| []() | | | +|---|---|---| +| Particle Density (1.0µm) | PM1_0 | µg/m3 | +| Particle Density (2.5µm) | PM2_5 | µg/m3 | +| Particle Density (10µm) | PM10 | µg/m3 | +| Particle Count (0.5µm) | PC0_5 | Count | +| Particle Count (1.0µm) | PC1_0 | Count | +| Particle Count (2.5µm) | PC2_5 | Count | +| Particle Count (5.0µm) | PC5_0 | Count | +| Particle Count (7.5µm) | PC7_5 | Count | +| Particle Count (10µm) | PC10 | Count | +| Sensor Status | Sensors | | +| Photodiode Status | PD | 0-3 | +| Laser Diode Status | LD | 0-3 | +| Fan Status | Fan | 0-3 | + +Sensor status: +| []() | | +|---|---| +| | PD LD Fan | +| 0 | 0 0 0 | +| 1 | Any 1, nor 2 & 3 | +| 2 | Any 2 | +| 3 | Any 3 nor 2 | + +PD status: +0: Normal status +1: Normal status (within -80% against initial value), with S/W correction +2: Abnormal (below -90% against initial value), loss of function +3: Abnormal (below -80% against initial value), with S/W correction + +LD operational status: +0: Normal status +1: Normal status (within -70% against initial LOP), with S/W correction +2: Abnormal (below -90% against initial LOP) or no LOP, loss of function +3: Abnormal (below -70% against initial LOP), with S/W correction + +Fan operational status: +0: Normal status +1: Normal status (1,000rpm or more), with S/W correction +2: In initial calibration +3: Abnormal (below 1,000rpm), out of control + + +--- +## VL53L1X laser ToF sensor + +| []() | | | +|---|---|---| +| Distance | distance_mm | mm | +| Range Status | distance_rangeStatus(0=good) | | +| Signal Rate | distance_signalRate | | + +--- +## VCNL4040 proximity sensor + +| []() | | | +|---|---|---| +| Proximity | prox(no unit) | none | +| Ambient Light | ambient_lux | | + +--- +## MCP9600 thermocouple amplifier + +| []() | | | +|---|---|---| +| Temperature | thermo_degC | Degrees Centigrade | +| Ambient Temperature | thermo_ambientDegC | Degrees Centigrade | + +--- +## Qwiic PT100 ADS122C04 platinum resistance sensor + +| []() | | | +|---|---|---| +| Temperature (C) | degC | Degrees Centigrade | +| Temperature (F) | degF | Degrees Fahrenheit | +| Temperature Internal | degC | Degrees Centigrade | +| Raw Voltage | V*2.048/2^23 | Volts * 2.048 / 223 | + +--- +## TMP117 precision temperature sensor + +| []() | | | +|---|---|---| +| Temperature | degC | Degrees Centigrade | + +--- +## NAU7802 load cell sensor + +| []() | | | +|---|---|---| +| Weight | weight(no unit) | none | + +--- +## Pulse Oximeter + +| []() | | | +|---|---|---| +| bpm | heart rate | beats per minute | +| conf% | confidence | percent | +| O2% | oxygen level | percent | +| stat | sensor status | 0 to 3 | +| eStat | extended status | -6 to 1 | +| O2R | oxygen SpO2 R value | | + +Sensor status: +0: No object detected. +1: Object detected. +2: Object other than finger detected +3: Finger detected + +Extended status: +0: Success ++1: Not ready +-1: Object detected +-2: Excessive sensor device motion +-3: No object detected +-4: Pressing too hard +-5: Object other than finger detected +-6: Excessive finger motion \ No newline at end of file