From ccb4280ae220cf17f0f194ae26f43f9183a5cd92 Mon Sep 17 00:00:00 2001 From: pelgraine <140762863+pelgraine@users.noreply.github.com> Date: Wed, 25 Feb 2026 19:14:56 +1100 Subject: [PATCH] updated bq27220 function for better fcc battery readings; updates to webreader to enable epub downloads to sd --- .../companion_radio/ui-new/ModemManager.cpp | 11 +- .../companion_radio/ui-new/ModemManager.h | 9 + examples/companion_radio/ui-new/UITask.cpp | 5 +- .../companion_radio/ui-new/Webreaderscreen.h | 225 ++++++++++++++---- variants/lilygo_tdeck_pro/TDeckBoard.cpp | 118 ++++++++- 5 files changed, 312 insertions(+), 56 deletions(-) diff --git a/examples/companion_radio/ui-new/ModemManager.cpp b/examples/companion_radio/ui-new/ModemManager.cpp index c09008f..a7971b2 100644 --- a/examples/companion_radio/ui-new/ModemManager.cpp +++ b/examples/companion_radio/ui-new/ModemManager.cpp @@ -715,8 +715,10 @@ restart: // Primary detection is via "VOICE CALL: BEGIN" URC (handled by // drainURCs/processURCLine above). CLCC polling is a safety net // in case the URC is missed or delayed. + // Skip when paused to avoid Core 0 contention with WiFi TLS. // ================================================================ - if (_state == ModemState::DIALING && + if (!_paused && + _state == ModemState::DIALING && millis() - lastCLCCPoll > CLCC_POLL_INTERVAL) { if (sendAT("AT+CLCC", "OK", 2000)) { // +CLCC: 1,0,0,0,0,"number",129 — stat field: @@ -747,8 +749,11 @@ restart: // ================================================================ // Step 4: SMS and signal polling (only when not in a call) + // Skip when paused to avoid Core 0 contention with WiFi/TLS. + // The modem task's sendAT() calls (AT+CMGL 5s, AT+CSQ 2s) do + // tight UART poll loops that disrupt WiFi packet timing. // ================================================================ - if (!isCallActive()) { + if (!_paused && !isCallActive()) { // Check for outgoing SMS in queue SMSOutgoing outMsg; if (xQueueReceive(_sendQueue, &outMsg, 0) == pdTRUE) { @@ -766,7 +771,7 @@ restart: } // Periodic signal strength update (always, even during calls) - if (millis() - lastCSQPoll > CSQ_POLL_INTERVAL) { + if (!_paused && millis() - lastCSQPoll > CSQ_POLL_INTERVAL) { // Only poll CSQ if not actively in a call (avoid interrupting audio) if (!isCallActive()) { pollCSQ(); diff --git a/examples/companion_radio/ui-new/ModemManager.h b/examples/companion_radio/ui-new/ModemManager.h index 19ac262..9cef7d4 100644 --- a/examples/companion_radio/ui-new/ModemManager.h +++ b/examples/companion_radio/ui-new/ModemManager.h @@ -158,6 +158,14 @@ public: const char* getCallPhone() const { return _callPhone; } uint32_t getCallStartTime() const { return _callStartTime; } + // Pause/resume polling — used by web reader to avoid Core 0 contention + // during WiFi TLS handshakes. While paused, the task skips AT commands + // (SMS poll, CSQ poll) but still drains URCs and handles call commands + // so incoming calls aren't missed. + void pausePolling() { _paused = true; } + void resumePolling() { _paused = false; } + bool isPaused() const { return _paused; } + static const char* stateToString(ModemState s); // Persistent enable/disable config (SD file /sms/modem.cfg) @@ -167,6 +175,7 @@ public: private: volatile ModemState _state = ModemState::OFF; volatile int _csq = 99; // 99 = unknown + volatile bool _paused = false; // Suppresses AT polling when true char _operator[24] = {0}; // Call state (written by modem task, read by main loop) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 69e6d85..8902126 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -642,8 +642,11 @@ public: display.drawTextRightAlign(display.width()-1, y, buf); y += 10; - // Remaining capacity + // Remaining capacity (clamped to design capacity — gauge FCC may be + // stale from factory defaults until a full charge cycle re-learns it) uint16_t remCap = board.getRemainingCapacity(); + uint16_t desCap = board.getDesignCapacity(); + if (desCap > 0 && remCap > desCap) remCap = desCap; display.drawTextLeftAlign(0, y, "remaining cap"); sprintf(buf, "%d mAh", remCap); display.drawTextRightAlign(display.width()-1, y, buf); diff --git a/examples/companion_radio/ui-new/Webreaderscreen.h b/examples/companion_radio/ui-new/Webreaderscreen.h index d34ba08..820838f 100644 --- a/examples/companion_radio/ui-new/Webreaderscreen.h +++ b/examples/companion_radio/ui-new/Webreaderscreen.h @@ -35,6 +35,9 @@ #include #include #include +#ifdef HAS_4G_MODEM +#include "ModemManager.h" +#endif #include "Utf8CP437.h" // Forward declarations @@ -1104,8 +1107,15 @@ private: // Fetch state unsigned long _fetchStartTime; int _fetchProgress; // Bytes received so far + int _fetchRetryCount; // Current retry attempt (0 = first try) String _fetchError; + // Persistent TLS client — kept alive between page loads to reuse TCP + // connections to the same host (avoids repeated 5-8s TLS handshakes). + // Destroyed on error, host change, or WiFi disconnect. + WiFiClientSecure* _tlsClient; + String _tlsHost; // Host _tlsClient is connected/configured for + // Download state (for EPUB/file downloads to SD) char _downloadedFile[64]; // Filename of last downloaded file bool _downloadOk; // true if download succeeded @@ -1228,6 +1238,27 @@ private: return result; } + // Remove Cloudflare challenge cookies for a domain. + // Stale __cf_bm and _cfuvid tokens from failed handshakes can poison + // subsequent requests, causing Cloudflare to keep returning 525/503. + void clearCloudflareCookies(const char* domain) { + int dst = 0; + for (int i = 0; i < _cookieCount; i++) { + bool isCfDomain = (strstr(domain, _cookies[i].domain) || + strcmp(domain, _cookies[i].domain) == 0); + bool isCfCookie = (strncmp(_cookies[i].name, "__cf_bm", 7) == 0 || + strncmp(_cookies[i].name, "_cfuvid", 7) == 0 || + strncmp(_cookies[i].name, "cf_", 3) == 0); + if (isCfDomain && isCfCookie) { + Serial.printf("WebReader: Cleared CF cookie '%s'\n", _cookies[i].name); + continue; // Skip — don't copy to dst + } + if (dst != i) _cookies[dst] = _cookies[i]; + dst++; + } + _cookieCount = dst; + } + // Parse Set-Cookie header(s) from HTTP response void parseSetCookie(const String& headerVal, const char* domain) { // Format: name=value; Path=/; ... @@ -1919,6 +1950,17 @@ private: const char* referer = nullptr) { Serial.printf("WebReader: fetchPage('%s', post=%s, ref=%s)\n", url, postBody ? "yes" : "no", referer ? referer : "(null)"); + + // Pause modem polling during fetch to avoid Core 0 contention with + // WiFi/TLS. The modem task's 10ms UART poll loop on Core 0 disrupts + // TLS handshakes, causing Cloudflare 525/503 errors. +#ifdef HAS_4G_MODEM + struct ModemPauseGuard { + ModemPauseGuard() { modemManager.pausePolling(); Serial.println("WebReader: modem paused"); } + ~ModemPauseGuard() { modemManager.resumePolling(); Serial.println("WebReader: modem resumed"); } + } _modemGuard; +#endif + if (!allocateBuffers()) { _fetchError = "Out of memory"; _mode = HOME; @@ -1928,6 +1970,7 @@ private: _mode = FETCHING; _fetchStartTime = millis(); _fetchProgress = 0; + _fetchRetryCount = 0; _fetchError = ""; // Push current URL to back stack (if we have one) @@ -1970,16 +2013,44 @@ private: const int maxRedirects = 5; bool isPost = (postBody != nullptr); - // Pre-create TLS client outside loop for connection reuse + // Use persistent TLS client (member variable) — survives across + // fetchPage() calls for connection reuse to the same host. ensureTlsUsesPsram(); - WiFiClientSecure* tlsClient = new WiFiClientSecure(); - tlsClient->setInsecure(); - tlsClient->setHandshakeTimeout(30); - String lastHost = ""; // Track host for connection reuse - int connRetries = 0; // Connection failure retries (max 1) - int serverRetries = 0; // 5xx error retries (max 1) + int totalRetries = 0; // Combined conn + server retries (max 4) + bool needsFreshTls = false; // Deferred TLS client recreation while (redirectCount <= maxRedirects) { + // Determine current host for TLS session management + bool isHttps = currentUrl.startsWith("https://"); + String currentHost; + if (isHttps) { + currentHost = currentUrl.substring(8); // skip "https://" + int slashIdx = currentHost.indexOf('/'); + if (slashIdx > 0) currentHost = currentHost.substring(0, slashIdx); + } + + // Unified TLS client creation/recreation. + // This runs BEFORE 'HTTPClient http' below is constructed, so it's safe + // to delete _tlsClient (no stack-local HTTPClient holds a reference yet). + // Triggers: error recovery, host change, first use, stale connection. + bool tlsStale = (_tlsClient && !_tlsClient->connected()); + if (tlsStale) { + Serial.println("WebReader: TLS connection closed by server, reconnecting"); + } + if (isHttps && (needsFreshTls || !_tlsClient || _tlsHost != currentHost || tlsStale)) { + if (_tlsClient) { + _tlsClient->stop(); + delete _tlsClient; + } + _tlsClient = new WiFiClientSecure(); + _tlsClient->setInsecure(); + _tlsClient->setHandshakeTimeout(15); + _tlsHost = currentHost; + needsFreshTls = false; + Serial.printf("WebReader: New TLS session for %s (heap: %d, largest: %d)\n", + currentHost.c_str(), ESP.getFreeHeap(), ESP.getMaxAllocHeap()); + } + // Update domain and cookies for current URL extractDomain(currentUrl.c_str(), domain, sizeof(domain)); cookieHeader = buildCookieHeader(domain); @@ -1989,31 +2060,15 @@ private: Serial.printf("WebReader: heap: %d, largest: %d\n", ESP.getFreeHeap(), ESP.getMaxAllocHeap()); - bool isHttps = currentUrl.startsWith("https://"); - HTTPClient http; http.setUserAgent(WEB_USER_AGENT); http.setFollowRedirects(HTTPC_DISABLE_FOLLOW_REDIRECTS); - http.setTimeout(30000); + http.setTimeout(15000); // 15s — fail fast to allow retries http.setReuse(true); // Keep connection alive for redirects bool beginOk; if (isHttps) { - // Check if we need a fresh TLS client (different host) - String currentHost = currentUrl.substring(8); // skip "https://" - int slashIdx = currentHost.indexOf('/'); - if (slashIdx > 0) currentHost = currentHost.substring(0, slashIdx); - - if (lastHost.length() > 0 && lastHost != currentHost) { - // Different host — need fresh TLS client - delete tlsClient; - tlsClient = new WiFiClientSecure(); - tlsClient->setInsecure(); - tlsClient->setHandshakeTimeout(30); - } - lastHost = currentHost; - - beginOk = http.begin(*tlsClient, currentUrl); + beginOk = http.begin(*_tlsClient, currentUrl); } else { beginOk = http.begin(currentUrl); } @@ -2086,17 +2141,42 @@ private: // Capture all Set-Cookie headers from this response captureResponseCookies(http, domain); - // Connection-level failure (timeout, TLS error, etc) — retry once - if (httpCode < 0 && connRetries < 1) { + // Connection-level failure (timeout, TLS error, etc) + if (httpCode < 0 && totalRetries < 4) { http.end(); - // Reset TLS client — stop connection but keep the object alive - // (HTTPClient destructor will access it when 'http' goes out of scope) - tlsClient->stop(); - lastHost = ""; // Force fresh connection on next begin() - connRetries++; - redirectCount++; - Serial.printf("WebReader: Connection error %d, retrying...\n", httpCode); - delay(2000); + totalRetries++; + _fetchRetryCount++; + needsFreshTls = true; // Recreate at top of next iteration (after http destructor) + + // After 2 failures, try WiFi reconnect — the lwIP stack may be wedged + if (totalRetries == 2 && isWiFiConnected()) { + Serial.println("WebReader: WiFi reconnect after persistent failures"); + // Destroy TLS before WiFi teardown + if (_tlsClient) { delete _tlsClient; _tlsClient = nullptr; } + _tlsHost = ""; + WiFi.disconnect(false); + delay(500); + WiFi.reconnect(); + unsigned long wt = millis(); + while (WiFi.status() != WL_CONNECTED && millis() - wt < 8000) delay(100); + if (WiFi.status() != WL_CONNECTED) { + Serial.println("WebReader: WiFi reconnect failed"); + _fetchError = "WiFi reconnect failed"; + break; + } + Serial.printf("WebReader: WiFi reconnected, IP: %s\n", + WiFi.localIP().toString().c_str()); + } + + int retryDelay = 1000 + totalRetries * 1000; // 2s, 3s, 4s, 5s + Serial.printf("WebReader: Connection error %d, retrying in %dms... (attempt %d/4)\n", + httpCode, retryDelay, totalRetries); + if (_display) { + _display->startFrame(); + renderFetching(*_display); + _display->endFrame(); + } + delay(retryDelay); continue; } if (httpCode < 0) { @@ -2108,10 +2188,11 @@ private: // Handle redirects if (httpCode == 301 || httpCode == 302 || httpCode == 303 || httpCode == 307) { String location = http.header("Location"); - // Don't call http.end() for same-host redirects — preserve connection - String body = http.getString(); // consume response body to free connection + // End the connection — the next loop iteration creates a fresh + // HTTPClient. Don't use getString() to "consume" the body because + // chunked responses without proper termination can hang forever. + http.end(); if (location.length() == 0) { - http.end(); _fetchError = "Redirect with no Location"; break; } @@ -2138,7 +2219,7 @@ private: Serial.println("WebReader: EPUB detected, downloading to SD"); free(htmlBuffer); bool dlOk = downloadToSD(http, currentUrl.c_str(), cdisp); - delete tlsClient; + // _tlsClient persists as member — cleaned up by exitReader() return dlOk; } @@ -2146,14 +2227,48 @@ private: success = (htmlLen > 0); http.end(); break; - } else if (httpCode >= 500 && httpCode < 600 && serverRetries < 1) { - // Server error (e.g. Cloudflare 525) — retry once after brief delay - String body = http.getString(); + } else if (httpCode >= 500 && httpCode < 600 && totalRetries < 4) { + // Server error (e.g. Cloudflare 525/503) + // Don't call getString() — CF error responses can use chunked encoding + // without proper termination, causing getString() to block forever. + // http.end() forcefully closes the socket which is fine since we're + // recreating the TLS client anyway. http.end(); - serverRetries++; - redirectCount++; - Serial.printf("WebReader: Server error %d, retrying...\n", httpCode); - delay(1500); + needsFreshTls = true; // Recreate at top of next iteration + totalRetries++; + _fetchRetryCount++; + + // Clear Cloudflare cookies — stale __cf_bm tokens from failed + // handshakes cause CF to keep rejecting us + clearCloudflareCookies(domain); + + // WiFi reconnect after 2 total failures (same logic as conn errors) + if (totalRetries == 2 && isWiFiConnected()) { + Serial.println("WebReader: WiFi reconnect after persistent failures"); + if (_tlsClient) { delete _tlsClient; _tlsClient = nullptr; } + _tlsHost = ""; + WiFi.disconnect(false); + delay(500); + WiFi.reconnect(); + unsigned long wt = millis(); + while (WiFi.status() != WL_CONNECTED && millis() - wt < 8000) delay(100); + if (WiFi.status() != WL_CONNECTED) { + _fetchError = "WiFi reconnect failed"; + break; + } + Serial.printf("WebReader: WiFi reconnected, IP: %s\n", + WiFi.localIP().toString().c_str()); + } + + int retryDelay = 1000 + totalRetries * 1000; // 2s, 3s, 4s, 5s + Serial.printf("WebReader: Server error %d, retrying in %dms... (attempt %d/4)\n", + httpCode, retryDelay, totalRetries); + if (_display) { + _display->startFrame(); + renderFetching(*_display); + _display->endFrame(); + } + delay(retryDelay); continue; } else { _fetchError = httpErrorString(httpCode); @@ -2162,7 +2277,8 @@ private: } } // end redirect loop - delete tlsClient; + // Don't delete _tlsClient — keep for connection reuse on next fetchPage(). + // Cleaned up by exitReader() or on next fetch to a different host. if (redirectCount > maxRedirects && !success) { _fetchError = "Too many redirects"; @@ -2968,9 +3084,12 @@ private: display.print(urlDisp); display.setCursor(10, 60); - char progBuf[40]; + char progBuf[48]; int elapsed = (int)((millis() - _fetchStartTime) / 1000); - if (_fetchProgress > 0) { + if (_fetchRetryCount > 0) { + snprintf(progBuf, sizeof(progBuf), "Retry %d/4... %ds", _fetchRetryCount, elapsed); + display.setColor(DisplayDriver::YELLOW); + } else if (_fetchProgress > 0) { snprintf(progBuf, sizeof(progBuf), "%d bytes (%ds)", _fetchProgress, elapsed); } else if (elapsed >= 2) { snprintf(progBuf, sizeof(progBuf), "Connecting... %ds", elapsed); @@ -4694,7 +4813,8 @@ public: _formCount(0), _forms(nullptr), _activeForm(-1), _activeField(0), _formFieldEditing(false), _formEditLen(0), _formLastCharAt(0), _cookies(nullptr), _cookieCount(0), - _fetchStartTime(0), _fetchProgress(0), + _fetchStartTime(0), _fetchProgress(0), _fetchRetryCount(0), + _tlsClient(nullptr), _downloadOk(false), _requestTextReader(false), _ircClient(nullptr), _ircUseTLS(false), _ircConnected(false), _ircRegistered(false), @@ -4734,6 +4854,7 @@ public: _ircClient = nullptr; } if (_ircMessages) { free(_ircMessages); _ircMessages = nullptr; } + if (_tlsClient) { delete _tlsClient; _tlsClient = nullptr; } } // Called when entering the web reader screen @@ -4828,6 +4949,10 @@ public: _fetchError = String(); _connectedSSID = String(); + // Destroy persistent TLS client before WiFi shutdown + if (_tlsClient) { delete _tlsClient; _tlsClient = nullptr; } + _tlsHost = String(); + // Shut down WiFi to reclaim ~50-70KB internal RAM WiFi.disconnect(true); WiFi.mode(WIFI_OFF); diff --git a/variants/lilygo_tdeck_pro/TDeckBoard.cpp b/variants/lilygo_tdeck_pro/TDeckBoard.cpp index 8e2f1ce..d5398db 100644 --- a/variants/lilygo_tdeck_pro/TDeckBoard.cpp +++ b/variants/lilygo_tdeck_pro/TDeckBoard.cpp @@ -177,7 +177,85 @@ bool TDeckBoard::configureFuelGauge(uint16_t designCapacity_mAh) { Serial.printf("BQ27220: Design Capacity = %d mAh (target %d)\n", currentDC, designCapacity_mAh); if (currentDC == designCapacity_mAh) { - Serial.println("BQ27220: Design Capacity already correct, skipping"); + // 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); + + uint16_t designEnergy = (uint16_t)((uint32_t)designCapacity_mAh * 37 / 10); + Serial.printf("BQ27220: Target Design Energy = %d mWh\n", designEnergy); + + // Unseal + 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 + bq27220_writeControl(0x0090); + bool ready = false; + for (int i = 0; i < 50; i++) { + delay(20); + uint16_t opSt = bq27220_read16(BQ27220_REG_OP_STATUS); + 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 + 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); + + 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); + + // 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); + + // Exit CFG_UPDATE with reinit + bq27220_writeControl(0x0091); + delay(200); + Serial.println("BQ27220: Design Energy updated, exited CFG_UPDATE"); + } else { + Serial.println("BQ27220: Failed to enter CFG_UPDATE for DE fix"); + bq27220_writeControl(0x0092); // Exit cleanly + } + + // Seal + bq27220_writeControl(0x0030); + delay(5); + + fcc = bq27220_read16(BQ27220_REG_FULL_CAP); + Serial.printf("BQ27220: FCC after Design Energy update: %d mAh\n", fcc); + } return true; } @@ -281,6 +359,39 @@ bool TDeckBoard::configureFuelGauge(uint16_t designCapacity_mAh) { Serial.printf("BQ27220: Verify in CFGUPDATE: DC bytes=0x%02X 0x%02X (%d mAh)\n", verMSB, verLSB, (verMSB << 8) | verLSB); + // Step 4g: Also update Design Energy (address 0x92A1) while in CFG_UPDATE. + // Design Energy = capacity × 3.7V (nominal LiPo voltage). + // The gauge uses both DC and DE to compute Full Charge Capacity. + { + uint16_t designEnergy = (uint16_t)((uint32_t)designCapacity_mAh * 37 / 10); + Wire.beginTransmission(BQ27220_I2C_ADDR); + Wire.write(0x3E); Wire.write(0xA1); Wire.write(0x92); + Wire.endTransmission(); + delay(10); + uint8_t deOldMSB = bq27220_read8(0x40); + uint8_t deOldLSB = bq27220_read8(0x41); + uint8_t deOldChk = bq27220_read8(0x60); + uint8_t deLen = bq27220_read8(0x61); + + uint8_t deNewMSB = (designEnergy >> 8) & 0xFF; + uint8_t deNewLSB = designEnergy & 0xFF; + uint8_t deTemp = (255 - deOldChk - deOldMSB - deOldLSB); + uint8_t deNewChk = 255 - ((deTemp + deNewMSB + deNewLSB) & 0xFF); + + Serial.printf("BQ27220: Design Energy: old=%d new=%d mWh\n", + (deOldMSB << 8) | deOldLSB, designEnergy); + + Wire.beginTransmission(BQ27220_I2C_ADDR); + Wire.write(0x3E); Wire.write(0xA1); Wire.write(0x92); + Wire.write(deNewMSB); Wire.write(deNewLSB); + Wire.endTransmission(); + delay(5); + Wire.beginTransmission(BQ27220_I2C_ADDR); + Wire.write(0x60); Wire.write(deNewChk); Wire.write(deLen); + Wire.endTransmission(); + delay(10); + } + // Step 5: Exit CFG_UPDATE (with reinit to apply changes immediately) bq27220_writeControl(0x0091); // EXIT_CFG_UPDATE_REINIT Serial.println("BQ27220: Sent EXIT_CFG_UPDATE_REINIT, waiting..."); @@ -291,13 +402,16 @@ bool TDeckBoard::configureFuelGauge(uint16_t designCapacity_mAh) { Serial.printf("BQ27220: Design Capacity now reads %d mAh (expected %d)\n", verifyDC, designCapacity_mAh); + uint16_t newFCC = bq27220_read16(BQ27220_REG_FULL_CAP); + Serial.printf("BQ27220: Full Charge Capacity: %d mAh\n", newFCC); + if (verifyDC == designCapacity_mAh) { Serial.println("BQ27220: Configuration SUCCESS"); } else { Serial.println("BQ27220: Configuration FAILED"); } - // Step 7: Seal the device + // Step 6: Seal the device bq27220_writeControl(0x0030); delay(5);