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>
This commit is contained in:
sl-firmware 2026-04-20 20:54:21 -04:00
parent f653c05a7f
commit 90ee2324f5
2 changed files with 48 additions and 14 deletions

View File

@ -4,11 +4,7 @@
*
* Orin comms: CH343 USB-to-serial on UART0 (GPIO43/44) /dev/ttyACM0 on Orin
* VESC CAN: SN65HVD230 transceiver on GPIO15 (TX) / GPIO16 (RX)
* Display: GC9A01 on SPI2 BL=GPIO40, RST=GPIO12
*
* GPIO2 is NOT used for CAN it is free. Earlier versions had an erroneous
* conflict between DISP_BL (GPIO2) and VESC_CAN_TX (GPIO2); corrected here
* to match motor-test-firmware verified hardware layout (commit 8e66430).
* Display: GC9A01 on SPI2 BL=GPIO2, RST=GPIO14 (Waveshare schematic-verified)
*/
/* ── Orin serial (CH343 USB-to-UART, 1a86:55d3 on Orin side) ── */
@ -37,8 +33,8 @@
#define DISP_CS_GPIO 9
#define DISP_SCK_GPIO 10
#define DISP_MOSI_GPIO 11
#define DISP_RST_GPIO 12
#define DISP_BL_GPIO 40
#define DISP_RST_GPIO 14
#define DISP_BL_GPIO 2
/* ── Safety / timing ── */
#define HB_TIMEOUT_MS 500u /* heartbeat watchdog: disarm if exceeded */

View File

@ -11,7 +11,9 @@
*/
#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"
@ -26,6 +28,40 @@ static const char *TAG = "ota_disp";
#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)
{
@ -64,7 +100,7 @@ void ota_display_update(void)
case OTA_SELF_DOWNLOADING:
case OTA_SELF_VERIFYING:
case OTA_SELF_APPLYING: {
/* Balance self-update in progress */
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..." :
@ -76,9 +112,11 @@ void ota_display_update(void)
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;
@ -88,10 +126,12 @@ void ota_display_update(void)
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);
@ -99,9 +139,11 @@ void ota_display_update(void)
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;
@ -109,13 +151,13 @@ void ota_display_update(void)
break;
}
/* Idle — show badge if update available */
/* 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) {
/* Show available versions on display when idle */
char verline[48];
if (bal_avail) {
snprintf(verline, sizeof(verline), "Bal v%s rdy",
@ -127,10 +169,6 @@ void ota_display_update(void)
g_io_update.version);
draw_status(verline, "", COL_ORANGE, COL_BG);
}
} else {
/* Clear OTA overlay area */
display_fill_rect(20, 90, 200, 60, COL_BG);
draw_badge(false, false);
}
}