From 1ecda1a8f5e108ee58040df009504aef87bbf2a0 Mon Sep 17 00:00:00 2001 From: pelgraine <140762863+pelgraine@users.noreply.github.com> Date: Sun, 1 Mar 2026 23:49:02 +1100 Subject: [PATCH] fix qmax entries so fcc is limited to 2000mah and not 3000mah --- variants/lilygo_tdeck_pro/TDeckBoard.cpp | 183 +++++++++++++++++------ 1 file changed, 138 insertions(+), 45 deletions(-) diff --git a/variants/lilygo_tdeck_pro/TDeckBoard.cpp b/variants/lilygo_tdeck_pro/TDeckBoard.cpp index 97da0c3..c635171 100644 --- a/variants/lilygo_tdeck_pro/TDeckBoard.cpp +++ b/variants/lilygo_tdeck_pro/TDeckBoard.cpp @@ -180,7 +180,7 @@ static bool bq27220_writeControl(uint16_t subcmd) { #endif // ---- BQ27220 Design Capacity configuration ---- -// The BQ27220 ships with a 3000 mAh default. The T-Deck Pro uses a 1400 mAh +// The BQ27220 ships with a 3000 mAh default. The T-Deck Pro uses a 2000 mAh // cell. This function checks on boot and writes the correct value via the // MAC Data Memory interface if needed. The value persists in battery-backed // RAM, so this typically only writes once (or after a full battery disconnect). @@ -197,29 +197,23 @@ bool TDeckBoard::configureFuelGauge(uint16_t designCapacity_mAh) { if (currentDC == designCapacity_mAh) { // Design Capacity correct, but check if Full Charge Capacity is sane. - // After a Design Capacity change, FCC may still hold the old factory - // value (e.g. 3000 mAh) until a RESET forces reinitialization. uint16_t fcc = bq27220_read16(BQ27220_REG_FULL_CAP); Serial.printf("BQ27220: Design Capacity already correct, FCC=%d mAh\n", fcc); if (fcc >= designCapacity_mAh * 3 / 2) { - // FCC is >=150% of design — stale from factory defaults. - // The gauge derives FCC from Design Energy (not just Design Capacity). - // Design Energy = capacity × nominal voltage (3.7V for LiPo). - // If Design Energy still reflects 3000 mAh, FCC stays at 3000. - // Fix: enter CFG_UPDATE and write correct Design Energy. - Serial.printf("BQ27220: FCC %d >> DC %d, updating Design Energy\n", - fcc, designCapacity_mAh); - + // FCC is >=150% of design — stale from factory defaults (typically 3000 mAh). uint16_t designEnergy = (uint16_t)((uint32_t)designCapacity_mAh * 37 / 10); - Serial.printf("BQ27220: Target Design Energy = %d mWh\n", designEnergy); + Serial.printf("BQ27220: FCC %d >> DC %d, checking Design Energy (target %d mWh)\n", + fcc, designCapacity_mAh, designEnergy); - // Unseal + // Unseal to read data memory and issue RESET bq27220_writeControl(0x0414); delay(2); bq27220_writeControl(0x3672); delay(2); // Full Access bq27220_writeControl(0xFFFF); delay(2); bq27220_writeControl(0xFFFF); delay(2); - // Enter CFG_UPDATE + + // Read current Design Energy from data memory to check if it needs writing + // Enter CFG_UPDATE to access data memory bq27220_writeControl(0x0090); bool ready = false; for (int i = 0; i < 50; i++) { @@ -228,52 +222,135 @@ bool TDeckBoard::configureFuelGauge(uint16_t designCapacity_mAh) { if (opSt & 0x0400) { ready = true; break; } } if (ready) { - // Design Energy is at data memory address 0x92A1 (2 bytes after DC at 0x929F) - // Read old values for checksum calculation + // Read Design Energy at data memory address 0x92A1 Wire.beginTransmission(BQ27220_I2C_ADDR); Wire.write(0x3E); Wire.write(0xA1); Wire.write(0x92); Wire.endTransmission(); delay(10); uint8_t oldMSB = bq27220_read8(0x40); uint8_t oldLSB = bq27220_read8(0x41); - uint8_t oldChk = bq27220_read8(0x60); - uint8_t dLen = bq27220_read8(0x61); + uint16_t currentDE = (oldMSB << 8) | oldLSB; - uint8_t newMSB = (designEnergy >> 8) & 0xFF; - uint8_t newLSB = designEnergy & 0xFF; - uint8_t temp = (255 - oldChk - oldMSB - oldLSB); - uint8_t newChk = 255 - ((temp + newMSB + newLSB) & 0xFF); + if (currentDE != designEnergy) { + // Design Energy actually needs updating — write it + uint8_t oldChk = bq27220_read8(0x60); + uint8_t dLen = bq27220_read8(0x61); + uint8_t newMSB = (designEnergy >> 8) & 0xFF; + uint8_t newLSB = designEnergy & 0xFF; + uint8_t temp = (255 - oldChk - oldMSB - oldLSB); + uint8_t newChk = 255 - ((temp + newMSB + newLSB) & 0xFF); - Serial.printf("BQ27220: DE old=0x%02X%02X new=0x%02X%02X chk=0x%02X\n", - oldMSB, oldLSB, newMSB, newLSB, newChk); + Serial.printf("BQ27220: DE old=%d new=%d mWh, writing\n", currentDE, designEnergy); - // Write new Design Energy - Wire.beginTransmission(BQ27220_I2C_ADDR); - Wire.write(0x3E); Wire.write(0xA1); Wire.write(0x92); - Wire.write(newMSB); Wire.write(newLSB); - Wire.endTransmission(); - delay(5); - // Write checksum - Wire.beginTransmission(BQ27220_I2C_ADDR); - Wire.write(0x60); Wire.write(newChk); Wire.write(dLen); - Wire.endTransmission(); - delay(10); + Wire.beginTransmission(BQ27220_I2C_ADDR); + Wire.write(0x3E); Wire.write(0xA1); Wire.write(0x92); + Wire.write(newMSB); Wire.write(newLSB); + Wire.endTransmission(); + delay(5); + Wire.beginTransmission(BQ27220_I2C_ADDR); + Wire.write(0x60); Wire.write(newChk); Wire.write(dLen); + Wire.endTransmission(); + delay(10); - // Exit CFG_UPDATE with reinit - bq27220_writeControl(0x0091); - delay(200); - Serial.println("BQ27220: Design Energy updated, exited CFG_UPDATE"); + // Exit with reinit since we actually changed data + bq27220_writeControl(0x0091); // EXIT_CFG_UPDATE_REINIT + delay(200); + Serial.println("BQ27220: Design Energy written, exited CFG_UPDATE"); + } else { + // DC=2000, DE=7400, Update Status=0x00, but FCC is stuck at 3000. + // Diagnostic scan found the culprits: + // 0x9106 = Qmax Cell 0 (IT Cfg class) — the raw capacity the + // gauge uses for FCC calculation. Factory default 3000. + // 0x929D = Stored FCC reference (Gas Gauging class, 2 bytes + // before Design Capacity). Also stuck at 3000. + // + // Fix: overwrite both with designCapacity_mAh (2000). + Serial.printf("BQ27220: DE correct (%d mWh) — fixing Qmax + stored FCC\n", currentDE); + + // --- Helper lambda for MAC data memory 2-byte write --- + // Reads old value + checksum, computes differential checksum, writes new value. + auto writeDM16 = [](uint16_t addr, uint16_t newVal) -> bool { + // Select address + Wire.beginTransmission(BQ27220_I2C_ADDR); + Wire.write(0x3E); + Wire.write(addr & 0xFF); + Wire.write((addr >> 8) & 0xFF); + Wire.endTransmission(); + delay(10); + + uint8_t oldMSB = bq27220_read8(0x40); + uint8_t oldLSB = bq27220_read8(0x41); + uint8_t oldChk = bq27220_read8(0x60); + uint8_t dLen = bq27220_read8(0x61); + uint16_t oldVal = (oldMSB << 8) | oldLSB; + + if (oldVal == newVal) { + Serial.printf("BQ27220: [0x%04X] already %d, skip\n", addr, newVal); + return true; // already correct + } + + uint8_t newMSB = (newVal >> 8) & 0xFF; + uint8_t newLSB = newVal & 0xFF; + uint8_t temp = (255 - oldChk - oldMSB - oldLSB); + uint8_t newChk = 255 - ((temp + newMSB + newLSB) & 0xFF); + + Serial.printf("BQ27220: [0x%04X] %d -> %d\n", addr, oldVal, newVal); + + // Write new value + Wire.beginTransmission(BQ27220_I2C_ADDR); + Wire.write(0x3E); + Wire.write(addr & 0xFF); + Wire.write((addr >> 8) & 0xFF); + Wire.write(newMSB); + Wire.write(newLSB); + Wire.endTransmission(); + delay(5); + + // Write checksum + Wire.beginTransmission(BQ27220_I2C_ADDR); + Wire.write(0x60); + Wire.write(newChk); + Wire.write(dLen); + Wire.endTransmission(); + delay(10); + return true; + }; + + // Overwrite Qmax Cell 0 (IT Cfg) — this is what FCC is derived from + writeDM16(0x9106, designCapacity_mAh); + + // Overwrite stored FCC reference (Gas Gauging, 2 bytes before DC) + writeDM16(0x929D, designCapacity_mAh); + + // Exit with reinit to apply the new values + bq27220_writeControl(0x0091); // EXIT_CFG_UPDATE_REINIT + delay(200); + Serial.println("BQ27220: Qmax + stored FCC updated, exited CFG_UPDATE"); + } } else { - Serial.println("BQ27220: Failed to enter CFG_UPDATE for DE fix"); - bq27220_writeControl(0x0092); // Exit cleanly + Serial.println("BQ27220: Failed to enter CFG_UPDATE for DE check"); } - // Seal - bq27220_writeControl(0x0030); + // Seal first, then issue RESET. + // RESET forces the gauge to fully reinitialize its Impedance Track + // algorithm and recalculate FCC from the current DC/DE values. + // This is the actual fix when DC and DE are correct but FCC is stuck. + bq27220_writeControl(0x0030); // SEAL delay(5); + Serial.println("BQ27220: Issuing RESET to force FCC recalculation..."); + bq27220_writeControl(0x0041); // RESET + delay(2000); // Full reset needs generous settle time fcc = bq27220_read16(BQ27220_REG_FULL_CAP); - Serial.printf("BQ27220: FCC after Design Energy update: %d mAh\n", fcc); + Serial.printf("BQ27220: FCC after RESET: %d mAh (target <= %d)\n", fcc, designCapacity_mAh); + + if (fcc > designCapacity_mAh * 3 / 2) { + // RESET didn't fix FCC — the gauge IT algorithm is stubbornly + // retaining its learned value. This typically resolves after one + // full charge/discharge cycle. Software clamp in + // getFullChargeCapacity() ensures correct display regardless. + Serial.printf("BQ27220: FCC still stale at %d — software clamp active\n", fcc); + } } return true; } @@ -434,6 +511,17 @@ bool TDeckBoard::configureFuelGauge(uint16_t designCapacity_mAh) { bq27220_writeControl(0x0030); delay(5); + // Step 7: Force full gauge RESET to reinitialize FCC from new DC/DE. + // Without this, the Impedance Track algorithm retains the old FCC + // (often 3000 mAh from factory) until a full charge/discharge cycle. + bq27220_writeControl(0x0041); // RESET + delay(1000); // Gauge needs time to fully reinitialize + + // Re-verify after hard reset + verifyDC = bq27220_read16(BQ27220_REG_DESIGN_CAP); + newFCC = bq27220_read16(BQ27220_REG_FULL_CAP); + Serial.printf("BQ27220: Post-RESET DC=%d FCC=%d mAh\n", verifyDC, newFCC); + return verifyDC == designCapacity_mAh; #else return false; @@ -474,7 +562,12 @@ uint16_t TDeckBoard::getRemainingCapacity() { uint16_t TDeckBoard::getFullChargeCapacity() { #if HAS_BQ27220 - return bq27220_read16(BQ27220_REG_FULL_CAP); + uint16_t fcc = bq27220_read16(BQ27220_REG_FULL_CAP); + // Clamp to design capacity — the gauge may report a stale factory FCC + // (e.g. 3000 mAh) until it completes a full learning cycle. Never let + // the reported FCC exceed what the actual cell can hold. + if (fcc > BQ27220_DESIGN_CAPACITY_MAH) fcc = BQ27220_DESIGN_CAPACITY_MAH; + return fcc; #else return 0; #endif