From dd2d4283e2365862e1cd599a59e0c68a9493d9f1 Mon Sep 17 00:00:00 2001 From: Matthew Date: Mon, 3 Aug 2026 20:48:19 -0700 Subject: [PATCH] Fix WiFi reconnect bugs, negative-temp rounding, and stale docs - Clear WIFI_CONNECTED_BIT on disconnect so wifi_is_connected() stops reporting a link that is gone; the weather task's reconnect path was dead code without it. - After the 8 fast retries are spent, retry every 30s via an esp_timer forever instead of giving up until power cycle. FAIL_BIT now only unblocks the boot-time wait and is cleared on reconnect. - Start the weather task even when the boot-time connect times out -- the background retries may still bring WiFi up later. Only an empty SSID skips it. - Round temperatures with lroundf(): the old +0.5f-and-truncate rounded toward zero below 0degF (-10.6 displayed as -10). - Drop the stale single-framebuffer "Partial-mode flush" comment in display_rgb.c, fix the README paragraph claiming the bounce buffer is disabled (it is enabled and load-bearing), and correct the sdkconfig.defaults header to N16R8/16MB. Co-Authored-By: Claude Fable 5 --- README.md | 11 +++++---- main/drivers/display/display_rgb.c | 8 ------- main/main.c | 26 ++++++++++++++------- main/wifi/wifi_manager.c | 36 ++++++++++++++++++++++++++---- sdkconfig.defaults | 4 ++-- 5 files changed, 59 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index cd6052f..d459f0d 100644 --- a/README.md +++ b/README.md @@ -130,7 +130,10 @@ Two 800×480×2 framebuffers (750 KB each) live in PSRAM. The 8 MB **octal** PSRAM at 80 MHz is required — QSPI cannot sustain the RGB refresh. Do **not** enable `CONFIG_SPIRAM_FETCH_INSTRUCTIONS` / `CONFIG_SPIRAM_RODATA`: -they contend with the panel for PSRAM bandwidth. Do **not** enable the RGB -bounce buffer either — its refill ISR reads PSRAM, which shares the cache with -flash on the S3, so any NVS write faults it with *"Cache disabled but cached -memory region accessed"*. +they contend with the panel for PSRAM bandwidth. The bounce buffer **is** +enabled (the panel cannot stream straight from PSRAM), which is exactly why +flash writes are dangerous while the panel runs: the refill ISR reads PSRAM, +which shares the cache with flash on the S3, so an NVS write faults it with +*"Cache disabled but cached memory region accessed"*. All flash writes happen +before `display_init()` and `CONFIG_ESP_WIFI_NVS_ENABLED` is off to keep the +WiFi stack from writing later. diff --git a/main/drivers/display/display_rgb.c b/main/drivers/display/display_rgb.c index 9306054..e2605ec 100644 --- a/main/drivers/display/display_rgb.c +++ b/main/drivers/display/display_rgb.c @@ -87,14 +87,6 @@ static const char *TAG = "display_rgb"; static esp_lcd_panel_handle_t panel = NULL; static lv_display_t *disp = NULL; -/* - * Partial-mode flush: blit the dirty rectangle from a small internal-RAM - * draw buffer into the single PSRAM framebuffer. - * - * Page-flipping two framebuffers instead would be cheaper, but the swap - * lands mid-scanout on this panel and tears visibly. Copying into one - * framebuffer that the LCD scans continuously is what the vendor driver does. - */ /* * Double-buffered flush. * diff --git a/main/main.c b/main/main.c index 62c63ad..ac58269 100644 --- a/main/main.c +++ b/main/main.c @@ -1,3 +1,4 @@ +#include #include #include @@ -54,7 +55,9 @@ static void weather_update_task(void *arg) snprintf(detail, sizeof(detail), "%.0f%% humidity", weather.has_humidity ? weather.humidity : 0.0f); - ui_update_weather((int16_t)(weather.temp_f + 0.5f), + /* lroundf, not +0.5f-and-truncate: the latter rounds toward zero + * below 0°F, turning -10.6 into -10. */ + ui_update_weather((int16_t)lroundf(weather.temp_f), weather.has_humidity ? detail : "Outdoor"); wifi_get_status(line, sizeof(line)); @@ -83,7 +86,7 @@ void app_main(void) */ const esp_err_t wifi_err = wifi_init_sta(); if (wifi_err != ESP_OK) { - ESP_LOGE(TAG, "WiFi failed -- continuing, weather will be disabled"); + ESP_LOGE(TAG, "WiFi not up yet -- weather starts when it connects"); } /* LVGL core must exist before the display and touch drivers register @@ -114,15 +117,22 @@ void app_main(void) vTaskDelay(pdMS_TO_TICKS(100)); ESP_ERROR_CHECK(backlight_set(255)); - if (wifi_err != ESP_OK) { - ui_set_status("WiFi: failed"); + if (wifi_err == ESP_OK) { + char status[96]; + wifi_get_status(status, sizeof(status)); + ui_set_status(status); + } else if (wifi_err == ESP_FAIL || wifi_err == ESP_ERR_TIMEOUT) { + /* The event handler keeps retrying in the background; the weather + * task polls wifi_is_connected(), so start it anyway rather than + * permanently disabling weather because boot raced a slow AP. */ + ui_set_status("WiFi: connecting..."); + } else { + /* No SSID configured (or setup failed outright) -- nothing will ever + * connect, so there is no point starting the weather task. */ + ui_set_status("WiFi: not configured"); return; } - char status[96]; - wifi_get_status(status, sizeof(status)); - ui_set_status(status); - if (weather_init() != ESP_OK) { ui_set_status("Weather: no API credentials"); return; diff --git a/main/wifi/wifi_manager.c b/main/wifi/wifi_manager.c index cf3ab57..e9a17f7 100644 --- a/main/wifi/wifi_manager.c +++ b/main/wifi/wifi_manager.c @@ -4,6 +4,7 @@ #include "esp_log.h" #include "esp_netif.h" #include "esp_system.h" +#include "esp_timer.h" #include "esp_wifi.h" #include "freertos/FreeRTOS.h" #include "freertos/event_groups.h" @@ -18,10 +19,20 @@ static const char *TAG = "wifi_manager"; #define WIFI_FAIL_BIT BIT1 #define WIFI_MAX_RETRIES 8 #define WIFI_CONNECT_TIMEOUT_MS 30000 +/* After the fast retries are spent, keep trying at this interval forever -- + * this is a wall appliance, so an AP outage must not require a power cycle. */ +#define WIFI_RECONNECT_DELAY_MS 30000 static EventGroupHandle_t s_wifi_event_group = NULL; +static esp_timer_handle_t s_reconnect_timer = NULL; static int s_retry_count = 0; +static void reconnect_timer_cb(void *arg) +{ + ESP_LOGI(TAG, "Slow reconnect attempt"); + esp_wifi_connect(); +} + static void wifi_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) { @@ -31,18 +42,28 @@ static void wifi_event_handler(void *arg, esp_event_base_t event_base, } if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) { + /* Without this, wifi_is_connected() keeps reporting true after a + * drop and callers never notice the link is gone. */ + xEventGroupClearBits(s_wifi_event_group, WIFI_CONNECTED_BIT); + /* - * Only give up after a bounded number of retries. Signalling failure - * on the first disconnect (as this previously did) meant a single - * transient drop was reported as a hard failure. + * A bounded burst of immediate retries covers transient drops; + * signalling failure on the first disconnect (as this previously did) + * meant a single blip was reported as a hard failure. Once the burst + * is spent, fall back to a slow periodic retry instead of giving up: + * FAIL_BIT only unblocks the boot-time wait in wifi_init_sta(). */ if (s_retry_count < WIFI_MAX_RETRIES) { s_retry_count++; ESP_LOGW(TAG, "Disconnected, retry %d/%d", s_retry_count, WIFI_MAX_RETRIES); esp_wifi_connect(); } else { - ESP_LOGE(TAG, "Giving up after %d retries", WIFI_MAX_RETRIES); + ESP_LOGW(TAG, "Still down after %d fast retries; retrying every %d s", + WIFI_MAX_RETRIES, WIFI_RECONNECT_DELAY_MS / 1000); xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT); + esp_timer_stop(s_reconnect_timer); + esp_timer_start_once(s_reconnect_timer, + (uint64_t)WIFI_RECONNECT_DELAY_MS * 1000); } return; } @@ -51,6 +72,7 @@ static void wifi_event_handler(void *arg, esp_event_base_t event_base, const ip_event_got_ip_t *event = (const ip_event_got_ip_t *)event_data; ESP_LOGI(TAG, "Got IP: " IPSTR, IP2STR(&event->ip_info.ip)); s_retry_count = 0; + xEventGroupClearBits(s_wifi_event_group, WIFI_FAIL_BIT); xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT); } } @@ -67,6 +89,12 @@ esp_err_t wifi_init_sta(void) return ESP_ERR_NO_MEM; } + const esp_timer_create_args_t timer_args = { + .callback = reconnect_timer_cb, + .name = "wifi_reconnect", + }; + ESP_ERROR_CHECK(esp_timer_create(&timer_args, &s_reconnect_timer)); + esp_err_t ret = nvs_flash_init(); if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { ESP_ERROR_CHECK(nvs_flash_erase()); diff --git a/sdkconfig.defaults b/sdkconfig.defaults index d19a2c7..acc07bc 100644 --- a/sdkconfig.defaults +++ b/sdkconfig.defaults @@ -1,5 +1,5 @@ -# Elecrow CrowPanel 7.0" HMI -- ESP32-S3-WROOM-1-N4R8 -# 4MB flash, 8MB octal PSRAM, 800x480 RGB panel. +# Elecrow CrowPanel 7.0" Advance HMI -- ESP32-S3-WROOM-1-N16R8 +# 16MB flash, 8MB octal PSRAM, 800x480 RGB panel. CONFIG_IDF_TARGET="esp32s3"