Add Phase 0 RS-232 characterization harness and test firmware with cli

This commit is contained in:
2026-08-22 16:15:53 +02:00
parent e43b624aec
commit 3024b135dc
11 changed files with 1164 additions and 71 deletions
+26 -9
View File
@@ -6,16 +6,17 @@ Universal wireless serial adaptor firmware for the ESP32-S3.
## Initial hardware target ## Initial hardware target
- ESP32-S3-DevKitC-1 - ESP32-S3-DevKitC-1-compatible development board
- ESP32-S3-WROOM-1-N16R8 module - ESP32-S3-WROOM-1-N16R8 module
- 16 MB flash - 16 MB flash
- 8 MB octal PSRAM - 8 MB octal PSRAM
- Adafruit MAX3243 full-pinout RS-232 breakout, product 5988
The initial smoke-test firmware continuously sweeps the onboard addressable RGB LED through the color wheel at reduced brightness. The official ESP32-S3-DevKitC-1 v1.0 uses GPIO48 for the LED data signal, while official v1.1 boards use GPIO38. Compatible boards and clones may vary. The current firmware is a command-driven **Phase 0 hardware-characterization harness**. It validates the MAX3243 breakout, its three drivers, its five receivers, valid-signal detection, active-low shutdown, and UART1 loopback before development of the wireless serial firmware begins. Tests run only when explicitly requested at the console; booting the board does not start an electrical test.
## Hardware wiring ## Hardware wiring
See [`wiring.md`](wiring.md) for hardware-specific wiring profiles. The initial profile covers the ESP32-S3-DevKitC-1 N16R8 and the Adafruit MAX3243 full-pinout RS-232 breakout. See [`wiring.md`](wiring.md) for the hardware profile, GPIO assignments, loopback diagrams, safety notes, and the recommended test sequence. The initial profile covers the ESP32-S3-DevKitC-1 N16R8 and the Adafruit MAX3243 full-pinout RS-232 breakout.
## Build ## Build
@@ -29,18 +30,34 @@ Connect the board's USB-to-UART port, then run:
```sh ```sh
pio run --target upload pio run --target upload
pio device monitor pio device monitor -b 115200
``` ```
The application logs its startup, detected PSRAM size, and configured RGB LED GPIO at 115200 baud. Expected output includes: The firmware starts an interactive console on UART0. Type `help` to display command descriptions. The Phase 0 commands are:
```text ```text
ESP32-S3 build-chain smoke test started status
PSRAM initialized: 8388608 bytes transceiver <enable|disable>
Sweeping the onboard RGB LED on GPIO48 drivers <tx 0|1> <dtr 0|1> <rts 0|1>
loopback-a
loopback-b
valid-test
uart-loopback <baud> [8N1|8E1|8O1|8N2|7E1|7O1] [bytes]
uart-suite
``` ```
If the firmware runs but the RGB LED remains dark, verify the board revision. The project defaults to GPIO48, commonly used by DevKitC-compatible boards and official v1.0 boards. For an official v1.1 board — or another board wired that way — change `RGB_LED_GPIO` in `platformio.ini` from `48` to `38`, rebuild, and upload again. `uart-loopback` defaults to `8N1` and 256 bytes. Its accepted payload range is 1512 bytes. `uart-suite` covers 300 through 250000 baud and all supported frame formats. Follow the applicable loopback wiring in [`wiring.md`](wiring.md) before invoking a test.
The onboard RGB LED reports the most recent test-harness state:
| Color | Meaning |
|---|---|
| Blue | Idle; waiting for a command |
| Yellow/orange | Test running |
| Green | Last test passed |
| Red | Last test failed |
This hardware profile uses the onboard RGB LED on GPIO48. Official ESP32-S3-DevKitC-1 v1.1 boards commonly use GPIO38 instead, and compatible boards or clones may vary. A different board revision requires an adjusted board pin profile before running this firmware.
## License ## License
-3
View File
@@ -10,8 +10,5 @@ board_build.flash_mode = qio
board_build.flash_size = 16MB board_build.flash_size = 16MB
board_upload.flash_size = 16MB board_upload.flash_size = 16MB
build_flags =
-D RGB_LED_GPIO=48
monitor_speed = 115200 monitor_speed = 115200
monitor_filters = esp32_exception_decoder monitor_filters = esp32_exception_decoder
+1 -1
View File
@@ -6,5 +6,5 @@ CONFIG_SPIRAM_SPEED_80M=y
CONFIG_SPIRAM_BOOT_INIT=y CONFIG_SPIRAM_BOOT_INIT=y
CONFIG_SPIRAM_USE_CAPS_ALLOC=y CONFIG_SPIRAM_USE_CAPS_ALLOC=y
# Keep the initial smoke-test output concise but useful. # Keep diagnostic and interactive-console logging concise but useful.
CONFIG_LOG_DEFAULT_LEVEL_INFO=y CONFIG_LOG_DEFAULT_LEVEL_INFO=y
+12 -1
View File
@@ -1,4 +1,15 @@
idf_component_register( idf_component_register(
SRCS "main.c" SRCS
"main.c"
"status_led.c"
"rs232_hw_test.c"
INCLUDE_DIRS "." INCLUDE_DIRS "."
REQUIRES
console
esp_driver_gpio
esp_driver_uart
esp_psram
esp_timer
freertos
led_strip
) )
+33
View File
@@ -0,0 +1,33 @@
#pragma once
#include "driver/gpio.h"
#include "driver/uart.h"
/*
* Hardware profile: ESP32-S3-DevKitC-1-compatible N16R8 board connected to
* the Adafruit MAX3243 full-pinout RS-232 breakout (product 5988).
*
* Keep these definitions in one place so future board profiles can select a
* different pin map without scattering hardware assumptions through drivers.
*/
#define BOARD_RGB_LED_GPIO GPIO_NUM_48
#define RS232_UART_PORT UART_NUM_1
#define RS232_TX_GPIO GPIO_NUM_17
#define RS232_RX_GPIO GPIO_NUM_18
#define RS232_RTS_GPIO GPIO_NUM_15
#define RS232_CTS_GPIO GPIO_NUM_16
#define RS232_DTR_GPIO GPIO_NUM_7
#define RS232_DSR_GPIO GPIO_NUM_5
#define RS232_DCD_GPIO GPIO_NUM_4
#define RS232_RI_GPIO GPIO_NUM_6
#define RS232_VALID_GPIO GPIO_NUM_8
/*
* Adafruit labels this pin OFF, but it is connected to the MAX3243
* active-low !FORCEOFF input. Releasing the open-drain GPIO enables the
* transceiver through the breakout's pull-up; driving it low disables it.
*/
#define RS232_FORCE_OFF_N_GPIO GPIO_NUM_9
+36 -57
View File
@@ -1,54 +1,20 @@
#include <stdint.h> #include "driver/uart.h"
#include "esp_console.h"
#include "esp_err.h" #include "esp_err.h"
#include "esp_log.h" #include "esp_log.h"
#include "esp_psram.h" #include "esp_psram.h"
#include "freertos/FreeRTOS.h" #include "rs232_hw_test.h"
#include "freertos/task.h" #include "status_led.h"
#include "led_strip.h"
#include "led_strip_rmt.h"
#ifndef RGB_LED_GPIO #define CONSOLE_BAUD_RATE 115200
#define RGB_LED_GPIO 48 #define CONSOLE_TX_GPIO 43
#endif #define CONSOLE_RX_GPIO 44
#define RGB_LED_COUNT 1 static const char *TAG = "phase0";
#define RGB_LED_BRIGHTNESS 32
#define FADE_STEP_DELAY_MS 20
static const char *TAG = "rgb_smoke_test";
static led_strip_handle_t configure_rgb_led(void)
{
const led_strip_config_t strip_config = {
.strip_gpio_num = RGB_LED_GPIO,
.max_leds = RGB_LED_COUNT,
.led_model = LED_MODEL_WS2812,
.color_component_format = LED_STRIP_COLOR_COMPONENT_FMT_GRB,
.flags = {
.invert_out = false,
},
};
const led_strip_rmt_config_t rmt_config = {
.clk_src = RMT_CLK_SRC_DEFAULT,
.resolution_hz = 10 * 1000 * 1000,
.mem_block_symbols = 0,
.flags = {
.with_dma = false,
},
};
led_strip_handle_t strip = NULL;
ESP_ERROR_CHECK(led_strip_new_rmt_device(&strip_config, &rmt_config, &strip));
ESP_ERROR_CHECK(led_strip_clear(strip));
return strip;
}
void app_main(void) void app_main(void)
{ {
ESP_LOGI(TAG, "ESP32-S3 build-chain smoke test started"); ESP_LOGI(TAG, "ESP32-S3 RS-232 Phase 0 hardware characterization started");
if (esp_psram_is_initialized()) { if (esp_psram_is_initialized()) {
ESP_LOGI(TAG, "PSRAM initialized: %u bytes", (unsigned int)esp_psram_get_size()); ESP_LOGI(TAG, "PSRAM initialized: %u bytes", (unsigned int)esp_psram_get_size());
@@ -56,19 +22,32 @@ void app_main(void)
ESP_LOGW(TAG, "PSRAM is not initialized"); ESP_LOGW(TAG, "PSRAM is not initialized");
} }
led_strip_handle_t strip = configure_rgb_led(); /* Blue means that the test harness is initialized and waiting for a command. */
ESP_LOGI(TAG, "Sweeping the onboard RGB LED on GPIO%d", RGB_LED_GPIO); ESP_ERROR_CHECK(status_led_init());
ESP_ERROR_CHECK(rs232_hw_test_init());
while (true) { esp_console_repl_config_t repl_config = ESP_CONSOLE_REPL_CONFIG_DEFAULT();
for (uint16_t hue = 0; hue < 360; ++hue) { repl_config.prompt = "rs232-test> ";
ESP_ERROR_CHECK(led_strip_set_pixel_hsv( repl_config.max_cmdline_length = 160;
strip, repl_config.task_stack_size = 8192;
0,
hue, /*
UINT8_MAX, * UART0 remains dedicated to development and diagnostics. The external
RGB_LED_BRIGHTNESS)); * RS-232 data path uses UART1 on GPIO17/18 and cannot disturb this REPL.
ESP_ERROR_CHECK(led_strip_refresh(strip)); */
vTaskDelay(pdMS_TO_TICKS(FADE_STEP_DELAY_MS)); esp_console_dev_uart_config_t uart_config = ESP_CONSOLE_DEV_UART_CONFIG_DEFAULT();
} uart_config.channel = UART_NUM_0;
} uart_config.baud_rate = CONSOLE_BAUD_RATE;
uart_config.tx_gpio_num = CONSOLE_TX_GPIO;
uart_config.rx_gpio_num = CONSOLE_RX_GPIO;
esp_console_repl_t *repl = NULL;
ESP_ERROR_CHECK(esp_console_new_repl_uart(&uart_config, &repl_config, &repl));
/* The REPL constructor initializes esp_console and installs `help`. */
ESP_ERROR_CHECK(rs232_hw_test_register_console_commands());
ESP_ERROR_CHECK(esp_console_start_repl(repl));
ESP_LOGI(TAG, "Interactive test console ready at %d baud", CONSOLE_BAUD_RATE);
ESP_LOGI(TAG, "Type 'help' for commands; no test runs automatically");
} }
+878
View File
@@ -0,0 +1,878 @@
#include "rs232_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/uart.h"
#include "esp_check.h"
#include "esp_console.h"
#include "esp_err.h"
#include "esp_timer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "freertos/task.h"
#include "status_led.h"
#define STATIC_SETTLE_TIME_MS 20
#define TRANSCEIVER_SETTLE_TIME_MS 100
#define UART_RX_BUFFER_SIZE 4096
#define UART_EVENT_QUEUE_SIZE 20
#define UART_MAX_PAYLOAD_SIZE 512
#define UART_MIN_BAUD_RATE 110
#define UART_MAX_BAUD_RATE 1000000
/*
* These values describe the state commanded on the MAX3243's logic side.
* A MAX3243 driver inverts them: logic 0 becomes a positive RS-232 voltage,
* while logic 1 becomes a negative RS-232 voltage (the idle/MARK state).
*/
static int s_tx_level = 1;
static int s_dtr_level = 1;
static int s_rts_level = 1;
static bool s_transceiver_enabled = true;
static bool s_initialized;
static bool s_uart_active;
typedef struct {
const char *name;
uart_word_length_t data_bits;
uart_parity_t parity;
uart_stop_bits_t stop_bits;
uint8_t data_mask;
} serial_format_t;
typedef struct {
unsigned int frame_errors;
unsigned int parity_errors;
unsigned int fifo_overflows;
unsigned int buffer_full_events;
unsigned int breaks;
} uart_error_counts_t;
typedef struct {
int baud_rate;
const char *format;
size_t payload_size;
} uart_suite_case_t;
static const serial_format_t s_serial_formats[] = {
{"8N1", UART_DATA_8_BITS, UART_PARITY_DISABLE, UART_STOP_BITS_1, 0xff},
{"8E1", UART_DATA_8_BITS, UART_PARITY_EVEN, UART_STOP_BITS_1, 0xff},
{"8O1", UART_DATA_8_BITS, UART_PARITY_ODD, UART_STOP_BITS_1, 0xff},
{"8N2", UART_DATA_8_BITS, UART_PARITY_DISABLE, UART_STOP_BITS_2, 0xff},
{"7E1", UART_DATA_7_BITS, UART_PARITY_EVEN, UART_STOP_BITS_1, 0x7f},
{"7O1", UART_DATA_7_BITS, UART_PARITY_ODD, UART_STOP_BITS_1, 0x7f},
};
static const uart_suite_case_t s_uart_suite[] = {
{300, "8N1", 32},
{1200, "8N1", 64},
{9600, "8N1", 256},
{115200, "8N1", 512},
{230400, "8N1", 512},
{250000, "8N1", 512},
{9600, "8E1", 128},
{9600, "8O1", 128},
{9600, "8N2", 128},
{9600, "7E1", 128},
{9600, "7O1", 128},
};
static TickType_t milliseconds_to_ticks(uint64_t milliseconds)
{
TickType_t ticks = pdMS_TO_TICKS(milliseconds);
/* A non-zero wait must remain non-zero even with a coarse RTOS tick. */
return (milliseconds > 0 && ticks == 0) ? 1 : ticks;
}
static esp_err_t drive_transceiver_enabled(bool enabled)
{
return gpio_set_level(RS232_FORCE_OFF_N_GPIO, enabled ? 1 : 0);
}
static esp_err_t configure_static_gpio(bool reset_driver_levels)
{
bool enable_after_configuration = s_transceiver_enabled;
/*
* Shut the MAX3243 down before changing pin routing. gpio_set_level sets
* the output latch first; GPIO_MODE_INPUT_OUTPUT_OD then actively pulls
* !FORCEOFF low and also permits physical pin-level readback.
*/
ESP_RETURN_ON_ERROR(
drive_transceiver_enabled(false),
"rs232_test",
"Set OFF latch for safe reconfiguration");
const gpio_config_t shutdown_config = {
.pin_bit_mask = 1ULL << RS232_FORCE_OFF_N_GPIO,
.mode = GPIO_MODE_INPUT_OUTPUT_OD,
.pull_up_en = GPIO_PULLUP_ENABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
ESP_RETURN_ON_ERROR(gpio_config(&shutdown_config), "rs232_test", "Configure OFF GPIO");
if (reset_driver_levels) {
s_tx_level = 1;
s_dtr_level = 1;
s_rts_level = 1;
}
/* Set output latches before enabling output drivers to minimize glitches. */
ESP_RETURN_ON_ERROR(gpio_set_level(RS232_TX_GPIO, s_tx_level), "rs232_test", "Set TX latch");
ESP_RETURN_ON_ERROR(gpio_set_level(RS232_DTR_GPIO, s_dtr_level), "rs232_test", "Set DTR latch");
ESP_RETURN_ON_ERROR(gpio_set_level(RS232_RTS_GPIO, s_rts_level), "rs232_test", "Set RTS latch");
const gpio_config_t driver_config = {
.pin_bit_mask = (1ULL << RS232_TX_GPIO) |
(1ULL << RS232_DTR_GPIO) |
(1ULL << RS232_RTS_GPIO),
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
ESP_RETURN_ON_ERROR(gpio_config(&driver_config), "rs232_test", "Configure driver GPIOs");
const gpio_config_t receiver_config = {
.pin_bit_mask = (1ULL << RS232_RX_GPIO) |
(1ULL << RS232_CTS_GPIO) |
(1ULL << RS232_DSR_GPIO) |
(1ULL << RS232_DCD_GPIO) |
(1ULL << RS232_RI_GPIO) |
(1ULL << RS232_VALID_GPIO),
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
ESP_RETURN_ON_ERROR(gpio_config(&receiver_config), "rs232_test", "Configure receiver GPIOs");
ESP_RETURN_ON_ERROR(
drive_transceiver_enabled(enable_after_configuration),
"rs232_test",
"Restore OFF state after configuration");
s_uart_active = false;
return ESP_OK;
}
static esp_err_t set_transceiver_enabled(bool enabled)
{
esp_err_t err = drive_transceiver_enabled(enabled);
if (err == ESP_OK) {
s_transceiver_enabled = enabled;
}
return err;
}
static esp_err_t set_driver_levels(int tx, int dtr, int rts)
{
ESP_RETURN_ON_ERROR(gpio_set_level(RS232_TX_GPIO, tx), "rs232_test", "Set TX");
ESP_RETURN_ON_ERROR(gpio_set_level(RS232_DTR_GPIO, dtr), "rs232_test", "Set DTR");
ESP_RETURN_ON_ERROR(gpio_set_level(RS232_RTS_GPIO, rts), "rs232_test", "Set RTS");
s_tx_level = tx;
s_dtr_level = dtr;
s_rts_level = rts;
return ESP_OK;
}
static bool parse_binary_level(const char *text, int *level)
{
if (strcmp(text, "0") == 0) {
*level = 0;
return true;
}
if (strcmp(text, "1") == 0) {
*level = 1;
return true;
}
return false;
}
static bool parse_integer(const char *text, long minimum, long maximum, long *value)
{
char *end = NULL;
errno = 0;
long parsed = strtol(text, &end, 10);
if (errno != 0 || end == text || *end != '\0' || parsed < minimum || parsed > maximum) {
return false;
}
*value = parsed;
return true;
}
static const serial_format_t *find_serial_format(const char *name)
{
for (size_t index = 0; index < sizeof(s_serial_formats) / sizeof(s_serial_formats[0]); ++index) {
if (strcmp(name, s_serial_formats[index].name) == 0) {
return &s_serial_formats[index];
}
}
return NULL;
}
static int command_status(int argc, char **argv)
{
(void)argc;
(void)argv;
printf("MAX3243: %s (OFF/!FORCEOFF GPIO%d=%d, 1 means released)\n",
s_transceiver_enabled ? "enabled" : "disabled",
RS232_FORCE_OFF_N_GPIO,
gpio_get_level(RS232_FORCE_OFF_N_GPIO));
printf("Drivers: TX=%d DTR=%d RTS=%d [logic 0 -> positive RS-232, logic 1 -> negative]\n",
s_tx_level,
s_dtr_level,
s_rts_level);
printf("Receivers: RX=%d DSR=%d CTS=%d DCD=%d RI=%d\n",
gpio_get_level(RS232_RX_GPIO),
gpio_get_level(RS232_DSR_GPIO),
gpio_get_level(RS232_CTS_GPIO),
gpio_get_level(RS232_DCD_GPIO),
gpio_get_level(RS232_RI_GPIO));
printf("VLD=%d (%s valid RS-232 voltage detected)\n",
gpio_get_level(RS232_VALID_GPIO),
gpio_get_level(RS232_VALID_GPIO) ? "at least one" : "no");
return 0;
}
static int command_transceiver(int argc, char **argv)
{
if (argc != 2 || (strcmp(argv[1], "enable") != 0 && strcmp(argv[1], "disable") != 0)) {
printf("Usage: transceiver <enable|disable>\n");
return 1;
}
bool enable = strcmp(argv[1], "enable") == 0;
esp_err_t err = set_transceiver_enabled(enable);
if (err != ESP_OK) {
printf("Could not control the transceiver: %s\n", esp_err_to_name(err));
return 1;
}
vTaskDelay(pdMS_TO_TICKS(STATIC_SETTLE_TIME_MS));
printf("MAX3243 %s; OFF/!FORCEOFF is %s. VLD=%d\n",
enable ? "enabled" : "disabled",
enable ? "released high" : "driven low",
gpio_get_level(RS232_VALID_GPIO));
return 0;
}
static int command_drivers(int argc, char **argv)
{
int tx;
int dtr;
int rts;
if (argc != 4 ||
!parse_binary_level(argv[1], &tx) ||
!parse_binary_level(argv[2], &dtr) ||
!parse_binary_level(argv[3], &rts)) {
printf("Usage: drivers <tx 0|1> <dtr 0|1> <rts 0|1>\n");
return 1;
}
esp_err_t err = set_driver_levels(tx, dtr, rts);
if (err != ESP_OK) {
printf("Could not set driver levels: %s\n", esp_err_to_name(err));
return 1;
}
printf("TX=%d DTR=%d RTS=%d\n", tx, dtr, rts);
printf("Logic 0 -> positive RS-232 voltage; logic 1 -> negative RS-232 voltage.\n");
return 0;
}
typedef enum {
LOOPBACK_CONFIGURATION_A,
LOOPBACK_CONFIGURATION_B,
} loopback_configuration_t;
static esp_err_t run_static_loopback(loopback_configuration_t configuration)
{
bool all_passed = true;
esp_err_t err = status_led_set(STATUS_LED_RUNNING);
if (err != ESP_OK) {
return err;
}
ESP_RETURN_ON_ERROR(set_transceiver_enabled(true), "rs232_test", "Enable transceiver");
vTaskDelay(pdMS_TO_TICKS(TRANSCEIVER_SETTLE_TIME_MS));
if (configuration == LOOPBACK_CONFIGURATION_A) {
printf("Configuration A expects DE-9 3->2, 4->6, and 7->8.\n");
printf("TX DTR RTS | RX DSR CTS VLD | result\n");
} else {
printf("Configuration B expects DE-9 3->1, 4->9, and 7->2.\n");
printf("TX DTR RTS | DCD RI RX VLD | result\n");
}
for (unsigned int pattern = 0; pattern < 8; ++pattern) {
int tx = (pattern >> 2) & 1;
int dtr = (pattern >> 1) & 1;
int rts = pattern & 1;
err = set_driver_levels(tx, dtr, rts);
if (err != ESP_OK) {
all_passed = false;
break;
}
vTaskDelay(pdMS_TO_TICKS(STATIC_SETTLE_TIME_MS));
int first;
int second;
int third;
bool pattern_passed;
int valid = gpio_get_level(RS232_VALID_GPIO);
if (configuration == LOOPBACK_CONFIGURATION_A) {
first = gpio_get_level(RS232_RX_GPIO);
second = gpio_get_level(RS232_DSR_GPIO);
third = gpio_get_level(RS232_CTS_GPIO);
pattern_passed = first == tx && second == dtr && third == rts && valid == 1;
} else {
first = gpio_get_level(RS232_DCD_GPIO);
second = gpio_get_level(RS232_RI_GPIO);
third = gpio_get_level(RS232_RX_GPIO);
pattern_passed = first == tx && second == dtr && third == rts && valid == 1;
}
printf(" %d %d %d | %d %d %d %d | %s\n",
tx, dtr, rts, first, second, third, valid,
pattern_passed ? "PASS" : "FAIL");
all_passed = all_passed && pattern_passed;
}
/* Return every RS-232 output to its idle negative-voltage state. */
esp_err_t idle_err = set_driver_levels(1, 1, 1);
if (err == ESP_OK && idle_err != ESP_OK) {
err = idle_err;
all_passed = false;
}
if (err != ESP_OK) {
printf("GPIO error: %s\n", esp_err_to_name(err));
}
printf("Static loopback %c: %s\n",
configuration == LOOPBACK_CONFIGURATION_A ? 'A' : 'B',
all_passed ? "PASS" : "FAIL");
status_led_set(all_passed ? STATUS_LED_PASS : STATUS_LED_FAIL);
return all_passed ? ESP_OK : ESP_FAIL;
}
static int command_loopback_a(int argc, char **argv)
{
(void)argc;
(void)argv;
return run_static_loopback(LOOPBACK_CONFIGURATION_A) == ESP_OK ? 0 : 1;
}
static int command_loopback_b(int argc, char **argv)
{
(void)argc;
(void)argv;
return run_static_loopback(LOOPBACK_CONFIGURATION_B) == ESP_OK ? 0 : 1;
}
static int command_valid_test(int argc, char **argv)
{
(void)argc;
(void)argv;
bool passed = true;
esp_err_t err = status_led_set(STATUS_LED_RUNNING);
if (err != ESP_OK) {
printf("Could not set status LED: %s\n", esp_err_to_name(err));
return 1;
}
printf("This test requires loopback A or B and no externally powered RS-232 peer.\n");
printf("It will briefly shut down the MAX3243 through active-low OFF/!FORCEOFF.\n");
/* Positive outputs provide an unambiguous valid voltage to looped receivers. */
if (set_driver_levels(0, 0, 0) != ESP_OK || set_transceiver_enabled(true) != ESP_OK) {
passed = false;
goto cleanup;
}
vTaskDelay(pdMS_TO_TICKS(TRANSCEIVER_SETTLE_TIME_MS));
int enabled_before = gpio_get_level(RS232_VALID_GPIO);
printf("Enabled: VLD=%d (expected 1) %s\n",
enabled_before, enabled_before == 1 ? "PASS" : "FAIL");
passed = passed && enabled_before == 1;
if (set_transceiver_enabled(false) != ESP_OK) {
passed = false;
goto cleanup;
}
vTaskDelay(pdMS_TO_TICKS(TRANSCEIVER_SETTLE_TIME_MS));
int disabled = gpio_get_level(RS232_VALID_GPIO);
printf("Disabled: VLD=%d (expected 0) %s\n",
disabled, disabled == 0 ? "PASS" : "FAIL");
passed = passed && disabled == 0;
if (set_transceiver_enabled(true) != ESP_OK) {
passed = false;
goto cleanup;
}
vTaskDelay(pdMS_TO_TICKS(TRANSCEIVER_SETTLE_TIME_MS));
int enabled_after = gpio_get_level(RS232_VALID_GPIO);
printf("Re-enabled: VLD=%d (expected 1) %s\n",
enabled_after, enabled_after == 1 ? "PASS" : "FAIL");
passed = passed && enabled_after == 1;
cleanup:;
/*
* Run every cleanup step independently: one GPIO error must not prevent us
* from attempting to restore the other safety-relevant outputs.
*/
esp_err_t shutdown_error = set_transceiver_enabled(false);
esp_err_t idle_error = set_driver_levels(1, 1, 1);
esp_err_t enable_error = set_transceiver_enabled(true);
if (shutdown_error != ESP_OK || idle_error != ESP_OK || enable_error != ESP_OK) {
printf("Cleanup error: OFF-low=%s idle-drivers=%s OFF-high=%s\n",
esp_err_to_name(shutdown_error),
esp_err_to_name(idle_error),
esp_err_to_name(enable_error));
passed = false;
}
printf("VLD/OFF test: %s\n", passed ? "PASS" : "FAIL");
status_led_set(passed ? STATUS_LED_PASS : STATUS_LED_FAIL);
return passed ? 0 : 1;
}
static void generate_payload(uint8_t *payload, size_t payload_size, uint8_t mask)
{
static const uint8_t diagnostic_prefix[] = {
0x00, 0xff, 0x55, 0xaa,
0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80,
0xfe, 0xfd, 0xfb, 0xf7,
};
uint32_t pseudo_random = 0x6d2b79f5;
for (size_t index = 0; index < payload_size; ++index) {
uint8_t value;
if (index < sizeof(diagnostic_prefix)) {
value = diagnostic_prefix[index];
} else {
/* xorshift32 is deterministic, making failures exactly repeatable. */
pseudo_random ^= pseudo_random << 13;
pseudo_random ^= pseudo_random >> 17;
pseudo_random ^= pseudo_random << 5;
value = (uint8_t)pseudo_random;
}
payload[index] = value & mask;
}
}
static void collect_uart_events(QueueHandle_t event_queue, uart_error_counts_t *errors)
{
uart_event_t event;
while (xQueueReceive(event_queue, &event, 0) == pdTRUE) {
switch (event.type) {
case UART_FRAME_ERR:
++errors->frame_errors;
break;
case UART_PARITY_ERR:
++errors->parity_errors;
break;
case UART_FIFO_OVF:
++errors->fifo_overflows;
break;
case UART_BUFFER_FULL:
++errors->buffer_full_events;
break;
case UART_BREAK:
case UART_DATA_BREAK:
++errors->breaks;
break;
default:
/* UART_DATA notifications need no action because reads use the ring buffer. */
break;
}
}
}
static uint64_t uart_test_timeout_ms(int baud_rate, size_t payload_size)
{
/* Twelve bits per character safely covers the widest supported frame. */
uint64_t nominal_ms = ((uint64_t)payload_size * 12U * 1000U + (uint64_t)baud_rate - 1U) /
(uint64_t)baud_rate;
return 1000U + nominal_ms * 3U;
}
static esp_err_t run_uart_loopback(int baud_rate, const serial_format_t *format, size_t payload_size)
{
uint8_t transmitted[UART_MAX_PAYLOAD_SIZE];
uint8_t received[UART_MAX_PAYLOAD_SIZE];
QueueHandle_t event_queue = NULL;
uart_error_counts_t uart_errors = {0};
bool driver_installed = false;
bool test_passed = false;
esp_err_t result = ESP_FAIL;
size_t received_size = 0;
size_t extra_bytes = 0;
size_t mismatches = 0;
generate_payload(transmitted, payload_size, format->data_mask);
memset(received, 0, sizeof(received));
result = status_led_set(STATUS_LED_RUNNING);
if (result != ESP_OK) {
printf("Could not set running LED: %s\n", esp_err_to_name(result));
goto cleanup;
}
/* Keep all RS-232 drivers off while GPIO17/18 are handed to UART1. */
result = drive_transceiver_enabled(false);
if (result != ESP_OK) {
printf("Could not shut down MAX3243 before UART setup: %s\n", esp_err_to_name(result));
goto cleanup;
}
result = set_driver_levels(1, 1, 1);
if (result != ESP_OK) {
printf("Could not set idle outputs before UART setup: %s\n", esp_err_to_name(result));
goto cleanup;
}
s_uart_active = true;
const uart_config_t uart_config = {
.baud_rate = baud_rate,
.data_bits = format->data_bits,
.parity = format->parity,
.stop_bits = format->stop_bits,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
.rx_flow_ctrl_thresh = 0,
.source_clk = UART_SCLK_DEFAULT,
.flags = {
.allow_pd = 0,
},
};
result = uart_driver_install(
RS232_UART_PORT,
UART_RX_BUFFER_SIZE,
0,
UART_EVENT_QUEUE_SIZE,
&event_queue,
0);
if (result != ESP_OK) {
printf("uart_driver_install failed: %s\n", esp_err_to_name(result));
goto cleanup;
}
driver_installed = true;
result = uart_param_config(RS232_UART_PORT, &uart_config);
if (result != ESP_OK) {
printf("uart_param_config failed: %s\n", esp_err_to_name(result));
goto cleanup;
}
/* RTS and CTS remain ordinary GPIOs until a dedicated flow-control test. */
result = uart_set_pin(
RS232_UART_PORT,
RS232_TX_GPIO,
RS232_RX_GPIO,
UART_PIN_NO_CHANGE,
UART_PIN_NO_CHANGE);
if (result != ESP_OK) {
printf("uart_set_pin failed: %s\n", esp_err_to_name(result));
goto cleanup;
}
result = uart_flush_input(RS232_UART_PORT);
if (result != ESP_OK) {
printf("Could not flush UART input: %s\n", esp_err_to_name(result));
goto cleanup;
}
xQueueReset(event_queue);
result = set_transceiver_enabled(true);
if (result != ESP_OK) {
printf("Could not enable MAX3243 after UART setup: %s\n", esp_err_to_name(result));
goto cleanup;
}
vTaskDelay(pdMS_TO_TICKS(TRANSCEIVER_SETTLE_TIME_MS));
printf("UART1 %d %s, %u bytes: transmitting...\n",
baud_rate, format->name, (unsigned int)payload_size);
int written = uart_write_bytes(RS232_UART_PORT, transmitted, payload_size);
if (written < 0 || (size_t)written != payload_size) {
printf("uart_write_bytes wrote %d of %u bytes\n", written, (unsigned int)payload_size);
result = ESP_FAIL;
goto cleanup;
}
uint64_t timeout_ms = uart_test_timeout_ms(baud_rate, payload_size);
result = uart_wait_tx_done(RS232_UART_PORT, milliseconds_to_ticks(timeout_ms));
if (result != ESP_OK) {
printf("Timed out waiting for UART transmission: %s\n", esp_err_to_name(result));
goto cleanup;
}
int64_t deadline_us = esp_timer_get_time() + (int64_t)(timeout_ms * 1000U);
while (received_size < payload_size) {
int64_t remaining_us = deadline_us - esp_timer_get_time();
if (remaining_us <= 0) {
break;
}
uint64_t remaining_ms = ((uint64_t)remaining_us + 999U) / 1000U;
int count = uart_read_bytes(
RS232_UART_PORT,
received + received_size,
payload_size - received_size,
milliseconds_to_ticks(remaining_ms));
if (count < 0) {
printf("uart_read_bytes failed\n");
result = ESP_FAIL;
goto cleanup;
}
if (count == 0) {
break;
}
received_size += (size_t)count;
}
size_t buffered_bytes = 0;
if (uart_get_buffered_data_len(RS232_UART_PORT, &buffered_bytes) == ESP_OK) {
uint8_t discard[64];
while (buffered_bytes > 0) {
size_t request = buffered_bytes < sizeof(discard) ? buffered_bytes : sizeof(discard);
int count = uart_read_bytes(RS232_UART_PORT, discard, request, 0);
if (count <= 0) {
break;
}
extra_bytes += (size_t)count;
buffered_bytes -= (size_t)count;
}
}
collect_uart_events(event_queue, &uart_errors);
size_t comparable = received_size < payload_size ? received_size : payload_size;
for (size_t index = 0; index < comparable; ++index) {
if (received[index] != transmitted[index]) {
++mismatches;
if (mismatches <= 8) {
printf(" mismatch at byte %u: sent 0x%02x, received 0x%02x\n",
(unsigned int)index, transmitted[index], received[index]);
}
}
}
mismatches += payload_size - comparable;
mismatches += extra_bytes;
test_passed = received_size == payload_size &&
extra_bytes == 0 &&
mismatches == 0 &&
uart_errors.frame_errors == 0 &&
uart_errors.parity_errors == 0 &&
uart_errors.fifo_overflows == 0 &&
uart_errors.buffer_full_events == 0 &&
uart_errors.breaks == 0;
printf("sent=%u received=%u extra=%u mismatches=%u\n",
(unsigned int)payload_size,
(unsigned int)received_size,
(unsigned int)extra_bytes,
(unsigned int)mismatches);
printf("UART events: frame=%u parity=%u FIFO-overflow=%u buffer-full=%u break=%u\n",
uart_errors.frame_errors,
uart_errors.parity_errors,
uart_errors.fifo_overflows,
uart_errors.buffer_full_events,
uart_errors.breaks);
printf("UART1 %d %s: %s\n", baud_rate, format->name, test_passed ? "PASS" : "FAIL");
result = test_passed ? ESP_OK : ESP_FAIL;
cleanup:;
/* Disable the line drivers before UART1 disconnects from GPIO17/18. */
esp_err_t shutdown_error = drive_transceiver_enabled(false);
if (shutdown_error != ESP_OK) {
printf("Could not shut down MAX3243 during cleanup: %s\n", esp_err_to_name(shutdown_error));
if (result == ESP_OK) {
result = shutdown_error;
}
}
if (driver_installed) {
esp_err_t delete_error = uart_driver_delete(RS232_UART_PORT);
if (delete_error != ESP_OK) {
printf("Could not delete UART1 driver: %s\n", esp_err_to_name(delete_error));
if (result == ESP_OK) {
result = delete_error;
}
}
}
/* GPIO17/18 return to static idle mode, then the transceiver is re-enabled. */
s_transceiver_enabled = true;
esp_err_t restore_error = configure_static_gpio(true);
if (restore_error != ESP_OK) {
printf("Could not restore static GPIO mode: %s\n", esp_err_to_name(restore_error));
if (result == ESP_OK) {
result = restore_error;
}
}
esp_err_t led_error = status_led_set(result == ESP_OK ? STATUS_LED_PASS : STATUS_LED_FAIL);
if (led_error != ESP_OK) {
printf("Could not update status LED: %s\n", esp_err_to_name(led_error));
if (result == ESP_OK) {
result = led_error;
}
}
return result;
}
static int command_uart_loopback(int argc, char **argv)
{
long baud_rate;
long payload_size = 256;
const char *format_name = "8N1";
if (argc < 2 || argc > 4 ||
!parse_integer(argv[1], UART_MIN_BAUD_RATE, UART_MAX_BAUD_RATE, &baud_rate)) {
printf("Usage: uart-loopback <baud 110..1000000> [8N1|8E1|8O1|8N2|7E1|7O1] [bytes 1..512]\n");
return 1;
}
if (argc >= 3) {
format_name = argv[2];
}
const serial_format_t *format = find_serial_format(format_name);
if (format == NULL) {
printf("Unsupported format '%s'. Use 8N1, 8E1, 8O1, 8N2, 7E1, or 7O1.\n", format_name);
return 1;
}
if (argc == 4 && !parse_integer(argv[3], 1, UART_MAX_PAYLOAD_SIZE, &payload_size)) {
printf("Payload size must be between 1 and %d bytes.\n", UART_MAX_PAYLOAD_SIZE);
return 1;
}
printf("Requires DE-9 pin 3 (TX) connected only to pin 2 (RX).\n");
return run_uart_loopback((int)baud_rate, format, (size_t)payload_size) == ESP_OK ? 0 : 1;
}
static int command_uart_suite(int argc, char **argv)
{
(void)argc;
(void)argv;
bool all_passed = true;
printf("Requires DE-9 pin 3 (TX) connected only to pin 2 (RX).\n");
printf("Running %u UART loopback cases. This takes several seconds.\n",
(unsigned int)(sizeof(s_uart_suite) / sizeof(s_uart_suite[0])));
for (size_t index = 0; index < sizeof(s_uart_suite) / sizeof(s_uart_suite[0]); ++index) {
const uart_suite_case_t *test_case = &s_uart_suite[index];
const serial_format_t *format = find_serial_format(test_case->format);
printf("\n[%u/%u] ",
(unsigned int)(index + 1),
(unsigned int)(sizeof(s_uart_suite) / sizeof(s_uart_suite[0])));
if (format == NULL ||
run_uart_loopback(test_case->baud_rate, format, test_case->payload_size) != ESP_OK) {
all_passed = false;
}
}
printf("\nUART loopback suite: %s\n", all_passed ? "PASS" : "FAIL");
status_led_set(all_passed ? STATUS_LED_PASS : STATUS_LED_FAIL);
return all_passed ? 0 : 1;
}
esp_err_t rs232_hw_test_init(void)
{
s_transceiver_enabled = true;
ESP_RETURN_ON_ERROR(configure_static_gpio(true), "rs232_test", "Initialize static GPIO mode");
s_initialized = true;
return ESP_OK;
}
esp_err_t rs232_hw_test_register_console_commands(void)
{
if (!s_initialized || s_uart_active) {
return ESP_ERR_INVALID_STATE;
}
const esp_console_cmd_t commands[] = {
{
.command = "status",
.help = "Show MAX3243 driver, receiver, VLD, and shutdown states",
.hint = NULL,
.func = &command_status,
.argtable = NULL,
},
{
.command = "transceiver",
.help = "Control active-low OFF: transceiver <enable|disable>",
.hint = NULL,
.func = &command_transceiver,
.argtable = NULL,
},
{
.command = "drivers",
.help = "Set static logic levels: drivers <TX 0|1> <DTR 0|1> <RTS 0|1>",
.hint = NULL,
.func = &command_drivers,
.argtable = NULL,
},
{
.command = "loopback-a",
.help = "Test TX->RX, DTR->DSR, RTS->CTS for all eight patterns",
.hint = NULL,
.func = &command_loopback_a,
.argtable = NULL,
},
{
.command = "loopback-b",
.help = "Test TX->DCD, DTR->RI, RTS->RX for all eight patterns",
.hint = NULL,
.func = &command_loopback_b,
.argtable = NULL,
},
{
.command = "valid-test",
.help = "Verify VLD while enabled, shut down, and re-enabled",
.hint = NULL,
.func = &command_valid_test,
.argtable = NULL,
},
{
.command = "uart-loopback",
.help = "Run one UART1 test: uart-loopback <baud> [format] [bytes]",
.hint = NULL,
.func = &command_uart_loopback,
.argtable = NULL,
},
{
.command = "uart-suite",
.help = "Run the predefined baud-rate and frame-format loopback suite",
.hint = NULL,
.func = &command_uart_suite,
.argtable = NULL,
},
};
for (size_t index = 0; index < sizeof(commands) / sizeof(commands[0]); ++index) {
ESP_RETURN_ON_ERROR(
esp_console_cmd_register(&commands[index]),
"rs232_test",
"Register console command");
}
return ESP_OK;
}
+9
View File
@@ -0,0 +1,9 @@
#pragma once
#include "esp_err.h"
/* Configure all MAX3243 logic-side signals in their safe static-test state. */
esp_err_t rs232_hw_test_init(void);
/* Register the Phase 0 hardware-characterization commands with esp_console. */
esp_err_t rs232_hw_test_register_console_commands(void);
+78
View File
@@ -0,0 +1,78 @@
#include "status_led.h"
#include <stdbool.h>
#include <stdint.h>
#include "board_pins.h"
#include "led_strip.h"
#include "led_strip_rmt.h"
#define RGB_LED_COUNT 1
#define RGB_LED_BRIGHTNESS 32
static led_strip_handle_t s_strip;
esp_err_t status_led_init(void)
{
const led_strip_config_t strip_config = {
.strip_gpio_num = BOARD_RGB_LED_GPIO,
.max_leds = RGB_LED_COUNT,
.led_model = LED_MODEL_WS2812,
.color_component_format = LED_STRIP_COLOR_COMPONENT_FMT_GRB,
.flags = {
.invert_out = false,
},
};
const led_strip_rmt_config_t rmt_config = {
.clk_src = RMT_CLK_SRC_DEFAULT,
.resolution_hz = 10 * 1000 * 1000,
.mem_block_symbols = 0,
.flags = {
.with_dma = false,
},
};
esp_err_t err = led_strip_new_rmt_device(&strip_config, &rmt_config, &s_strip);
if (err != ESP_OK) {
return err;
}
return status_led_set(STATUS_LED_IDLE);
}
esp_err_t status_led_set(status_led_state_t state)
{
uint8_t red = 0;
uint8_t green = 0;
uint8_t blue = 0;
if (s_strip == NULL) {
return ESP_ERR_INVALID_STATE;
}
switch (state) {
case STATUS_LED_IDLE:
blue = RGB_LED_BRIGHTNESS;
break;
case STATUS_LED_RUNNING:
red = RGB_LED_BRIGHTNESS;
green = RGB_LED_BRIGHTNESS / 2;
break;
case STATUS_LED_PASS:
green = RGB_LED_BRIGHTNESS;
break;
case STATUS_LED_FAIL:
red = RGB_LED_BRIGHTNESS;
break;
default:
return ESP_ERR_INVALID_ARG;
}
esp_err_t err = led_strip_set_pixel(s_strip, 0, red, green, blue);
if (err != ESP_OK) {
return err;
}
return led_strip_refresh(s_strip);
}
+13
View File
@@ -0,0 +1,13 @@
#pragma once
#include "esp_err.h"
typedef enum {
STATUS_LED_IDLE,
STATUS_LED_RUNNING,
STATUS_LED_PASS,
STATUS_LED_FAIL,
} status_led_state_t;
esp_err_t status_led_init(void);
esp_err_t status_led_set(status_led_state_t state);
+78
View File
@@ -88,6 +88,84 @@ The breakout's `OFF` pin is connected to the MAX3243 active-low `!FORCEOFF` inpu
GPIO8 and GPIO9 are not adjacent on the official J1 header. GPIO3 and GPIO46 lie between them and are boot-strapping pins, so follow the printed GPIO labels instead of counting header positions. GPIO8 and GPIO9 are not adjacent on the official J1 header. GPIO3 and GPIO46 lie between them and are boot-strapping pins, so follow the printed GPIO labels instead of counting header positions.
## Phase 0 loopback tests
The hardware-characterization firmware never starts a test automatically. Wire exactly one configuration below while the board is powered down, inspect the connections, power it again, and then invoke the corresponding console command.
> **Important:** DE-9 pins 3 (`TX`), 4 (`DTR`), and 7 (`RTS`) are all driven outputs. Never connect any of these three pins to another one of these output pins. Connect each output only to the receiver input specified by the selected test.
The temporary Dupont-wire breakout is mechanically fragile. Keep wires short, make all changes with power removed, and prevent loose conductors from touching neighboring pins.
### Configuration A: primary data and handshake pairs
Connect:
| From driven output | To receiver input | Expected ESP32 logic |
|---|---|---|
| DE-9 pin 3, `TX` | DE-9 pin 2, `RX` | `RX == TX` |
| DE-9 pin 4, `DTR` | DE-9 pin 6, `DSR` | `DSR == DTR` |
| DE-9 pin 7, `RTS` | DE-9 pin 8, `CTS` | `CTS == RTS` |
```text
DE-9 pin 3 TX ─────> pin 2 RX
DE-9 pin 4 DTR ─────> pin 6 DSR
DE-9 pin 7 RTS ─────> pin 8 CTS
```
Run `loopback-a`. The firmware cycles all eight TX/DTR/RTS logic combinations, waits for the MAX3243 outputs and receivers to settle, and verifies all three receiver states plus `VLD`. Two inversions occur—once in the driver and once in the receiver—so the final ESP32 logic levels must match.
Configuration A can also be used for:
- `valid-test`, with no external RS-232 peer connected.
- `uart-loopback <baud> [format] [bytes]`, although only the pin 3 to pin 2 link is needed by that command.
- `uart-suite`, again using only the pin 3 to pin 2 data link.
### Configuration B: remaining receivers
Remove all configuration A jumpers, then connect:
| From driven output | To receiver input | Expected ESP32 logic |
|---|---|---|
| DE-9 pin 3, `TX` | DE-9 pin 1, `DCD` | `DCD == TX` |
| DE-9 pin 4, `DTR` | DE-9 pin 9, `RI` | `RI == DTR` |
| DE-9 pin 7, `RTS` | DE-9 pin 2, `RX` | `RX == RTS` |
```text
DE-9 pin 3 TX ─────> pin 1 DCD
DE-9 pin 4 DTR ─────> pin 9 RI
DE-9 pin 7 RTS ─────> pin 2 RX
```
Run `loopback-b`. Together, configurations A and B exercise all three MAX3243 drivers and all five receivers.
### Manual voltage and polarity checks
With no DE-9 loopback jumpers installed, use the static `drivers` command and measure each driven output relative to DE-9 pin 5 (`GND`):
```text
drivers 0 0 0
```
Each of pins 3, 4, and 7 should be at a positive RS-232 voltage. Then run:
```text
drivers 1 1 1
```
Each output should be at a negative RS-232 voltage. Exact values vary with supply, load, meter, and charge-pump behavior; polarity is the primary check. The firmware leaves all outputs at logic 1 after automated tests.
### Recommended test order
1. Start with no DE-9 jumpers and run `status`.
2. Use `drivers 0 0 0` and `drivers 1 1 1` for the three output-polarity measurements.
3. Power down, install configuration A, power up, and run `loopback-a`.
4. With configuration A still installed and no external peer, run `valid-test`.
5. Keep only the pin 3 to pin 2 jumper and run a basic test such as `uart-loopback 9600 8N1 256`.
6. If that passes, run `uart-suite`.
7. Power down, replace the jumpers with configuration B, power up, and run `loopback-b`.
RTS/CTS hardware-flow-control behavior is intentionally deferred until these static and UART loopback tests pass. During the current UART tests, RTS and CTS remain ordinary GPIO signals and UART1 flow control is disabled.
## Future hardware profiles ## Future hardware profiles
Alternative boards—such as the LILYGO T-Display-S3—or different RS-232 transceivers will receive separate profiles here. GPIO assignments must be reviewed for each board's display, buttons, USB connection, flash/PSRAM wiring, boot-strapping pins, and onboard peripherals. Alternative boards—such as the LILYGO T-Display-S3—or different RS-232 transceivers will receive separate profiles here. GPIO assignments must be reviewed for each board's display, buttons, USB connection, flash/PSRAM wiring, boot-strapping pins, and onboard peripherals.