Add OLED and button diagnostics

This commit is contained in:
2026-08-29 14:13:34 +02:00
parent bb04e0ba79
commit ec9ca5e6d2
13 changed files with 1117 additions and 28 deletions
+2
View File
@@ -6,6 +6,7 @@ idf_component_register(
"system_console.c"
"secure_random.c"
"status_led.c"
"local_ui_hw_test.c"
"rs232_hw_test.c"
"rs232_port_owner.c"
"serial_config.c"
@@ -32,6 +33,7 @@ idf_component_register(
bootloader_support
console
esp_driver_gpio
esp_driver_i2c
esp_driver_uart
esp_event
esp_http_server
+9
View File
@@ -1,6 +1,7 @@
#pragma once
#include "driver/gpio.h"
#include "driver/i2c_types.h"
#include "driver/uart.h"
/*
@@ -12,6 +13,14 @@
*/
#define BOARD_RGB_LED_GPIO GPIO_NUM_48
/* Phase 7 local OLED and active-low navigation buttons. */
#define LOCAL_UI_I2C_PORT I2C_NUM_0
#define LOCAL_UI_DISPLAY_SDA_GPIO GPIO_NUM_11
#define LOCAL_UI_DISPLAY_SCL_GPIO GPIO_NUM_12
#define LOCAL_UI_BUTTON_PREVIOUS_GPIO GPIO_NUM_10
#define LOCAL_UI_BUTTON_SELECT_GPIO GPIO_NUM_13
#define LOCAL_UI_BUTTON_NEXT_GPIO GPIO_NUM_14
#define RS232_UART_PORT UART_NUM_1
/* UART2 is used only as an internal traffic generator during flow-control tests. */
+27
View File
@@ -24,6 +24,33 @@ static const char *const s_completion_candidates[] = {
"debug uart-suite",
"debug cts-flow-test",
"debug rts-flow-test",
"debug display",
"debug display status",
"debug display probe",
"debug display scan",
"debug display scan --force",
"debug display init",
"debug display init 0x3c",
"debug display init 0x78",
"debug display init 0x79",
"debug display init 0x3d",
"debug display init 0x7a",
"debug display init 0x7b",
"debug display off",
"debug display pattern",
"debug display pattern clear",
"debug display pattern fill",
"debug display pattern checker",
"debug display pattern grid",
"debug display pattern corners",
"debug display row",
"debug display contrast",
"debug display invert",
"debug display invert on",
"debug display invert off",
"debug buttons",
"debug buttons status",
"debug buttons test",
/* Serial service lifecycle, persistence, counters, and settings. */
"serial status",
+865
View File
@@ -0,0 +1,865 @@
/* SPDX-License-Identifier: GPL-3.0-only */
/* Phase 7A bounded SSD1315-compatible OLED and button diagnostics. */
#include "local_ui_hw_test.h"
#include <errno.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "board_pins.h"
#include "driver/gpio.h"
#include "driver/i2c_master.h"
#include "esp_err.h"
#include "esp_timer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#define DISPLAY_WIDTH 128U
#define DISPLAY_HEIGHT 64U
#define DISPLAY_PAGE_COUNT (DISPLAY_HEIGHT / 8U)
#define DISPLAY_FRAMEBUFFER_SIZE (DISPLAY_WIDTH * DISPLAY_PAGE_COUNT)
#define DISPLAY_I2C_SPEED_HZ 100000U
#define DISPLAY_I2C_TIMEOUT_MS 250
#define DISPLAY_PROBE_TIMEOUT_MS 50
#define DISPLAY_SCAN_TIMEOUT_MS 10
#define DISPLAY_DEFAULT_CONTRAST 127U
#define DISPLAY_COMMAND_CAPACITY 32U
#define DISPLAY_DATA_CHUNK_SIZE 128U
#define BUTTON_POLL_MS 10U
#define BUTTON_DEBOUNCE_MS 30U
#define BUTTON_LONG_PRESS_MS 1000U
#define BUTTON_TEST_DEFAULT_SECONDS 10U
#define BUTTON_TEST_MAX_SECONDS 30U
/* The tested module uses 7-bit 0x3c; 0x3d is the common alternate strap. */
static const uint8_t s_expected_addresses[] = {0x3cU, 0x3dU};
typedef struct {
const char *name;
gpio_num_t gpio;
} button_definition_t;
typedef struct {
int raw_level;
int stable_level;
int64_t raw_changed_us;
int64_t pressed_us;
bool long_reported;
uint32_t short_presses;
uint32_t long_presses;
uint32_t stable_transitions;
} button_test_state_t;
static const button_definition_t s_buttons[] = {
{.name = "previous/back", .gpio = LOCAL_UI_BUTTON_PREVIOUS_GPIO},
{.name = "select/confirm", .gpio = LOCAL_UI_BUTTON_SELECT_GPIO},
{.name = "next", .gpio = LOCAL_UI_BUTTON_NEXT_GPIO},
};
static i2c_master_bus_handle_t s_bus;
static i2c_master_dev_handle_t s_display;
static uint8_t s_framebuffer[DISPLAY_FRAMEBUFFER_SIZE];
static esp_err_t s_initialization_error = ESP_ERR_INVALID_STATE;
static bool s_bus_ready;
static bool s_buttons_ready;
static bool s_panel_initialized;
static uint8_t s_display_address;
static uint8_t s_contrast = DISPLAY_DEFAULT_CONTRAST;
static bool s_inverted;
static TickType_t milliseconds_to_ticks(uint32_t milliseconds)
{
TickType_t ticks = pdMS_TO_TICKS(milliseconds);
return (milliseconds > 0U && ticks == 0U) ? 1U : ticks;
}
static bool parse_unsigned(const char *text,
unsigned long minimum,
unsigned long maximum,
unsigned long *value)
{
if (text == NULL || value == NULL) {
return false;
}
char *end = NULL;
errno = 0;
unsigned long parsed = strtoul(text, &end, 10);
if (errno != 0 || end == text || *end != '\0' ||
parsed < minimum || parsed > maximum) {
return false;
}
*value = parsed;
return true;
}
static bool parse_display_address(const char *text, uint8_t *address)
{
if (text == NULL || address == NULL) {
return false;
}
char *end = NULL;
errno = 0;
unsigned long parsed = strtoul(text, &end, 0);
if (errno != 0 || end == text || *end != '\0') {
return false;
}
if (parsed == 0x3cU || parsed == 0x78U || parsed == 0x79U) {
*address = 0x3cU;
return true;
}
if (parsed == 0x3dU || parsed == 0x7aU || parsed == 0x7bU) {
*address = 0x3dU;
return true;
}
return false;
}
static int report_error(const char *operation, esp_err_t error)
{
printf("%s failed: %s\n", operation, esp_err_to_name(error));
return 1;
}
static esp_err_t configure_buttons(void)
{
const gpio_config_t config = {
.pin_bit_mask = (1ULL << LOCAL_UI_BUTTON_PREVIOUS_GPIO) |
(1ULL << LOCAL_UI_BUTTON_SELECT_GPIO) |
(1ULL << LOCAL_UI_BUTTON_NEXT_GPIO),
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_ENABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
esp_err_t error = gpio_config(&config);
if (error == ESP_OK) {
s_buttons_ready = true;
}
return error;
}
static esp_err_t configure_i2c_bus(void)
{
const i2c_master_bus_config_t config = {
.i2c_port = LOCAL_UI_I2C_PORT,
.sda_io_num = LOCAL_UI_DISPLAY_SDA_GPIO,
.scl_io_num = LOCAL_UI_DISPLAY_SCL_GPIO,
.clk_source = I2C_CLK_SRC_DEFAULT,
.glitch_ignore_cnt = 7,
.flags.enable_internal_pullup = false,
};
esp_err_t error = i2c_new_master_bus(&config, &s_bus);
if (error == ESP_OK) {
s_bus_ready = true;
}
return error;
}
static esp_err_t probe_address(uint8_t address)
{
if (!s_bus_ready) {
return s_initialization_error == ESP_OK ? ESP_ERR_INVALID_STATE
: s_initialization_error;
}
return i2c_master_probe(s_bus, address, DISPLAY_PROBE_TIMEOUT_MS);
}
static esp_err_t select_display(uint8_t address)
{
esp_err_t error = probe_address(address);
if (error != ESP_OK) {
return error;
}
if (s_display != NULL && s_display_address == address) {
return ESP_OK;
}
if (s_display != NULL) {
error = i2c_master_bus_rm_device(s_display);
if (error != ESP_OK) {
return error;
}
s_display = NULL;
s_panel_initialized = false;
}
const i2c_device_config_t config = {
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
.device_address = address,
.scl_speed_hz = DISPLAY_I2C_SPEED_HZ,
};
error = i2c_master_bus_add_device(s_bus, &config, &s_display);
if (error == ESP_OK) {
s_display_address = address;
}
return error;
}
static esp_err_t probe_expected_addresses(uint8_t *selected_address)
{
if (!s_bus_ready) {
return s_initialization_error == ESP_OK ? ESP_ERR_INVALID_STATE
: s_initialization_error;
}
uint32_t found = 0U;
uint8_t first = 0U;
for (size_t index = 0U;
index < sizeof(s_expected_addresses) / sizeof(s_expected_addresses[0]);
++index) {
uint8_t address = s_expected_addresses[index];
esp_err_t error = probe_address(address);
if (error == ESP_OK) {
printf("OLED response at 7-bit 0x%02x (8-bit 0x%02x write / 0x%02x read).\n",
address, (unsigned int)(address << 1U),
(unsigned int)((address << 1U) | 1U));
if (found == 0U) {
first = address;
}
++found;
} else {
printf("No OLED response at 7-bit 0x%02x (%s).\n",
address, esp_err_to_name(error));
}
}
if (found == 0U) {
printf("No display found at the expected 7-bit 0x3c/0x3d addresses. Check 3.3 V power, ground, SDA/SCL order, and pull-ups.\n");
return ESP_ERR_NOT_FOUND;
}
if (found > 1U) {
printf("Multiple expected addresses responded; selecting 7-bit 0x%02x for diagnostics.\n",
first);
} else {
printf("Selected display address 7-bit 0x%02x.\n", first);
}
if (selected_address != NULL) {
*selected_address = first;
}
return ESP_OK;
}
static esp_err_t display_send_commands(const uint8_t *commands, size_t count)
{
if (s_display == NULL || commands == NULL || count == 0U ||
count > DISPLAY_COMMAND_CAPACITY) {
return ESP_ERR_INVALID_ARG;
}
uint8_t transfer[DISPLAY_COMMAND_CAPACITY + 1U];
transfer[0] = 0x00U;
memcpy(&transfer[1], commands, count);
return i2c_master_transmit(s_display, transfer, count + 1U,
DISPLAY_I2C_TIMEOUT_MS);
}
static esp_err_t display_send_command(uint8_t command)
{
return display_send_commands(&command, 1U);
}
static esp_err_t display_refresh(void)
{
if (!s_panel_initialized || s_display == NULL) {
return ESP_ERR_INVALID_STATE;
}
const uint8_t address_commands[] = {
0x21U, 0x00U, (uint8_t)(DISPLAY_WIDTH - 1U),
0x22U, 0x00U, (uint8_t)(DISPLAY_PAGE_COUNT - 1U),
};
esp_err_t error = display_send_commands(
address_commands, sizeof(address_commands));
if (error != ESP_OK) {
return error;
}
uint8_t transfer[DISPLAY_DATA_CHUNK_SIZE + 1U];
transfer[0] = 0x40U;
for (size_t offset = 0U; offset < DISPLAY_FRAMEBUFFER_SIZE;
offset += DISPLAY_DATA_CHUNK_SIZE) {
size_t chunk = DISPLAY_FRAMEBUFFER_SIZE - offset;
if (chunk > DISPLAY_DATA_CHUNK_SIZE) {
chunk = DISPLAY_DATA_CHUNK_SIZE;
}
memcpy(&transfer[1], &s_framebuffer[offset], chunk);
error = i2c_master_transmit(s_display, transfer, chunk + 1U,
DISPLAY_I2C_TIMEOUT_MS);
if (error != ESP_OK) {
return error;
}
}
return ESP_OK;
}
static esp_err_t display_initialize(uint8_t address)
{
/* A failed transaction must never leave a partially initialized state. */
s_panel_initialized = false;
esp_err_t error = select_display(address);
if (error != ESP_OK) {
return error;
}
const uint8_t initialization[] = {
0xaeU, /* Display off while geometry is configured. */
0xd5U, 0x80U, /* Default oscillator and divide ratio. */
0xa8U, 0x3fU, /* 1/64 multiplex. */
0xd3U, 0x00U, /* No display offset. */
0x40U, /* Display start line zero. */
0x8dU, 0x14U, /* Enable the module's internal charge pump. */
0x20U, 0x00U, /* Horizontal addressing mode. */
0xa1U, /* Segment remap for common four-pin modules. */
0xc8U, /* Descending COM scan direction. */
0xdaU, 0x12U, /* Alternative COM pin configuration. */
0x81U, DISPLAY_DEFAULT_CONTRAST,
0xd9U, 0xf1U, /* Charge-pump precharge period. */
0xdbU, 0x40U, /* VCOMH deselect level. */
0xa4U, /* Use display RAM, not all-pixels-on mode. */
0xa6U, /* Normal, non-inverted pixels. */
0x2eU, /* Disable any prior scrolling mode. */
};
error = display_send_commands(initialization, sizeof(initialization));
if (error != ESP_OK) {
return error;
}
s_panel_initialized = true;
s_contrast = DISPLAY_DEFAULT_CONTRAST;
s_inverted = false;
memset(s_framebuffer, 0, sizeof(s_framebuffer));
error = display_refresh();
if (error == ESP_OK) {
error = display_send_command(0xafU);
}
if (error != ESP_OK) {
s_panel_initialized = false;
return error;
}
vTaskDelay(milliseconds_to_ticks(20U));
return ESP_OK;
}
static void framebuffer_set_pixel(uint32_t x, uint32_t y)
{
if (x >= DISPLAY_WIDTH || y >= DISPLAY_HEIGHT) {
return;
}
size_t index = (size_t)(y / 8U) * DISPLAY_WIDTH + x;
s_framebuffer[index] |= (uint8_t)(1U << (y & 7U));
}
static void framebuffer_draw_pattern(const char *name)
{
memset(s_framebuffer, 0, sizeof(s_framebuffer));
if (strcmp(name, "fill") == 0) {
memset(s_framebuffer, 0xff, sizeof(s_framebuffer));
return;
}
if (strcmp(name, "checker") == 0) {
for (uint32_t y = 0U; y < DISPLAY_HEIGHT; ++y) {
for (uint32_t x = 0U; x < DISPLAY_WIDTH; ++x) {
if (((x + y) & 1U) == 0U) {
framebuffer_set_pixel(x, y);
}
}
}
return;
}
if (strcmp(name, "grid") == 0) {
for (uint32_t y = 0U; y < DISPLAY_HEIGHT; ++y) {
for (uint32_t x = 0U; x < DISPLAY_WIDTH; ++x) {
if ((x % 8U) == 0U || (y % 8U) == 0U) {
framebuffer_set_pixel(x, y);
}
}
}
return;
}
if (strcmp(name, "corners") == 0) {
for (uint32_t x = 0U; x < DISPLAY_WIDTH; ++x) {
framebuffer_set_pixel(x, 0U);
framebuffer_set_pixel(x, DISPLAY_HEIGHT - 1U);
}
for (uint32_t y = 0U; y < DISPLAY_HEIGHT; ++y) {
framebuffer_set_pixel(0U, y);
framebuffer_set_pixel(DISPLAY_WIDTH - 1U, y);
}
for (uint32_t offset = 0U; offset < 8U; ++offset) {
framebuffer_set_pixel(offset, offset);
framebuffer_set_pixel(DISPLAY_WIDTH - 1U - offset, offset);
framebuffer_set_pixel(offset, DISPLAY_HEIGHT - 1U - offset);
framebuffer_set_pixel(DISPLAY_WIDTH - 1U - offset,
DISPLAY_HEIGHT - 1U - offset);
}
}
}
static void print_display_usage(void)
{
printf("Usage:\n");
printf(" debug display status|probe\n");
printf(" debug display scan --force\n");
printf(" debug display init [0x3c|0x3d|0x78|0x79|0x7a|0x7b]\n");
printf(" debug display off\n");
printf(" debug display pattern <clear|fill|checker|grid|corners>\n");
printf(" debug display row <0..63>\n");
printf(" debug display contrast <0..255>\n");
printf(" debug display invert <on|off>\n");
}
static int command_display_status(int argc, char **argv)
{
(void)argv;
if (argc != 1) {
print_display_usage();
return 1;
}
printf("Local UI diagnostics: init=%s bus=%s buttons=%s last-init=%s\n",
s_initialization_error == ESP_OK ? "ok" : "failed",
s_bus_ready ? "ready" : "unavailable",
s_buttons_ready ? "ready" : "unavailable",
esp_err_to_name(s_initialization_error));
if (s_display_address == 0U) {
printf("OLED: address=none initialized=%s speed=%u Hz contrast=%u inverted=%s\n",
s_panel_initialized ? "yes" : "no",
(unsigned int)DISPLAY_I2C_SPEED_HZ,
(unsigned int)s_contrast,
s_inverted ? "yes" : "no");
} else {
printf("OLED: 7-bit=0x%02x 8-bit=0x%02x/0x%02x initialized=%s speed=%u Hz contrast=%u inverted=%s\n",
s_display_address,
(unsigned int)(s_display_address << 1U),
(unsigned int)((s_display_address << 1U) | 1U),
s_panel_initialized ? "yes" : "no",
(unsigned int)DISPLAY_I2C_SPEED_HZ,
(unsigned int)s_contrast,
s_inverted ? "yes" : "no");
}
printf("Pins: SDA=%d level=%d SCL=%d level=%d; buttons previous=%d select=%d next=%d\n",
LOCAL_UI_DISPLAY_SDA_GPIO, gpio_get_level(LOCAL_UI_DISPLAY_SDA_GPIO),
LOCAL_UI_DISPLAY_SCL_GPIO, gpio_get_level(LOCAL_UI_DISPLAY_SCL_GPIO),
LOCAL_UI_BUTTON_PREVIOUS_GPIO,
LOCAL_UI_BUTTON_SELECT_GPIO,
LOCAL_UI_BUTTON_NEXT_GPIO);
return s_bus_ready && s_buttons_ready ? 0 : 1;
}
static int command_display_probe(int argc, char **argv)
{
(void)argv;
if (argc != 1) {
print_display_usage();
return 1;
}
uint8_t address = 0U;
esp_err_t error = probe_expected_addresses(&address);
if (error != ESP_OK) {
return report_error("Display probe", error);
}
return 0;
}
static int command_display_scan(int argc, char **argv)
{
if (argc != 2 || strcmp(argv[1], "--force") != 0) {
printf("A full usable-address scan sends an address probe to every 7-bit address from 0x08 through 0x77.\n");
print_display_usage();
return 1;
}
if (!s_bus_ready) {
return report_error("Display scan", s_initialization_error);
}
printf("Scanning usable 7-bit I2C addresses 0x08..0x77 at %u Hz.\n",
(unsigned int)DISPLAY_I2C_SPEED_HZ);
uint32_t found = 0U;
for (uint16_t address = 0x08U; address <= 0x77U; ++address) {
esp_err_t error = i2c_master_probe(s_bus, address, DISPLAY_SCAN_TIMEOUT_MS);
if (error == ESP_OK) {
printf(" response: 7-bit 0x%02x (8-bit 0x%02x write / 0x%02x read)\n",
(unsigned int)address,
(unsigned int)(address << 1U),
(unsigned int)((address << 1U) | 1U));
++found;
} else if (error != ESP_ERR_NOT_FOUND && error != ESP_ERR_TIMEOUT) {
printf(" 7-bit 0x%02x: %s\n", (unsigned int)address,
esp_err_to_name(error));
}
vTaskDelay(1U);
}
printf("I2C scan complete: %u responding address%s.\n", (unsigned int)found,
found == 1U ? "" : "es");
return found == 0U ? 1 : 0;
}
static int command_display_init(int argc, char **argv)
{
if (argc > 2) {
print_display_usage();
return 1;
}
uint8_t address = 0U;
if (argc == 2) {
if (!parse_display_address(argv[1], &address)) {
printf("Display address must be 7-bit 0x3c/0x3d or their 8-bit write/read forms.\n");
return 1;
}
} else {
esp_err_t error = probe_expected_addresses(&address);
if (error != ESP_OK) {
return report_error("Display probe", error);
}
}
esp_err_t error = display_initialize(address);
if (error != ESP_OK) {
return report_error("Display initialization", error);
}
printf("SSD1315-compatible 128x64 display initialized at 7-bit 0x%02x (8-bit 0x%02x/0x%02x), %u Hz.\n",
address, (unsigned int)(address << 1U),
(unsigned int)((address << 1U) | 1U),
(unsigned int)DISPLAY_I2C_SPEED_HZ);
return 0;
}
static int command_display_off(int argc, char **argv)
{
(void)argv;
if (argc != 1) {
print_display_usage();
return 1;
}
if (!s_panel_initialized) {
return report_error("Display off", ESP_ERR_INVALID_STATE);
}
esp_err_t error = display_send_command(0xaeU);
if (error != ESP_OK) {
return report_error("Display off", error);
}
s_panel_initialized = false;
printf("Display switched off; run 'debug display init' to reinitialize it.\n");
return 0;
}
static bool pattern_name_valid(const char *name)
{
return strcmp(name, "clear") == 0 || strcmp(name, "fill") == 0 ||
strcmp(name, "checker") == 0 || strcmp(name, "grid") == 0 ||
strcmp(name, "corners") == 0;
}
static int command_display_pattern(int argc, char **argv)
{
if (argc != 2 || !pattern_name_valid(argv[1])) {
print_display_usage();
return 1;
}
if (!s_panel_initialized) {
return report_error("Display pattern", ESP_ERR_INVALID_STATE);
}
framebuffer_draw_pattern(argv[1]);
esp_err_t error = display_refresh();
if (error != ESP_OK) {
return report_error("Display pattern", error);
}
printf("Displayed '%s' test pattern.\n", argv[1]);
return 0;
}
static int command_display_row(int argc, char **argv)
{
unsigned long row = 0U;
if (argc != 2 || !parse_unsigned(argv[1], 0U, DISPLAY_HEIGHT - 1U, &row)) {
print_display_usage();
return 1;
}
if (!s_panel_initialized) {
return report_error("Display row", ESP_ERR_INVALID_STATE);
}
memset(s_framebuffer, 0, sizeof(s_framebuffer));
for (uint32_t x = 0U; x < DISPLAY_WIDTH; ++x) {
framebuffer_set_pixel(x, (uint32_t)row);
}
esp_err_t error = display_refresh();
if (error != ESP_OK) {
return report_error("Display row", error);
}
printf("Displayed one-pixel horizontal line at row %lu.\n", row);
return 0;
}
static int command_display_contrast(int argc, char **argv)
{
unsigned long contrast = 0U;
if (argc != 2 || !parse_unsigned(argv[1], 0U, 255U, &contrast)) {
print_display_usage();
return 1;
}
if (!s_panel_initialized) {
return report_error("Display contrast", ESP_ERR_INVALID_STATE);
}
const uint8_t commands[] = {0x81U, (uint8_t)contrast};
esp_err_t error = display_send_commands(commands, sizeof(commands));
if (error != ESP_OK) {
return report_error("Display contrast", error);
}
s_contrast = (uint8_t)contrast;
printf("Display contrast set to %lu.\n", contrast);
return 0;
}
static int command_display_invert(int argc, char **argv)
{
if (argc != 2 || (strcmp(argv[1], "on") != 0 && strcmp(argv[1], "off") != 0)) {
print_display_usage();
return 1;
}
if (!s_panel_initialized) {
return report_error("Display inversion", ESP_ERR_INVALID_STATE);
}
bool invert = strcmp(argv[1], "on") == 0;
esp_err_t error = display_send_command(invert ? 0xa7U : 0xa6U);
if (error != ESP_OK) {
return report_error("Display inversion", error);
}
s_inverted = invert;
printf("Display inversion %s.\n", invert ? "enabled" : "disabled");
return 0;
}
static int command_display(int argc, char **argv)
{
if (argc < 2 || strcmp(argv[1], "help") == 0) {
print_display_usage();
return argc < 2 || argc == 2 ? 0 : 1;
}
if (strcmp(argv[1], "status") == 0) {
return command_display_status(argc - 1, argv + 1);
}
if (strcmp(argv[1], "probe") == 0) {
return command_display_probe(argc - 1, argv + 1);
}
if (strcmp(argv[1], "scan") == 0) {
return command_display_scan(argc - 1, argv + 1);
}
if (strcmp(argv[1], "init") == 0) {
return command_display_init(argc - 1, argv + 1);
}
if (strcmp(argv[1], "off") == 0) {
return command_display_off(argc - 1, argv + 1);
}
if (strcmp(argv[1], "pattern") == 0) {
return command_display_pattern(argc - 1, argv + 1);
}
if (strcmp(argv[1], "row") == 0) {
return command_display_row(argc - 1, argv + 1);
}
if (strcmp(argv[1], "contrast") == 0) {
return command_display_contrast(argc - 1, argv + 1);
}
if (strcmp(argv[1], "invert") == 0) {
return command_display_invert(argc - 1, argv + 1);
}
printf("Unknown display diagnostic '%s'.\n", argv[1]);
print_display_usage();
return 1;
}
static void print_buttons_usage(void)
{
printf("Usage:\n");
printf(" debug buttons status\n");
printf(" debug buttons test [seconds] (1..30, default 10)\n");
}
static int command_buttons_status(int argc, char **argv)
{
(void)argv;
if (argc != 1) {
print_buttons_usage();
return 1;
}
if (!s_buttons_ready) {
return report_error("Button status", s_initialization_error);
}
printf("Buttons are active-low with internal pull-ups:\n");
for (size_t index = 0U; index < sizeof(s_buttons) / sizeof(s_buttons[0]); ++index) {
int level = gpio_get_level(s_buttons[index].gpio);
printf(" %-14s GPIO%d level=%d %s\n",
s_buttons[index].name,
s_buttons[index].gpio,
level,
level == 0 ? "pressed" : "released");
}
return 0;
}
static int command_buttons_test(int argc, char **argv)
{
unsigned long seconds = BUTTON_TEST_DEFAULT_SECONDS;
if (argc > 2 ||
(argc == 2 && !parse_unsigned(argv[1], 1U, BUTTON_TEST_MAX_SECONDS,
&seconds))) {
print_buttons_usage();
return 1;
}
if (!s_buttons_ready) {
return report_error("Button test", s_initialization_error);
}
button_test_state_t states[sizeof(s_buttons) / sizeof(s_buttons[0])];
int64_t now = esp_timer_get_time();
for (size_t index = 0U; index < sizeof(s_buttons) / sizeof(s_buttons[0]); ++index) {
int level = gpio_get_level(s_buttons[index].gpio);
states[index] = (button_test_state_t){
.raw_level = level,
/* Start released; a held-low input must pass through debounce. */
.stable_level = 1,
.raw_changed_us = now,
.pressed_us = 0,
};
}
int64_t deadline = now + (int64_t)seconds * 1000000LL;
printf("Testing buttons for %lu second%s; short press each button and hold one for at least %u ms.\n",
seconds, seconds == 1U ? "" : "s",
(unsigned int)BUTTON_LONG_PRESS_MS);
while ((now = esp_timer_get_time()) < deadline) {
for (size_t index = 0U; index < sizeof(s_buttons) / sizeof(s_buttons[0]); ++index) {
button_test_state_t *state = &states[index];
int raw = gpio_get_level(s_buttons[index].gpio);
if (raw != state->raw_level) {
state->raw_level = raw;
state->raw_changed_us = now;
}
if (raw != state->stable_level &&
now - state->raw_changed_us >= (int64_t)BUTTON_DEBOUNCE_MS * 1000LL) {
state->stable_level = raw;
++state->stable_transitions;
if (raw == 0) {
state->pressed_us = now;
state->long_reported = false;
printf("%-14s pressed\n", s_buttons[index].name);
} else {
int64_t duration_ms = state->pressed_us == 0
? 0
: (now - state->pressed_us) / 1000LL;
if (!state->long_reported) {
++state->short_presses;
printf("%-14s short release after %lld ms\n",
s_buttons[index].name,
(long long)duration_ms);
} else {
printf("%-14s released after %lld ms\n",
s_buttons[index].name,
(long long)duration_ms);
}
state->pressed_us = 0;
}
}
if (state->stable_level == 0 && !state->long_reported &&
state->pressed_us != 0 &&
now - state->pressed_us >= (int64_t)BUTTON_LONG_PRESS_MS * 1000LL) {
state->long_reported = true;
++state->long_presses;
printf("%-14s long press\n", s_buttons[index].name);
}
}
vTaskDelay(milliseconds_to_ticks(BUTTON_POLL_MS));
}
printf("Button test summary:\n");
bool stuck = false;
for (size_t index = 0U; index < sizeof(s_buttons) / sizeof(s_buttons[0]); ++index) {
bool pressed = gpio_get_level(s_buttons[index].gpio) == 0;
printf(" %-14s short=%u long=%u transitions=%u final=%s\n",
s_buttons[index].name,
(unsigned int)states[index].short_presses,
(unsigned int)states[index].long_presses,
(unsigned int)states[index].stable_transitions,
pressed ? "PRESSED" : "released");
stuck |= pressed;
}
if (stuck) {
printf("Warning: one or more buttons remained asserted; check for a held or stuck input.\n");
}
return 0;
}
static int command_buttons(int argc, char **argv)
{
if (argc < 2 || strcmp(argv[1], "help") == 0) {
print_buttons_usage();
return argc < 2 || argc == 2 ? 0 : 1;
}
if (strcmp(argv[1], "status") == 0) {
return command_buttons_status(argc - 1, argv + 1);
}
if (strcmp(argv[1], "test") == 0) {
return command_buttons_test(argc - 1, argv + 1);
}
printf("Unknown button diagnostic '%s'.\n", argv[1]);
print_buttons_usage();
return 1;
}
esp_err_t local_ui_hw_test_init(void)
{
if (s_bus_ready || s_buttons_ready) {
return ESP_ERR_INVALID_STATE;
}
esp_err_t button_error = configure_buttons();
esp_err_t bus_error = configure_i2c_bus();
s_initialization_error = button_error != ESP_OK ? button_error : bus_error;
return s_initialization_error;
}
int local_ui_hw_test_execute(int argc, char **argv)
{
if (argc < 1 || argv == NULL || argv[0] == NULL) {
return 1;
}
if (strcmp(argv[0], "display") == 0) {
return command_display(argc, argv);
}
if (strcmp(argv[0], "buttons") == 0) {
return command_buttons(argc, argv);
}
return 1;
}
void local_ui_hw_test_print_usage(void)
{
printf(" debug display [status|probe|scan|init|off|pattern|row|contrast|invert]\n");
printf(" debug buttons [status|test]\n");
}
+23
View File
@@ -0,0 +1,23 @@
/* SPDX-License-Identifier: GPL-3.0-only */
/* Phase 7A local display and button electrical diagnostics. */
#pragma once
#include "esp_err.h"
#ifdef __cplusplus
extern "C" {
#endif
/* Configure the shared I2C bus and active-low button inputs without probing. */
esp_err_t local_ui_hw_test_init(void);
/* Handle argv beginning with either "display" or "buttons". */
int local_ui_hw_test_execute(int argc, char **argv);
/* Append the Phase 7A command forms to the existing debug-group usage. */
void local_ui_hw_test_print_usage(void);
#ifdef __cplusplus
}
#endif
+9 -1
View File
@@ -5,6 +5,7 @@
#include "esp_log.h"
#include "esp_psram.h"
#include "network_console.h"
#include "local_ui_hw_test.h"
#include "rs232_hw_test.h"
#include "rs232_port_owner.h"
#include "secure_random.h"
@@ -35,7 +36,7 @@ static const char *TAG = "firmware";
void app_main(void)
{
ESP_LOGI(TAG, "ESP32-S3 Serial Swiss Army Knife SSH transport phase started");
ESP_LOGI(TAG, "ESP32-S3 Serial Swiss Army Knife Phase 7A diagnostics started");
if (esp_psram_is_initialized()) {
ESP_LOGI(TAG, "PSRAM initialized: %u bytes", (unsigned int)esp_psram_get_size());
@@ -55,6 +56,13 @@ void app_main(void)
ESP_ERROR_CHECK(rs232_port_owner_init());
ESP_ERROR_CHECK(rs232_hw_test_init());
/* A missing or miswired optional display must never remove recovery paths. */
esp_err_t local_ui_error = local_ui_hw_test_init();
if (local_ui_error != ESP_OK) {
ESP_LOGW(TAG, "Local UI diagnostics unavailable: %s",
esp_err_to_name(local_ui_error));
}
serial_config_t serial_config;
bool used_stored_config = false;
esp_err_t config_error = serial_config_load(&serial_config, &used_stored_config);
+7 -1
View File
@@ -17,6 +17,7 @@
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "freertos/task.h"
#include "local_ui_hw_test.h"
#include "rs232_port_owner.h"
#include "status_led.h"
@@ -1583,6 +1584,7 @@ static void print_debug_usage(void)
printf(" debug loopback-a|loopback-b|valid-test\n");
printf(" debug uart-loopback <baud> [format] [bytes]\n");
printf(" debug uart-suite|cts-flow-test|rts-flow-test\n");
local_ui_hw_test_print_usage();
}
static int command_debug(int argc, char **argv)
@@ -1620,6 +1622,10 @@ static int command_debug(int argc, char **argv)
if (strcmp(argv[1], "rts-flow-test") == 0) {
return command_rts_flow_test(argc - 1, argv + 1);
}
if (strcmp(argv[1], "display") == 0 ||
strcmp(argv[1], "buttons") == 0) {
return local_ui_hw_test_execute(argc - 1, argv + 1);
}
printf("Unknown debug command '%s'.\n", argv[1]);
print_debug_usage();
@@ -1650,7 +1656,7 @@ esp_err_t rs232_hw_test_register_console_commands(void)
},
{
.command = "debug",
.help = "Low-level RS-232 hardware diagnostics; run 'debug' for subcommands",
.help = "Low-level RS-232 and local-UI hardware diagnostics; run 'debug' for subcommands",
.hint = NULL,
.func = &command_debug,
.argtable = NULL,
+1 -1
View File
@@ -5,5 +5,5 @@
/* Configure all MAX3243 logic-side signals in their safe static-test state. */
esp_err_t rs232_hw_test_init(void);
/* Register top-level status and the Phase 0 `debug` submenu. */
/* Register top-level status and the shared low-level `debug` submenu. */
esp_err_t rs232_hw_test_register_console_commands(void);