#include "battery.h" #ifdef FEATURE_BATTERY_MONITOR #include extern BLEBas blebas; // Battery ADC is kept permanently configured at 12-bit / AR_INTERNAL_3_0 to avoid // the SAADC re-init cost of analogReference() changes, which blocks the CPU for // several ms and causes BLE connection-interval violations (visible mouse freeze). // PIN_VBAT_ENABLE is held LOW permanently once battery monitoring starts. void initBatteryADC() { pinMode(PIN_VBAT_ENABLE, OUTPUT); digitalWrite(PIN_VBAT_ENABLE, LOW); pinMode(PIN_VBAT_READ, INPUT); analogReference(AR_INTERNAL_3_0); analogReadResolution(12); // Warm up with a few reads (no delay - just discard results) for (int i=0; i<8; i++) analogRead(PIN_VBAT_READ); } float readBatteryVoltage() { // 8 quick reads, no delay() calls, no analogReference() change int32_t raw=0; for (int i=0; i<8; i++) raw += analogRead(PIN_VBAT_READ); raw /= 8; // Seeed XIAO nRF52840 Sense: 1MΩ + 510kΩ voltage divider on VBAT. // Theoretical ratio is 1510/510 = 2.961, but real resistor tolerances // shift the actual ratio. Calibrated: 3.90V actual / 3.78V reported → ×1.0317. return (raw / 4096.0f) * 3.0f * (1510.0f / 510.0f) * 1.0317f; } int batteryPercent(float v) { return (int)constrain((v - BATT_EMPTY) / (BATT_FULL - BATT_EMPTY) * 100.f, 0, 100); } void updateBattery() { float v = readBatteryVoltage(); int pct = batteryPercent(v); bool chg = (digitalRead(PIN_CHG) == LOW); ChargeStatus status = chg ? (pct >= 99 ? CHGSTAT_FULL : CHGSTAT_CHARGING) : CHGSTAT_DISCHARGING; // Only write BLE Battery Service when connected - blebas.write() blocks on the // SoftDevice ATT layer and causes 30-40ms loop stalls when called during advertising. if (Bluefruit.connected()) blebas.write(pct); lastChargeStatus = status; #ifdef FEATURE_TELEMETRY telem.chargeStatus = (uint8_t)status; #endif const char* st[] = {"discharging","charging","full"}; Serial.print("[BATT] "); Serial.print(v,2); Serial.print("V "); Serial.print(pct); Serial.print("% "); Serial.println(st[status]); // Critical battery alert - only blink when not connected to avoid blocking BLE scheduler. // 6 × 160ms = 960ms hard block; skip during active connection. if (status == CHGSTAT_DISCHARGING && v < BATT_CRITICAL && !Bluefruit.connected()) for (int i=0; i<6; i++) { digitalWrite(LED_RED,LOW); delay(80); digitalWrite(LED_RED,HIGH); delay(80); } } #endif // FEATURE_BATTERY_MONITOR