|
|
|
@@ -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");
|
|
|
|
|
}
|