From 965fb693426df4cdb58d8ec146a268279ed6cc1d Mon Sep 17 00:00:00 2001 From: Sebastian Romero Date: Wed, 26 Apr 2023 10:44:03 +0200 Subject: [PATCH 01/48] nicla-system: Rework battery API. --- libraries/Nicla_System/src/Nicla_System.cpp | 198 +++++++++++++++++--- libraries/Nicla_System/src/Nicla_System.h | 97 +++++++++- 2 files changed, 262 insertions(+), 33 deletions(-) diff --git a/libraries/Nicla_System/src/Nicla_System.cpp b/libraries/Nicla_System/src/Nicla_System.cpp index d01ab1d60..88c0d4ce2 100644 --- a/libraries/Nicla_System/src/Nicla_System.cpp +++ b/libraries/Nicla_System/src/Nicla_System.cpp @@ -107,6 +107,10 @@ uint8_t nicla::readLDOreg() return _pmic.readByte(BQ25120A_ADDRESS, BQ25120A_LDO_CTRL); } +/** + * @brief Defines if the connected battery has a negative temperature coefficient (NTC) thermistor. + * NTCs are used to prevent the batteries from being charged at temperatures that are too high or too low. + */ bool nicla::ntc_disabled; bool nicla::enableCharge(uint8_t mA, bool disable_ntc) @@ -120,7 +124,7 @@ bool nicla::enableCharge(uint8_t mA, bool disable_ntc) } _pmic.writeByte(BQ25120A_ADDRESS, BQ25120A_FAST_CHG, _chg_reg); - // For very depleted batteries, set ULVO at the very minimum to re-enable charging + // For very depleted batteries, set ULVO at the very minimum (2.2V) to re-enable charging _pmic.writeByte(BQ25120A_ADDRESS, BQ25120A_ILIM_UVLO_CTRL, 0x3F); // Disable TS and interrupt on charge @@ -135,45 +139,191 @@ bool nicla::enableCharge(uint8_t mA, bool disable_ntc) return _pmic.readByte(BQ25120A_ADDRESS, BQ25120A_FAST_CHG) == _chg_reg; } +/** + * @brief Returns potential battery faults. The first 8 bits (bit 0-7) are the fault register, the last 2 bits are the TS_CONTROL register. + * + * @return uint16_t + */ uint16_t nicla::getFault() { uint16_t tmp = _pmic.readByte(BQ25120A_ADDRESS, BQ25120A_FAULTS) << 8; tmp |= (_pmic.readByte(BQ25120A_ADDRESS, BQ25120A_TS_CONTROL) & 0x60); return tmp; } -int nicla::getBatteryStatus() { - _pmic.writeByte(BQ25120A_ADDRESS, BQ25120A_BATT_MON, 1); - delay(3); +float nicla::getRegulatedBatteryVoltage(){ + /* + According to https://www.ti.com/lit/ds/symlink/bq25120a.pdf Page 40: + + +---------+--------------------+ + | Bit | Regulation Voltage | + +---------+--------------------+ + | 7 (MSB) | 640 mV | + | 6 | 320 mV | + | 5 | 160 mV | + | 4 | 80 mV | + | 3 | 40 mV | + | 2 | 20 mV | + | 1 | 10 mV | + | 0 (LSB) | – | + +---------+--------------------+ + + // Example: 01111000 results in + // 3.6 + 0.32 + 0.16 + 0.08 + 0.04 = 4.2V which is the default value after reset + */ + + // Read the Battery Voltage Control Register that holds the regulated battery voltage + uint8_t data = _pmic.readByte(BQ25120A_ADDRESS, BQ25120A_BATTERY_CTRL); + int milliVolts = 360; // 3.6V is the minimum voltage + + // Loop through bits 1-7. LSB is bit 0 and it's not used + for (int i = 1; i <= 7; ++i) { + if (data & (1 << i)) { + int addition = 1 << (i - 1); // 2^(i-1): 10, 20, 40, 80, 160, 320, 640 mV + milliVolts += addition; + } + } + + return milliVolts / 100.0f; + +} + +float nicla::getCurrentBatteryVoltage(){ + return getRegulatedBatteryVoltage() / 100 * getBatteryPercentage(); +} + +int8_t nicla::getBatteryPercentage(bool useLatchedValue) { + /* + * 9.3.4 Voltage Based Battery Monitor (Page 20) + * The device implements a simple voltage battery monitor which can be used to determine the depth of discharge. + * Prior to entering High-Z mode, the device will initiate a VBMON reading. The host can read the latched value for + * the no-load battery voltage, or initiate a reading using VBMON_READ to see the battery voltage under a known + * load. The register will be updated and can be read 2ms after a read is initiated. The VBMON voltage threshold is + * readable with 2% increments with ±1.5% accuracy between 60% and 100% of VBATREG using the VBMON_TH + * registers. Reading the value during charge is possible, but for the most accurate battery voltage indication, it is + * recommended to disable charge, initiate a read, and then re-enable charge. + */ + /* + According to https://www.ti.com/lit/ds/symlink/bq25120a.pdf Page 45: + MSB = Bit 7, LSB = Bit 0 + + +----------+------------------------+ + | Bits 5+6 | Meaning | + +----------+------------------------+ + | 11 | 90% to 100% of VBATREG | + | 10 | 80% to 90% of VBATREG | + | 01 | 70% to 80% of VBATREG | + | 00 | 60% to 70% of VBATREG | + +----------+------------------------+ + + +----------+-------------------------+ + | Bits 2-4 | Meaning | + +----------+-------------------------+ + | 111 | Above 8% of VBMON_RANGE | + | 110 | Above 6% of VBMON_RANGE | + | 011 | Above 4% of VBMON_RANGE | + | 010 | Above 2% of VBMON_RANGE | + | 001 | Above 0% of VBMON_RANGE | + +----------+-------------------------+ + + Example: 0 11 111 00 -> 90% + 8% = 98 - 100% of VBATREG + */ + constexpr uint8_t BAT_UVLO_FAULT = 0b00100000; // Battery Under-Voltage Lock-Out fault + uint8_t faults = _pmic.readByte(BQ25120A_ADDRESS, BQ25120A_FAULTS); + if(faults & BAT_UVLO_FAULT) return -1; // Battery is not connected or voltage is too low + + // Write 1 to VBMON_READ to trigger a new reading + // TODO: Disable charging while reading battery percentage. SEE chapter 9.3.4 + + if(!useLatchedValue){ + // Write 1 to VBMON_READ to trigger a new reading + _pmic.writeByte(BQ25120A_ADDRESS, BQ25120A_BATT_MON, 1); + delay(3); // According to datasheet, 2ms is enough, but we add 1ms for safety + } uint8_t data = _pmic.readByte(BQ25120A_ADDRESS, BQ25120A_BATT_MON); - float percent = 0.6f + (data >> 5) * 0.1f + ((data >> 2) & 0x7) * 0.02f; - - int res = 0; - if (percent >= 0.98) { - res |= BATTERY_FULL; - } else if (percent >= 0.94){ - res |= BATTERY_ALMOST_FULL; - } else if (percent >= 0.90){ - res |= BATTERY_HALF; - } else if (percent >= 0.86){ - res |= BATTERY_ALMOST_EMPTY; - } else { - res |= BATTERY_EMPTY; + + // If bit 2 - 7 are all zeroes, the battery status could not be read + if((data & 0b11111100) == 0) return -2; + + // Extract bits 5 and 6 + // Masking is optional because the MSB is always 0 + uint8_t bits56 = (data >> 5) & 0b11; + + // Extract bits 2 to 4 + uint8_t bits234 = (data >> 2) & 0b111; + + // FIXME: The pattern 000 is not defined in the datasheet but still occurs + // along with a valid bits56 pattern. We assume that it means 0%. + // if(bits234 == 0b000) return -1; // Battery status could not be read + + // Lookup tables for mapping bit patterns to percentage values + // The datasheet says that the threshold values are above what's written in the table. + // Therefore we use the next higher value in steps of 2%. + // That way the final percentage is always rounded up and can reach 100%. + int thresholdLookup[] = {0, 2, 4, 6, 0, 0, 8, 10}; + + // bits56 has a range of 0 to 3, so we multiply it by 10 and add 60 to get a range of 60 to 90 + int percentageTens = 60 + (bits56 * 10); + // Map bit patterns to percentage values using lookup table + int percentageOnes = thresholdLookup[bits234]; + + // Calculate the final percentage + return percentageTens + percentageOnes; +} + +uint8_t nicla::getBatteryChargeLevel() { + return getBatteryStatus() & BATTERY_CHARGE_MASK; +} + +uint8_t nicla::getBatteryStatus() { + auto percent = getBatteryPercentage(); + int res = BATTERY_UNKNOWN; + + if (percent >= 98) { + res = BATTERY_FULL; + } else if (percent >= 94){ + res = BATTERY_ALMOST_FULL; + } else if (percent >= 90){ + res = BATTERY_HALF; + } else if (percent >= 86){ + res = BATTERY_ALMOST_EMPTY; + } else if(percent < 86 && percent > 0) { + // < 84% is considered empty + res = BATTERY_EMPTY; } if (!ntc_disabled) { - auto ts = ((_pmic.readByte(BQ25120A_ADDRESS, BQ25120A_TS_CONTROL) >> 5) & 0x3); - if (ts == 1) { - res |= BATTERY_COLD; - } else if (ts == 2) { - res |= BATTERY_COOL; - } else if (ts == 3) { - res |= BATTERY_HOT; + // Extract bits 5 and 6 (TS_FAULT0 and TS_FAULT1) + uint8_t temperatureSenseFault = _pmic.readByte(BQ25120A_ADDRESS, BQ25120A_TS_CONTROL) >> 5 & 0b11; + + /* + +------+-------------------------------------------------------------+ + | Bits | Description | + +------+-------------------------------------------------------------+ + | 00 | Normal, No TS fault | + | 01 | TS temp < TCOLD or TS temp > THOT (Charging suspended) | + | 10 | TCOOL > TS temp > TCOLD (Charging current reduced by half) | + | 11 | TWARM < TS temp < THOT (Charging voltage reduced by 140 mV) | + +------+-------------------------------------------------------------+ + */ + + if(temperatureSenseFault == 0){ + res |= BATTERY_TEMPERATURE_NORMAL; + } else if (temperatureSenseFault == 1) { + res |= BATTERY_TEMPERATURE_EXTREME; + } else if (temperatureSenseFault == 2) { + res |= BATTERY_TEMPERTURE_COOL; + } else if (temperatureSenseFault == 3) { + res |= BATTERY_TEMPERTURE_WARM; } } return res; } +uint8_t nicla::getBatteryTemperature() { + return getBatteryStatus() & BATTERY_TEMPERATURE_MASK; +} + void nicla::checkChgReg() { if (_chg_reg != _pmic.readByte(BQ25120A_ADDRESS, BQ25120A_FAST_CHG)) { diff --git a/libraries/Nicla_System/src/Nicla_System.h b/libraries/Nicla_System/src/Nicla_System.h index 85849ba08..c28e89a2f 100644 --- a/libraries/Nicla_System/src/Nicla_System.h +++ b/libraries/Nicla_System/src/Nicla_System.h @@ -10,14 +10,22 @@ #define USE_FASTCHG_TO_KICK_WATCHDOG 1 -#define BATTERY_FULL 5 -#define BATTERY_ALMOST_FULL 4 -#define BATTERY_HALF 3 -#define BATTERY_ALMOST_EMPTY 2 -#define BATTERY_EMPTY 1 -#define BATTERY_COLD (1 << 4) -#define BATTERY_COOL (2 << 4) -#define BATTERY_HOT (3 << 4) +// 3 bits are used to indicate the battery charge level +#define BATTERY_CHARGE_MASK 0b00000111 +#define BATTERY_FULL 5 // Bit pattern: 101 +#define BATTERY_ALMOST_FULL 4 // Bit pattern: 100 +#define BATTERY_HALF 3 // Bit pattern: 011 +#define BATTERY_ALMOST_EMPTY 2 // Bit pattern: 010 +#define BATTERY_EMPTY 1 // Bit pattern: 001 +#define BATTERY_UNKNOWN 0 // Bit pattern: 000 + +// 2 bits are used to indicate the battery temperature +#define BATTERY_TEMPERATURE_MASK 0b00011000 +#define BATTERY_TEMPERATURE_NORMAL (0 << 4) +#define BATTERY_TEMPERATURE_EXTREME (1 << 4) +#define BATTERY_TEMPERTURE_COOL (2 << 4) +#define BATTERY_TEMPERTURE_WARM (3 << 4) + #define BATTERY_CHARGING (1 << 7) class nicla { @@ -27,11 +35,82 @@ class nicla { static bool enable3V3LDO(); static bool enable1V8LDO(); static bool disableLDO(); + + /** + * @brief Enter Ship Mode. This is used during the shipping time or the shelf time of the product. + * This will turn off the battery FET and thus isolate the battery from the system (turns off leakage path). + * So, whatever is leaky in your system won't be leaking out of the battery during this time. + * + * @return true if the ship mode is entered successfully. + */ static bool enterShipMode(); static uint8_t readLDOreg(); static bool enableCharge(uint8_t mA = 20, bool disable_ntc = true); - static int getBatteryStatus(); + + /** + * @brief Get the Regulated Battery Voltage in Volts. + * + * @return float The regulated battery voltage in Volts. The default regulated voltage is 4.2V. + */ + static float getRegulatedBatteryVoltage(); + + /** + * @brief Get the Current Battery Voltage in Volts. This value is calculated by multiplying + * the regulated voltage by the battery percentage. + * + * @return float The current battery voltage in Volts. + */ + static float getCurrentBatteryVoltage(); + + /** + * @brief Get the percentage of the battery's regulated voltage under a known load. + * + * The accuracy of the battery voltage monitor (VBAT monitor) is between -3.5% and +3.5% of the regulated voltage (VBATREG). + * @note This does not denote the actual battery charge level but the percentage of the fully charged voltage. + * Many common LiPo batteries have a nominal voltage of 3.7V and a fully charged voltage of 4.2V. + * For a 4.2V regulated voltage battery < 84% is considered empty. < 60% is considered critical; the battery may be damaged. + * @param useLatchedValue Before entering a low power state (High Impedance mode), the device will determine the voltage + * and store it in a latched register. If true, the latched (stored) value is returned. + * If false, a new reading is taken from the PMIC. The default is false, so a new reading is taken. + * @return int8_t The percentage of the regulated voltage in the range of 60% to 100%. + * A value of < 0 indicates that the battery percentage could not be determined. + */ + static int8_t getBatteryPercentage(bool useLatchedValue = false); + + /** + * @brief Get the Battery Charge level encoded as a number (0-5). The following values are possible: + * "Unknown", "Empty", "Almost Empty", "Half Full", "Almost Full", "Full" + * + * @return uint8_t The battery charge level represented by one of the following constants: + * BATTERY_UNKNOWN, BATTERY_FULL, BATTERY_ALMOST_FULL, BATTERY_HALF, BATTERY_ALMOST_EMPTY, BATTERY_EMPTY + */ + static uint8_t getBatteryChargeLevel(); + + /** + * @brief Get the Battery Temperature. The following values are possible: + * "Normal", "Extreme", "Cool", "Warm". + * When the battery is cool, the charging current is reduced by half. + * When the battery is warm, the charging current is reduced by 140 mV. + * When the battery is unter an extreme temperature (hot or cold), the charging is suspended. + * @note If the battery doesn't have a negative temperature coefficient (NTC) thermistor, the temperature is always "Normal". + * @return uint8_t The battery temperature represented by one of the following constants: + * BATTERY_TEMPERATURE_NORMAL, BATTERY_TEMPERATURE_EXTREME, BATTERY_TEMPERTURE_COOL, BATTERY_TEMPERTURE_WARM + */ + static uint8_t getBatteryTemperature(); + + /** + * @brief Get the Battery Status (charge level and temperature). + * The first 3 bits indicate the battery charge level. They can be retrieved using the BATTERY_CHARGE_MASK. + * The 4th and 5th bit indicate the battery temperature. They can be retrieved using the BATTERY_TEMPERATURE_MASK. + * @see getBatteryChargeLevel() + * @see getBatteryTemperature() + * @return uint8_t The battery status containing the charge level and temperature. + */ + static uint8_t getBatteryStatus(); + + static uint16_t getFault(); + static bool ntc_disabled; static RGBled leds; From d8fc79883820416d2a89f28451e6a60649c81c90 Mon Sep 17 00:00:00 2001 From: Sebastian Romero Date: Wed, 26 Apr 2023 16:52:55 +0200 Subject: [PATCH 02/48] nicla-sense-pmic: Use function to toggle Hi-Z mode. --- libraries/Nicla_System/src/BQ25120A.h | 19 ++++++++++++++++-- libraries/Nicla_System/src/pmic_driver.cpp | 23 +++++++++++++++++----- 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/libraries/Nicla_System/src/BQ25120A.h b/libraries/Nicla_System/src/BQ25120A.h index 7c3317382..77b05e5f7 100644 --- a/libraries/Nicla_System/src/BQ25120A.h +++ b/libraries/Nicla_System/src/BQ25120A.h @@ -4,13 +4,13 @@ #define BQ25120A_ADDRESS 0x6A // Register Map -// https://www.ti.com/lit/ds/symlink/bq25120a.pdf?ts=1610608851953&ref_url=https%253A%252F%252Fwww.startpage.com%252F +// https://www.ti.com/lit/ds/symlink/bq25120a.pdf #define BQ25120A_STATUS 0x00 #define BQ25120A_FAULTS 0x01 #define BQ25120A_TS_CONTROL 0x02 #define BQ25120A_FAST_CHG 0x03 #define BQ25120A_TERMINATION_CURR 0x04 -#define BQ25120A_BATTERY_CTRL 0x05 +#define BQ25120A_BATTERY_CTRL 0x05 // Battery Voltage Control Register #define BQ25120A_SYS_VOUT_CTRL 0x06 #define BQ25120A_LDO_CTRL 0x07 #define BQ25120A_PUSH_BUTT_CTRL 0x08 @@ -29,6 +29,21 @@ class BQ25120A void writeByte(uint8_t address, uint8_t subAddress, uint8_t data); uint8_t readByte(uint8_t address, uint8_t subAddress); + private: + /** + * @brief Set the High Impedance Mode Enabled or Disabled. + * When enabled, drives the CD pin low to enter high impedance mode. + * Note that this only applies when powered with a battery and the condition VIN < VUVLO is met. + * When VIN > VUVLO this enables charging instead. + * + * When disabled, drives the CD pin high to exit high impedance mode (Active Battery). + * When VIN > VUVLO this disables charging. + * When exiting this mode, charging resumes if VIN is present, CD is low and charging is enabled. + * + * @note The CD pin is internally pulled down. + * @param enabled Defines if the high impedance mode should be enabled or disabled. + */ + void setHighImpedanceModeEnabled(bool enabled); }; #endif diff --git a/libraries/Nicla_System/src/pmic_driver.cpp b/libraries/Nicla_System/src/pmic_driver.cpp index 097214468..ee9c3f265 100644 --- a/libraries/Nicla_System/src/pmic_driver.cpp +++ b/libraries/Nicla_System/src/pmic_driver.cpp @@ -4,7 +4,11 @@ #include "Nicla_System.h" #include "DigitalOut.h" -static mbed::DigitalOut cd(p25); +// Set the CD pin to low to enter high impedance mode +// Note that this only applies when powered with a battery +// and the condition VIN < VUVLO is met. +// When VIN > VUVLO this enables charging. +static mbed::DigitalOut cd(p25, 0); uint8_t BQ25120A::getStatus() { @@ -14,19 +18,19 @@ uint8_t BQ25120A::getStatus() void BQ25120A::writeByte(uint8_t address, uint8_t subAddress, uint8_t data) { - cd = 1; + setHighImpedanceModeEnabled(false); nicla::i2c_mutex.lock(); Wire1.beginTransmission(address); Wire1.write(subAddress); Wire1.write(data); Wire1.endTransmission(); nicla::i2c_mutex.unlock(); - cd = 0; + setHighImpedanceModeEnabled(true); } uint8_t BQ25120A::readByte(uint8_t address, uint8_t subAddress) { - cd = 1; + setHighImpedanceModeEnabled(false); nicla::i2c_mutex.lock(); Wire1.beginTransmission(address); Wire1.write(subAddress); @@ -37,6 +41,15 @@ uint8_t BQ25120A::readByte(uint8_t address, uint8_t subAddress) while(!Wire1.available() && (millis() - start_time) < timeout) {} uint8_t ret = Wire1.read(); nicla::i2c_mutex.unlock(); - cd = 0; + setHighImpedanceModeEnabled(true); return ret; } + +void BQ25120A::setHighImpedanceModeEnabled(bool enabled) { + if(enabled){ + cd = 0; + } else { + cd = 1; + delayMicroseconds(64); // Give some time to the BQ25120A to wake up + } +} \ No newline at end of file From 20ee494c6e96f31d5ae4a9aa43a9f12f106fb5c6 Mon Sep 17 00:00:00 2001 From: Sebastian Romero Date: Thu, 27 Apr 2023 10:36:20 +0200 Subject: [PATCH 03/48] nicla-system: Add battery monitor web app. --- .../Nicla_System/extras/BatteryMonitor/app.js | 141 ++++++++++++++++++ .../extras/BatteryMonitor/index.html | 25 ++++ .../extras/BatteryMonitor/style.css | 59 ++++++++ 3 files changed, 225 insertions(+) create mode 100644 libraries/Nicla_System/extras/BatteryMonitor/app.js create mode 100644 libraries/Nicla_System/extras/BatteryMonitor/index.html create mode 100644 libraries/Nicla_System/extras/BatteryMonitor/style.css diff --git a/libraries/Nicla_System/extras/BatteryMonitor/app.js b/libraries/Nicla_System/extras/BatteryMonitor/app.js new file mode 100644 index 000000000..5cd1dd552 --- /dev/null +++ b/libraries/Nicla_System/extras/BatteryMonitor/app.js @@ -0,0 +1,141 @@ +const connectButton = document.getElementById('connect'); +const batteryLevelElement = document.getElementById('battery-level'); +const batteryLabel = document.getElementById('battery-label'); + +const serviceUuid = '19b10000-0000-537e-4f6c-d104768a1214'; +let pollIntervalID; +let peripheralDevice; + +let data = { + "batteryPercentage": { + "name": "Battery Percentage", + "value": 0, + "unit": "%", + "characteristic": null, + "characteristicUUID": "19b10000-1001-537e-4f6c-d104768a1214", + "extractData": function(dataView) { + return dataView.getInt8(0); + } + }, + "batteryVoltage": { + "name": "Battery Voltage", + "value": 0, + "unit": "V", + "characteristic": null, + "characteristicUUID": "19b10000-1002-537e-4f6c-d104768a1214", + "extractData": function(dataView) { + return dataView.getFloat32(0, true); + } + }, + "batteryChargeLevel": { + "name": "Battery Charge Level", + "value": 0, + "unit": "", + "characteristic": null, + "characteristicUUID": "19b10000-1003-537e-4f6c-d104768a1214", + "extractData": function(dataView) { + return dataView.getInt8(0); + }, + "getColor": function(value) { + // Red to green range with 5 steps and white for the unknown state + const colors = ["#ffffff", "#ff2d2d", "#fc9228", "#ffea00", "#adfd5c", "#00c600"]; + return colors[value]; + } + } +}; + +function onDisconnected(event) { + let device = event.target; + connectButton.disabled = false; + connectButton.style.opacity = 1; + if(pollIntervalID) clearInterval(pollIntervalID); + console.log(`Device ${device.name} is disconnected.`); + + // Reset the battery level display + batteryLevelElement.style.width = "0px"; + batteryLabel.textContent = ""; +} + +async function connectToPeripheralDevice(usePolling = false, pollInterval = 5000){ + if (peripheralDevice && peripheralDevice.gatt.connected) { + console.log("Already connected"); + return; + } + + peripheralDevice = await navigator.bluetooth.requestDevice({ + filters: [{ services: [serviceUuid] }] + }); + peripheralDevice.addEventListener('gattserverdisconnected', onDisconnected); + + const server = await peripheralDevice.gatt.connect(); + console.log("Connected to: " + peripheralDevice.name); + const service = await server.getPrimaryService(serviceUuid); + + await Promise.all( + Object.keys(data).map(async (key) => { + let item = data[key]; + const characteristic = await service.getCharacteristic(item.characteristicUUID); + item.characteristic = characteristic; + + if (!usePolling) { + characteristic.addEventListener('characteristicvaluechanged', handleCharacteristicChange); + characteristic.readValue(); // Perform an initial read + await characteristic.startNotifications(); + } + }) + ); + + if (usePolling) { + pollIntervalID = setInterval(readCharacteristicsData, pollInterval); + await readCharacteristicsData(); + } +} + +connectButton.addEventListener('click', async () => { + try { + await connectToPeripheralDevice(true); + connectButton.disabled = true; + connectButton.style.opacity = 0.5; + } catch (error) { + if(error.message === "User cancelled the requestDevice() chooser."){ + return; + } + + console.error('Error:', error); + connectButton.style.backgroundColor = "red"; + } +}); + +function displayBatteryLevel() { + const batteryPercentage = data.batteryPercentage.value; + const batteryVoltage = data.batteryVoltage.value; + const regulatedVoltage = (batteryVoltage / batteryPercentage * 100).toFixed(2); + + // Map the range from 0-5 to 0-100 + const batteryPercentageMapped = data.batteryChargeLevel.value * 20; + batteryLevelElement.style.width = `${batteryPercentageMapped * 0.56}px`; // Scale the battery level to the width of the battery div + batteryLevelElement.style.backgroundColor = data.batteryChargeLevel.getColor(data.batteryChargeLevel.value); + batteryLabel.textContent = `${batteryVoltage.toFixed(2)}V (${batteryPercentage}% of ${regulatedVoltage}V)`; +} + +async function readCharacteristicsData() { + await Promise.all( + Object.keys(data).map(async (key) => { + let item = data[key]; + console.log("Requesting " + item.name + "..."); + item.value = item.extractData(await item.characteristic.readValue()); + console.log(item.name + ": " + item.value + item.unit); + }) + ); + displayBatteryLevel(); +} + +function handleCharacteristicChange(event) { + // Find the characteristic that changed in the data object by matching the UUID + let dataItem = Object.values(data).find(item => item.characteristicUUID === event.target.uuid); + let dataView = event.target.value; + dataItem.value = dataItem.extractData(dataView); + + console.log(dataItem.name + " changed: " + dataItem.value + dataItem.unit); + displayBatteryLevel(); +} diff --git a/libraries/Nicla_System/extras/BatteryMonitor/index.html b/libraries/Nicla_System/extras/BatteryMonitor/index.html new file mode 100644 index 000000000..8d3acdbae --- /dev/null +++ b/libraries/Nicla_System/extras/BatteryMonitor/index.html @@ -0,0 +1,25 @@ + + + + + + WebBLE Battery Monitor + + + + + + + +

