Add Panel-Aware Local OLED Display Service
This commit is contained in:
@@ -6,6 +6,7 @@ idf_component_register(
|
||||
"system_console.c"
|
||||
"secure_random.c"
|
||||
"status_led.c"
|
||||
"local_display.c"
|
||||
"local_ui_hw_test.c"
|
||||
"rs232_hw_test.c"
|
||||
"rs232_port_owner.c"
|
||||
|
||||
@@ -43,6 +43,7 @@ static const char *const s_completion_candidates[] = {
|
||||
"debug display pattern checker",
|
||||
"debug display pattern grid",
|
||||
"debug display pattern corners",
|
||||
"debug display pattern layout",
|
||||
"debug display row",
|
||||
"debug display contrast",
|
||||
"debug display invert",
|
||||
|
||||
@@ -0,0 +1,623 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-only */
|
||||
/* Bounded SSD1315-compatible OLED service with separate physical panels. */
|
||||
|
||||
#include "local_display.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "board_pins.h"
|
||||
#include "driver/i2c_master.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
#define LOCAL_DISPLAY_PAGE_COUNT (LOCAL_DISPLAY_HEIGHT / 8U)
|
||||
#define LOCAL_DISPLAY_FRAMEBUFFER_SIZE (LOCAL_DISPLAY_WIDTH * LOCAL_DISPLAY_PAGE_COUNT)
|
||||
#define LOCAL_DISPLAY_I2C_SPEED_HZ 100000U
|
||||
#define LOCAL_DISPLAY_I2C_TIMEOUT_MS 250U
|
||||
#define LOCAL_DISPLAY_PROBE_TIMEOUT_MS 50U
|
||||
#define LOCAL_DISPLAY_SCAN_TIMEOUT_MS 10U
|
||||
#define LOCAL_DISPLAY_LOCK_TIMEOUT_MS 3000U
|
||||
#define LOCAL_DISPLAY_DEFAULT_CONTRAST 127U
|
||||
#define LOCAL_DISPLAY_COMMAND_CAPACITY 32U
|
||||
#define LOCAL_DISPLAY_DATA_CHUNK_SIZE 128U
|
||||
|
||||
static const uint8_t s_expected_addresses[] = {0x3cU, 0x3dU};
|
||||
|
||||
typedef struct {
|
||||
char character;
|
||||
uint8_t columns[5];
|
||||
} glyph_t;
|
||||
|
||||
/* Compact 5x7 ASCII subset: lower-case input maps to upper-case glyphs. */
|
||||
static const glyph_t s_glyphs[] = {
|
||||
{' ', {0x00, 0x00, 0x00, 0x00, 0x00}},
|
||||
{'!', {0x00, 0x00, 0x5f, 0x00, 0x00}},
|
||||
{'-', {0x08, 0x08, 0x08, 0x08, 0x08}},
|
||||
{'.', {0x00, 0x60, 0x60, 0x00, 0x00}},
|
||||
{'/', {0x20, 0x10, 0x08, 0x04, 0x02}},
|
||||
{':', {0x00, 0x36, 0x36, 0x00, 0x00}},
|
||||
{'?', {0x02, 0x01, 0x51, 0x09, 0x06}},
|
||||
{'_', {0x40, 0x40, 0x40, 0x40, 0x40}},
|
||||
{'0', {0x3e, 0x51, 0x49, 0x45, 0x3e}},
|
||||
{'1', {0x00, 0x42, 0x7f, 0x40, 0x00}},
|
||||
{'2', {0x42, 0x61, 0x51, 0x49, 0x46}},
|
||||
{'3', {0x21, 0x41, 0x45, 0x4b, 0x31}},
|
||||
{'4', {0x18, 0x14, 0x12, 0x7f, 0x10}},
|
||||
{'5', {0x27, 0x45, 0x45, 0x45, 0x39}},
|
||||
{'6', {0x3c, 0x4a, 0x49, 0x49, 0x30}},
|
||||
{'7', {0x01, 0x71, 0x09, 0x05, 0x03}},
|
||||
{'8', {0x36, 0x49, 0x49, 0x49, 0x36}},
|
||||
{'9', {0x06, 0x49, 0x49, 0x29, 0x1e}},
|
||||
{'A', {0x7e, 0x11, 0x11, 0x11, 0x7e}},
|
||||
{'B', {0x7f, 0x49, 0x49, 0x49, 0x36}},
|
||||
{'C', {0x3e, 0x41, 0x41, 0x41, 0x22}},
|
||||
{'D', {0x7f, 0x41, 0x41, 0x22, 0x1c}},
|
||||
{'E', {0x7f, 0x49, 0x49, 0x49, 0x41}},
|
||||
{'F', {0x7f, 0x09, 0x09, 0x09, 0x01}},
|
||||
{'G', {0x3e, 0x41, 0x49, 0x49, 0x7a}},
|
||||
{'H', {0x7f, 0x08, 0x08, 0x08, 0x7f}},
|
||||
{'I', {0x00, 0x41, 0x7f, 0x41, 0x00}},
|
||||
{'J', {0x20, 0x40, 0x41, 0x3f, 0x01}},
|
||||
{'K', {0x7f, 0x08, 0x14, 0x22, 0x41}},
|
||||
{'L', {0x7f, 0x40, 0x40, 0x40, 0x40}},
|
||||
{'M', {0x7f, 0x02, 0x0c, 0x02, 0x7f}},
|
||||
{'N', {0x7f, 0x04, 0x08, 0x10, 0x7f}},
|
||||
{'O', {0x3e, 0x41, 0x41, 0x41, 0x3e}},
|
||||
{'P', {0x7f, 0x09, 0x09, 0x09, 0x06}},
|
||||
{'Q', {0x3e, 0x41, 0x51, 0x21, 0x5e}},
|
||||
{'R', {0x7f, 0x09, 0x19, 0x29, 0x46}},
|
||||
{'S', {0x46, 0x49, 0x49, 0x49, 0x31}},
|
||||
{'T', {0x01, 0x01, 0x7f, 0x01, 0x01}},
|
||||
{'U', {0x3f, 0x40, 0x40, 0x40, 0x3f}},
|
||||
{'V', {0x1f, 0x20, 0x40, 0x20, 0x1f}},
|
||||
{'W', {0x7f, 0x20, 0x18, 0x20, 0x7f}},
|
||||
{'X', {0x63, 0x14, 0x08, 0x14, 0x63}},
|
||||
{'Y', {0x03, 0x04, 0x78, 0x04, 0x03}},
|
||||
{'Z', {0x61, 0x51, 0x49, 0x45, 0x43}},
|
||||
};
|
||||
|
||||
static i2c_master_bus_handle_t s_bus;
|
||||
static i2c_master_dev_handle_t s_device;
|
||||
static StaticSemaphore_t s_mutex_storage;
|
||||
static SemaphoreHandle_t s_mutex;
|
||||
static uint8_t s_framebuffer[LOCAL_DISPLAY_FRAMEBUFFER_SIZE];
|
||||
static bool s_bus_ready;
|
||||
static bool s_initialized;
|
||||
static bool s_frame_active;
|
||||
static TaskHandle_t s_frame_owner;
|
||||
static uint8_t s_address;
|
||||
static uint8_t s_contrast = LOCAL_DISPLAY_DEFAULT_CONTRAST;
|
||||
static bool s_inverted;
|
||||
static uint8_t s_dirty_pages;
|
||||
static esp_err_t s_last_error = ESP_ERR_INVALID_STATE;
|
||||
|
||||
static TickType_t milliseconds_to_ticks(uint32_t milliseconds)
|
||||
{
|
||||
TickType_t ticks = pdMS_TO_TICKS(milliseconds);
|
||||
return (milliseconds > 0U && ticks == 0U) ? 1U : ticks;
|
||||
}
|
||||
|
||||
static esp_err_t take_lock(void)
|
||||
{
|
||||
if (s_mutex == NULL) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
return xSemaphoreTake(s_mutex, milliseconds_to_ticks(LOCAL_DISPLAY_LOCK_TIMEOUT_MS)) == pdTRUE
|
||||
? ESP_OK
|
||||
: ESP_ERR_TIMEOUT;
|
||||
}
|
||||
|
||||
static void give_lock(void)
|
||||
{
|
||||
if (s_mutex != NULL) {
|
||||
(void)xSemaphoreGive(s_mutex);
|
||||
}
|
||||
}
|
||||
|
||||
static void set_last_error(esp_err_t error)
|
||||
{
|
||||
s_last_error = error;
|
||||
}
|
||||
|
||||
static esp_err_t send_commands_locked(const uint8_t *commands, size_t count)
|
||||
{
|
||||
if (!s_initialized || s_device == NULL || commands == NULL || count == 0U ||
|
||||
count > LOCAL_DISPLAY_COMMAND_CAPACITY) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
uint8_t transfer[LOCAL_DISPLAY_COMMAND_CAPACITY + 1U];
|
||||
transfer[0] = 0x00U;
|
||||
memcpy(&transfer[1], commands, count);
|
||||
return i2c_master_transmit(s_device, transfer, count + 1U,
|
||||
LOCAL_DISPLAY_I2C_TIMEOUT_MS);
|
||||
}
|
||||
|
||||
static esp_err_t send_command_locked(uint8_t command)
|
||||
{
|
||||
return send_commands_locked(&command, 1U);
|
||||
}
|
||||
|
||||
static esp_err_t flush_dirty_locked(void)
|
||||
{
|
||||
if (!s_initialized || s_device == NULL) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
uint8_t transfer[LOCAL_DISPLAY_DATA_CHUNK_SIZE + 1U];
|
||||
transfer[0] = 0x40U;
|
||||
for (uint8_t page = 0U; page < LOCAL_DISPLAY_PAGE_COUNT; ++page) {
|
||||
uint8_t page_mask = (uint8_t)(1U << page);
|
||||
if ((s_dirty_pages & page_mask) == 0U) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const uint8_t commands[] = {
|
||||
0x21U, 0x00U, (uint8_t)(LOCAL_DISPLAY_WIDTH - 1U),
|
||||
0x22U, page, page,
|
||||
};
|
||||
esp_err_t error = send_commands_locked(commands, sizeof(commands));
|
||||
if (error != ESP_OK) {
|
||||
return error;
|
||||
}
|
||||
memcpy(&transfer[1], &s_framebuffer[(size_t)page * LOCAL_DISPLAY_WIDTH],
|
||||
LOCAL_DISPLAY_WIDTH);
|
||||
error = i2c_master_transmit(s_device, transfer, sizeof(transfer),
|
||||
LOCAL_DISPLAY_I2C_TIMEOUT_MS);
|
||||
if (error != ESP_OK) {
|
||||
return error;
|
||||
}
|
||||
s_dirty_pages &= (uint8_t)~page_mask;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static void set_pixel_raw(uint8_t x, uint8_t y, bool on)
|
||||
{
|
||||
if (x >= LOCAL_DISPLAY_WIDTH || y >= LOCAL_DISPLAY_HEIGHT) {
|
||||
return;
|
||||
}
|
||||
size_t index = (size_t)(y / 8U) * LOCAL_DISPLAY_WIDTH + x;
|
||||
uint8_t mask = (uint8_t)(1U << (y & 7U));
|
||||
uint8_t before = s_framebuffer[index];
|
||||
if (on) {
|
||||
s_framebuffer[index] |= mask;
|
||||
} else {
|
||||
s_framebuffer[index] &= (uint8_t)~mask;
|
||||
}
|
||||
if (before != s_framebuffer[index]) {
|
||||
s_dirty_pages |= (uint8_t)(1U << (y / 8U));
|
||||
}
|
||||
}
|
||||
|
||||
static bool panel_geometry(local_display_panel_t panel, uint8_t *origin_y, uint8_t *height)
|
||||
{
|
||||
if (origin_y == NULL || height == NULL) {
|
||||
return false;
|
||||
}
|
||||
switch (panel) {
|
||||
case LOCAL_DISPLAY_PANEL_STATUS:
|
||||
*origin_y = 0U;
|
||||
*height = LOCAL_DISPLAY_STATUS_HEIGHT;
|
||||
return true;
|
||||
case LOCAL_DISPLAY_PANEL_CONTENT:
|
||||
*origin_y = LOCAL_DISPLAY_STATUS_HEIGHT;
|
||||
*height = LOCAL_DISPLAY_CONTENT_HEIGHT;
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static const glyph_t *find_glyph(char character)
|
||||
{
|
||||
if (character >= 'a' && character <= 'z') {
|
||||
character = (char)(character - ('a' - 'A'));
|
||||
}
|
||||
for (size_t index = 0U; index < sizeof(s_glyphs) / sizeof(s_glyphs[0]); ++index) {
|
||||
if (s_glyphs[index].character == character) {
|
||||
return &s_glyphs[index];
|
||||
}
|
||||
}
|
||||
for (size_t index = 0U; index < sizeof(s_glyphs) / sizeof(s_glyphs[0]); ++index) {
|
||||
if (s_glyphs[index].character == '?') {
|
||||
return &s_glyphs[index];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static esp_err_t select_device_locked(uint8_t address)
|
||||
{
|
||||
if (address != 0x3cU && address != 0x3dU) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
esp_err_t error = i2c_master_probe(s_bus, address, LOCAL_DISPLAY_PROBE_TIMEOUT_MS);
|
||||
if (error != ESP_OK) {
|
||||
return error;
|
||||
}
|
||||
if (s_device != NULL && s_address == address) {
|
||||
return ESP_OK;
|
||||
}
|
||||
if (s_device != NULL) {
|
||||
error = i2c_master_bus_rm_device(s_device);
|
||||
if (error != ESP_OK) {
|
||||
return error;
|
||||
}
|
||||
s_device = NULL;
|
||||
s_initialized = false;
|
||||
}
|
||||
|
||||
const i2c_device_config_t config = {
|
||||
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
|
||||
.device_address = address,
|
||||
.scl_speed_hz = LOCAL_DISPLAY_I2C_SPEED_HZ,
|
||||
};
|
||||
error = i2c_master_bus_add_device(s_bus, &config, &s_device);
|
||||
if (error == ESP_OK) {
|
||||
s_address = address;
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
static esp_err_t initialize_locked(uint8_t address)
|
||||
{
|
||||
s_initialized = false;
|
||||
esp_err_t error = select_device_locked(address);
|
||||
if (error != ESP_OK) {
|
||||
return error;
|
||||
}
|
||||
|
||||
const uint8_t commands[] = {
|
||||
0xaeU, 0xd5U, 0x80U, 0xa8U, 0x3fU, 0xd3U, 0x00U, 0x40U,
|
||||
0x8dU, 0x14U, 0x20U, 0x00U, 0xa1U, 0xc8U, 0xdaU, 0x12U,
|
||||
0x81U, LOCAL_DISPLAY_DEFAULT_CONTRAST, 0xd9U, 0xf1U, 0xdbU,
|
||||
0x40U, 0xa4U, 0xa6U, 0x2eU,
|
||||
};
|
||||
|
||||
/* send_commands_locked requires initialized, so issue the bootstrap directly. */
|
||||
uint8_t transfer[sizeof(commands) + 1U];
|
||||
transfer[0] = 0x00U;
|
||||
memcpy(&transfer[1], commands, sizeof(commands));
|
||||
error = i2c_master_transmit(s_device, transfer, sizeof(transfer),
|
||||
LOCAL_DISPLAY_I2C_TIMEOUT_MS);
|
||||
if (error != ESP_OK) {
|
||||
return error;
|
||||
}
|
||||
|
||||
s_initialized = true;
|
||||
s_contrast = LOCAL_DISPLAY_DEFAULT_CONTRAST;
|
||||
s_inverted = false;
|
||||
memset(s_framebuffer, 0, sizeof(s_framebuffer));
|
||||
s_dirty_pages = (uint8_t)((1U << LOCAL_DISPLAY_PAGE_COUNT) - 1U);
|
||||
error = flush_dirty_locked();
|
||||
if (error == ESP_OK) {
|
||||
error = send_command_locked(0xafU);
|
||||
}
|
||||
if (error != ESP_OK) {
|
||||
s_initialized = false;
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
esp_err_t local_display_init(void)
|
||||
{
|
||||
if (s_bus_ready || s_mutex != NULL) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
s_mutex = xSemaphoreCreateMutexStatic(&s_mutex_storage);
|
||||
if (s_mutex == NULL) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
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) {
|
||||
vSemaphoreDelete(s_mutex);
|
||||
s_mutex = NULL;
|
||||
set_last_error(error);
|
||||
return error;
|
||||
}
|
||||
s_bus_ready = true;
|
||||
set_last_error(ESP_OK);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t local_display_probe_expected(uint8_t *address_7bit)
|
||||
{
|
||||
if (address_7bit == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (!s_bus_ready) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
esp_err_t error = take_lock();
|
||||
if (error != ESP_OK) {
|
||||
return error;
|
||||
}
|
||||
|
||||
uint8_t found = 0U;
|
||||
for (size_t index = 0U; index < sizeof(s_expected_addresses) / sizeof(s_expected_addresses[0]); ++index) {
|
||||
uint8_t address = s_expected_addresses[index];
|
||||
if (i2c_master_probe(s_bus, address, LOCAL_DISPLAY_PROBE_TIMEOUT_MS) == ESP_OK) {
|
||||
if (found == 0U) {
|
||||
*address_7bit = address;
|
||||
}
|
||||
++found;
|
||||
}
|
||||
}
|
||||
error = found == 0U ? ESP_ERR_NOT_FOUND : ESP_OK;
|
||||
set_last_error(error);
|
||||
give_lock();
|
||||
return error;
|
||||
}
|
||||
|
||||
esp_err_t local_display_start_at(uint8_t address_7bit)
|
||||
{
|
||||
if (!s_bus_ready) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
esp_err_t error = take_lock();
|
||||
if (error != ESP_OK) {
|
||||
return error;
|
||||
}
|
||||
error = initialize_locked(address_7bit);
|
||||
set_last_error(error);
|
||||
give_lock();
|
||||
return error;
|
||||
}
|
||||
|
||||
esp_err_t local_display_start(void)
|
||||
{
|
||||
uint8_t address = 0U;
|
||||
esp_err_t error = local_display_probe_expected(&address);
|
||||
if (error != ESP_OK) {
|
||||
return error;
|
||||
}
|
||||
return local_display_start_at(address);
|
||||
}
|
||||
|
||||
esp_err_t local_display_stop(void)
|
||||
{
|
||||
if (!s_bus_ready) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
esp_err_t error = take_lock();
|
||||
if (error != ESP_OK) {
|
||||
return error;
|
||||
}
|
||||
if (!s_initialized) {
|
||||
error = ESP_ERR_INVALID_STATE;
|
||||
} else {
|
||||
error = send_command_locked(0xaeU);
|
||||
/* A failed command leaves panel state unknown; force a clean reinit. */
|
||||
s_initialized = false;
|
||||
}
|
||||
set_last_error(error);
|
||||
give_lock();
|
||||
return error;
|
||||
}
|
||||
|
||||
esp_err_t local_display_get_snapshot(local_display_snapshot_t *snapshot)
|
||||
{
|
||||
if (snapshot == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
esp_err_t error = take_lock();
|
||||
if (error != ESP_OK) {
|
||||
return error;
|
||||
}
|
||||
*snapshot = (local_display_snapshot_t){
|
||||
.bus_ready = s_bus_ready,
|
||||
.initialized = s_initialized,
|
||||
.address_7bit = s_address,
|
||||
.contrast = s_contrast,
|
||||
.inverted = s_inverted,
|
||||
.dirty_page_mask = s_dirty_pages,
|
||||
.last_error = s_last_error,
|
||||
};
|
||||
give_lock();
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t local_display_scan(local_display_scan_callback_t callback,
|
||||
void *context,
|
||||
size_t *responding_count)
|
||||
{
|
||||
if (!s_bus_ready) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
esp_err_t error = take_lock();
|
||||
if (error != ESP_OK) {
|
||||
return error;
|
||||
}
|
||||
|
||||
uint8_t responses[0x78U - 0x08U];
|
||||
size_t found = 0U;
|
||||
for (uint16_t address = 0x08U; address <= 0x77U; ++address) {
|
||||
if (i2c_master_probe(s_bus, address, LOCAL_DISPLAY_SCAN_TIMEOUT_MS) == ESP_OK) {
|
||||
responses[found++] = (uint8_t)address;
|
||||
}
|
||||
vTaskDelay(1U);
|
||||
}
|
||||
if (responding_count != NULL) {
|
||||
*responding_count = found;
|
||||
}
|
||||
error = found == 0U ? ESP_ERR_NOT_FOUND : ESP_OK;
|
||||
set_last_error(error);
|
||||
give_lock();
|
||||
|
||||
/* Callers may safely use the display service from the callback. */
|
||||
if (callback != NULL) {
|
||||
for (size_t index = 0U; index < found; ++index) {
|
||||
callback(responses[index], context);
|
||||
}
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
esp_err_t local_display_set_contrast(uint8_t contrast)
|
||||
{
|
||||
esp_err_t error = take_lock();
|
||||
if (error != ESP_OK) {
|
||||
return error;
|
||||
}
|
||||
const uint8_t commands[] = {0x81U, contrast};
|
||||
error = send_commands_locked(commands, sizeof(commands));
|
||||
if (error == ESP_OK) {
|
||||
s_contrast = contrast;
|
||||
} else {
|
||||
s_initialized = false;
|
||||
}
|
||||
set_last_error(error);
|
||||
give_lock();
|
||||
return error;
|
||||
}
|
||||
|
||||
esp_err_t local_display_set_inverted(bool inverted)
|
||||
{
|
||||
esp_err_t error = take_lock();
|
||||
if (error != ESP_OK) {
|
||||
return error;
|
||||
}
|
||||
error = send_command_locked(inverted ? 0xa7U : 0xa6U);
|
||||
if (error == ESP_OK) {
|
||||
s_inverted = inverted;
|
||||
} else {
|
||||
s_initialized = false;
|
||||
}
|
||||
set_last_error(error);
|
||||
give_lock();
|
||||
return error;
|
||||
}
|
||||
|
||||
esp_err_t local_display_frame_begin(void)
|
||||
{
|
||||
esp_err_t error = take_lock();
|
||||
if (error != ESP_OK) {
|
||||
return error;
|
||||
}
|
||||
if (!s_initialized || s_frame_active) {
|
||||
give_lock();
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
s_frame_active = true;
|
||||
s_frame_owner = xTaskGetCurrentTaskHandle();
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t local_display_frame_end(void)
|
||||
{
|
||||
if (!s_frame_active || s_frame_owner != xTaskGetCurrentTaskHandle()) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
esp_err_t error = flush_dirty_locked();
|
||||
if (error != ESP_OK) {
|
||||
s_initialized = false;
|
||||
}
|
||||
set_last_error(error);
|
||||
s_frame_active = false;
|
||||
s_frame_owner = NULL;
|
||||
give_lock();
|
||||
return error;
|
||||
}
|
||||
|
||||
void local_display_frame_cancel(void)
|
||||
{
|
||||
if (s_frame_active && s_frame_owner == xTaskGetCurrentTaskHandle()) {
|
||||
s_frame_active = false;
|
||||
s_frame_owner = NULL;
|
||||
give_lock();
|
||||
}
|
||||
}
|
||||
|
||||
void local_display_frame_clear(local_display_panel_t panel)
|
||||
{
|
||||
if (!s_frame_active || s_frame_owner != xTaskGetCurrentTaskHandle()) {
|
||||
return;
|
||||
}
|
||||
uint8_t origin_y = 0U;
|
||||
uint8_t height = 0U;
|
||||
if (!panel_geometry(panel, &origin_y, &height)) {
|
||||
return;
|
||||
}
|
||||
for (uint8_t y = origin_y; y < origin_y + height; ++y) {
|
||||
for (uint8_t x = 0U; x < LOCAL_DISPLAY_WIDTH; ++x) {
|
||||
set_pixel_raw(x, y, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void local_display_frame_clear_all(void)
|
||||
{
|
||||
if (!s_frame_active || s_frame_owner != xTaskGetCurrentTaskHandle()) {
|
||||
return;
|
||||
}
|
||||
for (size_t index = 0U; index < sizeof(s_framebuffer); ++index) {
|
||||
if (s_framebuffer[index] != 0U) {
|
||||
s_framebuffer[index] = 0U;
|
||||
s_dirty_pages |= (uint8_t)(1U << (index / LOCAL_DISPLAY_WIDTH));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void local_display_frame_set_pixel(local_display_panel_t panel,
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
bool on)
|
||||
{
|
||||
if (!s_frame_active || s_frame_owner != xTaskGetCurrentTaskHandle()) {
|
||||
return;
|
||||
}
|
||||
uint8_t origin_y = 0U;
|
||||
uint8_t height = 0U;
|
||||
if (!panel_geometry(panel, &origin_y, &height) || x >= LOCAL_DISPLAY_WIDTH || y >= height) {
|
||||
return;
|
||||
}
|
||||
set_pixel_raw(x, (uint8_t)(origin_y + y), on);
|
||||
}
|
||||
|
||||
void local_display_frame_draw_text(local_display_panel_t panel,
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
const char *text)
|
||||
{
|
||||
if (!s_frame_active || s_frame_owner != xTaskGetCurrentTaskHandle() || text == NULL) {
|
||||
return;
|
||||
}
|
||||
uint8_t origin_y = 0U;
|
||||
uint8_t height = 0U;
|
||||
if (!panel_geometry(panel, &origin_y, &height) || y >= height) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t cursor_x = x;
|
||||
for (const char *character = text; *character != '\0'; ++character) {
|
||||
const glyph_t *glyph = find_glyph(*character);
|
||||
if (glyph == NULL || cursor_x + 5U > LOCAL_DISPLAY_WIDTH) {
|
||||
break;
|
||||
}
|
||||
for (uint8_t column = 0U; column < 5U; ++column) {
|
||||
for (uint8_t row = 0U; row < 7U; ++row) {
|
||||
if ((glyph->columns[column] & (uint8_t)(1U << row)) != 0U &&
|
||||
y + row < height) {
|
||||
set_pixel_raw((uint8_t)(cursor_x + column),
|
||||
(uint8_t)(origin_y + y + row), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
cursor_x += 6U;
|
||||
if (cursor_x >= LOCAL_DISPLAY_WIDTH) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-only */
|
||||
/* Bounded SSD1315-compatible local OLED service. */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "esp_err.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define LOCAL_DISPLAY_WIDTH 128U
|
||||
#define LOCAL_DISPLAY_HEIGHT 64U
|
||||
#define LOCAL_DISPLAY_STATUS_HEIGHT 16U
|
||||
#define LOCAL_DISPLAY_CONTENT_HEIGHT 48U
|
||||
|
||||
/* The physical black divider lies between status row 15 and content row 16. */
|
||||
typedef enum {
|
||||
LOCAL_DISPLAY_PANEL_STATUS = 0,
|
||||
LOCAL_DISPLAY_PANEL_CONTENT,
|
||||
} local_display_panel_t;
|
||||
|
||||
typedef struct {
|
||||
bool bus_ready;
|
||||
bool initialized;
|
||||
uint8_t address_7bit;
|
||||
uint8_t contrast;
|
||||
bool inverted;
|
||||
uint8_t dirty_page_mask;
|
||||
esp_err_t last_error;
|
||||
} local_display_snapshot_t;
|
||||
|
||||
typedef void (*local_display_scan_callback_t)(uint8_t address_7bit, void *context);
|
||||
|
||||
/* Set up I2C0 on the board-profile pins. No display probe occurs here. */
|
||||
esp_err_t local_display_init(void);
|
||||
|
||||
/* Probe standard OLED addresses and initialize the first responding display. */
|
||||
esp_err_t local_display_start(void);
|
||||
|
||||
/* Select and initialize one supported 7-bit address (0x3c or 0x3d). */
|
||||
esp_err_t local_display_start_at(uint8_t address_7bit);
|
||||
|
||||
/* Turn off the panel while preserving the I2C bus for later diagnostics/restart. */
|
||||
esp_err_t local_display_stop(void);
|
||||
|
||||
esp_err_t local_display_get_snapshot(local_display_snapshot_t *snapshot);
|
||||
esp_err_t local_display_probe_expected(uint8_t *address_7bit);
|
||||
|
||||
/* Bounded scan of usable 7-bit addresses 0x08 through 0x77. */
|
||||
esp_err_t local_display_scan(local_display_scan_callback_t callback,
|
||||
void *context,
|
||||
size_t *responding_count);
|
||||
|
||||
esp_err_t local_display_set_contrast(uint8_t contrast);
|
||||
esp_err_t local_display_set_inverted(bool inverted);
|
||||
|
||||
/*
|
||||
* A frame holds only the display's own mutex and is owned by the task that
|
||||
* begins it. Callers must never retain a service/broker mutex while beginning
|
||||
* or ending a frame. Only the owning task may end or cancel it; end sends only
|
||||
* modified 8-pixel pages and releases the display mutex on all outcomes.
|
||||
*/
|
||||
esp_err_t local_display_frame_begin(void);
|
||||
esp_err_t local_display_frame_end(void);
|
||||
void local_display_frame_cancel(void);
|
||||
|
||||
/* Drawing coordinates are panel-local and are clipped to the selected panel. */
|
||||
void local_display_frame_clear(local_display_panel_t panel);
|
||||
void local_display_frame_clear_all(void);
|
||||
void local_display_frame_set_pixel(local_display_panel_t panel,
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
bool on);
|
||||
void local_display_frame_draw_text(local_display_panel_t panel,
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
const char *text);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
+148
-411
@@ -1,5 +1,5 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-only */
|
||||
/* Phase 7A bounded SSD1315-compatible OLED and button diagnostics. */
|
||||
/* Phase 7A diagnostics built on the Phase 7B local display service. */
|
||||
|
||||
#include "local_ui_hw_test.h"
|
||||
|
||||
@@ -12,23 +12,11 @@
|
||||
|
||||
#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
|
||||
#include "local_display.h"
|
||||
|
||||
#define BUTTON_POLL_MS 10U
|
||||
#define BUTTON_DEBOUNCE_MS 30U
|
||||
@@ -36,9 +24,6 @@
|
||||
#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;
|
||||
@@ -61,16 +46,8 @@ static const button_definition_t s_buttons[] = {
|
||||
{.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 esp_err_t s_button_initialization_error = ESP_ERR_INVALID_STATE;
|
||||
|
||||
static TickType_t milliseconds_to_ticks(uint32_t milliseconds)
|
||||
{
|
||||
@@ -94,7 +71,6 @@ static bool parse_unsigned(const char *text,
|
||||
parsed < minimum || parsed > maximum) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*value = parsed;
|
||||
return true;
|
||||
}
|
||||
@@ -111,7 +87,6 @@ static bool parse_display_address(const char *text, uint8_t *address)
|
||||
if (errno != 0 || end == text || *end != '\0') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (parsed == 0x3cU || parsed == 0x78U || parsed == 0x79U) {
|
||||
*address = 0x3cU;
|
||||
return true;
|
||||
@@ -140,7 +115,6 @@ static esp_err_t configure_buttons(void)
|
||||
.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;
|
||||
@@ -148,264 +122,11 @@ static esp_err_t configure_buttons(void)
|
||||
return error;
|
||||
}
|
||||
|
||||
static esp_err_t configure_i2c_bus(void)
|
||||
static void print_address(uint8_t address)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
printf("7-bit 0x%02x (8-bit 0x%02x write / 0x%02x read)",
|
||||
address, (unsigned int)(address << 1U),
|
||||
(unsigned int)((address << 1U) | 1U));
|
||||
}
|
||||
|
||||
static void print_display_usage(void)
|
||||
@@ -415,7 +136,7 @@ static void print_display_usage(void)
|
||||
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 pattern <clear|fill|checker|grid|corners|layout>\n");
|
||||
printf(" debug display row <0..63>\n");
|
||||
printf(" debug display contrast <0..255>\n");
|
||||
printf(" debug display invert <on|off>\n");
|
||||
@@ -429,34 +150,31 @@ static int command_display_status(int argc, char **argv)
|
||||
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");
|
||||
local_display_snapshot_t snapshot;
|
||||
esp_err_t error = local_display_get_snapshot(&snapshot);
|
||||
if (error != ESP_OK) {
|
||||
return report_error("Display status", error);
|
||||
}
|
||||
printf("Local display: bus=%s initialized=%s last-error=%s dirty-pages=0x%02x\n",
|
||||
snapshot.bus_ready ? "ready" : "unavailable",
|
||||
snapshot.initialized ? "yes" : "no",
|
||||
esp_err_to_name(snapshot.last_error),
|
||||
snapshot.dirty_page_mask);
|
||||
if (snapshot.address_7bit != 0U) {
|
||||
printf("OLED: ");
|
||||
print_address(snapshot.address_7bit);
|
||||
printf(" contrast=%u inverted=%s\n", (unsigned int)snapshot.contrast,
|
||||
snapshot.inverted ? "yes" : "no");
|
||||
} else {
|
||||
printf("OLED: no selected address\n");
|
||||
}
|
||||
printf("Panels: status=128x16 rows 0..15; content=128x48 rows 16..63; physical black divider between them\n");
|
||||
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_PREVIOUS_GPIO, LOCAL_UI_BUTTON_SELECT_GPIO,
|
||||
LOCAL_UI_BUTTON_NEXT_GPIO);
|
||||
return s_bus_ready && s_buttons_ready ? 0 : 1;
|
||||
return snapshot.bus_ready && s_buttons_ready ? 0 : 1;
|
||||
}
|
||||
|
||||
static int command_display_probe(int argc, char **argv)
|
||||
@@ -468,13 +186,25 @@ static int command_display_probe(int argc, char **argv)
|
||||
}
|
||||
|
||||
uint8_t address = 0U;
|
||||
esp_err_t error = probe_expected_addresses(&address);
|
||||
esp_err_t error = local_display_probe_expected(&address);
|
||||
if (error != ESP_OK) {
|
||||
printf("No OLED response at expected 7-bit addresses 0x3c or 0x3d.\n");
|
||||
return report_error("Display probe", error);
|
||||
}
|
||||
printf("OLED response at ");
|
||||
print_address(address);
|
||||
printf(".\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void scan_print_callback(uint8_t address, void *context)
|
||||
{
|
||||
(void)context;
|
||||
printf(" response: ");
|
||||
print_address(address);
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
static int command_display_scan(int argc, char **argv)
|
||||
{
|
||||
if (argc != 2 || strcmp(argv[1], "--force") != 0) {
|
||||
@@ -482,30 +212,13 @@ static int command_display_scan(int argc, char **argv)
|
||||
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);
|
||||
}
|
||||
size_t found = 0U;
|
||||
printf("Scanning usable 7-bit I2C addresses 0x08..0x77 at 100000 Hz.\n");
|
||||
esp_err_t error = local_display_scan(scan_print_callback, NULL, &found);
|
||||
printf("I2C scan complete: %u responding address%s.\n", (unsigned int)found,
|
||||
found == 1U ? "" : "es");
|
||||
return found == 0U ? 1 : 0;
|
||||
return error == ESP_OK ? 0 : report_error("Display scan", error);
|
||||
}
|
||||
|
||||
static int command_display_init(int argc, char **argv)
|
||||
@@ -516,26 +229,27 @@ static int command_display_init(int argc, char **argv)
|
||||
}
|
||||
|
||||
uint8_t address = 0U;
|
||||
esp_err_t error;
|
||||
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;
|
||||
}
|
||||
error = local_display_start_at(address);
|
||||
} else {
|
||||
esp_err_t error = probe_expected_addresses(&address);
|
||||
if (error != ESP_OK) {
|
||||
return report_error("Display probe", error);
|
||||
error = local_display_start();
|
||||
if (error == ESP_OK) {
|
||||
local_display_snapshot_t snapshot;
|
||||
error = local_display_get_snapshot(&snapshot);
|
||||
address = snapshot.address_7bit;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
printf("SSD1315-compatible 128x64 display initialized at ");
|
||||
print_address(address);
|
||||
printf(".\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -546,15 +260,10 @@ static int command_display_off(int argc, char **argv)
|
||||
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);
|
||||
esp_err_t error = local_display_stop();
|
||||
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;
|
||||
}
|
||||
@@ -563,7 +272,66 @@ 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;
|
||||
strcmp(name, "corners") == 0 || strcmp(name, "layout") == 0;
|
||||
}
|
||||
|
||||
static void set_global_pixel(uint8_t x, uint8_t y, bool on)
|
||||
{
|
||||
if (y < LOCAL_DISPLAY_STATUS_HEIGHT) {
|
||||
local_display_frame_set_pixel(LOCAL_DISPLAY_PANEL_STATUS, x, y, on);
|
||||
} else {
|
||||
local_display_frame_set_pixel(LOCAL_DISPLAY_PANEL_CONTENT, x,
|
||||
(uint8_t)(y - LOCAL_DISPLAY_STATUS_HEIGHT), on);
|
||||
}
|
||||
}
|
||||
|
||||
static void draw_pattern(const char *name)
|
||||
{
|
||||
local_display_frame_clear_all();
|
||||
if (strcmp(name, "clear") == 0) {
|
||||
return;
|
||||
}
|
||||
if (strcmp(name, "fill") == 0) {
|
||||
for (uint8_t y = 0U; y < LOCAL_DISPLAY_HEIGHT; ++y) {
|
||||
for (uint8_t x = 0U; x < LOCAL_DISPLAY_WIDTH; ++x) {
|
||||
set_global_pixel(x, y, true);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (strcmp(name, "checker") == 0 || strcmp(name, "grid") == 0) {
|
||||
bool checker = strcmp(name, "checker") == 0;
|
||||
for (uint8_t y = 0U; y < LOCAL_DISPLAY_HEIGHT; ++y) {
|
||||
for (uint8_t x = 0U; x < LOCAL_DISPLAY_WIDTH; ++x) {
|
||||
bool on = checker ? (((x + y) & 1U) == 0U)
|
||||
: ((x % 8U) == 0U || (y % 8U) == 0U);
|
||||
if (on) {
|
||||
set_global_pixel(x, y, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (strcmp(name, "corners") == 0) {
|
||||
for (uint8_t x = 0U; x < LOCAL_DISPLAY_WIDTH; ++x) {
|
||||
set_global_pixel(x, 0U, true);
|
||||
set_global_pixel(x, LOCAL_DISPLAY_HEIGHT - 1U, true);
|
||||
}
|
||||
for (uint8_t y = 0U; y < LOCAL_DISPLAY_HEIGHT; ++y) {
|
||||
set_global_pixel(0U, y, true);
|
||||
set_global_pixel(LOCAL_DISPLAY_WIDTH - 1U, y, true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
local_display_frame_clear(LOCAL_DISPLAY_PANEL_STATUS);
|
||||
local_display_frame_clear(LOCAL_DISPLAY_PANEL_CONTENT);
|
||||
local_display_frame_draw_text(LOCAL_DISPLAY_PANEL_STATUS, 0U, 0U, "SER OK");
|
||||
local_display_frame_draw_text(LOCAL_DISPLAY_PANEL_STATUS, 0U, 8U, "WR NONE");
|
||||
local_display_frame_draw_text(LOCAL_DISPLAY_PANEL_CONTENT, 0U, 0U, "DISPLAY DRIVER");
|
||||
local_display_frame_draw_text(LOCAL_DISPLAY_PANEL_CONTENT, 0U, 8U, "STATUS 16 PX");
|
||||
local_display_frame_draw_text(LOCAL_DISPLAY_PANEL_CONTENT, 0U, 24U, "CONTENT 48 PX");
|
||||
local_display_frame_draw_text(LOCAL_DISPLAY_PANEL_CONTENT, 0U, 40U, "SEPARATE PANELS");
|
||||
}
|
||||
|
||||
static int command_display_pattern(int argc, char **argv)
|
||||
@@ -572,12 +340,12 @@ static int command_display_pattern(int argc, char **argv)
|
||||
print_display_usage();
|
||||
return 1;
|
||||
}
|
||||
if (!s_panel_initialized) {
|
||||
return report_error("Display pattern", ESP_ERR_INVALID_STATE);
|
||||
esp_err_t error = local_display_frame_begin();
|
||||
if (error != ESP_OK) {
|
||||
return report_error("Display pattern", error);
|
||||
}
|
||||
|
||||
framebuffer_draw_pattern(argv[1]);
|
||||
esp_err_t error = display_refresh();
|
||||
draw_pattern(argv[1]);
|
||||
error = local_display_frame_end();
|
||||
if (error != ESP_OK) {
|
||||
return report_error("Display pattern", error);
|
||||
}
|
||||
@@ -588,19 +356,19 @@ static int command_display_pattern(int argc, char **argv)
|
||||
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)) {
|
||||
if (argc != 2 || !parse_unsigned(argv[1], 0U, LOCAL_DISPLAY_HEIGHT - 1U, &row)) {
|
||||
print_display_usage();
|
||||
return 1;
|
||||
}
|
||||
if (!s_panel_initialized) {
|
||||
return report_error("Display row", ESP_ERR_INVALID_STATE);
|
||||
esp_err_t error = local_display_frame_begin();
|
||||
if (error != ESP_OK) {
|
||||
return report_error("Display row", error);
|
||||
}
|
||||
|
||||
memset(s_framebuffer, 0, sizeof(s_framebuffer));
|
||||
for (uint32_t x = 0U; x < DISPLAY_WIDTH; ++x) {
|
||||
framebuffer_set_pixel(x, (uint32_t)row);
|
||||
local_display_frame_clear_all();
|
||||
for (uint8_t x = 0U; x < LOCAL_DISPLAY_WIDTH; ++x) {
|
||||
set_global_pixel(x, (uint8_t)row, true);
|
||||
}
|
||||
esp_err_t error = display_refresh();
|
||||
error = local_display_frame_end();
|
||||
if (error != ESP_OK) {
|
||||
return report_error("Display row", error);
|
||||
}
|
||||
@@ -615,16 +383,10 @@ static int command_display_contrast(int argc, char **argv)
|
||||
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));
|
||||
esp_err_t error = local_display_set_contrast((uint8_t)contrast);
|
||||
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;
|
||||
}
|
||||
@@ -635,17 +397,12 @@ static int command_display_invert(int argc, char **argv)
|
||||
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);
|
||||
bool inverted = strcmp(argv[1], "on") == 0;
|
||||
esp_err_t error = local_display_set_inverted(inverted);
|
||||
if (error != ESP_OK) {
|
||||
return report_error("Display inversion", error);
|
||||
}
|
||||
s_inverted = invert;
|
||||
printf("Display inversion %s.\n", invert ? "enabled" : "disabled");
|
||||
printf("Display inversion %s.\n", inverted ? "enabled" : "disabled");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -655,7 +412,6 @@ static int command_display(int argc, char **argv)
|
||||
print_display_usage();
|
||||
return argc < 2 || argc == 2 ? 0 : 1;
|
||||
}
|
||||
|
||||
if (strcmp(argv[1], "status") == 0) {
|
||||
return command_display_status(argc - 1, argv + 1);
|
||||
}
|
||||
@@ -704,17 +460,14 @@ static int command_buttons_status(int argc, char **argv)
|
||||
return 1;
|
||||
}
|
||||
if (!s_buttons_ready) {
|
||||
return report_error("Button status", s_initialization_error);
|
||||
return report_error("Button status", s_button_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");
|
||||
printf(" %-14s GPIO%d level=%d %s\n", s_buttons[index].name,
|
||||
s_buttons[index].gpio, level, level == 0 ? "pressed" : "released");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -723,33 +476,27 @@ 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))) {
|
||||
(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);
|
||||
return report_error("Button test", s_button_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. */
|
||||
.raw_level = gpio_get_level(s_buttons[index].gpio),
|
||||
.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);
|
||||
|
||||
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];
|
||||
@@ -758,7 +505,6 @@ static int command_buttons_test(int argc, char **argv)
|
||||
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;
|
||||
@@ -768,23 +514,19 @@ static int command_buttons_test(int argc, char **argv)
|
||||
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;
|
||||
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);
|
||||
s_buttons[index].name, (long long)duration_ms);
|
||||
} else {
|
||||
printf("%-14s released after %lld ms\n",
|
||||
s_buttons[index].name,
|
||||
(long long)duration_ms);
|
||||
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) {
|
||||
@@ -801,8 +543,7 @@ static int command_buttons_test(int argc, char **argv)
|
||||
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,
|
||||
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");
|
||||
@@ -826,7 +567,6 @@ static int command_buttons(int argc, char **argv)
|
||||
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;
|
||||
@@ -834,14 +574,11 @@ static int command_buttons(int argc, char **argv)
|
||||
|
||||
esp_err_t local_ui_hw_test_init(void)
|
||||
{
|
||||
if (s_bus_ready || s_buttons_ready) {
|
||||
if (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;
|
||||
s_button_initialization_error = configure_buttons();
|
||||
return s_button_initialization_error;
|
||||
}
|
||||
|
||||
int local_ui_hw_test_execute(int argc, char **argv)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-only */
|
||||
/* Phase 7A local display and button electrical diagnostics. */
|
||||
/* Phase 7A local display/button diagnostics retained for Phase 7B validation. */
|
||||
|
||||
#pragma once
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Configure the shared I2C bus and active-low button inputs without probing. */
|
||||
/* Configure active-low button inputs; the Phase 7B display service owns I2C. */
|
||||
esp_err_t local_ui_hw_test_init(void);
|
||||
|
||||
/* Handle argv beginning with either "display" or "buttons". */
|
||||
|
||||
+16
-3
@@ -5,6 +5,7 @@
|
||||
#include "esp_log.h"
|
||||
#include "esp_psram.h"
|
||||
#include "network_console.h"
|
||||
#include "local_display.h"
|
||||
#include "local_ui_hw_test.h"
|
||||
#include "rs232_hw_test.h"
|
||||
#include "rs232_port_owner.h"
|
||||
@@ -36,7 +37,7 @@ static const char *TAG = "firmware";
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
ESP_LOGI(TAG, "ESP32-S3 Serial Swiss Army Knife Phase 7A diagnostics started");
|
||||
ESP_LOGI(TAG, "ESP32-S3 Serial Swiss Army Knife Phase 7B display driver started");
|
||||
|
||||
if (esp_psram_is_initialized()) {
|
||||
ESP_LOGI(TAG, "PSRAM initialized: %u bytes", (unsigned int)esp_psram_get_size());
|
||||
@@ -56,10 +57,22 @@ 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. */
|
||||
/* The optional display can fail without affecting UART0 or serial transports. */
|
||||
esp_err_t local_display_error = local_display_init();
|
||||
if (local_display_error != ESP_OK) {
|
||||
ESP_LOGW(TAG, "Local display bus unavailable: %s",
|
||||
esp_err_to_name(local_display_error));
|
||||
} else {
|
||||
local_display_error = local_display_start();
|
||||
if (local_display_error != ESP_OK) {
|
||||
ESP_LOGW(TAG, "Local display unavailable: %s",
|
||||
esp_err_to_name(local_display_error));
|
||||
}
|
||||
}
|
||||
|
||||
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_LOGW(TAG, "Local UI button diagnostics unavailable: %s",
|
||||
esp_err_to_name(local_ui_error));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user