onlyn00bs-badge

OnlyN00bs: a DEF CON 34 friend-finder badge. ESP32 firmware, Web Bluetooth setup app, printable case
git clone https://git.virtualshack.io/onlyn00bs-badge.git
Log | Files | Refs | README | LICENSE

battery.cpp (2600B)


      1 // battery.cpp — LiPo fuel gauge implementation. See battery.h.
      2 //
      3 #include <Arduino.h>
      4 #include "battery.h"
      5 #include "config.h"
      6 
      7 static uint16_t g_mv = 0;          // smoothed battery millivolts (0 = not yet sampled)
      8 static uint32_t g_last_sample_ms = 0;
      9 
     10 // Burst-read VBAT → divider-corrected millivolts. One discarded priming read lets
     11 // the ADC sample/hold cap settle (it can't fully charge through the ~500kΩ onboard
     12 // divider in a single conversion), then average VBAT_BURST_SAMPLES reads to beat
     13 // down the per-read noise + low-bias. Pin millivolts are averaged BEFORE the
     14 // ×VBAT_DIVIDER scale so the rounding happens at full resolution.
     15 static uint16_t read_vbat_mv() {
     16   (void)analogReadMilliVolts(PIN_VBAT);                 // priming read (settle the S/H cap)
     17   uint32_t sum = 0;
     18   for (int i = 0; i < VBAT_BURST_SAMPLES; i++)
     19     sum += analogReadMilliVolts(PIN_VBAT);
     20   return (uint16_t)((sum / VBAT_BURST_SAMPLES) * VBAT_DIVIDER);
     21 }
     22 
     23 void battery_init() {
     24   // GPIO34 is input-only; analogReadMilliVolts handles attenuation/calibration.
     25   // 11dB attenuation lets the ~0..2.1V at the pin (half of 4.2V) read cleanly.
     26   //
     27   // Arduino-ESP32 core 3.x's ADC oneshot driver wants the pin attached as an
     28   // analog channel BEFORE attenuation is configured, else it logs
     29   //   [E] __analogChannelConfig(): Pin is not configured as analog channel
     30   // A throwaway analogRead() attaches the channel first; harmless ordering on
     31   // core 2.x, which configures the pin lazily either way.
     32   analogRead(PIN_VBAT);
     33   analogSetPinAttenuation(PIN_VBAT, ADC_11db);
     34   // Seed the EMA with one immediate (burst-averaged) reading so the first frame
     35   // isn't 0% — and so the gauge opens near the true voltage, not a low single read.
     36   g_mv = read_vbat_mv();
     37   g_last_sample_ms = 0;
     38 }
     39 
     40 void battery_sample(uint32_t now) {
     41   if (g_last_sample_ms != 0 && now - g_last_sample_ms < VBAT_SAMPLE_MS) return;
     42   g_last_sample_ms = now;
     43 
     44   uint16_t raw = read_vbat_mv();
     45   if (g_mv == 0) {
     46     g_mv = raw;                    // first real sample
     47   } else {
     48     // EMA: g = g + alpha*(raw - g), alpha = NUM/DEN, in integer math.
     49     int32_t delta = (int32_t)raw - (int32_t)g_mv;
     50     g_mv = (uint16_t)((int32_t)g_mv + (delta * VBAT_EMA_NUM) / VBAT_EMA_DEN);
     51   }
     52 }
     53 
     54 uint16_t battery_mv() {
     55   return g_mv;
     56 }
     57 
     58 uint8_t battery_pct() {
     59   if (g_mv <= VBAT_EMPTY_MV) return 0;
     60   if (g_mv >= VBAT_FULL_MV) return 100;
     61   uint32_t span = VBAT_FULL_MV - VBAT_EMPTY_MV;
     62   return (uint8_t)(((uint32_t)(g_mv - VBAT_EMPTY_MV) * 100) / span);
     63 }
     64 
     65 bool battery_is_low() {
     66   return g_mv != 0 && g_mv <= VBAT_LOW_MV;
     67 }