sl-firmware 90ee2324f5 fix: correct display GPIO pins and add SAULT+battery HUD
Two bugs causing blank display on both ESP32 Balance boards:

1. Wrong GPIO pins: RST was GPIO12 (should be GPIO14) and BL was GPIO40
   (should be GPIO2) per Waveshare ESP32-S3-Touch-LCD-1.28 schematic.
   The previous "correction" from GPIO2→GPIO40 was itself erroneous.

2. No HUD rendering: ota_display_task only handled OTA overlay states and
   drew nothing in idle mode. Added draw_hud_idle() that clears the screen
   on first call, draws "SAULT" at scale=4 centred on the 240×240 display,
   and updates battery voltage (from g_vesc[0].voltage_x10) at 5 Hz.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 20:54:21 -04:00

189 lines
6.3 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* ota_display.c — OTA notification/progress UI on GC9A01 (bd-1yr8)
*
* Renders OTA state overlaid on the 240×240 round HUD display:
* - BADGE: small dot on top-right when update available (idle state)
* - UPDATE SCREEN: version compare, Update Balance / Update IO / Update All
* - PROGRESS: arc around display perimeter + % + status text
* - ERROR: red banner + "RETRY" prompt
*
* The display_draw_* primitives must be provided by the GC9A01 driver.
* Actual SPI driver implementation is in a separate driver bead.
*/
#include "ota_display.h"
#include "gc9a01.h"
#include "gitea_ota.h"
#include "vesc_can.h"
#include "version.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include <stdio.h>
#include <string.h>
static const char *TAG = "ota_disp";
/* Display centre and radius for the 240×240 GC9A01 */
#define CX 120
#define CY 120
#define RAD 110
/* "SAULT" at scale=4: 5 chars × 24px wide = 120px, 28px tall */
#define SAULT_SCALE 4
#define SAULT_X 60 /* (240 - 120) / 2 */
#define SAULT_Y 86 /* centre vertically */
/* Battery voltage line at scale=2: "XX.XV" ≤ 5 chars × 12px = 60px */
#define VBAT_SCALE 2
#define VBAT_Y 130
static bool s_hud_dirty = true; /* set true whenever OTA overlay was active */
/* ── Idle HUD: SAULT title + battery voltage ── */
static void draw_hud_idle(void)
{
if (s_hud_dirty) {
/* Clear full screen on first entry after OTA overlay */
display_fill_rect(0, 0, 240, 240, COL_BG);
display_draw_string_s(SAULT_X, SAULT_Y, "SAULT", COL_WHITE, COL_BG, SAULT_SCALE);
s_hud_dirty = false;
}
/* Update battery voltage every tick */
uint16_t vbat_mv = (uint16_t)((int32_t)g_vesc[0].voltage_x10 * 100);
char vbuf[16];
if (vbat_mv == 0) {
snprintf(vbuf, sizeof(vbuf), "--.-V");
} else {
snprintf(vbuf, sizeof(vbuf), "%2u.%uV", vbat_mv / 1000u, (vbat_mv % 1000u) / 100u);
}
int vx = CX - (int)(strlen(vbuf) * 6 * VBAT_SCALE / 2);
display_fill_rect(vx - 2, VBAT_Y - 2, (int)(strlen(vbuf) * 6 * VBAT_SCALE) + 4,
7 * VBAT_SCALE + 4, COL_BG);
display_draw_string_s(vx, VBAT_Y, vbuf, COL_GREEN, COL_BG, VBAT_SCALE);
}
/* ── Availability badge: 8×8 dot at top-right of display ── */
static void draw_badge(bool balance_avail, bool io_avail)
{
uint16_t col = (balance_avail || io_avail) ? COL_ORANGE : COL_BG;
display_fill_rect(200, 15, 12, 12, col);
}
/* ── Progress arc: sweeps 0→360° proportional to progress% ── */
static void draw_progress_arc(uint8_t pct, uint16_t color)
{
int end_deg = (int)(360 * pct / 100);
display_draw_arc(CX, CY, RAD, 0, end_deg, 6, color);
}
/* ── Status banner: 2 lines of text centred on display ── */
static void draw_status(const char *line1, const char *line2,
uint16_t fg, uint16_t bg)
{
display_fill_rect(20, 90, 200, 60, bg);
if (line1 && line1[0])
display_draw_string(CX - (int)(strlen(line1) * 6 / 2), 96,
line1, fg, bg);
if (line2 && line2[0])
display_draw_string(CX - (int)(strlen(line2) * 6 / 2), 116,
line2, fg, bg);
}
/* ── Main render logic ── */
void ota_display_update(void)
{
/* Determine dominant OTA state */
ota_self_state_t self = g_ota_self_state;
uart_ota_send_state_t io_s = g_uart_ota_state;
switch (self) {
case OTA_SELF_DOWNLOADING:
case OTA_SELF_VERIFYING:
case OTA_SELF_APPLYING: {
s_hud_dirty = true;
char pct_str[16];
snprintf(pct_str, sizeof(pct_str), "%d%%", g_ota_self_progress);
const char *phase = (self == OTA_SELF_VERIFYING) ? "Verifying..." :
(self == OTA_SELF_APPLYING) ? "Applying..." :
"Downloading...";
draw_progress_arc(g_ota_self_progress, COL_BLUE);
draw_status("Updating Balance", pct_str, COL_WHITE, COL_BG);
ESP_LOGD(TAG, "balance OTA %s %d%%", phase, g_ota_self_progress);
return;
}
case OTA_SELF_REBOOTING:
s_hud_dirty = true;
draw_status("Update complete", "Rebooting...", COL_GREEN, COL_BG);
return;
case OTA_SELF_FAILED:
s_hud_dirty = true;
draw_progress_arc(0, COL_RED);
draw_status("Balance update", "FAILED RETRY?", COL_RED, COL_BG);
return;
default:
break;
}
switch (io_s) {
case UART_OTA_S_DOWNLOADING:
s_hud_dirty = true;
draw_progress_arc(g_uart_ota_progress, COL_YELLOW);
draw_status("Downloading IO", "firmware...", COL_WHITE, COL_BG);
return;
case UART_OTA_S_SENDING: {
s_hud_dirty = true;
char pct_str[16];
snprintf(pct_str, sizeof(pct_str), "%d%%", g_uart_ota_progress);
draw_progress_arc(g_uart_ota_progress, COL_YELLOW);
draw_status("Updating IO", pct_str, COL_WHITE, COL_BG);
return;
}
case UART_OTA_S_DONE:
s_hud_dirty = true;
draw_status("IO update done", "", COL_GREEN, COL_BG);
return;
case UART_OTA_S_FAILED:
s_hud_dirty = true;
draw_progress_arc(0, COL_RED);
draw_status("IO update", "FAILED RETRY?", COL_RED, COL_BG);
return;
default:
break;
}
/* Idle — draw SAULT HUD + badge */
bool bal_avail = g_balance_update.available;
bool io_avail = g_io_update.available;
draw_hud_idle();
draw_badge(bal_avail, io_avail);
if (bal_avail || io_avail) {
char verline[48];
if (bal_avail) {
snprintf(verline, sizeof(verline), "Bal v%s rdy",
g_balance_update.version);
draw_status(verline, io_avail ? "IO update rdy" : "",
COL_ORANGE, COL_BG);
} else if (io_avail) {
snprintf(verline, sizeof(verline), "IO v%s rdy",
g_io_update.version);
draw_status(verline, "", COL_ORANGE, COL_BG);
}
}
}
/* ── Background display task (5 Hz) ── */
static void ota_display_task(void *arg)
{
for (;;) {
vTaskDelay(pdMS_TO_TICKS(200));
ota_display_update();
}
}
void ota_display_init(void)
{
xTaskCreate(ota_display_task, "ota_disp", 2048, NULL, 3, NULL);
ESP_LOGI(TAG, "OTA display task started");
}