diff --git a/Griduino.ino b/Griduino.ino index 9f52263..3b6f0c2 100644 --- a/Griduino.ino +++ b/Griduino.ino @@ -834,6 +834,7 @@ void setup() { //uint32_t prevTimeGPS = millis(); elapsedSeconds saveGpsTimer; // timer to process and save the current GPS location elapsedSeconds autoLogTimer; // timer to save GPS trail periodically no matter what +elapsedSeconds batteryTimer; // timer to log the coin battery voltage uint32_t prevTimeBaro = millis(); time_t prevTimeRTC = 0; // timer to print RTC to serial port (1 second) elapsedMillis displayClockTimer; // timer to update time-of-day display (1 second) @@ -856,6 +857,7 @@ const uint32_t GPS_AUTOSAVE_INTERVAL = SECS_PER_10MIN; // seconds between saving //const int BAROMETRIC_PROCESS_INTERVAL = 15*60*1000; // fifteen minutes in milliseconds const uint LOG_PRESSURE_INTERVAL = 15*60*1000; // 15 minutes, in milliseconds const uint LOS_ANNOUNCEMENT_INTERVAL = SECS_PER_5MIN * 1000; // msec between LOS announcements +const int LOG_COIN_BATTERY_INTERVAL = 60; // seconds between logging the coin battery voltage void loop() { @@ -1033,6 +1035,21 @@ void loop() { } } + // periodically log the coin battery's voltage + // todo: sensor exists only on PCB v11+ + if (batteryTimer > LOG_COIN_BATTERY_INTERVAL) { + batteryTimer = 0; + + const float analogRef = 3.3; // analog reference voltage + const uint16_t analogBits = 1024; // ADC resolution is 10 bits + + int coin_adc = analogRead(BATTERY_ADC); + float coin_voltage = (float)coin_adc * analogRef / analogBits; + logger.info("Coin battery = %s", coin_voltage, 3); + + trail.rememberBAT(coin_voltage); + } + // log GPS position every few minutes, to keep track of lingering in one spot if (autoLogTimer > GPS_AUTOSAVE_INTERVAL) { autoLogTimer = 0; diff --git a/constants.h b/constants.h index 6d0176b..25ece91 100644 --- a/constants.h +++ b/constants.h @@ -212,8 +212,9 @@ struct FunctionButton { #define rFIRSTVALIDTIME "TIM" #define rLOSSOFSIGNAL "LOS" #define rACQUISITIONOFSIGNAL "AOS" +#define rCOINBATTERYVOLTAGE "BAT" #define rRESET "\0\0\0" -#define rVALIDATE rGPS rPOWERUP rPOWERDOWN rFIRSTVALIDTIME rLOSSOFSIGNAL rACQUISITIONOFSIGNAL +#define rVALIDATE rGPS rPOWERUP rPOWERDOWN rFIRSTVALIDTIME rLOSSOFSIGNAL rACQUISITIONOFSIGNAL rCOINBATTERYVOLTAGE // Breadcrumb data definition for circular buffer class Location { @@ -222,7 +223,7 @@ class Location { PointGPS loc; // has-a lat/long, degrees time_t timestamp; // has-a GMT time uint8_t numSatellites; // number of satellites in use (not the same as in view) - float speed; // current speed over ground in MPH + float speed; // current speed over ground in MPH (or coin battery voltage) float direction; // direction of travel, degrees from true north float altitude; // altitude, meters above MSL public: @@ -269,6 +270,10 @@ class Location { return (strncmp(recordType, rACQUISITIONOFSIGNAL, sizeof(recordType)) == 0); } + bool isCoinBatteryVoltage() const { + return (strncmp(recordType, rCOINBATTERYVOLTAGE, sizeof(recordType)) == 0); + } + // print ourself - a sanity check void printLocation(const char *comment = NULL) { // debug Serial.println(". Rec, ___Date___ __Time__, (__Lat__, __Long__=), Alt, Spd, Dir, Sats"); diff --git a/examples/Coin_Battery/Coin_Battery.ino b/examples/Coin_Battery/Coin_Battery.ino new file mode 100644 index 0000000..f6a2cbf --- /dev/null +++ b/examples/Coin_Battery/Coin_Battery.ino @@ -0,0 +1,126 @@ +#line 1 "C:\\Users\\barry\\Documents\\Arduino\\Griduino\\examples\\Coin_Battery\\Coin_Battery.ino" +// Please format this file with clang before check-in to GitHub +/* + Coin_Battery -- simple voltage measurement of coin battery + + Version history: + 2024-02-14 written for Griduino both pcb v4 and v12 + + Software: Barry Hansen, K7BWH, barry@k7bwh.com, Seattle, WA + Hardware: John Vanderbeck, KM7O, Seattle, WA + + This example code is in the public domain. +*/ + +#include +#include +#include + +const float analogRef = 3.3; // default analog reference voltage +const uint16_t analogBits = 1024; // default ADC resolution bits +int pcbVersion = 0; // default to unknown PCB + +#line 22 "C:\\Users\\barry\\Documents\\Arduino\\Griduino\\examples\\Coin_Battery\\Coin_Battery.ino" +bool detectDevice(int address); +#line 66 "C:\\Users\\barry\\Documents\\Arduino\\Griduino\\examples\\Coin_Battery\\Coin_Battery.ino" +int pcb_detect(); +#line 80 "C:\\Users\\barry\\Documents\\Arduino\\Griduino\\examples\\Coin_Battery\\Coin_Battery.ino" +void setup(); +#line 93 "C:\\Users\\barry\\Documents\\Arduino\\Griduino\\examples\\Coin_Battery\\Coin_Battery.ino" +void loop(); +#line 22 "C:\\Users\\barry\\Documents\\Arduino\\Griduino\\examples\\Coin_Battery\\Coin_Battery.ino" +bool detectDevice(int address) { + // https://github.com/adafruit/Adafruit_BusIO/blob/master/Adafruit_I2CDevice.h + Adafruit_I2CDevice device(address); + if (device.detected()) { + Serial.print("I2C device found at address 0x"); + if (address < 16) + Serial.print("0"); + Serial.println(address, HEX); + + delay(10); + device.end(); + return true; + } else { + delay(10); + device.end(); + return false; + } + + /* ***** + // https://learn.adafruit.com/scanning-i2c-addresses/arduino + byte error; + + // The i2c_scanner uses the return value of the Write.endTransmission + // to see if a device did acknowledge the address. + //WIRE.beginTransmission(address); + //error = WIRE.endTransmission(); + + if (error == 0) { + Serial.print("I2C device found at address 0x"); + if (address < 16) + Serial.print("0"); + Serial.println(address, HEX); + return true; + + } else if (error == 4) { + Serial.print("Unknown error at address 0x"); + if (address < 16) + Serial.print("0"); + Serial.println(address, HEX); + } + return false; + ***** */ +} + +int pcb_detect() { + Serial.println("Scanning..."); + bool i00 = detectDevice(0x00); + bool i01 = detectDevice(0x01); + bool i3E = detectDevice(0x3E); + bool i77 = detectDevice(0x77); + + if (!i3E || !i77) { + return 4; + } else { + return 11; + } +} + +void setup() { + Serial.begin(115200); + + // Wait for Serial port to open + while (!Serial) { + delay(10); + } + delay(500); + Serial.println(__FILE__); + Serial.println("Griduino Coin Battery Measurement"); + Serial.println("Sampled resolution is (3.3v)/1024 = 3 mV"); +} + +void loop() { + Serial.println(""); + pcbVersion = pcb_detect(); + Serial.print("PCB version = "); + Serial.println(pcbVersion); + + int coin_adc = analogRead(A2); + float coin_voltage = (float)coin_adc * analogRef / analogBits; + + Serial.print("Coin battery sampled = "); + Serial.println(coin_adc); + + Serial.print("Coin battery voltage = "); + Serial.print(coin_voltage); + Serial.print(" volts"); + + if (coin_voltage < 2.8) { + Serial.println(" Warning! Low voltage!"); + } else { + Serial.println(""); + } + + delay(4000); +} diff --git a/hardware.h b/hardware.h index 9890cf4..f0d76e9 100644 --- a/hardware.h +++ b/hardware.h @@ -118,10 +118,8 @@ On-board lights: #define PIN_SPEAKER DAC0 // uses DAC #endif -// ---------- Battery voltage sensor -#if defined(ARDUINO_ADAFRUIT_FEATHER_RP2040) -#define BATTERY_ADC A1 -#endif +// ---------- Coin battery voltage sensor +#define BATTERY_ADC A2 // ---------- Feather RP2040 onboard led #if defined(ARDUINO_ADAFRUIT_FEATHER_RP2040) diff --git a/logger.h b/logger.h index bc85477..12bd3cc 100644 --- a/logger.h +++ b/logger.h @@ -39,6 +39,9 @@ enum { #include // for "strncpy" and others #include "logger.h" // conditional printing to Serial port +// extern +void floatToCharArray(char *result, int maxlen, double fValue, int decimalPlaces); // Griduino.ino + class Logger { public: @@ -117,6 +120,15 @@ class Logger { Serial.println(msg); } } + void info(const char *pText, const float value1, const int decimalPlaces) { // one format string containing %s, one float, one int + if (print_info) { + char msg[256]; + char sFloat[8]; + floatToCharArray(sFloat, sizeof(sFloat), value1, decimalPlaces); + snprintf(msg, sizeof(msg), pText, sFloat); + Serial.println(msg); + } + } void info(const char *pText, const int value1, const int value2) { // one format string, two numbers if (print_info) { char msg[256]; diff --git a/model_breadcrumbs.cpp b/model_breadcrumbs.cpp index 4482c23..dd285b4 100644 --- a/model_breadcrumbs.cpp +++ b/model_breadcrumbs.cpp @@ -94,6 +94,12 @@ void Breadcrumbs::dumpHistoryGPS(int limit) { snprintf(out, sizeof(out), "%d, %s, %s, %s, %s, %s, %s, %s, %s, %s, %d", ii, item->recordType, sDate, sTime, grid6, sLat, sLng, sAltitude, sSpeed, sDirection, nSats); + } else if (item->isCoinBatteryVoltage()) { + char sVolts[12]; + floatToCharArray(sVolts, sizeof(sVolts), item->speed, 2); + snprintf(out, sizeof(out), "%d, %s, %s, %s, %s", + ii, item->recordType, sDate, sTime, sVolts); + } else { // format for "should not happen" messages snprintf(out, sizeof(out), "%d, --> Type '%s' unknown: ", ii, item->recordType); diff --git a/model_breadcrumbs.h b/model_breadcrumbs.h index 4e56458..fe25e27 100644 --- a/model_breadcrumbs.h +++ b/model_breadcrumbs.h @@ -8,7 +8,7 @@ Breadcrumb trail strategy: This is a "model" of the Model-View-Controller design pattern. - As such, it contains the data for holding all the breadcrumbs and is a + As such, it contains the data for holding all the breadcrumbs and is a low-level component with minimal side effects and few dependencies. This "Breadcrumbs" object will just only manage itself. @@ -18,7 +18,7 @@ We don't reach into the "model" from here and tell it what to do. If the controller tells us to save to file, don't tell the "model" to do anything. - When should the controller should remember a new breadcrumb? + When should the controller tell us to remember a new breadcrumb? 1. Every ten minutes 2. When we drive a visible distance on the screen 3. When we drive into a new 6-digit grid square (todo) @@ -166,6 +166,13 @@ class Breadcrumbs { remember(vLoc); } + void rememberBAT(float volts) { // save "coin battery voltage" in history buffer + // all we have to do is save a float, so re-use the "speed" field + Location bat{rCOINBATTERYVOLTAGE, noLocation, now(), noSatellites, volts, noDirection, noAltitude}; + strncpy(bat.recordType, rCOINBATTERYVOLTAGE, sizeof(bat.recordType)); + remember(bat); + } + void rememberFirstValidTime(time_t vTime, uint8_t vSats) { // save "first valid time received from GPS" // "first valid time" can happen _without_ a satellite fix, // so the only data stored is the GMT timestamp