From d6a31f5b290d7e74fdc473023bfe7c590e600246 Mon Sep 17 00:00:00 2001 From: PauZC Date: Sun, 6 Dec 2020 09:56:01 +0000 Subject: [PATCH 01/14] Changing the sprintf for micros() from %d to %lu --- Firmware/OpenLog_Artemis/Sensors.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Firmware/OpenLog_Artemis/Sensors.ino b/Firmware/OpenLog_Artemis/Sensors.ino index 3b110ff..4df6662 100644 --- a/Firmware/OpenLog_Artemis/Sensors.ino +++ b/Firmware/OpenLog_Artemis/Sensors.ino @@ -39,7 +39,7 @@ void getData() if (settings.logMicroseconds) { char microseconds[11]; // - sprintf(microseconds, "%d,", micros()); + sprintf(microseconds, "%lu,", micros()); strcat(outputData, microseconds); } } //end if use RTC for timestamp From 69add82d90a27c8f1712b5c25c2ad954eac72732 Mon Sep 17 00:00:00 2001 From: PauZC Date: Sun, 6 Dec 2020 09:58:03 +0000 Subject: [PATCH 02/14] Changing release_candidate to v1.9 --- Firmware/OpenLog_Artemis/OpenLog_Artemis.ino | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Firmware/OpenLog_Artemis/OpenLog_Artemis.ino b/Firmware/OpenLog_Artemis/OpenLog_Artemis.ino index 6c714b6..7f1db9d 100644 --- a/Firmware/OpenLog_Artemis/OpenLog_Artemis.ino +++ b/Firmware/OpenLog_Artemis/OpenLog_Artemis.ino @@ -77,7 +77,7 @@ */ const int FIRMWARE_VERSION_MAJOR = 1; -const int FIRMWARE_VERSION_MINOR = 8; +const int FIRMWARE_VERSION_MINOR = 9; //Define the OLA board identifier: // This is an int which is unique to this variant of the OLA and which allows us @@ -87,7 +87,7 @@ const int FIRMWARE_VERSION_MINOR = 8; // the variant * 0x100 (OLA = 1; GNSS_LOGGER = 2; GEOPHONE_LOGGER = 3) // the major firmware version * 0x10 // the minor firmware version -#define OLA_IDENTIFIER 0x118 // Stored as 280 decimal in OLA_settings.txt +#define OLA_IDENTIFIER 0x119 // Stored as 280 decimal in OLA_settings.txt #include "settings.h" From 753410bd9ad5308475588b0e3903944c9ce85eb0 Mon Sep 17 00:00:00 2001 From: PauZC Date: Sun, 6 Dec 2020 10:10:11 +0000 Subject: [PATCH 03/14] Correcting the magnetometer typo --- Firmware/OpenLog_Artemis/menuIMU.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Firmware/OpenLog_Artemis/menuIMU.ino b/Firmware/OpenLog_Artemis/menuIMU.ino index c88fae4..64f2371 100644 --- a/Firmware/OpenLog_Artemis/menuIMU.ino +++ b/Firmware/OpenLog_Artemis/menuIMU.ino @@ -19,7 +19,7 @@ void menuIMU() if (settings.logIMUGyro) Serial.println(F("Enabled")); else Serial.println(F("Disabled")); - Serial.print(F("4) Magnotometer Logging: ")); + Serial.print(F("4) Magnetometer Logging: ")); if (settings.logIMUMag) Serial.println(F("Enabled")); else Serial.println(F("Disabled")); From fb5026e171ade9dba3ee05ebac9dc583b7f42a3a Mon Sep 17 00:00:00 2001 From: PaulZC Date: Thu, 28 Jan 2021 17:01:59 +0000 Subject: [PATCH 04/14] Allow the TX and RX pins to be used for the Terminal This is a **big** change. Via a new option in the Terminal Output menu, the TX pins and RX pins can now be used for the Terminal. When this feature is enabled, all Serial data that would normally go just to Serial (USB) now goes to SerialLog too. The menu choices functions accept data from Serial and SerialLog. The SD card menu (option "s") can be accessed on either Serial (USB) or SerialLog (TX/RX). This menu will only appear on one or the other. ZMODEM support works on Serial (USB) or SerialLog (TX/RX) too. This is very cool beacuase you can now hook up a Bluetooth Mate or BlueSMiRF or radio modem to the TX and RX pins and access the OLA via that instead! --- Firmware/OpenLog_Artemis/OpenLog_Artemis.ino | 164 ++- Firmware/OpenLog_Artemis/Sensors.ino | 10 +- Firmware/OpenLog_Artemis/autoDetect.ino | 48 +- Firmware/OpenLog_Artemis/logging.ino | 16 +- Firmware/OpenLog_Artemis/lowerPower.ino | 28 +- .../OpenLog_Artemis/menuAnalogLogging.ino | 78 +- .../OpenLog_Artemis/menuAttachedDevices.ino | 1052 +++++++++-------- Firmware/OpenLog_Artemis/menuDebug.ino | 12 +- Firmware/OpenLog_Artemis/menuIMU.ino | 188 +-- Firmware/OpenLog_Artemis/menuMain.ino | 66 +- Firmware/OpenLog_Artemis/menuPower.ino | 46 +- .../OpenLog_Artemis/menuSerialLogging.ino | 38 +- Firmware/OpenLog_Artemis/menuTerminal.ino | 160 ++- Firmware/OpenLog_Artemis/menuTimeStamp.ino | 79 +- Firmware/OpenLog_Artemis/nvm.ino | 131 +- Firmware/OpenLog_Artemis/settings.h | 1 + Firmware/OpenLog_Artemis/support.ino | 134 ++- Firmware/OpenLog_Artemis/zmodem.h | 2 +- Firmware/OpenLog_Artemis/zmodem.ino | 60 +- Firmware/OpenLog_Artemis/zmodem_config.h | 6 +- Firmware/OpenLog_Artemis/zmodem_fixes.h | 37 +- Firmware/OpenLog_Artemis/zmodem_rz.cpp | 114 +- Firmware/OpenLog_Artemis/zmodem_sz.cpp | 72 +- Firmware/OpenLog_Artemis/zmodem_zm.cpp | 167 ++- Firmware/OpenLog_Artemis/zmodem_zm.h | 4 +- 25 files changed, 1543 insertions(+), 1170 deletions(-) diff --git a/Firmware/OpenLog_Artemis/OpenLog_Artemis.ino b/Firmware/OpenLog_Artemis/OpenLog_Artemis.ino index 7f1db9d..888243c 100644 --- a/Firmware/OpenLog_Artemis/OpenLog_Artemis.ino +++ b/Firmware/OpenLog_Artemis/OpenLog_Artemis.ino @@ -13,6 +13,9 @@ The Board should be set to SparkFun Apollo3 \ SparkFun RedBoard Artemis ATP. + Please note: this firmware currently only compiles on v1.2.1 of the Apollo3 boards. + It does not yet work with the new Mbed version (v2.0) of the core. + v1.0 Power Consumption: Sleep between reads, RTC fully charged, no Qwiic, SD, no USB, no Power LED: 260uA 10Hz logging IMU, no Qwiic, SD, no USB, no Power LED: 9-27mA @@ -74,6 +77,8 @@ (done) Add a fix to make sure the MS8607 is detected correctly: https://github.com/sparkfun/OpenLog_Artemis/issues/54 (done) Add logMicroseconds: https://github.com/sparkfun/OpenLog_Artemis/issues/49 (done) Add an option to use autoPVT when logging GNSS data: https://github.com/sparkfun/OpenLog_Artemis/issues/50 + (done) Corrected an issue when using multiple MS8607's: https://github.com/sparkfun/OpenLog_Artemis/issues/62 + (done) Add a feature to use the TX and RX pins as a duplicate Terminal */ const int FIRMWARE_VERSION_MAJOR = 1; @@ -87,7 +92,7 @@ const int FIRMWARE_VERSION_MINOR = 9; // the variant * 0x100 (OLA = 1; GNSS_LOGGER = 2; GEOPHONE_LOGGER = 3) // the major firmware version * 0x10 // the minor firmware version -#define OLA_IDENTIFIER 0x119 // Stored as 280 decimal in OLA_settings.txt +#define OLA_IDENTIFIER 0x119 // Stored as 281 decimal in OLA_settings.txt #include "settings.h" @@ -198,7 +203,7 @@ ICM_20948_SPI myICM; #include "SparkFun_VCNL4040_Arduino_Library.h" //Click here to get the library: http://librarymanager/All#SparkFun_VCNL4040 #include "SparkFun_MS5637_Arduino_Library.h" //Click here to get the library: http://librarymanager/All#SparkFun_MS5637 #include "SparkFun_TMP117.h" //Click here to get the library: http://librarymanager/All#SparkFun_TMP117 -#include "SparkFun_Ublox_Arduino_Library.h" //http://librarymanager/All#SparkFun_Ublox_GPS +#include "SparkFun_u-blox_GNSS_Arduino_Library.h" //http://librarymanager/All#SparkFun_u-blox_GNSS #include "SparkFun_Qwiic_Scale_NAU7802_Arduino_Library.h" //Click here to get the library: http://librarymanager/All#SparkFun_NAU7802 #include "SparkFun_SCD30_Arduino_Library.h" //Click here to get the library: http://librarymanager/All#SparkFun_SCD30 #include "SparkFun_Qwiic_Humidity_AHT20.h" //Click here to get the library: http://librarymanager/All#Qwiic_Humidity_AHT20 by SparkFun @@ -230,9 +235,34 @@ const int lowBatteryReadingsLimit = 10; // Don't declare the battery voltage low volatile static bool triggerEdgeSeen = false; //Flag to indicate if a trigger interrupt has been seen //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +uint8_t getByteChoice(int numberOfSeconds, bool updateDZSERIAL = false); // Header + +// gfvalvo's flash string helper code: https://forum.arduino.cc/index.php?topic=533118.msg3634809#msg3634809 +void SerialPrint(const char *); +void SerialPrint(const __FlashStringHelper *); +void SerialPrintln(const char *); +void SerialPrintln(const __FlashStringHelper *); +void DoSerialPrint(char (*)(const char *), const char *, bool newLine = false); + //unsigned long startTime = 0; -#define DUMP(varname) {Serial.printf("%s: %llu\r\n", #varname, varname);} +#define DUMP( varname ) {Serial.printf("%s: %llu\r\n", #varname, varname); if (settings.useTxRxPinsForTerminal == true) SerialLog.printf("%s: %llu\r\n", #varname, varname);} +#define SerialPrintf1( var ) {Serial.printf( var ); if (settings.useTxRxPinsForTerminal == true) SerialLog.printf( var );} +#define SerialPrintf2( var1, var2 ) {Serial.printf( var1, var2 ); if (settings.useTxRxPinsForTerminal == true) SerialLog.printf( var1, var2 );} +#define SerialPrintf3( var1, var2, var3 ) {Serial.printf( var1, var2, var3 ); if (settings.useTxRxPinsForTerminal == true) SerialLog.printf( var1, var2, var3 );} +#define SerialPrintf4( var1, var2, var3, var4 ) {Serial.printf( var1, var2, var3, var4 ); if (settings.useTxRxPinsForTerminal == true) SerialLog.printf( var1, var2, var3, var4 );} +#define SerialPrintf5( var1, var2, var3, var4, var5 ) {Serial.printf( var1, var2, var3, var4, var5 ); if (settings.useTxRxPinsForTerminal == true) SerialLog.printf( var1, var2, var3, var4, var5 );} + +// The Serial port for the Zmodem connection +// must not be the same as DSERIAL unless all +// debugging output to DSERIAL is removed +//#define ZSERIAL Serial3 +//#define ZSERIAL Serial +Stream *ZSERIAL; + +// Serial output for debugging info +//#define DSERIAL Serial +Stream *DSERIAL; void setup() { //If 3.3V rail drops below 3V, system will power down and maintain RTC @@ -249,7 +279,7 @@ void setup() { digitalWrite(PIN_STAT_LED, HIGH); // Turn the STAT LED on while we configure everything Serial.begin(115200); //Default for initial debug messages if necessary - Serial.println(); + SerialPrintln(F("")); SPI.begin(); //Needed if SD is disabled @@ -270,13 +300,19 @@ void setup() { loadSettings(); //50 - 250ms + if (settings.useTxRxPinsForTerminal == true) + { + SerialLog.begin(settings.serialTerminalBaudRate); // Start the serial port + } + Serial.flush(); //Complete any previous prints Serial.begin(settings.serialTerminalBaudRate); - Serial.printf("Artemis OpenLog v%d.%d\r\n", FIRMWARE_VERSION_MAJOR, FIRMWARE_VERSION_MINOR); + + SerialPrintf3("Artemis OpenLog v%d.%d\r\n", FIRMWARE_VERSION_MAJOR, FIRMWARE_VERSION_MINOR); if (settings.useGPIO32ForStopLogging == true) { - Serial.println(F("Stop Logging is enabled. Pull GPIO pin 32 to GND to stop logging.")); + SerialPrintln(F("Stop Logging is enabled. Pull GPIO pin 32 to GND to stop logging.")); pinMode(PIN_STOP_LOGGING, INPUT_PULLUP); delay(1); // Let the pin stabilize attachInterrupt(digitalPinToInterrupt(PIN_STOP_LOGGING), stopLoggingISR, FALLING); // Enable the interrupt @@ -289,12 +325,12 @@ void setup() { delay(1); // Let the pin stabilize if (settings.fallingEdgeTrigger == true) { - Serial.println(F("Falling-edge triggering is enabled. Sensor data will be logged on a falling edge on GPIO pin 11.")); + SerialPrintln(F("Falling-edge triggering is enabled. Sensor data will be logged on a falling edge on GPIO pin 11.")); attachInterrupt(digitalPinToInterrupt(PIN_TRIGGER), triggerPinISR, FALLING); // Enable the interrupt } else { - Serial.println(F("Rising-edge triggering is enabled. Sensor data will be logged on a rising edge on GPIO pin 11.")); + SerialPrintln(F("Rising-edge triggering is enabled. Sensor data will be logged on a rising edge on GPIO pin 11.")); attachInterrupt(digitalPinToInterrupt(PIN_TRIGGER), triggerPinISR, RISING); // Enable the interrupt } triggerEdgeSeen = false; // Make sure the flag is clear @@ -305,26 +341,29 @@ void setup() { beginDataLogging(); //180ms lastSDFileNameChangeTime = rtcMillis(); // Record the time of the file name change - beginSerialLogging(); //20 - 99ms - beginSerialOutput(); // Begin serial data output on the TX pin + if (settings.useTxRxPinsForTerminal == false) + { + beginSerialLogging(); //20 - 99ms + beginSerialOutput(); // Begin serial data output on the TX pin + } beginIMU(); //61ms - if (online.microSD == true) Serial.println(F("SD card online")); - else Serial.println(F("SD card offline")); + if (online.microSD == true) SerialPrintln(F("SD card online")); + else SerialPrintln(F("SD card offline")); - if (online.dataLogging == true) Serial.println(F("Data logging online")); - else Serial.println(F("Datalogging offline")); + if (online.dataLogging == true) SerialPrintln(F("Data logging online")); + else SerialPrintln(F("Datalogging offline")); - if (online.serialLogging == true) Serial.println(F("Serial logging online")); - else Serial.println(F("Serial logging offline")); + if (online.serialLogging == true) SerialPrintln(F("Serial logging online")); + else SerialPrintln(F("Serial logging offline")); - if (online.IMU == true) Serial.println(F("IMU online")); - else Serial.println(F("IMU offline")); + if (online.IMU == true) SerialPrintln(F("IMU online")); + else SerialPrintln(F("IMU offline")); - if (settings.logMaxRate == true) Serial.println(F("Logging analog pins at max data rate")); + if (settings.logMaxRate == true) SerialPrintln(F("Logging analog pins at max data rate")); - if (settings.enableTerminalOutput == false && settings.logData == true) Serial.println(F("Logging to microSD card with no terminal output")); + if (settings.enableTerminalOutput == false && settings.logData == true) SerialPrintln(F("Logging to microSD card with no terminal output")); if (detectQwiicDevices() == true) //159 - 865ms but varies based on number of devices attached { @@ -334,7 +373,7 @@ void setup() { printOnlineDevice(); } else - Serial.println(F("No Qwiic devices detected")); + SerialPrintln(F("No Qwiic devices detected")); if (settings.showHelperText == true) printHelperText(false); //printHelperText to terminal and sensor file @@ -344,7 +383,7 @@ void setup() { else measurementStartTime = millis(); - //Serial.printf("Setup time: %.02f ms\r\n", (micros() - startTime) / 1000.0); + //SerialPrintf2("Setup time: %.02f ms\r\n", (micros() - startTime) / 1000.0); digitalWrite(PIN_STAT_LED, LOW); // Turn the STAT LED off now that everything is configured @@ -358,9 +397,10 @@ void loop() { checkBattery(); // Check for low battery - if (Serial.available()) menuMain(); //Present user menu + if ((Serial.available()) || ((settings.useTxRxPinsForTerminal == true) && (SerialLog.available()))) + menuMain(); //Present user menu - if (settings.logSerial == true && online.serialLogging == true) + if (settings.logSerial == true && online.serialLogging == true && settings.useTxRxPinsForTerminal == false) { if (SerialLog.available()) { @@ -471,7 +511,7 @@ void loop() { //Print to terminal if (settings.enableTerminalOutput == true) - Serial.print(outputData); //Print to terminal + SerialPrint(outputData); //Print to terminal //Output to TX pin if ((settings.outputSerial == true) && (online.serialOutput == true)) @@ -487,7 +527,9 @@ void loop() { if (recordLength != strlen(outputData)) //Record the buffer to the card { if (settings.printDebugMessages == true) - Serial.printf("*** sensorDataFile.write data length mismatch! *** recordLength: %d, outputDataLength: %d\r\n", recordLength, strlen(outputData)); + { + SerialPrintf3("*** sensorDataFile.write data length mismatch! *** recordLength: %d, outputDataLength: %d\r\n", recordLength, strlen(outputData)); + } } //Force sync every 500ms @@ -586,7 +628,7 @@ void beginSD() } if (sd.begin(PIN_MICROSD_CHIP_SELECT, SD_SCK_MHZ(24)) == false) //Standard SdFat { - Serial.println(F("SD init failed (second attempt). Is card present? Formatted?")); + SerialPrintln(F("SD init failed (second attempt). Is card present? Formatted?")); digitalWrite(PIN_MICROSD_CHIP_SELECT, HIGH); //Be sure SD is deselected online.microSD = false; return; @@ -596,7 +638,7 @@ void beginSD() //Change to root directory. All new file creation will be in root. if (sd.chdir() == false) { - Serial.println(F("SD change directory failed")); + SerialPrintln(F("SD change directory failed")); online.microSD = false; return; } @@ -672,7 +714,7 @@ void beginIMU() { printDebug("beginIMU: second attempt at myICM.begin failed. myICM.status = " + (String)myICM.status + "\r\n"); digitalWrite(PIN_IMU_CHIP_SELECT, HIGH); //Be sure IMU is deselected - Serial.println(F("ICM-20948 failed to init.")); + SerialPrintln(F("ICM-20948 failed to init.")); imuPowerOff(); online.IMU = false; return; @@ -691,12 +733,12 @@ void beginIMU() ICM_20948_Status_e retval = myICM.enableDLPF(ICM_20948_Internal_Acc, settings.imuAccDLPF); if (retval != ICM_20948_Stat_Ok) { - Serial.println(F("Error: Could not configure the IMU Accelerometer DLPF!")); + SerialPrintln(F("Error: Could not configure the IMU Accelerometer DLPF!")); } retval = myICM.enableDLPF(ICM_20948_Internal_Gyr, settings.imuGyroDLPF); if (retval != ICM_20948_Stat_Ok) { - Serial.println(F("Error: Could not configure the IMU Gyro DLPF!")); + SerialPrintln(F("Error: Could not configure the IMU Gyro DLPF!")); } ICM_20948_dlpcfg_t dlpcfg; dlpcfg.a = settings.imuAccDLPFBW; @@ -704,7 +746,7 @@ void beginIMU() retval = myICM.setDLPFcfg((ICM_20948_Internal_Acc | ICM_20948_Internal_Gyr), dlpcfg); if (retval != ICM_20948_Stat_Ok) { - Serial.println(F("Error: Could not configure the IMU DLPF BW!")); + SerialPrintln(F("Error: Could not configure the IMU DLPF BW!")); } ICM_20948_fss_t FSS; FSS.a = settings.imuAccFSS; @@ -712,7 +754,7 @@ void beginIMU() retval = myICM.setFullScale((ICM_20948_Internal_Acc | ICM_20948_Internal_Gyr), FSS); if (retval != ICM_20948_Stat_Ok) { - Serial.println(F("Error: Could not configure the IMU Full Scale!")); + SerialPrintln(F("Error: Could not configure the IMU Full Scale!")); } online.IMU = true; @@ -738,7 +780,7 @@ void beginDataLogging() // O_WRITE - open for write if (sensorDataFile.open(sensorDataFileName, O_CREAT | O_APPEND | O_WRITE) == false) { - Serial.println(F("Failed to create sensor data file")); + SerialPrintln(F("Failed to create sensor data file")); online.dataLogging = false; return; } @@ -761,7 +803,7 @@ void beginSerialLogging() if (serialDataFile.open(serialDataFileName, O_CREAT | O_APPEND | O_WRITE) == false) { - Serial.println(F("Failed to create serial log file")); + SerialPrintln(F("Failed to create serial log file")); //systemError(ERROR_FILE_OPEN); online.serialLogging = false; return; @@ -825,3 +867,55 @@ void triggerPinISR(void) { triggerEdgeSeen = true; } + +void SerialFlush(void) +{ + Serial.flush(); + if (settings.useTxRxPinsForTerminal == true) + { + SerialLog.flush(); + } +} + +// gfvalvo's flash string helper code: https://forum.arduino.cc/index.php?topic=533118.msg3634809#msg3634809 + +void SerialPrint(const char *line) +{ + DoSerialPrint([](const char *ptr) {return *ptr;}, line); +} + +void SerialPrint(const __FlashStringHelper *line) +{ + DoSerialPrint([](const char *ptr) {return (char) pgm_read_byte_near(ptr);}, + (const char*) line); +} + +void SerialPrintln(const char *line) +{ + DoSerialPrint([](const char *ptr) {return *ptr;}, line, true); +} + +void SerialPrintln(const __FlashStringHelper *line) +{ + DoSerialPrint([](const char *ptr) {return (char) pgm_read_byte_near(ptr);}, + (const char*) line, true); +} + +void DoSerialPrint(char (*funct)(const char *), const char *string, bool newLine) +{ + char ch; + + while ((ch = funct(string++))) + { + Serial.print(ch); + if (settings.useTxRxPinsForTerminal == true) + SerialLog.print(ch); + } + + if (newLine) + { + Serial.println(); + if (settings.useTxRxPinsForTerminal == true) + SerialLog.println(); + } +} diff --git a/Firmware/OpenLog_Artemis/Sensors.ino b/Firmware/OpenLog_Artemis/Sensors.ino index 4df6662..9f3180c 100644 --- a/Firmware/OpenLog_Artemis/Sensors.ino +++ b/Firmware/OpenLog_Artemis/Sensors.ino @@ -45,7 +45,7 @@ void getData() } //end if use RTC for timestamp else //Use GPS for timestamp { - Serial.println(F("Print GPS Timestamp / not yet implemented")); + SerialPrintln(F("Print GPS Timestamp / not yet implemented")); } } @@ -254,7 +254,7 @@ void gatherDeviceValues() { qwiic.setPullups(0); //Disable pullups to minimize CRC issues - SFE_UBLOX_GPS *nodeDevice = (SFE_UBLOX_GPS *)temp->classPtr; + SFE_UBLOX_GNSS *nodeDevice = (SFE_UBLOX_GNSS *)temp->classPtr; struct_uBlox *nodeSetting = (struct_uBlox *)temp->configPtr; if (nodeSetting->log == true) @@ -804,7 +804,7 @@ void gatherDeviceValues() } break; default: - Serial.printf("printDeviceValue unknown device type: %s\r\n", getDeviceName(temp->deviceType)); + SerialPrintf2("printDeviceValue unknown device type: %s\r\n", getDeviceName(temp->deviceType)); break; } @@ -1173,7 +1173,7 @@ void printHelperText(bool terminalOnly) } break; default: - Serial.printf("\nprinterHelperText device not found: %d\r\n", temp->deviceType); + SerialPrintf2("\nprinterHelperText device not found: %d\r\n", temp->deviceType); break; } } @@ -1188,7 +1188,7 @@ void printHelperText(bool terminalOnly) strcat(helperText, "\r\n"); - Serial.print(helperText); + SerialPrint(helperText); if ((terminalOnly == false) && (settings.logData == true) && (online.microSD) && (settings.enableSD && online.microSD)) sensorDataFile.print(helperText); } diff --git a/Firmware/OpenLog_Artemis/autoDetect.ino b/Firmware/OpenLog_Artemis/autoDetect.ino index ea7a6c0..a8d83f6 100644 --- a/Firmware/OpenLog_Artemis/autoDetect.ino +++ b/Firmware/OpenLog_Artemis/autoDetect.ino @@ -137,7 +137,7 @@ bool addDevice(deviceType_e deviceType, uint8_t address, uint8_t muxAddress, uin break; case DEVICE_GPS_UBLOX: { - temp->classPtr = new SFE_UBLOX_GPS; + temp->classPtr = new SFE_UBLOX_GNSS; temp->configPtr = new struct_uBlox; } break; @@ -238,7 +238,7 @@ bool addDevice(deviceType_e deviceType, uint8_t address, uint8_t muxAddress, uin } break; default: - Serial.printf("addDevice Device type not found: %d\r\n", deviceType); + SerialPrintf2("addDevice Device type not found: %d\r\n", deviceType); break; } @@ -283,8 +283,8 @@ bool beginQwiicDevices() if (settings.printDebugMessages == true) { - Serial.printf("beginQwiicDevices: attempting to begin deviceType %s", getDeviceName(temp->deviceType)); - Serial.printf(" at address 0x%02X using mux address 0x%02X and port number %d\r\n", temp->address, temp->muxAddress, temp->portNumber); + SerialPrintf2("beginQwiicDevices: attempting to begin deviceType %s", getDeviceName(temp->deviceType)); + SerialPrintf4(" at address 0x%02X using mux address 0x%02X and port number %d\r\n", temp->address, temp->muxAddress, temp->portNumber); } //Attempt to begin the device @@ -318,7 +318,7 @@ bool beginQwiicDevices() case DEVICE_GPS_UBLOX: { qwiic.setPullups(0); //Disable pullups for u-blox comms. - SFE_UBLOX_GPS *tempDevice = (SFE_UBLOX_GPS *)temp->classPtr; + SFE_UBLOX_GNSS *tempDevice = (SFE_UBLOX_GNSS *)temp->classPtr; struct_uBlox *nodeSetting = (struct_uBlox *)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 temp->online = tempDevice->begin(qwiic, temp->address); //Wire port, Address @@ -460,7 +460,7 @@ bool beginQwiicDevices() } break; default: - Serial.printf("beginQwiicDevices: device type not found: %d\r\n", temp->deviceType); + SerialPrintf2("beginQwiicDevices: device type not found: %d\r\n", temp->deviceType); break; } @@ -510,13 +510,15 @@ void printOnlineDevice() { sprintf(sensorOnlineText, "%s failed to respond\r\n", getDeviceName(temp->deviceType)); } - Serial.print(sensorOnlineText); + SerialPrint(sensorOnlineText); temp = temp->next; } if (settings.printDebugMessages == true) - Serial.printf("Device count: %d\r\n", deviceCount); + { + SerialPrintf2("Device count: %d\r\n", deviceCount); + } } //Given the node number, apply the node's configuration settings to the device @@ -569,7 +571,7 @@ void configureDevice(node * temp) { qwiic.setPullups(0); //Disable pullups for u-blox comms. - SFE_UBLOX_GPS *sensor = (SFE_UBLOX_GPS *)temp->classPtr; + SFE_UBLOX_GNSS *sensor = (SFE_UBLOX_GNSS *)temp->classPtr; struct_uBlox *nodeSetting = (struct_uBlox *)temp->configPtr; sensor->setI2COutput(COM_TYPE_UBX); //Set the I2C port to output UBX only (turn off NMEA noise) @@ -706,7 +708,7 @@ void configureDevice(node * temp) //Nothing to configure break; default: - Serial.printf("configureDevice: Unknown device type %d: %s\r\n", deviceType, getDeviceName((deviceType_e)deviceType)); + SerialPrintf3("configureDevice: Unknown device type %d: %s\r\n", deviceType, getDeviceName((deviceType_e)deviceType)); break; } } @@ -796,8 +798,8 @@ FunctionPointer getConfigFunctionPtr(uint8_t nodeNumber) ptr = (FunctionPointer)menuConfigure_SNGCJA5; break; default: - Serial.println(F("getConfigFunctionPtr: Unknown device type")); - Serial.flush(); + SerialPrintln(F("getConfigFunctionPtr: Unknown device type")); + SerialFlush(); break; } @@ -840,7 +842,7 @@ bool openConnection(uint8_t muxAddress, uint8_t portNumber) { if (head == NULL) { - Serial.println(F("OpenConnection Error: No devices in list")); + SerialPrintln(F("OpenConnection Error: No devices in list")); return false; } @@ -1026,7 +1028,7 @@ deviceType_e testDevice(uint8_t i2cAddress, uint8_t muxAddress, uint8_t portNumb { //Confidence: High - Sends/receives CRC checked data response qwiic.setPullups(0); //Disable pullups to minimize CRC issues - SFE_UBLOX_GPS sensor; + SFE_UBLOX_GNSS sensor; if(settings.printDebugMessages == true) sensor.enableDebugging(); // Enable debug messages if required if (sensor.begin(qwiic, i2cAddress) == true) //Wire port, address { @@ -1260,14 +1262,18 @@ deviceType_e testDevice(uint8_t i2cAddress, uint8_t muxAddress, uint8_t portNumb default: { if (muxAddress == 0) - Serial.printf("Unknown device at address (0x%02X)\r\n", i2cAddress); + { + SerialPrintf2("Unknown device at address (0x%02X)\r\n", i2cAddress); + } else - Serial.printf("Unknown device at address (0x%02X)(Mux:0x%02X Port:%d)\r\n", i2cAddress, muxAddress, portNumber); + { + SerialPrintf4("Unknown device at address (0x%02X)(Mux:0x%02X Port:%d)\r\n", i2cAddress, muxAddress, portNumber); + } return DEVICE_UNKNOWN_DEVICE; } break; } - Serial.printf("Known I2C address but device failed identification at address 0x%02X\r\n", i2cAddress); + SerialPrintf2("Known I2C address but device failed identification at address 0x%02X\r\n", i2cAddress); return DEVICE_UNKNOWN_DEVICE; } @@ -1344,9 +1350,13 @@ deviceType_e testMuxDevice(uint8_t i2cAddress, uint8_t muxAddress, uint8_t portN default: { if (muxAddress == 0) - Serial.printf("Unknown device at address (0x%02X)\r\n", i2cAddress); + { + SerialPrintf2("Unknown device at address (0x%02X)\r\n", i2cAddress); + } else - Serial.printf("Unknown device at address (0x%02X)(Mux:0x%02X Port:%d)\r\n", i2cAddress, muxAddress, portNumber); + { + SerialPrintf4("Unknown device at address (0x%02X)(Mux:0x%02X Port:%d)\r\n", i2cAddress, muxAddress, portNumber); + } return DEVICE_UNKNOWN_DEVICE; } break; diff --git a/Firmware/OpenLog_Artemis/logging.ino b/Firmware/OpenLog_Artemis/logging.ino index 10e0169..1135815 100644 --- a/Firmware/OpenLog_Artemis/logging.ino +++ b/Firmware/OpenLog_Artemis/logging.ino @@ -1,7 +1,7 @@ //Print a message both to terminal and to log void msg(const char * message) { - Serial.println(message); + SerialPrintln(message); if (online.microSD) sensorDataFile.println(message); } @@ -15,8 +15,8 @@ char* findNextAvailableLog(int &newFileNumber, const char *fileLeader) if (newFileNumber < 2) //If the settings have been reset, let's warn the user that this could take a while! { - Serial.println(F("Finding the next available log file.")); - Serial.println(F("This could take a long time if the SD card contains many existing log files.")); + SerialPrintln(F("Finding the next available log file.")); + SerialPrintln(F("This could take a long time if the SD card contains many existing log files.")); } if (newFileNumber > 0) @@ -46,15 +46,15 @@ char* findNextAvailableLog(int &newFileNumber, const char *fileLeader) if (newFileNumber >= 100000) // Have we hit the maximum number of files? { - Serial.print(F("***** WARNING! File number limit reached! (Overwriting ")); - Serial.print(newFileName); - Serial.println(F(") *****")); + SerialPrint(F("***** WARNING! File number limit reached! (Overwriting ")); + SerialPrint(newFileName); + SerialPrintln(F(") *****")); newFileNumber = 100000; // This will overwrite Log99999.TXT next time thanks to the newFileNumber-- above } else { - Serial.print(F("Logging to: ")); - Serial.println(newFileName); + SerialPrint(F("Logging to: ")); + SerialPrintln(newFileName); } //Record new file number to EEPROM and to config file diff --git a/Firmware/OpenLog_Artemis/lowerPower.ino b/Firmware/OpenLog_Artemis/lowerPower.ino index ad6fdd3..6d43889 100644 --- a/Firmware/OpenLog_Artemis/lowerPower.ino +++ b/Firmware/OpenLog_Artemis/lowerPower.ino @@ -30,10 +30,10 @@ void checkBattery(void) delay(sdPowerDownDelay); // Give the SD card time to finish writing ***** THIS IS CRITICAL ***** - Serial.println(F("*** LOW BATTERY VOLTAGE DETECTED! GOING INTO POWERDOWN ***")); - Serial.println(F("*** PLEASE CHANGE THE POWER SOURCE AND RESET THE OLA TO CONTINUE ***")); + SerialPrintln(F("*** LOW BATTERY VOLTAGE DETECTED! GOING INTO POWERDOWN ***")); + SerialPrintln(F("*** PLEASE CHANGE THE POWER SOURCE AND RESET THE OLA TO CONTINUE ***")); - Serial.flush(); //Finish any prints + SerialFlush(); //Finish any prints powerDown(); // power down and wait for reset } @@ -82,7 +82,7 @@ void powerDown() // serialDataFile.close(); // } - //Serial.flush(); //Don't waste time waiting for prints to finish + //SerialFlush(); //Don't waste time waiting for prints to finish // Wire.end(); //Power down I2C qwiic.end(); //Power down I2C @@ -211,7 +211,7 @@ void goToSleep() delay(sdPowerDownDelay); // Give the SD card time to finish writing ***** THIS IS CRITICAL ***** - Serial.flush(); //Finish any prints + SerialFlush(); //Finish any prints // Wire.end(); //Power down I2C qwiic.end(); //Power down I2C @@ -367,6 +367,11 @@ void wakeFromSleep() Serial.begin(settings.serialTerminalBaudRate); + if (settings.useTxRxPinsForTerminal == true) + { + SerialLog.begin(settings.serialTerminalBaudRate); // Start the serial port + } + printDebug("wakeFromSleep: I'm awake!\r\n"); printDebug("wakeFromSleep: adcError is " + (String)adcError + "."); if (adcError > 0) @@ -383,7 +388,10 @@ void wakeFromSleep() beginDataLogging(); //180ms - beginSerialLogging(); //20 - 99ms + if (settings.useTxRxPinsForTerminal == false) + { + beginSerialLogging(); //20 - 99ms + } beginIMU(); //61ms //printDebug("wakeFromSleep: online.IMU = " + (String)online.IMU + "\r\n"); @@ -396,7 +404,7 @@ void wakeFromSleep() configureQwiicDevices(); //Apply config settings to each device in the node list } - //Serial.printf("Wake up time: %.02f ms\r\n", (micros() - startTime) / 1000.0); + //SerialPrintf2("Wake up time: %.02f ms\r\n", (micros() - startTime) / 1000.0); //When we wake up micros has been reset to zero so we need to let the main loop know to take a reading takeReading = true; @@ -420,9 +428,11 @@ void stopLogging(void) serialDataFile.close(); } - Serial.print(F("Logging is stopped. Please reset OpenLog Artemis and open a terminal at ")); + SerialPrint(F("Logging is stopped. Please reset OpenLog Artemis and open a terminal at ")); Serial.print((String)settings.serialTerminalBaudRate); - Serial.println(F("bps...")); + if (settings.useTxRxPinsForTerminal == true) + SerialLog.print((String)settings.serialTerminalBaudRate); + SerialPrintln(F("bps...")); delay(sdPowerDownDelay); // Give the SD card time to shut down powerDown(); } diff --git a/Firmware/OpenLog_Artemis/menuAnalogLogging.ino b/Firmware/OpenLog_Artemis/menuAnalogLogging.ino index f50b2ae..f8298f8 100644 --- a/Firmware/OpenLog_Artemis/menuAnalogLogging.ino +++ b/Firmware/OpenLog_Artemis/menuAnalogLogging.ino @@ -3,37 +3,39 @@ void menuAnalogLogging() while (1) { #if(HARDWARE_VERSION_MAJOR == 0) - Serial.println(); - Serial.println(F("Note: VIN logging is only supported on V10+ hardware. X04 will show 0.0V.")); + SerialPrintln(F("")); + SerialPrintln(F("Note: VIN logging is only supported on V10+ hardware. X04 will show 0.0V.")); #endif - Serial.println(); - Serial.println(F("Menu: Configure Analog Logging")); + SerialPrintln(F("")); + SerialPrintln(F("Menu: Configure Analog Logging")); - Serial.print(F("1) Log analog pin 11 (2V Max): ")); - if (settings.logA11 == true) Serial.println(F("Enabled. (Triggering is disabled)")); - else Serial.println(F("Disabled")); + SerialPrint(F("1) Log analog pin 11 (2V Max): ")); + if (settings.logA11 == true) SerialPrintln(F("Enabled. (Triggering is disabled)")); + else SerialPrintln(F("Disabled")); - Serial.print(F("2) Log analog pin 12 (TX) (2V Max): ")); - if (settings.logA12 == true) Serial.println(F("Enabled. (Serial output is disabled)")); - else Serial.println(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)")); + else if (settings.logA12 == true) SerialPrintln(F("Enabled. (Serial output is disabled)")); + else SerialPrintln(F("Disabled")); - Serial.print(F("3) Log analog pin 13 (RX) (2V Max): ")); - if (settings.logA13 == true) Serial.println(F("Enabled. (Serial logging is disabled)")); - else Serial.println(F("Disabled")); + SerialPrint(F("3) Log analog pin 13 (RX) (2V Max): ")); + if (settings.useTxRxPinsForTerminal == true) SerialPrintln(F("Disabled. (TX and RX pins are being used for the Terminal)")); + else if (settings.logA13 == true) SerialPrintln(F("Enabled. (Serial logging is disabled)")); + else SerialPrintln(F("Disabled")); - Serial.print(F("4) Log analog pin 32 (2V Max): ")); - if (settings.logA32 == true) Serial.println(F("Enabled. (Stop logging is disabled)")); - else Serial.println(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")); - Serial.print(F("5) Log output type: ")); - if (settings.logAnalogVoltages == true) Serial.println(F("Calculated Voltage")); - else Serial.println(F("Raw ADC reading")); + SerialPrint(F("5) Log output type: ")); + if (settings.logAnalogVoltages == true) SerialPrintln(F("Calculated Voltage")); + else SerialPrintln(F("Raw ADC reading")); - Serial.print(F("6) Log VIN (battery) voltage (6V Max): ")); - if (settings.logVIN == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("6) Log VIN (battery) voltage (6V Max): ")); + if (settings.logVIN == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.println(F("x) Exit")); + SerialPrintln(F("x) Exit")); byte incoming = getByteChoice(menuTimeout); //Timeout after x seconds @@ -55,9 +57,18 @@ void menuAnalogLogging() { if(settings.logA12 == false) { - online.serialOutput = false; // Disable serial output - settings.outputSerial = false; - settings.logA12 = true; + if (settings.useTxRxPinsForTerminal == true) + { + SerialPrintln(F("")); + SerialPrintln(F("Analog logging on pin 12 is not possible. TX and RX pins are being used for the Terminal.")); + SerialPrintln(F("")); + } + else + { + online.serialOutput = false; // Disable serial output + settings.outputSerial = false; + settings.logA12 = true; + } } else settings.logA12 = false; @@ -66,9 +77,18 @@ void menuAnalogLogging() { if(settings.logA13 == false) { - online.serialLogging = false; //Disable serial logging - settings.logSerial = false; - settings.logA13 = true; + if (settings.useTxRxPinsForTerminal == true) + { + SerialPrintln(F("")); + SerialPrintln(F("Analog logging on pin 13 is not possible. TX and RX pins are being used for the Terminal.")); + SerialPrintln(F("")); + } + else + { + online.serialLogging = false; //Disable serial logging + settings.logSerial = false; + settings.logA13 = true; + } } else settings.logA13 = false; diff --git a/Firmware/OpenLog_Artemis/menuAttachedDevices.ino b/Firmware/OpenLog_Artemis/menuAttachedDevices.ino index 53f3fa5..8f09ea3 100644 --- a/Firmware/OpenLog_Artemis/menuAttachedDevices.ino +++ b/Firmware/OpenLog_Artemis/menuAttachedDevices.ino @@ -47,7 +47,7 @@ bool detectQwiicDevices() // lock up the I2C bus if you don't discover it and then begin it in one go... // The following code has been restructured to try and keep the MCP9600 happy. - Serial.println(F("Identifying Qwiic Muxes...")); + SerialPrintln(F("Identifying Qwiic Muxes...")); //First scan for Muxes. Valid addresses are 0x70 to 0x77 (112 to 119). //If any are found, they will be begin()'d causing their ports to turn off @@ -65,19 +65,25 @@ bool detectQwiicDevices() //} somethingDetected = true; if (settings.printDebugMessages == true) - Serial.printf("detectQwiicDevices: something detected at address 0x%02X\r\n", address); + { + SerialPrintf2("detectQwiicDevices: something detected at address 0x%02X\r\n", address); + } deviceType_e foundType = testMuxDevice(address, 0, 0); //No mux or port numbers for this test if (foundType == DEVICE_MULTIPLEXER) { addDevice(foundType, address, 0, 0); //Add this device to our map if (settings.printDebugMessages == true) - Serial.printf("detectQwiicDevices: multiplexer found at address 0x%02X\r\n", address); + { + SerialPrintf2("detectQwiicDevices: multiplexer found at address 0x%02X\r\n", address); + } muxCount++; } else if (foundType == DEVICE_PRESSURE_MS5637) { if (settings.printDebugMessages == true) - Serial.printf("detectQwiicDevices: MS8607/MS5637 found at address 0x%02X. Ignoring it for now...\r\n", address); + { + SerialPrintf2("detectQwiicDevices: MS8607/MS5637 found at address 0x%02X. Ignoring it for now...\r\n", address); + } } } } @@ -86,17 +92,17 @@ bool detectQwiicDevices() { if (settings.printDebugMessages == true) { - Serial.printf("detectQwiicDevices: found %d", muxCount); + SerialPrintf2("detectQwiicDevices: found %d", muxCount); if (muxCount == 1) - Serial.println(F(" multiplexer")); + SerialPrintln(F(" multiplexer")); else - Serial.println(F(" multiplexers")); + SerialPrintln(F(" multiplexers")); } beginQwiicDevices(); //begin() the muxes to disable their ports } //Before going into mux sub branches, scan the main branch for all remaining devices - Serial.println(F("Identifying Qwiic Devices...")); + SerialPrintln(F("Identifying Qwiic Devices...")); bool foundMS8607 = false; // The MS8607 appears as two devices (MS8607 and MS5637). We need to skip the MS5637 if we have found a MS8607. for (uint8_t address = 1 ; address < 127 ; address++) { @@ -105,7 +111,9 @@ bool detectQwiicDevices() { somethingDetected = true; if (settings.printDebugMessages == true) - Serial.printf("detectQwiicDevices: something detected at address 0x%02X\r\n", address); + { + SerialPrintf2("detectQwiicDevices: something detected at address 0x%02X\r\n", address); + } deviceType_e foundType = testDevice(address, 0, 0); //No mux or port numbers for this test if (foundType != DEVICE_UNKNOWN_DEVICE) { @@ -118,7 +126,9 @@ bool detectQwiicDevices() if (addDevice(foundType, address, 0, 0) == true) //Records this device. //Returns false if mux/device was already recorded. { if (settings.printDebugMessages == true) - Serial.printf("detectQwiicDevices: added %s at address 0x%02X\r\n", getDeviceName(foundType), address); + { + SerialPrintf3("detectQwiicDevices: added %s at address 0x%02X\r\n", getDeviceName(foundType), address); + } } } if (foundType == DEVICE_PHT_MS8607) @@ -134,7 +144,7 @@ bool detectQwiicDevices() //If we have muxes, begin scanning their sub nets if (muxCount > 0) { - Serial.println(F("Multiplexers found. Scanning sub nets...")); + SerialPrintln(F("Multiplexers found. Scanning sub nets...")); //Step into first mux and begin stepping through ports for (int muxNumber = 0 ; muxNumber < muxCount ; muxNumber++) @@ -162,7 +172,9 @@ bool detectQwiicDevices() if (deviceExists(DEVICE_TOTAL_DEVICES, address, 0, 0)) // Check if we found any type of device with this address on the main branch { if (settings.printDebugMessages == true) - Serial.printf("detectQwiicDevices: skipping device address 0x%02X because we found one on the main branch\r\n", address); + { + SerialPrintf2("detectQwiicDevices: skipping device address 0x%02X because we found one on the main branch\r\n", address); + } } else { @@ -186,14 +198,14 @@ bool detectQwiicDevices() if (foundType == DEVICE_MULTIPLEXER) // Let's ignore multiplexers hanging off multiplexer ports. (Multiple muxes on the main branch is OK.) { if (settings.printDebugMessages == true) - Serial.printf("detectQwiicDevices: ignoring %s at address 0x%02X.0x%02X.%d\r\n", getDeviceName(foundType), address, muxNode->address, portNumber); + SerialPrintf5("detectQwiicDevices: ignoring %s at address 0x%02X.0x%02X.%d\r\n", getDeviceName(foundType), address, muxNode->address, portNumber); } else { if (addDevice(foundType, address, muxNode->address, portNumber) == true) //Record this device, with mux port specifics. { if (settings.printDebugMessages == true) - Serial.printf("detectQwiicDevices: added %s at address 0x%02X.0x%02X.%d\r\n", getDeviceName(foundType), address, muxNode->address, portNumber); + SerialPrintf5("detectQwiicDevices: added %s at address 0x%02X.0x%02X.%d\r\n", getDeviceName(foundType), address, muxNode->address, portNumber); } } } @@ -219,7 +231,7 @@ bool detectQwiicDevices() setMaxI2CSpeed(); //Try for 400kHz but reduce to 100kHz or low if certain devices are attached - Serial.println(F("Autodetect complete")); + SerialPrintln(F("Autodetect complete")); return (true); } // /detectQwiicDevices @@ -228,8 +240,8 @@ void menuAttachedDevices() { while (1) { - Serial.println(); - Serial.println(F("Menu: Configure Attached Devices")); + SerialPrintln(F("")); + SerialPrintln(F("Menu: Configure Attached Devices")); int availableDevices = 0; @@ -237,7 +249,7 @@ void menuAttachedDevices() node *temp = head; if (temp == NULL) - Serial.println(F("**No devices detected on Qwiic bus**")); + SerialPrintln(F("**No devices detected on Qwiic bus**")); while (temp != NULL) { @@ -256,67 +268,67 @@ void menuAttachedDevices() switch (temp->deviceType) { case DEVICE_MULTIPLEXER: - //Serial.printf("%s Multiplexer %s\r\n", strDeviceMenu, strAddress); + //SerialPrintf3("%s Multiplexer %s\r\n", strDeviceMenu, strAddress); break; case DEVICE_LOADCELL_NAU7802: - Serial.printf("%s NAU7802 Weight Sensor %s\r\n", strDeviceMenu, strAddress); + SerialPrintf3("%s NAU7802 Weight Sensor %s\r\n", strDeviceMenu, strAddress); break; case DEVICE_DISTANCE_VL53L1X: - Serial.printf("%s VL53L1X Distance Sensor %s\r\n", strDeviceMenu, strAddress); + SerialPrintf3("%s VL53L1X Distance Sensor %s\r\n", strDeviceMenu, strAddress); break; case DEVICE_GPS_UBLOX: - Serial.printf("%s u-blox GPS Receiver %s\r\n", strDeviceMenu, strAddress); + SerialPrintf3("%s u-blox GPS Receiver %s\r\n", strDeviceMenu, strAddress); break; case DEVICE_PROXIMITY_VCNL4040: - Serial.printf("%s VCNL4040 Proximity Sensor %s\r\n", strDeviceMenu, strAddress); + SerialPrintf3("%s VCNL4040 Proximity Sensor %s\r\n", strDeviceMenu, strAddress); break; case DEVICE_TEMPERATURE_TMP117: - Serial.printf("%s TMP117 High Precision Temperature Sensor %s\r\n", strDeviceMenu, strAddress); + SerialPrintf3("%s TMP117 High Precision Temperature Sensor %s\r\n", strDeviceMenu, strAddress); break; case DEVICE_PRESSURE_MS5637: - Serial.printf("%s MS5637 Pressure Sensor %s\r\n", strDeviceMenu, strAddress); + SerialPrintf3("%s MS5637 Pressure Sensor %s\r\n", strDeviceMenu, strAddress); break; case DEVICE_PRESSURE_LPS25HB: - Serial.printf("%s LPS25HB Pressure Sensor %s\r\n", strDeviceMenu, strAddress); + SerialPrintf3("%s LPS25HB Pressure Sensor %s\r\n", strDeviceMenu, strAddress); break; case DEVICE_PHT_BME280: - Serial.printf("%s BME280 Pressure/Humidity/Temp (PHT) Sensor %s\r\n", strDeviceMenu, strAddress); + SerialPrintf3("%s BME280 Pressure/Humidity/Temp (PHT) Sensor %s\r\n", strDeviceMenu, strAddress); break; case DEVICE_UV_VEML6075: - Serial.printf("%s VEML6075 UV Sensor %s\r\n", strDeviceMenu, strAddress); + SerialPrintf3("%s VEML6075 UV Sensor %s\r\n", strDeviceMenu, strAddress); break; case DEVICE_VOC_CCS811: - Serial.printf("%s CCS811 tVOC and CO2 Sensor %s\r\n", strDeviceMenu, strAddress); + SerialPrintf3("%s CCS811 tVOC and CO2 Sensor %s\r\n", strDeviceMenu, strAddress); break; case DEVICE_VOC_SGP30: - Serial.printf("%s SGP30 tVOC and CO2 Sensor %s\r\n", strDeviceMenu, strAddress); + SerialPrintf3("%s SGP30 tVOC and CO2 Sensor %s\r\n", strDeviceMenu, strAddress); break; case DEVICE_CO2_SCD30: - Serial.printf("%s SCD30 CO2 Sensor %s\r\n", strDeviceMenu, strAddress); + SerialPrintf3("%s SCD30 CO2 Sensor %s\r\n", strDeviceMenu, strAddress); break; case DEVICE_PHT_MS8607: - Serial.printf("%s MS8607 Pressure/Humidity/Temp (PHT) Sensor %s\r\n", strDeviceMenu, strAddress); + SerialPrintf3("%s MS8607 Pressure/Humidity/Temp (PHT) Sensor %s\r\n", strDeviceMenu, strAddress); break; case DEVICE_TEMPERATURE_MCP9600: - Serial.printf("%s MCP9600 Thermocouple Sensor %s\r\n", strDeviceMenu, strAddress); + SerialPrintf3("%s MCP9600 Thermocouple Sensor %s\r\n", strDeviceMenu, strAddress); break; case DEVICE_HUMIDITY_AHT20: - Serial.printf("%s AHT20 Humidity Sensor %s\r\n", strDeviceMenu, strAddress); + SerialPrintf3("%s AHT20 Humidity Sensor %s\r\n", strDeviceMenu, strAddress); break; case DEVICE_HUMIDITY_SHTC3: - Serial.printf("%s SHTC3 Humidity Sensor %s\r\n", strDeviceMenu, strAddress); + SerialPrintf3("%s SHTC3 Humidity Sensor %s\r\n", strDeviceMenu, strAddress); break; case DEVICE_ADC_ADS122C04: - Serial.printf("%s ADS122C04 ADC (Qwiic PT100) %s\r\n", strDeviceMenu, strAddress); + SerialPrintf3("%s ADS122C04 ADC (Qwiic PT100) %s\r\n", strDeviceMenu, strAddress); break; case DEVICE_PRESSURE_MPR0025PA1: - Serial.printf("%s MPR MicroPressure Sensor %s\r\n", strDeviceMenu, strAddress); + SerialPrintf3("%s MPR MicroPressure Sensor %s\r\n", strDeviceMenu, strAddress); break; case DEVICE_PARTICLE_SNGCJA5: - Serial.printf("%s SN-GCJA5 Particle Sensor %s\r\n", strDeviceMenu, strAddress); + SerialPrintf3("%s SN-GCJA5 Particle Sensor %s\r\n", strDeviceMenu, strAddress); break; default: - Serial.printf("Unknown device type %d in menuAttachedDevices\r\n", temp->deviceType); + SerialPrintf2("Unknown device type %d in menuAttachedDevices\r\n", temp->deviceType); break; } } @@ -324,9 +336,9 @@ void menuAttachedDevices() temp = temp->next; } - Serial.printf("%d) Configure Qwiic Settings\r\n", availableDevices++ + 1); + SerialPrintf2("%d) Configure Qwiic Settings\r\n", availableDevices++ + 1); - Serial.println(F("x) Exit")); + SerialPrintln(F("x) Exit")); int nodeNumber = getNumber(menuTimeout); //Timeout after x seconds if (nodeNumber > 0 && nodeNumber < availableDevices) @@ -357,30 +369,30 @@ void menuConfigure_QwiicBus() { while (1) { - Serial.println(); - Serial.println(F("Menu: Configure Qwiic Bus")); + SerialPrintln(F("")); + SerialPrintln(F("Menu: Configure Qwiic Bus")); - Serial.print(F("1) Turn off bus power between readings (>2s): ")); - if (settings.powerDownQwiicBusBetweenReads == true) Serial.println(F("Yes")); - else Serial.println(F("No")); + SerialPrint(F("1) Turn off bus power between readings (>2s): ")); + if (settings.powerDownQwiicBusBetweenReads == true) SerialPrintln(F("Yes")); + else SerialPrintln(F("No")); - Serial.printf("2) Set Max Qwiic Bus Speed: %d Hz\r\n", settings.qwiicBusMaxSpeed); + SerialPrintf2("2) Set Max Qwiic Bus Speed: %d Hz\r\n", settings.qwiicBusMaxSpeed); - Serial.printf("3) Set minimum Qwiic bus power up delay: %d ms\r\n", settings.qwiicBusPowerUpDelayMs); + SerialPrintf2("3) Set minimum Qwiic bus power up delay: %d ms\r\n", settings.qwiicBusPowerUpDelayMs); - Serial.print(F("4) Qwiic bus pull-ups (internal to the Artemis): ")); + SerialPrint(F("4) Qwiic bus pull-ups (internal to the Artemis): ")); if (settings.qwiicBusPullUps == 1) - Serial.println(F("1.5k")); + SerialPrintln(F("1.5k")); else if (settings.qwiicBusPullUps == 6) - Serial.println(F("6k")); + SerialPrintln(F("6k")); else if (settings.qwiicBusPullUps == 12) - Serial.println(F("12k")); + SerialPrintln(F("12k")); else if (settings.qwiicBusPullUps == 24) - Serial.println(F("24k")); + SerialPrintln(F("24k")); else - Serial.println(F("None")); + SerialPrintln(F("None")); - Serial.println(F("x) Exit")); + SerialPrintln(F("x) Exit")); byte incoming = getByteChoice(menuTimeout); //Timeout after x seconds @@ -388,32 +400,32 @@ void menuConfigure_QwiicBus() settings.powerDownQwiicBusBetweenReads ^= 1; else if (incoming == '2') { - Serial.print(F("Enter max frequency to run Qwiic bus: (100000 or 400000): ")); + SerialPrint(F("Enter max frequency to run Qwiic bus: (100000 or 400000): ")); uint32_t amt = getNumber(menuTimeout); if ((amt == 100000) || (amt == 400000)) settings.qwiicBusMaxSpeed = amt; else - Serial.println(F("Error: Out of range")); + SerialPrintln(F("Error: Out of range")); } else if (incoming == '3') { // 60 seconds is more than long enough for a ZED-F9P to do a warm start after being powered cycled, so that seems a sensible maximum // minimumQwiicPowerOnDelay is defined in settings.h - Serial.printf("Enter the minimum number of milliseconds to wait for Qwiic VCC to stabilize before communication: (%d to 60000): ", minimumQwiicPowerOnDelay); + SerialPrintf2("Enter the minimum number of milliseconds to wait for Qwiic VCC to stabilize before communication: (%d to 60000): ", minimumQwiicPowerOnDelay); unsigned long amt = getNumber(menuTimeout); if ((amt >= minimumQwiicPowerOnDelay) && (amt <= 60000)) settings.qwiicBusPowerUpDelayMs = amt; else - Serial.println(F("Error: Out of range")); + SerialPrintln(F("Error: Out of range")); } else if (incoming == '4') { - Serial.print(F("Enter the Artemis pull-up resistance (0 = None; 1 = 1.5k; 6 = 6k; 12 = 12k; 24 = 24k): ")); + SerialPrint(F("Enter the Artemis pull-up resistance (0 = None; 1 = 1.5k; 6 = 6k; 12 = 12k; 24 = 24k): ")); uint32_t pur = (uint32_t)getNumber(menuTimeout); if ((pur == 0) || (pur == 1) || (pur == 6) || (pur == 12) || (pur == 24)) settings.qwiicBusPullUps = pur; else - Serial.println(F("Error: Invalid resistance. Possible values are 0,1,6,12,24.")); + SerialPrintln(F("Error: Invalid resistance. Possible values are 0,1,6,12,24.")); } else if (incoming == 'x') break; @@ -428,10 +440,10 @@ void menuConfigure_Multiplexer(void *configPtr) { //struct_multiplexer *sensor = (struct_multiplexer*)configPtr; - Serial.println(); - Serial.println(F("Menu: Configure Multiplexer")); + SerialPrintln(F("")); + SerialPrintln(F("Menu: Configure Multiplexer")); - Serial.println(F("There are currently no configurable options for this device.")); + SerialPrintln(F("There are currently no configurable options for this device.")); for (int i = 0; i < 500; i++) { checkBattery(); @@ -449,39 +461,39 @@ void menuConfigure_VL53L1X(void *configPtr) while (1) { - Serial.println(); - Serial.println(F("Menu: Configure VL53L1X Distance Sensor")); + SerialPrintln(F("")); + SerialPrintln(F("Menu: Configure VL53L1X Distance Sensor")); - Serial.print(F("1) Sensor Logging: ")); - if (sensorSetting->log == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("1) Sensor Logging: ")); + if (sensorSetting->log == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); if (sensorSetting->log == true) { - Serial.print(F("2) Log Distance: ")); - if (sensorSetting->logDistance == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("2) Log Distance: ")); + if (sensorSetting->logDistance == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("3) Log Range Status: ")); - if (sensorSetting->logRangeStatus == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("3) Log Range Status: ")); + if (sensorSetting->logRangeStatus == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("4) Log Signal Rate: ")); - if (sensorSetting->logSignalRate == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("4) Log Signal Rate: ")); + if (sensorSetting->logSignalRate == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("5) Set Distance Mode: ")); + SerialPrint(F("5) Set Distance Mode: ")); if (sensorSetting->distanceMode == VL53L1X_DISTANCE_MODE_SHORT) - Serial.print(F("Short")); + SerialPrint(F("Short")); else - Serial.print(F("Long")); - Serial.println(); + SerialPrint(F("Long")); + SerialPrintln(F("")); - Serial.printf("6) Set Intermeasurement Period: %d ms\r\n", sensorSetting->intermeasurementPeriod); - Serial.printf("7) Set Offset: %d mm\r\n", sensorSetting->offset); - Serial.printf("8) Set Cross Talk (counts per second): %d cps\r\n", sensorSetting->crosstalk); + SerialPrintf2("6) Set Intermeasurement Period: %d ms\r\n", sensorSetting->intermeasurementPeriod); + SerialPrintf2("7) Set Offset: %d mm\r\n", sensorSetting->offset); + SerialPrintf2("8) Set Cross Talk (counts per second): %d cps\r\n", sensorSetting->crosstalk); } - Serial.println(F("x) Exit")); + SerialPrintln(F("x) Exit")); byte incoming = getByteChoice(menuTimeout); //Timeout after x seconds @@ -506,7 +518,7 @@ void menuConfigure_VL53L1X(void *configPtr) if (sensorSetting->distanceMode == VL53L1X_DISTANCE_MODE_LONG && sensorSetting->intermeasurementPeriod < 140) { sensorSetting->intermeasurementPeriod = 140; - Serial.println(F("Intermeasurement Period increased to 140ms")); + SerialPrintln(F("Intermeasurement Period increased to 140ms")); } } else if (incoming == '6') @@ -516,28 +528,28 @@ void menuConfigure_VL53L1X(void *configPtr) min = 140; - Serial.printf("Set timing budget (%d to 1000ms): ", min); + SerialPrintf2("Set timing budget (%d to 1000ms): ", min); int amt = getNumber(menuTimeout); //x second timeout if (amt < min || amt > 1000) - Serial.println(F("Error: Out of range")); + SerialPrintln(F("Error: Out of range")); else sensorSetting->intermeasurementPeriod = amt; } else if (incoming == '7') { - Serial.print(F("Set Offset in mm (0 to 4000mm): ")); + SerialPrint(F("Set Offset in mm (0 to 4000mm): ")); int amt = getNumber(menuTimeout); //x second timeout if (amt < 0 || amt > 4000) - Serial.println(F("Error: Out of range")); + SerialPrintln(F("Error: Out of range")); else sensorSetting->offset = amt; } else if (incoming == '8') { - Serial.print(F("Set Crosstalk in Counts Per Second: ")); + SerialPrint(F("Set Crosstalk in Counts Per Second: ")); int amt = getNumber(menuTimeout); //x second timeout if (amt < 0 || amt > 4000) - Serial.println(F("Error: Out of range")); + SerialPrintln(F("Error: Out of range")); else sensorSetting->crosstalk = amt; } @@ -564,32 +576,32 @@ void menuConfigure_BME280(void *configPtr) while (1) { - Serial.println(); - Serial.println(F("Menu: Configure BME280 Pressure/Humidity/Temperature Sensor")); + SerialPrintln(F("")); + SerialPrintln(F("Menu: Configure BME280 Pressure/Humidity/Temperature Sensor")); - Serial.print(F("1) Sensor Logging: ")); - if (sensorSetting->log == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("1) Sensor Logging: ")); + if (sensorSetting->log == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); if (sensorSetting->log == true) { - Serial.print(F("2) Log Pressure: ")); - if (sensorSetting->logPressure == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("2) Log Pressure: ")); + if (sensorSetting->logPressure == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("3) Log Humidity: ")); - if (sensorSetting->logHumidity == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("3) Log Humidity: ")); + if (sensorSetting->logHumidity == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("4) Log Altitude: ")); - if (sensorSetting->logAltitude == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("4) Log Altitude: ")); + if (sensorSetting->logAltitude == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("5) Log Temperature: ")); - if (sensorSetting->logTemperature == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("5) Log Temperature: ")); + if (sensorSetting->logTemperature == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); } - Serial.println(F("x) Exit")); + SerialPrintln(F("x) Exit")); byte incoming = getByteChoice(menuTimeout); //Timeout after x seconds @@ -627,24 +639,24 @@ void menuConfigure_CCS811(void *configPtr) while (1) { - Serial.println(); - Serial.println(F("Menu: Configure CCS811 tVOC and CO2 Sensor")); + SerialPrintln(F("")); + SerialPrintln(F("Menu: Configure CCS811 tVOC and CO2 Sensor")); - Serial.print(F("1) Sensor Logging: ")); - if (sensorSetting->log == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("1) Sensor Logging: ")); + if (sensorSetting->log == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); if (sensorSetting->log == true) { - Serial.print(F("2) Log tVOC: ")); - if (sensorSetting->logTVOC == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("2) Log tVOC: ")); + if (sensorSetting->logTVOC == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("3) Log CO2: ")); - if (sensorSetting->logCO2 == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("3) Log CO2: ")); + if (sensorSetting->logCO2 == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); } - Serial.println(F("x) Exit")); + SerialPrintln(F("x) Exit")); byte incoming = getByteChoice(menuTimeout); //Timeout after x seconds @@ -677,24 +689,24 @@ void menuConfigure_LPS25HB(void *configPtr) struct_LPS25HB *sensorSetting = (struct_LPS25HB*)configPtr; while (1) { - Serial.println(); - Serial.println(F("Menu: Configure LPS25HB Pressure Sensor")); + SerialPrintln(F("")); + SerialPrintln(F("Menu: Configure LPS25HB Pressure Sensor")); - Serial.print(F("1) Sensor Logging: ")); - if (sensorSetting->log == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("1) Sensor Logging: ")); + if (sensorSetting->log == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); if (sensorSetting->log == true) { - Serial.print(F("2) Log Pressure: ")); - if (sensorSetting->logPressure == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("2) Log Pressure: ")); + if (sensorSetting->logPressure == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("3) Log Temperature: ")); - if (sensorSetting->logTemperature == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("3) Log Temperature: ")); + if (sensorSetting->logTemperature == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); } - Serial.println(F("x) Exit")); + SerialPrintln(F("x) Exit")); byte incoming = getByteChoice(menuTimeout); //Timeout after x seconds @@ -736,7 +748,7 @@ void menuConfigure_NAU7802(void *configPtr) } if (temp == NULL) { - Serial.println(F("NAU7802 node not found. Returning.")); + SerialPrintln(F("NAU7802 node not found. Returning.")); for (int i = 0; i < 1000; i++) { checkBattery(); @@ -750,27 +762,27 @@ void menuConfigure_NAU7802(void *configPtr) while (1) { - Serial.println(); - Serial.println(F("Menu: Configure NAU7802 Load Cell Amplifier")); + SerialPrintln(F("")); + SerialPrintln(F("Menu: Configure NAU7802 Load Cell Amplifier")); - Serial.print(F("1) Sensor Logging: ")); - if (sensorConfig->log == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("1) Sensor Logging: ")); + if (sensorConfig->log == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); if (sensorConfig->log == true) { - Serial.println(F("2) Calibrate Scale")); - Serial.printf("\tScale calibration factor: %f\r\n", sensorConfig->calibrationFactor); - Serial.printf("\tScale zero offset: %d\r\n", sensorConfig->zeroOffset); - Serial.printf("\tWeight currently on scale: %f\r\n", sensor->getWeight()); + SerialPrintln(F("2) Calibrate Scale")); + SerialPrintf2("\tScale calibration factor: %f\r\n", sensorConfig->calibrationFactor); + SerialPrintf2("\tScale zero offset: %d\r\n", sensorConfig->zeroOffset); + SerialPrintf2("\tWeight currently on scale: %f\r\n", sensor->getWeight()); - Serial.printf("3) Number of decimal places: %d\r\n", sensorConfig->decimalPlaces); - Serial.printf("4) Average number of readings to take per weight read: %d\r\n", sensorConfig->averageAmount); + SerialPrintf2("3) Number of decimal places: %d\r\n", sensorConfig->decimalPlaces); + SerialPrintf2("4) Average number of readings to take per weight read: %d\r\n", sensorConfig->averageAmount); } - Serial.println(F("x) Exit")); + SerialPrintln(F("x) Exit")); - byte incoming = getByteChoice(menuTimeout); //Timeout after x seconds + byte incoming = getByteChoice(menuTimeout, true); //Timeout after x seconds and set DSERIAL & ZSERIAL if (incoming == '1') { @@ -781,38 +793,40 @@ void menuConfigure_NAU7802(void *configPtr) if (incoming == '2') { //Gives user the ability to set a known weight on the scale and calculate a calibration factor - Serial.println(); - Serial.println(F("Scale calibration")); + SerialPrintln(F("")); + SerialPrintln(F("Scale calibration")); - Serial.println(F("Setup scale with no weight on it. Press a key when ready.")); + SerialPrintln(F("Setup scale with no weight on it. Press a key when ready.")); waitForInput(); sensor->calculateZeroOffset(64); //Zero or Tare the scale. Average over 64 readings. - Serial.print(F("New zero offset: ")); + SerialPrint(F("New zero offset: ")); Serial.println(sensor->getZeroOffset()); + if (settings.useTxRxPinsForTerminal == true) + SerialLog.println(sensor->getZeroOffset()); - Serial.println(F("Place known weight on scale. Press a key when weight is in place and stable.")); + SerialPrintln(F("Place known weight on scale. Press a key when weight is in place and stable.")); waitForInput(); - Serial.print(F("Please enter the weight, without units, currently sitting on the scale (for example '4.25'): ")); + SerialPrint(F("Please enter the weight, without units, currently sitting on the scale (for example '4.25'): ")); waitForInput(); //Read user input - float weightOnScale = Serial.parseFloat(); + float weightOnScale = DSERIAL->parseFloat(); sensor->calculateCalibrationFactor(weightOnScale, 64); //Tell the library how much weight is currently on it. Average over 64 readings. sensorConfig->calibrationFactor = sensor->getCalibrationFactor(); sensorConfig->zeroOffset = sensor->getZeroOffset(); - Serial.println(); + SerialPrintln(F("")); } else if (incoming == '3') { - Serial.print(F("Enter number of decimal places to print (1 to 10): ")); + SerialPrint(F("Enter number of decimal places to print (1 to 10): ")); int places = getNumber(menuTimeout); if (places < 1 || places > 10) { - Serial.println(F("Error: Decimal places out of range")); + SerialPrintln(F("Error: Decimal places out of range")); } else { @@ -821,11 +835,11 @@ void menuConfigure_NAU7802(void *configPtr) } else if (incoming == '4') { - Serial.print(F("Enter number of readings to take per weight read (1 to 10): ")); + SerialPrint(F("Enter number of readings to take per weight read (1 to 10): ")); int amt = getNumber(menuTimeout); if (amt < 1 || amt > 10) { - Serial.println(F("Error: Average number of readings out of range")); + SerialPrintln(F("Error: Average number of readings out of range")); } else { @@ -854,72 +868,72 @@ void menuConfigure_uBlox(void *configPtr) while (1) { - Serial.println(); - Serial.println(F("Menu: Configure uBlox GPS Receiver")); + SerialPrintln(F("")); + SerialPrintln(F("Menu: Configure uBlox GPS Receiver")); - Serial.print(F("1) Sensor Logging: ")); - if (sensorSetting->log == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("1) Sensor Logging: ")); + if (sensorSetting->log == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); if (sensorSetting->log == true) { - Serial.print(F("2) Log GPS Date: ")); - if (sensorSetting->logDate == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("2) Log GPS Date: ")); + if (sensorSetting->logDate == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("3) Log GPS Time: ")); - if (sensorSetting->logTime == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("3) Log GPS Time: ")); + if (sensorSetting->logTime == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("4) Log Longitude/Latitude: ")); - if (sensorSetting->logPosition == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("4) Log Longitude/Latitude: ")); + if (sensorSetting->logPosition == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("5) Log Altitude: ")); - if (sensorSetting->logAltitude == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("5) Log Altitude: ")); + if (sensorSetting->logAltitude == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("6) Log Altitude Mean Sea Level: ")); - if (sensorSetting->logAltitudeMSL == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("6) Log Altitude Mean Sea Level: ")); + if (sensorSetting->logAltitudeMSL == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("7) Log Satellites In View: ")); - if (sensorSetting->logSIV == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("7) Log Satellites In View: ")); + if (sensorSetting->logSIV == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("8) Log Fix Type: ")); - if (sensorSetting->logFixType == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("8) Log Fix Type: ")); + if (sensorSetting->logFixType == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("9) Log Carrier Solution: ")); - if (sensorSetting->logCarrierSolution == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("9) Log Carrier Solution: ")); + if (sensorSetting->logCarrierSolution == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("10) Log Ground Speed: ")); - if (sensorSetting->logGroundSpeed == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("10) Log Ground Speed: ")); + if (sensorSetting->logGroundSpeed == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("11) Log Heading of Motion: ")); - if (sensorSetting->logHeadingOfMotion == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("11) Log Heading of Motion: ")); + if (sensorSetting->logHeadingOfMotion == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("12) Log Position Dilution of Precision (pDOP): ")); - if (sensorSetting->logpDOP == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("12) Log Position Dilution of Precision (pDOP): ")); + if (sensorSetting->logpDOP == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("13) Log Interval Time Of Week (iTOW): ")); - if (sensorSetting->logiTOW == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("13) Log Interval Time Of Week (iTOW): ")); + if (sensorSetting->logiTOW == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.printf("14) Set I2C Interface Speed (u-blox modules have pullups built in. Remove *all* I2C pullups to achieve 400kHz): %d\r\n", sensorSetting->i2cSpeed); + SerialPrintf2("14) Set I2C Interface Speed (u-blox modules have pullups built in. Remove *all* I2C pullups to achieve 400kHz): %d\r\n", sensorSetting->i2cSpeed); - Serial.print(F("15) Use autoPVT: ")); - if (sensorSetting->useAutoPVT == true) Serial.println(F("Yes")); - else Serial.println(F("No")); + SerialPrint(F("15) Use autoPVT: ")); + if (sensorSetting->useAutoPVT == true) SerialPrintln(F("Yes")); + else SerialPrintln(F("No")); - Serial.flush(); + SerialFlush(); } - Serial.println(F("x) Exit")); + SerialPrintln(F("x) Exit")); int incoming = getNumber(menuTimeout); //Timeout after 10 seconds @@ -1010,7 +1024,7 @@ void getUbloxDateTime(int &year, int &month, int &day, int &hour, int &minute, i { qwiic.setPullups(0); //Disable pullups to minimize CRC issues - SFE_UBLOX_GPS *nodeDevice = (SFE_UBLOX_GPS *)temp->classPtr; + SFE_UBLOX_GNSS *nodeDevice = (SFE_UBLOX_GNSS *)temp->classPtr; struct_uBlox *nodeSetting = (struct_uBlox *)temp->configPtr; //Get latested date/time from GPS @@ -1037,24 +1051,24 @@ void menuConfigure_MCP9600(void *configPtr) struct_MCP9600 *sensorSetting = (struct_MCP9600*)configPtr; while (1) { - Serial.println(); - Serial.println(F("Menu: Configure MCP9600 Thermocouple Sensor")); + SerialPrintln(F("")); + SerialPrintln(F("Menu: Configure MCP9600 Thermocouple Sensor")); - Serial.print(F("1) Sensor Logging: ")); - if (sensorSetting->log == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("1) Sensor Logging: ")); + if (sensorSetting->log == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); if (sensorSetting->log == true) { - Serial.print(F("2) Log Thermocouple Temperature: ")); - if (sensorSetting->logTemperature == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("2) Log Thermocouple Temperature: ")); + if (sensorSetting->logTemperature == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("3) Log Ambient Temperature: ")); - if (sensorSetting->logAmbientTemperature == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("3) Log Ambient Temperature: ")); + if (sensorSetting->logAmbientTemperature == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); } - Serial.println(F("x) Exit")); + SerialPrintln(F("x) Exit")); byte incoming = getByteChoice(menuTimeout); //Timeout after x seconds @@ -1088,30 +1102,30 @@ void menuConfigure_VCNL4040(void *configPtr) struct_VCNL4040 *sensorSetting = (struct_VCNL4040*)configPtr; while (1) { - Serial.println(); - Serial.println(F("Menu: Configure VCNL4040 Proximity Sensor")); + SerialPrintln(F("")); + SerialPrintln(F("Menu: Configure VCNL4040 Proximity Sensor")); - Serial.print(F("1) Sensor Logging: ")); - if (sensorSetting->log == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("1) Sensor Logging: ")); + if (sensorSetting->log == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); if (sensorSetting->log == true) { - Serial.print(F("2) Log Proximity: ")); - if (sensorSetting->logProximity == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); - - Serial.print(F("3) Log Ambient Light: ")); - if (sensorSetting->logAmbientLight == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); - - Serial.printf("4) Set LED Current: %d\r\n", sensorSetting->LEDCurrent); - Serial.printf("5) Set IR Duty Cycle: %d\r\n", sensorSetting->IRDutyCycle); - Serial.printf("6) Set Proximity Integration Time: %d\r\n", sensorSetting->proximityIntegrationTime); - Serial.printf("7) Set Ambient Integration Time: %d\r\n", sensorSetting->ambientIntegrationTime); - Serial.printf("8) Set Resolution (bits): %d\r\n", sensorSetting->resolution); + SerialPrint(F("2) Log Proximity: ")); + if (sensorSetting->logProximity == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); + + SerialPrint(F("3) Log Ambient Light: ")); + if (sensorSetting->logAmbientLight == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); + + SerialPrintf2("4) Set LED Current: %d\r\n", sensorSetting->LEDCurrent); + SerialPrintf2("5) Set IR Duty Cycle: %d\r\n", sensorSetting->IRDutyCycle); + SerialPrintf2("6) Set Proximity Integration Time: %d\r\n", sensorSetting->proximityIntegrationTime); + SerialPrintf2("7) Set Ambient Integration Time: %d\r\n", sensorSetting->ambientIntegrationTime); + SerialPrintf2("8) Set Resolution (bits): %d\r\n", sensorSetting->resolution); } - Serial.println(F("x) Exit")); + SerialPrintln(F("x) Exit")); byte incoming = getByteChoice(menuTimeout); //Timeout after x seconds @@ -1125,48 +1139,48 @@ void menuConfigure_VCNL4040(void *configPtr) sensorSetting->logAmbientLight ^= 1; else if (incoming == '4') { - Serial.print(F("Enter current (mA) for IR LED drive (50 to 200mA): ")); + SerialPrint(F("Enter current (mA) for IR LED drive (50 to 200mA): ")); int amt = getNumber(menuTimeout); //x second timeout if (amt < 50 || amt > 200) - Serial.println(F("Error: Out of range")); + SerialPrintln(F("Error: Out of range")); else sensorSetting->LEDCurrent = amt; } else if (incoming == '5') { - Serial.print(F("Enter IR Duty Cycle (40 to 320): ")); + SerialPrint(F("Enter IR Duty Cycle (40 to 320): ")); int amt = getNumber(menuTimeout); //x second timeout if (amt < 40 || amt > 320) - Serial.println(F("Error: Out of range")); + SerialPrintln(F("Error: Out of range")); else sensorSetting->IRDutyCycle = amt; } else if (incoming == '6') { - Serial.print(F("Enter Proximity Integration Time (1 to 8): ")); + SerialPrint(F("Enter Proximity Integration Time (1 to 8): ")); int amt = getNumber(menuTimeout); //x second timeout if (amt < 1 || amt > 8) - Serial.println(F("Error: Out of range")); + SerialPrintln(F("Error: Out of range")); else sensorSetting->proximityIntegrationTime = amt; } else if (incoming == '7') { - Serial.print(F("Enter Ambient Light Integration Time (80 to 640ms): ")); + SerialPrint(F("Enter Ambient Light Integration Time (80 to 640ms): ")); int amt = getNumber(menuTimeout); //x second timeout if (amt < 80 || amt > 640) - Serial.println(F("Error: Out of range")); + SerialPrintln(F("Error: Out of range")); else sensorSetting->ambientIntegrationTime = amt; } else if (incoming == '8') { - Serial.print(F("Enter Proximity Resolution (12 or 16 bit): ")); + SerialPrint(F("Enter Proximity Resolution (12 or 16 bit): ")); int amt = getNumber(menuTimeout); //x second timeout if (amt == 12 || amt == 16) sensorSetting->resolution = amt; else - Serial.println(F("Error: Out of range")); + SerialPrintln(F("Error: Out of range")); } else if (incoming == 'x') break; @@ -1190,14 +1204,14 @@ void menuConfigure_TMP117(void *configPtr) struct_TMP117 *sensorSetting = (struct_TMP117*)configPtr; while (1) { - Serial.println(); - Serial.println(F("Menu: Configure TMP117 Precision Temperature Sensor")); + SerialPrintln(F("")); + SerialPrintln(F("Menu: Configure TMP117 Precision Temperature Sensor")); - Serial.print(F("1) Sensor Logging: ")); - if (sensorSetting->log == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("1) Sensor Logging: ")); + if (sensorSetting->log == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.println(F("x) Exit")); + SerialPrintln(F("x) Exit")); byte incoming = getByteChoice(menuTimeout); //Timeout after x seconds @@ -1219,32 +1233,32 @@ void menuConfigure_SGP30(void *configPtr) struct_SGP30 *sensorSetting = (struct_SGP30*)configPtr; while (1) { - Serial.println(); - Serial.println(F("Menu: Configure SGP30 tVOC and CO2 Sensor")); + SerialPrintln(F("")); + SerialPrintln(F("Menu: Configure SGP30 tVOC and CO2 Sensor")); - Serial.print(F("1) Sensor Logging: ")); - if (sensorSetting->log == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("1) Sensor Logging: ")); + if (sensorSetting->log == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); if (sensorSetting->log == true) { - Serial.print(F("2) Log tVOC: ")); - if (sensorSetting->logTVOC == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("2) Log tVOC: ")); + if (sensorSetting->logTVOC == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("3) Log CO2: ")); - if (sensorSetting->logCO2 == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("3) Log CO2: ")); + if (sensorSetting->logCO2 == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("4) Log H2: ")); - if (sensorSetting->logH2 == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("4) Log H2: ")); + if (sensorSetting->logH2 == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("5) Log Ethanol: ")); - if (sensorSetting->logEthanol == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("5) Log Ethanol: ")); + if (sensorSetting->logEthanol == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); } - Serial.println(F("x) Exit")); + SerialPrintln(F("x) Exit")); byte incoming = getByteChoice(menuTimeout); //Timeout after x seconds @@ -1281,28 +1295,28 @@ void menuConfigure_VEML6075(void *configPtr) struct_VEML6075 *sensorSetting = (struct_VEML6075*)configPtr; while (1) { - Serial.println(); - Serial.println(F("Menu: Configure VEML6075 UV Index Sensor")); + SerialPrintln(F("")); + SerialPrintln(F("Menu: Configure VEML6075 UV Index Sensor")); - Serial.print(F("1) Sensor Logging: ")); - if (sensorSetting->log == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("1) Sensor Logging: ")); + if (sensorSetting->log == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); if (sensorSetting->log == true) { - Serial.print(F("2) Log UVA: ")); - if (sensorSetting->logUVA == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("2) Log UVA: ")); + if (sensorSetting->logUVA == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("3) Log UVB: ")); - if (sensorSetting->logUVB == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("3) Log UVB: ")); + if (sensorSetting->logUVB == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("4) Log UV Index: ")); - if (sensorSetting->logUVIndex == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("4) Log UV Index: ")); + if (sensorSetting->logUVIndex == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); } - Serial.println(F("x) Exit")); + SerialPrintln(F("x) Exit")); byte incoming = getByteChoice(menuTimeout); //Timeout after x seconds @@ -1339,24 +1353,24 @@ void menuConfigure_MS5637(void *configPtr) struct_MS5637 *sensorSetting = (struct_MS5637*)configPtr; while (1) { - Serial.println(); - Serial.println(F("Menu: Configure MS5637 Pressure Sensor")); + SerialPrintln(F("")); + SerialPrintln(F("Menu: Configure MS5637 Pressure Sensor")); - Serial.print(F("1) Sensor Logging: ")); - if (sensorSetting->log == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("1) Sensor Logging: ")); + if (sensorSetting->log == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); if (sensorSetting->log == true) { - Serial.print(F("2) Log Pressure: ")); - if (sensorSetting->logPressure == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("2) Log Pressure: ")); + if (sensorSetting->logPressure == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("3) Log Temperature: ")); - if (sensorSetting->logTemperature == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("3) Log Temperature: ")); + if (sensorSetting->logTemperature == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); } - Serial.println(F("x) Exit")); + SerialPrintln(F("x) Exit")); byte incoming = getByteChoice(menuTimeout); //Timeout after x seconds @@ -1399,7 +1413,7 @@ void menuConfigure_SCD30(void *configPtr) } if (temp == NULL) { - Serial.println(F("SCD30 node not found. Returning.")); + SerialPrintln(F("SCD30 node not found. Returning.")); for (int i = 0; i < 1000; i++) { checkBattery(); @@ -1413,33 +1427,33 @@ void menuConfigure_SCD30(void *configPtr) while (1) { - Serial.println(); - Serial.println(F("Menu: Configure SCD30 CO2 and Humidity Sensor")); + SerialPrintln(F("")); + SerialPrintln(F("Menu: Configure SCD30 CO2 and Humidity Sensor")); - Serial.print(F("1) Sensor Logging: ")); - if (sensorSetting->log == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("1) Sensor Logging: ")); + if (sensorSetting->log == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); if (sensorSetting->log == true) { - Serial.print(F("2) Log CO2: ")); - if (sensorSetting->logCO2 == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); - - Serial.print(F("3) Log Humidity: ")); - if (sensorSetting->logHumidity == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); - - Serial.print(F("4) Log Temperature: ")); - if (sensorSetting->logTemperature == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); - - Serial.printf("5) Set Measurement Interval: %d\r\n", sensorSetting->measurementInterval); - Serial.printf("6) Set Altitude Compensation: %d\r\n", sensorSetting->altitudeCompensation); - Serial.printf("7) Set Ambient Pressure: %d\r\n", sensorSetting->ambientPressure); - Serial.printf("8) Set Temperature Offset: %d\r\n", sensorSetting->temperatureOffset); + SerialPrint(F("2) Log CO2: ")); + if (sensorSetting->logCO2 == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); + + SerialPrint(F("3) Log Humidity: ")); + if (sensorSetting->logHumidity == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); + + SerialPrint(F("4) Log Temperature: ")); + if (sensorSetting->logTemperature == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); + + SerialPrintf2("5) Set Measurement Interval: %d\r\n", sensorSetting->measurementInterval); + SerialPrintf2("6) Set Altitude Compensation: %d\r\n", sensorSetting->altitudeCompensation); + SerialPrintf2("7) Set Ambient Pressure: %d\r\n", sensorSetting->ambientPressure); + SerialPrintf2("8) Set Temperature Offset: %d\r\n", sensorSetting->temperatureOffset); } - Serial.println(F("x) Exit")); + SerialPrintln(F("x) Exit")); byte incoming = getByteChoice(menuTimeout); //Timeout after x seconds @@ -1455,42 +1469,44 @@ void menuConfigure_SCD30(void *configPtr) sensorSetting->logTemperature ^= 1; else if (incoming == '5') { - Serial.print(F("Enter the seconds between measurements (2 to 1800): ")); + SerialPrint(F("Enter the seconds between measurements (2 to 1800): ")); int amt = getNumber(menuTimeout); //x second timeout if (amt < 2 || amt > 1800) - Serial.println(F("Error: Out of range")); + SerialPrintln(F("Error: Out of range")); else sensorSetting->measurementInterval = amt; } else if (incoming == '6') { - Serial.print(F("Enter the Altitude Compensation in meters (0 to 10000): ")); + SerialPrint(F("Enter the Altitude Compensation in meters (0 to 10000): ")); int amt = getNumber(menuTimeout); //x second timeout if (amt < 0 || amt > 10000) - Serial.println(F("Error: Out of range")); + SerialPrintln(F("Error: Out of range")); else sensorSetting->altitudeCompensation = amt; } else if (incoming == '7') { - Serial.print(F("Enter Ambient Pressure in mBar (700 to 1200): ")); + SerialPrint(F("Enter Ambient Pressure in mBar (700 to 1200): ")); int amt = getNumber(menuTimeout); //x second timeout if (amt < 700 || amt > 1200) - Serial.println(F("Error: Out of range")); + SerialPrintln(F("Error: Out of range")); else sensorSetting->ambientPressure = amt; } else if (incoming == '8') { - Serial.print(F("The current temperature offset read from the sensor is: ")); + SerialPrint(F("The current temperature offset read from the sensor is: ")); Serial.print(sensor->getTemperatureOffset(), 2); - Serial.println(F("C")); - Serial.print(F("Enter new temperature offset in C (-50 to 50): ")); + if (settings.useTxRxPinsForTerminal == true) + SerialLog.print(sensor->getTemperatureOffset(), 2); + SerialPrintln(F("C")); + SerialPrint(F("Enter new temperature offset in C (-50 to 50): ")); int amt = getNumber(menuTimeout); //x second timeout if (amt < -50 || amt > 50) sensorSetting->temperatureOffset = amt; else - Serial.println(F("Error: Out of range")); + SerialPrintln(F("Error: Out of range")); } else if (incoming == 'x') break; @@ -1515,58 +1531,58 @@ void menuConfigure_MS8607(void *configPtr) struct_MS8607 *sensorSetting = (struct_MS8607*)configPtr; while (1) { - Serial.println(); - Serial.println(F("Menu: Configure MS8607 Pressure Humidity Temperature (PHT) Sensor")); + SerialPrintln(F("")); + SerialPrintln(F("Menu: Configure MS8607 Pressure Humidity Temperature (PHT) Sensor")); - Serial.print(F("1) Sensor Logging: ")); - if (sensorSetting->log == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("1) Sensor Logging: ")); + if (sensorSetting->log == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); if (sensorSetting->log == true) { - Serial.print(F("2) Log Pressure: ")); - if (sensorSetting->logPressure == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("2) Log Pressure: ")); + if (sensorSetting->logPressure == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("3) Log Humidity: ")); - if (sensorSetting->logHumidity == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("3) Log Humidity: ")); + if (sensorSetting->logHumidity == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("4) Log Temperature: ")); - if (sensorSetting->logTemperature == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("4) Log Temperature: ")); + if (sensorSetting->logTemperature == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("5) Heater: ")); - if (sensorSetting->enableHeater == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("5) Heater: ")); + if (sensorSetting->enableHeater == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("6) Set Pressure Resolution: ")); + SerialPrint(F("6) Set Pressure Resolution: ")); if (sensorSetting->pressureResolution == MS8607_pressure_resolution_osr_256) - Serial.print(F("0.11")); + SerialPrint(F("0.11")); else if (sensorSetting->pressureResolution == MS8607_pressure_resolution_osr_512) - Serial.print(F("0.062")); + SerialPrint(F("0.062")); else if (sensorSetting->pressureResolution == MS8607_pressure_resolution_osr_1024) - Serial.print(F("0.039")); + SerialPrint(F("0.039")); else if (sensorSetting->pressureResolution == MS8607_pressure_resolution_osr_2048) - Serial.print(F("0.028")); + SerialPrint(F("0.028")); else if (sensorSetting->pressureResolution == MS8607_pressure_resolution_osr_4096) - Serial.print(F("0.021")); + SerialPrint(F("0.021")); else if (sensorSetting->pressureResolution == MS8607_pressure_resolution_osr_8192) - Serial.print(F("0.016")); - Serial.println(F(" mbar")); + SerialPrint(F("0.016")); + SerialPrintln(F(" mbar")); - Serial.print(F("7) Set Humidity Resolution: ")); + SerialPrint(F("7) Set Humidity Resolution: ")); if (sensorSetting->humidityResolution == MS8607_humidity_resolution_8b) - Serial.print(F("8")); + SerialPrint(F("8")); else if (sensorSetting->humidityResolution == MS8607_humidity_resolution_10b) - Serial.print(F("10")); + SerialPrint(F("10")); else if (sensorSetting->humidityResolution == MS8607_humidity_resolution_11b) - Serial.print(F("11")); + SerialPrint(F("11")); else if (sensorSetting->humidityResolution == MS8607_humidity_resolution_12b) - Serial.print(F("12")); - Serial.println(F(" bits")); + SerialPrint(F("12")); + SerialPrintln(F(" bits")); } - Serial.println(F("x) Exit")); + SerialPrintln(F("x) Exit")); byte incoming = getByteChoice(menuTimeout); //Timeout after x seconds @@ -1584,26 +1600,26 @@ void menuConfigure_MS8607(void *configPtr) sensorSetting->enableHeater ^= 1; else if (incoming == '6') { - Serial.println(F("Set Pressure Resolution:")); - Serial.println(F("1) 0.11 mbar")); - Serial.println(F("2) 0.062 mbar")); - Serial.println(F("3) 0.039 mbar")); - Serial.println(F("4) 0.028 mbar")); - Serial.println(F("5) 0.021 mbar")); - Serial.println(F("6) 0.016 mbar")); + SerialPrintln(F("Set Pressure Resolution:")); + SerialPrintln(F("1) 0.11 mbar")); + SerialPrintln(F("2) 0.062 mbar")); + SerialPrintln(F("3) 0.039 mbar")); + SerialPrintln(F("4) 0.028 mbar")); + SerialPrintln(F("5) 0.021 mbar")); + SerialPrintln(F("6) 0.016 mbar")); int amt = getNumber(menuTimeout); //x second timeout if (amt >= 1 && amt <= 6) sensorSetting->pressureResolution = (MS8607_pressure_resolution)(amt - 1); else - Serial.println(F("Error: Out of range")); + SerialPrintln(F("Error: Out of range")); } else if (incoming == '7') { - Serial.println(F("Set Humidity Resolution:")); - Serial.println(F("1) 8 bit")); - Serial.println(F("2) 10 bit")); - Serial.println(F("3) 11 bit")); - Serial.println(F("4) 12 bit")); + SerialPrintln(F("Set Humidity Resolution:")); + SerialPrintln(F("1) 8 bit")); + SerialPrintln(F("2) 10 bit")); + SerialPrintln(F("3) 11 bit")); + SerialPrintln(F("4) 12 bit")); int amt = getNumber(menuTimeout); //x second timeout if (amt >= 1 && amt <= 4) { @@ -1614,7 +1630,7 @@ void menuConfigure_MS8607(void *configPtr) if (amt == 4) sensorSetting->humidityResolution = MS8607_humidity_resolution_12b; } else - Serial.println(F("Error: Out of range")); + SerialPrintln(F("Error: Out of range")); } else if (incoming == 'x') break; @@ -1638,24 +1654,24 @@ void menuConfigure_AHT20(void *configPtr) while (1) { - Serial.println(); - Serial.println(F("Menu: Configure AHT20 Humidity Sensor")); + SerialPrintln(F("")); + SerialPrintln(F("Menu: Configure AHT20 Humidity Sensor")); - Serial.print(F("1) Sensor Logging: ")); - if (sensorSetting->log == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("1) Sensor Logging: ")); + if (sensorSetting->log == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); if (sensorSetting->log == true) { - Serial.print(F("2) Log Humidity: ")); - if (sensorSetting->logHumidity == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("2) Log Humidity: ")); + if (sensorSetting->logHumidity == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("3) Log Temperature: ")); - if (sensorSetting->logTemperature == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("3) Log Temperature: ")); + if (sensorSetting->logTemperature == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); } - Serial.println(F("x) Exit")); + SerialPrintln(F("x) Exit")); byte incoming = getByteChoice(menuTimeout); //Timeout after x seconds @@ -1689,24 +1705,24 @@ void menuConfigure_SHTC3(void *configPtr) while (1) { - Serial.println(); - Serial.println(F("Menu: Configure SHTC3 Humidity Sensor")); + SerialPrintln(F("")); + SerialPrintln(F("Menu: Configure SHTC3 Humidity Sensor")); - Serial.print(F("1) Sensor Logging: ")); - if (sensorSetting->log == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("1) Sensor Logging: ")); + if (sensorSetting->log == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); if (sensorSetting->log == true) { - Serial.print(F("2) Log Humidity: ")); - if (sensorSetting->logHumidity == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("2) Log Humidity: ")); + if (sensorSetting->logHumidity == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("3) Log Temperature: ")); - if (sensorSetting->logTemperature == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("3) Log Temperature: ")); + if (sensorSetting->logTemperature == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); } - Serial.println(F("x) Exit")); + SerialPrintln(F("x) Exit")); byte incoming = getByteChoice(menuTimeout); //Timeout after x seconds @@ -1740,56 +1756,56 @@ void menuConfigure_ADS122C04(void *configPtr) while (1) { - Serial.println(); - Serial.println(F("Menu: Configure ADS122C04 ADC (Qwiic PT100)")); + SerialPrintln(F("")); + SerialPrintln(F("Menu: Configure ADS122C04 ADC (Qwiic PT100)")); - Serial.print(F("1) Sensor Logging: ")); - if (sensorSetting->log == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("1) Sensor Logging: ")); + if (sensorSetting->log == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); if (sensorSetting->log == true) { - Serial.print(F("2) Log Centigrade: ")); - if (sensorSetting->logCentigrade == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("2) Log Centigrade: ")); + if (sensorSetting->logCentigrade == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("3) Log Fahrenheit: ")); - if (sensorSetting->logFahrenheit == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("3) Log Fahrenheit: ")); + if (sensorSetting->logFahrenheit == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("4) Log Internal Temperature: ")); - if (sensorSetting->logInternalTemperature == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("4) Log Internal Temperature: ")); + if (sensorSetting->logInternalTemperature == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("5) Log Raw Voltage: ")); - if (sensorSetting->logRawVoltage == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("5) Log Raw Voltage: ")); + if (sensorSetting->logRawVoltage == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("6) Use 4-Wire Mode: ")); - if (sensorSetting->useFourWireMode == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("6) Use 4-Wire Mode: ")); + if (sensorSetting->useFourWireMode == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("7) Use 3-Wire Mode: ")); - if (sensorSetting->useThreeWireMode == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("7) Use 3-Wire Mode: ")); + if (sensorSetting->useThreeWireMode == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("8) Use 2-Wire Mode: ")); - if (sensorSetting->useTwoWireMode == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("8) Use 2-Wire Mode: ")); + if (sensorSetting->useTwoWireMode == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("9) Use 4-Wire High Temperature Mode: ")); - if (sensorSetting->useFourWireHighTemperatureMode == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("9) Use 4-Wire High Temperature Mode: ")); + if (sensorSetting->useFourWireHighTemperatureMode == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("10) Use 3-Wire High Temperature Mode: ")); - if (sensorSetting->useThreeWireHighTemperatureMode == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("10) Use 3-Wire High Temperature Mode: ")); + if (sensorSetting->useThreeWireHighTemperatureMode == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("11) Use 2-Wire High Temperature Mode: ")); - if (sensorSetting->useTwoWireHighTemperatureMode == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("11) Use 2-Wire High Temperature Mode: ")); + if (sensorSetting->useTwoWireHighTemperatureMode == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); } - Serial.println(F("x) Exit")); + SerialPrintln(F("x) Exit")); int incoming = getNumber(menuTimeout); //Timeout after x seconds @@ -1881,49 +1897,49 @@ void menuConfigure_MPR0025PA1(void *configPtr) while (1) { - Serial.println(); - Serial.println(F("Menu: Configure MPR MicroPressure Sensor")); + SerialPrintln(F("")); + SerialPrintln(F("Menu: Configure MPR MicroPressure Sensor")); - Serial.print(F("1) Sensor Logging: ")); - if (sensorSetting->log == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("1) Sensor Logging: ")); + if (sensorSetting->log == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); if (sensorSetting->log == true) { - Serial.printf("2) Minimum PSI: %d\r\n", sensorSetting->minimumPSI); + SerialPrintf2("2) Minimum PSI: %d\r\n", sensorSetting->minimumPSI); - Serial.printf("3) Maximum PSI: %d\r\n", sensorSetting->maximumPSI); + SerialPrintf2("3) Maximum PSI: %d\r\n", sensorSetting->maximumPSI); - Serial.print(F("4) Use PSI: ")); - if (sensorSetting->usePSI == true) Serial.println(F("Yes")); - else Serial.println(F("No")); + SerialPrint(F("4) Use PSI: ")); + if (sensorSetting->usePSI == true) SerialPrintln(F("Yes")); + else SerialPrintln(F("No")); - Serial.print(F("5) Use Pa: ")); - if (sensorSetting->usePA == true) Serial.println(F("Yes")); - else Serial.println(F("No")); + SerialPrint(F("5) Use Pa: ")); + if (sensorSetting->usePA == true) SerialPrintln(F("Yes")); + else SerialPrintln(F("No")); - Serial.print(F("6) Use kPa: ")); - if (sensorSetting->useKPA == true) Serial.println(F("Yes")); - else Serial.println(F("No")); + SerialPrint(F("6) Use kPa: ")); + if (sensorSetting->useKPA == true) SerialPrintln(F("Yes")); + else SerialPrintln(F("No")); - Serial.print(F("7) Use torr: ")); - if (sensorSetting->useTORR == true) Serial.println(F("Yes")); - else Serial.println(F("No")); + SerialPrint(F("7) Use torr: ")); + if (sensorSetting->useTORR == true) SerialPrintln(F("Yes")); + else SerialPrintln(F("No")); - Serial.print(F("8) Use inHg: ")); - if (sensorSetting->useINHG == true) Serial.println(F("Yes")); - else Serial.println(F("No")); + SerialPrint(F("8) Use inHg: ")); + if (sensorSetting->useINHG == true) SerialPrintln(F("Yes")); + else SerialPrintln(F("No")); - Serial.print(F("9) Use atm: ")); - if (sensorSetting->useATM == true) Serial.println(F("Yes")); - else Serial.println(F("No")); + SerialPrint(F("9) Use atm: ")); + if (sensorSetting->useATM == true) SerialPrintln(F("Yes")); + else SerialPrintln(F("No")); - Serial.print(F("10) Use bar: ")); - if (sensorSetting->useBAR == true) Serial.println(F("Yes")); - else Serial.println(F("No")); + SerialPrint(F("10) Use bar: ")); + if (sensorSetting->useBAR == true) SerialPrintln(F("Yes")); + else SerialPrintln(F("No")); } - Serial.println(F("x) Exit")); + SerialPrintln(F("x) Exit")); int incoming = getNumber(menuTimeout); //Timeout after x seconds @@ -1933,19 +1949,19 @@ void menuConfigure_MPR0025PA1(void *configPtr) { if (incoming == 2) { - Serial.print(F("Enter the sensor minimum pressure in PSI (this should be 0 for the MPR0025PA): ")); + SerialPrint(F("Enter the sensor minimum pressure in PSI (this should be 0 for the MPR0025PA): ")); int minPSI = getNumber(menuTimeout); //x second timeout if (minPSI < 0 || minPSI > 30) - Serial.println(F("Error: Out of range")); + SerialPrintln(F("Error: Out of range")); else sensorSetting->minimumPSI = minPSI; } else if (incoming == 3) { - Serial.print(F("Enter the sensor maximum pressure in PSI (this should be 25 for the MPR0025PA): ")); + SerialPrint(F("Enter the sensor maximum pressure in PSI (this should be 25 for the MPR0025PA): ")); int maxPSI = getNumber(menuTimeout); //x second timeout if (maxPSI < 0 || maxPSI > 30) - Serial.println(F("Error: Out of range")); + SerialPrintln(F("Error: Out of range")); else sensorSetting->maximumPSI = maxPSI; } @@ -2041,68 +2057,68 @@ void menuConfigure_SNGCJA5(void *configPtr) while (1) { - Serial.println(); - Serial.println(F("Menu: Configure SNGCJA5 Particle Sensor")); + SerialPrintln(F("")); + SerialPrintln(F("Menu: Configure SNGCJA5 Particle Sensor")); - Serial.print(F("1) Sensor Logging: ")); - if (sensorSetting->log == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("1) Sensor Logging: ")); + if (sensorSetting->log == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); if (sensorSetting->log == true) { - Serial.print(F("2) Log Particle Mass Density 1.0um (ug/m^3): ")); - if (sensorSetting->logPM1 == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("2) Log Particle Mass Density 1.0um (ug/m^3): ")); + if (sensorSetting->logPM1 == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("3) Log Particle Mass Density 2.5um (ug/m^3): ")); - if (sensorSetting->logPM25 == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("3) Log Particle Mass Density 2.5um (ug/m^3): ")); + if (sensorSetting->logPM25 == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("4) Log Particle Mass Density 10.0um (ug/m^3): ")); - if (sensorSetting->logPM10 == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("4) Log Particle Mass Density 10.0um (ug/m^3): ")); + if (sensorSetting->logPM10 == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("5) Log Particle Count 0.5um: ")); - if (sensorSetting->logPC05 == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("5) Log Particle Count 0.5um: ")); + if (sensorSetting->logPC05 == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("6) Log Particle Count 1.0um: ")); - if (sensorSetting->logPC1 == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("6) Log Particle Count 1.0um: ")); + if (sensorSetting->logPC1 == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("7) Log Particle Count 2.5um: ")); - if (sensorSetting->logPC25 == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("7) Log Particle Count 2.5um: ")); + if (sensorSetting->logPC25 == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("8) Log Particle Count 5.0um: ")); - if (sensorSetting->logPC50 == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("8) Log Particle Count 5.0um: ")); + if (sensorSetting->logPC50 == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("9) Log Particle Count 7.5um: ")); - if (sensorSetting->logPC75 == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("9) Log Particle Count 7.5um: ")); + if (sensorSetting->logPC75 == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("10) Log Particle Count 10.0um: ")); - if (sensorSetting->logPC10 == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("10) Log Particle Count 10.0um: ")); + if (sensorSetting->logPC10 == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("11) Log Combined Sensor Status: ")); - if (sensorSetting->logSensorStatus == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("11) Log Combined Sensor Status: ")); + if (sensorSetting->logSensorStatus == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("12) Log PhotoDiode Status: ")); - if (sensorSetting->logPDStatus == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("12) Log PhotoDiode Status: ")); + if (sensorSetting->logPDStatus == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("13) Log LaserDiode Status: ")); - if (sensorSetting->logLDStatus == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("13) Log LaserDiode Status: ")); + if (sensorSetting->logLDStatus == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("14) Log Fan Status: ")); - if (sensorSetting->logFanStatus == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("14) Log Fan Status: ")); + if (sensorSetting->logFanStatus == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); } - Serial.println(F("x) Exit")); + SerialPrintln(F("x) Exit")); int incoming = getNumber(menuTimeout); //Timeout after x seconds diff --git a/Firmware/OpenLog_Artemis/menuDebug.ino b/Firmware/OpenLog_Artemis/menuDebug.ino index 18869fe..054596d 100644 --- a/Firmware/OpenLog_Artemis/menuDebug.ino +++ b/Firmware/OpenLog_Artemis/menuDebug.ino @@ -2,14 +2,14 @@ void menuDebug() { while (1) { - Serial.println(); - Serial.println(F("Menu: Configure Debug Settings")); + SerialPrintln(F("")); + SerialPrintln(F("Menu: Configure Debug Settings")); - Serial.print(F("1) Debug Messages: ")); - if (settings.printDebugMessages == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("1) Debug Messages: ")); + if (settings.printDebugMessages == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.println(F("x) Exit")); + SerialPrintln(F("x) Exit")); byte incoming = getByteChoice(menuTimeout); //Timeout after x seconds diff --git a/Firmware/OpenLog_Artemis/menuIMU.ino b/Firmware/OpenLog_Artemis/menuIMU.ino index 64f2371..c91af34 100644 --- a/Firmware/OpenLog_Artemis/menuIMU.ino +++ b/Firmware/OpenLog_Artemis/menuIMU.ino @@ -2,158 +2,158 @@ void menuIMU() { while (1) { - Serial.println(); - Serial.println(F("Menu: Configure IMU")); + SerialPrintln(F("")); + SerialPrintln(F("Menu: Configure IMU")); - Serial.print(F("1) Sensor Logging: ")); - if (settings.enableIMU == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("1) Sensor Logging: ")); + if (settings.enableIMU == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); if (settings.enableIMU == true) { - Serial.print(F("2) Accelerometer Logging: ")); - if (settings.logIMUAccel) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("2) Accelerometer Logging: ")); + if (settings.logIMUAccel) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("3) Gyro Logging: ")); - if (settings.logIMUGyro) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("3) Gyro Logging: ")); + if (settings.logIMUGyro) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("4) Magnetometer Logging: ")); - if (settings.logIMUMag) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("4) Magnetometer Logging: ")); + if (settings.logIMUMag) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("5) Temperature Logging: ")); - if (settings.logIMUTemp) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("5) Temperature Logging: ")); + if (settings.logIMUTemp) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); if (online.IMU == true) { - Serial.print(F("6) Accelerometer Full Scale: +/- ")); + SerialPrint(F("6) Accelerometer Full Scale: +/- ")); switch (settings.imuAccFSS) { case 0: - Serial.println(F("2g")); + SerialPrintln(F("2g")); break; case 1: - Serial.println(F("4g")); + SerialPrintln(F("4g")); break; case 2: - Serial.println(F("8g")); + SerialPrintln(F("8g")); break; case 3: - Serial.println(F("16g")); + SerialPrintln(F("16g")); break; default: - Serial.println(F("UNKNOWN")); + SerialPrintln(F("UNKNOWN")); break; } - Serial.print(F("7) Accelerometer Digital Low Pass Filter: ")); + SerialPrint(F("7) Accelerometer Digital Low Pass Filter: ")); if (settings.imuAccDLPF) { - Serial.println(F("Enabled")); - Serial.print(F("8) Accelerometer DLPF Bandwidth (Hz): ")); + SerialPrintln(F("Enabled")); + SerialPrint(F("8) Accelerometer DLPF Bandwidth (Hz): ")); switch (settings.imuAccDLPFBW) { case 0: - Serial.println(F("246.0 (3dB) 265.0 (Nyquist)")); + SerialPrintln(F("246.0 (3dB) 265.0 (Nyquist)")); break; case 1: - Serial.println(F("246.0 (3dB) 265.0 (Nyquist)")); + SerialPrintln(F("246.0 (3dB) 265.0 (Nyquist)")); break; case 2: - Serial.println(F("111.4 (3dB) 136.0 (Nyquist)")); + SerialPrintln(F("111.4 (3dB) 136.0 (Nyquist)")); break; case 3: - Serial.println(F("50.4 (3dB) 68.8 (Nyquist)")); + SerialPrintln(F("50.4 (3dB) 68.8 (Nyquist)")); break; case 4: - Serial.println(F("23.9 (3dB) 34.4 (Nyquist)")); + SerialPrintln(F("23.9 (3dB) 34.4 (Nyquist)")); break; case 5: - Serial.println(F("11.5 (3dB) 17.0 (Nyquist)")); + SerialPrintln(F("11.5 (3dB) 17.0 (Nyquist)")); break; case 6: - Serial.println(F("5.7 (3dB) 8.3 (Nyquist)")); + SerialPrintln(F("5.7 (3dB) 8.3 (Nyquist)")); break; case 7: - Serial.println(F("473 (3dB) 499 (Nyquist)")); + SerialPrintln(F("473 (3dB) 499 (Nyquist)")); break; default: - Serial.println(F("UNKNOWN")); + SerialPrintln(F("UNKNOWN")); break; } } else { - Serial.println(F("Disabled (Bandwidth is 1209 Hz (3dB) 1248 Hz (Nyquist))")); + SerialPrintln(F("Disabled (Bandwidth is 1209 Hz (3dB) 1248 Hz (Nyquist))")); } - Serial.print(F("9) Gyro Full Scale: +/- ")); + SerialPrint(F("9) Gyro Full Scale: +/- ")); switch (settings.imuGyroFSS) { case 0: - Serial.println(F("250dps")); + SerialPrintln(F("250dps")); break; case 1: - Serial.println(F("500dps")); + SerialPrintln(F("500dps")); break; case 2: - Serial.println(F("1000dps")); + SerialPrintln(F("1000dps")); break; case 3: - Serial.println(F("2000dps")); + SerialPrintln(F("2000dps")); break; default: - Serial.println(F("UNKNOWN")); + SerialPrintln(F("UNKNOWN")); break; } - Serial.print(F("10) Gyro Digital Low Pass Filter: ")); + SerialPrint(F("10) Gyro Digital Low Pass Filter: ")); if (settings.imuGyroDLPF) { - Serial.println(F("Enabled")); - Serial.print(F("11) Gyro DLPF Bandwidth (Hz): ")); + SerialPrintln(F("Enabled")); + SerialPrint(F("11) Gyro DLPF Bandwidth (Hz): ")); switch (settings.imuGyroDLPFBW) { case 0: - Serial.println(F("196.6 (3dB) 229.8 (Nyquist)")); + SerialPrintln(F("196.6 (3dB) 229.8 (Nyquist)")); break; case 1: - Serial.println(F("151.8 (3dB) 187.6 (Nyquist)")); + SerialPrintln(F("151.8 (3dB) 187.6 (Nyquist)")); break; case 2: - Serial.println(F("119.5 (3dB) 154.3 (Nyquist)")); + SerialPrintln(F("119.5 (3dB) 154.3 (Nyquist)")); break; case 3: - Serial.println(F("51.2 (3dB) 73.3 (Nyquist)")); + SerialPrintln(F("51.2 (3dB) 73.3 (Nyquist)")); break; case 4: - Serial.println(F("23.9 (3dB) 35.9 (Nyquist)")); + SerialPrintln(F("23.9 (3dB) 35.9 (Nyquist)")); break; case 5: - Serial.println(F("11.6 (3dB) 17.8 (Nyquist)")); + SerialPrintln(F("11.6 (3dB) 17.8 (Nyquist)")); break; case 6: - Serial.println(F("5.7 (3dB) 8.9 (Nyquist)")); + SerialPrintln(F("5.7 (3dB) 8.9 (Nyquist)")); break; case 7: - Serial.println(F("361.4 (3dB) 376.5 (Nyquist)")); + SerialPrintln(F("361.4 (3dB) 376.5 (Nyquist)")); break; default: - Serial.println(F("UNKNOWN")); + SerialPrintln(F("UNKNOWN")); break; } } else { - Serial.println(F("Disabled (Bandwidth is 12106 Hz (3dB) 12316 Hz (Nyquist))")); + SerialPrintln(F("Disabled (Bandwidth is 12106 Hz (3dB) 12316 Hz (Nyquist))")); } } } - Serial.println(F("x) Exit")); + SerialPrintln(F("x) Exit")); int incoming = getNumber(menuTimeout); //Timeout after x seconds @@ -175,14 +175,14 @@ void menuIMU() settings.logIMUTemp ^= 1; else if ((incoming == 6) && (online.IMU == true)) { - Serial.println(F("Enter Accelerometer Full Scale (0 to 3): ")); - Serial.println(F("0: +/- 2g")); - Serial.println(F("1: +/- 4g")); - Serial.println(F("2: +/- 8g")); - Serial.println(F("3: +/- 16g")); + 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) - Serial.println(F("Error: Out of range")); + SerialPrintln(F("Error: Out of range")); else { settings.imuAccFSS = afs; @@ -192,7 +192,7 @@ void menuIMU() ICM_20948_Status_e retval = myICM.setFullScale((ICM_20948_Internal_Acc | ICM_20948_Internal_Gyr), FSS); if (retval != ICM_20948_Stat_Ok) { - Serial.println(F("Error: Could not configure the IMU!")); + SerialPrintln(F("Error: Could not configure the IMU!")); } } } @@ -202,23 +202,23 @@ void menuIMU() ICM_20948_Status_e retval = myICM.enableDLPF(ICM_20948_Internal_Acc, settings.imuAccDLPF); if (retval != ICM_20948_Stat_Ok) { - Serial.println(F("Error: Could not configure the IMU!")); + SerialPrintln(F("Error: Could not configure the IMU!")); } } else if ((incoming == 8) && (online.IMU == true) && (settings.imuAccDLPF == true)) { - Serial.println(F("Enter Accelerometer DLPF Bandwidth (0 to 7): ")); - Serial.println(F("0: 246.0 (3dB) 265.0 (Nyquist) (Hz)")); - Serial.println(F("1: 246.0 (3dB) 265.0 (Nyquist) (Hz)")); - Serial.println(F("2: 111.4 (3dB) 136.0 (Nyquist) (Hz)")); - Serial.println(F("3: 50.4 (3dB) 68.8 (Nyquist) (Hz)")); - Serial.println(F("4: 23.9 (3dB) 34.4 (Nyquist) (Hz)")); - Serial.println(F("5: 11.5 (3dB) 17.0 (Nyquist) (Hz)")); - Serial.println(F("6: 5.7 (3dB) 8.3 (Nyquist) (Hz)")); - Serial.println(F("7: 473 (3dB) 499 (Nyquist) (Hz)")); + 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) - Serial.println(F("Error: Out of range")); + SerialPrintln(F("Error: Out of range")); else { settings.imuAccDLPFBW = afbw; @@ -228,20 +228,20 @@ void menuIMU() ICM_20948_Status_e retval = myICM.setDLPFcfg((ICM_20948_Internal_Acc | ICM_20948_Internal_Gyr), dlpcfg); if (retval != ICM_20948_Stat_Ok) { - Serial.println(F("Error: Could not configure the IMU!")); + SerialPrintln(F("Error: Could not configure the IMU!")); } } } else if ((incoming == 9) && (online.IMU == true)) { - Serial.println(F("Enter Gyro Full Scale (0 to 3): ")); - Serial.println(F("0: +/- 250dps")); - Serial.println(F("1: +/- 500dps")); - Serial.println(F("2: +/- 1000dps")); - Serial.println(F("3: +/- 2000dps")); + 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) - Serial.println(F("Error: Out of range")); + SerialPrintln(F("Error: Out of range")); else { settings.imuGyroFSS = gfs; @@ -251,7 +251,7 @@ void menuIMU() ICM_20948_Status_e retval = myICM.setFullScale((ICM_20948_Internal_Acc | ICM_20948_Internal_Gyr), FSS); if (retval != ICM_20948_Stat_Ok) { - Serial.println(F("Error: Could not configure the IMU!")); + SerialPrintln(F("Error: Could not configure the IMU!")); } } } @@ -261,23 +261,23 @@ void menuIMU() ICM_20948_Status_e retval = myICM.enableDLPF(ICM_20948_Internal_Gyr, settings.imuGyroDLPF); if (retval != ICM_20948_Stat_Ok) { - Serial.println(F("Error: Could not configure the IMU!")); + SerialPrintln(F("Error: Could not configure the IMU!")); } } else if ((incoming == 11) && (online.IMU == true) && (settings.imuGyroDLPF == true)) { - Serial.println(F("Enter Gyro DLPF Bandwidth (0 to 7): ")); - Serial.println(F("0: 196.6 (3dB) 229.8 (Nyquist) (Hz)")); - Serial.println(F("1: 151.8 (3dB) 187.6 (Nyquist) (Hz)")); - Serial.println(F("2: 119.5 (3dB) 154.3 (Nyquist) (Hz)")); - Serial.println(F("3: 51.2 (3dB) 73.3 (Nyquist) (Hz)")); - Serial.println(F("4: 23.9 (3dB) 35.9 (Nyquist) (Hz)")); - Serial.println(F("5: 11.6 (3dB) 17.8 (Nyquist) (Hz)")); - Serial.println(F("6: 5.7 (3dB) 8.9 (Nyquist) (Hz)")); - Serial.println(F("7: 361.4 (3dB) 376.5 (Nyquist) (Hz)")); + 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) - Serial.println(F("Error: Out of range")); + SerialPrintln(F("Error: Out of range")); else { settings.imuGyroDLPFBW = gfbw; @@ -287,7 +287,7 @@ void menuIMU() ICM_20948_Status_e retval = myICM.setDLPFcfg((ICM_20948_Internal_Acc | ICM_20948_Internal_Gyr), dlpcfg); if (retval != ICM_20948_Stat_Ok) { - Serial.println(F("Error: Could not configure the IMU!")); + SerialPrintln(F("Error: Could not configure the IMU!")); } } } diff --git a/Firmware/OpenLog_Artemis/menuMain.ino b/Firmware/OpenLog_Artemis/menuMain.ino index bee9a82..6a3faa8 100644 --- a/Firmware/OpenLog_Artemis/menuMain.ino +++ b/Firmware/OpenLog_Artemis/menuMain.ino @@ -4,37 +4,38 @@ void menuMain() { while (1) { - Serial.println(); - Serial.println(F("Menu: Main Menu")); + SerialPrintln(F("")); + SerialPrintln(F("Menu: Main Menu")); - Serial.println(F("1) Configure Terminal Output")); + SerialPrintln(F("1) Configure Terminal Output")); - Serial.println(F("2) Configure Time Stamp")); + SerialPrintln(F("2) Configure Time Stamp")); - Serial.println(F("3) Configure IMU Logging")); + SerialPrintln(F("3) Configure IMU Logging")); - Serial.println(F("4) Configure Serial Logging")); + if (settings.useTxRxPinsForTerminal == false) + SerialPrintln(F("4) Configure Serial Logging")); - Serial.println(F("5) Configure Analog Logging")); + SerialPrintln(F("5) Configure Analog Logging")); - Serial.println(F("6) Detect / Configure Attached Devices")); + SerialPrintln(F("6) Detect / Configure Attached Devices")); - Serial.println(F("7) Configure Power Options")); + SerialPrintln(F("7) Configure Power Options")); - Serial.println(F("h) Print Sensor Helper Text (and return to logging)")); + SerialPrintln(F("h) Print Sensor Helper Text (and return to logging)")); if (settings.enableSD && online.microSD) - Serial.println(F("s) SD Card File Transfer")); + SerialPrintln(F("s) SD Card File Transfer")); - Serial.println(F("r) Reset all settings to default")); + SerialPrintln(F("r) Reset all settings to default")); - Serial.println(F("q) Quit: Close log files and power down")); + SerialPrintln(F("q) Quit: Close log files and power down")); - //Serial.println(F("d) Debug Menu")); + //SerialPrintln(F("d) Debug Menu")); - Serial.println(F("x) Return to logging")); + SerialPrintln(F("x) Return to logging")); - byte incoming = getByteChoice(menuTimeout); //Timeout after x seconds + byte incoming = getByteChoice(menuTimeout, true); //Get byte choice and set DSERIAL & ZSERIAL if (incoming == '1') menuLogRate(); @@ -42,7 +43,7 @@ void menuMain() menuTimeStamp(); else if (incoming == '3') menuIMU(); - else if (incoming == '4') + else if ((incoming == '4') && (settings.useTxRxPinsForTerminal == false)) menuSerialLogging(); else if (incoming == '5') menuAnalogLogging(); @@ -75,11 +76,11 @@ void menuMain() serialDataFile.close(); } - Serial.println(); - Serial.println(); + SerialPrintln(F("")); + SerialPrintln(F("")); sdCardMenu(); // Located in zmodem.ino - Serial.println(); - Serial.println(); + SerialPrintln(F("")); + SerialPrintln(F("")); if (online.dataLogging == true) { @@ -96,7 +97,7 @@ void menuMain() } else if (incoming == 'r') { - Serial.println(F("\r\nResetting to factory defaults. Press 'y' to confirm:")); + SerialPrintln(F("\r\nResetting to factory defaults. Press 'y' to confirm:")); byte bContinue = getByteChoice(menuTimeout); if (bContinue == 'y') { @@ -106,17 +107,19 @@ void menuMain() if (sd.exists("OLA_deviceSettings.txt")) sd.remove("OLA_deviceSettings.txt"); - Serial.print(F("Settings erased. Please reset OpenLog Artemis and open a terminal at ")); + SerialPrint(F("Settings erased. Please reset OpenLog Artemis and open a terminal at ")); Serial.print((String)settings.serialTerminalBaudRate); - Serial.println(F("bps...")); + if (settings.useTxRxPinsForTerminal == true) + SerialLog.print((String)settings.serialTerminalBaudRate); + SerialPrintln(F("bps...")); while (1); } else - Serial.println(F("Reset aborted")); + SerialPrintln(F("Reset aborted")); } else if (incoming == 'q') { - Serial.println(F("\r\nQuit? Press 'y' to confirm:")); + SerialPrintln(F("\r\nQuit? Press 'y' to confirm:")); byte bContinue = getByteChoice(menuTimeout); if (bContinue == 'y') { @@ -133,14 +136,16 @@ void menuMain() updateDataFileAccess(&serialDataFile); // Update the file access time & date serialDataFile.close(); } - Serial.print(F("Log files are closed. Please reset OpenLog Artemis and open a terminal at ")); + SerialPrint(F("Log files are closed. Please reset OpenLog Artemis and open a terminal at ")); Serial.print((String)settings.serialTerminalBaudRate); - Serial.println(F("bps...")); + if (settings.useTxRxPinsForTerminal == true) + SerialLog.print((String)settings.serialTerminalBaudRate); + SerialPrintln(F("bps...")); delay(sdPowerDownDelay); // Give the SD card time to shut down powerDown(); } else - Serial.println(F("Quit aborted")); + SerialPrintln(F("Quit aborted")); } else if (incoming == 'x') break; @@ -158,6 +163,9 @@ void menuMain() while (Serial.available()) Serial.read(); //Empty buffer of any newline chars + if (settings.useTxRxPinsForTerminal == true) + while (SerialLog.available()) SerialLog.read(); //Empty buffer of any newline chars + //Reset measurements measurementCount = 0; totalCharactersPrinted = 0; diff --git a/Firmware/OpenLog_Artemis/menuPower.ino b/Firmware/OpenLog_Artemis/menuPower.ino index 870895f..f3721da 100644 --- a/Firmware/OpenLog_Artemis/menuPower.ino +++ b/Firmware/OpenLog_Artemis/menuPower.ino @@ -2,34 +2,34 @@ void menuPower() { while (1) { - Serial.println(); - Serial.println(F("Menu: Configure Power Options")); + SerialPrintln(F("")); + SerialPrintln(F("Menu: Configure Power Options")); - Serial.print(F("1) Turn off Qwiic bus power between readings (>2s): ")); - if (settings.powerDownQwiicBusBetweenReads == true) Serial.println(F("Yes")); - else Serial.println(F("No")); + SerialPrint(F("1) Turn off Qwiic bus power between readings (>2s): ")); + if (settings.powerDownQwiicBusBetweenReads == true) SerialPrintln(F("Yes")); + else SerialPrintln(F("No")); - Serial.print(F("2) Use pin 32 to Stop Logging: ")); - if (settings.useGPIO32ForStopLogging == true) Serial.println(F("Yes")); - else Serial.println(F("No")); + SerialPrint(F("2) Use pin 32 to Stop Logging: ")); + if (settings.useGPIO32ForStopLogging == true) SerialPrintln(F("Yes")); + else SerialPrintln(F("No")); #if(HARDWARE_VERSION_MAJOR >= 1) - Serial.print(F("3) Power LED During Sleep: ")); - if (settings.enablePwrLedDuringSleep == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("3) Power LED During Sleep: ")); + if (settings.enablePwrLedDuringSleep == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("4) Low Battery Voltage Detection: ")); - if (settings.enableLowBatteryDetection == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("4) Low Battery Voltage Detection: ")); + if (settings.enableLowBatteryDetection == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("5) Low Battery Threshold (V): ")); - Serial.printf("%.2f\r\n", settings.lowBatteryThreshold); + SerialPrint(F("5) Low Battery Threshold (V): ")); + SerialPrintf2("%.2f\r\n", settings.lowBatteryThreshold); - Serial.print(F("6) VIN measurement correction factor: ")); - Serial.printf("%.3f\r\n", settings.vinCorrectionFactor); + SerialPrint(F("6) VIN measurement correction factor: ")); + SerialPrintf2("%.3f\r\n", settings.vinCorrectionFactor); #endif - Serial.println(F("x) Exit")); + SerialPrintln(F("x) Exit")); byte incoming = getByteChoice(menuTimeout); //Timeout after x seconds @@ -69,22 +69,22 @@ void menuPower() } else if (incoming == '5') { - Serial.println(F("Please enter the new low battery threshold:")); + SerialPrintln(F("Please enter the new low battery threshold:")); float tempBT = (float)getDouble(menuTimeout); //Timeout after x seconds if ((tempBT < 3.0) || (tempBT > 6.0)) - Serial.println(F("Error: Threshold out of range")); + SerialPrintln(F("Error: Threshold out of range")); else settings.lowBatteryThreshold = tempBT; } else if (incoming == '6') { - Serial.println(F("Please measure the voltage on the MEAS pin and enter it here:")); + SerialPrintln(F("Please measure the voltage on the MEAS pin and enter it here:")); float tempCF = (float)getDouble(menuTimeout); //Timeout after x seconds 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) tempCF = tempCF / vin; //Calculate the new correction factor if ((tempCF < 1.0) || (tempCF > 2.0)) - Serial.println(F("Error: Correction factor out of range")); + SerialPrintln(F("Error: Correction factor out of range")); else settings.vinCorrectionFactor = tempCF; } diff --git a/Firmware/OpenLog_Artemis/menuSerialLogging.ino b/Firmware/OpenLog_Artemis/menuSerialLogging.ino index af80f14..1d01bfc 100644 --- a/Firmware/OpenLog_Artemis/menuSerialLogging.ino +++ b/Firmware/OpenLog_Artemis/menuSerialLogging.ino @@ -4,29 +4,33 @@ void menuSerialLogging() { while (1) { - Serial.println(); - Serial.println(F("Menu: Configure Serial Logging")); + SerialPrintln(F("")); + SerialPrintln(F("Menu: Configure Serial Logging")); - Serial.print(F("1) Log serial data: ")); - if (settings.logSerial == true) Serial.println(F("Enabled, analog logging on RX/A13 pin disabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("1) Log serial data: ")); + if (settings.logSerial == true) SerialPrintln(F("Enabled, analog logging on RX/A13 pin disabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("2) Output serial data to TX pin: ")); - if (settings.outputSerial == true) Serial.println(F("Enabled, analog logging on TX/A12 pin disabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("2) Output serial data to TX pin: ")); + if (settings.outputSerial == true) SerialPrintln(F("Enabled, analog logging on TX/A12 pin disabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("3) zmodem start delay: ")); + SerialPrint(F("3) zmodem start delay: ")); Serial.print(settings.zmodemStartDelay); - Serial.println(F(" seconds")); + if (settings.useTxRxPinsForTerminal == true) + SerialLog.print(settings.zmodemStartDelay); + SerialPrintln(F(" seconds")); if ((settings.logSerial == true) || (settings.outputSerial == true)) { - Serial.print(F("4) Set serial baud rate: ")); + SerialPrint(F("4) Set serial baud rate: ")); Serial.print(settings.serialLogBaudRate); - Serial.println(F(" bps")); + if (settings.useTxRxPinsForTerminal == true) + SerialLog.print(settings.zmodemStartDelay); + SerialPrintln(F(" bps")); } - Serial.println(F("x) Exit")); + SerialPrintln(F("x) Exit")); byte incoming = getByteChoice(menuTimeout); //Timeout after x seconds @@ -69,11 +73,11 @@ void menuSerialLogging() } else if (incoming == '3') { - Serial.print(F("Enter zmodem start delay (5 to 60): ")); + SerialPrint(F("Enter zmodem start delay (5 to 60): ")); int newDelay = getNumber(menuTimeout); //Timeout after x seconds if (newDelay < 5 || newDelay > 60) { - Serial.println(F("Error: start delay out of range")); + SerialPrintln(F("Error: start delay out of range")); } else { @@ -84,11 +88,11 @@ void menuSerialLogging() { if (incoming == '4') { - Serial.print(F("Enter baud rate (1200 to 500000): ")); + SerialPrint(F("Enter baud rate (1200 to 500000): ")); int newBaud = getNumber(menuTimeout); //Timeout after x seconds if (newBaud < 1200 || newBaud > 500000) { - Serial.println(F("Error: baud rate out of range")); + SerialPrintln(F("Error: baud rate out of range")); } else { diff --git a/Firmware/OpenLog_Artemis/menuTerminal.ino b/Firmware/OpenLog_Artemis/menuTerminal.ino index 6b9775d..19668a4 100644 --- a/Firmware/OpenLog_Artemis/menuTerminal.ino +++ b/Firmware/OpenLog_Artemis/menuTerminal.ino @@ -2,92 +2,103 @@ void menuLogRate() { while (1) { - Serial.println(); - Serial.println(F("Menu: Configure Terminal Output")); + SerialPrintln(F("")); + SerialPrintln(F("Menu: Configure Terminal Output")); - Serial.print(F("1) Log to microSD: ")); - if (settings.logData == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("1) Log to microSD: ")); + if (settings.logData == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("2) Log to Terminal: ")); - if (settings.enableTerminalOutput == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("2) Log to Terminal: ")); + if (settings.enableTerminalOutput == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("3) Set Serial Baud Rate: ")); + SerialPrint(F("3) Set Serial Terminal Baud Rate: ")); Serial.print(settings.serialTerminalBaudRate); - Serial.println(F(" bps")); + if (settings.useTxRxPinsForTerminal == true) + SerialLog.print(settings.serialTerminalBaudRate); + SerialPrintln(F(" bps")); if (settings.useGPIO11ForTrigger == false) { - Serial.print(F("4) Set Log Rate in Hz: ")); - if (settings.logMaxRate == true) Serial.println(F("Max rate enabled")); + SerialPrint(F("4) Set Log Rate in Hz: ")); + if (settings.logMaxRate == true) SerialPrintln(F("Max rate enabled")); else { if (settings.usBetweenReadings < 1000000ULL) //Take more than one measurement per second { //Display Integer Hertz int logRate = (int)(1000000ULL / settings.usBetweenReadings); - Serial.printf("%d\r\n", logRate); + SerialPrintf2("%d\r\n", logRate); } else { //Display fractional Hertz uint32_t logRateSeconds = (uint32_t)(settings.usBetweenReadings / 1000000ULL); - Serial.printf("%.06lf\r\n", 1.0 / logRateSeconds); + SerialPrintf2("%.06lf\r\n", 1.0 / logRateSeconds); } } - Serial.print(F("5) Set Log Rate in seconds between readings: ")); - if (settings.logMaxRate == true) Serial.println(F("Max rate enabled")); + SerialPrint(F("5) Set Log Rate in seconds between readings: ")); + if (settings.logMaxRate == true) SerialPrintln(F("Max rate enabled")); else { if (settings.usBetweenReadings > 1000000ULL) //Take more than one measurement per second { uint32_t interval = (uint32_t)(settings.usBetweenReadings / 1000000ULL); - Serial.printf("%d\r\n", interval); + SerialPrintf2("%d\r\n", interval); } else { float rate = (float)(settings.usBetweenReadings / 1000000.0); - Serial.printf("%.06f\r\n", rate); + SerialPrintf2("%.06f\r\n", rate); } } - Serial.print(F("6) Enable maximum logging: ")); - if (settings.logMaxRate == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("6) Enable maximum logging: ")); + if (settings.logMaxRate == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); } - Serial.print(F("7) Output Actual Hertz: ")); - if (settings.logHertz == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("7) Output Actual Hertz: ")); + if (settings.logHertz == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("8) Output Column Titles: ")); - if (settings.showHelperText == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("8) Output Column Titles: ")); + if (settings.showHelperText == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("9) Output Measurement Count: ")); - if (settings.printMeasurementCount == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("9) Output Measurement Count: ")); + if (settings.printMeasurementCount == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("10) Open New Log Files After (s): ")); - Serial.printf("%d", settings.openNewLogFilesAfter); - if (settings.openNewLogFilesAfter == 0) Serial.println(F(" (Never)")); - else Serial.println(); + SerialPrint(F("10) Open New Log Files After (s): ")); + SerialPrintf2("%d", settings.openNewLogFilesAfter); + if (settings.openNewLogFilesAfter == 0) SerialPrintln(F(" (Never)")); + else SerialPrintln(F("")); - Serial.print(F("11) Frequent log file access timestamps: ")); - if (settings.frequentFileAccessTimestamps == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("11) Frequent log file access timestamps: ")); + if (settings.frequentFileAccessTimestamps == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("12) Use pin 11 to trigger logging: ")); - if (settings.useGPIO11ForTrigger == true) Serial.println(F("Yes")); - else Serial.println(F("No")); + SerialPrint(F("12) Use pin 11 to trigger logging: ")); + if (settings.useGPIO11ForTrigger == true) SerialPrintln(F("Yes")); + else SerialPrintln(F("No")); - Serial.print(F("13) Logging is triggered when the signal on pin 11 is: ")); - if (settings.fallingEdgeTrigger == true) Serial.println(F("Falling")); - else Serial.println(F("Rising")); + SerialPrint(F("13) Logging is triggered when the signal on pin 11 is: ")); + if (settings.fallingEdgeTrigger == true) SerialPrintln(F("Falling")); + else SerialPrintln(F("Rising")); - Serial.println(F("x) Exit")); + SerialPrint(F("14) Use TX and RX pins for Terminal: ")); + if (settings.useTxRxPinsForTerminal == true) + { + SerialPrintln(F("Enabled")); + SerialPrintln(F(" Analog logging on TX/A12 and RX/A13 is permanently disabled")); + SerialPrintln(F(" Serial logging on RX/A13 is permanently disabled")); + } + else SerialPrintln(F("Disabled")); + + SerialPrintln(F("x) Exit")); int incoming = getNumber(menuTimeout); //Timeout after x seconds @@ -97,18 +108,18 @@ void menuLogRate() settings.enableTerminalOutput ^= 1; else if (incoming == 3) { - Serial.print(F("Enter baud rate (1200 to 500000): ")); + SerialPrint(F("Enter baud rate (1200 to 500000): ")); int newBaud = getNumber(menuTimeout); //Timeout after x seconds if (newBaud < 1200 || newBaud > 500000) { - Serial.println(F("Error: baud rate out of range")); + SerialPrintln(F("Error: baud rate out of range")); } else { settings.serialTerminalBaudRate = newBaud; recordSystemSettings(); //Normally recorded upon all menu exits recordDeviceSettingsToFile(); //Normally recorded upon all menu exits - Serial.printf("Terminal now set at %dbps. Please reset device and open terminal at new baud rate. Freezing...\r\n", settings.serialTerminalBaudRate); + SerialPrintf2("Terminal now set at %dbps. Please reset device and open terminal at new baud rate. Freezing...\r\n", settings.serialTerminalBaudRate); while (1); } } @@ -121,10 +132,10 @@ void menuLogRate() if (maxOutputRate < 10) maxOutputRate = 10; //TODO this is forced. Needed when multi seconds between readings. - Serial.printf("How many readings per second would you like to log? (Current max is %d): ", maxOutputRate); + SerialPrintf2("How many readings per second would you like to log? (Current max is %d): ", maxOutputRate); int tempRPS = getNumber(menuTimeout); //Timeout after x seconds if (tempRPS < 1 || tempRPS > maxOutputRate) - Serial.println(F("Error: Readings Per Second out of range")); + SerialPrintln(F("Error: Readings Per Second out of range")); else settings.usBetweenReadings = 1000000ULL / ((uint64_t)tempRPS); } @@ -136,10 +147,10 @@ void menuLogRate() //The Deep Sleep duration is set with am_hal_stimer_compare_delta_set, the duration of which is uint32_t //So the maximum we can sleep for is 2^32 / 32768 = 131072 seconds = 36.4 hours //Let's limit this to 36 hours = 129600 seconds - Serial.println(F("How many seconds would you like to sleep between readings? (1 to 129,600):")); + SerialPrintln(F("How many seconds would you like to sleep between readings? (1 to 129,600):")); int64_t tempSeconds = getNumber(menuTimeout); //Timeout after x seconds if (tempSeconds < 1 || tempSeconds > 129600) - Serial.println(F("Error: Readings Per Second out of range")); + SerialPrintln(F("Error: Readings Per Second out of range")); else settings.usBetweenReadings = 1000000ULL * ((uint64_t)tempSeconds); } @@ -150,7 +161,7 @@ void menuLogRate() { if (settings.logMaxRate == false) { - Serial.println(F("\r\nEnabling max log rate will disable the IMU, \r\nterminal output, and serial logging. \r\nOnly analog values will be logged. Continue?")); + SerialPrintln(F("\r\nEnabling max log rate will disable the IMU, \r\nterminal output, and serial logging. \r\nOnly analog values will be logged. Continue?")); byte bContinue = getByteChoice(menuTimeout); if (bContinue == 'y') { @@ -168,7 +179,7 @@ void menuLogRate() recordSystemSettings(); //Normally recorded upon all menu exits recordDeviceSettingsToFile(); //Normally recorded upon all menu exits - Serial.println(F("OpenLog Artemis configured for max data rate. Please reset. Freezing...")); + SerialPrintln(F("OpenLog Artemis configured for max data rate. Please reset. Freezing...")); while (1); } } @@ -187,10 +198,10 @@ void menuLogRate() settings.printMeasurementCount ^= 1; else if (incoming == 10) { - Serial.println(F("Open new log files after this many seconds (0 or 10 to 129,600) (0 = Never):")); + SerialPrintln(F("Open new log files after this many seconds (0 or 10 to 129,600) (0 = Never):")); int64_t tempSeconds = getNumber(menuTimeout); //Timeout after x seconds if ((tempSeconds < 0) || ((tempSeconds > 0) && (tempSeconds < 10)) || (tempSeconds > 129600ULL)) - Serial.println(F("Error: Invalid interval")); + SerialPrintln(F("Error: Invalid interval")); else settings.openNewLogFilesAfter = tempSeconds; } @@ -236,6 +247,45 @@ void menuLogRate() else settings.fallingEdgeTrigger ^= 1; // Interrupt is not currently enabled so simply invert the flag } + else if (incoming == 14) + { + if (settings.useTxRxPinsForTerminal == false) + { + SerialPrintln(F("")); + SerialPrintln(F("WARNING:")); + SerialPrintln(F("\"Use TX and RX pins for terminal\" can only be disabled by \"Reset all settings to default\".")); + SerialPrintln(F("Analog logging on TX/A12 and RX/A13 will be disabled.")); + SerialPrintln(F("Serial logging will be disabled.")); + SerialPrintln(F("Are you sure? Press 'y' to confirm:")); + byte bContinue = getByteChoice(menuTimeout); + if (bContinue == 'y') + { + settings.useTxRxPinsForTerminal = true; + if (online.serialLogging == true) + { + serialDataFile.sync(); + updateDataFileAccess(&serialDataFile); // Update the file access time & date + serialDataFile.close(); + } + online.serialLogging = false; + settings.logSerial = false; + settings.outputSerial = false; + online.serialOutput = false; + settings.logA12 = false; + settings.logA13 = false; + SerialLog.begin(settings.serialTerminalBaudRate); // (Re)Start the serial port + } + else + SerialPrintln(F("\"Use TX and RX pins for terminal\" aborted")); + } + else + { + SerialPrintln(F("")); + SerialPrintln(F("\"Use TX and RX pins for terminal\" can not be disabled.")); + SerialPrintln(F("You need to use \"Reset all settings to default\" from the main menu.")); + SerialPrintln(F("")); + } + } else if (incoming == STATUS_PRESSED_X) return; else if (incoming == STATUS_GETNUMBER_TIMEOUT) diff --git a/Firmware/OpenLog_Artemis/menuTimeStamp.ino b/Firmware/OpenLog_Artemis/menuTimeStamp.ino index 14c58d0..6082b69 100644 --- a/Firmware/OpenLog_Artemis/menuTimeStamp.ino +++ b/Firmware/OpenLog_Artemis/menuTimeStamp.ino @@ -2,9 +2,9 @@ void menuTimeStamp() { while (1) { - Serial.println(); - Serial.println(F("Menu: Configure Time Stamp")); - Serial.print(F("Current date/time: ")); + SerialPrintln(F("")); + SerialPrintln(F("Menu: Configure Time Stamp")); + SerialPrint(F("Current date/time: ")); myRTC.getTime(); char rtcDate[11]; //10/12/2019 @@ -13,8 +13,8 @@ void menuTimeStamp() else sprintf(rtcDate, "%02d/%02d/20%02d", myRTC.dayOfMonth, myRTC.month, myRTC.year); - Serial.print(rtcDate); - Serial.print(F(" ")); + SerialPrint(rtcDate); + SerialPrint(F(" ")); char rtcTime[12]; //09:14:37.41 int adjustedHour = myRTC.hour; @@ -23,55 +23,56 @@ void menuTimeStamp() if (adjustedHour > 12) adjustedHour -= 12; } sprintf(rtcTime, "%02d:%02d:%02d.%02d", adjustedHour, myRTC.minute, myRTC.seconds, myRTC.hundredths); - Serial.println(rtcTime); + SerialPrintln(rtcTime); - Serial.print(F("1) Log Date: ")); - if (settings.logDate == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("1) Log Date: ")); + if (settings.logDate == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.print(F("2) Log Time: ")); - if (settings.logTime == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("2) Log Time: ")); + if (settings.logTime == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); if (settings.logDate == true || settings.logTime == true) { - Serial.println(F("3) Set RTC to compiler macro time")); + SerialPrintln(F("3) Set RTC to compiler macro time")); } if (settings.logDate == true) { - Serial.println(F("4) Manually set RTC date")); + SerialPrintln(F("4) Manually set RTC date")); - Serial.print(F("5) Toggle date style: ")); - if (settings.americanDateStyle == true) Serial.println(F("mm/dd/yyyy")); - else Serial.println(F("dd/mm/yyyy")); + SerialPrint(F("5) Toggle date style: ")); + if (settings.americanDateStyle == true) SerialPrintln(F("mm/dd/yyyy")); + else SerialPrintln(F("dd/mm/yyyy")); } if (settings.logTime == true) { - Serial.println(F("6) Manually set RTC time")); + SerialPrintln(F("6) Manually set RTC time")); - Serial.print(F("7) Toggle time style: ")); - if (settings.hour24Style == true) Serial.println(F("24 hour")); - else Serial.println(F("12 hour")); + SerialPrint(F("7) Toggle time style: ")); + if (settings.hour24Style == true) SerialPrintln(F("24 hour")); + else SerialPrintln(F("12 hour")); } if (settings.logDate == true || settings.logTime == true) { if (isUbloxAttached() == true) { - Serial.println(F("8) Synchronize RTC to GPS")); + SerialPrintln(F("8) Synchronize RTC to GPS")); } - Serial.print(F("9) Local offset from UTC: ")); + SerialPrint(F("9) Local offset from UTC: ")); Serial.println(settings.localUTCOffset); - + if (settings.useTxRxPinsForTerminal == true) + SerialLog.println(settings.localUTCOffset); } - Serial.print(F("10) Log Microseconds: ")); - if (settings.logMicroseconds == true) Serial.println(F("Enabled")); - else Serial.println(F("Disabled")); + SerialPrint(F("10) Log Microseconds: ")); + if (settings.logMicroseconds == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); - Serial.println(F("x) Exit")); + SerialPrintln(F("x) Exit")); int incoming = getNumber(menuTimeout); //Timeout after x seconds @@ -92,7 +93,7 @@ void menuTimeStamp() if (incoming == 3) { myRTC.setToCompilerTime(); //Set RTC using the system __DATE__ and __TIME__ macros from compiler - Serial.println(F("RTC set to compiler time")); + SerialPrintln(F("RTC set to compiler time")); } else if ((incoming == 8) && (isUbloxAttached() == true)) { @@ -102,18 +103,18 @@ void menuTimeStamp() getGPSDateTime(yy, mm, dd, h, m, s, ms, dateValid, timeValid); // Get the GPS date and time, corrected for localUTCOffset myRTC.setTime(h, m, s, (ms / 10), dd, mm, (yy - 2000)); //Manually set RTC lastSDFileNameChangeTime = rtcMillis(); // Record the time of the file name change - Serial.println(F("RTC set to GPS (UTC) time")); + SerialPrintln(F("RTC set to GPS (UTC) time")); if ((dateValid == false) || (timeValid == false)) { - Serial.println(F("\r\nWarning: the GPS time or date was not valid. Please try again.\r\n")); + SerialPrintln(F("\r\nWarning: the GPS time or date was not valid. Please try again.\r\n")); } } else if (incoming == 9) { - Serial.print(F("Enter the local hour offset from UTC (-12 to 14): ")); + SerialPrint(F("Enter the local hour offset from UTC (-12 to 14): ")); int offset = getNumber(menuTimeout); //Timeout after x seconds if (offset < -12 || offset > 14) - Serial.println(F("Error: Offset is out of range")); + SerialPrintln(F("Error: Offset is out of range")); else settings.localUTCOffset = offset; } @@ -127,14 +128,14 @@ void menuTimeStamp() myRTC.getTime(); int dd = myRTC.dayOfMonth, mm = myRTC.month, yy = myRTC.year, h = myRTC.hour, m = myRTC.minute, s = myRTC.seconds; - Serial.print(F("Enter current two digit year: ")); + SerialPrint(F("Enter current two digit year: ")); yy = getNumber(menuTimeout); //Timeout after x seconds if (yy > 2000 && yy < 2100) yy -= 2000; - Serial.print(F("Enter current month (1 to 12): ")); + SerialPrint(F("Enter current month (1 to 12): ")); mm = getNumber(menuTimeout); //Timeout after x seconds - Serial.print(F("Enter current day (1 to 31): ")); + SerialPrint(F("Enter current day (1 to 31): ")); dd = getNumber(menuTimeout); //Timeout after x seconds myRTC.setTime(h, m, s, 0, dd, mm, yy); //Manually set RTC @@ -154,13 +155,13 @@ void menuTimeStamp() myRTC.getTime(); int dd = myRTC.dayOfMonth, mm = myRTC.month, yy = myRTC.year, h = myRTC.hour, m = myRTC.minute, s = myRTC.seconds; - Serial.print(F("Enter current hour (0 to 23): ")); + SerialPrint(F("Enter current hour (0 to 23): ")); h = getNumber(menuTimeout); //Timeout after x seconds - Serial.print(F("Enter current minute (0 to 59): ")); + SerialPrint(F("Enter current minute (0 to 59): ")); m = getNumber(menuTimeout); //Timeout after x seconds - Serial.print(F("Enter current second (0 to 59): ")); + SerialPrint(F("Enter current second (0 to 59): ")); s = getNumber(menuTimeout); //Timeout after x seconds myRTC.setTime(h, m, s, 0, dd, mm, yy); //Manually set RTC diff --git a/Firmware/OpenLog_Artemis/nvm.ino b/Firmware/OpenLog_Artemis/nvm.ino index 7226341..a0e8b29 100644 --- a/Firmware/OpenLog_Artemis/nvm.ino +++ b/Firmware/OpenLog_Artemis/nvm.ino @@ -8,7 +8,7 @@ void loadSettings() uint32_t testRead = 0; if (EEPROM.get(0, testRead) == 0xFFFFFFFF) { - Serial.println(F("EEPROM is blank. Default settings applied")); + SerialPrintln(F("EEPROM is blank. Default settings applied")); recordSystemSettings(); //Record default settings to EEPROM and config file. At power on, settings are in default state } @@ -18,7 +18,7 @@ void loadSettings() EEPROM.get(0, tempSize); //Load the sizeOfSettings if (tempSize != sizeof(settings)) { - Serial.println(F("Settings wrong size. Default settings applied")); + SerialPrintln(F("Settings wrong size. Default settings applied")); recordSystemSettings(); //Record default settings to EEPROM and config file. At power on, settings are in default state } @@ -28,7 +28,7 @@ void loadSettings() EEPROM.get(sizeof(int), tempIdentifier); //Load the identifier from the EEPROM location after sizeOfSettings (int) if (tempIdentifier != OLA_IDENTIFIER) { - Serial.println(F("Settings are not valid for this variant of the OLA. Default settings applied")); + SerialPrintln(F("Settings are not valid for this variant of the OLA. Default settings applied")); recordSystemSettings(); //Record default settings to EEPROM and config file. At power on, settings are in default state } @@ -60,7 +60,7 @@ void recordSystemSettingsToFile() SdFile settingsFile; //FAT32 if (settingsFile.open("OLA_settings.txt", O_CREAT | O_APPEND | O_WRITE) == false) { - Serial.println(F("Failed to create settings file")); + SerialPrintln(F("Failed to create settings file")); return; } @@ -155,6 +155,7 @@ void recordSystemSettingsToFile() settingsFile.println("imuGyroFSS=" + (String)settings.imuGyroFSS); settingsFile.println("imuGyroDLPFBW=" + (String)settings.imuGyroDLPFBW); settingsFile.println("logMicroseconds=" + (String)settings.logMicroseconds); + settingsFile.println("useTxRxPinsForTerminal=" + (String)settings.useTxRxPinsForTerminal); updateDataFileAccess(&settingsFile); // Update the file access time & date settingsFile.close(); } @@ -173,7 +174,7 @@ bool loadSystemSettingsFromFile() SdFile settingsFile; //FAT32 if (settingsFile.open("OLA_settings.txt", O_READ) == false) { - Serial.println(F("Failed to open settings file")); + SerialPrintln(F("Failed to open settings file")); return (false); } @@ -183,23 +184,23 @@ bool loadSystemSettingsFromFile() while (settingsFile.available()) { int n = settingsFile.fgets(line, sizeof(line)); if (n <= 0) { - Serial.printf("Failed to read line %d from settings file\r\n", lineNumber); + SerialPrintf2("Failed to read line %d from settings file\r\n", lineNumber); } else if (line[n - 1] != '\n' && n == (sizeof(line) - 1)) { - Serial.printf("Settings line %d too long\r\n", lineNumber); + SerialPrintf2("Settings line %d too long\r\n", lineNumber); if (lineNumber == 0) { //If we can't read the first line of the settings file, give up - Serial.println(F("Giving up on settings file")); + SerialPrintln(F("Giving up on settings file")); return (false); } } else if (parseLine(line) == false) { - Serial.printf("Failed to parse line %d: %s\r\n", lineNumber, line); + SerialPrintf3("Failed to parse line %d: %s\r\n", lineNumber, line); if (lineNumber == 0) { //If we can't read the first line of the settings file, give up - Serial.println(F("Giving up on settings file")); + SerialPrintln(F("Giving up on settings file")); return (false); } } @@ -207,19 +208,19 @@ bool loadSystemSettingsFromFile() lineNumber++; } - //Serial.println(F("Config file read complete")); + //SerialPrintln(F("Config file read complete")); settingsFile.close(); return (true); } else { - Serial.println(F("No config file found. Using settings from EEPROM.")); + SerialPrintln(F("No config file found. Using settings from EEPROM.")); //The defaults of the struct will be recorded to a file later on. return (false); } } - Serial.println(F("Config file read failed: SD offline")); + SerialPrintln(F("Config file read failed: SD offline")); return (false); //SD offline } @@ -235,8 +236,8 @@ bool parseLine(char* str) { char* ptr; //Debug - //Serial.printf("Line contents: %s", str); - //Serial.flush(); + //SerialPrintf2("Line contents: %s", str); + //SerialFlush(); // Set strtok start of line. str = strtok(str, "="); @@ -250,15 +251,15 @@ bool parseLine(char* str) { str = strtok(nullptr, "\n"); if (!str) return false; - //Serial.printf("s = %s\r\n", str); - //Serial.flush(); + //SerialPrintf2("s = %s\r\n", str); + //SerialFlush(); // Convert string to double. double d = strtod(str, &ptr); if (str == ptr || *skipSpace(ptr)) return false; - //Serial.printf("d = %lf\r\n", d); - //Serial.flush(); + //SerialPrintf2("d = %lf\r\n", d); + //SerialFlush(); // Get setting name if (strcmp(settingName, "sizeOfSettings") == 0) @@ -269,13 +270,13 @@ bool parseLine(char* str) { { EEPROM.erase(); sd.remove("OLA_settings.txt"); - Serial.println(F("OpenLog Artemis has been factory reset. Freezing. Please restart and open terminal at 115200bps.")); + SerialPrintln(F("OpenLog Artemis has been factory reset. Freezing. Please restart and open terminal at 115200bps.")); while (1); } //Check to see if this setting file is compatible with this version of OLA if (d != sizeof(settings)) - Serial.printf("Warning: Settings size is %d but current firmware expects %d. Attempting to use settings from file.\r\n", d, sizeof(settings)); + SerialPrintf3("Warning: Settings size is %d but current firmware expects %d. Attempting to use settings from file.\r\n", d, sizeof(settings)); } else if (strcmp(settingName, "olaIdentifier") == 0) @@ -397,8 +398,10 @@ bool parseLine(char* str) { settings.imuGyroDLPFBW = d; else if (strcmp(settingName, "logMicroseconds") == 0) settings.logMicroseconds = d; + else if (strcmp(settingName, "useTxRxPinsForTerminal") == 0) + settings.useTxRxPinsForTerminal = d; else - Serial.printf("Unknown setting %s on line: %s\r\n", settingName, str); + SerialPrintf3("Unknown setting %s on line: %s\r\n", settingName, str); return (true); } @@ -414,7 +417,7 @@ void recordDeviceSettingsToFile() SdFile settingsFile; //FAT32 if (settingsFile.open("OLA_deviceSettings.txt", O_CREAT | O_APPEND | O_WRITE) == false) { - Serial.println(F("Failed to create device settings file")); + SerialPrintln(F("Failed to create device settings file")); return; } @@ -650,7 +653,7 @@ void recordDeviceSettingsToFile() } break; default: - Serial.printf("recordSettingsToFile Unknown device: %s\r\n", base); + SerialPrintf2("recordSettingsToFile Unknown device: %s\r\n", base); //settingsFile.println((String)base + "=UnknownDeviceSettings"); break; } @@ -674,7 +677,7 @@ bool loadDeviceSettingsFromFile() SdFile settingsFile; //FAT32 if (settingsFile.open("OLA_deviceSettings.txt", O_READ) == false) { - Serial.println(F("Failed to open device settings file")); + SerialPrintln(F("Failed to open device settings file")); return (false); } @@ -684,32 +687,32 @@ bool loadDeviceSettingsFromFile() while (settingsFile.available()) { int n = settingsFile.fgets(line, sizeof(line)); if (n <= 0) { - Serial.printf("Failed to read line %d from settings file\r\n", lineNumber); + SerialPrintf2("Failed to read line %d from settings file\r\n", lineNumber); } else if (line[n - 1] != '\n' && n == (sizeof(line) - 1)) { - Serial.printf("Settings line %d too long\n", lineNumber); + SerialPrintf2("Settings line %d too long\n", lineNumber); } else if (parseDeviceLine(line) == false) { - Serial.printf("Failed to parse line %d: %s\r\n", lineNumber + 1, line); + SerialPrintf3("Failed to parse line %d: %s\r\n", lineNumber + 1, line); } lineNumber++; } - //Serial.println(F("Device config file read complete")); + //SerialPrintln(F("Device config file read complete")); updateDataFileAccess(&settingsFile); // Update the file access time & date settingsFile.close(); return (true); } else { - Serial.println(F("No device config file found. Creating one with device defaults.")); + SerialPrintln(F("No device config file found. Creating one with device defaults.")); recordDeviceSettingsToFile(); //Record the current settings to create the initial file return (false); } } - Serial.println(F("Device config file read failed: SD offline")); + SerialPrintln(F("Device config file read failed: SD offline")); return (false); //SD offline } @@ -719,8 +722,8 @@ bool parseDeviceLine(char* str) { char* ptr; //Debug - //Serial.printf("Line contents: %s", str); - //Serial.flush(); + //SerialPrintf2("Line contents: %s", str); + //SerialFlush(); // Set strtok start of line. str = strtok(str, "="); @@ -734,15 +737,15 @@ bool parseDeviceLine(char* str) { str = strtok(nullptr, "\n"); if (!str) return false; - //Serial.printf("s = %s\r\n", str); - //Serial.flush(); + //SerialPrintf2("s = %s\r\n", str); + //SerialFlush(); // Convert string to double. double d = strtod(str, &ptr); if (str == ptr || *skipSpace(ptr)) return false; - //Serial.printf("d = %lf\r\n", d); - //Serial.flush(); + //SerialPrintf2("d = %lf\r\n", d); + //SerialFlush(); //Break device setting into its constituent parts char deviceSettingName[50]; @@ -772,19 +775,19 @@ bool parseDeviceLine(char* str) { if (count < 5) { - Serial.printf("Incomplete setting: %s\r\n", settingName); + SerialPrintf2("Incomplete setting: %s\r\n", settingName); return false; } - //Serial.printf("%d: %d.%d.%d - %s\r\n", deviceType, address, muxAddress, portNumber, deviceSettingName); - //Serial.flush(); + //SerialPrintf6("%d: %d.%d.%d - %s\r\n", deviceType, address, muxAddress, portNumber, deviceSettingName); + //SerialFlush(); //Find the device in the list that has this device type and address void *deviceConfigPtr = getConfigPointer(deviceType, address, muxAddress, portNumber); if (deviceConfigPtr == NULL) { - //Serial.printf("Setting in file found but no matching device on bus is available: %s\r\n", settingName); - //Serial.flush(); + //SerialPrintf2("Setting in file found but no matching device on bus is available: %s\r\n", settingName); + //SerialFlush(); } else { @@ -792,7 +795,7 @@ bool parseDeviceLine(char* str) { { case DEVICE_MULTIPLEXER: { - Serial.println(F("There are no known settings for a multiplexer to load.")); + SerialPrintln(F("There are no known settings for a multiplexer to load.")); } break; case DEVICE_LOADCELL_NAU7802: @@ -811,7 +814,7 @@ bool parseDeviceLine(char* str) { else if (strcmp(deviceSettingName, "averageAmount") == 0) nodeSetting->averageAmount = d; else - Serial.printf("Unknown device setting: %s\r\n", deviceSettingName); + SerialPrintf2("Unknown device setting: %s\r\n", deviceSettingName); } break; case DEVICE_DISTANCE_VL53L1X: @@ -834,7 +837,7 @@ bool parseDeviceLine(char* str) { else if (strcmp(deviceSettingName, "crosstalk") == 0) nodeSetting->crosstalk = d; else - Serial.printf("Unknown device setting: %s\r\n", deviceSettingName); + SerialPrintf2("Unknown device setting: %s\r\n", deviceSettingName); } break; case DEVICE_GPS_UBLOX: @@ -873,7 +876,7 @@ bool parseDeviceLine(char* str) { else if (strcmp(deviceSettingName, "useAutoPVT") == 0) nodeSetting->useAutoPVT = d; else - Serial.printf("Unknown device setting: %s\r\n", deviceSettingName); + SerialPrintf2("Unknown device setting: %s\r\n", deviceSettingName); } break; case DEVICE_PROXIMITY_VCNL4040: @@ -896,7 +899,7 @@ bool parseDeviceLine(char* str) { else if (strcmp(deviceSettingName, "resolution") == 0) nodeSetting->resolution = d; else - Serial.printf("Unknown device setting: %s\r\n", deviceSettingName); + SerialPrintf2("Unknown device setting: %s\r\n", deviceSettingName); } break; case DEVICE_TEMPERATURE_TMP117: @@ -907,7 +910,7 @@ bool parseDeviceLine(char* str) { else if (strcmp(deviceSettingName, "logTemperature") == 0) nodeSetting->logTemperature = d; else - Serial.printf("Unknown device setting: %s\r\n", deviceSettingName); + SerialPrintf2("Unknown device setting: %s\r\n", deviceSettingName); } break; case DEVICE_PRESSURE_MS5637: @@ -920,7 +923,7 @@ bool parseDeviceLine(char* str) { else if (strcmp(deviceSettingName, "logTemperature") == 0) nodeSetting->logTemperature = d; else - Serial.printf("Unknown device setting: %s\r\n", deviceSettingName); + SerialPrintf2("Unknown device setting: %s\r\n", deviceSettingName); } break; case DEVICE_PRESSURE_LPS25HB: @@ -933,7 +936,7 @@ bool parseDeviceLine(char* str) { else if (strcmp(deviceSettingName, "logTemperature") == 0) nodeSetting->logTemperature = d; else - Serial.printf("Unknown device setting: %s\r\n", deviceSettingName); + SerialPrintf2("Unknown device setting: %s\r\n", deviceSettingName); } break; case DEVICE_PHT_BME280: @@ -950,7 +953,7 @@ bool parseDeviceLine(char* str) { else if (strcmp(deviceSettingName, "logTemperature") == 0) nodeSetting->logTemperature = d; else - Serial.printf("Unknown device setting: %s\r\n", deviceSettingName); + SerialPrintf2("Unknown device setting: %s\r\n", deviceSettingName); } break; case DEVICE_UV_VEML6075: @@ -965,7 +968,7 @@ bool parseDeviceLine(char* str) { else if (strcmp(deviceSettingName, "logUVIndex") == 0) nodeSetting->logUVIndex = d; else - Serial.printf("Unknown device setting: %s\r\n", deviceSettingName); + SerialPrintf2("Unknown device setting: %s\r\n", deviceSettingName); } break; case DEVICE_VOC_CCS811: @@ -978,7 +981,7 @@ bool parseDeviceLine(char* str) { else if (strcmp(deviceSettingName, "logCO2") == 0) nodeSetting->logCO2 = d; else - Serial.printf("Unknown device setting: %s\r\n", deviceSettingName); + SerialPrintf2("Unknown device setting: %s\r\n", deviceSettingName); } break; case DEVICE_VOC_SGP30: @@ -995,7 +998,7 @@ bool parseDeviceLine(char* str) { else if (strcmp(deviceSettingName, "logEthanol") == 0) nodeSetting->logEthanol = d; else - Serial.printf("Unknown device setting: %s\r\n", deviceSettingName); + SerialPrintf2("Unknown device setting: %s\r\n", deviceSettingName); } break; case DEVICE_CO2_SCD30: @@ -1018,7 +1021,7 @@ bool parseDeviceLine(char* str) { else if (strcmp(deviceSettingName, "temperatureOffset") == 0) nodeSetting->temperatureOffset = d; else - Serial.printf("Unknown device setting: %s\r\n", deviceSettingName); + SerialPrintf2("Unknown device setting: %s\r\n", deviceSettingName); } break; case DEVICE_PHT_MS8607: @@ -1039,7 +1042,7 @@ bool parseDeviceLine(char* str) { else if (strcmp(deviceSettingName, "humidityResolution") == 0) nodeSetting->humidityResolution = (MS8607_humidity_resolution)d; else - Serial.printf("Unknown device setting: %s\r\n", deviceSettingName); + SerialPrintf2("Unknown device setting: %s\r\n", deviceSettingName); } break; case DEVICE_TEMPERATURE_MCP9600: @@ -1052,7 +1055,7 @@ bool parseDeviceLine(char* str) { else if (strcmp(deviceSettingName, "logAmbientTemperature") == 0) nodeSetting->logAmbientTemperature = d; else - Serial.printf("Unknown device setting: %s\r\n", deviceSettingName); + SerialPrintf2("Unknown device setting: %s\r\n", deviceSettingName); } break; case DEVICE_HUMIDITY_AHT20: @@ -1065,7 +1068,7 @@ bool parseDeviceLine(char* str) { else if (strcmp(deviceSettingName, "logTemperature") == 0) nodeSetting->logTemperature = d; else - Serial.printf("Unknown device setting: %s\r\n", deviceSettingName); + SerialPrintf2("Unknown device setting: %s\r\n", deviceSettingName); } break; case DEVICE_HUMIDITY_SHTC3: @@ -1078,7 +1081,7 @@ bool parseDeviceLine(char* str) { else if (strcmp(deviceSettingName, "logTemperature") == 0) nodeSetting->logTemperature = d; else - Serial.printf("Unknown device setting: %s\r\n", deviceSettingName); + SerialPrintf2("Unknown device setting: %s\r\n", deviceSettingName); } break; case DEVICE_ADC_ADS122C04: @@ -1107,7 +1110,7 @@ bool parseDeviceLine(char* str) { else if (strcmp(deviceSettingName, "useTwoWireHighTemperatureMode") == 0) nodeSetting->useTwoWireHighTemperatureMode = d; else - Serial.printf("Unknown device setting: %s\r\n", deviceSettingName); + SerialPrintf2("Unknown device setting: %s\r\n", deviceSettingName); } break; case DEVICE_PRESSURE_MPR0025PA1: @@ -1134,7 +1137,7 @@ bool parseDeviceLine(char* str) { else if (strcmp(deviceSettingName, "useBAR") == 0) nodeSetting->useBAR = d; else - Serial.printf("Unknown device setting: %s\r\n", deviceSettingName); + SerialPrintf2("Unknown device setting: %s\r\n", deviceSettingName); } break; case DEVICE_PARTICLE_SNGCJA5: @@ -1169,12 +1172,12 @@ bool parseDeviceLine(char* str) { else if (strcmp(deviceSettingName, "logFanStatus") == 0) nodeSetting->logFanStatus = d; else - Serial.printf("Unknown device setting: %s\r\n", deviceSettingName); + SerialPrintf2("Unknown device setting: %s\r\n", deviceSettingName); } break; default: - Serial.printf("Unknown device type: %d\r\n", deviceType); - Serial.flush(); + SerialPrintf2("Unknown device type: %d\r\n", deviceType); + SerialFlush(); break; } } diff --git a/Firmware/OpenLog_Artemis/settings.h b/Firmware/OpenLog_Artemis/settings.h index 465e6ab..bd769e7 100644 --- a/Firmware/OpenLog_Artemis/settings.h +++ b/Firmware/OpenLog_Artemis/settings.h @@ -351,6 +351,7 @@ struct struct_settings { int imuGyroFSS = 0; // IMU gyro full scale - default to 250 degrees per second (ICM_20948_GYRO_CONFIG_1_FS_SEL_e) int imuGyroDLPFBW = 7; // IMU gyro DLPF bandwidth - default to gyr_d361bw4_n376bw5 (ICM_20948_GYRO_CONFIG_1_DLPCFG_e) bool logMicroseconds = false; // Log micros() + bool useTxRxPinsForTerminal = false; // If true, the terminal is echo'd to the Tx and Rx pins. Note: setting this to true will _permanently_ disable serial logging and analog input on those pins! } settings; //These are the devices on board OpenLog that may be on or offline. diff --git a/Firmware/OpenLog_Artemis/support.ino b/Firmware/OpenLog_Artemis/support.ino index 40eb7db..70f7332 100644 --- a/Firmware/OpenLog_Artemis/support.ino +++ b/Firmware/OpenLog_Artemis/support.ino @@ -2,7 +2,9 @@ void printDebug(String thingToPrint) { if(settings.printDebugMessages == true) { - Serial.print(thingToPrint); + Serial.print(thingToPrint); + if (settings.useTxRxPinsForTerminal == true) + SerialLog.print(thingToPrint); } } @@ -10,15 +12,19 @@ void printDebug(String thingToPrint) //Option not known void printUnknown(uint8_t unknownChoice) { - Serial.print(F("Unknown choice: ")); + SerialPrint(F("Unknown choice: ")); Serial.write(unknownChoice); - Serial.println(); + if (settings.useTxRxPinsForTerminal == true) + SerialLog.write(unknownChoice); + SerialPrintln(F("")); } void printUnknown(int unknownValue) { - Serial.print(F("Unknown value: ")); + SerialPrint(F("Unknown value: ")); Serial.write(unknownValue); - Serial.println(); + if (settings.useTxRxPinsForTerminal == true) + SerialLog.write(unknownValue); + SerialPrintln(F("")); } //Blocking wait for user input @@ -29,9 +35,24 @@ void waitForInput() checkBattery(); delay(1); } + while (Serial.available() > 0) Serial.read(); //Clear buffer - while (Serial.available() == 0) + + if (settings.useTxRxPinsForTerminal == true) + while (SerialLog.available() > 0) SerialLog.read(); //Clear buffer + + bool keepChecking = true; + while (keepChecking) { + if (Serial.available()) + keepChecking = false; + + if (settings.useTxRxPinsForTerminal == true) + { + if (SerialLog.available()) + keepChecking = false; + } + checkBattery(); } } @@ -40,16 +61,21 @@ void waitForInput() //Waits for and returns the character that the user provides //Returns STATUS_GETNUMBER_TIMEOUT if input times out //Returns 'x' if user presses 'x' -uint8_t getByteChoice(int numberOfSeconds) +uint8_t getByteChoice(int numberOfSeconds, bool updateDZSERIAL) { - Serial.flush(); + SerialFlush(); + for (int i = 0; i < 50; i++) //Wait for any incoming chars to hit buffer { checkBattery(); delay(1); } + while (Serial.available() > 0) Serial.read(); //Clear buffer + if (settings.useTxRxPinsForTerminal == true) + while (SerialLog.available() > 0) SerialLog.read(); //Clear buffer + long startTime = millis(); byte incoming; while (1) @@ -57,8 +83,32 @@ uint8_t getByteChoice(int numberOfSeconds) if (Serial.available() > 0) { incoming = Serial.read(); -// Serial.print(F("byte: 0x")); + if (updateDZSERIAL) + { + DSERIAL = &Serial; + ZSERIAL = &Serial; + } +// SerialPrint(F("byte: 0x")); +// Serial.println(incoming, HEX); +// if (settings.useTxRxPinsForTerminal == true) +// SerialLog.println(incoming, HEX); + if (incoming >= 'a' && incoming <= 'z') break; + if (incoming >= 'A' && incoming <= 'Z') break; + if (incoming >= '0' && incoming <= '9') break; + } + + if ((settings.useTxRxPinsForTerminal == true) && (SerialLog.available() > 0)) + { + incoming = SerialLog.read(); + if (updateDZSERIAL) + { + DSERIAL = &SerialLog; + ZSERIAL = &SerialLog; + } +// SerialPrint(F("byte: 0x")); // Serial.println(incoming, HEX); +// if (settings.useTxRxPinsForTerminal == true) +// SerialLog.println(incoming, HEX); if (incoming >= 'a' && incoming <= 'z') break; if (incoming >= 'A' && incoming <= 'Z') break; if (incoming >= '0' && incoming <= '9') break; @@ -66,7 +116,7 @@ uint8_t getByteChoice(int numberOfSeconds) if ( (millis() - startTime) / 1000 >= numberOfSeconds) { - Serial.println(F("No user input received.")); + SerialPrintln(F("No user input received.")); return (STATUS_GETBYTE_TIMEOUT); //Timeout. No user input. } @@ -87,8 +137,12 @@ int64_t getNumber(int numberOfSeconds) checkBattery(); delay(1); } + while (Serial.available() > 0) Serial.read(); //Clear buffer + if (settings.useTxRxPinsForTerminal == true) + while (SerialLog.available() > 0) SerialLog.read(); //Clear buffer + //Get input from user char cleansed[20]; //Good for very large numbers: 123,456,789,012,345,678\0 @@ -96,15 +150,22 @@ int64_t getNumber(int numberOfSeconds) int spot = 0; while (spot < 20 - 1) //Leave room for terminating \0 { - while (Serial.available() == 0) //Wait for user input + bool serialAvailable = false; + while (serialAvailable == false) //Wait for user input { + if (Serial.available()) + serialAvailable = true; + + if ((settings.useTxRxPinsForTerminal == true) && (SerialLog.available())) + serialAvailable = true; + checkBattery(); if ( (millis() - startTime) / 1000 >= numberOfSeconds) { if (spot == 0) { - Serial.println(F("No user input received. Do you have line endings turned on?")); + SerialPrintln(F("No user input received. Do you have line endings turned on?")); return (STATUS_GETNUMBER_TIMEOUT); //Timeout. No user input. } else if (spot > 0) @@ -117,20 +178,31 @@ int64_t getNumber(int numberOfSeconds) //See if we timed out waiting for a line ending if (spot > 0 && (millis() - startTime) / 1000 >= numberOfSeconds) { - Serial.println(F("Do you have line endings turned on?")); + SerialPrintln(F("Do you have line endings turned on?")); break; //Timeout, but we have data } - byte incoming = Serial.read(); + byte incoming; + + if (Serial.available()) + incoming = Serial.read(); + + else + incoming = SerialLog.read(); + if (incoming == '\n' || incoming == '\r') { - Serial.println(); + SerialPrintln(F("")); break; } if ((isDigit(incoming) == true) || ((incoming == '-') && (spot == 0))) // Check for digits and a minus sign { Serial.write(incoming); //Echo user's typing + + if (settings.useTxRxPinsForTerminal == true) + SerialLog.write(incoming); //Echo user's typing + cleansed[spot++] = (char)incoming; } @@ -170,8 +242,12 @@ double getDouble(int numberOfSeconds) checkBattery(); delay(1); } + while (Serial.available() > 0) Serial.read(); //Clear buffer + if (settings.useTxRxPinsForTerminal == true) + while (SerialLog.available() > 0) SerialLog.read(); //Clear buffer + //Get input from user char cleansed[20]; //Good for very large numbers: 123,456,789,012,345,678\0 @@ -180,15 +256,22 @@ double getDouble(int numberOfSeconds) bool dpSeen = false; while (spot < 20 - 1) //Leave room for terminating \0 { - while (Serial.available() == 0) //Wait for user input + bool serialAvailable = false; + while (serialAvailable == false) //Wait for user input { + if (Serial.available()) + serialAvailable = true; + + if ((settings.useTxRxPinsForTerminal == true) && (SerialLog.available())) + serialAvailable = true; + checkBattery(); if ( (millis() - startTime) / 1000 >= numberOfSeconds) { if (spot == 0) { - Serial.println(F("No user input received. Do you have line endings turned on?")); + SerialPrintln(F("No user input received. Do you have line endings turned on?")); return (STATUS_GETNUMBER_TIMEOUT); //Timeout. No user input. } else if (spot > 0) @@ -201,20 +284,31 @@ double getDouble(int numberOfSeconds) //See if we timed out waiting for a line ending if (spot > 0 && (millis() - startTime) / 1000 >= numberOfSeconds) { - Serial.println(F("Do you have line endings turned on?")); + SerialPrintln(F("Do you have line endings turned on?")); break; //Timeout, but we have data } - byte incoming = Serial.read(); + byte incoming; + + if (Serial.available()) + incoming = Serial.read(); + + else + incoming = SerialLog.read(); + if (incoming == '\n' || incoming == '\r') { - Serial.println(); + SerialPrintln(F("")); break; } if ((isDigit(incoming) == true) || ((incoming == '-') && (spot == 0)) || ((incoming == '.') && (dpSeen == false))) // Check for digits/minus/dp { Serial.write(incoming); //Echo user's typing + + if (settings.useTxRxPinsForTerminal == true) + SerialLog.write(incoming); //Echo user's typing + cleansed[spot++] = (char)incoming; } diff --git a/Firmware/OpenLog_Artemis/zmodem.h b/Firmware/OpenLog_Artemis/zmodem.h index 1815571..073f2d0 100644 --- a/Firmware/OpenLog_Artemis/zmodem.h +++ b/Firmware/OpenLog_Artemis/zmodem.h @@ -185,7 +185,7 @@ int wcrx(); #define CPMEOF 032 #define WANTCRC 0103 /* send C not NAK to get crc not checksum */ #define WANTG 0107 /* Send G not NAK to get nonstop batch xmsn */ -#define TIMEOUT (-2) +//#define TIMEOUT (-2) #define RCDO (-3) #define Tx_RETRYMAX 10 #define Rx_RETRYMAX 5 diff --git a/Firmware/OpenLog_Artemis/zmodem.ino b/Firmware/OpenLog_Artemis/zmodem.ino index 84fdcdf..b46ce74 100644 --- a/Firmware/OpenLog_Artemis/zmodem.ino +++ b/Firmware/OpenLog_Artemis/zmodem.ino @@ -145,29 +145,29 @@ size_t DSERIALprint(const __FlashStringHelper *ifsh) while (1) { unsigned char c = pgm_read_byte(p++); if (c == 0) break; - if (DSERIAL.availableForWrite() > SERIAL_TX_BUFFER_SIZE / 2) DSERIAL.flush(); - if (DSERIAL.write(c)) n++; + if (DSERIAL->availableForWrite() > SERIAL_TX_BUFFER_SIZE / 2) DSERIAL->flush(); + if (DSERIAL->write(c)) n++; else break; } return n; } -#define DSERIALprintln(_p) ({ DSERIALprint(_p); DSERIAL.write("\r\n"); }) +#define DSERIALprintln(_p) ({ DSERIALprint(_p); DSERIAL->write("\r\n"); }) void sdCardHelp(void) { DSERIALprint(F("\r\n")); DSERIALprint(Progname); DSERIALprint(F(" - Transfer rate: ")); - DSERIAL.flush(); DSERIAL.println(settings.serialTerminalBaudRate); DSERIAL.flush(); - DSERIALprintln(F("Available Commands:")); DSERIAL.flush(); - DSERIALprintln(F("HELP - Print this list of commands")); DSERIAL.flush(); - DSERIALprintln(F("DIR - List files in current working directory - alternate LS")); DSERIAL.flush(); - DSERIALprintln(F("DEL file - Delete file - alternate RM")); DSERIAL.flush(); - DSERIALprintln(F("SZ file - Send file from OLA to terminal using ZModem (\"SZ *\" will send all files)")); DSERIAL.flush(); - DSERIALprintln(F("SS file - Send file from OLA using serial TX pin")); DSERIAL.flush(); - DSERIALprintln(F("CAT file - Type file to this terminal - alternate TYPE")); DSERIAL.flush(); - DSERIALprintln(F("X - Exit to OpenLog Artemis Main Menu")); DSERIAL.flush(); + DSERIAL->flush(); DSERIAL->println(settings.serialTerminalBaudRate); DSERIAL->flush(); + DSERIALprintln(F("Available Commands:")); DSERIAL->flush(); + DSERIALprintln(F("HELP - Print this list of commands")); DSERIAL->flush(); + DSERIALprintln(F("DIR - List files in current working directory - alternate LS")); DSERIAL->flush(); + DSERIALprintln(F("DEL file - Delete file - alternate RM")); DSERIAL->flush(); + DSERIALprintln(F("SZ file - Send file from OLA to terminal using ZModem (\"SZ *\" will send all files)")); DSERIAL->flush(); + DSERIALprintln(F("SS file - Send file from OLA using serial TX pin")); DSERIAL->flush(); + DSERIALprintln(F("CAT file - Type file to this terminal - alternate TYPE")); DSERIAL->flush(); + DSERIALprintln(F("X - Exit to OpenLog Artemis Main Menu")); DSERIAL->flush(); DSERIALprint(F("\r\n")); } @@ -214,17 +214,17 @@ void sdCardMenu(void) *cmd = 0; - while (DSERIAL.available()) DSERIAL.read(); + while (DSERIAL->available()) DSERIAL->read(); char c = 0; while(1) { - if (DSERIAL.available() > 0) + if (DSERIAL->available() > 0) { - c = DSERIAL.read(); + c = DSERIAL->read(); if ((c == 8 or c == 127) && strlen(cmd) > 0) cmd[strlen(cmd)-1] = 0; if (c == '\n' || c == '\r') break; - DSERIAL.write(c); + DSERIAL->write(c); if (c != 8 && c != 127) strncat(cmd, &c, 1); } else @@ -251,7 +251,7 @@ void sdCardMenu(void) } strupr(cmd); - DSERIAL.println(); + DSERIAL->println(); // DSERIALprintln(command); // DSERIALprintln(parameter); @@ -264,7 +264,10 @@ void sdCardMenu(void) { DSERIALprintln(F("\r\nRoot Directory Listing:")); - sd.ls("/", LS_DATE | LS_SIZE); // Do a non-recursive LS of the root directory showing file modification dates and sizes + if (DSERIAL == &Serial) + sd.ls("/", LS_DATE | LS_SIZE); // Do a non-recursive LS of the root directory showing file modification dates and sizes + else + sd.ls(&SerialLog, "/", LS_DATE | LS_SIZE); DSERIALprintln(F("End of Directory\r\n")); } @@ -274,13 +277,13 @@ void sdCardMenu(void) if (!sd.remove(param)) { DSERIALprint(F("\r\nFailed to delete file ")); - DSERIAL.flush(); DSERIAL.println(param); DSERIAL.flush(); + DSERIAL->flush(); DSERIAL->println(param); DSERIAL->flush(); DSERIALprintln(F("\r\n")); } else { DSERIALprint(F("\r\nFile ")); - DSERIAL.flush(); DSERIAL.print(param); DSERIAL.flush(); + DSERIAL->flush(); DSERIAL->print(param); DSERIAL->flush(); DSERIALprintln(F(" deleted\r\n")); } } @@ -291,14 +294,14 @@ void sdCardMenu(void) { count_files(&Filesleft, &Totalleft); - DSERIALprint(F("\r\nTransferring ")); DSERIAL.print(Filesleft); DSERIALprint(F(" files (")); DSERIAL.print(Totalleft); DSERIALprintln(F(" bytes)")); + DSERIALprint(F("\r\nTransferring ")); DSERIAL->print(Filesleft); DSERIALprint(F(" files (")); DSERIAL->print(Totalleft); DSERIALprintln(F(" bytes)")); root.open("/"); // (re)open the root directory root.rewind(); // rewind if (Filesleft > 0) { - DSERIALprint(F("Starting zmodem transfer in ")); Serial.print(settings.zmodemStartDelay); DSERIALprintln(F(" seconds...")); + DSERIALprint(F("Starting zmodem transfer in ")); DSERIAL->print(settings.zmodemStartDelay); DSERIALprintln(F(" seconds...")); DSERIALprintln(F("(If you are using Tera Term, you need to start your File\\Transfer\\ZMODEM\\Receive now!)")); if (oneTime == false) { @@ -327,7 +330,7 @@ void sdCardMenu(void) char fname[30]; size_t fsize = 30; fout.getName(fname, fsize); - //Serial.print("fname: "); Serial.println(fname); + //DSERIAL->print("fname: "); DSERIAL->println(fname); if (wcs(fname) == ERROR) { for (int i = 0; i < 500; i++) @@ -367,7 +370,7 @@ void sdCardMenu(void) } else { - DSERIALprint(F("\r\nStarting zmodem transfer in ")); Serial.print(settings.zmodemStartDelay); DSERIALprintln(F(" seconds...")); + DSERIALprint(F("\r\nStarting zmodem transfer in ")); DSERIAL->print(settings.zmodemStartDelay); DSERIALprintln(F(" seconds...")); DSERIALprintln(F("(If you are using Tera Term, you need to start your File\\Transfer\\ZMODEM\\Receive now!)")); if (oneTime == false) { @@ -384,7 +387,7 @@ void sdCardMenu(void) // Start the ZMODEM transfer Filesleft = 1; Totalleft = fout.fileSize(); - //ZSERIAL.print(F("rz\r")); + //ZSERIAL->print(F("rz\r")); sendzrqinit(); for (int i = 0; i < 200; i++) { @@ -408,9 +411,10 @@ void sdCardMenu(void) else { settings.logA12 = false; //Disable analog readings on TX pin - SerialLog.begin(settings.serialLogBaudRate); // (Re)start the serial port + if (settings.useTxRxPinsForTerminal == false) + SerialLog.begin(settings.serialLogBaudRate); // (Re)start the serial port - DSERIALprint(F("\r\nSending ")); Serial.print(param); DSERIALprint(F(" to the TX pin at ")); Serial.print(settings.serialLogBaudRate); DSERIALprintln(F(" baud")); + DSERIALprint(F("\r\nSending ")); DSERIAL->print(param); DSERIALprint(F(" to the TX pin at ")); DSERIAL->print(settings.serialLogBaudRate); DSERIALprintln(F(" baud")); while (fout.available()) { @@ -438,7 +442,7 @@ void sdCardMenu(void) { char ch; if (fout.read(&ch, 1) == 1) // Read a single char - Serial.write(ch); // Send it via SerialLog (TX pin) + DSERIAL->write(ch); // Send it via SerialLog (TX pin) } fout.close(); diff --git a/Firmware/OpenLog_Artemis/zmodem_config.h b/Firmware/OpenLog_Artemis/zmodem_config.h index b96f666..c39244a 100644 --- a/Firmware/OpenLog_Artemis/zmodem_config.h +++ b/Firmware/OpenLog_Artemis/zmodem_config.h @@ -17,13 +17,15 @@ //#endif // Serial output for debugging info -#define DSERIAL Serial +//#define DSERIAL Serial +//Stream *DSERIAL; // The Serial port for the Zmodem connection // must not be the same as DSERIAL unless all // debugging output to DSERIAL is removed //#define ZSERIAL Serial3 -#define ZSERIAL Serial +//#define ZSERIAL Serial +//Stream *ZSERIAL; //#ifdef TEENSYDUINO // #ifndef SERIAL_TX_BUFFER_SIZE diff --git a/Firmware/OpenLog_Artemis/zmodem_fixes.h b/Firmware/OpenLog_Artemis/zmodem_fixes.h index 4155191..b5ec90d 100644 --- a/Firmware/OpenLog_Artemis/zmodem_fixes.h +++ b/Firmware/OpenLog_Artemis/zmodem_fixes.h @@ -19,18 +19,39 @@ extern SdFat sd; #include -// Dylan (monte_carlo_ecm, bitflipper, etc.) - changed serial read/write to macros to try to squeeze +// Dylan (monte_carlo_ecm, bitflipper, etc.) - changed serial read/write to macros to try to squeeze // out higher speed #define READCHECK #define TYPICAL_SERIAL_TIMEOUT 1200 -#define readline(timeout) ({ char _c; ZSERIAL.readBytes(&_c, 1) > 0 ? _c : TIMEOUT; }) -int zdlread2(int); -#define zdlread(void) ({ int _z; ((_z = readline(Rxtimeout)) & 0140) ? _z : zdlread2(_z); }) -//#define sendline(_c) ZSERIAL.write(char(_c)) -#define sendline(_c) ({ if (ZSERIAL.availableForWrite() > SERIAL_TX_BUFFER_SIZE / 2) ZSERIAL.flush(); ZSERIAL.write(char(_c)); }) -#define zsendline(_z) ({ (_z & 0140 ) ? sendline(_z) : zsendline2(_z); }) +extern Stream *ZSERIAL; +extern int Rxtimeout; +#define TIMEOUT (-2) + +//#define readline(timeout) ({ char _c; ZSERIAL->readBytes(&_c, 1) > 0 ? _c : TIMEOUT; }) +//#define zdlread(void) ({ int _z; ((_z = readline(Rxtimeout)) & 0140) ? _z : zdlread2(_z); }) +//#define sendline(_c) ZSERIAL->write(char(_c)) +//#define sendline(_c) ({ if (ZSERIAL->availableForWrite() > SERIAL_TX_BUFFER_SIZE / 2) ZSERIAL->flush(); ZSERIAL->write(char(_c)); }) +//#define zsendline(_z) ({ (_z & 0140 ) ? sendline(_z) : zsendline2(_z); }) + +int readline(int timeout); // Header. Code is in zmodem_zm.cpp + +void sendline(int _c); // Header. Code is in zmodem_zm.cpp +#define xsendline(c) sendline(c) + +//int zdlread2(int); // Header. Code is in zmodem_zm.cpp + +void zsendline(int _z); // Header. Code is in zmodem_zm.cpp + +//int zdlread(void) +//{ +// int _z; +// if ((_z = readline(Rxtimeout)) & 0140) +// return (_z); +// else +// return (zdlread2(_z)); +//} void sendzrqinit(void); int wctxpn(const char *name); @@ -62,7 +83,7 @@ extern int Filcnt; // enter the "if" statement's clause #define setjmp(...) -#define printf(s, ... ) DSERIAL.println(s); +#define printf(s, ... ) DSERIAL->println(s); #define fprintf(...) // fseek(in, Rxpos, 0) diff --git a/Firmware/OpenLog_Artemis/zmodem_rz.cpp b/Firmware/OpenLog_Artemis/zmodem_rz.cpp index b92a85f..c6b06f8 100644 --- a/Firmware/OpenLog_Artemis/zmodem_rz.cpp +++ b/Firmware/OpenLog_Artemis/zmodem_rz.cpp @@ -27,14 +27,14 @@ * Professional-YAM, PowerCom, YAM, IMP, or programs supporting XMODEM. * rz uses Unix buffered input to reduce wasted CPU time. * - * Iff the program is invoked by rzCOMMAND, output is piped to + * Iff the program is invoked by rzCOMMAND, output is piped to * "COMMAND filename" (Unix only) * * Some systems (Venix, Coherent, Regulus) may not support tty raw mode * read(2) the same way as Unix. ONEREAD must be defined to force one * character reads for these systems. Added 7-01-84 CAF * - * Alarm signal handling changed to work with 4.2 BSD 7-15-84 CAF + * Alarm signal handling changed to work with 4.2 BSD 7-15-84 CAF * * BIX added 6-30-87 to support BIX(TM) upload protocol used by the * Byte Information Exchange. @@ -62,8 +62,6 @@ #include -#define xsendline(c) sendline(c) - #include "zmodem.h" #include "zmodem_zm.h" #include "zmodem_crc16.cpp" @@ -222,7 +220,7 @@ void bibi(int n) { if (Zmodem) zmputs(Attn); - canit(); + canit(); mode(0); fprintf(stderr, "rz: caught signal %d; exiting\n", n); cucheck(); @@ -265,47 +263,47 @@ int wcreceive(int argc, char **argp) return OK; } if (c == ERROR) { -//DSERIAL.println(F("fubar 1")); +//DSERIAL->println(F("fubar 1")); goto fubar; } c = rzfiles(); if (c) { -//DSERIAL.println(F("fubar 2")); +//DSERIAL->println(F("fubar 2")); goto fubar; } - } + } else { for (;;) { if (wcrxpn(secbuf)== ERROR) { -//DSERIAL.println(F("fubar 3")); +//DSERIAL->println(F("fubar 3")); goto fubar; } if (secbuf[0]==0) return OK; if (procheader(secbuf) == ERROR) { -//DSERIAL.println(F("fubar 4")); +//DSERIAL->println(F("fubar 4")); goto fubar; } if (wcrx()==ERROR) { -//DSERIAL.println(F("fubar 5")); +//DSERIAL->println(F("fubar 5")); goto fubar; } } } - } + } else { - Bytesleft = DEFBYTL; - //Filemode = 0; + Bytesleft = DEFBYTL; + //Filemode = 0; //Modtime = 0L; - procheader(""); - strcpy(Pathname, *argp); + procheader(""); + strcpy(Pathname, *argp); // if (checkpath(Pathname)) { // canit(); // return ERROR; // } -//DSERIAL.print("rz: ready to receive "); -//DSERIAL.println(Pathname); +//DSERIAL->print("rz: ready to receive "); +//DSERIAL->println(Pathname); #ifndef ARDUINO if ((fout=fopen(Pathname, "w")) == NULL) #else @@ -313,9 +311,9 @@ int wcreceive(int argc, char **argp) #endif return ERROR; rxbytes = fout.fileSize(); - + if (wcrx()==ERROR) { -DSERIAL.println(F("fubar 6")); +//DSERIAL->println(F("fubar 6")); goto fubar; } } @@ -326,7 +324,7 @@ DSERIAL.println(F("fubar 6")); #ifndef ARDUINO #ifndef vax11c if (Topipe && fout) { - pclose(fout); + pclose(fout); return ERROR; } #endif @@ -364,7 +362,7 @@ int wcrxpn(char *rpn) #endif et_tu: - Firstsec=TRUE; + Firstsec=TRUE; Eofseen=FALSE; sendline(Crcflg?WANTCRC:NAK); Lleft=0; /* Do read next time ... */ @@ -378,7 +376,7 @@ int wcrxpn(char *rpn) } return ERROR; - + } sendline(ACK); return OK; @@ -396,7 +394,7 @@ int wcrx() int cblklen; /* bytes to dump this block */ Firstsec=TRUE; - sectnum=0; + sectnum=0; Eofseen=FALSE; sendchar=Crcflg?WANTCRC:NAK; @@ -454,7 +452,7 @@ int wcgetsec(char *rxbuf,int maxtime) for (Lastrx=errors=0; errors=0; ) { if ((firstch=readline(1)) < 0) { -//DSERIAL.println(F("bilge 1")); +//DSERIAL->println(F("bilge 1")); goto bilge; } oldcrc=updcrc(firstch, oldcrc); checksum += (*p++ = firstch); } if ((firstch=readline(1)) < 0) { -//DSERIAL.println(F("bilge 2")); +//DSERIAL->println(F("bilge 2")); goto bilge; } if (Crcflg) { oldcrc=updcrc(firstch, oldcrc); if ((firstch=readline(1)) < 0) { -//DSERIAL.println(F("bilge 3")); +//DSERIAL->println(F("bilge 3")); goto bilge; } @@ -513,7 +511,7 @@ int wcgetsec(char *rxbuf,int maxtime) if (Lastrx==CAN) { zperr( "Sender CANcelled"); return ERROR; - } + } else { Lastrx=CAN; continue; @@ -535,9 +533,9 @@ int wcgetsec(char *rxbuf,int maxtime) if (Firstsec) { sendline(Crcflg?WANTCRC:NAK); Lleft=0; /* Do read next time ... */ - } + } else { - maxtime=40; + maxtime=40; sendline(NAK); Lleft=0; /* Do read next time ... */ } @@ -578,7 +576,7 @@ int procheader(char *name) /* Check for existing file */ #ifndef ARDUINO if (!Rxclob && (zmanag&ZMMASK) != ZMCLOB && (fout=fopen(name, "r"))) { - fclose(fout); + fclose(fout); return ERROR; } #else @@ -588,8 +586,8 @@ int procheader(char *name) #endif #endif - Bytesleft = DEFBYTL; - //Filemode = 0; + Bytesleft = DEFBYTL; + //Filemode = 0; //Modtime = 0L; p = name + 1 + strlen(name); @@ -645,7 +643,7 @@ int procheader(char *name) if ( !(fout = popen(name+1, "w"))) { return ERROR; } - Topipe = -1; + Topipe = -1; return(OK); } #endif @@ -696,10 +694,10 @@ int putsec(char *buf,int n) if ( *p == '\r') continue; if (*p == CPMEOF) { - Eofseen=TRUE; + Eofseen=TRUE; return OK; } -#ifndef ARDUINO +#ifndef ARDUINO putc(*p ,fout); #else fout.write(*p); @@ -748,7 +746,7 @@ int tryz(void) int c, n; int cmdzack1flg; -//DSERIAL.println(F("Entering tryz")); +//DSERIAL->println(F("Entering tryz")); if (Nozmodem) /* Check for "rb" program name */ return 0; @@ -766,7 +764,7 @@ int tryz(void) // This unfortunate piece is a limiting factor. This parameter is supposed to control // the maximum buffer size that the sender expects us to have. It seems most ZModem send implementations // ignore it, with Hyperterminal being the only exception I can find. Without setting this, even -// Hyperterminal outstrips the Arduino's speed and buffer (64 bytes) at 57600 baud. +// Hyperterminal outstrips the Arduino's speed and buffer (64 bytes) at 57600 baud. stohdr(SECBUF_LEN); #endif @@ -786,16 +784,16 @@ int tryz(void) again: switch (zgethdr(Rxhdr, 0)) { case ZRQINIT: -//DSERIAL.println(F("tryz got ZRQINIT")); +//DSERIAL->println(F("tryz got ZRQINIT")); continue; case ZEOF: -//DSERIAL.println(F("tryz got ZEOF")); +//DSERIAL->println(F("tryz got ZEOF")); continue; case TIMEOUT: -//DSERIAL.println(F("tryz got TIMEOUT")); +//DSERIAL->println(F("tryz got TIMEOUT")); continue; case ZFILE: -//DSERIAL.println(F("tryz got ZFILE")); +//DSERIAL->println(F("tryz got ZFILE")); zconv = Rxhdr[ZF0]; zmanag = Rxhdr[ZF1]; @@ -808,7 +806,7 @@ int tryz(void) zshhdr(ZNAK, Txhdr); goto again; case ZSINIT: -//DSERIAL.println(F("tryz got ZSINIT")); +//DSERIAL->println(F("tryz got ZSINIT")); Zctlesc = TESCCTL & Rxhdr[ZF0]; if (zrdata(Attn, ZATTNLEN) == GOTCRCW) { @@ -826,7 +824,7 @@ int tryz(void) #ifdef vax11c return ERROR; #else -//DSERIAL.println(F("tryz got ZCOMMAND")); +//DSERIAL->println(F("tryz got ZCOMMAND")); cmdzack1flg = Rxhdr[ZF0]; if (zrdata(secbuf, SECBUF_LEN) == GOTCRCW) { @@ -844,24 +842,24 @@ int tryz(void) // exec2(secbuf); return ZCOMPL; } - zshhdr(ZNAK, Txhdr); + zshhdr(ZNAK, Txhdr); goto again; #endif case ZCOMPL: -//DSERIAL.println(F("tryz got ZCOMPL")); +//DSERIAL->println(F("tryz got ZCOMPL")); goto again; default: -//DSERIAL.println(F("tryz got default")); - +//DSERIAL->println(F("tryz got default")); + continue; case ZFIN: -//DSERIAL.println(F("tryz got ZFIN")); +//DSERIAL->println(F("tryz got ZFIN")); - ackbibi(); + ackbibi(); return ZCOMPL; case ZCAN: -//DSERIAL.println(F("tryz got ZCAN")); +//DSERIAL->println(F("tryz got ZCAN")); return ERROR; } @@ -911,7 +909,7 @@ int rzfile(void) return (tryzhdrtype = ZSKIP); } - n = 20; + n = 20; // rxbytes = 0l; for (;;) { @@ -949,7 +947,7 @@ int rzfile(void) * a timeout because the eof might have gone * out before we sent our zrpos. */ - errors = 0; + errors = 0; goto nxthdr; } if (closeit()) { @@ -987,7 +985,7 @@ int rzfile(void) putsec(secbuf, chinseg); chinseg = 0; #endif - zmputs(Attn); + zmputs(Attn); continue; } moredata: @@ -1091,14 +1089,14 @@ void zmputs(char *s) while (*s) { switch (c = *s++) { case '\336': -#ifndef ARDUINO - sleep(1); +#ifndef ARDUINO + sleep(1); #else delay(1000); #endif continue; case '\335': - sendbrk(); + sendbrk(); continue; default: sendline(c); diff --git a/Firmware/OpenLog_Artemis/zmodem_sz.cpp b/Firmware/OpenLog_Artemis/zmodem_sz.cpp index 4a8ccd4..d52ec27 100644 --- a/Firmware/OpenLog_Artemis/zmodem_sz.cpp +++ b/Firmware/OpenLog_Artemis/zmodem_sz.cpp @@ -226,7 +226,7 @@ int wctxpn(const char *name) char *p, *q; -//DSERIAL.println("\nwctxpn"); +//DSERIAL->println("\nwctxpn"); strcpy(txbuf,name); p = q = txbuf + strlen(txbuf)+1; @@ -247,10 +247,10 @@ int wctxpn(const char *name) ultoa(Totalleft, q, 10); Totalleft -= fout.fileSize(); -//DSERIAL.print(F("wctxpn sf = ")); -//DSERIAL.print(sf); -//DSERIAL.print(F(" length = ")); -//DSERIAL.println(Totalleft); +//DSERIAL->print(F("wctxpn sf = ")); +//DSERIAL->print(sf); +//DSERIAL->print(F(" length = ")); +//DSERIAL->println(Totalleft); if (--Filesleft <= 0) Totalleft = 0; if (Totalleft < 0) @@ -276,7 +276,7 @@ int wctx(long flen) int sectnum, attempts, firstch; long charssent; -//DSERIAL.println("\nwctx"); +//DSERIAL->println("\nwctx"); charssent = 0; firstsec=TRUE; @@ -412,16 +412,16 @@ int filbuf(char *buf,int count) { int c, m; -//DSERIAL.println("\nfilbuf"); +//DSERIAL->println("\nfilbuf"); if ( !Ascii) { // m = read(fileno(in), buf, count); m = fout.read(buf, count); -//DSERIAL.println(F("filbuf: '")); +//DSERIAL->println(F("filbuf: '")); //for(int i=0;iprint(buf[i]); //} -//DSERIAL.println(F("'")); +//DSERIAL->println(F("'")); if (m <= 0) return 0; while (m < count) @@ -477,7 +477,7 @@ int zsendfile(char *buf, int blen) int c; UNSL long crc; -//DSERIAL.println(F("\nzsendfile")); +//DSERIAL->println(F("\nzsendfile")); for (;;) { Txhdr[ZF0] = Lzconv; /* file conversion request */ @@ -503,7 +503,7 @@ int zsendfile(char *buf, int blen) case TIMEOUT: case ZABORT: case ZFIN: -//DSERIAL.println(F("\nzsendfile - ZFIN")); +//DSERIAL->println(F("\nzsendfile - ZFIN")); return ERROR; case ZCRC: @@ -524,7 +524,7 @@ int zsendfile(char *buf, int blen) case ZSKIP: fout.close(); //fclose(in); -//DSERIAL.println(F("\nzsendfile - ZSKIP")); +//DSERIAL->println(F("\nzsendfile - ZSKIP")); return c; case ZRPOS: /* @@ -537,8 +537,8 @@ int zsendfile(char *buf, int blen) return ERROR; Lastsync = (bytcnt = Txpos = Rxpos) -1; int ret = zsendfdata(); -//DSERIAL.print(F("\nzsendfile - exit - ")); -//DSERIAL.println(ret); +//DSERIAL->print(F("\nzsendfile - exit - ")); +//DSERIAL->println(ret); return(ret); } } @@ -554,11 +554,11 @@ int zsendfdata(void) int newcnt; uint8_t junkcount; /* Counts garbage chars received by TX */ -//DSERIAL.print(F("\nzsendfdata: ")); -//DSERIAL.print(F("number = ")); -//DSERIAL.print(Filesleft+1); -//DSERIAL.print(F(" length = ")); -//DSERIAL.println(Totalleft); +//DSERIAL->print(F("\nzsendfdata: ")); +//DSERIAL->print(F("number = ")); +//DSERIAL->print(Filesleft+1); +//DSERIAL->print(F(" length = ")); +//DSERIAL->println(Totalleft); Lrxpos = 0; junkcount = 0; Beenhereb4 = FALSE; @@ -574,7 +574,7 @@ int zsendfdata(void) case ZCAN: fout.close(); //fclose(in); -//DSERIAL.println(F("zsendfdata - error - 1")); +//DSERIAL->println(F("zsendfdata - error - 1")); return ERROR; case ZSKIP: fout.close(); @@ -593,7 +593,7 @@ int zsendfdata(void) * sent by the receiver, in place of setjmp/longjmp * rdchk(fdes) returns non 0 if a character is available */ - while (ZSERIAL.available()) { + while (ZSERIAL->available()) { #ifdef SV switch (checked) #else @@ -612,7 +612,7 @@ int zsendfdata(void) #endif } -//DSERIAL.println("zsendfdata - 1"); +//DSERIAL->println("zsendfdata - 1"); // if ( !Fromcu) // signal(SIGINT, onintr); @@ -621,12 +621,12 @@ int zsendfdata(void) stohdr(Txpos); zsbhdr(ZDATA, Txhdr); -//DSERIAL.println("zsendfdata - 2"); +//DSERIAL->println("zsendfdata - 2"); do { n = zfilbuf(); // AHA - it reads the 18 chars here -//DSERIAL.println(n); +//DSERIAL->println(n); if (Eofseen) e = ZCRCE; else if (junkcount > 3) @@ -655,7 +655,7 @@ int zsendfdata(void) * rdchk(fdes) returns non 0 if a character is available */ // fflush(stdout); - while (ZSERIAL.available()) { + while (ZSERIAL->available()) { #ifdef SV switch (checked) #else @@ -684,7 +684,7 @@ int zsendfdata(void) } while (!Eofseen); -//DSERIAL.println("zsendfdata - 4"); +//DSERIAL->println("zsendfdata - 4"); // if ( !Fromcu) // signal(SIGINT, SIG_IGN); @@ -694,23 +694,23 @@ int zsendfdata(void) zsbhdr(ZEOF, Txhdr); switch (getinsync(0)) { case ZACK: -//DSERIAL.println(F("zsendfdata - ZAK")); +//DSERIAL->println(F("zsendfdata - ZAK")); continue; case ZRPOS: -//DSERIAL.println(F("zsendfdata - ZRPOS")); +//DSERIAL->println(F("zsendfdata - ZRPOS")); goto somemore; case ZRINIT: -//DSERIAL.println(F("zsendfdata - OK")); +//DSERIAL->println(F("zsendfdata - OK")); return OK; case ZSKIP: fout.close(); //fclose(in); -//DSERIAL.println(F("zsendfdata - ZSKIP")); +//DSERIAL->println(F("zsendfdata - ZSKIP")); return c; default: fout.close(); //fclose(in); -//DSERIAL.println(F("zsendfdata - error - 2")); +//DSERIAL->println(F("zsendfdata - error - 2")); return ERROR; } } @@ -728,7 +728,7 @@ int getinsync(int flag) for (;;) { if (Test) { -//DSERIAL.println(F("***** Signal Caught *****")); +//DSERIAL->println(F("***** Signal Caught *****")); Rxpos = 0; c = ZRPOS; } @@ -739,7 +739,7 @@ int getinsync(int flag) case ZABORT: case ZFIN: case TIMEOUT: -//DSERIAL.println(F("getinsync - timeout")); +//DSERIAL->println(F("getinsync - timeout")); return ERROR; case ZRPOS: /* ************************************* */ @@ -750,7 +750,7 @@ int getinsync(int flag) // if (fseek(in, Rxpos, 0)) { // seekSet returns true on success if(!fout.seekSet(Rxpos)) { -//DSERIAL.println(F("getinsync - fseek")); +//DSERIAL->println(F("getinsync - fseek")); return ERROR; } Eofseen = 0; @@ -792,7 +792,7 @@ int getinsync(int flag) void sendzrqinit(void) { - ZSERIAL.print(ZRQINIT_STR); + ZSERIAL->print(ZRQINIT_STR); } /* Say "bibi" to the receiver, try to do it cleanly */ diff --git a/Firmware/OpenLog_Artemis/zmodem_zm.cpp b/Firmware/OpenLog_Artemis/zmodem_zm.cpp index 0e27c25..1ea66ac 100644 --- a/Firmware/OpenLog_Artemis/zmodem_zm.cpp +++ b/Firmware/OpenLog_Artemis/zmodem_zm.cpp @@ -34,12 +34,16 @@ #endif #endif +int zdlread(void); // Header. Code is below. +int zdlread2(int c); // Header. Code is below. + // Shared globals long Bytesleft; // from rz - Shared with sz bytcnt long rxbytes; // from rz - Shared with sz Lrxpos int Blklen; // from rz - Shared with sz blklen -#define Rxtimeout 100 /* Tenths of seconds to wait for something */ +//#define Rxtimeout 100 /* Tenths of seconds to wait for something */ +int Rxtimeout = 100; /* Tenths of seconds to wait for something */ #define Verbose 0 // This buffer blends Txb (from sz) and secbuf (from rz) into a single buffer, saving 1K @@ -108,8 +112,8 @@ char *frametypes[] = { (char *)"ZCOMMAND", (char *)"ZSTDERR", (char *)"xxxxx" -#define FRTYPES 22 // Total number of frame types in this array - // not including psuedo negative entries +#define FRTYPES 22 // Total number of frame types in this array + // not including psuedo negative entries }; */ #define badcrc F("Bad CRC"); @@ -128,18 +132,18 @@ void zsbhdr(int type, char *hdr) for (n = Znulls; --n >=0; ) xsendline(0); */ - xsendline(ZPAD); + xsendline(ZPAD); xsendline(ZDLE); //Pete (El Supremo) This looks wrong but it is correct - the code fails if == is used if ((Crc32t = Txfcs32)) { int n; UNSL long crc; - - xsendline(ZBIN32); + + xsendline(ZBIN32); zsendline(type); - crc = 0xFFFFFFFFL; + crc = 0xFFFFFFFFL; crc = UPDC32(type, crc); - + for (n=4; --n >= 0; ++hdr) { crc = UPDC32((0377 & *hdr), crc); zsendline(*hdr); @@ -152,9 +156,9 @@ void zsbhdr(int type, char *hdr) } else { int n; unsigned short crc; - - xsendline(ZBIN); - zsendline(type); + + xsendline(ZBIN); + zsendline(type); crc = updcrc(type, 0); for (n=4; --n >= 0; ++hdr) { @@ -177,24 +181,24 @@ void zshhdr(int type,char *hdr) unsigned short crc; vfile(F("zshhdr: %s %lx"), frametypes[type+FTOFFSET], rclhdr(hdr)); - sendline(ZPAD); - sendline(ZPAD); - sendline(ZDLE); + sendline(ZPAD); + sendline(ZPAD); + sendline(ZDLE); sendline(ZHEX); zputhex(type); Crc32t = 0; crc = updcrc(type, 0); for (n=4; --n >= 0; ++hdr) { - zputhex(*hdr); + zputhex(*hdr); crc = updcrc((0377 & *hdr), crc); } crc = updcrc(0,updcrc(0,crc)); - zputhex(crc>>8); + zputhex(crc>>8); zputhex(crc); /* Make it printable on remote machine */ - sendline(015); + sendline(015); sendline(0212); /* * Uncork the remote in case a fake XOFF has stopped data flow @@ -209,8 +213,8 @@ void zshhdr(int type,char *hdr) * Send binary array buf of length length, with ending ZDLE sequence frameend */ /* -static char *Zendnames[] = { - (char *)"ZCRCE", +static char *Zendnames[] = { + (char *)"ZCRCE", (char *)"ZCRCG", (char *)"ZCRCQ", (char *)"ZCRCW" @@ -224,7 +228,7 @@ void zsdata(char *buf,int length,int frameend) if (Crc32t) { int c; UNSL long crc; - + crc = 0xFFFFFFFFL; for (;--length >= 0; ++buf) { c = *buf & 0377; @@ -234,33 +238,33 @@ void zsdata(char *buf,int length,int frameend) zsendline(c); crc = UPDC32(c, crc); } - xsendline(ZDLE); + xsendline(ZDLE); xsendline(frameend); crc = UPDC32(frameend, crc); - + crc = ~crc; for (length=4; --length >= 0;) { - zsendline((int)crc); + zsendline((int)crc); crc >>= 8; } } else { unsigned short crc; - + crc = 0; for (;--length >= 0; ++buf) { - zsendline(*buf); + zsendline(*buf); crc = updcrc((0377 & *buf), crc); } - xsendline(ZDLE); + xsendline(ZDLE); xsendline(frameend); crc = updcrc(frameend, crc); crc = updcrc(0,updcrc(0,crc)); - zsendline(crc>>8); + zsendline(crc>>8); zsendline(crc); } if (frameend == ZCRCW) { - xsendline(XON); + xsendline(XON); flushmo(); } } @@ -278,9 +282,9 @@ int zrdata(char *buf,int length) if (Rxframeind == ZBIN32) { UNSL long crc; - - crc = 0xFFFFFFFFL; - Rxcount = 0; + + crc = 0xFFFFFFFFL; + Rxcount = 0; end = buf + length; while (buf <= end) { if ((c = zdlread()) & ~0377) { @@ -290,7 +294,7 @@ int zrdata(char *buf,int length) case GOTCRCG: case GOTCRCQ: case GOTCRCW: - d = c; + d = c; c &= 0377; crc = UPDC32(c, crc); if ((c = zdlread()) & ~0377) @@ -332,7 +336,7 @@ int zrdata(char *buf,int length) } else { unsigned short crc; - crc = Rxcount = 0; + crc = Rxcount = 0; end = buf + length; while (buf <= end) { if ((c = zdlread()) & ~0377) { @@ -397,10 +401,10 @@ int zgethdr(char *hdr,int eflag) cancount = 5; again: /* Return immediate ERROR if ZCRCW sequence seen */ - ZSERIAL.setTimeout(Rxtimeout * 100); + ZSERIAL->setTimeout(Rxtimeout * 100); c = readline(Rxtimeout); - ZSERIAL.setTimeout(TYPICAL_SERIAL_TIMEOUT); - + ZSERIAL->setTimeout(TYPICAL_SERIAL_TIMEOUT); + switch (c) { case RCDO: case TIMEOUT: @@ -408,7 +412,7 @@ int zgethdr(char *hdr,int eflag) case CAN: gotcan: if (--cancount <= 0) { - c = ZCAN; + c = ZCAN; goto fifi; } switch (c = readline(1)) { @@ -423,7 +427,7 @@ int zgethdr(char *hdr,int eflag) break; case CAN: if (--cancount <= 0) { - c = ZCAN; + c = ZCAN; goto fifi; } goto again; @@ -467,7 +471,7 @@ int zgethdr(char *hdr,int eflag) case TIMEOUT: goto fifi; case ZBIN: - Rxframeind = ZBIN; + Rxframeind = ZBIN; Crc32 = FALSE; c = zrbhdr(hdr); break; @@ -476,7 +480,7 @@ int zgethdr(char *hdr,int eflag) c = zrbhdr32(hdr); break; case ZHEX: - Rxframeind = ZHEX; + Rxframeind = ZHEX; Crc32 = FALSE; c = zrhhdr(hdr); break; @@ -537,7 +541,7 @@ int zrbhdr(char *hdr) if ((c = zdlread()) & ~0377) return c; crc = updcrc(c, crc); - if (crc & 0xFFFF) { + if (crc & 0xFFFF) { zperr(badcrc); return ERROR; } @@ -559,7 +563,7 @@ int zrbhdr32(char *hdr) if ((c = zdlread()) & ~0377) return c; Rxtype = c; - crc = 0xFFFFFFFFL; + crc = 0xFFFFFFFFL; crc = UPDC32(c, crc); #ifdef DEBUGZ vfile(F("zrbhdr32 c=%X crc=%lX"), c, crc); @@ -621,7 +625,7 @@ int zrhhdr(char *hdr) return c; crc = updcrc(c, crc); if (crc & 0xFFFF) { - zperr(badcrc); + zperr(badcrc); return ERROR; } switch ( c = readline(1)) { @@ -638,7 +642,7 @@ int zrhhdr(char *hdr) #ifdef ZMODEM Protocol = ZMODEM; #endif -// Zmodem = 1; +// Zmodem = 1; return Rxtype; } @@ -739,6 +743,16 @@ int zgethex(void) * Read a byte, checking for ZMODEM escape encoding * including CAN*5 which represents a quick abort */ + +int zdlread(void) +{ + int _z; + if ((_z = readline(Rxtimeout)) & 0140) + return (_z); + else + return (zdlread2(_z)); +} + /* int zdlread(void) { @@ -801,6 +815,7 @@ int zdlread(void) return ERROR; } */ + int zdlread2(int c) { again: @@ -814,7 +829,7 @@ int zdlread2(int c) case 021: case 0221: if ((c = readline(Rxtimeout)) & 0140) - return c; + return c; goto again; default: if (Zctlesc && !(c & 0140)) { @@ -921,13 +936,13 @@ long rclhdr(char *hdr) // Why was this called sendline ?? //void sendline(int c) //{ -// ZSERIAL.write(c & 0xFF); -// ZSERIAL.write(char(c)); -// ZSERIAL.flush(); +// ZSERIAL->write(c & 0xFF); +// ZSERIAL->write(char(c)); +// ZSERIAL->flush(); -//DSERIAL.print("SEND: "); -//DSERIAL.print(c, HEX); -//DSERIAL.println(); +//DSERIAL->print("SEND: "); +//DSERIAL->print(c, HEX); +//DSERIAL->println(); //} /* @@ -937,18 +952,18 @@ int readline(int timeout) { long then; unsigned char c; - + then = millis(); - while(ZSERIAL.available() < 1) { + while(ZSERIAL->available() < 1) { if(millis() - then > (unsigned int)timeout*100UL) { -DSERIAL.println("readline - TIMEOUT"); +DSERIAL->println("readline - TIMEOUT"); return(TIMEOUT); } } - c = ZSERIAL.read(); -//DSERIAL.print("READ: "); -//DSERIAL.print(c, HEX); -//DSERIAL.println(); + c = ZSERIAL->read(); +//DSERIAL->print("READ: "); +//DSERIAL->print(c, HEX); +//DSERIAL->println(); return(c); } */ @@ -958,7 +973,7 @@ DSERIAL.println("readline - TIMEOUT"); */ void purgeline(void) { - while(ZSERIAL.available())ZSERIAL.read(); + while(ZSERIAL->available())ZSERIAL->read(); } /* @@ -974,21 +989,43 @@ void bttyout(int c) void flushmo(void) { - ZSERIAL.flush(); + ZSERIAL->flush(); } - - /* send cancel string to get the other end to shut up */ void canit(void) { for (int i=0; i < 10; ++i) { - ZSERIAL.write(24); + ZSERIAL->write(24); } for (int i=0; i < 10; ++i) { - ZSERIAL.write(8); + ZSERIAL->write(8); } - ZSERIAL.flush(); + ZSERIAL->flush(); +} + +int readline(int timeout) +{ + char _c; + if (ZSERIAL->readBytes(&_c, 1) > 0) + return (_c); + else + return (TIMEOUT); +} + +void sendline(int _c) +{ + if (ZSERIAL->availableForWrite() > SERIAL_TX_BUFFER_SIZE / 2) + ZSERIAL->flush(); + ZSERIAL->write(char(_c)); +} + +void zsendline(int _z) +{ + if (_z & 0140) + sendline(_z); + else + zsendline2(_z); } /* End of zm.c */ diff --git a/Firmware/OpenLog_Artemis/zmodem_zm.h b/Firmware/OpenLog_Artemis/zmodem_zm.h index 9dd8a3d..11e3118 100644 --- a/Firmware/OpenLog_Artemis/zmodem_zm.h +++ b/Firmware/OpenLog_Artemis/zmodem_zm.h @@ -42,10 +42,10 @@ extern uint32_t Baudrate; /* #define WHERESTR "[FILE : %s, FUNC : %s, LINE : %d]: " #define WHEREARG __FILE__,__func__,__LINE__ -#define DEBUG(...) {char s[256]; sprintf(s, __VA_ARGS__); DSERIAL.println(s);} +#define DEBUG(...) {char s[256]; sprintf(s, __VA_ARGS__); DSERIAL->println(s);} #define zperr(_fmt, ...) DEBUG(WHERESTR _fmt, WHEREARG,__VA_ARGS__) */ -//#define zperr(...) {char s[256]; sprintf(s, __VA_ARGS__); DSERIAL.println(s);} +//#define zperr(...) {char s[256]; sprintf(s, __VA_ARGS__); DSERIAL->println(s);} #define zperr(...) #endif From c7c3ad716d0034631669bcc0032be5ac9fcafdf0 Mon Sep 17 00:00:00 2001 From: PaulZC Date: Fri, 29 Jan 2021 10:32:50 +0000 Subject: [PATCH 05/14] Adding the timestamp feature as described in PR #70 and Issue #63. Deleted settings.getRTCfromGPS --- Firmware/OpenLog_Artemis/OpenLog_Artemis.ino | 50 +++++++++++-- Firmware/OpenLog_Artemis/Sensors.ino | 61 +++------------ .../OpenLog_Artemis/menuSerialLogging.ino | 49 ++++++++++++ Firmware/OpenLog_Artemis/nvm.ino | 14 +++- Firmware/OpenLog_Artemis/settings.h | 3 +- Firmware/OpenLog_Artemis/timeStamp.ino | 75 ++++++++----------- 6 files changed, 144 insertions(+), 108 deletions(-) diff --git a/Firmware/OpenLog_Artemis/OpenLog_Artemis.ino b/Firmware/OpenLog_Artemis/OpenLog_Artemis.ino index 888243c..0766d2f 100644 --- a/Firmware/OpenLog_Artemis/OpenLog_Artemis.ino +++ b/Firmware/OpenLog_Artemis/OpenLog_Artemis.ino @@ -79,6 +79,7 @@ (done) Add an option to use autoPVT when logging GNSS data: https://github.com/sparkfun/OpenLog_Artemis/issues/50 (done) Corrected an issue when using multiple MS8607's: https://github.com/sparkfun/OpenLog_Artemis/issues/62 (done) Add a feature to use the TX and RX pins as a duplicate Terminal + (done) Add serial log timestamps with a token (as suggested by @DennisMelamed in PR https://github.com/sparkfun/OpenLog_Artemis/pull/70 and Issue https://github.com/sparkfun/OpenLog_Artemis/issues/63) */ const int FIRMWARE_VERSION_MAJOR = 1; @@ -233,6 +234,7 @@ unsigned long qwiicPowerOnDelayMillis; //Wait for this many milliseconds after t 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) volatile static bool triggerEdgeSeen = false; //Flag to indicate if a trigger interrupt has been seen +char serialTimestamp[40]; //Buffer to store serial timestamp, if needed //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- uint8_t getByteChoice(int numberOfSeconds, bool updateDZSERIAL = false); // Header @@ -256,12 +258,9 @@ void DoSerialPrint(char (*)(const char *), const char *, bool newLine = false); // The Serial port for the Zmodem connection // must not be the same as DSERIAL unless all // debugging output to DSERIAL is removed -//#define ZSERIAL Serial3 -//#define ZSERIAL Serial Stream *ZSERIAL; -// Serial output for debugging info -//#define DSERIAL Serial +// Serial output for debugging info for Zmodem Stream *DSERIAL; void setup() { @@ -279,6 +278,7 @@ void setup() { digitalWrite(PIN_STAT_LED, HIGH); // Turn the STAT LED on while we configure everything Serial.begin(115200); //Default for initial debug messages if necessary + SerialLog.begin(115200); //Default for initial debug messages if necessary SerialPrintln(F("")); SPI.begin(); //Needed if SD is disabled @@ -302,7 +302,11 @@ void setup() { if (settings.useTxRxPinsForTerminal == true) { - SerialLog.begin(settings.serialTerminalBaudRate); // Start the serial port + SerialLog.begin(settings.serialTerminalBaudRate); // Restart the serial port + } + else + { + SerialLog.end(); // Stop the SerialLog port } Serial.flush(); //Complete any previous prints @@ -341,6 +345,8 @@ void setup() { beginDataLogging(); //180ms lastSDFileNameChangeTime = rtcMillis(); // Record the time of the file name change + serialTimestamp[0] = '\0'; // Empty the serial timestamp buffer + if (settings.useTxRxPinsForTerminal == false) { beginSerialLogging(); //20 - 99ms @@ -402,11 +408,39 @@ void loop() { if (settings.logSerial == true && online.serialLogging == true && settings.useTxRxPinsForTerminal == false) { - if (SerialLog.available()) + size_t timestampCharsLeftToWrite = strlen(serialTimestamp); + //SerialPrintf2("timestampCharsLeftToWrite is %d\r\n", timestampCharsLeftToWrite); + //SerialFlush(); + + if (SerialLog.available() || (timestampCharsLeftToWrite > 0)) { - while (SerialLog.available()) + while (SerialLog.available() || (timestampCharsLeftToWrite > 0)) { - incomingBuffer[incomingBufferSpot++] = SerialLog.read(); + if (timestampCharsLeftToWrite > 0) // Based on code written by @DennisMelamed in PR #70 + { + incomingBuffer[incomingBufferSpot++] = serialTimestamp[0]; // Add a timestamp character to incomingBuffer + + for (size_t i = 0; i < timestampCharsLeftToWrite; i++) + { + serialTimestamp[i] = serialTimestamp[i+1]; // Shuffle the remaining chars along by one + } + + timestampCharsLeftToWrite -= 1; + } + else + { + incomingBuffer[incomingBufferSpot++] = SerialLog.read(); + + //Get the RTC timestamp if we just received the timestamp token + if (settings.timestampSerial && (incomingBuffer[incomingBufferSpot-1] == settings.timeStampToken)) + { + getTimeString(&serialTimestamp[2]); + serialTimestamp[0] = 0x0A; // Add Line Feed at the start of the timestamp + serialTimestamp[1] = '^'; // Add an up-arrow to indicate the timestamp relates to the preceeding data + serialTimestamp[strlen(serialTimestamp) - 1] = 0x0A; // Change the final comma of the timestamp to a Line Feed + } + } + if (incomingBufferSpot == sizeof(incomingBuffer)) { digitalWrite(PIN_STAT_LED, HIGH); //Toggle stat LED to indicating log recording diff --git a/Firmware/OpenLog_Artemis/Sensors.ino b/Firmware/OpenLog_Artemis/Sensors.ino index 9f3180c..279a574 100644 --- a/Firmware/OpenLog_Artemis/Sensors.ino +++ b/Firmware/OpenLog_Artemis/Sensors.ino @@ -9,44 +9,10 @@ void getData() if (settings.logRTC) { - //Decide if we are using the internal RTC or GPS for timestamps - if (settings.getRTCfromGPS == false) - { - myRTC.getTime(); - - if (settings.logDate) - { - char rtcDate[12]; //10/12/2019, - if (settings.americanDateStyle == true) - sprintf(rtcDate, "%02d/%02d/20%02d,", myRTC.month, myRTC.dayOfMonth, myRTC.year); - else - sprintf(rtcDate, "%02d/%02d/20%02d,", myRTC.dayOfMonth, myRTC.month, myRTC.year); - strcat(outputData, rtcDate); - } - - if (settings.logTime) - { - char rtcTime[13]; //09:14:37.41, - int adjustedHour = myRTC.hour; - if (settings.hour24Style == false) - { - if (adjustedHour > 12) adjustedHour -= 12; - } - sprintf(rtcTime, "%02d:%02d:%02d.%02d,", adjustedHour, myRTC.minute, myRTC.seconds, myRTC.hundredths); - strcat(outputData, rtcTime); - } - - if (settings.logMicroseconds) - { - char microseconds[11]; // - sprintf(microseconds, "%lu,", micros()); - strcat(outputData, microseconds); - } - } //end if use RTC for timestamp - else //Use GPS for timestamp - { - SerialPrintln(F("Print GPS Timestamp / not yet implemented")); - } + //Code written by @DennisMelamed in PR #70 + char timeString[37]; + getTimeString(timeString); // getTimeString is in timeStamp.ino + strcat(outputData, timeString); } if (settings.logA11) @@ -821,19 +787,12 @@ void printHelperText(bool terminalOnly) if (settings.logRTC) { - //Decide if we are using the internal RTC or GPS for timestamps - if (settings.getRTCfromGPS == false) - { - if (settings.logDate) - strcat(helperText, "rtcDate,"); - if (settings.logTime) - strcat(helperText, "rtcTime,"); - if (settings.logMicroseconds) - strcat(helperText, "micros,"); - } - } //end if use RTC for timestamp - else //Use GPS for timestamp - { + if (settings.logDate) + strcat(helperText, "rtcDate,"); + if (settings.logTime) + strcat(helperText, "rtcTime,"); + if (settings.logMicroseconds) + strcat(helperText, "micros,"); } if (settings.logA11) diff --git a/Firmware/OpenLog_Artemis/menuSerialLogging.ino b/Firmware/OpenLog_Artemis/menuSerialLogging.ino index 1d01bfc..350bcea 100644 --- a/Firmware/OpenLog_Artemis/menuSerialLogging.ino +++ b/Firmware/OpenLog_Artemis/menuSerialLogging.ino @@ -30,6 +30,40 @@ void menuSerialLogging() SerialPrintln(F(" bps")); } + if (settings.logSerial == true) // Suggested by @DennisMelamed in Issue #63 + { + SerialPrint(F("5) Add RTC timestamp when token is received: ")); + if (settings.timestampSerial == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); + + SerialPrint(F("6) Timestamp token: ")); + Serial.print(settings.timeStampToken); + if (settings.useTxRxPinsForTerminal == true) + SerialLog.print(settings.timeStampToken); + SerialPrint(F(" (Decimal)")); + switch (settings.timeStampToken) + { + case 0x00: + SerialPrintln(F(" = NULL")); + break; + case 0x03: + SerialPrintln(F(" = End of Text")); + break; + case 0x0A: + SerialPrintln(F(" = Line Feed")); + break; + case 0x0D: + SerialPrintln(F(" = Carriage Return")); + break; + case 0x1B: + SerialPrintln(F(" = Escape")); + break; + default: + SerialPrintln(F("")); + break; + } + } + SerialPrintln(F("x) Exit")); byte incoming = getByteChoice(menuTimeout); //Timeout after x seconds @@ -100,6 +134,21 @@ void menuSerialLogging() SerialLog.begin(settings.serialLogBaudRate); } } + else if (incoming == '5') + settings.timestampSerial ^= 1; + else if (incoming == '6') + { + SerialPrint(F("Enter the timestamp token in decimal (0 to 255): ")); + int newToken = getNumber(menuTimeout); //Timeout after x seconds + if (newToken < 0 || newToken > 255) + { + SerialPrintln(F("Error: token out of range")); + } + else + { + settings.timeStampToken = (uint8_t)newToken; + } + } else if (incoming == 'x') return; else if (incoming == STATUS_GETBYTE_TIMEOUT) diff --git a/Firmware/OpenLog_Artemis/nvm.ino b/Firmware/OpenLog_Artemis/nvm.ino index a0e8b29..b6f54c5 100644 --- a/Firmware/OpenLog_Artemis/nvm.ino +++ b/Firmware/OpenLog_Artemis/nvm.ino @@ -117,7 +117,6 @@ void recordSystemSettingsToFile() settingsFile.println("logIMUTemp=" + (String)settings.logIMUTemp); settingsFile.println("logRTC=" + (String)settings.logRTC); settingsFile.println("logHertz=" + (String)settings.logHertz); - settingsFile.println("getRTCfromGPS=" + (String)settings.getRTCfromGPS); settingsFile.println("correctForDST=" + (String)settings.correctForDST); settingsFile.println("americanDateStyle=" + (String)settings.americanDateStyle); settingsFile.println("hour24Style=" + (String)settings.hour24Style); @@ -156,6 +155,8 @@ void recordSystemSettingsToFile() settingsFile.println("imuGyroDLPFBW=" + (String)settings.imuGyroDLPFBW); settingsFile.println("logMicroseconds=" + (String)settings.logMicroseconds); settingsFile.println("useTxRxPinsForTerminal=" + (String)settings.useTxRxPinsForTerminal); + settingsFile.println("timestampSerial=" + (String)settings.timestampSerial); + settingsFile.println("timeStampToken=" + (String)settings.timeStampToken); updateDataFileAccess(&settingsFile); // Update the file access time & date settingsFile.close(); } @@ -322,8 +323,6 @@ bool parseLine(char* str) { settings.logRTC = d; else if (strcmp(settingName, "logHertz") == 0) settings.logHertz = d; - else if (strcmp(settingName, "getRTCfromGPS") == 0) - settings.getRTCfromGPS = d; else if (strcmp(settingName, "correctForDST") == 0) settings.correctForDST = d; else if (strcmp(settingName, "americanDateStyle") == 0) @@ -400,8 +399,15 @@ bool parseLine(char* str) { settings.logMicroseconds = d; else if (strcmp(settingName, "useTxRxPinsForTerminal") == 0) settings.useTxRxPinsForTerminal = d; + else if (strcmp(settingName, "timestampSerial") == 0) + settings.timestampSerial = d; + else if (strcmp(settingName, "timeStampToken") == 0) + settings.timeStampToken = d; else - SerialPrintf3("Unknown setting %s on line: %s\r\n", settingName, str); + { + SerialPrintf2("Unknown setting %s. Ignoring...\r\n", settingName); + return(false); + } return (true); } diff --git a/Firmware/OpenLog_Artemis/settings.h b/Firmware/OpenLog_Artemis/settings.h index bd769e7..383523e 100644 --- a/Firmware/OpenLog_Artemis/settings.h +++ b/Firmware/OpenLog_Artemis/settings.h @@ -309,7 +309,6 @@ struct struct_settings { bool logIMUTemp = true; bool logRTC = true; bool logHertz = true; - bool getRTCfromGPS = false; bool correctForDST = false; bool americanDateStyle = true; bool hour24Style = true; @@ -352,6 +351,8 @@ struct struct_settings { int imuGyroDLPFBW = 7; // IMU gyro DLPF bandwidth - default to gyr_d361bw4_n376bw5 (ICM_20948_GYRO_CONFIG_1_DLPCFG_e) bool logMicroseconds = false; // Log micros() bool useTxRxPinsForTerminal = false; // If true, the terminal is echo'd to the Tx and Rx pins. Note: setting this to true will _permanently_ disable serial logging and analog input on those pins! + bool timestampSerial = false; // If true, the RTC time will be added to the serial log file when timeStampToken is received + uint8_t timeStampToken = 0x0A; // Add RTC time to the serial log when this token is received. Default to Line Feed (0x0A). Suggested by @DennisMelamed in Issue #63 } settings; //These are the devices on board OpenLog that may be on or offline. diff --git a/Firmware/OpenLog_Artemis/timeStamp.ino b/Firmware/OpenLog_Artemis/timeStamp.ino index 7786248..e0288f2 100644 --- a/Firmware/OpenLog_Artemis/timeStamp.ino +++ b/Firmware/OpenLog_Artemis/timeStamp.ino @@ -1,54 +1,41 @@ -//Gets the current time from GPS -//Adjust the hour by local hour offset -//Adjust the hour by DST as necessary -//Adjust the date as necessary -//Returns a string according to user's settings -//Leap year is taken into account but does not interact with DST (DST happens later in March) -// -//Note: this function should only be called if we know that a u-blox GNSS is actually connected -// -String getGPSDateTimeAsStr() { - //Get latested date/time from GPS -// int year = gpsSensor_ublox.getYear(); -// int month = gpsSensor_ublox.getMonth(); -// int day = gpsSensor_ublox.getDay(); -// int hour = gpsSensor_ublox.getHour(); -// int minute = gpsSensor_ublox.getMinute(); -// int second = gpsSensor_ublox.getSecond(); - - int year = 19; - int month = 1; - int day = 1; - int hour = 6; - int minute = 14; - int second = 37; - - adjustToLocalDateTime(year, month, day, hour, settings.localUTCOffset); - - if (settings.hour24Style == false) - { - if (hour > 12) hour -= 12; - } +//Query the RTC and put the appropriately formatted (according to settings) +//string into the passed buffer. timeStringBuffer should be at least 37 chars long +//Code modified by @DennisMelamed in PR #70 +void getTimeString(char timeStringBuffer[]) +{ + //reset the buffer + timeStringBuffer[0] = '\0'; - String myTime = ""; + myRTC.getTime(); - if (settings.logDate == true) + if (settings.logDate) { - char gpsDate[11]; //10/12/2019 + char rtcDate[12]; //10/12/2019, if (settings.americanDateStyle == true) - sprintf(gpsDate, "%02d/%02d/20%02d", month, day, year); + sprintf(rtcDate, "%02d/%02d/20%02d,", myRTC.month, myRTC.dayOfMonth, myRTC.year); else - sprintf(gpsDate, "%02d/%02d/20%02d", day, month, year); - myTime += String(gpsDate); - myTime += ","; + sprintf(rtcDate, "%02d/%02d/20%02d,", myRTC.dayOfMonth, myRTC.month, myRTC.year); + strcat(timeStringBuffer, rtcDate); } - char gpsTime[13]; //09:14:37.412 - sprintf(gpsTime, "%02d:%02d:%02d.%03d", hour, minute, second, millis() % 1000); //TODO get GPS hundredths() - myTime += String(gpsTime); - myTime += ","; - - return (myTime); + if (settings.logTime) + { + char rtcTime[13]; //09:14:37.41, + int adjustedHour = myRTC.hour; + if (settings.hour24Style == false) + { + if (adjustedHour > 12) adjustedHour -= 12; + } + sprintf(rtcTime, "%02d:%02d:%02d.%02d,", adjustedHour, myRTC.minute, myRTC.seconds, myRTC.hundredths); + strcat(timeStringBuffer, rtcTime); + } + + if (settings.logMicroseconds) + { + char microseconds[11]; // + sprintf(microseconds, "%lu,", micros()); + strcat(timeStringBuffer, microseconds); + } } //Gets the current time from GPS From 61c1118a16d418b133f2eab575cf0a24d7d1d5cb Mon Sep 17 00:00:00 2001 From: PaulZC Date: Fri, 29 Jan 2021 16:35:04 +0000 Subject: [PATCH 06/14] Adding "sleep on pin" and "wake at specified time" functionality These changes are based on code by @ryanneve in PR #64 and suggestions in Issue #46 --- Firmware/OpenLog_Artemis/OpenLog_Artemis.ino | 95 ++++++++++++- Firmware/OpenLog_Artemis/lowerPower.ino | 24 +--- Firmware/OpenLog_Artemis/menuTerminal.ino | 134 ++++++++++++++++++- Firmware/OpenLog_Artemis/nvm.ino | 18 +++ Firmware/OpenLog_Artemis/settings.h | 7 + 5 files changed, 254 insertions(+), 24 deletions(-) diff --git a/Firmware/OpenLog_Artemis/OpenLog_Artemis.ino b/Firmware/OpenLog_Artemis/OpenLog_Artemis.ino index 0766d2f..1ad38cf 100644 --- a/Firmware/OpenLog_Artemis/OpenLog_Artemis.ino +++ b/Firmware/OpenLog_Artemis/OpenLog_Artemis.ino @@ -80,6 +80,8 @@ (done) Corrected an issue when using multiple MS8607's: https://github.com/sparkfun/OpenLog_Artemis/issues/62 (done) Add a feature to use the TX and RX pins as a duplicate Terminal (done) Add serial log timestamps with a token (as suggested by @DennisMelamed in PR https://github.com/sparkfun/OpenLog_Artemis/pull/70 and Issue https://github.com/sparkfun/OpenLog_Artemis/issues/63) + (done?) Add "sleep on pin" functionality based @ryanneve's PR https://github.com/sparkfun/OpenLog_Artemis/pull/64 and Issue https://github.com/sparkfun/OpenLog_Artemis/issues/46 + (done?) Add "wake at specified times" functionality based on Issue https://github.com/sparkfun/OpenLog_Artemis/issues/46 */ const int FIRMWARE_VERSION_MAJOR = 1; @@ -395,7 +397,7 @@ void setup() { //If we are immediately going to go to sleep after the first reading then //first present the user with the config menu in case they need to change something - if (settings.usBetweenReadings >= maxUsBeforeSleep) + if (checkIfItIsTimeToSleep()) menuMain(); } @@ -613,12 +615,97 @@ 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 - //Go to sleep if the time between readings is greater than maxUsBeforeSleep (2 seconds) and triggering is not enabled - if ((settings.useGPIO11ForTrigger == false) && (settings.usBetweenReadings >= maxUsBeforeSleep)) + // Code changes here are based on suggestions by @ryanneve in Issue #46 and PR #64 + if (checkIfItIsTimeToSleep()) + { + goToSleep(howLongToSleepFor()); + } + } +} + +uint32_t howLongToSleepFor(void) +{ + //Counter/Timer 6 will use the 32kHz clock + //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 + + uint32_t msToSleep; + + if (checkSleepOnRTCTime() || checkSleepOnFastSlowPin()) + msToSleep = (uint32_t)(settings.slowLoggingIntervalSeconds * 1000UL); + else + msToSleep = (uint32_t)(settings.usBetweenReadings / 1000ULL); + + uint32_t sysTicksToSleep; + if (msToSleep < 131000) + { + sysTicksToSleep = msToSleep * 32768L; // Do the multiply first for short intervals + sysTicksToSleep = sysTicksToSleep / 1000L; // Now do the divide + } + else + { + sysTicksToSleep = msToSleep / 1000L; // Do the division first for long intervals (to avoid an overflow) + sysTicksToSleep = sysTicksToSleep * 32768L; // Now do the multiply + } + + return (sysTicksToSleep); +} + +bool checkIfItIsTimeToSleep(void) +{ + + if (checkSleepOnUsBetweenReadings() + || checkSleepOnRTCTime() + || checkSleepOnFastSlowPin()) + return(true); + else + return(false); +} + +//Go to sleep if the time between readings is greater than maxUsBeforeSleep (2 seconds) and triggering is not enabled +bool checkSleepOnUsBetweenReadings(void) +{ + if ((settings.useGPIO11ForTrigger == false) && (settings.usBetweenReadings >= maxUsBeforeSleep)) + return (true); + else + return (false); +} + +//Go to sleep if Fast/Slow logging on Pin 11 is enabled and Pin 11 is in the correct state +bool checkSleepOnFastSlowPin(void) +{ + if ((settings.useGPIO11ForFastSlowLogging == true) && (digitalRead(PIN_TRIGGER) == settings.slowLoggingWhenPin11Is)) + return (true); + else + return (false); +} + +// Go to sleep if useRTCForFastSlowLogging is enabled and RTC time is between the start and stop times +bool checkSleepOnRTCTime(void) +{ + // Check if we should be sleeping based on useGPIO11ForFastSlowLogging and slowLoggingStartMOD + slowLoggingStopMOD + bool sleepOnRTCTime = false; + if (settings.useRTCForFastSlowLogging == true) + { + if (settings.slowLoggingStartMOD != settings.slowLoggingStopMOD) // Only perform the check if the start and stop times are not equal { - goToSleep(); + myRTC.getTime(); // Get the RTC time + int minutesOfDay = (myRTC.hour * 60) + myRTC.minute; + + if (settings.slowLoggingStartMOD > settings.slowLoggingStopMOD) // If slow logging starts later than the stop time (i.e. slow over midnight) + { + if ((minutesOfDay >= settings.slowLoggingStartMOD) || (minutesOfDay < settings.slowLoggingStopMOD)) + sleepOnRTCTime = true; + } + else // Slow logging starts earlier than the stop time + { + if ((minutesOfDay >= settings.slowLoggingStartMOD) && (minutesOfDay < settings.slowLoggingStopMOD)) + sleepOnRTCTime = true; + } } } + return(sleepOnRTCTime); } void beginQwiic() diff --git a/Firmware/OpenLog_Artemis/lowerPower.ino b/Firmware/OpenLog_Artemis/lowerPower.ino index 6d43889..110450b 100644 --- a/Firmware/OpenLog_Artemis/lowerPower.ino +++ b/Firmware/OpenLog_Artemis/lowerPower.ino @@ -154,25 +154,8 @@ void powerDown() } //Power everything down and wait for interrupt wakeup -void goToSleep() +void goToSleep(uint32_t sysTicksToSleep) { - //Counter/Timer 6 will use the 32kHz clock - //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 - uint32_t msToSleep = (uint32_t)(settings.usBetweenReadings / 1000ULL); - uint32_t sysTicksToSleep; - if (msToSleep < 131000) - { - sysTicksToSleep = msToSleep * 32768L; // Do the multiply first for short intervals - sysTicksToSleep = sysTicksToSleep / 1000L; // Now do the divide - } - else - { - sysTicksToSleep = msToSleep / 1000L; // Do the division first for long intervals (to avoid an overflow) - sysTicksToSleep = sysTicksToSleep * 32768L; // Now do the multiply - } - //printDebug("goToSleep: sysTicksToSleep = " + (String)sysTicksToSleep + "\r\n"); //printDebug("goToSleep: online.IMU = " + (String)online.IMU + "\r\n"); @@ -360,6 +343,11 @@ void wakeFromSleep() triggerEdgeSeen = false; // Make sure the flag is clear } + if (settings.useGPIO11ForFastSlowLogging == true) + { + pinMode(PIN_TRIGGER, INPUT_PULLUP); + } + pinMode(PIN_STAT_LED, OUTPUT); digitalWrite(PIN_STAT_LED, LOW); diff --git a/Firmware/OpenLog_Artemis/menuTerminal.ino b/Firmware/OpenLog_Artemis/menuTerminal.ino index 19668a4..0a9d9fe 100644 --- a/Firmware/OpenLog_Artemis/menuTerminal.ino +++ b/Firmware/OpenLog_Artemis/menuTerminal.ino @@ -98,6 +98,40 @@ void menuLogRate() } else SerialPrintln(F("Disabled")); + SerialPrint(F("15) Use Pin 11 to control fast/slow logging: ")); + if (settings.useGPIO11ForFastSlowLogging == true) SerialPrintln(F("Yes")); + else SerialPrintln(F("No")); + + if (settings.useGPIO11ForFastSlowLogging == true) + { + SerialPrint(F("16) Log slowly when Pin 11 is: ")); + if (settings.slowLoggingWhenPin11Is == true) SerialPrintln(F("High")); + else SerialPrintln(F("Low")); + } + + SerialPrint(F("17) Use RTC to control fast/slow logging: ")); + if (settings.useRTCForFastSlowLogging == true) SerialPrintln(F("Yes")); + else SerialPrintln(F("No")); + + if ((settings.useGPIO11ForFastSlowLogging == true) || (settings.useRTCForFastSlowLogging == true)) + { + SerialPrint(F("18) Slow logging interval (seconds): ")); + SerialPrintf2("%d\r\n", settings.slowLoggingIntervalSeconds); + } + + if (settings.useRTCForFastSlowLogging == true) + { + SerialPrint(F("19) Slow logging starts at (minute of day): ")); + int slowHour = settings.slowLoggingStartMOD / 60; + int slowMin = settings.slowLoggingStartMOD % 60; + SerialPrintf4("%d (%02d:%02d)\r\n", settings.slowLoggingStartMOD, slowHour, slowMin); + + SerialPrint(F("20) Slow logging ends at (minute of day): ")); + slowHour = settings.slowLoggingStopMOD / 60; + slowMin = settings.slowLoggingStopMOD % 60; + SerialPrintf4("%d (%02d:%02d)\r\n", settings.slowLoggingStopMOD, slowHour, slowMin); + } + SerialPrintln(F("x) Exit")); int incoming = getNumber(menuTimeout); //Timeout after x seconds @@ -147,10 +181,10 @@ void menuLogRate() //The Deep Sleep duration is set with am_hal_stimer_compare_delta_set, the duration of which is uint32_t //So the maximum we can sleep for is 2^32 / 32768 = 131072 seconds = 36.4 hours //Let's limit this to 36 hours = 129600 seconds - SerialPrintln(F("How many seconds would you like to sleep between readings? (1 to 129,600):")); + SerialPrintln(F("How many seconds would you like to wait between readings? (1 to 129,600):")); int64_t tempSeconds = getNumber(menuTimeout); //Timeout after x seconds if (tempSeconds < 1 || tempSeconds > 129600) - SerialPrintln(F("Error: Readings Per Second out of range")); + SerialPrintln(F("Error: logging interval out of range")); else settings.usBetweenReadings = 1000000ULL * ((uint64_t)tempSeconds); } @@ -230,6 +264,8 @@ void menuLogRate() 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 if (incoming == 13) @@ -286,6 +322,100 @@ void menuLogRate() SerialPrintln(F("")); } } + else if (incoming == 15) + { + if (settings.useGPIO11ForFastSlowLogging == false) // If the user is trying to enable Pin 11 fast / slow logging + { + 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 + } + } + else if (incoming == 16) + { + if (settings.useGPIO11ForFastSlowLogging == true) + { + settings.slowLoggingWhenPin11Is ^= 1; + } + } + else if (incoming == 17) + { + if (settings.useRTCForFastSlowLogging == false) // If the user is trying to enable RTC fast / slow logging + { + settings.useRTCForFastSlowLogging = true; + if (settings.useGPIO11ForFastSlowLogging == true) + { + pinMode(PIN_TRIGGER, INPUT); // Remove the pull-up + } + settings.useGPIO11ForFastSlowLogging = false; + settings.logA11 = false; // Disable analog logging on pin 11 + // Disable triggering + if (settings.useGPIO11ForTrigger == true) + { + detachInterrupt(digitalPinToInterrupt(PIN_TRIGGER)); // Disable the interrupt + pinMode(PIN_TRIGGER, INPUT); // Remove the pull-up + triggerEdgeSeen = false; // Make sure the flag is clear + } + settings.useGPIO11ForTrigger = false; + } + else // If the user is trying to disable RTC fast / slow logging + { + settings.useRTCForFastSlowLogging = false; + } + } + else if (incoming == 18) + { + if ((settings.useGPIO11ForFastSlowLogging == true) || (settings.useRTCForFastSlowLogging == true)) + { + //The Deep Sleep duration is set with am_hal_stimer_compare_delta_set, the duration of which is uint32_t + //So the maximum we can sleep for is 2^32 / 32768 = 131072 seconds = 36.4 hours + //Let's limit this to 36 hours = 129600 seconds + SerialPrintln(F("How many seconds would you like to sleep between readings? (5 to 129,600):")); + int64_t tempSeconds = getNumber(menuTimeout); //Timeout after x seconds + if (tempSeconds < 5 || tempSeconds > 129600) + SerialPrintln(F("Error: sleep interval out of range")); + else + settings.slowLoggingIntervalSeconds = (int)tempSeconds; + } + } + else if (incoming == 19) + { + if (settings.useRTCForFastSlowLogging == true) + { + SerialPrintln(F("Enter the time slow logging should start in Minutes Of Day (0 to 1439) (E.g. 1260 = 21:00):")); + int64_t tempMOD = getNumber(menuTimeout); //Timeout after x seconds + if (tempMOD < 1 || tempMOD > 1439) + SerialPrintln(F("Error: time out of range")); + else + settings.slowLoggingStartMOD = (int)tempMOD; + } + } + else if (incoming == 20) + { + if (settings.useRTCForFastSlowLogging == true) + { + SerialPrintln(F("Enter the time slow logging should stop in Minutes Of Day (0 to 1439) (E.g. 420 = 07:00):")); + int64_t tempMOD = getNumber(menuTimeout); //Timeout after x seconds + if (tempMOD < 1 || tempMOD > 1439) + SerialPrintln(F("Error: time out of range")); + else + settings.slowLoggingStopMOD = (int)tempMOD; + } + } 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 b6f54c5..0e1c2c4 100644 --- a/Firmware/OpenLog_Artemis/nvm.ino +++ b/Firmware/OpenLog_Artemis/nvm.ino @@ -157,6 +157,12 @@ void recordSystemSettingsToFile() settingsFile.println("useTxRxPinsForTerminal=" + (String)settings.useTxRxPinsForTerminal); settingsFile.println("timestampSerial=" + (String)settings.timestampSerial); settingsFile.println("timeStampToken=" + (String)settings.timeStampToken); + settingsFile.println("useGPIO11ForFastSlowLogging=" + (String)settings.useGPIO11ForFastSlowLogging); + settingsFile.println("slowLoggingWhenPin11Is=" + (String)settings.slowLoggingWhenPin11Is); + settingsFile.println("useRTCForFastSlowLogging=" + (String)settings.useRTCForFastSlowLogging); + settingsFile.println("slowLoggingIntervalSeconds=" + (String)settings.slowLoggingIntervalSeconds); + settingsFile.println("slowLoggingStartMOD=" + (String)settings.slowLoggingStartMOD); + settingsFile.println("slowLoggingStopMOD=" + (String)settings.slowLoggingStopMOD); updateDataFileAccess(&settingsFile); // Update the file access time & date settingsFile.close(); } @@ -403,6 +409,18 @@ bool parseLine(char* str) { settings.timestampSerial = d; else if (strcmp(settingName, "timeStampToken") == 0) settings.timeStampToken = d; + else if (strcmp(settingName, "useGPIO11ForFastSlowLogging") == 0) + settings.useGPIO11ForFastSlowLogging = d; + else if (strcmp(settingName, "slowLoggingWhenPin11Is") == 0) + settings.slowLoggingWhenPin11Is = d; + else if (strcmp(settingName, "useRTCForFastSlowLogging") == 0) + settings.useRTCForFastSlowLogging = d; + else if (strcmp(settingName, "slowLoggingIntervalSeconds") == 0) + settings.slowLoggingIntervalSeconds = d; + else if (strcmp(settingName, "slowLoggingStartMOD") == 0) + settings.slowLoggingStartMOD = d; + else if (strcmp(settingName, "slowLoggingStopMOD") == 0) + settings.slowLoggingStopMOD = d; else { SerialPrintf2("Unknown setting %s. Ignoring...\r\n", settingName); diff --git a/Firmware/OpenLog_Artemis/settings.h b/Firmware/OpenLog_Artemis/settings.h index 383523e..a77c21e 100644 --- a/Firmware/OpenLog_Artemis/settings.h +++ b/Firmware/OpenLog_Artemis/settings.h @@ -353,6 +353,13 @@ struct struct_settings { bool useTxRxPinsForTerminal = false; // If true, the terminal is echo'd to the Tx and Rx pins. Note: setting this to true will _permanently_ disable serial logging and analog input on those pins! bool timestampSerial = false; // If true, the RTC time will be added to the serial log file when timeStampToken is received uint8_t timeStampToken = 0x0A; // Add RTC time to the serial log when this token is received. Default to Line Feed (0x0A). Suggested by @DennisMelamed in Issue #63 + bool useGPIO11ForFastSlowLogging = false; // If true, Pin 11 will control if readings are taken quickly or slowly. Suggested by @ryanneve in Issue #46 and PR #64 + bool slowLoggingWhenPin11Is = false; // Controls the polarity of Pin 11 for fast / slow logging + bool useRTCForFastSlowLogging = false; // If true, logging will be slow during the specified times + int slowLoggingIntervalSeconds = 300; // Slow logging interval in seconds. Default to 5 mins + 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 + } settings; //These are the devices on board OpenLog that may be on or offline. From f1ff99631529077004ddb9fbd9525430745c5c6f Mon Sep 17 00:00:00 2001 From: PaulZC Date: Sat, 30 Jan 2021 10:10:05 +0000 Subject: [PATCH 07/14] Correcting logHertz. Slow logging start and end times now entered as hour and minute - not minute-of-day. --- Firmware/OpenLog_Artemis/OpenLog_Artemis.ino | 7 ++-- Firmware/OpenLog_Artemis/Sensors.ino | 6 ++-- Firmware/OpenLog_Artemis/menuMain.ino | 7 ++-- Firmware/OpenLog_Artemis/menuTerminal.ino | 36 ++++++++++++++------ 4 files changed, 40 insertions(+), 16 deletions(-) diff --git a/Firmware/OpenLog_Artemis/OpenLog_Artemis.ino b/Firmware/OpenLog_Artemis/OpenLog_Artemis.ino index 1ad38cf..6c9ffa6 100644 --- a/Firmware/OpenLog_Artemis/OpenLog_Artemis.ino +++ b/Firmware/OpenLog_Artemis/OpenLog_Artemis.ino @@ -385,8 +385,11 @@ void setup() { if (settings.showHelperText == true) printHelperText(false); //printHelperText to terminal and sensor file - //If we are sleeping between readings then we cannot rely on millis() as it is powered down. Used RTC instead. - if (settings.usBetweenReadings >= maxUsBeforeSleep) + //If we are sleeping between readings then we cannot rely on millis() as it is powered down + //Use RTC instead + if (((settings.useGPIO11ForTrigger == false) && (settings.usBetweenReadings >= maxUsBeforeSleep)) + || (settings.useGPIO11ForFastSlowLogging == true) + || (settings.useRTCForFastSlowLogging == true)) measurementStartTime = rtcMillis(); else measurementStartTime = millis(); diff --git a/Firmware/OpenLog_Artemis/Sensors.ino b/Firmware/OpenLog_Artemis/Sensors.ino index 279a574..d05262c 100644 --- a/Firmware/OpenLog_Artemis/Sensors.ino +++ b/Firmware/OpenLog_Artemis/Sensors.ino @@ -127,8 +127,10 @@ void getData() uint64_t currentMillis; //If we are sleeping between readings then we cannot rely on millis() as it is powered down - //Used RTC instead - if (settings.usBetweenReadings >= maxUsBeforeSleep) + //Use RTC instead + if (((settings.useGPIO11ForTrigger == false) && (settings.usBetweenReadings >= maxUsBeforeSleep)) + || (settings.useGPIO11ForFastSlowLogging == true) + || (settings.useRTCForFastSlowLogging == true)) { currentMillis = rtcMillis(); } diff --git a/Firmware/OpenLog_Artemis/menuMain.ino b/Firmware/OpenLog_Artemis/menuMain.ino index 6a3faa8..4ae9c47 100644 --- a/Firmware/OpenLog_Artemis/menuMain.ino +++ b/Firmware/OpenLog_Artemis/menuMain.ino @@ -169,8 +169,11 @@ void menuMain() //Reset measurements measurementCount = 0; totalCharactersPrinted = 0; - //If we are sleeping between readings then we cannot rely on millis() as it is powered down. Used RTC instead. - if (settings.usBetweenReadings >= maxUsBeforeSleep) + //If we are sleeping between readings then we cannot rely on millis() as it is powered down + //Use RTC instead + if (((settings.useGPIO11ForTrigger == false) && (settings.usBetweenReadings >= maxUsBeforeSleep)) + || (settings.useGPIO11ForFastSlowLogging == true) + || (settings.useRTCForFastSlowLogging == true)) measurementStartTime = rtcMillis(); else measurementStartTime = millis(); diff --git a/Firmware/OpenLog_Artemis/menuTerminal.ino b/Firmware/OpenLog_Artemis/menuTerminal.ino index 0a9d9fe..972ec2b 100644 --- a/Firmware/OpenLog_Artemis/menuTerminal.ino +++ b/Firmware/OpenLog_Artemis/menuTerminal.ino @@ -121,15 +121,15 @@ void menuLogRate() if (settings.useRTCForFastSlowLogging == true) { - SerialPrint(F("19) Slow logging starts at (minute of day): ")); + SerialPrint(F("19) Slow logging starts at: ")); int slowHour = settings.slowLoggingStartMOD / 60; int slowMin = settings.slowLoggingStartMOD % 60; - SerialPrintf4("%d (%02d:%02d)\r\n", settings.slowLoggingStartMOD, slowHour, slowMin); + SerialPrintf3("%02d:%02d\r\n", slowHour, slowMin); - SerialPrint(F("20) Slow logging ends at (minute of day): ")); + SerialPrint(F("20) Slow logging ends at: ")); slowHour = settings.slowLoggingStopMOD / 60; slowMin = settings.slowLoggingStopMOD % 60; - SerialPrintf4("%d (%02d:%02d)\r\n", settings.slowLoggingStopMOD, slowHour, slowMin); + SerialPrintf3("%02d:%02d\r\n", slowHour, slowMin); } SerialPrintln(F("x) Exit")); @@ -396,24 +396,40 @@ void menuLogRate() { if (settings.useRTCForFastSlowLogging == true) { - SerialPrintln(F("Enter the time slow logging should start in Minutes Of Day (0 to 1439) (E.g. 1260 = 21:00):")); + SerialPrintln(F("Enter the Hour slow logging should start (0 to 23):")); int64_t tempMOD = getNumber(menuTimeout); //Timeout after x seconds - if (tempMOD < 1 || tempMOD > 1439) + if (tempMOD < 0 || tempMOD > 23) SerialPrintln(F("Error: time out of range")); else - settings.slowLoggingStartMOD = (int)tempMOD; + { + settings.slowLoggingStartMOD = (int)tempMOD * 60; // Convert to minutes + SerialPrintln(F("\r\nEnter the Minute slow logging should start (0 to 59):")); + tempMOD = getNumber(menuTimeout); //Timeout after x seconds + if (tempMOD < 0 || tempMOD > 59) + SerialPrintln(F("Error: time out of range")); + else + settings.slowLoggingStartMOD += (int)tempMOD; + } } } else if (incoming == 20) { if (settings.useRTCForFastSlowLogging == true) { - SerialPrintln(F("Enter the time slow logging should stop in Minutes Of Day (0 to 1439) (E.g. 420 = 07:00):")); + SerialPrintln(F("Enter the Hour slow logging should end (0 to 23):")); int64_t tempMOD = getNumber(menuTimeout); //Timeout after x seconds - if (tempMOD < 1 || tempMOD > 1439) + if (tempMOD < 0 || tempMOD > 23) SerialPrintln(F("Error: time out of range")); else - settings.slowLoggingStopMOD = (int)tempMOD; + { + settings.slowLoggingStopMOD = (int)tempMOD * 60; // Convert to minutes + SerialPrintln(F("\r\nEnter the Minute slow logging should end (0 to 59):")); + tempMOD = getNumber(menuTimeout); //Timeout after x seconds + if (tempMOD < 0 || tempMOD > 59) + SerialPrintln(F("Error: time out of range")); + else + settings.slowLoggingStopMOD += (int)tempMOD; + } } } else if (incoming == STATUS_PRESSED_X) From a48a1cc7d431393edea937e1d1db69f7824f244a Mon Sep 17 00:00:00 2001 From: PaulZC Date: Sat, 30 Jan 2021 16:42:04 +0000 Subject: [PATCH 08/14] Adjust msToSleep if checkSleepOnRTCTime is true and we need to wake before slowLoggingIntervalSeconds * 1000 --- Firmware/OpenLog_Artemis/OpenLog_Artemis.ino | 25 ++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/Firmware/OpenLog_Artemis/OpenLog_Artemis.ino b/Firmware/OpenLog_Artemis/OpenLog_Artemis.ino index 6c9ffa6..6b821ec 100644 --- a/Firmware/OpenLog_Artemis/OpenLog_Artemis.ino +++ b/Firmware/OpenLog_Artemis/OpenLog_Artemis.ino @@ -635,9 +635,30 @@ uint32_t howLongToSleepFor(void) uint32_t msToSleep; - if (checkSleepOnRTCTime() || checkSleepOnFastSlowPin()) + if (checkSleepOnFastSlowPin()) msToSleep = (uint32_t)(settings.slowLoggingIntervalSeconds * 1000UL); - else + else if (checkSleepOnRTCTime()) + { + // checkSleepOnRTCTime has returned true, so we know that we are between slowLoggingStartMOD and slowLoggingStopMOD + // We need to check how long it is until slowLoggingStopMOD (accounting for midnight!) and adjust the sleep duration + // if slowLoggingStopMOD occurs before slowLoggingIntervalSeconds + + msToSleep = (uint32_t)(settings.slowLoggingIntervalSeconds * 1000UL); // Default to this + + myRTC.getTime(); // Get the RTC time + long secondsOfDay = (myRTC.hour * 60 * 60) + (myRTC.minute * 60) + myRTC.seconds; + + long slowLoggingStopSOD = settings.slowLoggingStopMOD * 60; // Convert slowLoggingStop to seconds-of-day + + long secondsUntilStop = slowLoggingStopSOD - secondsOfDay; // Calculate how long it is until slowLoggingStop + + // If secondsUntilStop is negative then we know that now is before midnight and slowLoggingStop is after midnight + if (secondsUntilStop < 0) secondsUntilStop += 24 * 60 * 60; // Add a day's worth of seconds if required to make secondsUntilStop positive + + if (secondsUntilStop < settings.slowLoggingIntervalSeconds) // If we need to sleep for less than slowLoggingIntervalSeconds + 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); uint32_t sysTicksToSleep; From 910925dc18533790819ef2436d14ca2ff487f036 Mon Sep 17 00:00:00 2001 From: PaulZC Date: Mon, 1 Feb 2021 12:22:45 +0000 Subject: [PATCH 09/14] Adding SCD30 improvements - thank you paulvha --- Firmware/OpenLog_Artemis/OpenLog_Artemis.ino | 1 + Firmware/OpenLog_Artemis/autoDetect.ino | 6 +++--- Firmware/OpenLog_Artemis/settings.h | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Firmware/OpenLog_Artemis/OpenLog_Artemis.ino b/Firmware/OpenLog_Artemis/OpenLog_Artemis.ino index 6b821ec..5632082 100644 --- a/Firmware/OpenLog_Artemis/OpenLog_Artemis.ino +++ b/Firmware/OpenLog_Artemis/OpenLog_Artemis.ino @@ -82,6 +82,7 @@ (done) Add serial log timestamps with a token (as suggested by @DennisMelamed in PR https://github.com/sparkfun/OpenLog_Artemis/pull/70 and Issue https://github.com/sparkfun/OpenLog_Artemis/issues/63) (done?) Add "sleep on pin" functionality based @ryanneve's PR https://github.com/sparkfun/OpenLog_Artemis/pull/64 and Issue https://github.com/sparkfun/OpenLog_Artemis/issues/46 (done?) Add "wake at specified times" functionality based on Issue https://github.com/sparkfun/OpenLog_Artemis/issues/46 + (done?) Add corrections for the SCD30 based on Forum post by paulvha: https://forum.sparkfun.com/viewtopic.php?p=222455#p222455 */ const int FIRMWARE_VERSION_MAJOR = 1; diff --git a/Firmware/OpenLog_Artemis/autoDetect.ino b/Firmware/OpenLog_Artemis/autoDetect.ino index a8d83f6..69fa4fc 100644 --- a/Firmware/OpenLog_Artemis/autoDetect.ino +++ b/Firmware/OpenLog_Artemis/autoDetect.ino @@ -1124,11 +1124,11 @@ deviceType_e testDevice(uint8_t i2cAddress, uint8_t muxAddress, uint8_t portNumb if (sensor.begin(i2cAddress, qwiic) == true) //Address, Wire port return (DEVICE_TEMPERATURE_MCP9600); - //Confidence: Medium - Begin doesn't confirm anything, but a readMeasurement() must pass CRC check + //Confidence: High - begin now checks FW Ver CRC SCD30 sensor1; + //sensor1.enableDebugging(); if (sensor1.begin(qwiic) == true) //Wire port - if (sensor1.readMeasurement() == true) //This reads the measurement register and calculates a CRC on the interchange - return (DEVICE_CO2_SCD30); + return (DEVICE_CO2_SCD30); } break; case 0x62: diff --git a/Firmware/OpenLog_Artemis/settings.h b/Firmware/OpenLog_Artemis/settings.h index a77c21e..c440ed5 100644 --- a/Firmware/OpenLog_Artemis/settings.h +++ b/Firmware/OpenLog_Artemis/settings.h @@ -204,7 +204,7 @@ struct struct_SCD30 { bool logTemperature = true; int measurementInterval = 2; //2 seconds int altitudeCompensation = 0; //0 m above sea level - int ambientPressure = 835; //mBar STP + int ambientPressure = 1000; //mBar STP (Toto, I have a feeling we're not in Boulder anymore) int temperatureOffset = 0; //C - Be careful not to overwrite the value on the sensor unsigned long powerOnDelayMillis = 5000; // Wait for at least this many millis before communicating with this device }; From fc61e4bd9691700ed6e8b8db05265cae4d80f08f Mon Sep 17 00:00:00 2001 From: PaulZC Date: Mon, 1 Feb 2021 17:18:58 +0000 Subject: [PATCH 10/14] Try to stagger SCD30 readings to stagger the peak current draw --- Firmware/OpenLog_Artemis/autoDetect.ino | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Firmware/OpenLog_Artemis/autoDetect.ino b/Firmware/OpenLog_Artemis/autoDetect.ino index 69fa4fc..2227aaa 100644 --- a/Firmware/OpenLog_Artemis/autoDetect.ino +++ b/Firmware/OpenLog_Artemis/autoDetect.ino @@ -268,6 +268,8 @@ bool beginQwiicDevices() qwiicPowerOnDelayMillis = settings.qwiicBusPowerUpDelayMs; // Set qwiicPowerOnDelayMillis to the _minimum_ defined by settings.qwiicBusPowerUpDelayMs. It will be increased if required. + int numberOfSCD30s = 0; // Keep track of how many SCD30s we begin so we can delay before starting the second and subsequent ones + //Step through the list node *temp = head; @@ -395,6 +397,13 @@ bool beginQwiicDevices() SCD30 *tempDevice = (SCD30 *)temp->classPtr; struct_SCD30 *nodeSetting = (struct_SCD30 *)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 + numberOfSCD30s++; // Keep track of how many SCD30s we begin + // Delay before starting the second and subsequent SCD30s to try and stagger the measurements and the peak current draw + if (numberOfSCD30s >= 2) + { + printDebug("beginQwiicDevices: found more than one SCD30. Delaying for 375ms to stagger the peak current draw...\r\n"); + delay(375); + } temp->online = tempDevice->begin(qwiic); //Wire port } break; @@ -1127,7 +1136,8 @@ deviceType_e testDevice(uint8_t i2cAddress, uint8_t muxAddress, uint8_t portNumb //Confidence: High - begin now checks FW Ver CRC SCD30 sensor1; //sensor1.enableDebugging(); - if (sensor1.begin(qwiic) == true) //Wire port + // Set measBegin to false. beginQwiicDevices will call begin with measBegin set true. + if (sensor1.begin(qwiic, true, false) == true) //Wire port, autoCalibrate, measBegin return (DEVICE_CO2_SCD30); } break; From 99d89dc1465119979aa2f7511fe4eaf7ad55c592 Mon Sep 17 00:00:00 2001 From: PaulZC Date: Tue, 2 Feb 2021 10:15:13 +0000 Subject: [PATCH 11/14] Adding resetOnZeroDeviceCount --- Firmware/OpenLog_Artemis/OpenLog_Artemis.ino | 12 +- Firmware/OpenLog_Artemis/autoDetect.ino | 15 ++- Firmware/OpenLog_Artemis/lowerPower.ino | 120 +++++++++++++++++- .../OpenLog_Artemis/menuAttachedDevices.ino | 6 +- Firmware/OpenLog_Artemis/menuDebug.ino | 25 ++++ Firmware/OpenLog_Artemis/menuMain.ino | 2 +- Firmware/OpenLog_Artemis/menuTerminal.ino | 3 +- Firmware/OpenLog_Artemis/nvm.ino | 11 +- Firmware/OpenLog_Artemis/settings.h | 2 +- 9 files changed, 173 insertions(+), 23 deletions(-) diff --git a/Firmware/OpenLog_Artemis/OpenLog_Artemis.ino b/Firmware/OpenLog_Artemis/OpenLog_Artemis.ino index 5632082..5b6ed84 100644 --- a/Firmware/OpenLog_Artemis/OpenLog_Artemis.ino +++ b/Firmware/OpenLog_Artemis/OpenLog_Artemis.ino @@ -379,7 +379,13 @@ void setup() { beginQwiicDevices(); //Begin() each device in the node list loadDeviceSettingsFromFile(); //Load config settings into node list configureQwiicDevices(); //Apply config settings to each device in the node list - printOnlineDevice(); + int deviceCount = printOnlineDevice(); + if ((deviceCount == 0) && (settings.resetOnZeroDeviceCount == true)) + { + SerialPrintln(F("*** Zero Qwiic Devices Found! Resetting... ***")); + SerialFlush(); + resetArtemis(); //Thank you and goodnight... + } } else SerialPrintln(F("No Qwiic devices detected")); @@ -766,7 +772,7 @@ void beginSD() if (sd.begin(PIN_MICROSD_CHIP_SELECT, SD_SCK_MHZ(24)) == false) //Standard SdFat { - printDebug("SD init failed (first attempt). Trying again...\r\n"); + 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 { checkBattery(); @@ -810,7 +816,7 @@ void enableCIPOpullUp() cipoPinCfg.ePullup = AM_HAL_GPIO_PIN_PULLUP_1_5K; padMode(MISO, cipoPinCfg, &retval); if (retval != AP3_OK) - printDebug("Setting CIPO padMode failed!"); + printDebug(F("Setting CIPO padMode failed!")); } void beginIMU() diff --git a/Firmware/OpenLog_Artemis/autoDetect.ino b/Firmware/OpenLog_Artemis/autoDetect.ino index 2227aaa..bda4a51 100644 --- a/Firmware/OpenLog_Artemis/autoDetect.ino +++ b/Firmware/OpenLog_Artemis/autoDetect.ino @@ -401,9 +401,10 @@ bool beginQwiicDevices() // Delay before starting the second and subsequent SCD30s to try and stagger the measurements and the peak current draw if (numberOfSCD30s >= 2) { - printDebug("beginQwiicDevices: found more than one SCD30. Delaying for 375ms to stagger the peak current draw...\r\n"); + printDebug(F("beginQwiicDevices: found more than one SCD30. Delaying for 375ms to stagger the peak current draw...\r\n")); delay(375); } + if(settings.printDebugMessages == true) tempDevice->enableDebugging(); // Enable debug messages if required temp->online = tempDevice->begin(qwiic); //Wire port } break; @@ -475,11 +476,11 @@ bool beginQwiicDevices() if (temp->online == true) { - printDebug("beginQwiicDevices: device is online\r\n"); + printDebug(F("beginQwiicDevices: device is online\r\n")); } else { - printDebug("beginQwiicDevices: device is **NOT** online\r\n"); + printDebug(F("beginQwiicDevices: device is **NOT** online\r\n")); everythingStarted = false; } @@ -490,7 +491,7 @@ bool beginQwiicDevices() } //Pretty print all the online devices -void printOnlineDevice() +int printOnlineDevice() { int deviceCount = 0; @@ -500,7 +501,7 @@ void printOnlineDevice() if (temp == NULL) { printDebug(F("printOnlineDevice: No devices detected\r\n")); - return; + return (0); } while (temp != NULL) @@ -528,6 +529,8 @@ void printOnlineDevice() { SerialPrintf2("Device count: %d\r\n", deviceCount); } + + return (deviceCount); } //Given the node number, apply the node's configuration settings to the device @@ -1135,7 +1138,7 @@ deviceType_e testDevice(uint8_t i2cAddress, uint8_t muxAddress, uint8_t portNumb //Confidence: High - begin now checks FW Ver CRC SCD30 sensor1; - //sensor1.enableDebugging(); + if(settings.printDebugMessages == true) sensor1.enableDebugging(); // Enable debug messages if required // Set measBegin to false. beginQwiicDevices will call begin with measBegin set true. if (sensor1.begin(qwiic, true, false) == true) //Wire port, autoCalibrate, measBegin return (DEVICE_CO2_SCD30); diff --git a/Firmware/OpenLog_Artemis/lowerPower.ino b/Firmware/OpenLog_Artemis/lowerPower.ino index 110450b..820561a 100644 --- a/Firmware/OpenLog_Artemis/lowerPower.ino +++ b/Firmware/OpenLog_Artemis/lowerPower.ino @@ -153,6 +153,77 @@ void powerDown() } } +//Reset the Artemis +void resetArtemis(void) +{ + //Save files before resetting + if (online.dataLogging == true) + { + sensorDataFile.sync(); + updateDataFileAccess(&sensorDataFile); // Update the file access time & date + sensorDataFile.close(); //No need to close files. https://forum.arduino.cc/index.php?topic=149504.msg1125098#msg1125098 + } + if (online.serialLogging == true) + { + serialDataFile.sync(); + updateDataFileAccess(&serialDataFile); // Update the file access time & date + serialDataFile.close(); + } + + delay(sdPowerDownDelay); // Give the SD card time to finish writing ***** THIS IS CRITICAL ***** + + SerialFlush(); //Finish any prints + + // Wire.end(); //Power down I2C + 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 + SerialLog.end(); + + //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_POWER_LOSS)) && + //(x != ap3_gpio_pin2pad(PIN_LOGIC_DEBUG)) && + (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(); + + //Disable power for the Qwiic bus + qwiicPowerOff(); + + //Disable the power LED + powerLEDOff(); + + //Enable the Watchdog so it can reset the Artemis + startWatchdog(); + while (1) // That's all folks! Artemis will reset in 1.25 seconds + ; +} + //Power everything down and wait for interrupt wakeup void goToSleep(uint32_t sysTicksToSleep) { @@ -360,11 +431,11 @@ void wakeFromSleep() SerialLog.begin(settings.serialTerminalBaudRate); // Start the serial port } - printDebug("wakeFromSleep: I'm awake!\r\n"); + printDebug(F("wakeFromSleep: I'm awake!\r\n")); printDebug("wakeFromSleep: adcError is " + (String)adcError + "."); if (adcError > 0) - printDebug(" This indicates an error was returned by ap3_adc_setup or one of the calls to ap3_set_pin_to_analog."); - printDebug("\r\n"); + printDebug(F(" This indicates an error was returned by ap3_adc_setup or one of the calls to ap3_set_pin_to_analog.")); + printDebug(F("\r\n")); beginQwiic(); //Power up Qwiic bus as early as possible @@ -560,3 +631,46 @@ int calculateDayOfYear(int day, int month, int year) doy += day; return doy; } + +//WatchDog Timer code by Adam Garbo: +//https://forum.sparkfun.com/viewtopic.php?f=169&t=52431&p=213296#p213296 + +// Watchdog timer configuration structure. +am_hal_wdt_config_t g_sWatchdogConfig = { + + // Configuration values for generated watchdog timer event. + .ui32Config = AM_HAL_WDT_LFRC_CLK_16HZ | AM_HAL_WDT_ENABLE_RESET | AM_HAL_WDT_ENABLE_INTERRUPT, + + // Number of watchdog timer ticks allowed before a watchdog interrupt event is generated. + .ui16InterruptCount = 16, // Set WDT interrupt timeout for 1 second. + + // Number of watchdog timer ticks allowed before the watchdog will issue a system reset. + .ui16ResetCount = 20 // Set WDT reset timeout for 1.25 seconds. +}; + +void startWatchdog() +{ + // LFRC must be turned on for this example as the watchdog only runs off of the LFRC. + am_hal_clkgen_control(AM_HAL_CLKGEN_CONTROL_LFRC_START, 0); + + // Configure the watchdog. + am_hal_wdt_init(&g_sWatchdogConfig); + + // Enable the interrupt for the watchdog in the NVIC. + NVIC_EnableIRQ(WDT_IRQn); + //NVIC_SetPriority(WDT_IRQn, 0); // Set the interrupt priority to 0 = highest (255 = lowest) + //am_hal_interrupt_master_enable(); // ap3_initialization.cpp does this - no need to do it here + + // Enable the watchdog. + am_hal_wdt_start(); +} + +// Interrupt handler for the watchdog. +extern "C++" void am_watchdog_isr(void) +{ + // Clear the watchdog interrupt. + am_hal_wdt_int_clear(); + + // DON'T Restart the watchdog. + //am_hal_wdt_restart(); // "Pet" the dog. +} diff --git a/Firmware/OpenLog_Artemis/menuAttachedDevices.ino b/Firmware/OpenLog_Artemis/menuAttachedDevices.ino index 8f09ea3..7bf1708 100644 --- a/Firmware/OpenLog_Artemis/menuAttachedDevices.ino +++ b/Firmware/OpenLog_Artemis/menuAttachedDevices.ino @@ -30,7 +30,7 @@ //Returns true if devices detected > 0 bool detectQwiicDevices() { - printDebug("detectQwiicDevices started\r\n"); + printDebug(F("detectQwiicDevices started\r\n")); bool somethingDetected = false; qwiic.setClock(100000); //During detection, go slow @@ -154,7 +154,7 @@ bool detectQwiicDevices() QWIICMUX *myMux = (QWIICMUX *)muxNode->classPtr; printDebug("detectQwiicDevices: scanning the ports of multiplexer " + (String)muxNumber); - printDebug("\r\n"); + printDebug(F("\r\n")); for (int portNumber = 0 ; portNumber < 8 ; portNumber++) //Assumes we are using a mux with 8 ports max { @@ -163,7 +163,7 @@ bool detectQwiicDevices() printDebug("detectQwiicDevices: scanning port number " + (String)portNumber); printDebug(" on multiplexer " + (String)muxNumber); - printDebug("\r\n"); + printDebug(F("\r\n")); //Scan this new bus for new addresses for (uint8_t address = 1 ; address < 127 ; address++) diff --git a/Firmware/OpenLog_Artemis/menuDebug.ino b/Firmware/OpenLog_Artemis/menuDebug.ino index 054596d..1a63549 100644 --- a/Firmware/OpenLog_Artemis/menuDebug.ino +++ b/Firmware/OpenLog_Artemis/menuDebug.ino @@ -9,6 +9,10 @@ void menuDebug() if (settings.printDebugMessages == true) SerialPrintln(F("Enabled")); else SerialPrintln(F("Disabled")); + SerialPrint(F("2) Reset on Zero Device Count: ")); + if (settings.resetOnZeroDeviceCount == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); + SerialPrintln(F("x) Exit")); byte incoming = getByteChoice(menuTimeout); //Timeout after x seconds @@ -17,6 +21,27 @@ void menuDebug() { settings.printDebugMessages ^= 1; } + else if (incoming == '2') + { + if (settings.resetOnZeroDeviceCount == false) + { + SerialPrintln(F("")); + SerialPrintln(F("Enabling resetOnZeroDeviceCount will cause the OLA to completely reset if no devices are found on the Qwiic bus.")); + SerialPrintln(F("Do not enable this option if you are only logging IMU or Serial data.")); + SerialPrintln(F("Are you sure? Press 'y' to confirm: ")); + byte bContinue = getByteChoice(menuTimeout); + if (bContinue == 'y') + { + settings.resetOnZeroDeviceCount ^= 1; + } + else + SerialPrintln(F("\"resetOnZeroDeviceCount\" aborted")); + } + else + { + settings.resetOnZeroDeviceCount ^= 1; + } + } else if (incoming == 'x') break; else if (incoming == STATUS_GETBYTE_TIMEOUT) diff --git a/Firmware/OpenLog_Artemis/menuMain.ino b/Firmware/OpenLog_Artemis/menuMain.ino index 4ae9c47..0cc2202 100644 --- a/Firmware/OpenLog_Artemis/menuMain.ino +++ b/Firmware/OpenLog_Artemis/menuMain.ino @@ -97,7 +97,7 @@ void menuMain() } else if (incoming == 'r') { - SerialPrintln(F("\r\nResetting to factory defaults. Press 'y' to confirm:")); + SerialPrintln(F("\r\nResetting to factory defaults. Press 'y' to confirm: ")); byte bContinue = getByteChoice(menuTimeout); if (bContinue == 'y') { diff --git a/Firmware/OpenLog_Artemis/menuTerminal.ino b/Firmware/OpenLog_Artemis/menuTerminal.ino index 972ec2b..da2059e 100644 --- a/Firmware/OpenLog_Artemis/menuTerminal.ino +++ b/Firmware/OpenLog_Artemis/menuTerminal.ino @@ -288,11 +288,10 @@ void menuLogRate() if (settings.useTxRxPinsForTerminal == false) { SerialPrintln(F("")); - SerialPrintln(F("WARNING:")); SerialPrintln(F("\"Use TX and RX pins for terminal\" can only be disabled by \"Reset all settings to default\".")); SerialPrintln(F("Analog logging on TX/A12 and RX/A13 will be disabled.")); SerialPrintln(F("Serial logging will be disabled.")); - SerialPrintln(F("Are you sure? Press 'y' to confirm:")); + SerialPrintln(F("Are you sure? Press 'y' to confirm: ")); byte bContinue = getByteChoice(menuTimeout); if (bContinue == 'y') { diff --git a/Firmware/OpenLog_Artemis/nvm.ino b/Firmware/OpenLog_Artemis/nvm.ino index 0e1c2c4..1f7b599 100644 --- a/Firmware/OpenLog_Artemis/nvm.ino +++ b/Firmware/OpenLog_Artemis/nvm.ino @@ -98,9 +98,9 @@ void recordSystemSettingsToFile() settingsFile.println("usBetweenReadings=" + (String)tempTime); - //printDebug("Saving usBetweenReadings to SD card: "); + //printDebug(F("Saving usBetweenReadings to SD card: ")); //printDebug((String)tempTime); - //printDebug("\r\n"); + //printDebug(F("\r\n")); settingsFile.println("logMaxRate=" + (String)settings.logMaxRate); settingsFile.println("enableRTC=" + (String)settings.enableRTC); @@ -163,6 +163,7 @@ void recordSystemSettingsToFile() settingsFile.println("slowLoggingIntervalSeconds=" + (String)settings.slowLoggingIntervalSeconds); settingsFile.println("slowLoggingStartMOD=" + (String)settings.slowLoggingStartMOD); settingsFile.println("slowLoggingStopMOD=" + (String)settings.slowLoggingStopMOD); + settingsFile.println("resetOnZeroDeviceCount=" + (String)settings.resetOnZeroDeviceCount); updateDataFileAccess(&settingsFile); // Update the file access time & date settingsFile.close(); } @@ -295,9 +296,9 @@ bool parseLine(char* str) { else if (strcmp(settingName, "usBetweenReadings") == 0) { settings.usBetweenReadings = d; - //printDebug("Read usBetweenReadings from SD card: "); + //printDebug(F("Read usBetweenReadings from SD card: ")); //printDebug(String(d)); - //printDebug("\r\n"); + //printDebug(F("\r\n")); } else if (strcmp(settingName, "logMaxRate") == 0) settings.logMaxRate = d; @@ -421,6 +422,8 @@ bool parseLine(char* str) { settings.slowLoggingStartMOD = d; else if (strcmp(settingName, "slowLoggingStopMOD") == 0) settings.slowLoggingStopMOD = d; + else if (strcmp(settingName, "resetOnZeroDeviceCount") == 0) + settings.resetOnZeroDeviceCount = d; else { SerialPrintf2("Unknown setting %s. Ignoring...\r\n", settingName); diff --git a/Firmware/OpenLog_Artemis/settings.h b/Firmware/OpenLog_Artemis/settings.h index c440ed5..0dde9ef 100644 --- a/Firmware/OpenLog_Artemis/settings.h +++ b/Firmware/OpenLog_Artemis/settings.h @@ -359,7 +359,7 @@ struct struct_settings { int slowLoggingIntervalSeconds = 300; // Slow logging interval in seconds. Default to 5 mins 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. } settings; //These are the devices on board OpenLog that may be on or offline. From ccd148fe818f3b4dfb1264bd97a9e1488c13d9c0 Mon Sep 17 00:00:00 2001 From: PaulZC Date: Tue, 2 Feb 2021 11:13:07 +0000 Subject: [PATCH 12/14] Alow user to open menuMain on startup to disable resetOnZeroDeviceCount --- Firmware/OpenLog_Artemis/OpenLog_Artemis.ino | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/Firmware/OpenLog_Artemis/OpenLog_Artemis.ino b/Firmware/OpenLog_Artemis/OpenLog_Artemis.ino index 5b6ed84..023777f 100644 --- a/Firmware/OpenLog_Artemis/OpenLog_Artemis.ino +++ b/Firmware/OpenLog_Artemis/OpenLog_Artemis.ino @@ -379,12 +379,18 @@ void setup() { beginQwiicDevices(); //Begin() each device in the node list loadDeviceSettingsFromFile(); //Load config settings into node list configureQwiicDevices(); //Apply config settings to each device in the node list - int deviceCount = printOnlineDevice(); - if ((deviceCount == 0) && (settings.resetOnZeroDeviceCount == true)) + int deviceCount = printOnlineDevice(); // Pretty-print the online devices + + if ((deviceCount == 0) && (settings.resetOnZeroDeviceCount == true)) // Check for resetOnZeroDeviceCount { - SerialPrintln(F("*** Zero Qwiic Devices Found! Resetting... ***")); - SerialFlush(); - resetArtemis(); //Thank you and goodnight... + if ((Serial.available()) || ((settings.useTxRxPinsForTerminal == true) && (SerialLog.available()))) + menuMain(); //Present user menu - in case the user wants to disable resetOnZeroDeviceCount + else + { + SerialPrintln(F("*** Zero Qwiic Devices Found! Resetting... ***")); + SerialFlush(); + resetArtemis(); //Thank you and goodnight... + } } } else From 46873aa1bbc32651b719aad08ccf11a0274d0957 Mon Sep 17 00:00:00 2001 From: PaulZC Date: Tue, 2 Feb 2021 19:08:12 +0000 Subject: [PATCH 13/14] Adding SGP40, SDP3X and MS5837 --- CHANGELOG.md | 7 + Firmware/OpenLog_Artemis/OpenLog_Artemis.ino | 6 + Firmware/OpenLog_Artemis/Sensors.ino | 113 ++++++++- Firmware/OpenLog_Artemis/autoDetect.ino | 119 +++++++++- .../OpenLog_Artemis/menuAttachedDevices.ino | 224 ++++++++++++++++++ Firmware/OpenLog_Artemis/nvm.ino | 86 +++++++ Firmware/OpenLog_Artemis/settings.h | 32 +++ README.md | 3 + SENSOR_UNITS.md | 33 ++- 9 files changed, 620 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 74bca50..050d2f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ Change Log ====================== +v1.9 +--------- + +* Added support for the SGP40 air quality sensor +* Added support for the SDP3X differential pressure sensor +* Added support for the MS5837 depth / pressure sensor - as used in the BlueRobotics Bar02 + v1.8 --------- diff --git a/Firmware/OpenLog_Artemis/OpenLog_Artemis.ino b/Firmware/OpenLog_Artemis/OpenLog_Artemis.ino index 023777f..4a9bf09 100644 --- a/Firmware/OpenLog_Artemis/OpenLog_Artemis.ino +++ b/Firmware/OpenLog_Artemis/OpenLog_Artemis.ino @@ -83,6 +83,9 @@ (done?) Add "sleep on pin" functionality based @ryanneve's PR https://github.com/sparkfun/OpenLog_Artemis/pull/64 and Issue https://github.com/sparkfun/OpenLog_Artemis/issues/46 (done?) Add "wake at specified times" functionality based on Issue https://github.com/sparkfun/OpenLog_Artemis/issues/46 (done?) Add corrections for the SCD30 based on Forum post by paulvha: https://forum.sparkfun.com/viewtopic.php?p=222455#p222455 + (done) Add support for the SGP40 + (done) Add support for the SDP31 + (done) Add support for the MS5837 - as used in the BlueRobotics BAR02 and BAR30 water pressure sensors */ const int FIRMWARE_VERSION_MAJOR = 1; @@ -215,6 +218,9 @@ ICM_20948_SPI myICM; #include "SparkFun_ADS122C04_ADC_Arduino_Library.h" // Click here to get the library: http://librarymanager/All#SparkFun_ADS122C04 #include "SparkFun_MicroPressure.h" // Click here to get the library: http://librarymanager/All#SparkFun_MicroPressure #include "SparkFun_Particle_Sensor_SN-GCJA5_Arduino_Library.h" // Click here to get the library: http://librarymanager/All#SparkFun_Particle_Sensor_SN-GCJA5 +#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 //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- diff --git a/Firmware/OpenLog_Artemis/Sensors.ino b/Firmware/OpenLog_Artemis/Sensors.ino index d05262c..5ccc542 100644 --- a/Firmware/OpenLog_Artemis/Sensors.ino +++ b/Firmware/OpenLog_Artemis/Sensors.ino @@ -771,6 +771,79 @@ void gatherDeviceValues() } } break; + case DEVICE_VOC_SGP40: + { + SGP40 *nodeDevice = (SGP40 *)temp->classPtr; + struct_SGP40 *nodeSetting = (struct_SGP40 *)temp->configPtr; + if (nodeSetting->log == true) + { + if (nodeSetting->logVOC) + { + sprintf(tempData, "%d,", nodeDevice->getVOCindex(nodeSetting->RH, nodeSetting->T)); + strcat(outputData, tempData); + } + } + } + break; + case DEVICE_PRESSURE_SDP3X: + { + SDP3X *nodeDevice = (SDP3X *)temp->classPtr; + struct_SDP3X *nodeSetting = (struct_SDP3X *)temp->configPtr; + if (nodeSetting->log == true) + { + float pressure; + float temperature; + if ((nodeSetting->logPressure) || (nodeSetting->logTemperature)) + { + nodeDevice->triggeredMeasurement(nodeSetting->massFlow, nodeSetting->clockStretching); + nodeDevice->readMeasurement(&pressure, &temperature); + } + if (nodeSetting->logPressure) + { + sprintf(tempData, "%.02f,", pressure); + strcat(outputData, tempData); + } + if (nodeSetting->logTemperature) + { + sprintf(tempData, "%.02f,", temperature); + strcat(outputData, tempData); + } + } + } + break; + case DEVICE_PRESSURE_MS5837: + { + MS5837 *nodeDevice = (MS5837 *)temp->classPtr; + struct_MS5837 *nodeSetting = (struct_MS5837 *)temp->configPtr; + if (nodeSetting->log == true) + { + if ((nodeSetting->logPressure) || (nodeSetting->logTemperature) || (nodeSetting->logDepth) || (nodeSetting->logAltitude)) + { + nodeDevice->read(); + } + if (nodeSetting->logPressure) + { + sprintf(tempData, "%.02f,", nodeDevice->pressure(nodeSetting->conversion)); + strcat(outputData, tempData); + } + if (nodeSetting->logTemperature) + { + sprintf(tempData, "%.02f,", nodeDevice->temperature()); + strcat(outputData, tempData); + } + if (nodeSetting->logDepth) + { + sprintf(tempData, "%.03f,", nodeDevice->depth()); + strcat(outputData, tempData); + } + if (nodeSetting->logAltitude) + { + sprintf(tempData, "%.02f,", nodeDevice->altitude()); + strcat(outputData, tempData); + } + } + } + break; default: SerialPrintf2("printDeviceValue unknown device type: %s\r\n", getDeviceName(temp->deviceType)); break; @@ -923,7 +996,7 @@ void printHelperText(bool terminalOnly) if (nodeSetting->logPressure) strcat(helperText, "pressure_hPa,"); if (nodeSetting->logTemperature) - strcat(helperText, "pressure_degC,"); + strcat(helperText, "temperature_degC,"); } } break; @@ -1133,6 +1206,44 @@ void printHelperText(bool terminalOnly) } } break; + case DEVICE_VOC_SGP40: + { + struct_SGP40 *nodeSetting = (struct_SGP40 *)temp->configPtr; + if (nodeSetting->log) + { + if (nodeSetting->logVOC) + strcat(helperText, "VOCindex,"); + } + } + break; + case DEVICE_PRESSURE_SDP3X: + { + struct_SDP3X *nodeSetting = (struct_SDP3X *)temp->configPtr; + if (nodeSetting->log) + { + if (nodeSetting->logPressure) + strcat(helperText, "Pa,"); + if (nodeSetting->logTemperature) + strcat(helperText, "degC,"); + } + } + break; + case DEVICE_PRESSURE_MS5837: + { + struct_MS5837 *nodeSetting = (struct_MS5837 *)temp->configPtr; + if (nodeSetting->log) + { + if (nodeSetting->logPressure) + strcat(helperText, "mbar,"); + if (nodeSetting->logTemperature) + strcat(helperText, "degC,"); + if (nodeSetting->logDepth) + strcat(helperText, "depth_m,"); + if (nodeSetting->logAltitude) + strcat(helperText, "alt_m,"); + } + } + 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 bda4a51..d4af4d0 100644 --- a/Firmware/OpenLog_Artemis/autoDetect.ino +++ b/Firmware/OpenLog_Artemis/autoDetect.ino @@ -237,6 +237,24 @@ bool addDevice(deviceType_e deviceType, uint8_t address, uint8_t muxAddress, uin temp->configPtr = new struct_SNGCJA5; } break; + case DEVICE_VOC_SGP40: + { + temp->classPtr = new SGP40; + temp->configPtr = new struct_SGP40; + } + break; + case DEVICE_PRESSURE_SDP3X: + { + temp->classPtr = new SDP3X; + temp->configPtr = new struct_SDP3X; + } + break; + case DEVICE_PRESSURE_MS5837: + { + temp->classPtr = new MS5837; + temp->configPtr = new struct_MS5837; + } + break; default: SerialPrintf2("addDevice Device type not found: %d\r\n", deviceType); break; @@ -469,6 +487,33 @@ bool beginQwiicDevices() temp->online = true; } break; + case DEVICE_VOC_SGP40: + { + SGP40 *tempDevice = (SGP40 *)temp->classPtr; + struct_SGP40 *nodeSetting = (struct_SGP40 *)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) == true) //Wire port. Returns true on success. + temp->online = true; + } + break; + case DEVICE_PRESSURE_SDP3X: + { + SDP3X *tempDevice = (SDP3X *)temp->classPtr; + struct_SDP3X *nodeSetting = (struct_SDP3X *)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_PRESSURE_MS5837: + { + MS5837 *tempDevice = (MS5837 *)temp->classPtr; + struct_MS5837 *nodeSetting = (struct_MS5837 *)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) == true) //Wire port. Returns true on success. + temp->online = true; + } + break; default: SerialPrintf2("beginQwiicDevices: device type not found: %d\r\n", temp->deviceType); break; @@ -719,6 +764,21 @@ void configureDevice(node * temp) case DEVICE_PARTICLE_SNGCJA5: //Nothing to configure break; + case DEVICE_VOC_SGP40: + //Nothing to configure + break; + case DEVICE_PRESSURE_SDP3X: + //Nothing to configure + break; + case DEVICE_PRESSURE_MS5837: + { + MS5837 *sensor = (MS5837 *)temp->classPtr; + struct_MS5837 *sensorSetting = (struct_MS5837 *)temp->configPtr; + + sensor->setModel(sensorSetting->model); + sensor->setFluidDensity(sensorSetting->fluidDensity); + } + break; default: SerialPrintf3("configureDevice: Unknown device type %d: %s\r\n", deviceType, getDeviceName((deviceType_e)deviceType)); break; @@ -809,6 +869,15 @@ FunctionPointer getConfigFunctionPtr(uint8_t nodeNumber) case DEVICE_PARTICLE_SNGCJA5: ptr = (FunctionPointer)menuConfigure_SNGCJA5; break; + case DEVICE_VOC_SGP40: + ptr = (FunctionPointer)menuConfigure_SGP40; + break; + case DEVICE_PRESSURE_SDP3X: + ptr = (FunctionPointer)menuConfigure_SDP3X; + break; + case DEVICE_PRESSURE_MS5837: + ptr = (FunctionPointer)menuConfigure_MS5837; + break; default: SerialPrintln(F("getConfigFunctionPtr: Unknown device type")); SerialFlush(); @@ -938,6 +1007,7 @@ void swap(struct node * a, struct node * b) //We no longer use defines in the search table. These are just here for reference. #define ADR_VEML6075 0x10 #define ADR_MPR0025PA1 0x18 +#define ADR_SDP3X 0x21 //Alternates: 0x22, 0x23 #define ADR_NAU7802 0x2A #define ADR_VL53L1X 0x29 #define ADR_SNGCJA5 0x33 @@ -947,6 +1017,7 @@ void swap(struct node * a, struct node * b) #define ADR_ADS122C04 0x45 //Alternates: 0x44, 0x41 and 0x40 #define ADR_TMP117 0x48 //Alternates: 0x49, 0x4A, and 0x4B #define ADR_SGP30 0x58 +#define ADR_SGP40 0x59 #define ADR_CCS811 0x5B //Alternates: 0x5A #define ADR_LPS25HB 0x5D //Alternates: 0x5C #define ADR_VCNL4040 0x60 @@ -955,6 +1026,7 @@ void swap(struct node * a, struct node * b) #define ADR_MULTIPLEXER 0x70 //0x70 to 0x77 #define ADR_SHTC3 0x70 #define ADR_MS5637 0x76 +#define ADR_MS5837 0x76 //#define ADR_MS8607 0x76 //Pressure portion of the MS8607 sensor. We'll catch the 0x40 first #define ADR_BME280 0x77 //Alternates: 0x76 @@ -981,6 +1053,30 @@ deviceType_e testDevice(uint8_t i2cAddress, uint8_t muxAddress, uint8_t portNumb return (DEVICE_PRESSURE_MPR0025PA1); } break; + case 0x21: + { + //Confidence: Medium - .begin reads the product ID + SDP3X sensor; + if (sensor.begin(i2cAddress, qwiic) == true) //Address, Wire port + return (DEVICE_PRESSURE_SDP3X); + } + break; + case 0x22: + { + //Confidence: Medium - .begin reads the product ID + SDP3X sensor; + if (sensor.begin(i2cAddress, qwiic) == true) //Address, Wire port + return (DEVICE_PRESSURE_SDP3X); + } + break; + case 0x23: + { + //Confidence: Medium - .begin reads the product ID + SDP3X sensor; + if (sensor.begin(i2cAddress, qwiic) == true) //Address, Wire port + return (DEVICE_PRESSURE_SDP3X); + } + break; case 0x2A: { //Confidence: High - Checks 8 bit revision code (0x0F) @@ -1098,6 +1194,13 @@ deviceType_e testDevice(uint8_t i2cAddress, uint8_t muxAddress, uint8_t portNumb return (DEVICE_VOC_SGP30); } break; + case 0x59: + { + SGP40 sensor; + if (sensor.begin(qwiic) == true) //Wire port + return (DEVICE_VOC_SGP40); + } + break; case 0x5A: case 0x5B: { @@ -1244,7 +1347,12 @@ deviceType_e testDevice(uint8_t i2cAddress, uint8_t muxAddress, uint8_t portNumb //Ignore devices we've already recorded. This was causing the mux to get tested, a begin() would happen, and the mux would be reset. if (deviceExists(DEVICE_MULTIPLEXER, i2cAddress, muxAddress, portNumber) == true) return (DEVICE_MULTIPLEXER); - //Confidence: High - does CRC on internal EEPROM read + //Confidence: High - does CRC on internal EEPROM read and checks sensor version + MS5837 sensor2; + if (sensor2.begin(qwiic) == true) //Wire port + return (DEVICE_PRESSURE_MS5837); + + //Confidence: High - does CRC on internal EEPROM read - but do this second as a MS5837 will appear as a MS5637 MS5637 sensor; if (sensor.begin(qwiic) == true) //Wire port return (DEVICE_PRESSURE_MS5637); @@ -1493,6 +1601,15 @@ const char* getDeviceName(deviceType_e deviceNumber) case DEVICE_PARTICLE_SNGCJA5: return "Particle-SNGCJA5"; break; + case DEVICE_VOC_SGP40: + return "VOC-SGP40"; + break; + case DEVICE_PRESSURE_SDP3X: + return "Pressure-SDP3X"; + break; + case DEVICE_PRESSURE_MS5837: + return "Pressure-MS5837"; + break; case DEVICE_UNKNOWN_DEVICE: return "Unknown device"; diff --git a/Firmware/OpenLog_Artemis/menuAttachedDevices.ino b/Firmware/OpenLog_Artemis/menuAttachedDevices.ino index 7bf1708..e9bb0c2 100644 --- a/Firmware/OpenLog_Artemis/menuAttachedDevices.ino +++ b/Firmware/OpenLog_Artemis/menuAttachedDevices.ino @@ -327,6 +327,15 @@ void menuAttachedDevices() case DEVICE_PARTICLE_SNGCJA5: SerialPrintf3("%s SN-GCJA5 Particle Sensor %s\r\n", strDeviceMenu, strAddress); break; + case DEVICE_VOC_SGP40: + SerialPrintf3("%s SGP40 VOC Sensor %s\r\n", strDeviceMenu, strAddress); + break; + case DEVICE_PRESSURE_SDP3X: + SerialPrintf3("%s SDP3X Differential Pressure Sensor %s\r\n", strDeviceMenu, strAddress); + break; + case DEVICE_PRESSURE_MS5837: + SerialPrintf3("%s MS5837 (BAR30 / BAR02) Pressure Sensor %s\r\n", strDeviceMenu, strAddress); + break; default: SerialPrintf2("Unknown device type %d in menuAttachedDevices\r\n", temp->deviceType); break; @@ -2167,3 +2176,218 @@ void menuConfigure_SNGCJA5(void *configPtr) printUnknown(incoming); } } + +void menuConfigure_SGP40(void *configPtr) +{ + struct_SGP40 *sensorSetting = (struct_SGP40*)configPtr; + + while (1) + { + SerialPrintln(F("")); + SerialPrintln(F("Menu: Configure SGP40 VOC Sensor")); + + SerialPrint(F("1) Sensor Logging: ")); + if (sensorSetting->log == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); + + if (sensorSetting->log == true) + { + SerialPrint(F("2) Log VOC: ")); + if (sensorSetting->logVOC == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); + + SerialPrintf2("3) Sensor Compensation: Relative Humidity (%): %d\r\n", sensorSetting->RH); + + SerialPrintf2("4) Sensor Compensation: Temperature (C): %d\r\n", sensorSetting->T); + } + 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->logVOC ^= 1; + else if (incoming == 3) + { + SerialPrint(F("Enter the %RH for sensor compensation (0 to 100): ")); + int RH = getNumber(menuTimeout); //x second timeout + if (RH < 0 || RH > 100) + SerialPrintln(F("Error: Out of range")); + else + sensorSetting->RH = RH; + } + else if (incoming == 4) + { + SerialPrint(F("Enter the temperature (C) for sensor compensation (-45 to 130): ")); + int T = getNumber(menuTimeout); //x second timeout + if (T < -45 || T > 130) + SerialPrintln(F("Error: Out of range")); + else + sensorSetting->T = T; + } + 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_SDP3X(void *configPtr) +{ + struct_SDP3X *sensorSetting = (struct_SDP3X*)configPtr; + + while (1) + { + SerialPrintln(F("")); + SerialPrintln(F("Menu: Configure SDP3X Differential Pressure Sensor")); + + SerialPrint(F("1) Sensor Logging: ")); + if (sensorSetting->log == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); + + if (sensorSetting->log == true) + { + SerialPrint(F("2) Log Pressure: ")); + if (sensorSetting->logPressure == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); + + SerialPrint(F("3) Log Temperature: ")); + if (sensorSetting->logTemperature == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); + + SerialPrint(F("4) Temperature Compensation: ")); + if (sensorSetting->massFlow == true) SerialPrintln(F("Mass Flow")); + else SerialPrintln(F("Differential Pressure")); + + SerialPrint(F("5) Use Clock Stretching: ")); + if (sensorSetting->clockStretching == true) SerialPrintln(F("Yes")); + else SerialPrintln(F("No")); + } + 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->logPressure ^= 1; + else if (incoming == 3) + sensorSetting->logTemperature ^= 1; + else if (incoming == 4) + sensorSetting->massFlow ^= 1; + else if (incoming == 5) + sensorSetting->clockStretching ^= 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); + } +} + +void menuConfigure_MS5837(void *configPtr) +{ + struct_MS5837 *sensorSetting = (struct_MS5837*)configPtr; + + while (1) + { + SerialPrintln(F("")); + SerialPrintln(F("Menu: Configure MS5837 Pressure Sensor")); + + SerialPrint(F("1) Sensor Logging: ")); + if (sensorSetting->log == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); + + if (sensorSetting->log == true) + { + SerialPrint(F("2) Log Pressure: ")); + if (sensorSetting->logPressure == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); + + SerialPrint(F("3) Log Temperature: ")); + if (sensorSetting->logTemperature == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); + + SerialPrint(F("4) Log Depth: ")); + if (sensorSetting->logDepth == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); + + SerialPrint(F("5) Log Altitude: ")); + if (sensorSetting->logAltitude == true) SerialPrintln(F("Enabled")); + else SerialPrintln(F("Disabled")); + + SerialPrint(F("6) Sensor Model: ")); + if (sensorSetting->model == true) SerialPrintln(F("MS5837-02BA / BlueRobotics Bar02: 2 Bar Absolute / 10m Depth")); + else SerialPrintln(F("MS5837-30BA / BlueRobotics Bar30: 30 Bar Absolute / 300m Depth")); + + SerialPrintf2("7) Fluid Density (kg/m^3): %f\r\n", sensorSetting->fluidDensity); + + SerialPrintf2("8) Pressure Conversion Factor: %.02f\r\n", sensorSetting->conversion); + } + 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->logPressure ^= 1; + else if (incoming == 3) + sensorSetting->logTemperature ^= 1; + else if (incoming == 4) + sensorSetting->logDepth ^= 1; + else if (incoming == 5) + sensorSetting->logAltitude ^= 1; + else if (incoming == 6) + sensorSetting->model ^= 1; + else if (incoming == 7) + { + SerialPrint(F("Enter the Fluid Density (kg/m^3): ")); + double FD = getDouble(menuTimeout); //x second timeout + sensorSetting->fluidDensity = (float)FD; + } + else if (incoming == 8) + { + SerialPrint(F("Enter the Pressure Conversion Factor: ")); + double PCF = getDouble(menuTimeout); //x second timeout + sensorSetting->conversion = (float)PCF; + } + 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/nvm.ino b/Firmware/OpenLog_Artemis/nvm.ino index 1f7b599..226c677 100644 --- a/Firmware/OpenLog_Artemis/nvm.ino +++ b/Firmware/OpenLog_Artemis/nvm.ino @@ -679,6 +679,37 @@ void recordDeviceSettingsToFile() settingsFile.println((String)base + "logFanStatus=" + nodeSetting->logFanStatus); } break; + case DEVICE_VOC_SGP40: + { + struct_SGP40 *nodeSetting = (struct_SGP40 *)temp->configPtr; + settingsFile.println((String)base + "log=" + nodeSetting->log); + settingsFile.println((String)base + "logVOC=" + nodeSetting->logVOC); + settingsFile.println((String)base + "RH=" + nodeSetting->RH); + settingsFile.println((String)base + "T=" + nodeSetting->T); + } + break; + case DEVICE_PRESSURE_SDP3X: + { + struct_SDP3X *nodeSetting = (struct_SDP3X *)temp->configPtr; + settingsFile.println((String)base + "logPressure=" + nodeSetting->logPressure); + settingsFile.println((String)base + "logTemperature=" + nodeSetting->logTemperature); + settingsFile.println((String)base + "massFlow=" + nodeSetting->massFlow); + settingsFile.println((String)base + "clockStretching=" + nodeSetting->clockStretching); + } + break; + case DEVICE_PRESSURE_MS5837: + { + struct_MS5837 *nodeSetting = (struct_MS5837 *)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 + "logDepth=" + nodeSetting->logDepth); + settingsFile.println((String)base + "logAltitude=" + nodeSetting->logAltitude); + settingsFile.println((String)base + "model=" + nodeSetting->model); + settingsFile.println((String)base + "fluidDensity=" + nodeSetting->fluidDensity); + settingsFile.println((String)base + "conversion=" + nodeSetting->conversion); + } + break; default: SerialPrintf2("recordSettingsToFile Unknown device: %s\r\n", base); //settingsFile.println((String)base + "=UnknownDeviceSettings"); @@ -1202,6 +1233,61 @@ bool parseDeviceLine(char* str) { SerialPrintf2("Unknown device setting: %s\r\n", deviceSettingName); } break; + case DEVICE_VOC_SGP40: + { + struct_SGP40 *nodeSetting = (struct_SGP40 *)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, "logVOC") == 0) + nodeSetting->logVOC = d; + else if (strcmp(deviceSettingName, "RH") == 0) + nodeSetting->RH = d; + else if (strcmp(deviceSettingName, "T") == 0) + nodeSetting->T = d; + else + SerialPrintf2("Unknown device setting: %s\r\n", deviceSettingName); + } + break; + case DEVICE_PRESSURE_SDP3X: + { + struct_SDP3X *nodeSetting = (struct_SDP3X *)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, "logPressure") == 0) + nodeSetting->logPressure = d; + else if (strcmp(deviceSettingName, "logTemperature") == 0) + nodeSetting->logTemperature = d; + else if (strcmp(deviceSettingName, "massFlow") == 0) + nodeSetting->massFlow = d; + else if (strcmp(deviceSettingName, "clockStretching") == 0) + nodeSetting->clockStretching = d; + else + SerialPrintf2("Unknown device setting: %s\r\n", deviceSettingName); + } + break; + case DEVICE_PRESSURE_MS5837: + { + struct_MS5837 *nodeSetting = (struct_MS5837 *)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, "logPressure") == 0) + nodeSetting->logPressure = d; + else if (strcmp(deviceSettingName, "logTemperature") == 0) + nodeSetting->logTemperature = d; + else if (strcmp(deviceSettingName, "logDepth") == 0) + nodeSetting->logDepth = d; + else if (strcmp(deviceSettingName, "logAltitude") == 0) + nodeSetting->logAltitude = d; + else if (strcmp(deviceSettingName, "model") == 0) + nodeSetting->model = d; + else if (strcmp(deviceSettingName, "fluidDensity") == 0) + nodeSetting->fluidDensity = d; + else if (strcmp(deviceSettingName, "conversion") == 0) + nodeSetting->conversion = 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 0dde9ef..c2bea0a 100644 --- a/Firmware/OpenLog_Artemis/settings.h +++ b/Firmware/OpenLog_Artemis/settings.h @@ -23,6 +23,9 @@ typedef enum DEVICE_ADC_ADS122C04, DEVICE_PRESSURE_MPR0025PA1, // 0-25 PSI, I2C Address 0x18 DEVICE_PARTICLE_SNGCJA5, + DEVICE_VOC_SGP40, + DEVICE_PRESSURE_SDP3X, + DEVICE_PRESSURE_MS5837, DEVICE_TOTAL_DEVICES, //Marks the end, used to iterate loops DEVICE_UNKNOWN_DEVICE, @@ -281,6 +284,35 @@ struct struct_SNGCJA5 { unsigned long powerOnDelayMillis = minimumQwiicPowerOnDelay; // Wait for at least this many millis before communicating with this device. Increase if required! }; +struct struct_SGP40 { + bool log = true; + bool logVOC = true; + float RH = 50; + float T = 25; + unsigned long powerOnDelayMillis = minimumQwiicPowerOnDelay; // Wait for at least this many millis before communicating with this device. Increase if required! +}; + +struct struct_SDP3X { + bool log = true; + bool logPressure = true; + bool logTemperature = true; + bool massFlow = true; + bool clockStretching = false; + unsigned long powerOnDelayMillis = minimumQwiicPowerOnDelay; // Wait for at least this many millis before communicating with this device. Increase if required! +}; + +struct struct_MS5837 { + bool log = true; + bool logPressure = true; + bool logTemperature = true; + bool logDepth = true; + bool logAltitude = true; + bool model = true; // Valid options are: 0 (MS5837::MS5837_30BA) and 1 (MS5837::MS5837_02BA) + float fluidDensity = 997; + float conversion = 1.0; + 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 { diff --git a/README.md b/README.md index 3e59633..6aaa4f3 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,8 @@ The OpenLog Artemis automatically scans, detects, configures, and logs various Q * [LPS25HB Barometric Pressure Sensor](https://www.sparkfun.com/products/14767) * [BME280 Humidity and Barometric Pressure Sensor](https://www.sparkfun.com/products/15440) * [MS5637 Barometric Pressure Sensor](https://www.sparkfun.com/products/14688) +* [MS5837 Depth / Pressure Sensor](https://www.sparkfun.com/products/17709) +* [SDP3X Differential Pressure Sensor](https://www.sparkfun.com/products/nnnnn) * [MS8607 Pressure Humidity Temperature Sensor](https://www.sparkfun.com/products/16298) * [MPR0025PA MicroPressure Sensor](https://www.sparkfun.com/products/16476) * [TMP117 High Precision Temperature Sensor](https://www.sparkfun.com/products/15805) @@ -38,6 +40,7 @@ The OpenLog Artemis automatically scans, detects, configures, and logs various Q * [SHTC3 Humidity and Temperature Sensor](https://www.sparkfun.com/products/16467) * [CCS811 Air Quality Sensor](https://www.sparkfun.com/products/14348) * [SGP30 Air Quality Sensor](https://www.sparkfun.com/products/16531) +* [SGP40 Air Quality Sensor](https://www.sparkfun.com/products/17729) * [SCD30 CO2 and Air Quality Sensor](https://www.sparkfun.com/products/15112) * [SN-GCJA5 Particle Sensor](https://www.sparkfun.com/products/17123) * [VEML6075 UV Sensor](https://www.sparkfun.com/products/15089) diff --git a/SENSOR_UNITS.md b/SENSOR_UNITS.md index 1282c11..a47e854 100644 --- a/SENSOR_UNITS.md +++ b/SENSOR_UNITS.md @@ -20,14 +20,20 @@ This document summarizes the units used for each sensor measurement. - [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) @@ -146,7 +152,25 @@ Carrier Solution: | []() | | | |---|---|---| | Pressure | pressure_hPa | hectoPascals | -| Temperature | pressure_degC | Degrees Centigrade | +| 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 @@ -193,6 +217,13 @@ VOC = Volatile Organic Compounds | H2 | H2 | none | | Ethanol | ethanol | none | +--- +## SGP40 air quality sensor + +| []() | | | +|---|---|---| +| VOC Index | VOCindex | none | + --- ## SCD30 CO2 humidity and temperature sensor From 0b20d73d10557264adfa2234ac71157185b18bb5 Mon Sep 17 00:00:00 2001 From: PaulZC Date: Wed, 3 Feb 2021 10:55:37 +0000 Subject: [PATCH 14/14] Final tweaks for v1.9 --- Binaries/OpenLog_Artemis-V10-v19.bin | Bin 0 -> 200568 bytes Binaries/OpenLog_Artemis-X04-v19.bin | Bin 0 -> 199168 bytes CHANGELOG.md | 14 ++++++++++++-- Firmware/OpenLog_Artemis/OpenLog_Artemis.ino | 10 +++++----- Firmware/OpenLog_Artemis/autoDetect.ino | 3 ++- .../OpenLog_Artemis/menuAttachedDevices.ino | 16 +++++++--------- 6 files changed, 26 insertions(+), 17 deletions(-) create mode 100644 Binaries/OpenLog_Artemis-V10-v19.bin create mode 100644 Binaries/OpenLog_Artemis-X04-v19.bin diff --git a/Binaries/OpenLog_Artemis-V10-v19.bin b/Binaries/OpenLog_Artemis-V10-v19.bin new file mode 100644 index 0000000000000000000000000000000000000000..07decad23f048e4c3d73f09b2960470d99dd64bb GIT binary patch literal 200568 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

Ty!AD z1=0L3i^-P6)?@{1B&wZw4%@_JMkVuV^$nVG%_QyQOw79O#arAAPDo7>{vP3sl}{0k zOZ8^r?vnKcXr%*pTX-RCs#CTnxQL!*g_=fvQ%yBnWfzoOjf-j3E8I?27?awd{>Upkj9q`&<+|l7<>8(LqUdDJ4E66$9=Q=ovSaXez&zK zLF>^E`V^;et6SEI2}gPd5|aCQ44`c}jI*!rd${AJHg@Fk`uHK=`nw+toAYn?ySMc8qBVpE?1M)7$zEWB3G@1%m)-M>^W9c z`!p3NGc|)xHdooIwr2jpX7UW+$70^s>zLtl!Og8iOTDgcZqvq91g2;AJITyvi))=^ z@iWf%N6d6%ziFPo**4m1;~BJ1cc*nfTJLug&c3ZaeX_=xxz=9Q9JHlvt@rs$o=&t< zKDBRXd78%zHz>BPP8x1iTN4F{rB?OXuEUJFHxSphLZpJa<7Nv>iEuUM3jF4uS0s^n=98Ds%D9;>tP8vbOkzEYM!7 zN#5l~Tdn5@VwRJ}F?kF7O)fEf8o{h}EY8g=

=?7ft{s%|rUtI!yrJ)%{z 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�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 literal 0 HcmV?d00001 diff --git a/Binaries/OpenLog_Artemis-X04-v19.bin b/Binaries/OpenLog_Artemis-X04-v19.bin new file mode 100644 index 0000000000000000000000000000000000000000..22c68bff0607a6c056775c37225b55b11d73c690 GIT binary patch literal 199168 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 literal 0 HcmV?d00001 diff --git a/CHANGELOG.md b/CHANGELOG.md index 050d2f4..7237ced 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,9 +4,19 @@ Change Log v1.9 --------- -* Added support for the SGP40 air quality sensor -* Added support for the SDP3X differential pressure sensor +* Allows the TX and RX pins to be used for the serial terminal / console + * This is very cool as it means you can now connect the OLA to your choice of (e.g.) Bluetooth or radio modem and access your data over that instead of USB + * File transfer using ZMODEM is possible on both USB and TX/RX +* Added support for the SGP40 air quality sensor - which provides a robust VOC Index for indoor air quality +* Added support for the SDP3X differential pressure sensor - opening up possibilities for air flow and air speed measurement * Added support for the MS5837 depth / pressure sensor - as used in the BlueRobotics Bar02 +* Corrects an issue which was corrupting data when using multiple MS8607s [62]https://github.com/sparkfun/OpenLog_Artemis/issues/62 +* Adds serial logging timestamps with a configurable timestamp token [63](https://github.com/sparkfun/OpenLog_Artemis/issues/63) - thank you @DennisMelamed +* Adds a _slow logging_ feature to extend battery life + * Pin11 can be used to enter slow logging mode [60](https://github.com/sparkfun/OpenLog_Artemis/issues/60) - thank you @ryanneve + * Slow logging can be entered once per day, with configurable start and end time [46](https://github.com/sparkfun/OpenLog_Artemis/issues/46) +* Adds improved support for the SCD30 [67](https://github.com/sparkfun/OpenLog_Artemis/issues/67) - thank you @paulvha +* Corrects a bug in logMicroseconds [57](https://github.com/sparkfun/OpenLog_Artemis/issues/57) v1.8 --------- diff --git a/Firmware/OpenLog_Artemis/OpenLog_Artemis.ino b/Firmware/OpenLog_Artemis/OpenLog_Artemis.ino index 4a9bf09..40a2205 100644 --- a/Firmware/OpenLog_Artemis/OpenLog_Artemis.ino +++ b/Firmware/OpenLog_Artemis/OpenLog_Artemis.ino @@ -80,11 +80,11 @@ (done) Corrected an issue when using multiple MS8607's: https://github.com/sparkfun/OpenLog_Artemis/issues/62 (done) Add a feature to use the TX and RX pins as a duplicate Terminal (done) Add serial log timestamps with a token (as suggested by @DennisMelamed in PR https://github.com/sparkfun/OpenLog_Artemis/pull/70 and Issue https://github.com/sparkfun/OpenLog_Artemis/issues/63) - (done?) Add "sleep on pin" functionality based @ryanneve's PR https://github.com/sparkfun/OpenLog_Artemis/pull/64 and Issue https://github.com/sparkfun/OpenLog_Artemis/issues/46 - (done?) Add "wake at specified times" functionality based on Issue https://github.com/sparkfun/OpenLog_Artemis/issues/46 - (done?) Add corrections for the SCD30 based on Forum post by paulvha: https://forum.sparkfun.com/viewtopic.php?p=222455#p222455 - (done) Add support for the SGP40 - (done) Add support for the SDP31 + (done) Add "sleep on pin" functionality based @ryanneve's PR https://github.com/sparkfun/OpenLog_Artemis/pull/64 and Issue https://github.com/sparkfun/OpenLog_Artemis/issues/46 + (done) Add "wake at specified times" functionality based on Issue https://github.com/sparkfun/OpenLog_Artemis/issues/46 + (done) Add corrections for the SCD30 based on Forum post by paulvha: https://forum.sparkfun.com/viewtopic.php?p=222455#p222455 + (done) Add support for the SGP40 VOC Index sensor + (done) Add support for the SDP3X Differential Pressure sensor (done) Add support for the MS5837 - as used in the BlueRobotics BAR02 and BAR30 water pressure sensors */ diff --git a/Firmware/OpenLog_Artemis/autoDetect.ino b/Firmware/OpenLog_Artemis/autoDetect.ino index d4af4d0..a7aeeda 100644 --- a/Firmware/OpenLog_Artemis/autoDetect.ino +++ b/Firmware/OpenLog_Artemis/autoDetect.ino @@ -775,7 +775,8 @@ void configureDevice(node * temp) MS5837 *sensor = (MS5837 *)temp->classPtr; struct_MS5837 *sensorSetting = (struct_MS5837 *)temp->configPtr; - sensor->setModel(sensorSetting->model); + //sensor->setModel(sensorSetting->model); + sensorSetting->model = sensor->getModel(); sensor->setFluidDensity(sensorSetting->fluidDensity); } break; diff --git a/Firmware/OpenLog_Artemis/menuAttachedDevices.ino b/Firmware/OpenLog_Artemis/menuAttachedDevices.ino index e9bb0c2..8ad45d6 100644 --- a/Firmware/OpenLog_Artemis/menuAttachedDevices.ino +++ b/Firmware/OpenLog_Artemis/menuAttachedDevices.ino @@ -2316,6 +2316,10 @@ void menuConfigure_MS5837(void *configPtr) SerialPrintln(F("")); SerialPrintln(F("Menu: Configure MS5837 Pressure Sensor")); + SerialPrint(F("Sensor Model: ")); + if (sensorSetting->model == true) SerialPrintln(F("MS5837-02BA / BlueRobotics Bar02: 2 Bar Absolute / 10m Depth")); + else SerialPrintln(F("MS5837-30BA / BlueRobotics Bar30: 30 Bar Absolute / 300m Depth")); + SerialPrint(F("1) Sensor Logging: ")); if (sensorSetting->log == true) SerialPrintln(F("Enabled")); else SerialPrintln(F("Disabled")); @@ -2338,13 +2342,9 @@ void menuConfigure_MS5837(void *configPtr) if (sensorSetting->logAltitude == true) SerialPrintln(F("Enabled")); else SerialPrintln(F("Disabled")); - SerialPrint(F("6) Sensor Model: ")); - if (sensorSetting->model == true) SerialPrintln(F("MS5837-02BA / BlueRobotics Bar02: 2 Bar Absolute / 10m Depth")); - else SerialPrintln(F("MS5837-30BA / BlueRobotics Bar30: 30 Bar Absolute / 300m Depth")); - - SerialPrintf2("7) Fluid Density (kg/m^3): %f\r\n", sensorSetting->fluidDensity); + SerialPrintf2("6) Fluid Density (kg/m^3): %f\r\n", sensorSetting->fluidDensity); - SerialPrintf2("8) Pressure Conversion Factor: %.02f\r\n", sensorSetting->conversion); + SerialPrintf2("7) Pressure Conversion Factor: %.02f\r\n", sensorSetting->conversion); } SerialPrintln(F("x) Exit")); @@ -2363,14 +2363,12 @@ void menuConfigure_MS5837(void *configPtr) else if (incoming == 5) sensorSetting->logAltitude ^= 1; else if (incoming == 6) - sensorSetting->model ^= 1; - else if (incoming == 7) { SerialPrint(F("Enter the Fluid Density (kg/m^3): ")); double FD = getDouble(menuTimeout); //x second timeout sensorSetting->fluidDensity = (float)FD; } - else if (incoming == 8) + else if (incoming == 7) { SerialPrint(F("Enter the Pressure Conversion Factor: ")); double PCF = getDouble(menuTimeout); //x second timeout