mirror of
https://github.com/pelgraine/Meck.git
synced 2026-03-28 17:42:44 +01:00
updated bq27220 function for better fcc battery readings; updates to webreader to enable epub downloads to sd
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -35,6 +35,9 @@
|
||||
#include <esp_heap_caps.h>
|
||||
#include <SD.h>
|
||||
#include <vector>
|
||||
#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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user