WebBLE Battery Monitor 🔋

+
+
+
+
+

+ +
+ + diff --git a/libraries/Nicla_System/extras/BatteryMonitor/style.css b/libraries/Nicla_System/extras/BatteryMonitor/style.css new file mode 100644 index 000000000..d7336b0ec --- /dev/null +++ b/libraries/Nicla_System/extras/BatteryMonitor/style.css @@ -0,0 +1,59 @@ +:root { + --main-control-color: #008184; + --main-control-color-hover: #005c5f; + --main-flexbox-gap: 16px; + --secondary-text-color: #87898b; +} + +body { + font-family: 'Open Sans', sans-serif; + text-align: center; +} + +.container { + display: inline-block; + margin-top: 20px; +} + +button { + font-family: 'Open Sans', sans-serif; + font-weight: 700; + font-size: 1rem; + justify-content: center; + background-color: var(--main-control-color); + color: #fff; + cursor: pointer; + letter-spacing: 1.28px; + line-height: normal; + outline: none; + padding: 8px 18px; + text-align: center; + text-decoration: none; + border: 2px solid transparent; + border-radius: 32px; + text-transform: uppercase; + box-sizing: border-box; +} + +button:hover { + background-color: var(--main-control-color-hover); +} + +.battery { + width: 60px; + height: 30px; + border: 2px solid #999; + border-radius: 5px; + position: relative; + margin: 20px auto; +} + +.battery-level { + position: absolute; + bottom: 2px; + left: 2px; + background-color: green; + border-radius: 2px; + width: 0; + height: 26px; +} From 864d9a38fd14dd380b4bed69c758bf50cc97ea19 Mon Sep 17 00:00:00 2001 From: Sebastian Romero Date: Thu, 27 Apr 2023 10:37:15 +0200 Subject: [PATCH 04/48] nicla-system: Add battery check sketch for Nicla Sense. --- .../NiclaSenseME_BatteryStatus.ino | 221 ++++++++++++++++++ 1 file changed, 221 insertions(+) create mode 100644 libraries/Nicla_System/examples/NiclaSenseME_BatteryStatus/NiclaSenseME_BatteryStatus.ino diff --git a/libraries/Nicla_System/examples/NiclaSenseME_BatteryStatus/NiclaSenseME_BatteryStatus.ino b/libraries/Nicla_System/examples/NiclaSenseME_BatteryStatus/NiclaSenseME_BatteryStatus.ino new file mode 100644 index 000000000..aa5a5c6fe --- /dev/null +++ b/libraries/Nicla_System/examples/NiclaSenseME_BatteryStatus/NiclaSenseME_BatteryStatus.ino @@ -0,0 +1,221 @@ +#include "Nicla_System.h" +#include + +constexpr auto printInterval { 4000ul }; +constexpr auto batteryMeasureInterval { 5000ul }; +int8_t batteryChargeLevel = -1; +int8_t batteryPercentage = -1; +float batteryVoltage = -1.0f; + + +#define DEVICE_NAME "NiclaSenseME" +#define DEVICE_UUID(val) ("19b10000-" val "-537e-4f6c-d104768a1214") +BLEService service(DEVICE_UUID("0000")); + +BLEIntCharacteristic batteryPercentageCharacteristic(DEVICE_UUID("1001"), BLERead | BLENotify); +BLEFloatCharacteristic batteryVoltageCharacteristic(DEVICE_UUID("1002"), BLERead | BLENotify); +BLEIntCharacteristic batteryChargeLevelCharacteristic(DEVICE_UUID("1003"), BLERead | BLENotify); + +bool updateBatteryLevel(bool enforceNewReading = false) { + static auto updateTimestamp = millis(); + bool intervalFired = millis() - updateTimestamp >= batteryMeasureInterval; + bool isFirstReading = batteryPercentage == -1 || batteryVoltage == -1.0f; + + if (intervalFired || isFirstReading || enforceNewReading) { + Serial.println("Checking the battery level..."); + updateTimestamp = millis(); + auto percentage = nicla::getBatteryPercentage(); + + if (percentage < 0) { + return false; // Percentage couldn't be determined. + } + + if (batteryPercentage != percentage) { + batteryPercentage = percentage; + batteryVoltage = nicla::getCurrentBatteryVoltage(); + batteryChargeLevel = nicla::getBatteryChargeLevel(); + + Serial.print("New battery level: "); + Serial.println(batteryPercentage); + + return true; + } + } + + return false; +} + +String getBatteryTemperatureDescription(int status) { + switch (status) { + case BATTERY_TEMPERATURE_NORMAL: + return "Normal"; + case BATTERY_TEMPERATURE_EXTREME: + return "Extreme"; + case BATTERY_TEMPERTURE_COOL: + return "Cool"; + case BATTERY_TEMPERTURE_WARM: + return "Warm"; + default: + return "Unknown"; + } +} + +String getBatteryChargeLevelDescription(int status) { + switch (status) { + case BATTERY_EMPTY: + return "Empty"; + case BATTERY_ALMOST_EMPTY: + return "Almost Empty"; + case BATTERY_HALF: + return "Half Full"; + case BATTERY_ALMOST_FULL: + return "Almost Full"; + case BATTERY_FULL: + return "Full"; + default: + return "Unknown"; + } +} + + +void blePeripheralDisconnectHandler(BLEDevice central) { + nicla::leds.setColor(red); + Serial.println("Device disconnected."); +} + +void blePeripheralConnectHandler(BLEDevice central) { + nicla::leds.setColor(blue); + Serial.println("Device connected."); +} + +void onBatteryVoltageCharacteristicRead(BLEDevice central, BLECharacteristic characteristic) { + Serial.println("Requesting battery voltage..."); + updateBatteryLevel(); + Serial.print("Battery voltage: "); + Serial.println(batteryVoltage); + batteryVoltageCharacteristic.writeValue(batteryVoltage); +} + +void onBatteryPercentageCharacteristicRead(BLEDevice central, BLECharacteristic characteristic) { + Serial.println("Requesting battery percentage..."); + updateBatteryLevel(); + Serial.print("Battery Percent: "); + Serial.println(batteryPercentage); + batteryPercentageCharacteristic.writeValue(batteryPercentage); +} + +void onBatteryChargeLevelCharacteristicRead(BLEDevice central, BLECharacteristic characteristic) { + Serial.println("Requesting battery charge level..."); + updateBatteryLevel(); + Serial.print("Battery Charge Level: "); + Serial.println(batteryChargeLevel); + batteryChargeLevelCharacteristic.writeValue(batteryChargeLevel); +} + +void onCharacteristicSubscribed(BLEDevice central, BLECharacteristic characteristic) { + Serial.println("Device subscribed to characteristic: " + String(characteristic.uuid())); +} + +void setupBLE() { + if (!BLE.begin()) { + Serial.println("Failed to initialized BLE!"); + + while (true) { + // Blink the red LED to indicate failure + nicla::leds.setColor(red); + delay(500); + nicla::leds.setColor(off); + delay(500); + } + } + + BLE.setLocalName(DEVICE_NAME); + BLE.setDeviceName(DEVICE_NAME); + BLE.setAdvertisedService(service); + BLE.setEventHandler(BLEDisconnected, blePeripheralDisconnectHandler); + BLE.setEventHandler(BLEConnected, blePeripheralConnectHandler); + + service.addCharacteristic(batteryPercentageCharacteristic); + batteryPercentageCharacteristic.setEventHandler(BLERead, onBatteryPercentageCharacteristicRead); + batteryPercentageCharacteristic.setEventHandler(BLESubscribed, onCharacteristicSubscribed); + batteryPercentageCharacteristic.writeValue(batteryPercentage); + + service.addCharacteristic(batteryVoltageCharacteristic); + batteryVoltageCharacteristic.setEventHandler(BLERead, onBatteryVoltageCharacteristicRead); + batteryVoltageCharacteristic.setEventHandler(BLESubscribed, onCharacteristicSubscribed); + batteryVoltageCharacteristic.writeValue(batteryVoltage); + + service.addCharacteristic(batteryChargeLevelCharacteristic); + batteryChargeLevelCharacteristic.setEventHandler(BLERead, onBatteryChargeLevelCharacteristicRead); + batteryChargeLevelCharacteristic.setEventHandler(BLESubscribed, onCharacteristicSubscribed); + batteryChargeLevelCharacteristic.writeValue(batteryChargeLevel); + + BLE.addService(service); + BLE.advertise(); +} + +void setup() +{ + Serial.begin(115200); + for (const auto timeout = millis() + 2500; millis() < timeout && !Serial; delay(250)); + + // run this code once when Nicla Sense ME board turns on + nicla::begin(); // initialise library + nicla::leds.begin(); // Start I2C connection + + nicla::ntc_disabled = false; // Set to true for standard LiPo batteries without NTC + setupBLE(); + + nicla::leds.setColor(green); +} + +void loop() +{ + //BLE.poll(); // Implicit when calling BLE.connected(). Uncomment when only using BLERead + + if (BLE.connected()) { + bool newBatteryLevelAvailable = updateBatteryLevel(); + + if (batteryPercentageCharacteristic.subscribed() && newBatteryLevelAvailable) { + Serial.print("Battery Percentage: "); + Serial.println(batteryPercentage); + batteryPercentageCharacteristic.writeValue(batteryPercentage); + } + + if (batteryVoltageCharacteristic.subscribed() && newBatteryLevelAvailable) { + Serial.print("Battery Voltage: "); + Serial.println(batteryVoltage); + batteryVoltageCharacteristic.writeValue(batteryVoltage); + } + + if (batteryChargeLevelCharacteristic.subscribed() && newBatteryLevelAvailable) { + Serial.print("Battery charge level: "); + Serial.println(batteryChargeLevel); + batteryChargeLevelCharacteristic.writeValue(batteryChargeLevel); + } + + return; + } + + static auto updateTimestamp = millis(); + + if (millis() - updateTimestamp >= printInterval) { + updateTimestamp = millis(); + + float voltage = nicla::getCurrentBatteryVoltage(); + Serial.print("Voltage: "); + Serial.println(voltage); + + Serial.print("Battery Percent: "); + auto percent = nicla::getBatteryPercentage(); + Serial.println(percent); + + Serial.print("Battery Temperature: "); + auto temperature = nicla::getBatteryTemperature(); + Serial.println(getBatteryTemperatureDescription(temperature)); + + auto chargeLevel = nicla::getBatteryChargeLevel(); + Serial.println("Battery is " + getBatteryChargeLevelDescription(chargeLevel)); + Serial.println("----------------------"); + } +} From bd0ee3e68af6c008d31dc3708f5756d293390606 Mon Sep 17 00:00:00 2001 From: Sebastian Romero Date: Fri, 28 Apr 2023 14:53:54 +0200 Subject: [PATCH 05/48] nicla-system: Add function for toggling battery NTC. --- .../NiclaSenseME_BatteryStatus.ino | 2 +- libraries/Nicla_System/src/Nicla_System.cpp | 18 +++++++++--------- libraries/Nicla_System/src/Nicla_System.h | 14 +++++++++++++- 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/libraries/Nicla_System/examples/NiclaSenseME_BatteryStatus/NiclaSenseME_BatteryStatus.ino b/libraries/Nicla_System/examples/NiclaSenseME_BatteryStatus/NiclaSenseME_BatteryStatus.ino index aa5a5c6fe..8de238873 100644 --- a/libraries/Nicla_System/examples/NiclaSenseME_BatteryStatus/NiclaSenseME_BatteryStatus.ino +++ b/libraries/Nicla_System/examples/NiclaSenseME_BatteryStatus/NiclaSenseME_BatteryStatus.ino @@ -163,7 +163,7 @@ void setup() nicla::begin(); // initialise library nicla::leds.begin(); // Start I2C connection - nicla::ntc_disabled = false; // Set to true for standard LiPo batteries without NTC + nicla::setBatteryNTCEnabled(true); // Set to false if your battery doesn't have an NTC thermistor. setupBLE(); nicla::leds.setColor(green); diff --git a/libraries/Nicla_System/src/Nicla_System.cpp b/libraries/Nicla_System/src/Nicla_System.cpp index 88c0d4ce2..2858bbe15 100644 --- a/libraries/Nicla_System/src/Nicla_System.cpp +++ b/libraries/Nicla_System/src/Nicla_System.cpp @@ -14,6 +14,7 @@ BQ25120A nicla::_pmic; rtos::Mutex nicla::i2c_mutex; bool nicla::started = false; uint8_t nicla::_chg_reg = 0; +bool nicla::_ntcEnabled = true; void nicla::pingI2CThd() { while(1) { @@ -107,12 +108,6 @@ uint8_t nicla::readLDOreg() return _pmic.readByte(BQ25120A_ADDRESS, BQ25120A_LDO_CTRL); } -/** - * @brief Defines if the connected battery has a negative temperature coefficient (NTC) thermistor. - * NTCs are used to prevent the batteries from being charged at temperatures that are too high or too low. - */ -bool nicla::ntc_disabled; - bool nicla::enableCharge(uint8_t mA, bool disable_ntc) { if (mA < 5) { @@ -128,8 +123,8 @@ bool nicla::enableCharge(uint8_t mA, bool disable_ntc) _pmic.writeByte(BQ25120A_ADDRESS, BQ25120A_ILIM_UVLO_CTRL, 0x3F); // Disable TS and interrupt on charge - ntc_disabled = disable_ntc; - if (ntc_disabled) { + _ntcEnabled = !disable_ntc; + if (!_ntcEnabled) { _pmic.writeByte(BQ25120A_ADDRESS, BQ25120A_TS_CONTROL, 1 << 3); } @@ -150,6 +145,10 @@ uint16_t nicla::getFault() { return tmp; } +void nicla::setBatteryNTCEnabled(bool enabled){ + _ntcEnabled = enabled; +} + float nicla::getRegulatedBatteryVoltage(){ /* According to https://www.ti.com/lit/ds/symlink/bq25120a.pdf Page 40: @@ -291,7 +290,7 @@ uint8_t nicla::getBatteryStatus() { res = BATTERY_EMPTY; } - if (!ntc_disabled) { + if (_ntcEnabled) { // Extract bits 5 and 6 (TS_FAULT0 and TS_FAULT1) uint8_t temperatureSenseFault = _pmic.readByte(BQ25120A_ADDRESS, BQ25120A_TS_CONTROL) >> 5 & 0b11; @@ -321,6 +320,7 @@ uint8_t nicla::getBatteryStatus() { } uint8_t nicla::getBatteryTemperature() { + if(!_ntcEnabled) return BATTERY_TEMPERATURE_NORMAL; return getBatteryStatus() & BATTERY_TEMPERATURE_MASK; } diff --git a/libraries/Nicla_System/src/Nicla_System.h b/libraries/Nicla_System/src/Nicla_System.h index c28e89a2f..5eea6f959 100644 --- a/libraries/Nicla_System/src/Nicla_System.h +++ b/libraries/Nicla_System/src/Nicla_System.h @@ -47,6 +47,15 @@ class nicla { static uint8_t readLDOreg(); static bool enableCharge(uint8_t mA = 20, bool disable_ntc = true); + /** + * @brief Enables or disables the negative temperature coefficient (NTC) thermistor. + * NTCs are used to prevent the batteries from being charged at temperatures that are too high or too low. + * Set to disabled for standard LiPo batteries without NTC. + * If your battery has only a plus and minus wire, it does not have an NTC. + * The default is enabled. + */ + static void setBatteryNTCEnabled(bool enabled); + /** * @brief Get the Regulated Battery Voltage in Volts. * @@ -93,6 +102,8 @@ class nicla { * When the battery is warm, the charging current is reduced by 140 mV. * When the battery is unter an extreme temperature (hot or cold), the charging is suspended. * @note If the battery doesn't have a negative temperature coefficient (NTC) thermistor, the temperature is always "Normal". + * This is not determined automatically and needs to be set using the setBatteryNTCEnabled() function. + * @see setBatteryNTCEnabled() * @return uint8_t The battery temperature represented by one of the following constants: * BATTERY_TEMPERATURE_NORMAL, BATTERY_TEMPERATURE_EXTREME, BATTERY_TEMPERTURE_COOL, BATTERY_TEMPERTURE_WARM */ @@ -111,7 +122,6 @@ class nicla { static uint16_t getFault(); - static bool ntc_disabled; static RGBled leds; static BQ25120A _pmic; @@ -123,6 +133,8 @@ class nicla { static bool started; private: + /// Defines if the connected battery has a negative temperature coefficient (NTC) thermistor. + static bool _ntcEnabled; static void pingI2CThd(); static void checkChgReg(); static rtos::Mutex i2c_mutex; From a47ec888fd0c13f608f2d04b624ae5e2df5d3c53 Mon Sep 17 00:00:00 2001 From: Sebastian Romero Date: Fri, 28 Apr 2023 16:28:05 +0200 Subject: [PATCH 06/48] nicla-system: Refactor names. --- .../src/{pmic_driver.cpp => BQ25120A.cpp} | 8 ++-- libraries/Nicla_System/src/Nicla_System.cpp | 44 +++++++++---------- libraries/Nicla_System/src/Nicla_System.h | 36 +++++++++++---- libraries/Nicla_System/src/RGBled.cpp | 8 ++-- 4 files changed, 55 insertions(+), 41 deletions(-) rename libraries/Nicla_System/src/{pmic_driver.cpp => BQ25120A.cpp} (92%) diff --git a/libraries/Nicla_System/src/pmic_driver.cpp b/libraries/Nicla_System/src/BQ25120A.cpp similarity index 92% rename from libraries/Nicla_System/src/pmic_driver.cpp rename to libraries/Nicla_System/src/BQ25120A.cpp index ee9c3f265..50c46c73b 100644 --- a/libraries/Nicla_System/src/pmic_driver.cpp +++ b/libraries/Nicla_System/src/BQ25120A.cpp @@ -19,19 +19,19 @@ uint8_t BQ25120A::getStatus() void BQ25120A::writeByte(uint8_t address, uint8_t subAddress, uint8_t data) { setHighImpedanceModeEnabled(false); - nicla::i2c_mutex.lock(); + nicla::_i2c_mutex.lock(); Wire1.beginTransmission(address); Wire1.write(subAddress); Wire1.write(data); Wire1.endTransmission(); - nicla::i2c_mutex.unlock(); + nicla::_i2c_mutex.unlock(); setHighImpedanceModeEnabled(true); } uint8_t BQ25120A::readByte(uint8_t address, uint8_t subAddress) { setHighImpedanceModeEnabled(false); - nicla::i2c_mutex.lock(); + nicla::_i2c_mutex.lock(); Wire1.beginTransmission(address); Wire1.write(subAddress); Wire1.endTransmission(false); @@ -40,7 +40,7 @@ uint8_t BQ25120A::readByte(uint8_t address, uint8_t subAddress) uint32_t start_time = millis(); while(!Wire1.available() && (millis() - start_time) < timeout) {} uint8_t ret = Wire1.read(); - nicla::i2c_mutex.unlock(); + nicla::_i2c_mutex.unlock(); setHighImpedanceModeEnabled(true); return ret; } diff --git a/libraries/Nicla_System/src/Nicla_System.cpp b/libraries/Nicla_System/src/Nicla_System.cpp index 2858bbe15..cabd2c12c 100644 --- a/libraries/Nicla_System/src/Nicla_System.cpp +++ b/libraries/Nicla_System/src/Nicla_System.cpp @@ -11,32 +11,32 @@ RGBled nicla::leds; BQ25120A nicla::_pmic; -rtos::Mutex nicla::i2c_mutex; +rtos::Mutex nicla::_i2c_mutex; bool nicla::started = false; -uint8_t nicla::_chg_reg = 0; +uint8_t nicla::_fastChargeRegisterData = 0; bool nicla::_ntcEnabled = true; -void nicla::pingI2CThd() { +void nicla::pingI2C() { while(1) { // already protected by a mutex on Wire operations - checkChgReg(); + synchronizeFastChargeSettings(); delay(10000); } } -bool nicla::begin(bool mounted_on_mkr) +bool nicla::begin(bool mountedOnMkr) { - if (mounted_on_mkr) { + if (mountedOnMkr) { // GPIO3 is on MKR RESET pin, so we must configure it HIGH or it will, well, reset the board :) pinMode(p25, OUTPUT); pinMode(P0_10, OUTPUT); digitalWrite(P0_10, HIGH); } Wire1.begin(); - _chg_reg = _pmic.readByte(BQ25120A_ADDRESS, BQ25120A_FAST_CHG); + _fastChargeRegisterData = _pmic.readByte(BQ25120A_ADDRESS, BQ25120A_FAST_CHG); #ifndef NO_NEED_FOR_WATCHDOG_THREAD static rtos::Thread th(osPriorityHigh, 768, nullptr, "ping_thread"); - th.start(&nicla::pingI2CThd); + th.start(&nicla::pingI2C); #endif started = true; @@ -108,22 +108,23 @@ uint8_t nicla::readLDOreg() return _pmic.readByte(BQ25120A_ADDRESS, BQ25120A_LDO_CTRL); } -bool nicla::enableCharge(uint8_t mA, bool disable_ntc) +bool nicla::enableCharge(uint8_t mA, bool disableNtc) { + if (mA < 5) { - _chg_reg = 0x3; + _fastChargeRegisterData = 0x3; } else if (mA < 35) { - _chg_reg = ((mA-5) << 2); + _fastChargeRegisterData = ((mA-5) << 2); } else { - _chg_reg = (((mA-40)/10) << 2) | 0x80; + _fastChargeRegisterData = (((mA-40)/10) << 2) | 0x80; } - _pmic.writeByte(BQ25120A_ADDRESS, BQ25120A_FAST_CHG, _chg_reg); + _pmic.writeByte(BQ25120A_ADDRESS, BQ25120A_FAST_CHG, _fastChargeRegisterData); // For very depleted batteries, set ULVO at the very minimum (2.2V) to re-enable charging _pmic.writeByte(BQ25120A_ADDRESS, BQ25120A_ILIM_UVLO_CTRL, 0x3F); // Disable TS and interrupt on charge - _ntcEnabled = !disable_ntc; + _ntcEnabled = !disableNtc; if (!_ntcEnabled) { _pmic.writeByte(BQ25120A_ADDRESS, BQ25120A_TS_CONTROL, 1 << 3); } @@ -131,15 +132,10 @@ bool nicla::enableCharge(uint8_t mA, bool disable_ntc) // also set max battery voltage to 4.2V (VBREG) // _pmic.writeByte(BQ25120A_ADDRESS, BQ25120A_BATTERY_CTRL, (4.2f - 3.6f)*100); - return _pmic.readByte(BQ25120A_ADDRESS, BQ25120A_FAST_CHG) == _chg_reg; + return _pmic.readByte(BQ25120A_ADDRESS, BQ25120A_FAST_CHG) == _fastChargeRegisterData; } -/** - * @brief Returns potential battery faults. The first 8 bits (bit 0-7) are the fault register, the last 2 bits are the TS_CONTROL register. - * - * @return uint16_t - */ -uint16_t nicla::getFault() { +uint16_t nicla::getBatteryFaults() { uint16_t tmp = _pmic.readByte(BQ25120A_ADDRESS, BQ25120A_FAULTS) << 8; tmp |= (_pmic.readByte(BQ25120A_ADDRESS, BQ25120A_TS_CONTROL) & 0x60); return tmp; @@ -324,10 +320,10 @@ uint8_t nicla::getBatteryTemperature() { return getBatteryStatus() & BATTERY_TEMPERATURE_MASK; } -void nicla::checkChgReg() +void nicla::synchronizeFastChargeSettings() { - if (_chg_reg != _pmic.readByte(BQ25120A_ADDRESS, BQ25120A_FAST_CHG)) { - _pmic.writeByte(BQ25120A_ADDRESS, BQ25120A_FAST_CHG, _chg_reg); + if (_fastChargeRegisterData != _pmic.readByte(BQ25120A_ADDRESS, BQ25120A_FAST_CHG)) { + _pmic.writeByte(BQ25120A_ADDRESS, BQ25120A_FAST_CHG, _fastChargeRegisterData); } } diff --git a/libraries/Nicla_System/src/Nicla_System.h b/libraries/Nicla_System/src/Nicla_System.h index 5eea6f959..1cc7eef00 100644 --- a/libraries/Nicla_System/src/Nicla_System.h +++ b/libraries/Nicla_System/src/Nicla_System.h @@ -31,7 +31,7 @@ class nicla { public: - static bool begin(bool mounted_on_mkr = false); + static bool begin(bool mountedOnMkr = false); static bool enable3V3LDO(); static bool enable1V8LDO(); static bool disableLDO(); @@ -45,7 +45,7 @@ class nicla { */ static bool enterShipMode(); static uint8_t readLDOreg(); - static bool enableCharge(uint8_t mA = 20, bool disable_ntc = true); + static bool enableCharge(uint8_t mA = 20, bool disableNtc = true); /** * @brief Enables or disables the negative temperature coefficient (NTC) thermistor. @@ -119,26 +119,44 @@ class nicla { */ static uint8_t getBatteryStatus(); - - static uint16_t getFault(); + /** + * @brief Returns potential battery faults. The first 8 bits (bit 0-7) are the fault register. + * The last 2 bits are the TS_CONTROL register. They are used to retrieve temperature related faults. + * + * @return uint16_t + */ + static uint16_t getBatteryFaults(); static RGBled leds; static BQ25120A _pmic; + + /// Flag to check if the begin function has been called. This is used to automatically call the begin function if necessary. + static bool started; friend class RGBled; friend class BQ25120A; friend class Arduino_BHY2; - static bool started; private: /// Defines if the connected battery has a negative temperature coefficient (NTC) thermistor. static bool _ntcEnabled; - static void pingI2CThd(); - static void checkChgReg(); - static rtos::Mutex i2c_mutex; - static uint8_t _chg_reg; + + /** + * @brief Pings the I2C interface by querying the PMIC's fast charge register every 10 seconds. + * This is invoked by a thread and is meant to kick the watchdog timer to prevent the PMIC from entering a low power state. + * The I2C interface reset timer for the host is 50 seconds. + */ + static void pingI2C(); + + /** + * @brief Synchronizes the fast charge settings with the PMIC. + * This ensures that the fast charge settings as specified via enableCharge() are applied again the register got wiped. + */ + static void synchronizeFastChargeSettings(); + static uint8_t _fastChargeRegisterData; + static rtos::Mutex _i2c_mutex; }; #endif \ No newline at end of file diff --git a/libraries/Nicla_System/src/RGBled.cpp b/libraries/Nicla_System/src/RGBled.cpp index 11486c7b3..395e1b79f 100644 --- a/libraries/Nicla_System/src/RGBled.cpp +++ b/libraries/Nicla_System/src/RGBled.cpp @@ -150,17 +150,17 @@ void RGBled::ledBlink(RGBColors color, uint32_t duration) void RGBled::writeByte(uint8_t address, uint8_t subAddress, uint8_t data) { - nicla::i2c_mutex.lock(); + nicla::_i2c_mutex.lock(); Wire1.beginTransmission(address); Wire1.write(subAddress); Wire1.write(data); Wire1.endTransmission(); - nicla::i2c_mutex.unlock(); + nicla::_i2c_mutex.unlock(); } uint8_t RGBled::readByte(uint8_t address, uint8_t subAddress) { - nicla::i2c_mutex.lock(); + nicla::_i2c_mutex.lock(); //char response = 0xFF; Wire1.beginTransmission(address); Wire1.write(subAddress); @@ -170,6 +170,6 @@ uint8_t RGBled::readByte(uint8_t address, uint8_t subAddress) uint32_t start_time = millis(); while(!Wire1.available() && (millis() - start_time) < timeout) {} uint8_t ret = Wire1.read(); - nicla::i2c_mutex.unlock(); + nicla::_i2c_mutex.unlock(); return ret; } \ No newline at end of file From 254eecbd13313f7bf6f83e11d4726ecc5045027c Mon Sep 17 00:00:00 2001 From: Sebastian Romero Date: Tue, 2 May 2023 11:37:10 +0200 Subject: [PATCH 07/48] nicla-system: Remove duplicate functionality. --- libraries/Nicla_System/src/Nicla_System.cpp | 7 +++---- libraries/Nicla_System/src/Nicla_System.h | 13 +++++++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/libraries/Nicla_System/src/Nicla_System.cpp b/libraries/Nicla_System/src/Nicla_System.cpp index cabd2c12c..3674a01b7 100644 --- a/libraries/Nicla_System/src/Nicla_System.cpp +++ b/libraries/Nicla_System/src/Nicla_System.cpp @@ -135,10 +135,9 @@ bool nicla::enableCharge(uint8_t mA, bool disableNtc) return _pmic.readByte(BQ25120A_ADDRESS, BQ25120A_FAST_CHG) == _fastChargeRegisterData; } -uint16_t nicla::getBatteryFaults() { - uint16_t tmp = _pmic.readByte(BQ25120A_ADDRESS, BQ25120A_FAULTS) << 8; - tmp |= (_pmic.readByte(BQ25120A_ADDRESS, BQ25120A_TS_CONTROL) & 0x60); - return tmp; +uint8_t nicla::getBatteryFaults() { + // Skips the mask bits (4 LSBs) + return (_pmic.readByte(BQ25120A_ADDRESS, BQ25120A_FAULTS) >> 4) & 0b1111; } void nicla::setBatteryNTCEnabled(bool enabled){ diff --git a/libraries/Nicla_System/src/Nicla_System.h b/libraries/Nicla_System/src/Nicla_System.h index 1cc7eef00..a42b747e3 100644 --- a/libraries/Nicla_System/src/Nicla_System.h +++ b/libraries/Nicla_System/src/Nicla_System.h @@ -120,12 +120,17 @@ class nicla { static uint8_t getBatteryStatus(); /** - * @brief Returns potential battery faults. The first 8 bits (bit 0-7) are the fault register. - * The last 2 bits are the TS_CONTROL register. They are used to retrieve temperature related faults. + * @brief Returns potential battery faults retrieved from the fault register. * - * @return uint16_t + * - Bit 3: 1 - VIN overvoltage fault. VIN_OV continues to show fault after an I2C read as long as OV exists + * - Bit 2: 1 - VIN undervoltage fault. VIN_UV is set when the input falls below VSLP. VIN_UV fault shows only one time. Once read, VIN_UV clears until the the UVLO event occurs. + * - Bit 1: 1 – BAT_UVLO fault. BAT_UVLO continues to show fault after an I2C read as long as BAT_UVLO conditions exist. + * - Bit 0: 1 – BAT_OCP fault. BAT_OCP is cleared after I2C read. + * + * @note Some of the registers are not persistent. See chapter 9.6.2 and 9.6.3 of the datasheet. + * @return uint8_t The battery faults encoded in a 16bit integer. */ - static uint16_t getBatteryFaults(); + static uint8_t getBatteryFaults(); static RGBled leds; From 66bc14135d8d152ac87ec42504503429e8ae0547 Mon Sep 17 00:00:00 2001 From: Sebastian Romero Date: Tue, 2 May 2023 15:46:09 +0200 Subject: [PATCH 08/48] nicla-system: Add API for operating system. --- .../NiclaSenseME_BatteryStatus.ino | 7 +++++++ libraries/Nicla_System/src/Nicla_System.cpp | 8 ++++++++ libraries/Nicla_System/src/Nicla_System.h | 16 ++++++++++++++-- 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/libraries/Nicla_System/examples/NiclaSenseME_BatteryStatus/NiclaSenseME_BatteryStatus.ino b/libraries/Nicla_System/examples/NiclaSenseME_BatteryStatus/NiclaSenseME_BatteryStatus.ino index 8de238873..3bcf73484 100644 --- a/libraries/Nicla_System/examples/NiclaSenseME_BatteryStatus/NiclaSenseME_BatteryStatus.ino +++ b/libraries/Nicla_System/examples/NiclaSenseME_BatteryStatus/NiclaSenseME_BatteryStatus.ino @@ -167,6 +167,8 @@ void setup() setupBLE(); nicla::leds.setColor(green); + + nicla::enableCharge(); } void loop() @@ -216,6 +218,11 @@ void loop() auto chargeLevel = nicla::getBatteryChargeLevel(); Serial.println("Battery is " + getBatteryChargeLevelDescription(chargeLevel)); + + bool isCharging = nicla::getOperatingStatus() == OperatingStatus::Charging; + Serial.print("Battery is charging: "); + Serial.println(isCharging ? "Yes" : "No"); + Serial.println("----------------------"); } } diff --git a/libraries/Nicla_System/src/Nicla_System.cpp b/libraries/Nicla_System/src/Nicla_System.cpp index 3674a01b7..9302b110f 100644 --- a/libraries/Nicla_System/src/Nicla_System.cpp +++ b/libraries/Nicla_System/src/Nicla_System.cpp @@ -319,6 +319,14 @@ uint8_t nicla::getBatteryTemperature() { return getBatteryStatus() & BATTERY_TEMPERATURE_MASK; } + +OperatingStatus nicla::getOperatingStatus() { + // Extract bits 6 and 7 + uint8_t status = _pmic.getStatus() >> 6 & 0b11; + return static_cast(status); +} + + void nicla::synchronizeFastChargeSettings() { if (_fastChargeRegisterData != _pmic.readByte(BQ25120A_ADDRESS, BQ25120A_FAST_CHG)) { diff --git a/libraries/Nicla_System/src/Nicla_System.h b/libraries/Nicla_System/src/Nicla_System.h index a42b747e3..9d425ea83 100644 --- a/libraries/Nicla_System/src/Nicla_System.h +++ b/libraries/Nicla_System/src/Nicla_System.h @@ -10,6 +10,13 @@ #define USE_FASTCHG_TO_KICK_WATCHDOG 1 +enum class OperatingStatus { + Ready = 0b00, + Charging = 0b01, + ChargingComplete = 0b10, + Error = 0b11 +}; + // 3 bits are used to indicate the battery charge level #define BATTERY_CHARGE_MASK 0b00000111 #define BATTERY_FULL 5 // Bit pattern: 101 @@ -26,8 +33,6 @@ #define BATTERY_TEMPERTURE_COOL (2 << 4) #define BATTERY_TEMPERTURE_WARM (3 << 4) -#define BATTERY_CHARGING (1 << 7) - class nicla { public: @@ -133,6 +138,13 @@ class nicla { static uint8_t getBatteryFaults(); + /** + * @brief Get the current operating status of the PMIC. + * + * @return OperatingStatus One of the following: Ready, Charging, ChargingComplete, Error. + */ + static OperatingStatus getOperatingStatus(); + static RGBled leds; static BQ25120A _pmic; From 3b0af63caa99c79eda29076c76cca87ad232a7ba Mon Sep 17 00:00:00 2001 From: Sebastian Romero Date: Tue, 2 May 2023 15:56:00 +0200 Subject: [PATCH 09/48] nicla-system: Rename function. --- libraries/Nicla_System/src/BQ25120A.cpp | 5 ++--- libraries/Nicla_System/src/BQ25120A.h | 2 +- libraries/Nicla_System/src/Nicla_System.cpp | 4 ++-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/libraries/Nicla_System/src/BQ25120A.cpp b/libraries/Nicla_System/src/BQ25120A.cpp index 50c46c73b..61a22eee6 100644 --- a/libraries/Nicla_System/src/BQ25120A.cpp +++ b/libraries/Nicla_System/src/BQ25120A.cpp @@ -10,10 +10,9 @@ // When VIN > VUVLO this enables charging. static mbed::DigitalOut cd(p25, 0); -uint8_t BQ25120A::getStatus() +uint8_t BQ25120A::getStatusRegister() { - uint8_t c = readByte(BQ25120A_ADDRESS, BQ25120A_STATUS); // Read PRODUCT_ID register for BQ25120A - return c; + return readByte(BQ25120A_ADDRESS, BQ25120A_STATUS); } void BQ25120A::writeByte(uint8_t address, uint8_t subAddress, uint8_t data) diff --git a/libraries/Nicla_System/src/BQ25120A.h b/libraries/Nicla_System/src/BQ25120A.h index 77b05e5f7..1aeeac447 100644 --- a/libraries/Nicla_System/src/BQ25120A.h +++ b/libraries/Nicla_System/src/BQ25120A.h @@ -25,7 +25,7 @@ class BQ25120A public: BQ25120A() {}; - uint8_t getStatus(); + uint8_t getStatusRegister(); void writeByte(uint8_t address, uint8_t subAddress, uint8_t data); uint8_t readByte(uint8_t address, uint8_t subAddress); diff --git a/libraries/Nicla_System/src/Nicla_System.cpp b/libraries/Nicla_System/src/Nicla_System.cpp index 9302b110f..db8478a85 100644 --- a/libraries/Nicla_System/src/Nicla_System.cpp +++ b/libraries/Nicla_System/src/Nicla_System.cpp @@ -98,7 +98,7 @@ bool nicla::enterShipMode() // | B7 | B6 | B5 | B4 | B3 | B2 | B1 | B0 | // | RO | RO | EN_SHIPMODE | RO | RO | RO | RO | RO | - uint8_t status_reg = _pmic.getStatus(); + uint8_t status_reg = _pmic.getStatusRegister(); status_reg |= 0x20; _pmic.writeByte(BQ25120A_ADDRESS, BQ25120A_STATUS, status_reg); } @@ -322,7 +322,7 @@ uint8_t nicla::getBatteryTemperature() { OperatingStatus nicla::getOperatingStatus() { // Extract bits 6 and 7 - uint8_t status = _pmic.getStatus() >> 6 & 0b11; + uint8_t status = _pmic.getStatusRegister() >> 6 & 0b11; return static_cast(status); } From edaf10954e744bdd88781f62df74717f2e8edbfc Mon Sep 17 00:00:00 2001 From: Sebastian Romero Date: Tue, 2 May 2023 16:06:04 +0200 Subject: [PATCH 10/48] nicla-system: Use getFaultsRegister to query faults register. --- libraries/Nicla_System/src/BQ25120A.cpp | 5 +++++ libraries/Nicla_System/src/BQ25120A.h | 2 ++ libraries/Nicla_System/src/Nicla_System.cpp | 4 ++-- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/libraries/Nicla_System/src/BQ25120A.cpp b/libraries/Nicla_System/src/BQ25120A.cpp index 61a22eee6..f25631387 100644 --- a/libraries/Nicla_System/src/BQ25120A.cpp +++ b/libraries/Nicla_System/src/BQ25120A.cpp @@ -15,6 +15,11 @@ uint8_t BQ25120A::getStatusRegister() return readByte(BQ25120A_ADDRESS, BQ25120A_STATUS); } +uint8_t BQ25120A::getFaultsRegister() +{ + return readByte(BQ25120A_ADDRESS, BQ25120A_FAULTS); +} + void BQ25120A::writeByte(uint8_t address, uint8_t subAddress, uint8_t data) { setHighImpedanceModeEnabled(false); diff --git a/libraries/Nicla_System/src/BQ25120A.h b/libraries/Nicla_System/src/BQ25120A.h index 1aeeac447..8bd28d1e6 100644 --- a/libraries/Nicla_System/src/BQ25120A.h +++ b/libraries/Nicla_System/src/BQ25120A.h @@ -26,6 +26,8 @@ class BQ25120A BQ25120A() {}; uint8_t getStatusRegister(); + uint8_t getFaultsRegister(); + void writeByte(uint8_t address, uint8_t subAddress, uint8_t data); uint8_t readByte(uint8_t address, uint8_t subAddress); diff --git a/libraries/Nicla_System/src/Nicla_System.cpp b/libraries/Nicla_System/src/Nicla_System.cpp index db8478a85..5c7eb5472 100644 --- a/libraries/Nicla_System/src/Nicla_System.cpp +++ b/libraries/Nicla_System/src/Nicla_System.cpp @@ -137,7 +137,7 @@ bool nicla::enableCharge(uint8_t mA, bool disableNtc) uint8_t nicla::getBatteryFaults() { // Skips the mask bits (4 LSBs) - return (_pmic.readByte(BQ25120A_ADDRESS, BQ25120A_FAULTS) >> 4) & 0b1111; + return (_pmic.getFaultsRegister() >> 4) & 0b1111; } void nicla::setBatteryNTCEnabled(bool enabled){ @@ -222,7 +222,7 @@ int8_t nicla::getBatteryPercentage(bool useLatchedValue) { Example: 0 11 111 00 -> 90% + 8% = 98 - 100% of VBATREG */ constexpr uint8_t BAT_UVLO_FAULT = 0b00100000; // Battery Under-Voltage Lock-Out fault - uint8_t faults = _pmic.readByte(BQ25120A_ADDRESS, BQ25120A_FAULTS); + uint8_t faults = _pmic.getFaultsRegister(); if(faults & BAT_UVLO_FAULT) return -1; // Battery is not connected or voltage is too low // Write 1 to VBMON_READ to trigger a new reading From 03cbd92a654f9fbca5cd3140b3aab7b0370b2c9d Mon Sep 17 00:00:00 2001 From: Sebastian Romero Date: Tue, 2 May 2023 16:13:35 +0200 Subject: [PATCH 11/48] nicla-system: Add documentation. --- libraries/Nicla_System/src/BQ25120A.h | 27 +++++++++++++++++++++++ libraries/Nicla_System/src/Nicla_System.h | 7 ++++++ 2 files changed, 34 insertions(+) diff --git a/libraries/Nicla_System/src/BQ25120A.h b/libraries/Nicla_System/src/BQ25120A.h index 8bd28d1e6..40b5bb99c 100644 --- a/libraries/Nicla_System/src/BQ25120A.h +++ b/libraries/Nicla_System/src/BQ25120A.h @@ -25,10 +25,37 @@ class BQ25120A public: BQ25120A() {}; + /** + * @brief Gets the data from the status register. + * @see Section 9.6.1 of the datasheet. + * + * @return uint8_t The data from the status register. + */ uint8_t getStatusRegister(); + + /** + * @brief Gets the data from the faults register. + * @see Section 9.6.2 of the datasheet. + * + * @return uint8_t The data from the faults register. + */ uint8_t getFaultsRegister(); + /** + * @brief Writes a byte to the BQ25120A over I2C. + * @param address The I2C address of the BQ25120A. + * @param subAddress The memory location of the register to write to. + * @param data The data to write to the register. + */ void writeByte(uint8_t address, uint8_t subAddress, uint8_t data); + + /** + * @brief Reads a byte from the BQ25120A over I2C. + * + * @param address The I2C address of the BQ25120A. + * @param subAddress The memory location of the register to read from. + * @return uint8_t The data read from the register. + */ uint8_t readByte(uint8_t address, uint8_t subAddress); private: diff --git a/libraries/Nicla_System/src/Nicla_System.h b/libraries/Nicla_System/src/Nicla_System.h index 9d425ea83..947425a54 100644 --- a/libraries/Nicla_System/src/Nicla_System.h +++ b/libraries/Nicla_System/src/Nicla_System.h @@ -172,7 +172,14 @@ class nicla { * This ensures that the fast charge settings as specified via enableCharge() are applied again the register got wiped. */ static void synchronizeFastChargeSettings(); + + /** + * A cached version of the fast charge settings for the PMIC. + * This is used to reapply the settings if the register got wiped. + **/ static uint8_t _fastChargeRegisterData; + + /// Mutex to prevent concurrent access to the I2C interface. static rtos::Mutex _i2c_mutex; }; From 1c55bb57948ce6cb64b58ac55b954c9d2ace84f6 Mon Sep 17 00:00:00 2001 From: Sebastian Romero Date: Tue, 2 May 2023 16:21:52 +0200 Subject: [PATCH 12/48] nicla-system: Remove battery status function. --- libraries/Nicla_System/src/Nicla_System.cpp | 73 ++++++++++----------- libraries/Nicla_System/src/Nicla_System.h | 19 +++--- 2 files changed, 43 insertions(+), 49 deletions(-) diff --git a/libraries/Nicla_System/src/Nicla_System.cpp b/libraries/Nicla_System/src/Nicla_System.cpp index 5c7eb5472..0387f661e 100644 --- a/libraries/Nicla_System/src/Nicla_System.cpp +++ b/libraries/Nicla_System/src/Nicla_System.cpp @@ -265,58 +265,53 @@ int8_t nicla::getBatteryPercentage(bool useLatchedValue) { } uint8_t nicla::getBatteryChargeLevel() { - return getBatteryStatus() & BATTERY_CHARGE_MASK; -} - -uint8_t nicla::getBatteryStatus() { auto percent = getBatteryPercentage(); - int res = BATTERY_UNKNOWN; if (percent >= 98) { - res = BATTERY_FULL; + return BATTERY_FULL; } else if (percent >= 94){ - res = BATTERY_ALMOST_FULL; + return BATTERY_ALMOST_FULL; } else if (percent >= 90){ - res = BATTERY_HALF; + return BATTERY_HALF; } else if (percent >= 86){ - res = BATTERY_ALMOST_EMPTY; + return BATTERY_ALMOST_EMPTY; } else if(percent < 86 && percent > 0) { // < 84% is considered empty - res = BATTERY_EMPTY; - } - - if (_ntcEnabled) { - // Extract bits 5 and 6 (TS_FAULT0 and TS_FAULT1) - uint8_t temperatureSenseFault = _pmic.readByte(BQ25120A_ADDRESS, BQ25120A_TS_CONTROL) >> 5 & 0b11; - - /* - +------+-------------------------------------------------------------+ - | Bits | Description | - +------+-------------------------------------------------------------+ - | 00 | Normal, No TS fault | - | 01 | TS temp < TCOLD or TS temp > THOT (Charging suspended) | - | 10 | TCOOL > TS temp > TCOLD (Charging current reduced by half) | - | 11 | TWARM < TS temp < THOT (Charging voltage reduced by 140 mV) | - +------+-------------------------------------------------------------+ - */ - - if(temperatureSenseFault == 0){ - res |= BATTERY_TEMPERATURE_NORMAL; - } else if (temperatureSenseFault == 1) { - res |= BATTERY_TEMPERATURE_EXTREME; - } else if (temperatureSenseFault == 2) { - res |= BATTERY_TEMPERTURE_COOL; - } else if (temperatureSenseFault == 3) { - res |= BATTERY_TEMPERTURE_WARM; - } + return BATTERY_EMPTY; + } else { + // Battery status could not be read + return BATTERY_UNKNOWN; } - - return res; } uint8_t nicla::getBatteryTemperature() { if(!_ntcEnabled) return BATTERY_TEMPERATURE_NORMAL; - return getBatteryStatus() & BATTERY_TEMPERATURE_MASK; + + // Extract bits 5 and 6 (TS_FAULT0 and TS_FAULT1) + uint8_t temperatureSenseFault = _pmic.readByte(BQ25120A_ADDRESS, BQ25120A_TS_CONTROL) >> 5 & 0b11; + + /* + +------+-------------------------------------------------------------+ + | Bits | Description | + +------+-------------------------------------------------------------+ + | 00 | Normal, No TS fault | + | 01 | TS temp < TCOLD or TS temp > THOT (Charging suspended) | + | 10 | TCOOL > TS temp > TCOLD (Charging current reduced by half) | + | 11 | TWARM < TS temp < THOT (Charging voltage reduced by 140 mV) | + +------+-------------------------------------------------------------+ + */ + + if(temperatureSenseFault == 0){ + return BATTERY_TEMPERATURE_NORMAL; + } else if (temperatureSenseFault == 1) { + return BATTERY_TEMPERATURE_EXTREME; + } else if (temperatureSenseFault == 2) { + return BATTERY_TEMPERTURE_COOL; + } else if (temperatureSenseFault == 3) { + return BATTERY_TEMPERTURE_WARM; + } + + return BATTERY_TEMPERATURE_NORMAL; } diff --git a/libraries/Nicla_System/src/Nicla_System.h b/libraries/Nicla_System/src/Nicla_System.h index 947425a54..78d633c25 100644 --- a/libraries/Nicla_System/src/Nicla_System.h +++ b/libraries/Nicla_System/src/Nicla_System.h @@ -26,6 +26,15 @@ enum class OperatingStatus { #define BATTERY_EMPTY 1 // Bit pattern: 001 #define BATTERY_UNKNOWN 0 // Bit pattern: 000 +enum class BatteryChargeLevel { + Unknown = 0b000, + Empty = 0b001, + AlmostEmpty = 0b010, + HalfFull = 0b011, + AlmostFull = 0b100, + Full = 0b101 +}; + // 2 bits are used to indicate the battery temperature #define BATTERY_TEMPERATURE_MASK 0b00011000 #define BATTERY_TEMPERATURE_NORMAL (0 << 4) @@ -114,16 +123,6 @@ class nicla { */ static uint8_t getBatteryTemperature(); - /** - * @brief Get the Battery Status (charge level and temperature). - * The first 3 bits indicate the battery charge level. They can be retrieved using the BATTERY_CHARGE_MASK. - * The 4th and 5th bit indicate the battery temperature. They can be retrieved using the BATTERY_TEMPERATURE_MASK. - * @see getBatteryChargeLevel() - * @see getBatteryTemperature() - * @return uint8_t The battery status containing the charge level and temperature. - */ - static uint8_t getBatteryStatus(); - /** * @brief Returns potential battery faults retrieved from the fault register. * From c29e36397e7ffa01766f1c862188cea913e569d4 Mon Sep 17 00:00:00 2001 From: Sebastian Romero Date: Tue, 2 May 2023 16:30:15 +0200 Subject: [PATCH 13/48] nicla-system: Replace defines with enum for battery level. --- .../NiclaSenseME_BatteryStatus.ino | 14 +++++++------- libraries/Nicla_System/src/Nicla_System.cpp | 14 +++++++------- libraries/Nicla_System/src/Nicla_System.h | 15 ++++----------- 3 files changed, 18 insertions(+), 25 deletions(-) diff --git a/libraries/Nicla_System/examples/NiclaSenseME_BatteryStatus/NiclaSenseME_BatteryStatus.ino b/libraries/Nicla_System/examples/NiclaSenseME_BatteryStatus/NiclaSenseME_BatteryStatus.ino index 3bcf73484..5dde1c0a5 100644 --- a/libraries/Nicla_System/examples/NiclaSenseME_BatteryStatus/NiclaSenseME_BatteryStatus.ino +++ b/libraries/Nicla_System/examples/NiclaSenseME_BatteryStatus/NiclaSenseME_BatteryStatus.ino @@ -33,7 +33,7 @@ bool updateBatteryLevel(bool enforceNewReading = false) { if (batteryPercentage != percentage) { batteryPercentage = percentage; batteryVoltage = nicla::getCurrentBatteryVoltage(); - batteryChargeLevel = nicla::getBatteryChargeLevel(); + batteryChargeLevel = static_cast(nicla::getBatteryChargeLevel()); Serial.print("New battery level: "); Serial.println(batteryPercentage); @@ -60,17 +60,17 @@ String getBatteryTemperatureDescription(int status) { } } -String getBatteryChargeLevelDescription(int status) { +String getBatteryChargeLevelDescription(BatteryChargeLevel status) { switch (status) { - case BATTERY_EMPTY: + case BatteryChargeLevel::Empty: return "Empty"; - case BATTERY_ALMOST_EMPTY: + case BatteryChargeLevel::AlmostEmpty: return "Almost Empty"; - case BATTERY_HALF: + case BatteryChargeLevel::HalfFull: return "Half Full"; - case BATTERY_ALMOST_FULL: + case BatteryChargeLevel::AlmostFull: return "Almost Full"; - case BATTERY_FULL: + case BatteryChargeLevel::Full: return "Full"; default: return "Unknown"; diff --git a/libraries/Nicla_System/src/Nicla_System.cpp b/libraries/Nicla_System/src/Nicla_System.cpp index 0387f661e..3271f6e7d 100644 --- a/libraries/Nicla_System/src/Nicla_System.cpp +++ b/libraries/Nicla_System/src/Nicla_System.cpp @@ -264,23 +264,23 @@ int8_t nicla::getBatteryPercentage(bool useLatchedValue) { return percentageTens + percentageOnes; } -uint8_t nicla::getBatteryChargeLevel() { +BatteryChargeLevel nicla::getBatteryChargeLevel() { auto percent = getBatteryPercentage(); if (percent >= 98) { - return BATTERY_FULL; + return BatteryChargeLevel::Full; } else if (percent >= 94){ - return BATTERY_ALMOST_FULL; + return BatteryChargeLevel::AlmostFull; } else if (percent >= 90){ - return BATTERY_HALF; + return BatteryChargeLevel::HalfFull; } else if (percent >= 86){ - return BATTERY_ALMOST_EMPTY; + return BatteryChargeLevel::AlmostEmpty; } else if(percent < 86 && percent > 0) { // < 84% is considered empty - return BATTERY_EMPTY; + return BatteryChargeLevel::Empty; } else { // Battery status could not be read - return BATTERY_UNKNOWN; + return BatteryChargeLevel::Unknown; } } diff --git a/libraries/Nicla_System/src/Nicla_System.h b/libraries/Nicla_System/src/Nicla_System.h index 78d633c25..67e56881d 100644 --- a/libraries/Nicla_System/src/Nicla_System.h +++ b/libraries/Nicla_System/src/Nicla_System.h @@ -18,14 +18,6 @@ enum class OperatingStatus { }; // 3 bits are used to indicate the battery charge level -#define BATTERY_CHARGE_MASK 0b00000111 -#define BATTERY_FULL 5 // Bit pattern: 101 -#define BATTERY_ALMOST_FULL 4 // Bit pattern: 100 -#define BATTERY_HALF 3 // Bit pattern: 011 -#define BATTERY_ALMOST_EMPTY 2 // Bit pattern: 010 -#define BATTERY_EMPTY 1 // Bit pattern: 001 -#define BATTERY_UNKNOWN 0 // Bit pattern: 000 - enum class BatteryChargeLevel { Unknown = 0b000, Empty = 0b001, @@ -104,10 +96,11 @@ class nicla { * @brief Get the Battery Charge level encoded as a number (0-5). The following values are possible: * "Unknown", "Empty", "Almost Empty", "Half Full", "Almost Full", "Full" * - * @return uint8_t The battery charge level represented by one of the following constants: - * BATTERY_UNKNOWN, BATTERY_FULL, BATTERY_ALMOST_FULL, BATTERY_HALF, BATTERY_ALMOST_EMPTY, BATTERY_EMPTY + * @return BatteryChargeLevel The battery charge level represented by one of the following values: + * BatteryChargeLevel::Unknown, BatteryChargeLevel::Empty, BatteryChargeLevel::AlmostEmpty, + * BatteryChargeLevel::HalfFull, BatteryChargeLevel::AlmostFull, BatteryChargeLevel::Full */ - static uint8_t getBatteryChargeLevel(); + static BatteryChargeLevel getBatteryChargeLevel(); /** * @brief Get the Battery Temperature. The following values are possible: From 9f7b9a97147204c86c2b9cc45314834661103531 Mon Sep 17 00:00:00 2001 From: Sebastian Romero Date: Tue, 2 May 2023 16:42:23 +0200 Subject: [PATCH 14/48] nicla-system: Add getter for fast charge register. --- libraries/Nicla_System/src/BQ25120A.cpp | 5 +++++ libraries/Nicla_System/src/BQ25120A.h | 8 ++++++++ libraries/Nicla_System/src/Nicla_System.cpp | 6 +++--- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/libraries/Nicla_System/src/BQ25120A.cpp b/libraries/Nicla_System/src/BQ25120A.cpp index f25631387..3c43ab0f5 100644 --- a/libraries/Nicla_System/src/BQ25120A.cpp +++ b/libraries/Nicla_System/src/BQ25120A.cpp @@ -20,6 +20,11 @@ uint8_t BQ25120A::getFaultsRegister() return readByte(BQ25120A_ADDRESS, BQ25120A_FAULTS); } +uint8_t BQ25120A::getFastChargeControlRegister() +{ + return readByte(BQ25120A_ADDRESS, BQ25120A_FAST_CHG); +} + void BQ25120A::writeByte(uint8_t address, uint8_t subAddress, uint8_t data) { setHighImpedanceModeEnabled(false); diff --git a/libraries/Nicla_System/src/BQ25120A.h b/libraries/Nicla_System/src/BQ25120A.h index 40b5bb99c..874390040 100644 --- a/libraries/Nicla_System/src/BQ25120A.h +++ b/libraries/Nicla_System/src/BQ25120A.h @@ -41,6 +41,14 @@ class BQ25120A */ uint8_t getFaultsRegister(); + /** + * @brief Gets the data from the fast charge control register. + * @see Section 9.6.4 of the datasheet. + * + * @return uint8_t The data from the fast charge control register. + */ + uint8_t getFastChargeControlRegister(); + /** * @brief Writes a byte to the BQ25120A over I2C. * @param address The I2C address of the BQ25120A. diff --git a/libraries/Nicla_System/src/Nicla_System.cpp b/libraries/Nicla_System/src/Nicla_System.cpp index 3271f6e7d..9d8708673 100644 --- a/libraries/Nicla_System/src/Nicla_System.cpp +++ b/libraries/Nicla_System/src/Nicla_System.cpp @@ -33,7 +33,7 @@ bool nicla::begin(bool mountedOnMkr) digitalWrite(P0_10, HIGH); } Wire1.begin(); - _fastChargeRegisterData = _pmic.readByte(BQ25120A_ADDRESS, BQ25120A_FAST_CHG); + _fastChargeRegisterData = _pmic.getFastChargeControlRegister(); #ifndef NO_NEED_FOR_WATCHDOG_THREAD static rtos::Thread th(osPriorityHigh, 768, nullptr, "ping_thread"); th.start(&nicla::pingI2C); @@ -132,7 +132,7 @@ bool nicla::enableCharge(uint8_t mA, bool disableNtc) // also set max battery voltage to 4.2V (VBREG) // _pmic.writeByte(BQ25120A_ADDRESS, BQ25120A_BATTERY_CTRL, (4.2f - 3.6f)*100); - return _pmic.readByte(BQ25120A_ADDRESS, BQ25120A_FAST_CHG) == _fastChargeRegisterData; + return _pmic.getFastChargeControlRegister() == _fastChargeRegisterData; } uint8_t nicla::getBatteryFaults() { @@ -324,7 +324,7 @@ OperatingStatus nicla::getOperatingStatus() { void nicla::synchronizeFastChargeSettings() { - if (_fastChargeRegisterData != _pmic.readByte(BQ25120A_ADDRESS, BQ25120A_FAST_CHG)) { + if (_fastChargeRegisterData != _pmic.getFastChargeControlRegister()) { _pmic.writeByte(BQ25120A_ADDRESS, BQ25120A_FAST_CHG, _fastChargeRegisterData); } } From f14d519e193292231d6ed94ebb7163addc40b7f1 Mon Sep 17 00:00:00 2001 From: Sebastian Romero Date: Tue, 2 May 2023 17:12:30 +0200 Subject: [PATCH 15/48] nicla-system: Replace battery temperature with enum. --- .../NiclaSenseME_BatteryStatus.ino | 10 +++++----- libraries/Nicla_System/src/Nicla_System.h | 17 +++++++++-------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/libraries/Nicla_System/examples/NiclaSenseME_BatteryStatus/NiclaSenseME_BatteryStatus.ino b/libraries/Nicla_System/examples/NiclaSenseME_BatteryStatus/NiclaSenseME_BatteryStatus.ino index 5dde1c0a5..fccf698b2 100644 --- a/libraries/Nicla_System/examples/NiclaSenseME_BatteryStatus/NiclaSenseME_BatteryStatus.ino +++ b/libraries/Nicla_System/examples/NiclaSenseME_BatteryStatus/NiclaSenseME_BatteryStatus.ino @@ -45,15 +45,15 @@ bool updateBatteryLevel(bool enforceNewReading = false) { return false; } -String getBatteryTemperatureDescription(int status) { +String getBatteryTemperatureDescription(BatteryTemperature status) { switch (status) { - case BATTERY_TEMPERATURE_NORMAL: + case BatteryTemperature::Normal: return "Normal"; - case BATTERY_TEMPERATURE_EXTREME: + case BatteryTemperature::Extreme: return "Extreme"; - case BATTERY_TEMPERTURE_COOL: + case BatteryTemperature::Cool: return "Cool"; - case BATTERY_TEMPERTURE_WARM: + case BatteryTemperature::Warm: return "Warm"; default: return "Unknown"; diff --git a/libraries/Nicla_System/src/Nicla_System.h b/libraries/Nicla_System/src/Nicla_System.h index 67e56881d..989360f58 100644 --- a/libraries/Nicla_System/src/Nicla_System.h +++ b/libraries/Nicla_System/src/Nicla_System.h @@ -28,11 +28,12 @@ enum class BatteryChargeLevel { }; // 2 bits are used to indicate the battery temperature -#define BATTERY_TEMPERATURE_MASK 0b00011000 -#define BATTERY_TEMPERATURE_NORMAL (0 << 4) -#define BATTERY_TEMPERATURE_EXTREME (1 << 4) -#define BATTERY_TEMPERTURE_COOL (2 << 4) -#define BATTERY_TEMPERTURE_WARM (3 << 4) +enum class BatteryTemperature { + Normal = 0b00, + Extreme = 0b01, + Cool = 0b10, + Warm = 0b11 +}; class nicla { @@ -111,10 +112,10 @@ class nicla { * @note If the battery doesn't have a negative temperature coefficient (NTC) thermistor, the temperature is always "Normal". * This is not determined automatically and needs to be set using the setBatteryNTCEnabled() function. * @see setBatteryNTCEnabled() - * @return uint8_t The battery temperature represented by one of the following constants: - * BATTERY_TEMPERATURE_NORMAL, BATTERY_TEMPERATURE_EXTREME, BATTERY_TEMPERTURE_COOL, BATTERY_TEMPERTURE_WARM + * @return BatteryTemperature The battery temperature represented by one of the following constants: + * BatteryTemperature::Normal, BatteryTemperature::Extreme, BatteryTemperature::Cool, BatteryTemperature::Warm */ - static uint8_t getBatteryTemperature(); + static BatteryTemperature getBatteryTemperature(); /** * @brief Returns potential battery faults retrieved from the fault register. From 6e8f4541039770bfc336e83f3f798a8988d234a8 Mon Sep 17 00:00:00 2001 From: Sebastian Romero Date: Tue, 2 May 2023 17:13:28 +0200 Subject: [PATCH 16/48] nicla-system: Add API to set regulated battery voltage. --- libraries/Nicla_System/src/Nicla_System.cpp | 54 ++++++++++++++------- libraries/Nicla_System/src/Nicla_System.h | 7 +++ 2 files changed, 44 insertions(+), 17 deletions(-) diff --git a/libraries/Nicla_System/src/Nicla_System.cpp b/libraries/Nicla_System/src/Nicla_System.cpp index 9d8708673..50692e4b4 100644 --- a/libraries/Nicla_System/src/Nicla_System.cpp +++ b/libraries/Nicla_System/src/Nicla_System.cpp @@ -181,6 +181,38 @@ float nicla::getRegulatedBatteryVoltage(){ } +void nicla::setRegulatedBatteryVoltage(float voltage){ + if (voltage < 3.6f){ + voltage = 3.6f; + } else if (voltage > 4.2f) { + voltage = 4.2f; + } + + // The formula is: VBATREG = 3.6 V + Sum(VBREG[Bit 7:1]) + + /* + +---------+--------------------+ + | Bit | Regulation Voltage | + +---------+--------------------+ + | 7 (MSB) | 640 mV | + | 6 | 320 mV | + | 5 | 160 mV | + | 4 | 80 mV | + | 3 | 40 mV | + | 2 | 20 mV | + | 1 | 10 mV | + | 0 (LSB) | – | + +---------+--------------------+ + */ + + // Shift one bit to the left because the LSB is not used. + uint16_t additionalMilliVolts = (voltage - 3.6f) * 100; + uint8_t value = additionalMilliVolts << 1; + // e.g. 4.2V - 3.6V = 0.6V * 100 = 60. 60 << 1 = 120 = 01111000 + + _pmic.writeByte(BQ25120A_ADDRESS, BQ25120A_BATTERY_CTRL, value); +} + float nicla::getCurrentBatteryVoltage(){ return getRegulatedBatteryVoltage() / 100 * getBatteryPercentage(); } @@ -284,12 +316,9 @@ BatteryChargeLevel nicla::getBatteryChargeLevel() { } } -uint8_t nicla::getBatteryTemperature() { - if(!_ntcEnabled) return BATTERY_TEMPERATURE_NORMAL; +BatteryTemperature nicla::getBatteryTemperature() { + if(!_ntcEnabled) return BatteryTemperature::Normal; - // Extract bits 5 and 6 (TS_FAULT0 and TS_FAULT1) - uint8_t temperatureSenseFault = _pmic.readByte(BQ25120A_ADDRESS, BQ25120A_TS_CONTROL) >> 5 & 0b11; - /* +------+-------------------------------------------------------------+ | Bits | Description | @@ -300,18 +329,9 @@ uint8_t nicla::getBatteryTemperature() { | 11 | TWARM < TS temp < THOT (Charging voltage reduced by 140 mV) | +------+-------------------------------------------------------------+ */ - - if(temperatureSenseFault == 0){ - return BATTERY_TEMPERATURE_NORMAL; - } else if (temperatureSenseFault == 1) { - return BATTERY_TEMPERATURE_EXTREME; - } else if (temperatureSenseFault == 2) { - return BATTERY_TEMPERTURE_COOL; - } else if (temperatureSenseFault == 3) { - return BATTERY_TEMPERTURE_WARM; - } - - return BATTERY_TEMPERATURE_NORMAL; + // Extract bits 5 and 6 (TS_FAULT0 and TS_FAULT1) + uint8_t temperatureSenseFault = _pmic.readByte(BQ25120A_ADDRESS, BQ25120A_TS_CONTROL) >> 5 & 0b11; + return static_cast(temperatureSenseFault); } diff --git a/libraries/Nicla_System/src/Nicla_System.h b/libraries/Nicla_System/src/Nicla_System.h index 989360f58..064131dde 100644 --- a/libraries/Nicla_System/src/Nicla_System.h +++ b/libraries/Nicla_System/src/Nicla_System.h @@ -70,6 +70,13 @@ class nicla { */ static float getRegulatedBatteryVoltage(); + /** + * @brief Set the Regulated Battery Voltage. + * + * @param voltage The voltage in the range of 3.6V to 4.65V. + */ + static void setRegulatedBatteryVoltage(float voltage); + /** * @brief Get the Current Battery Voltage in Volts. This value is calculated by multiplying * the regulated voltage by the battery percentage. From d2c93a0f1786f8207b92b411605072a069c720a1 Mon Sep 17 00:00:00 2001 From: Sebastian Romero Date: Tue, 2 May 2023 18:35:41 +0200 Subject: [PATCH 17/48] nicla-system: Move function to read LDO register to pmic driver. --- libraries/Nicla_System/src/BQ25120A.cpp | 5 +++++ libraries/Nicla_System/src/BQ25120A.h | 8 ++++++++ libraries/Nicla_System/src/Nicla_System.cpp | 16 ++++------------ libraries/Nicla_System/src/Nicla_System.h | 1 - 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/libraries/Nicla_System/src/BQ25120A.cpp b/libraries/Nicla_System/src/BQ25120A.cpp index 3c43ab0f5..91daa7d6b 100644 --- a/libraries/Nicla_System/src/BQ25120A.cpp +++ b/libraries/Nicla_System/src/BQ25120A.cpp @@ -25,6 +25,11 @@ uint8_t BQ25120A::getFastChargeControlRegister() return readByte(BQ25120A_ADDRESS, BQ25120A_FAST_CHG); } +uint8_t BQ25120A::getLDOControlRegister() +{ + return readByte(BQ25120A_ADDRESS, BQ25120A_LDO_CTRL); +} + void BQ25120A::writeByte(uint8_t address, uint8_t subAddress, uint8_t data) { setHighImpedanceModeEnabled(false); diff --git a/libraries/Nicla_System/src/BQ25120A.h b/libraries/Nicla_System/src/BQ25120A.h index 874390040..78f032109 100644 --- a/libraries/Nicla_System/src/BQ25120A.h +++ b/libraries/Nicla_System/src/BQ25120A.h @@ -41,6 +41,14 @@ class BQ25120A */ uint8_t getFaultsRegister(); + /** + * @brief Gets the data from the SYS VOUT Control Register. + * @see Section 9.6.7 of the datasheet. + * + * @return uint8_t The data from the SYS VOUT Control Register. + */ + uint8_t getLDOControlRegister(); + /** * @brief Gets the data from the fast charge control register. * @see Section 9.6.4 of the datasheet. diff --git a/libraries/Nicla_System/src/Nicla_System.cpp b/libraries/Nicla_System/src/Nicla_System.cpp index 50692e4b4..b3b2aaac2 100644 --- a/libraries/Nicla_System/src/Nicla_System.cpp +++ b/libraries/Nicla_System/src/Nicla_System.cpp @@ -65,7 +65,7 @@ bool nicla::enable3V3LDO() { uint8_t ldo_reg = 0xE4; _pmic.writeByte(BQ25120A_ADDRESS, BQ25120A_LDO_CTRL, ldo_reg); - if (_pmic.readByte(BQ25120A_ADDRESS, BQ25120A_LDO_CTRL) != ldo_reg) { + if (_pmic.getLDOControlRegister() != ldo_reg) { return false; } return true; @@ -75,7 +75,7 @@ bool nicla::enable1V8LDO() { uint8_t ldo_reg = 0xA8; _pmic.writeByte(BQ25120A_ADDRESS, BQ25120A_LDO_CTRL, ldo_reg); - if (_pmic.readByte(BQ25120A_ADDRESS, BQ25120A_LDO_CTRL) != ldo_reg) { + if (_pmic.getLDOControlRegister() != ldo_reg) { return false; } return true; @@ -83,13 +83,10 @@ bool nicla::enable1V8LDO() bool nicla::disableLDO() { - uint8_t ldo_reg = _pmic.readByte(BQ25120A_ADDRESS, BQ25120A_LDO_CTRL); + uint8_t ldo_reg = _pmic.getLDOControlRegister(); ldo_reg &= 0x7F; _pmic.writeByte(BQ25120A_ADDRESS, BQ25120A_LDO_CTRL, ldo_reg); - if (_pmic.readByte(BQ25120A_ADDRESS, BQ25120A_LDO_CTRL) != ldo_reg) { - return false; - } - return true; + return _pmic.getLDOControlRegister() == ldo_reg; } bool nicla::enterShipMode() @@ -103,11 +100,6 @@ bool nicla::enterShipMode() _pmic.writeByte(BQ25120A_ADDRESS, BQ25120A_STATUS, status_reg); } -uint8_t nicla::readLDOreg() -{ - return _pmic.readByte(BQ25120A_ADDRESS, BQ25120A_LDO_CTRL); -} - bool nicla::enableCharge(uint8_t mA, bool disableNtc) { diff --git a/libraries/Nicla_System/src/Nicla_System.h b/libraries/Nicla_System/src/Nicla_System.h index 064131dde..16d70eaa0 100644 --- a/libraries/Nicla_System/src/Nicla_System.h +++ b/libraries/Nicla_System/src/Nicla_System.h @@ -51,7 +51,6 @@ class nicla { * @return true if the ship mode is entered successfully. */ static bool enterShipMode(); - static uint8_t readLDOreg(); static bool enableCharge(uint8_t mA = 20, bool disableNtc = true); /** From 56b67bec1fcfe0c9aab799488b890bfb872c9ed3 Mon Sep 17 00:00:00 2001 From: Sebastian Romero Date: Wed, 3 May 2023 09:33:37 +0200 Subject: [PATCH 18/48] nicla-system: Catch error case. --- libraries/Nicla_System/src/Nicla_System.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/libraries/Nicla_System/src/Nicla_System.cpp b/libraries/Nicla_System/src/Nicla_System.cpp index b3b2aaac2..ac30ac7c2 100644 --- a/libraries/Nicla_System/src/Nicla_System.cpp +++ b/libraries/Nicla_System/src/Nicla_System.cpp @@ -206,7 +206,11 @@ void nicla::setRegulatedBatteryVoltage(float voltage){ } float nicla::getCurrentBatteryVoltage(){ - return getRegulatedBatteryVoltage() / 100 * getBatteryPercentage(); + auto percentage = getBatteryPercentage(); + if (percentage < 0) { + return 0; + } + return getRegulatedBatteryVoltage() / 100 * percentage; } int8_t nicla::getBatteryPercentage(bool useLatchedValue) { From d69d3ad3cf2cf6f4137b788ec0b04df151e2115c Mon Sep 17 00:00:00 2001 From: Sebastian Romero Date: Wed, 3 May 2023 09:56:03 +0200 Subject: [PATCH 19/48] nicla-system: Rename function name. --- .../NiclaSenseME_BatteryStatus.ino | 4 ++-- libraries/Nicla_System/src/Nicla_System.cpp | 6 +++--- libraries/Nicla_System/src/Nicla_System.h | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/libraries/Nicla_System/examples/NiclaSenseME_BatteryStatus/NiclaSenseME_BatteryStatus.ino b/libraries/Nicla_System/examples/NiclaSenseME_BatteryStatus/NiclaSenseME_BatteryStatus.ino index fccf698b2..b6fbf1408 100644 --- a/libraries/Nicla_System/examples/NiclaSenseME_BatteryStatus/NiclaSenseME_BatteryStatus.ino +++ b/libraries/Nicla_System/examples/NiclaSenseME_BatteryStatus/NiclaSenseME_BatteryStatus.ino @@ -24,7 +24,7 @@ bool updateBatteryLevel(bool enforceNewReading = false) { if (intervalFired || isFirstReading || enforceNewReading) { Serial.println("Checking the battery level..."); updateTimestamp = millis(); - auto percentage = nicla::getBatteryPercentage(); + auto percentage = nicla::getBatteryVoltagePercentage(); if (percentage < 0) { return false; // Percentage couldn't be determined. @@ -209,7 +209,7 @@ void loop() Serial.println(voltage); Serial.print("Battery Percent: "); - auto percent = nicla::getBatteryPercentage(); + auto percent = nicla::getBatteryVoltagePercentage(); Serial.println(percent); Serial.print("Battery Temperature: "); diff --git a/libraries/Nicla_System/src/Nicla_System.cpp b/libraries/Nicla_System/src/Nicla_System.cpp index ac30ac7c2..cfd5d241c 100644 --- a/libraries/Nicla_System/src/Nicla_System.cpp +++ b/libraries/Nicla_System/src/Nicla_System.cpp @@ -206,14 +206,14 @@ void nicla::setRegulatedBatteryVoltage(float voltage){ } float nicla::getCurrentBatteryVoltage(){ - auto percentage = getBatteryPercentage(); + auto percentage = getBatteryVoltagePercentage(); if (percentage < 0) { return 0; } return getRegulatedBatteryVoltage() / 100 * percentage; } -int8_t nicla::getBatteryPercentage(bool useLatchedValue) { +int8_t nicla::getBatteryVoltagePercentage(bool useLatchedValue) { /* * 9.3.4 Voltage Based Battery Monitor (Page 20) * The device implements a simple voltage battery monitor which can be used to determine the depth of discharge. @@ -293,7 +293,7 @@ int8_t nicla::getBatteryPercentage(bool useLatchedValue) { } BatteryChargeLevel nicla::getBatteryChargeLevel() { - auto percent = getBatteryPercentage(); + auto percent = getBatteryVoltagePercentage(); if (percent >= 98) { return BatteryChargeLevel::Full; diff --git a/libraries/Nicla_System/src/Nicla_System.h b/libraries/Nicla_System/src/Nicla_System.h index 16d70eaa0..f63be91f5 100644 --- a/libraries/Nicla_System/src/Nicla_System.h +++ b/libraries/Nicla_System/src/Nicla_System.h @@ -97,7 +97,7 @@ class nicla { * @return int8_t The percentage of the regulated voltage in the range of 60% to 100%. * A value of < 0 indicates that the battery percentage could not be determined. */ - static int8_t getBatteryPercentage(bool useLatchedValue = false); + static int8_t getBatteryVoltagePercentage(bool useLatchedValue = false); /** * @brief Get the Battery Charge level encoded as a number (0-5). The following values are possible: From 444a74df42c13482c049f3e69f1cead8a4605da7 Mon Sep 17 00:00:00 2001 From: Sebastian Romero Date: Wed, 3 May 2023 11:02:53 +0200 Subject: [PATCH 20/48] nicla-system: Improve efficiency of voltage calculation. --- libraries/Nicla_System/src/Nicla_System.cpp | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/libraries/Nicla_System/src/Nicla_System.cpp b/libraries/Nicla_System/src/Nicla_System.cpp index cfd5d241c..e3ba81e98 100644 --- a/libraries/Nicla_System/src/Nicla_System.cpp +++ b/libraries/Nicla_System/src/Nicla_System.cpp @@ -159,17 +159,13 @@ float nicla::getRegulatedBatteryVoltage(){ // Read the Battery Voltage Control Register that holds the regulated battery voltage uint8_t data = _pmic.readByte(BQ25120A_ADDRESS, BQ25120A_BATTERY_CTRL); - int milliVolts = 360; // 3.6V is the minimum voltage - - // Loop through bits 1-7. LSB is bit 0 and it's not used - for (int i = 1; i <= 7; ++i) { - if (data & (1 << i)) { - int addition = 1 << (i - 1); // 2^(i-1): 10, 20, 40, 80, 160, 320, 640 mV - milliVolts += addition; - } - } + int milliVolts = 3600; // 3.6V is the minimum voltage + + // Shift the data to the right by 1 bit to remove the LSB that is not used. + uint8_t shiftedData = (data >> 1) & 0b01111111; + milliVolts += shiftedData * 10; - return milliVolts / 100.0f; + return milliVolts / 1000.0f; } @@ -197,9 +193,9 @@ void nicla::setRegulatedBatteryVoltage(float voltage){ +---------+--------------------+ */ + uint16_t voltageAddition = (voltage - 3.6f) * 100; // Shift one bit to the left because the LSB is not used. - uint16_t additionalMilliVolts = (voltage - 3.6f) * 100; - uint8_t value = additionalMilliVolts << 1; + uint8_t value = voltageAddition << 1; // e.g. 4.2V - 3.6V = 0.6V * 100 = 60. 60 << 1 = 120 = 01111000 _pmic.writeByte(BQ25120A_ADDRESS, BQ25120A_BATTERY_CTRL, value); From 070898055eff08e42dfbcb31bbff04f50ef58ce5 Mon Sep 17 00:00:00 2001 From: Sebastian Romero Date: Wed, 3 May 2023 16:42:22 +0200 Subject: [PATCH 21/48] nicla-system: Fix incorrect INT disable. --- libraries/Nicla_System/src/Nicla_System.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libraries/Nicla_System/src/Nicla_System.cpp b/libraries/Nicla_System/src/Nicla_System.cpp index e3ba81e98..d15de047b 100644 --- a/libraries/Nicla_System/src/Nicla_System.cpp +++ b/libraries/Nicla_System/src/Nicla_System.cpp @@ -115,10 +115,11 @@ bool nicla::enableCharge(uint8_t mA, bool disableNtc) // For very depleted batteries, set ULVO at the very minimum (2.2V) to re-enable charging _pmic.writeByte(BQ25120A_ADDRESS, BQ25120A_ILIM_UVLO_CTRL, 0x3F); - // Disable TS and interrupt on charge _ntcEnabled = !disableNtc; if (!_ntcEnabled) { - _pmic.writeByte(BQ25120A_ADDRESS, BQ25120A_TS_CONTROL, 1 << 3); + // Disable Temperature Sense (B7 = 0) and interrupt on charge status change (B3 = 0). + // INT only shows faults and does not show charge status) + _pmic.writeByte(BQ25120A_ADDRESS, BQ25120A_TS_CONTROL, 0); } // also set max battery voltage to 4.2V (VBREG) From e6ff5c559c6af2cfb2ddaeb035910e676e6dfd50 Mon Sep 17 00:00:00 2001 From: Sebastian Romero Date: Wed, 3 May 2023 16:43:10 +0200 Subject: [PATCH 22/48] nicla-system: Add documentation to charging function. --- libraries/Nicla_System/src/Nicla_System.cpp | 36 +++++++++++++++++---- libraries/Nicla_System/src/Nicla_System.h | 8 +++++ 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/libraries/Nicla_System/src/Nicla_System.cpp b/libraries/Nicla_System/src/Nicla_System.cpp index d15de047b..57b6757e0 100644 --- a/libraries/Nicla_System/src/Nicla_System.cpp +++ b/libraries/Nicla_System/src/Nicla_System.cpp @@ -102,17 +102,42 @@ bool nicla::enterShipMode() bool nicla::enableCharge(uint8_t mA, bool disableNtc) { + /* + The ICHRG is calculated using the following equation: + - If ICHRG_RANGE (Bit 7) is 0, then ICHRG = 5 mA + ICHRGCODE x 1 mA. + - If ICHRG_RANGE (Bit 7) is 1, then ICHRG = 40 mA + ICHRGCODE x 10 mA. + - If a value greater than 35 mA (ICHRG_RANGE = 0) or 300 mA (ICHRG_RANGE = 1) is written, + the setting goes to 35 mA or 300 mA respectively, except if the ICHRG bits are all 1 (that is, 11111), + then the externally programmed value is used. See section 9.6.4 in the datasheet. + */ + + if (mA > 300) { + mA = 300; + } if (mA < 5) { - _fastChargeRegisterData = 0x3; - } else if (mA < 35) { - _fastChargeRegisterData = ((mA-5) << 2); + mA = 5; + } + + if(mA > 35 && mA < 40) { + mA = 35; + } + + if (mA <= 35) { + // Values 5 mA to 35 mA + _fastChargeRegisterData = ((mA-5) << 2); // e.g. 20mA - 5mA = 15mA << 2 -> 0b00111100 } else { + // Values 40 mA to 300 mA + // e.g. (200mA - 40mA) / 10 = 16mA << 2 -> 0b01000000 | 0x80 -> 0b11000000 _fastChargeRegisterData = (((mA-40)/10) << 2) | 0x80; } + _pmic.writeByte(BQ25120A_ADDRESS, BQ25120A_FAST_CHG, _fastChargeRegisterData); - // For very depleted batteries, set ULVO at the very minimum (2.2V) to re-enable charging + // For very depleted batteries, set BULVO to the very minimum to re-enable charging. + // 2.2V or 2.0V are the minimum values for BULVO. The latter is not mentioned in the datasheet + // but it looks like a typo since 2.2V is mentioned twice. See: Table 22 in the datasheet. + // Also sets the input current limit to 350mA. _pmic.writeByte(BQ25120A_ADDRESS, BQ25120A_ILIM_UVLO_CTRL, 0x3F); _ntcEnabled = !disableNtc; @@ -122,9 +147,6 @@ bool nicla::enableCharge(uint8_t mA, bool disableNtc) _pmic.writeByte(BQ25120A_ADDRESS, BQ25120A_TS_CONTROL, 0); } - // also set max battery voltage to 4.2V (VBREG) - // _pmic.writeByte(BQ25120A_ADDRESS, BQ25120A_BATTERY_CTRL, (4.2f - 3.6f)*100); - return _pmic.getFastChargeControlRegister() == _fastChargeRegisterData; } diff --git a/libraries/Nicla_System/src/Nicla_System.h b/libraries/Nicla_System/src/Nicla_System.h index f63be91f5..9ce23c931 100644 --- a/libraries/Nicla_System/src/Nicla_System.h +++ b/libraries/Nicla_System/src/Nicla_System.h @@ -51,6 +51,14 @@ class nicla { * @return true if the ship mode is entered successfully. */ static bool enterShipMode(); + + /** + * @brief Enables fast charging of the battery. + * + * @param mA The desired milliampere (mA) charging current. The default is 20mA. + * @param disableNtc Whether to disable Temperature Sense and interrupt on charge. The default is true. + * @return true If the fast charging is enabled successfully. False, otherwise. + */ static bool enableCharge(uint8_t mA = 20, bool disableNtc = true); /** From 6d8c08a4a789d925db2b84275644cfb62515896e Mon Sep 17 00:00:00 2001 From: Sebastian Romero Date: Wed, 3 May 2023 16:43:28 +0200 Subject: [PATCH 23/48] nicla-system: Rename charging function. --- .../NiclaSenseME_BatteryStatus/NiclaSenseME_BatteryStatus.ino | 2 +- libraries/Nicla_System/src/Nicla_System.cpp | 2 +- libraries/Nicla_System/src/Nicla_System.h | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/libraries/Nicla_System/examples/NiclaSenseME_BatteryStatus/NiclaSenseME_BatteryStatus.ino b/libraries/Nicla_System/examples/NiclaSenseME_BatteryStatus/NiclaSenseME_BatteryStatus.ino index b6fbf1408..ba194dd06 100644 --- a/libraries/Nicla_System/examples/NiclaSenseME_BatteryStatus/NiclaSenseME_BatteryStatus.ino +++ b/libraries/Nicla_System/examples/NiclaSenseME_BatteryStatus/NiclaSenseME_BatteryStatus.ino @@ -168,7 +168,7 @@ void setup() nicla::leds.setColor(green); - nicla::enableCharge(); + nicla::enableCharging(); } void loop() diff --git a/libraries/Nicla_System/src/Nicla_System.cpp b/libraries/Nicla_System/src/Nicla_System.cpp index 57b6757e0..e67870209 100644 --- a/libraries/Nicla_System/src/Nicla_System.cpp +++ b/libraries/Nicla_System/src/Nicla_System.cpp @@ -100,7 +100,7 @@ bool nicla::enterShipMode() _pmic.writeByte(BQ25120A_ADDRESS, BQ25120A_STATUS, status_reg); } -bool nicla::enableCharge(uint8_t mA, bool disableNtc) +bool nicla::enableCharging(uint16_t mA, bool disableNtc) { /* The ICHRG is calculated using the following equation: diff --git a/libraries/Nicla_System/src/Nicla_System.h b/libraries/Nicla_System/src/Nicla_System.h index 9ce23c931..e87b2b14b 100644 --- a/libraries/Nicla_System/src/Nicla_System.h +++ b/libraries/Nicla_System/src/Nicla_System.h @@ -55,11 +55,11 @@ class nicla { /** * @brief Enables fast charging of the battery. * - * @param mA The desired milliampere (mA) charging current. The default is 20mA. + * @param mA The desired milliampere (mA) charging current. Range: 5mA - 35mA and 40mA - 300mA. The default is 20mA. * @param disableNtc Whether to disable Temperature Sense and interrupt on charge. The default is true. * @return true If the fast charging is enabled successfully. False, otherwise. */ - static bool enableCharge(uint8_t mA = 20, bool disableNtc = true); + static bool enableCharging(uint16_t mA = 20, bool disableNtc = true); /** * @brief Enables or disables the negative temperature coefficient (NTC) thermistor. From 9f1b6cc17fa5b9b08bf51e783585f50fe8952938 Mon Sep 17 00:00:00 2001 From: Sebastian Romero Date: Wed, 3 May 2023 16:49:27 +0200 Subject: [PATCH 24/48] nicla-system: Avoid High-Z mode when powered from VIN. --- libraries/Nicla_System/src/BQ25120A.cpp | 33 +++++++++++++++++++------ libraries/Nicla_System/src/BQ25120A.h | 17 +++++++++++++ 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/libraries/Nicla_System/src/BQ25120A.cpp b/libraries/Nicla_System/src/BQ25120A.cpp index 91daa7d6b..f6ee864c0 100644 --- a/libraries/Nicla_System/src/BQ25120A.cpp +++ b/libraries/Nicla_System/src/BQ25120A.cpp @@ -30,10 +30,21 @@ uint8_t BQ25120A::getLDOControlRegister() return readByte(BQ25120A_ADDRESS, BQ25120A_LDO_CTRL); } +bool BQ25120A::runsOnBattery(uint8_t address){ + uint8_t faults = readByteUnprotected(address, BQ25120A_FAULTS); + // Read VIN under voltage fault (VIN_UV on Bit 6) from the faults register. + bool runsOnBattery = (faults & 0b01000000) != 0; + return runsOnBattery; +} + void BQ25120A::writeByte(uint8_t address, uint8_t subAddress, uint8_t data) { - setHighImpedanceModeEnabled(false); nicla::_i2c_mutex.lock(); + // Only enter active mode when runnning on battery. + // When powered from VIN, driving CD HIGH would disable charging. + if(runsOnBattery(address)){ + setHighImpedanceModeEnabled(false); + } Wire1.beginTransmission(address); Wire1.write(subAddress); Wire1.write(data); @@ -42,10 +53,7 @@ void BQ25120A::writeByte(uint8_t address, uint8_t subAddress, uint8_t data) setHighImpedanceModeEnabled(true); } -uint8_t BQ25120A::readByte(uint8_t address, uint8_t subAddress) -{ - setHighImpedanceModeEnabled(false); - nicla::_i2c_mutex.lock(); +uint8_t BQ25120A::readByteUnprotected(uint8_t address, uint8_t subAddress){ Wire1.beginTransmission(address); Wire1.write(subAddress); Wire1.endTransmission(false); @@ -53,8 +61,19 @@ uint8_t BQ25120A::readByte(uint8_t address, uint8_t subAddress) uint32_t timeout = 100; uint32_t start_time = millis(); while(!Wire1.available() && (millis() - start_time) < timeout) {} - uint8_t ret = Wire1.read(); - nicla::_i2c_mutex.unlock(); + return Wire1.read(); +} + +uint8_t BQ25120A::readByte(uint8_t address, uint8_t subAddress) +{ + nicla::_i2c_mutex.lock(); + // Only enter active mode when runnning on battery. + // When powered from VIN, driving CD HIGH would disable charging. + if(runsOnBattery(address)){ + setHighImpedanceModeEnabled(false); + } + uint8_t ret = readByteUnprotected(address, subAddress); + nicla::_i2c_mutex.unlock(); setHighImpedanceModeEnabled(true); return ret; } diff --git a/libraries/Nicla_System/src/BQ25120A.h b/libraries/Nicla_System/src/BQ25120A.h index 78f032109..8cfedf654 100644 --- a/libraries/Nicla_System/src/BQ25120A.h +++ b/libraries/Nicla_System/src/BQ25120A.h @@ -57,6 +57,14 @@ class BQ25120A */ uint8_t getFastChargeControlRegister(); + + /** + * @brief Determines if the board is charged from the battery. + * + * @return true If the board is powered from the battery. False, when powered from USB / VIN. + */ + bool runsOnBattery(uint8_t address); + /** * @brief Writes a byte to the BQ25120A over I2C. * @param address The I2C address of the BQ25120A. @@ -89,6 +97,15 @@ class BQ25120A * @param enabled Defines if the high impedance mode should be enabled or disabled. */ void setHighImpedanceModeEnabled(bool enabled); + + /** + * @brief Reads a byte from the BQ25120A over I2C without locking the bus through the mutex. + * + * @param address The I2C address of the BQ25120A. + * @param subAddress The memory location of the register to read from. + * @return uint8_t The data read from the register. + */ + uint8_t readByteUnprotected(uint8_t address, uint8_t subAddress); }; #endif From ad89fb1c6672f933e937b6835bdf3419769b90a0 Mon Sep 17 00:00:00 2001 From: Sebastian Romero Date: Wed, 3 May 2023 18:07:19 +0200 Subject: [PATCH 25/48] nicla-system: Add function to check if board runs on battery. --- .../NiclaSenseME_BatteryStatus.ino | 5 +++++ libraries/Nicla_System/src/Nicla_System.cpp | 4 ++++ libraries/Nicla_System/src/Nicla_System.h | 7 +++++++ 3 files changed, 16 insertions(+) diff --git a/libraries/Nicla_System/examples/NiclaSenseME_BatteryStatus/NiclaSenseME_BatteryStatus.ino b/libraries/Nicla_System/examples/NiclaSenseME_BatteryStatus/NiclaSenseME_BatteryStatus.ino index ba194dd06..c026c5bee 100644 --- a/libraries/Nicla_System/examples/NiclaSenseME_BatteryStatus/NiclaSenseME_BatteryStatus.ino +++ b/libraries/Nicla_System/examples/NiclaSenseME_BatteryStatus/NiclaSenseME_BatteryStatus.ino @@ -222,6 +222,11 @@ void loop() bool isCharging = nicla::getOperatingStatus() == OperatingStatus::Charging; Serial.print("Battery is charging: "); Serial.println(isCharging ? "Yes" : "No"); + + bool runsOnBattery = nicla::runsOnBattery(); + Serial.print("Runs on battery: "); + Serial.println(runsOnBattery ? "Yes" : "No"); + Serial.println("----------------------"); } diff --git a/libraries/Nicla_System/src/Nicla_System.cpp b/libraries/Nicla_System/src/Nicla_System.cpp index e67870209..7280723f5 100644 --- a/libraries/Nicla_System/src/Nicla_System.cpp +++ b/libraries/Nicla_System/src/Nicla_System.cpp @@ -150,6 +150,10 @@ bool nicla::enableCharging(uint16_t mA, bool disableNtc) return _pmic.getFastChargeControlRegister() == _fastChargeRegisterData; } +bool nicla::runsOnBattery() { + return _pmic.runsOnBattery(BQ25120A_ADDRESS); +} + uint8_t nicla::getBatteryFaults() { // Skips the mask bits (4 LSBs) return (_pmic.getFaultsRegister() >> 4) & 0b1111; diff --git a/libraries/Nicla_System/src/Nicla_System.h b/libraries/Nicla_System/src/Nicla_System.h index e87b2b14b..d6cf84213 100644 --- a/libraries/Nicla_System/src/Nicla_System.h +++ b/libraries/Nicla_System/src/Nicla_System.h @@ -61,6 +61,13 @@ class nicla { */ static bool enableCharging(uint16_t mA = 20, bool disableNtc = true); + /** + * @brief Determines if the board is charged from the battery. + * + * @return true If the board is powered from the battery. False, when powered from USB / VIN. + */ + static bool runsOnBattery(); + /** * @brief Enables or disables the negative temperature coefficient (NTC) thermistor. * NTCs are used to prevent the batteries from being charged at temperatures that are too high or too low. From 6ab5b665cb20130f83dc2b1bca14abbefce15b03 Mon Sep 17 00:00:00 2001 From: Sebastian Romero Date: Thu, 4 May 2023 10:10:24 +0200 Subject: [PATCH 26/48] nicla-system: Add documentation to LDO functions. --- libraries/Nicla_System/src/Nicla_System.cpp | 2 +- libraries/Nicla_System/src/Nicla_System.h | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/libraries/Nicla_System/src/Nicla_System.cpp b/libraries/Nicla_System/src/Nicla_System.cpp index 7280723f5..fbb1b488e 100644 --- a/libraries/Nicla_System/src/Nicla_System.cpp +++ b/libraries/Nicla_System/src/Nicla_System.cpp @@ -84,7 +84,7 @@ bool nicla::enable1V8LDO() bool nicla::disableLDO() { uint8_t ldo_reg = _pmic.getLDOControlRegister(); - ldo_reg &= 0x7F; + ldo_reg &= 0x7F; // Zeroes the EN_LS_LDO bit to turn it off _pmic.writeByte(BQ25120A_ADDRESS, BQ25120A_LDO_CTRL, ldo_reg); return _pmic.getLDOControlRegister() == ldo_reg; } diff --git a/libraries/Nicla_System/src/Nicla_System.h b/libraries/Nicla_System/src/Nicla_System.h index d6cf84213..d0b57fedd 100644 --- a/libraries/Nicla_System/src/Nicla_System.h +++ b/libraries/Nicla_System/src/Nicla_System.h @@ -39,8 +39,29 @@ class nicla { public: static bool begin(bool mountedOnMkr = false); + + /** + * @brief Enables the 3.3V LDO voltage regulator. + * + * @return true if the LDO is enabled successfully. + * This is done by verifying that the register was written correctly. + */ static bool enable3V3LDO(); + + /** + * @brief Enables the 1.8V LDO voltage regulator. + * + * @return true if the LDO is enabled successfully. + * This is done by verifying that the register was written correctly. + */ static bool enable1V8LDO(); + + /** + * @brief Disables the LDO voltage regulator. + * + * @return true if the LDO is disabled successfully. + * This is done by verifying that the register was written correctly. + */ static bool disableLDO(); /** From 6286728bc0249aa837efce0f6b30d6f0a88ed92c Mon Sep 17 00:00:00 2001 From: Sebastian Romero Date: Thu, 4 May 2023 10:44:57 +0200 Subject: [PATCH 27/48] nicla-system: Add function to disable charging. --- libraries/Nicla_System/src/Nicla_System.cpp | 8 ++++++++ libraries/Nicla_System/src/Nicla_System.h | 7 +++++++ 2 files changed, 15 insertions(+) diff --git a/libraries/Nicla_System/src/Nicla_System.cpp b/libraries/Nicla_System/src/Nicla_System.cpp index fbb1b488e..ff17a8417 100644 --- a/libraries/Nicla_System/src/Nicla_System.cpp +++ b/libraries/Nicla_System/src/Nicla_System.cpp @@ -150,6 +150,14 @@ bool nicla::enableCharging(uint16_t mA, bool disableNtc) return _pmic.getFastChargeControlRegister() == _fastChargeRegisterData; } +bool nicla::disableCharging() +{ + // Set Bit 1 to 1 to disable charging. + _fastChargeRegisterData |= 0b10; + _pmic.writeByte(BQ25120A_ADDRESS, BQ25120A_FAST_CHG, _fastChargeRegisterData); + return _pmic.getFastChargeControlRegister() == _fastChargeRegisterData; +} + bool nicla::runsOnBattery() { return _pmic.runsOnBattery(BQ25120A_ADDRESS); } diff --git a/libraries/Nicla_System/src/Nicla_System.h b/libraries/Nicla_System/src/Nicla_System.h index d0b57fedd..440f4632e 100644 --- a/libraries/Nicla_System/src/Nicla_System.h +++ b/libraries/Nicla_System/src/Nicla_System.h @@ -82,6 +82,13 @@ class nicla { */ static bool enableCharging(uint16_t mA = 20, bool disableNtc = true); + /** + * @brief Disables charging of the battery. It can be resumed by calling enableCharging(). + * + * @return true If the charging is disabled successfully. False, otherwise. + */ + static bool disableCharging(); + /** * @brief Determines if the board is charged from the battery. * From abbc54fee1dd154d662d963e77fbe520f1b429be Mon Sep 17 00:00:00 2001 From: Sebastian Romero Date: Thu, 4 May 2023 11:07:55 +0200 Subject: [PATCH 28/48] nicla-system: Disable charging while taking a battery reading. --- libraries/Nicla_System/src/Nicla_System.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/libraries/Nicla_System/src/Nicla_System.cpp b/libraries/Nicla_System/src/Nicla_System.cpp index ff17a8417..7089cb9d4 100644 --- a/libraries/Nicla_System/src/Nicla_System.cpp +++ b/libraries/Nicla_System/src/Nicla_System.cpp @@ -284,13 +284,23 @@ int8_t nicla::getBatteryVoltagePercentage(bool useLatchedValue) { uint8_t faults = _pmic.getFaultsRegister(); if(faults & BAT_UVLO_FAULT) return -1; // Battery is not connected or voltage is too low - // Write 1 to VBMON_READ to trigger a new reading - // TODO: Disable charging while reading battery percentage. SEE chapter 9.3.4 if(!useLatchedValue){ + // Disable charging while reading battery percentage. SEE chapter 9.3.4 + bool chargingEnabled = (_fastChargeRegisterData & 0b10) == 0; // Bit 1 is 0 if charging is enabled. + + if(chargingEnabled) { + disableCharging(); + } // Write 1 to VBMON_READ to trigger a new reading _pmic.writeByte(BQ25120A_ADDRESS, BQ25120A_BATT_MON, 1); delay(3); // According to datasheet, 2ms is enough, but we add 1ms for safety + + if(chargingEnabled) { + // Re-enable charging by setting bit 1 to 0 + _fastChargeRegisterData &= 0b11111101; + _pmic.writeByte(BQ25120A_ADDRESS, BQ25120A_FAST_CHG, _fastChargeRegisterData); + } } uint8_t data = _pmic.readByte(BQ25120A_ADDRESS, BQ25120A_BATT_MON); From 2871417f6414d8139a08448c496326af460aa903 Mon Sep 17 00:00:00 2001 From: Sebastian Romero Date: Thu, 4 May 2023 12:55:06 +0200 Subject: [PATCH 29/48] nicla-system: Display charging and power status. --- .../NiclaSenseME_BatteryStatus.ino | 78 ++++++++++++++++++- .../Nicla_System/extras/BatteryMonitor/app.js | 38 +++++++-- .../extras/BatteryMonitor/index.html | 13 ++++ .../extras/BatteryMonitor/style.css | 11 +++ 4 files changed, 131 insertions(+), 9 deletions(-) diff --git a/libraries/Nicla_System/examples/NiclaSenseME_BatteryStatus/NiclaSenseME_BatteryStatus.ino b/libraries/Nicla_System/examples/NiclaSenseME_BatteryStatus/NiclaSenseME_BatteryStatus.ino index c026c5bee..537547cde 100644 --- a/libraries/Nicla_System/examples/NiclaSenseME_BatteryStatus/NiclaSenseME_BatteryStatus.ino +++ b/libraries/Nicla_System/examples/NiclaSenseME_BatteryStatus/NiclaSenseME_BatteryStatus.ino @@ -2,10 +2,12 @@ #include constexpr auto printInterval { 4000ul }; -constexpr auto batteryMeasureInterval { 5000ul }; +constexpr auto batteryUpdateInterval { 4000ul }; int8_t batteryChargeLevel = -1; int8_t batteryPercentage = -1; float batteryVoltage = -1.0f; +int8_t runsOnBattery = -1; // Using an int to be able to represent an unknown state. +int8_t batteryIsCharging = -1; // Using an int to be able to represent an unknown state. #define DEVICE_NAME "NiclaSenseME" @@ -15,10 +17,39 @@ BLEService service(DEVICE_UUID("0000")); BLEIntCharacteristic batteryPercentageCharacteristic(DEVICE_UUID("1001"), BLERead | BLENotify); BLEFloatCharacteristic batteryVoltageCharacteristic(DEVICE_UUID("1002"), BLERead | BLENotify); BLEIntCharacteristic batteryChargeLevelCharacteristic(DEVICE_UUID("1003"), BLERead | BLENotify); +BLEBooleanCharacteristic runsOnBatteryCharacteristic(DEVICE_UUID("1004"), BLERead | BLENotify); +BLEBooleanCharacteristic isChargingCharacteristic(DEVICE_UUID("1005"), BLERead | BLENotify); -bool updateBatteryLevel(bool enforceNewReading = false) { +bool updateBatteryStatus(){ static auto updateTimestamp = millis(); - bool intervalFired = millis() - updateTimestamp >= batteryMeasureInterval; + bool intervalFired = millis() - updateTimestamp >= batteryUpdateInterval; + bool isFirstReading = runsOnBattery == -1 || batteryIsCharging == -1; + + if (intervalFired || isFirstReading) { + Serial.println("Checking the battery status..."); + updateTimestamp = millis(); + int8_t isCharging = nicla::getOperatingStatus() == OperatingStatus::Charging; + int8_t batteryPowered = nicla::runsOnBattery(); + bool valueUpdated = false; + + if (batteryIsCharging != isCharging) { + batteryIsCharging = isCharging; + valueUpdated = true; + } + + if (runsOnBattery != batteryPowered) { + runsOnBattery = batteryPowered; + valueUpdated = true; + } + + return valueUpdated; + } + + return false; +} + + static auto updateTimestamp = millis(); + bool intervalFired = millis() - updateTimestamp >= batteryUpdateInterval; bool isFirstReading = batteryPercentage == -1 || batteryVoltage == -1.0f; if (intervalFired || isFirstReading || enforceNewReading) { @@ -112,6 +143,22 @@ void onBatteryChargeLevelCharacteristicRead(BLEDevice central, BLECharacteristic batteryChargeLevelCharacteristic.writeValue(batteryChargeLevel); } +void onRunsOnBatteryCharacteristicRead(BLEDevice central, BLECharacteristic characteristic) { + Serial.println("Checking if device runs on battery..."); + updateBatteryStatus(); + Serial.print("Runs on battery: "); + Serial.println(runsOnBattery == 1 ? "Yes" : "No"); + runsOnBatteryCharacteristic.writeValue(runsOnBattery == 1); +} + +void onIsChargingCharacteristicRead(BLEDevice central, BLECharacteristic characteristic) { + Serial.println("Checking if battery is charging..."); + updateBatteryStatus(); + Serial.print("Battery is charging: "); + Serial.println(batteryIsCharging == 1 ? "Yes" : "No"); + isChargingCharacteristic.writeValue(batteryIsCharging == 1); +} + void onCharacteristicSubscribed(BLEDevice central, BLECharacteristic characteristic) { Serial.println("Device subscribed to characteristic: " + String(characteristic.uuid())); } @@ -150,6 +197,16 @@ void setupBLE() { batteryChargeLevelCharacteristic.setEventHandler(BLESubscribed, onCharacteristicSubscribed); batteryChargeLevelCharacteristic.writeValue(batteryChargeLevel); + service.addCharacteristic(runsOnBatteryCharacteristic); + runsOnBatteryCharacteristic.setEventHandler(BLERead, onRunsOnBatteryCharacteristicRead); + runsOnBatteryCharacteristic.setEventHandler(BLESubscribed, onCharacteristicSubscribed); + runsOnBatteryCharacteristic.writeValue(runsOnBattery == 1); + + service.addCharacteristic(isChargingCharacteristic); + isChargingCharacteristic.setEventHandler(BLERead, onIsChargingCharacteristicRead); + isChargingCharacteristic.setEventHandler(BLESubscribed, onCharacteristicSubscribed); + isChargingCharacteristic.writeValue(batteryIsCharging == 1); + BLE.addService(service); BLE.advertise(); } @@ -177,6 +234,7 @@ void loop() if (BLE.connected()) { bool newBatteryLevelAvailable = updateBatteryLevel(); + bool newBatteryStatusAvailable = updateBatteryStatus(); if (batteryPercentageCharacteristic.subscribed() && newBatteryLevelAvailable) { Serial.print("Battery Percentage: "); @@ -195,7 +253,19 @@ void loop() Serial.println(batteryChargeLevel); batteryChargeLevelCharacteristic.writeValue(batteryChargeLevel); } - + + if(runsOnBatteryCharacteristic.subscribed() && newBatteryStatusAvailable) { + Serial.print("Runs on battery: "); + Serial.println(runsOnBattery == 1 ? "Yes" : "No"); + runsOnBatteryCharacteristic.writeValue(runsOnBattery == 1); + } + + if(isChargingCharacteristic.subscribed() && newBatteryStatusAvailable) { + Serial.print("Battery is charging: "); + Serial.println(batteryIsCharging == 1 ? "Yes" : "No"); + isChargingCharacteristic.writeValue(batteryIsCharging == 1); + } + return; } diff --git a/libraries/Nicla_System/extras/BatteryMonitor/app.js b/libraries/Nicla_System/extras/BatteryMonitor/app.js index 5cd1dd552..ad743fdf2 100644 --- a/libraries/Nicla_System/extras/BatteryMonitor/app.js +++ b/libraries/Nicla_System/extras/BatteryMonitor/app.js @@ -1,6 +1,8 @@ const connectButton = document.getElementById('connect'); const batteryLevelElement = document.getElementById('battery-level'); const batteryLabel = document.getElementById('battery-label'); +const chargingIconElement = document.getElementById('charging-icon'); +const externalPowerIconElement = document.getElementById('external-powered-icon'); const serviceUuid = '19b10000-0000-537e-4f6c-d104768a1214'; let pollIntervalID; @@ -41,6 +43,27 @@ let data = { const colors = ["#ffffff", "#ff2d2d", "#fc9228", "#ffea00", "#adfd5c", "#00c600"]; return colors[value]; } + }, + "runsOnBattery": { + "name": "Runs on Battery", + "value": false, + "unit": "", + "characteristic": null, + "characteristicUUID": "19b10000-1004-537e-4f6c-d104768a1214", + "extractData": function(dataView) { + return dataView.getUint8(0) == 1; + } + }, + + "isCharging": { + "name": "Is Charging", + "value": false, + "unit": "", + "characteristic": null, + "characteristicUUID": "19b10000-1005-537e-4f6c-d104768a1214", + "extractData": function(dataView) { + return dataView.getUint8(0) == 1; + } } }; @@ -54,6 +77,8 @@ function onDisconnected(event) { // Reset the battery level display batteryLevelElement.style.width = "0px"; batteryLabel.textContent = ""; + chargingIconElement.style.display = "none"; + externalPowerIconElement.style.display = "none"; } async function connectToPeripheralDevice(usePolling = false, pollInterval = 5000){ @@ -93,7 +118,7 @@ async function connectToPeripheralDevice(usePolling = false, pollInterval = 5000 connectButton.addEventListener('click', async () => { try { - await connectToPeripheralDevice(true); + await connectToPeripheralDevice(); connectButton.disabled = true; connectButton.style.opacity = 0.5; } catch (error) { @@ -106,7 +131,7 @@ connectButton.addEventListener('click', async () => { } }); -function displayBatteryLevel() { +function displayBatteryData() { const batteryPercentage = data.batteryPercentage.value; const batteryVoltage = data.batteryVoltage.value; const regulatedVoltage = (batteryVoltage / batteryPercentage * 100).toFixed(2); @@ -116,6 +141,9 @@ function displayBatteryLevel() { batteryLevelElement.style.width = `${batteryPercentageMapped * 0.56}px`; // Scale the battery level to the width of the battery div batteryLevelElement.style.backgroundColor = data.batteryChargeLevel.getColor(data.batteryChargeLevel.value); batteryLabel.textContent = `${batteryVoltage.toFixed(2)}V (${batteryPercentage}% of ${regulatedVoltage}V)`; + + chargingIconElement.style.display = data.isCharging.value ? "block" : "none"; + externalPowerIconElement.style.display = data.runsOnBattery.value ? "none" : "block"; } async function readCharacteristicsData() { @@ -127,7 +155,7 @@ async function readCharacteristicsData() { console.log(item.name + ": " + item.value + item.unit); }) ); - displayBatteryLevel(); + displayBatteryData(); } function handleCharacteristicChange(event) { @@ -136,6 +164,6 @@ function handleCharacteristicChange(event) { let dataView = event.target.value; dataItem.value = dataItem.extractData(dataView); - console.log(dataItem.name + " changed: " + dataItem.value + dataItem.unit); - displayBatteryLevel(); + console.log(`'${dataItem.name}' changed: ${dataItem.value}${dataItem.unit}`); + displayBatteryData(); } diff --git a/libraries/Nicla_System/extras/BatteryMonitor/index.html b/libraries/Nicla_System/extras/BatteryMonitor/index.html index 8d3acdbae..cef2c7524 100644 --- a/libraries/Nicla_System/extras/BatteryMonitor/index.html +++ b/libraries/Nicla_System/extras/BatteryMonitor/index.html @@ -19,6 +19,19 @@

WebBLE Battery Monitor 🔋

+
+
+ + + + +
+
+ + + +
+
diff --git a/libraries/Nicla_System/extras/BatteryMonitor/style.css b/libraries/Nicla_System/extras/BatteryMonitor/style.css index d7336b0ec..fe302e9d6 100644 --- a/libraries/Nicla_System/extras/BatteryMonitor/style.css +++ b/libraries/Nicla_System/extras/BatteryMonitor/style.css @@ -57,3 +57,14 @@ button:hover { width: 0; height: 26px; } + +#battery-status { + display: flex; + flex-direction: row; + margin: 20px 0; + justify-content: center; +} + +#battery-status > div { + display: none; +} \ No newline at end of file From de371efce33538aa631600b01aacfd802a086c8d Mon Sep 17 00:00:00 2001 From: Sebastian Romero Date: Thu, 4 May 2023 13:03:32 +0200 Subject: [PATCH 30/48] nicla-system: Make BLE updates more robust. --- .../NiclaSenseME_BatteryStatus.ino | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/libraries/Nicla_System/examples/NiclaSenseME_BatteryStatus/NiclaSenseME_BatteryStatus.ino b/libraries/Nicla_System/examples/NiclaSenseME_BatteryStatus/NiclaSenseME_BatteryStatus.ino index 537547cde..6ab8ad126 100644 --- a/libraries/Nicla_System/examples/NiclaSenseME_BatteryStatus/NiclaSenseME_BatteryStatus.ino +++ b/libraries/Nicla_System/examples/NiclaSenseME_BatteryStatus/NiclaSenseME_BatteryStatus.ino @@ -48,11 +48,12 @@ bool updateBatteryStatus(){ return false; } +bool updateBatteryLevel() { static auto updateTimestamp = millis(); bool intervalFired = millis() - updateTimestamp >= batteryUpdateInterval; bool isFirstReading = batteryPercentage == -1 || batteryVoltage == -1.0f; - if (intervalFired || isFirstReading || enforceNewReading) { + if (intervalFired || isFirstReading) { Serial.println("Checking the battery level..."); updateTimestamp = millis(); auto percentage = nicla::getBatteryVoltagePercentage(); @@ -61,14 +62,22 @@ bool updateBatteryStatus(){ return false; // Percentage couldn't be determined. } + // Only if the percentage has changed, we update the values as they depend on it. if (batteryPercentage != percentage) { - batteryPercentage = percentage; - batteryVoltage = nicla::getCurrentBatteryVoltage(); - batteryChargeLevel = static_cast(nicla::getBatteryChargeLevel()); + int8_t currentChargeLevel = static_cast(nicla::getBatteryChargeLevel()); + if(currentChargeLevel == 0){ + return false; // Unknown battery charge level. + } - Serial.print("New battery level: "); - Serial.println(batteryPercentage); + auto currentVoltage = nicla::getCurrentBatteryVoltage(); + if(currentVoltage == 0){ + return false; // Unknown battery voltage. + } + batteryPercentage = percentage; + batteryChargeLevel = currentChargeLevel; + batteryVoltage = currentVoltage; + return true; } } From 4dee246dbbc378cabc8a59d2c1c2b38f849f927b Mon Sep 17 00:00:00 2001 From: Sebastian Romero Date: Thu, 4 May 2023 13:11:23 +0200 Subject: [PATCH 31/48] nicla-system: Add documentation to the web app. --- .../Nicla_System/extras/BatteryMonitor/app.js | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/libraries/Nicla_System/extras/BatteryMonitor/app.js b/libraries/Nicla_System/extras/BatteryMonitor/app.js index ad743fdf2..083b070b0 100644 --- a/libraries/Nicla_System/extras/BatteryMonitor/app.js +++ b/libraries/Nicla_System/extras/BatteryMonitor/app.js @@ -1,3 +1,4 @@ +/// UI elements const connectButton = document.getElementById('connect'); const batteryLevelElement = document.getElementById('battery-level'); const batteryLabel = document.getElementById('battery-label'); @@ -8,6 +9,7 @@ const serviceUuid = '19b10000-0000-537e-4f6c-d104768a1214'; let pollIntervalID; let peripheralDevice; +/// Data structure to hold the characteristics and their values plus data conversion functions. let data = { "batteryPercentage": { "name": "Battery Percentage", @@ -81,6 +83,12 @@ function onDisconnected(event) { externalPowerIconElement.style.display = "none"; } +/** + * Connects to the Arduino board and starts reading the characteristics. + * @param {Boolean} usePolling The default is to use notifications, but polling can be used instead. + * In that case a poll interval can be defined. + * @param {Number} pollInterval The interval in milliseconds to poll the characteristics from the device. + */ async function connectToPeripheralDevice(usePolling = false, pollInterval = 5000){ if (peripheralDevice && peripheralDevice.gatt.connected) { console.log("Already connected"); @@ -131,6 +139,12 @@ connectButton.addEventListener('click', async () => { } }); +/** + * Renders the data from the device in the UI. + * It displays the battery level as a visual bar color coded from red to green. + * It also displays the battery voltage and the percentage of the regulated voltage. + * It also displays the charging and external power status. + */ function displayBatteryData() { const batteryPercentage = data.batteryPercentage.value; const batteryVoltage = data.batteryVoltage.value; @@ -146,6 +160,10 @@ function displayBatteryData() { externalPowerIconElement.style.display = data.runsOnBattery.value ? "none" : "block"; } +/** + * Used together with polling to read the characteristics from the device. + * After reading the data it is displayed in the UI by calling displayBatteryData(). + */ async function readCharacteristicsData() { await Promise.all( Object.keys(data).map(async (key) => { @@ -158,6 +176,11 @@ async function readCharacteristicsData() { displayBatteryData(); } +/** + * Callback function that is called when a characteristic value changes. + * Updates the data object with the new value and displays it in the UI by calling displayBatteryData(). + * @param {*} event The event that contains the characteristic that changed. + */ function handleCharacteristicChange(event) { // Find the characteristic that changed in the data object by matching the UUID let dataItem = Object.values(data).find(item => item.characteristicUUID === event.target.uuid); From 48fb48e5154d7f2de0db5f6ca4d23b875a1c7bbd Mon Sep 17 00:00:00 2001 From: Sebastian Romero Date: Thu, 4 May 2023 13:22:53 +0200 Subject: [PATCH 32/48] nicla-system: Change order of setup instructions. --- .../NiclaSenseME_BatteryStatus/NiclaSenseME_BatteryStatus.ino | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/Nicla_System/examples/NiclaSenseME_BatteryStatus/NiclaSenseME_BatteryStatus.ino b/libraries/Nicla_System/examples/NiclaSenseME_BatteryStatus/NiclaSenseME_BatteryStatus.ino index 6ab8ad126..cfc414d3d 100644 --- a/libraries/Nicla_System/examples/NiclaSenseME_BatteryStatus/NiclaSenseME_BatteryStatus.ino +++ b/libraries/Nicla_System/examples/NiclaSenseME_BatteryStatus/NiclaSenseME_BatteryStatus.ino @@ -230,11 +230,11 @@ void setup() nicla::leds.begin(); // Start I2C connection nicla::setBatteryNTCEnabled(true); // Set to false if your battery doesn't have an NTC thermistor. - setupBLE(); + nicla::enableCharging(); nicla::leds.setColor(green); - nicla::enableCharging(); + setupBLE(); } void loop() From cea8fa2a352a3e14edde0d186e76984550375c30 Mon Sep 17 00:00:00 2001 From: Sebastian Romero Date: Thu, 4 May 2023 13:36:03 +0200 Subject: [PATCH 33/48] nicla-system: Add function for backwards compatibility. --- libraries/Nicla_System/src/Nicla_System.cpp | 5 +++++ libraries/Nicla_System/src/Nicla_System.h | 3 +++ 2 files changed, 8 insertions(+) diff --git a/libraries/Nicla_System/src/Nicla_System.cpp b/libraries/Nicla_System/src/Nicla_System.cpp index 7089cb9d4..6b0003a98 100644 --- a/libraries/Nicla_System/src/Nicla_System.cpp +++ b/libraries/Nicla_System/src/Nicla_System.cpp @@ -386,6 +386,11 @@ void nicla::synchronizeFastChargeSettings() } } +void nicla::checkChgReg(){ + synchronizeFastChargeSettings(); +} + + I2CLed LEDR(red); I2CLed LEDG(green); I2CLed LEDB(blue); diff --git a/libraries/Nicla_System/src/Nicla_System.h b/libraries/Nicla_System/src/Nicla_System.h index 440f4632e..19d4f776d 100644 --- a/libraries/Nicla_System/src/Nicla_System.h +++ b/libraries/Nicla_System/src/Nicla_System.h @@ -215,6 +215,9 @@ class nicla { */ static void synchronizeFastChargeSettings(); + [[deprecated("Use synchronizeFastChargeSettings() instead.")]] + static void checkChgReg(); + /** * A cached version of the fast charge settings for the PMIC. * This is used to reapply the settings if the register got wiped. From b16d5ea7fddf954dde5705859542457ea11996da Mon Sep 17 00:00:00 2001 From: Sebastian Romero Date: Fri, 5 May 2023 16:48:24 +0200 Subject: [PATCH 34/48] nicla-system: Give PMIC more time to wake up. --- libraries/Nicla_System/src/BQ25120A.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/Nicla_System/src/BQ25120A.cpp b/libraries/Nicla_System/src/BQ25120A.cpp index f6ee864c0..784970912 100644 --- a/libraries/Nicla_System/src/BQ25120A.cpp +++ b/libraries/Nicla_System/src/BQ25120A.cpp @@ -83,6 +83,6 @@ void BQ25120A::setHighImpedanceModeEnabled(bool enabled) { cd = 0; } else { cd = 1; - delayMicroseconds(64); // Give some time to the BQ25120A to wake up + delayMicroseconds(128); // Give some time to the BQ25120A to wake up } } \ No newline at end of file From b3d7f821c9c08e61b409f1a3f5c9c874b00577f1 Mon Sep 17 00:00:00 2001 From: Sebastian Romero Date: Fri, 5 May 2023 17:07:24 +0200 Subject: [PATCH 35/48] nicla-system: Add error messages to sketch. --- .../NiclaSenseME_BatteryStatus.ino | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/libraries/Nicla_System/examples/NiclaSenseME_BatteryStatus/NiclaSenseME_BatteryStatus.ino b/libraries/Nicla_System/examples/NiclaSenseME_BatteryStatus/NiclaSenseME_BatteryStatus.ino index cfc414d3d..752d2cff0 100644 --- a/libraries/Nicla_System/examples/NiclaSenseME_BatteryStatus/NiclaSenseME_BatteryStatus.ino +++ b/libraries/Nicla_System/examples/NiclaSenseME_BatteryStatus/NiclaSenseME_BatteryStatus.ino @@ -58,20 +58,23 @@ bool updateBatteryLevel() { updateTimestamp = millis(); auto percentage = nicla::getBatteryVoltagePercentage(); - if (percentage < 0) { - return false; // Percentage couldn't be determined. + if (percentage < 0) { + Serial.println("Battery voltage percentage couldn't be determined."); + return false; } // Only if the percentage has changed, we update the values as they depend on it. if (batteryPercentage != percentage) { int8_t currentChargeLevel = static_cast(nicla::getBatteryChargeLevel()); if(currentChargeLevel == 0){ - return false; // Unknown battery charge level. + Serial.println("Battery charge level couldn't be determined."); + return false; } auto currentVoltage = nicla::getCurrentBatteryVoltage(); if(currentVoltage == 0){ - return false; // Unknown battery voltage. + Serial.println("Battery voltage couldn't be determined."); + return false; } batteryPercentage = percentage; From f7845c26d84250e78adf127b7cdc0f81aade3967 Mon Sep 17 00:00:00 2001 From: Sebastian Romero Date: Tue, 9 May 2023 13:25:57 +0200 Subject: [PATCH 36/48] nicla-system: Fix incorrect battery voltage reading. --- libraries/Nicla_System/src/Nicla_System.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libraries/Nicla_System/src/Nicla_System.cpp b/libraries/Nicla_System/src/Nicla_System.cpp index 6b0003a98..083c4d3e0 100644 --- a/libraries/Nicla_System/src/Nicla_System.cpp +++ b/libraries/Nicla_System/src/Nicla_System.cpp @@ -292,8 +292,9 @@ int8_t nicla::getBatteryVoltagePercentage(bool useLatchedValue) { if(chargingEnabled) { disableCharging(); } - // Write 1 to VBMON_READ to trigger a new reading - _pmic.writeByte(BQ25120A_ADDRESS, BQ25120A_BATT_MON, 1); + // Write 1 to bit 7 VBMON_READ to trigger a new reading + _pmic.writeByte(BQ25120A_ADDRESS, BQ25120A_BATT_MON, 0b10000000); + delay(3); // According to datasheet, 2ms is enough, but we add 1ms for safety if(chargingEnabled) { From d47a6dbb166b94aa76b649e8ca08b2d1cc91fc7d Mon Sep 17 00:00:00 2001 From: Sebastian Romero Date: Tue, 9 May 2023 13:26:20 +0200 Subject: [PATCH 37/48] nicla-system: Add documentation about charging rate. --- libraries/Nicla_System/src/Nicla_System.h | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/libraries/Nicla_System/src/Nicla_System.h b/libraries/Nicla_System/src/Nicla_System.h index 19d4f776d..7e86d7a31 100644 --- a/libraries/Nicla_System/src/Nicla_System.h +++ b/libraries/Nicla_System/src/Nicla_System.h @@ -74,9 +74,13 @@ class nicla { static bool enterShipMode(); /** - * @brief Enables fast charging of the battery. + * @brief Enables fast charging of the battery. By default charging is already enabled when the board is powered. + * The default charging current without enabling fast charging is 10mA. Charging can be disabled by calling disableCharging(). * - * @param mA The desired milliampere (mA) charging current. Range: 5mA - 35mA and 40mA - 300mA. The default is 20mA. + * @param mA The desired milliampere (mA) charging current. Range: 5mA - 35mA and 40mA - 300mA. The default value is 20mA. + * A safe default charging current value that works for most common LiPo batteries is 0.5C, which means charging at a rate equal to half of the battery's capacity. + * For example, a 200mAh battery could be charged at 100mA (0.1A). + * This charging rate is generally safe for most LiPo batteries and provides a good balance between charging speed and battery longevity. * @param disableNtc Whether to disable Temperature Sense and interrupt on charge. The default is true. * @return true If the fast charging is enabled successfully. False, otherwise. */ From a0b13de946cf0236fa258b5530dfccb499acac99 Mon Sep 17 00:00:00 2001 From: Sebastian Romero Date: Tue, 9 May 2023 17:58:18 +0200 Subject: [PATCH 38/48] nicla-system: Remove NTC toggling from charge function. --- libraries/Nicla_System/src/Nicla_System.cpp | 27 ++++++++++++++------- libraries/Nicla_System/src/Nicla_System.h | 13 ++++++---- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/libraries/Nicla_System/src/Nicla_System.cpp b/libraries/Nicla_System/src/Nicla_System.cpp index 083c4d3e0..329fefb0d 100644 --- a/libraries/Nicla_System/src/Nicla_System.cpp +++ b/libraries/Nicla_System/src/Nicla_System.cpp @@ -100,7 +100,7 @@ bool nicla::enterShipMode() _pmic.writeByte(BQ25120A_ADDRESS, BQ25120A_STATUS, status_reg); } -bool nicla::enableCharging(uint16_t mA, bool disableNtc) +bool nicla::enableCharging(uint16_t mA) { /* The ICHRG is calculated using the following equation: @@ -140,13 +140,6 @@ bool nicla::enableCharging(uint16_t mA, bool disableNtc) // Also sets the input current limit to 350mA. _pmic.writeByte(BQ25120A_ADDRESS, BQ25120A_ILIM_UVLO_CTRL, 0x3F); - _ntcEnabled = !disableNtc; - if (!_ntcEnabled) { - // Disable Temperature Sense (B7 = 0) and interrupt on charge status change (B3 = 0). - // INT only shows faults and does not show charge status) - _pmic.writeByte(BQ25120A_ADDRESS, BQ25120A_TS_CONTROL, 0); - } - return _pmic.getFastChargeControlRegister() == _fastChargeRegisterData; } @@ -168,7 +161,23 @@ uint8_t nicla::getBatteryFaults() { } void nicla::setBatteryNTCEnabled(bool enabled){ - _ntcEnabled = enabled; + if (_ntcEnabled != enabled) { + _ntcEnabled = enabled; + + // Read the current TS_CONTROL register value + uint8_t tsControlRegister = _pmic.readByte(BQ25120A_ADDRESS, BQ25120A_TS_CONTROL); + + if (_ntcEnabled) { + // Set bit 7 and bit 3 to 1 to enable temperature sense and interrupt on charge status change. + tsControlRegister |= 0b10001000; + } else { + // Set bit 7 and bit 3 to 0 to disable temperature sense and interrupt on charge status change. + // INT only shows faults and does not show charge status. + tsControlRegister &= 0b01110111; + } + + _pmic.writeByte(BQ25120A_ADDRESS, BQ25120A_TS_CONTROL, tsControlRegister); + } } float nicla::getRegulatedBatteryVoltage(){ diff --git a/libraries/Nicla_System/src/Nicla_System.h b/libraries/Nicla_System/src/Nicla_System.h index 7e86d7a31..0f9b0ebb4 100644 --- a/libraries/Nicla_System/src/Nicla_System.h +++ b/libraries/Nicla_System/src/Nicla_System.h @@ -81,10 +81,11 @@ class nicla { * A safe default charging current value that works for most common LiPo batteries is 0.5C, which means charging at a rate equal to half of the battery's capacity. * For example, a 200mAh battery could be charged at 100mA (0.1A). * This charging rate is generally safe for most LiPo batteries and provides a good balance between charging speed and battery longevity. - * @param disableNtc Whether to disable Temperature Sense and interrupt on charge. The default is true. + * @note Make sure to call setBatteryNTCEnabled(false) if your battery does not have an NTC thermistor. + * Otherwise the charging will be disabled due to the NTC thermistor not being connected. * @return true If the fast charging is enabled successfully. False, otherwise. */ - static bool enableCharging(uint16_t mA = 20, bool disableNtc = true); + static bool enableCharging(uint16_t mA = 20); /** * @brief Disables charging of the battery. It can be resumed by calling enableCharging(). @@ -101,11 +102,12 @@ class nicla { static bool runsOnBattery(); /** - * @brief Enables or disables the negative temperature coefficient (NTC) thermistor. + * @brief Enables or disables the negative temperature coefficient (NTC) thermistor. It is enabled by default. * NTCs are used to prevent the batteries from being charged at temperatures that are too high or too low. * Set to disabled for standard LiPo batteries without NTC. * If your battery has only a plus and minus wire, it does not have an NTC. - * The default is enabled. + * @note Disabling the NTC will also disable the on-charge-state-change interrupt. + * @param enabled Whether to enabled the NTC. */ static void setBatteryNTCEnabled(bool enabled); @@ -186,7 +188,8 @@ class nicla { /** * @brief Get the current operating status of the PMIC. - * + * @note If it doesn't report 'Charging' even though you enabled charging with enableCharging(), + * you may need to disable the NTC thermistor with setBatteryNTCEnabled(false) in case your battery doesn't have one. * @return OperatingStatus One of the following: Ready, Charging, ChargingComplete, Error. */ static OperatingStatus getOperatingStatus(); From f5e67184123eba6a1f3206b235f5e1e8a1024386 Mon Sep 17 00:00:00 2001 From: Sebastian Romero Date: Wed, 10 May 2023 10:04:05 +0200 Subject: [PATCH 39/48] nicla-system: Add more documentation. --- libraries/Nicla_System/src/Nicla_System.cpp | 2 ++ libraries/Nicla_System/src/Nicla_System.h | 28 +++++++++++++++------ 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/libraries/Nicla_System/src/Nicla_System.cpp b/libraries/Nicla_System/src/Nicla_System.cpp index 329fefb0d..31c0db16b 100644 --- a/libraries/Nicla_System/src/Nicla_System.cpp +++ b/libraries/Nicla_System/src/Nicla_System.cpp @@ -14,6 +14,8 @@ BQ25120A nicla::_pmic; rtos::Mutex nicla::_i2c_mutex; bool nicla::started = false; uint8_t nicla::_fastChargeRegisterData = 0; + +/// Enabled is the default value also represented in the TS Control Register (Bit 7 = 1). bool nicla::_ntcEnabled = true; void nicla::pingI2C() { diff --git a/libraries/Nicla_System/src/Nicla_System.h b/libraries/Nicla_System/src/Nicla_System.h index 0f9b0ebb4..e5e61d6b4 100644 --- a/libraries/Nicla_System/src/Nicla_System.h +++ b/libraries/Nicla_System/src/Nicla_System.h @@ -8,6 +8,8 @@ #include #include +// Deprecated. Whether or not to use a write operation by default +// can now be controlled with the default paramter of pingI2C(). #define USE_FASTCHG_TO_KICK_WATCHDOG 1 enum class OperatingStatus { @@ -38,6 +40,15 @@ enum class BatteryTemperature { class nicla { public: + + /** + * @brief Initializes the Nicla Sense ME board. + * + * @param mountedOnMkr Defines whether the board is mounted as a shield on a MKR board or not. + * This is used to drive the pin high that connects to the reset pin + * of the MKR board to prevent it from resetting. + * @return true if the board is initialized successfully. + */ static bool begin(bool mountedOnMkr = false); /** @@ -81,9 +92,10 @@ class nicla { * A safe default charging current value that works for most common LiPo batteries is 0.5C, which means charging at a rate equal to half of the battery's capacity. * For example, a 200mAh battery could be charged at 100mA (0.1A). * This charging rate is generally safe for most LiPo batteries and provides a good balance between charging speed and battery longevity. - * @note Make sure to call setBatteryNTCEnabled(false) if your battery does not have an NTC thermistor. - * Otherwise the charging will be disabled due to the NTC thermistor not being connected. + * @note If your battery doesn't have an NTC thermistor, the charging speed will be limited to ~35mA. + * @note There is a saftey timer that will stop the charging after 9 hours. * @return true If the fast charging is enabled successfully. False, otherwise. + * @see disableCharging() */ static bool enableCharging(uint16_t mA = 20); @@ -159,13 +171,13 @@ class nicla { static BatteryChargeLevel getBatteryChargeLevel(); /** - * @brief Get the Battery Temperature. The following values are possible: - * "Normal", "Extreme", "Cool", "Warm". + * @brief Get the Battery Temperature using the negative temperature coefficient (NTC) thermistor. + * The following values are possible: "Normal", "Extreme", "Cool", "Warm". * When the battery is cool, the charging current is reduced by half. * When the battery is warm, the charging current is reduced by 140 mV. * When the battery is unter an extreme temperature (hot or cold), the charging is suspended. - * @note If the battery doesn't have a negative temperature coefficient (NTC) thermistor, the temperature is always "Normal". - * This is not determined automatically and needs to be set using the setBatteryNTCEnabled() function. + * @note If the battery isn't configured to have a NTC, the temperature is reported as "Normal". + * The presence of the NTC is not determined automatically and needs to be set using the setBatteryNTCEnabled() function. * @see setBatteryNTCEnabled() * @return BatteryTemperature The battery temperature represented by one of the following constants: * BatteryTemperature::Normal, BatteryTemperature::Extreme, BatteryTemperature::Cool, BatteryTemperature::Warm @@ -188,12 +200,12 @@ class nicla { /** * @brief Get the current operating status of the PMIC. - * @note If it doesn't report 'Charging' even though you enabled charging with enableCharging(), - * you may need to disable the NTC thermistor with setBatteryNTCEnabled(false) in case your battery doesn't have one. + * @note If it doesn't report 'Charging' even though you enabled charging with enableCharging(), the battery might be full. * @return OperatingStatus One of the following: Ready, Charging, ChargingComplete, Error. */ static OperatingStatus getOperatingStatus(); + /// Provides access to the IS31FL3194 LED driver that drives the RGB LED. static RGBled leds; static BQ25120A _pmic; From d2271c1164a6a69f9b99e9bca0ff7f3b8f9be83c Mon Sep 17 00:00:00 2001 From: Sebastian Romero Date: Wed, 10 May 2023 11:46:26 +0200 Subject: [PATCH 40/48] nicla-system: Simplify the watchdog reset machanism. --- libraries/Nicla_System/src/Nicla_System.cpp | 31 +++++++++++---------- libraries/Nicla_System/src/Nicla_System.h | 14 ++++------ 2 files changed, 21 insertions(+), 24 deletions(-) diff --git a/libraries/Nicla_System/src/Nicla_System.cpp b/libraries/Nicla_System/src/Nicla_System.cpp index 31c0db16b..ec617c0bc 100644 --- a/libraries/Nicla_System/src/Nicla_System.cpp +++ b/libraries/Nicla_System/src/Nicla_System.cpp @@ -18,11 +18,13 @@ uint8_t nicla::_fastChargeRegisterData = 0; /// Enabled is the default value also represented in the TS Control Register (Bit 7 = 1). bool nicla::_ntcEnabled = true; -void nicla::pingI2C() { - while(1) { - // already protected by a mutex on Wire operations - synchronizeFastChargeSettings(); - delay(10000); +void nicla::pingI2C(bool useWriteOperation) { + // PMIC commands already protected by a mutex on Wire operations. + if(useWriteOperation){ + // Write the current charging settings to the register to reset the watchdog timer. + _pmic.writeByte(BQ25120A_ADDRESS, BQ25120A_FAST_CHG, _fastChargeRegisterData); + } else { + _pmic.getStatusRegister(); } } @@ -36,9 +38,16 @@ bool nicla::begin(bool mountedOnMkr) } Wire1.begin(); _fastChargeRegisterData = _pmic.getFastChargeControlRegister(); + #ifndef NO_NEED_FOR_WATCHDOG_THREAD + // If not using the BHY2 library, we need to start a thread to ping the PMIC every 10 seconds. static rtos::Thread th(osPriorityHigh, 768, nullptr, "ping_thread"); - th.start(&nicla::pingI2C); + th.start([]() { + while(1) { + pingI2C(); + delay(10000); + } + }); #endif started = true; @@ -390,16 +399,8 @@ OperatingStatus nicla::getOperatingStatus() { return static_cast(status); } - -void nicla::synchronizeFastChargeSettings() -{ - if (_fastChargeRegisterData != _pmic.getFastChargeControlRegister()) { - _pmic.writeByte(BQ25120A_ADDRESS, BQ25120A_FAST_CHG, _fastChargeRegisterData); - } -} - void nicla::checkChgReg(){ - synchronizeFastChargeSettings(); + pingI2C(); } diff --git a/libraries/Nicla_System/src/Nicla_System.h b/libraries/Nicla_System/src/Nicla_System.h index e5e61d6b4..61cfdc7ca 100644 --- a/libraries/Nicla_System/src/Nicla_System.h +++ b/libraries/Nicla_System/src/Nicla_System.h @@ -225,21 +225,17 @@ class nicla { * @brief Pings the I2C interface by querying the PMIC's fast charge register every 10 seconds. * This is invoked by a thread and is meant to kick the watchdog timer to prevent the PMIC from entering a low power state. * The I2C interface reset timer for the host is 50 seconds. + * @param useWriteOperation If true, a write operation to a register is performed to reset the watchdog timer. + * If false, a read operation is performed. The default is false. */ - static void pingI2C(); + static void pingI2C(bool useWriteOperation = false); - /** - * @brief Synchronizes the fast charge settings with the PMIC. - * This ensures that the fast charge settings as specified via enableCharge() are applied again the register got wiped. - */ - static void synchronizeFastChargeSettings(); - - [[deprecated("Use synchronizeFastChargeSettings() instead.")]] + [[deprecated("Use pingI2C() instead.")]] static void checkChgReg(); /** * A cached version of the fast charge settings for the PMIC. - * This is used to reapply the settings if the register got wiped. + * This is used to avoid unnecessary I2C communication. **/ static uint8_t _fastChargeRegisterData; From 14117b6f4752aec009ff30c576819b01f11025c7 Mon Sep 17 00:00:00 2001 From: Sebastian Romero Date: Wed, 10 May 2023 14:54:32 +0200 Subject: [PATCH 41/48] nicla-system: Remove unused code. --- libraries/Nicla_System/src/Nicla_System.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/libraries/Nicla_System/src/Nicla_System.cpp b/libraries/Nicla_System/src/Nicla_System.cpp index ec617c0bc..97d8d6bc3 100644 --- a/libraries/Nicla_System/src/Nicla_System.cpp +++ b/libraries/Nicla_System/src/Nicla_System.cpp @@ -32,7 +32,6 @@ bool nicla::begin(bool mountedOnMkr) { if (mountedOnMkr) { // GPIO3 is on MKR RESET pin, so we must configure it HIGH or it will, well, reset the board :) - pinMode(p25, OUTPUT); pinMode(P0_10, OUTPUT); digitalWrite(P0_10, HIGH); } From d399ee3bb66ebd42bc381729e27fd6f786a1caba Mon Sep 17 00:00:00 2001 From: Sebastian Romero Date: Wed, 10 May 2023 17:42:24 +0200 Subject: [PATCH 42/48] nicla-system: Increase safety charge time from 3 to 9 hours. --- libraries/Nicla_System/src/Nicla_System.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/libraries/Nicla_System/src/Nicla_System.cpp b/libraries/Nicla_System/src/Nicla_System.cpp index 97d8d6bc3..82ae8983d 100644 --- a/libraries/Nicla_System/src/Nicla_System.cpp +++ b/libraries/Nicla_System/src/Nicla_System.cpp @@ -150,6 +150,13 @@ bool nicla::enableCharging(uint16_t mA) // Also sets the input current limit to 350mA. _pmic.writeByte(BQ25120A_ADDRESS, BQ25120A_ILIM_UVLO_CTRL, 0x3F); + // Set safety timer to 9 hours (Bit 1+2 0b10) to give the battery enough of time to charge. + // Set it to 0b11 to disable safety timers. See: Table 24 in the datasheet. + uint8_t dpmTimerRegisterData = _pmic.readByte(BQ25120A_ADDRESS, BQ25120A_VIN_DPM); + dpmTimerRegisterData |= 0b00000100; // Set Bit 2 to 1 + dpmTimerRegisterData &= 0b11111101; // Set Bit 1 to 0 + _pmic.writeByte(BQ25120A_ADDRESS, BQ25120A_VIN_DPM, dpmTimerRegisterData); + return _pmic.getFastChargeControlRegister() == _fastChargeRegisterData; } From 9bce6e86de50cc6d8ff52f791337c4e8683632e4 Mon Sep 17 00:00:00 2001 From: Sebastian Romero Date: Thu, 11 May 2023 11:26:39 +0200 Subject: [PATCH 43/48] nicla-system: Add more documentation. --- .../NiclaSenseME_BatteryStatus.ino | 25 ++++++++++++++++++- .../Nicla_System/extras/BatteryMonitor/app.js | 15 +++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/libraries/Nicla_System/examples/NiclaSenseME_BatteryStatus/NiclaSenseME_BatteryStatus.ino b/libraries/Nicla_System/examples/NiclaSenseME_BatteryStatus/NiclaSenseME_BatteryStatus.ino index 752d2cff0..57b2cf8e9 100644 --- a/libraries/Nicla_System/examples/NiclaSenseME_BatteryStatus/NiclaSenseME_BatteryStatus.ino +++ b/libraries/Nicla_System/examples/NiclaSenseME_BatteryStatus/NiclaSenseME_BatteryStatus.ino @@ -1,3 +1,24 @@ +/* + * This example shows how to use the Nicla Sense ME library to read the battery status and send it over BLE. + * + * When not connected over BLE, the battery status is printed over the serial port every 4 seconds. + * When connected over BLE, the battery status is checked every 4 seconds and sent over BLE if a value has changed. + * That is, when using the notification mechanism in the BatteryMonitor web app, which is the default. + * The BatteryMonitor web app can also be configured to poll the battery status every X seconds. + * + * The LED colors are used to indicate the BLE connection status: + * - Green: Board is ready but no BLE connection has been established yet. + * - Blue: Board is connected over BLE to a device that wants to read the battery status. + * - Red: A device that was previously connected over BLE has disconnected. + * + * Instructions: + * 1. Upload this sketch to your Nicla Sense ME board. + * 2. Open the BatteryMonitor web app (index.html) in a browser that supports Web Bluetooth (Chrome, Edge, Opera, etc.). + * 3. Connect to your Nicla Sense ME board by clicking the "Connect" button. + * + * Initial author: Sebastian Romero @sebromero + */ + #include "Nicla_System.h" #include @@ -242,8 +263,10 @@ void setup() void loop() { - //BLE.poll(); // Implicit when calling BLE.connected(). Uncomment when only using BLERead + //BLE.poll(); // Implicit when calling BLE.connected(). Uncomment when only using BLERead (polling mechanism) + // Check if a BLE device is connected and handle battery updates + // via the notification mechanism. if (BLE.connected()) { bool newBatteryLevelAvailable = updateBatteryLevel(); bool newBatteryStatusAvailable = updateBatteryStatus(); diff --git a/libraries/Nicla_System/extras/BatteryMonitor/app.js b/libraries/Nicla_System/extras/BatteryMonitor/app.js index 083b070b0..8baf51923 100644 --- a/libraries/Nicla_System/extras/BatteryMonitor/app.js +++ b/libraries/Nicla_System/extras/BatteryMonitor/app.js @@ -1,3 +1,18 @@ +/** + * This is a simple example of a web app that connects to an Arduino board + * and reads the battery level and other battery related characteristics. + * + * It uses the Web Bluetooth API to connect to the Arduino board. + * + * Instructions: + * 1. Upload the NiclaSenseME_BatteryStatus sketch to the Arduino board. + * 2. Open the index.html file in a browser that supports the Web Bluetooth API (Chrome, Edge, Opera). + * 3. Click on the Connect button to connect to the Arduino board. + * + * Initial author: Sebastian Romero @sebromero + */ + + /// UI elements const connectButton = document.getElementById('connect'); const batteryLevelElement = document.getElementById('battery-level'); From c316d0621ca7a963561c9aed1831e8a3bd399517 Mon Sep 17 00:00:00 2001 From: Sebastian Romero Date: Thu, 11 May 2023 11:46:20 +0200 Subject: [PATCH 44/48] nicla-system: Add sketch that shows how to charge the battery. --- .../NiclaSenseME_BatteryChargingSimple.ino | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 libraries/Nicla_System/examples/NiclaSenseME_BatteryChargingSimple/NiclaSenseME_BatteryChargingSimple.ino diff --git a/libraries/Nicla_System/examples/NiclaSenseME_BatteryChargingSimple/NiclaSenseME_BatteryChargingSimple.ino b/libraries/Nicla_System/examples/NiclaSenseME_BatteryChargingSimple/NiclaSenseME_BatteryChargingSimple.ino new file mode 100644 index 000000000..f3cd9b2dd --- /dev/null +++ b/libraries/Nicla_System/examples/NiclaSenseME_BatteryChargingSimple/NiclaSenseME_BatteryChargingSimple.ino @@ -0,0 +1,79 @@ +/* + * This example shows how to use the Nicla Sense ME library to charge a battery. + * + * The LED colors will change depending on the battery's operating status: + * - Blue: Ready + * - Yellow: Charging + * - Green: Charging complete + * - Red: Error + * + * Instructions: + * 1. Connect a battery to the board. + * 2. Configure the charge current in the setup() function. + * 3. Upload this sketch to your Nicla Sense ME board. + * + * Initial author: Sebastian Romero @sebromero + */ + +#include "Nicla_System.h" + +constexpr auto printInterval { 10000ul }; +float voltage = -1.0f; + +void setup(){ + Serial.begin(115200); + for (const auto timeout = millis() + 2500; millis() < timeout && !Serial; delay(250)); + nicla::begin(); // Initialise library + nicla::leds.begin(); // Start I2C connection to LED driver + nicla::setBatteryNTCEnabled(true); // Set to false if your battery doesn't have a NTC. + + /* + A safe default charging current value that works for most common LiPo batteries is 0.5C, + which means charging at a rate equal to half of the battery's capacity. + For example, a 200mAh battery could be charged at 100mA (0.1A). + */ + nicla::enableCharging(100); + nicla::leds.setColor(blue); +} + +void loop(){ + + static auto updateTimestamp = millis(); + + if (millis() - updateTimestamp >= printInterval) { + updateTimestamp = millis(); + + float currentVoltage = nicla::getCurrentBatteryVoltage(); + if(currentVoltage != voltage){ + voltage = currentVoltage; + Serial.print("\nVoltage: "); + Serial.println(voltage); + } else { + Serial.print("."); + } + + auto operatingStatus = nicla::getOperatingStatus(); + + switch(operatingStatus) { + case OperatingStatus::Charging: + nicla::leds.setColor(255,100,0); // Yellow + break; + case OperatingStatus::ChargingComplete: + nicla::leds.setColor(green); + + // This will stop further charging until enableCharging() is called again. + nicla::disableCharging(); + break; + case OperatingStatus::Error: + nicla::leds.setColor(red); + break; + case OperatingStatus::Ready: + nicla::leds.setColor(blue); + break; + default: + nicla::leds.setColor(off); + break; + } + + } +} From 21a7c564bd85d2bc03bd5d4c43ec2cf62ea8e4c9 Mon Sep 17 00:00:00 2001 From: Sebastian Romero Date: Thu, 11 May 2023 14:25:38 +0200 Subject: [PATCH 45/48] nicla-system: Add new measurement of charge current to docs. --- libraries/Nicla_System/src/Nicla_System.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/Nicla_System/src/Nicla_System.h b/libraries/Nicla_System/src/Nicla_System.h index 61cfdc7ca..90bd314e7 100644 --- a/libraries/Nicla_System/src/Nicla_System.h +++ b/libraries/Nicla_System/src/Nicla_System.h @@ -92,7 +92,7 @@ class nicla { * A safe default charging current value that works for most common LiPo batteries is 0.5C, which means charging at a rate equal to half of the battery's capacity. * For example, a 200mAh battery could be charged at 100mA (0.1A). * This charging rate is generally safe for most LiPo batteries and provides a good balance between charging speed and battery longevity. - * @note If your battery doesn't have an NTC thermistor, the charging speed will be limited to ~35mA. + * @note If your battery doesn't have an NTC thermistor, the charging speed will be limited to ~16mA. * @note There is a saftey timer that will stop the charging after 9 hours. * @return true If the fast charging is enabled successfully. False, otherwise. * @see disableCharging() From e51a02188354e674a663b479fd4cdb424c387180 Mon Sep 17 00:00:00 2001 From: Sebastian Romero Date: Thu, 11 May 2023 15:11:11 +0200 Subject: [PATCH 46/48] nicla-system: Add option to configure safety timer. --- libraries/Nicla_System/src/Nicla_System.cpp | 20 ++++++++++++++------ libraries/Nicla_System/src/Nicla_System.h | 21 ++++++++++++++++++++- 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/libraries/Nicla_System/src/Nicla_System.cpp b/libraries/Nicla_System/src/Nicla_System.cpp index 82ae8983d..36cd65d7b 100644 --- a/libraries/Nicla_System/src/Nicla_System.cpp +++ b/libraries/Nicla_System/src/Nicla_System.cpp @@ -150,14 +150,22 @@ bool nicla::enableCharging(uint16_t mA) // Also sets the input current limit to 350mA. _pmic.writeByte(BQ25120A_ADDRESS, BQ25120A_ILIM_UVLO_CTRL, 0x3F); - // Set safety timer to 9 hours (Bit 1+2 0b10) to give the battery enough of time to charge. - // Set it to 0b11 to disable safety timers. See: Table 24 in the datasheet. - uint8_t dpmTimerRegisterData = _pmic.readByte(BQ25120A_ADDRESS, BQ25120A_VIN_DPM); - dpmTimerRegisterData |= 0b00000100; // Set Bit 2 to 1 - dpmTimerRegisterData &= 0b11111101; // Set Bit 1 to 0 + configureChargingSafetyTimer(ChargingSafetyTimerOption::NineHours); + + return _pmic.getFastChargeControlRegister() == _fastChargeRegisterData; +} + +bool nicla::configureChargingSafetyTimer(ChargingSafetyTimerOption option){ + // See: Table 24 in the datasheet. + // The two bits need to be shifted to skip the unused LSB. + uint8_t timerValue = static_cast(option) << 1; + uint8_t dpmTimerRegisterData = _pmic.readByte(BQ25120A_ADDRESS, BQ25120A_VIN_DPM); + + dpmTimerRegisterData &= 0b11111001; // Clear bits 1 and 2 + dpmTimerRegisterData |= timerValue; // Update bits 1 and 2 _pmic.writeByte(BQ25120A_ADDRESS, BQ25120A_VIN_DPM, dpmTimerRegisterData); - return _pmic.getFastChargeControlRegister() == _fastChargeRegisterData; + return _pmic.readByte(BQ25120A_ADDRESS, BQ25120A_VIN_DPM) == dpmTimerRegisterData; } bool nicla::disableCharging() diff --git a/libraries/Nicla_System/src/Nicla_System.h b/libraries/Nicla_System/src/Nicla_System.h index 90bd314e7..3054282f8 100644 --- a/libraries/Nicla_System/src/Nicla_System.h +++ b/libraries/Nicla_System/src/Nicla_System.h @@ -37,6 +37,14 @@ enum class BatteryTemperature { Warm = 0b11 }; +enum class ChargingSafetyTimerOption { + ThirtyMinutes = 0b00, + ThreeHours = 0b01, + NineHours = 0b10, + Disabled = 0b11 +}; + + class nicla { public: @@ -93,12 +101,23 @@ class nicla { * For example, a 200mAh battery could be charged at 100mA (0.1A). * This charging rate is generally safe for most LiPo batteries and provides a good balance between charging speed and battery longevity. * @note If your battery doesn't have an NTC thermistor, the charging speed will be limited to ~16mA. - * @note There is a saftey timer that will stop the charging after 9 hours. + * @note There is a saftey timer that will stop the charging after 9 hours by default. + * This can be configured by calling configureChargingSafetyTimer(). * @return true If the fast charging is enabled successfully. False, otherwise. * @see disableCharging() */ static bool enableCharging(uint16_t mA = 20); + /** + * @brief Configures the charging safety timer after which the charging is stopped. + * This is useful to prevent overcharging the battery. The timer can have one of the following options: + * 30 minutes, 3 hours, 9 hours or disabled. + * + * @param option One of the following options: ThirtyMinutes, ThreeHours, NineHours or Disabled. + * @return true if the charging safety timer is configured successfully, false otherwise. + */ + static bool configureChargingSafetyTimer(ChargingSafetyTimerOption option); + /** * @brief Disables charging of the battery. It can be resumed by calling enableCharging(). * From 5fff648c46d7c0cd3e80e9f7d1bc7e2b93cb7108 Mon Sep 17 00:00:00 2001 From: Sebastian Romero Date: Thu, 11 May 2023 15:23:39 +0200 Subject: [PATCH 47/48] nicla-system: Make the safety timer configuration explicit. --- .../NiclaSenseME_BatteryChargingSimple.ino | 11 +++++++++++ libraries/Nicla_System/src/Nicla_System.cpp | 2 -- libraries/Nicla_System/src/Nicla_System.h | 2 +- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/libraries/Nicla_System/examples/NiclaSenseME_BatteryChargingSimple/NiclaSenseME_BatteryChargingSimple.ino b/libraries/Nicla_System/examples/NiclaSenseME_BatteryChargingSimple/NiclaSenseME_BatteryChargingSimple.ino index f3cd9b2dd..3932b3abd 100644 --- a/libraries/Nicla_System/examples/NiclaSenseME_BatteryChargingSimple/NiclaSenseME_BatteryChargingSimple.ino +++ b/libraries/Nicla_System/examples/NiclaSenseME_BatteryChargingSimple/NiclaSenseME_BatteryChargingSimple.ino @@ -27,12 +27,23 @@ void setup(){ nicla::leds.begin(); // Start I2C connection to LED driver nicla::setBatteryNTCEnabled(true); // Set to false if your battery doesn't have a NTC. + /* + Set the maximum charging time to 9 hours. This helps to prevent overcharging. + Set this to a lower value (e.g. 3h) if your battery will be done with charging sooner. + To get an estimation of the charging time, you can use the following formula: + Charging time (in hours) = (Battery capacity in mAh) / (0.8 * Charging current in mA) + This formula takes into account that the charging process is approximately 80% efficient (hence the 0.8 factor). + This is just a rough estimate, and actual charging time may vary depending on factors like the charger, battery quality, and charging conditions. + */ + nicla::configureChargingSafetyTimer(ChargingSafetyTimerOption::NineHours); + /* A safe default charging current value that works for most common LiPo batteries is 0.5C, which means charging at a rate equal to half of the battery's capacity. For example, a 200mAh battery could be charged at 100mA (0.1A). */ nicla::enableCharging(100); + nicla::leds.setColor(blue); } diff --git a/libraries/Nicla_System/src/Nicla_System.cpp b/libraries/Nicla_System/src/Nicla_System.cpp index 36cd65d7b..e9fc4cb41 100644 --- a/libraries/Nicla_System/src/Nicla_System.cpp +++ b/libraries/Nicla_System/src/Nicla_System.cpp @@ -150,8 +150,6 @@ bool nicla::enableCharging(uint16_t mA) // Also sets the input current limit to 350mA. _pmic.writeByte(BQ25120A_ADDRESS, BQ25120A_ILIM_UVLO_CTRL, 0x3F); - configureChargingSafetyTimer(ChargingSafetyTimerOption::NineHours); - return _pmic.getFastChargeControlRegister() == _fastChargeRegisterData; } diff --git a/libraries/Nicla_System/src/Nicla_System.h b/libraries/Nicla_System/src/Nicla_System.h index 3054282f8..176851cd1 100644 --- a/libraries/Nicla_System/src/Nicla_System.h +++ b/libraries/Nicla_System/src/Nicla_System.h @@ -101,7 +101,7 @@ class nicla { * For example, a 200mAh battery could be charged at 100mA (0.1A). * This charging rate is generally safe for most LiPo batteries and provides a good balance between charging speed and battery longevity. * @note If your battery doesn't have an NTC thermistor, the charging speed will be limited to ~16mA. - * @note There is a saftey timer that will stop the charging after 9 hours by default. + * @note There is a saftey timer that will stop the charging after 3 hours by default. * This can be configured by calling configureChargingSafetyTimer(). * @return true If the fast charging is enabled successfully. False, otherwise. * @see disableCharging() From 1ca6fc33a999dd387ab58eab2fe43423a5a315a1 Mon Sep 17 00:00:00 2001 From: Sebastian Romero Date: Fri, 12 May 2023 09:53:28 +0200 Subject: [PATCH 48/48] nicla-system: Apply suggestions from Ali's code review. Co-authored-by: Ali Jahangiri <75624145+aliphys@users.noreply.github.com> --- .../NiclaSenseME_BatteryChargingSimple.ino | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/Nicla_System/examples/NiclaSenseME_BatteryChargingSimple/NiclaSenseME_BatteryChargingSimple.ino b/libraries/Nicla_System/examples/NiclaSenseME_BatteryChargingSimple/NiclaSenseME_BatteryChargingSimple.ino index 3932b3abd..10e4cabfc 100644 --- a/libraries/Nicla_System/examples/NiclaSenseME_BatteryChargingSimple/NiclaSenseME_BatteryChargingSimple.ino +++ b/libraries/Nicla_System/examples/NiclaSenseME_BatteryChargingSimple/NiclaSenseME_BatteryChargingSimple.ino @@ -1,14 +1,14 @@ /* * This example shows how to use the Nicla Sense ME library to charge a battery. * - * The LED colors will change depending on the battery's operating status: + * The LED color will change depending on the battery's operating status: * - Blue: Ready * - Yellow: Charging * - Green: Charging complete * - Red: Error * * Instructions: - * 1. Connect a battery to the board. + * 1. Connect a single cell (3.7V nominal) LiPo/Li-Ion battery to the board via battery connector (J4) or the pin header (J3). * 2. Configure the charge current in the setup() function. * 3. Upload this sketch to your Nicla Sense ME board. *