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 <noreply@anthropic.com>
This commit is contained in:
mattandClaude Fable 5 committed 2026-08-03 20:48:19 -07:00
1 parent 0ef2017d18
commit dd2d4283e2
5 files changed
+59 -26

No files matched your search

+7 -4
View File
@@ -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. PSRAM at 80 MHz is required — QSPI cannot sustain the RGB refresh.
Do **not** enable `CONFIG_SPIRAM_FETCH_INSTRUCTIONS` / `CONFIG_SPIRAM_RODATA`: Do **not** enable `CONFIG_SPIRAM_FETCH_INSTRUCTIONS` / `CONFIG_SPIRAM_RODATA`:
they contend with the panel for PSRAM bandwidth. Do **not** enable the RGB they contend with the panel for PSRAM bandwidth. The bounce buffer **is**
bounce buffer either — its refill ISR reads PSRAM, which shares the cache with enabled (the panel cannot stream straight from PSRAM), which is exactly why
flash on the S3, so any NVS write faults it with *"Cache disabled but cached flash writes are dangerous while the panel runs: the refill ISR reads PSRAM,
memory region accessed"*. 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.
-8
View File
@@ -87,14 +87,6 @@ static const char *TAG = "display_rgb";
static esp_lcd_panel_handle_t panel = NULL; static esp_lcd_panel_handle_t panel = NULL;
static lv_display_t *disp = 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. * Double-buffered flush.
* *
+18 -8
View File
@@ -1,3 +1,4 @@
#include <math.h>
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
@@ -54,7 +55,9 @@ static void weather_update_task(void *arg)
snprintf(detail, sizeof(detail), "%.0f%% humidity", snprintf(detail, sizeof(detail), "%.0f%% humidity",
weather.has_humidity ? weather.humidity : 0.0f); 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"); weather.has_humidity ? detail : "Outdoor");
wifi_get_status(line, sizeof(line)); wifi_get_status(line, sizeof(line));
@@ -83,7 +86,7 @@ void app_main(void)
*/ */
const esp_err_t wifi_err = wifi_init_sta(); const esp_err_t wifi_err = wifi_init_sta();
if (wifi_err != ESP_OK) { 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 /* LVGL core must exist before the display and touch drivers register
@@ -114,15 +117,22 @@ void app_main(void)
vTaskDelay(pdMS_TO_TICKS(100)); vTaskDelay(pdMS_TO_TICKS(100));
ESP_ERROR_CHECK(backlight_set(255)); ESP_ERROR_CHECK(backlight_set(255));
if (wifi_err != ESP_OK) { if (wifi_err == ESP_OK) {
ui_set_status("WiFi: failed"); 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; return;
} }
char status[96];
wifi_get_status(status, sizeof(status));
ui_set_status(status);
if (weather_init() != ESP_OK) { if (weather_init() != ESP_OK) {
ui_set_status("Weather: no API credentials"); ui_set_status("Weather: no API credentials");
return; return;
+32 -4
View File
@@ -4,6 +4,7 @@
#include "esp_log.h" #include "esp_log.h"
#include "esp_netif.h" #include "esp_netif.h"
#include "esp_system.h" #include "esp_system.h"
#include "esp_timer.h"
#include "esp_wifi.h" #include "esp_wifi.h"
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h" #include "freertos/event_groups.h"
@@ -18,10 +19,20 @@ static const char *TAG = "wifi_manager";
#define WIFI_FAIL_BIT BIT1 #define WIFI_FAIL_BIT BIT1
#define WIFI_MAX_RETRIES 8 #define WIFI_MAX_RETRIES 8
#define WIFI_CONNECT_TIMEOUT_MS 30000 #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 EventGroupHandle_t s_wifi_event_group = NULL;
static esp_timer_handle_t s_reconnect_timer = NULL;
static int s_retry_count = 0; 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, static void wifi_event_handler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data) 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) { 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 * A bounded burst of immediate retries covers transient drops;
* on the first disconnect (as this previously did) meant a single * signalling failure on the first disconnect (as this previously did)
* transient drop was reported as a hard failure. * 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) { if (s_retry_count < WIFI_MAX_RETRIES) {
s_retry_count++; s_retry_count++;
ESP_LOGW(TAG, "Disconnected, retry %d/%d", s_retry_count, WIFI_MAX_RETRIES); ESP_LOGW(TAG, "Disconnected, retry %d/%d", s_retry_count, WIFI_MAX_RETRIES);
esp_wifi_connect(); esp_wifi_connect();
} else { } 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); 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; 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; 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)); ESP_LOGI(TAG, "Got IP: " IPSTR, IP2STR(&event->ip_info.ip));
s_retry_count = 0; s_retry_count = 0;
xEventGroupClearBits(s_wifi_event_group, WIFI_FAIL_BIT);
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_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; 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(); esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase()); ESP_ERROR_CHECK(nvs_flash_erase());
+2 -2
View File
@@ -1,5 +1,5 @@
# Elecrow CrowPanel 7.0" HMI -- ESP32-S3-WROOM-1-N4R8 # Elecrow CrowPanel 7.0" Advance HMI -- ESP32-S3-WROOM-1-N16R8
# 4MB flash, 8MB octal PSRAM, 800x480 RGB panel. # 16MB flash, 8MB octal PSRAM, 800x480 RGB panel.
CONFIG_IDF_TARGET="esp32s3" CONFIG_IDF_TARGET="esp32s3"