#include "rs232_hw_test.h" #include #include #include #include #include #include #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 #define FLOW_TEST_BAUD_RATE 115200 #define FLOW_TEST_QUEUE_SIZE 32 #define CTS_TEST_PAYLOAD_SIZE 512 #define CTS_TEST_RX_BUFFER_SIZE 1024 #define CTS_TEST_TX_BUFFER_SIZE 2048 #define CTS_TEST_BLOCK_TIME_MS 250 #define CTS_TEST_RESUME_TIMEOUT_MS 2000 #define RTS_TEST_PAYLOAD_SIZE 4096 #define RTS_TEST_RX_BUFFER_SIZE 1024 #define RTS_TEST_GENERATOR_RX_BUFFER_SIZE 256 #define RTS_TEST_GENERATOR_TX_BUFFER_SIZE 8192 #define RTS_TEST_QUEUE_SIZE 128 #define RTS_TEST_RX_INTERRUPT_THRESHOLD 64 #define RTS_TEST_FLOW_THRESHOLD 96 #define RTS_TEST_FILL_TIMEOUT_MS 2000 /* Longer than an unthrottled 4096-byte 8N1 transfer at 115200 baud. */ #define RTS_TEST_BLOCK_TIME_MS 600 #define RTS_TEST_COMPLETE_TIMEOUT_MS 6000 #define RTS_TEST_READ_CHUNK_SIZE 256 /* * 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 data_events; 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 esp_err_t set_dtr_level(int level) { esp_err_t err = gpio_set_level(RS232_DTR_GPIO, level); if (err == ESP_OK) { s_dtr_level = level; } return err; } 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 \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 \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 classify_uart_event(const uart_event_t *event, uart_error_counts_t *errors) { switch (event->type) { case UART_DATA: ++errors->data_events; break; 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: break; } } 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) { classify_uart_event(&event, errors); } } static bool uart_has_data_errors(const uart_error_counts_t *errors) { return errors->frame_errors != 0 || errors->parity_errors != 0 || errors->fifo_overflows != 0 || errors->breaks != 0; } static bool wait_for_gpio_level(gpio_num_t gpio, int expected_level, uint32_t timeout_ms) { int64_t deadline_us = esp_timer_get_time() + (int64_t)timeout_ms * 1000; while (esp_timer_get_time() < deadline_us) { if (gpio_get_level(gpio) == expected_level) { return true; } vTaskDelay(milliseconds_to_ticks(1)); } return gpio_get_level(gpio) == expected_level; } static esp_err_t read_exact_uart( uart_port_t uart_port, uint8_t *destination, size_t expected_size, uint32_t timeout_ms, size_t *received_size) { int64_t deadline_us = esp_timer_get_time() + (int64_t)timeout_ms * 1000; *received_size = 0; while (*received_size < expected_size) { int64_t remaining_us = deadline_us - esp_timer_get_time(); if (remaining_us <= 0) { return ESP_ERR_TIMEOUT; } uint64_t remaining_ms = ((uint64_t)remaining_us + 999U) / 1000U; int count = uart_read_bytes( uart_port, destination + *received_size, expected_size - *received_size, milliseconds_to_ticks(remaining_ms)); if (count < 0) { return ESP_FAIL; } if (count == 0) { return ESP_ERR_TIMEOUT; } *received_size += (size_t)count; } return ESP_OK; } 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 [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; } static esp_err_t finish_flow_test( bool uart1_installed, bool generator_uart_installed, esp_err_t result) { /* Stop physical line activity before disconnecting either UART peripheral. */ 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; } } /* Stop the traffic source before removing the receiver's backpressure. */ if (generator_uart_installed) { esp_err_t delete_error = uart_driver_delete(RS232_TEST_GENERATOR_UART_PORT); if (delete_error != ESP_OK) { printf("Could not delete UART2 generator: %s\n", esp_err_to_name(delete_error)); if (result == ESP_OK) { result = delete_error; } } } if (uart1_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; } } } 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 esp_err_t run_cts_flow_test(void) { uint8_t transmitted[CTS_TEST_PAYLOAD_SIZE]; uint8_t received[CTS_TEST_PAYLOAD_SIZE]; uint8_t blocked_probe[8]; QueueHandle_t event_queue = NULL; uart_error_counts_t uart_events = {0}; bool uart1_installed = false; esp_err_t result = ESP_FAIL; size_t received_size = 0; size_t blocked_rx_size = 0; size_t extra_bytes = 0; size_t mismatches = 0; generate_payload(transmitted, sizeof(transmitted), 0xff); 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; } result = drive_transceiver_enabled(false); if (result != ESP_OK) { printf("Could not shut down MAX3243 before CTS setup: %s\n", esp_err_to_name(result)); goto cleanup; } result = set_driver_levels(1, 1, 1); if (result != ESP_OK) { printf("Could not establish idle driver levels: %s\n", esp_err_to_name(result)); goto cleanup; } s_uart_active = true; const uart_config_t uart_config = { .baud_rate = FLOW_TEST_BAUD_RATE, .data_bits = UART_DATA_8_BITS, .parity = UART_PARITY_DISABLE, .stop_bits = UART_STOP_BITS_1, .flow_ctrl = UART_HW_FLOWCTRL_CTS, .rx_flow_ctrl_thresh = 0, .source_clk = UART_SCLK_DEFAULT, .flags = { .allow_pd = 0, }, }; result = uart_driver_install( RS232_UART_PORT, CTS_TEST_RX_BUFFER_SIZE, CTS_TEST_TX_BUFFER_SIZE, FLOW_TEST_QUEUE_SIZE, &event_queue, 0); if (result != ESP_OK) { printf("Could not install UART1 for CTS test: %s\n", esp_err_to_name(result)); goto cleanup; } uart1_installed = true; result = uart_param_config(RS232_UART_PORT, &uart_config); if (result != ESP_OK) { printf("Could not configure UART1 for CTS test: %s\n", esp_err_to_name(result)); goto cleanup; } result = uart_set_line_inverse(RS232_UART_PORT, 0); if (result != ESP_OK) { printf("Could not clear UART1 signal inversion: %s\n", esp_err_to_name(result)); goto cleanup; } result = uart_set_pin( RS232_UART_PORT, RS232_TX_GPIO, RS232_RX_GPIO, UART_PIN_NO_CHANGE, RS232_CTS_GPIO); if (result != ESP_OK) { printf("Could not route UART1 CTS test pins: %s\n", esp_err_to_name(result)); goto cleanup; } result = set_transceiver_enabled(true); if (result != ESP_OK) { printf("Could not enable MAX3243 for CTS test: %s\n", esp_err_to_name(result)); goto cleanup; } vTaskDelay(pdMS_TO_TICKS(TRANSCEIVER_SETTLE_TIME_MS)); result = uart_flush_input(RS232_UART_PORT); if (result != ESP_OK) { printf("Could not flush UART1 before CTS test: %s\n", esp_err_to_name(result)); goto cleanup; } xQueueReset(event_queue); int blocked_cts_level = gpio_get_level(RS232_CTS_GPIO); printf("CTS blocked phase: GPIO%d=%d (expected inactive/high=1)\n", RS232_CTS_GPIO, blocked_cts_level); if (blocked_cts_level != 1) { printf("CTS is not inactive; check the DE-9 pin 4 -> pin 8 jumper.\n"); result = ESP_FAIL; goto cleanup; } int written = uart_write_bytes(RS232_UART_PORT, transmitted, sizeof(transmitted)); if (written < 0 || (size_t)written != sizeof(transmitted)) { printf("UART1 queued %d of %u bytes\n", written, (unsigned int)sizeof(transmitted)); result = ESP_FAIL; goto cleanup; } esp_err_t blocked_wait = uart_wait_tx_done( RS232_UART_PORT, milliseconds_to_ticks(CTS_TEST_BLOCK_TIME_MS)); result = uart_get_buffered_data_len(RS232_UART_PORT, &blocked_rx_size); if (result != ESP_OK) { printf("Could not inspect UART1 RX length: %s\n", esp_err_to_name(result)); goto cleanup; } int blocked_read = uart_read_bytes( RS232_UART_PORT, blocked_probe, sizeof(blocked_probe), 0); collect_uart_events(event_queue, &uart_events); bool blocked_phase_passed = blocked_wait == ESP_ERR_TIMEOUT && blocked_rx_size == 0 && blocked_read == 0 && uart_events.data_events == 0 && uart_events.buffer_full_events == 0 && !uart_has_data_errors(&uart_events); printf("Queued=%u TX-complete=%s RX-buffered=%u RX-read=%d data-events=%u: %s\n", (unsigned int)sizeof(transmitted), blocked_wait == ESP_ERR_TIMEOUT ? "no (blocked)" : "yes/unexpected", (unsigned int)blocked_rx_size, blocked_read, uart_events.data_events, blocked_phase_passed ? "PASS" : "FAIL"); if (!blocked_phase_passed) { result = ESP_FAIL; goto cleanup; } result = set_dtr_level(0); if (result != ESP_OK) { printf("Could not assert DTR to release CTS: %s\n", esp_err_to_name(result)); goto cleanup; } vTaskDelay(pdMS_TO_TICKS(STATIC_SETTLE_TIME_MS)); int active_cts_level = gpio_get_level(RS232_CTS_GPIO); printf("CTS resume phase: GPIO%d=%d (expected active/low=0)\n", RS232_CTS_GPIO, active_cts_level); if (active_cts_level != 0) { printf("CTS did not follow DTR; check the DE-9 pin 4 -> pin 8 jumper.\n"); result = ESP_FAIL; goto cleanup; } result = uart_wait_tx_done( RS232_UART_PORT, milliseconds_to_ticks(CTS_TEST_RESUME_TIMEOUT_MS)); if (result != ESP_OK) { printf("UART1 did not resume after CTS assertion: %s\n", esp_err_to_name(result)); goto cleanup; } result = read_exact_uart( RS232_UART_PORT, received, sizeof(received), CTS_TEST_RESUME_TIMEOUT_MS, &received_size); if (result != ESP_OK) { printf("UART1 received %u of %u bytes after CTS release: %s\n", (unsigned int)received_size, (unsigned int)sizeof(received), esp_err_to_name(result)); goto cleanup; } vTaskDelay(pdMS_TO_TICKS(STATIC_SETTLE_TIME_MS)); result = uart_get_buffered_data_len(RS232_UART_PORT, &extra_bytes); if (result != ESP_OK) { printf("Could not inspect extra UART1 data: %s\n", esp_err_to_name(result)); goto cleanup; } collect_uart_events(event_queue, &uart_events); for (size_t index = 0; index < sizeof(transmitted); ++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]); } } } bool resumed_phase_passed = received_size == sizeof(transmitted) && extra_bytes == 0 && mismatches == 0 && uart_events.buffer_full_events == 0 && !uart_has_data_errors(&uart_events); printf("CTS resume: sent=%u received=%u extra=%u mismatches=%u\n", (unsigned int)sizeof(transmitted), (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_events.frame_errors, uart_events.parity_errors, uart_events.fifo_overflows, uart_events.buffer_full_events, uart_events.breaks); printf("CTS hardware flow-control test: %s\n", resumed_phase_passed ? "PASS" : "FAIL"); result = resumed_phase_passed ? ESP_OK : ESP_FAIL; cleanup:; return finish_flow_test(uart1_installed, false, result); } static int command_cts_flow_test(int argc, char **argv) { (void)argc; (void)argv; printf("Disconnect every external peer; requires only DE-9 pin 3 -> pin 2 and pin 4 -> pin 8.\n"); return run_cts_flow_test() == ESP_OK ? 0 : 1; } static esp_err_t run_rts_flow_test(void) { uint8_t *transmitted = NULL; uint8_t *received = NULL; QueueHandle_t uart1_event_queue = NULL; uart_error_counts_t uart1_events = {0}; bool uart1_installed = false; bool generator_uart_installed = false; esp_err_t result = ESP_FAIL; size_t received_size = 0; size_t extra_bytes = 0; size_t mismatches = 0; size_t ring_before_block = 0; size_t ring_after_block = 0; 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; } transmitted = malloc(RTS_TEST_PAYLOAD_SIZE); received = malloc(RTS_TEST_PAYLOAD_SIZE); if (transmitted == NULL || received == NULL) { printf("Could not allocate RTS test payload buffers.\n"); result = ESP_ERR_NO_MEM; goto cleanup; } generate_payload(transmitted, RTS_TEST_PAYLOAD_SIZE, 0xff); memset(received, 0, RTS_TEST_PAYLOAD_SIZE); result = drive_transceiver_enabled(false); if (result != ESP_OK) { printf("Could not shut down MAX3243 before RTS setup: %s\n", esp_err_to_name(result)); goto cleanup; } result = set_driver_levels(1, 1, 1); if (result != ESP_OK) { printf("Could not establish idle driver levels: %s\n", esp_err_to_name(result)); goto cleanup; } s_uart_active = true; const uart_config_t receiver_config = { .baud_rate = FLOW_TEST_BAUD_RATE, .data_bits = UART_DATA_8_BITS, .parity = UART_PARITY_DISABLE, .stop_bits = UART_STOP_BITS_1, .flow_ctrl = UART_HW_FLOWCTRL_RTS, .rx_flow_ctrl_thresh = RTS_TEST_FLOW_THRESHOLD, .source_clk = UART_SCLK_DEFAULT, .flags = { .allow_pd = 0, }, }; const uart_config_t generator_config = { .baud_rate = FLOW_TEST_BAUD_RATE, .data_bits = UART_DATA_8_BITS, .parity = UART_PARITY_DISABLE, .stop_bits = UART_STOP_BITS_1, .flow_ctrl = UART_HW_FLOWCTRL_CTS, .rx_flow_ctrl_thresh = 0, .source_clk = UART_SCLK_DEFAULT, .flags = { .allow_pd = 0, }, }; result = uart_driver_install( RS232_UART_PORT, RTS_TEST_RX_BUFFER_SIZE, 0, RTS_TEST_QUEUE_SIZE, &uart1_event_queue, 0); if (result != ESP_OK) { printf("Could not install UART1 receiver: %s\n", esp_err_to_name(result)); goto cleanup; } uart1_installed = true; result = uart_param_config(RS232_UART_PORT, &receiver_config); if (result != ESP_OK) { printf("Could not configure UART1 receiver: %s\n", esp_err_to_name(result)); goto cleanup; } result = uart_set_line_inverse(RS232_UART_PORT, 0); if (result != ESP_OK) { printf("Could not clear UART1 inversion: %s\n", esp_err_to_name(result)); goto cleanup; } result = uart_set_pin( RS232_UART_PORT, UART_PIN_NO_CHANGE, RS232_RX_GPIO, RS232_RTS_GPIO, UART_PIN_NO_CHANGE); if (result != ESP_OK) { printf("Could not route UART1 RX/RTS pins: %s\n", esp_err_to_name(result)); goto cleanup; } result = uart_set_rx_full_threshold(RS232_UART_PORT, RTS_TEST_RX_INTERRUPT_THRESHOLD); if (result != ESP_OK) { printf("Could not set UART1 RX interrupt threshold: %s\n", esp_err_to_name(result)); goto cleanup; } result = uart_driver_install( RS232_TEST_GENERATOR_UART_PORT, RTS_TEST_GENERATOR_RX_BUFFER_SIZE, RTS_TEST_GENERATOR_TX_BUFFER_SIZE, 0, NULL, 0); if (result != ESP_OK) { printf("Could not install UART2 generator: %s\n", esp_err_to_name(result)); goto cleanup; } generator_uart_installed = true; result = uart_param_config(RS232_TEST_GENERATOR_UART_PORT, &generator_config); if (result != ESP_OK) { printf("Could not configure UART2 generator: %s\n", esp_err_to_name(result)); goto cleanup; } result = uart_set_line_inverse(RS232_TEST_GENERATOR_UART_PORT, 0); if (result != ESP_OK) { printf("Could not clear UART2 inversion: %s\n", esp_err_to_name(result)); goto cleanup; } result = uart_set_pin( RS232_TEST_GENERATOR_UART_PORT, RS232_DTR_GPIO, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, RS232_DCD_GPIO); if (result != ESP_OK) { printf("Could not route UART2 TX/CTS pins: %s\n", esp_err_to_name(result)); goto cleanup; } result = set_transceiver_enabled(true); if (result != ESP_OK) { printf("Could not enable MAX3243 for RTS test: %s\n", esp_err_to_name(result)); goto cleanup; } vTaskDelay(pdMS_TO_TICKS(TRANSCEIVER_SETTLE_TIME_MS)); result = uart_flush_input(RS232_UART_PORT); if (result != ESP_OK) { printf("Could not flush UART1 before RTS test: %s\n", esp_err_to_name(result)); goto cleanup; } xQueueReset(uart1_event_queue); int ready_level = gpio_get_level(RS232_DCD_GPIO); printf("RTS ready phase: DCD/GPIO%d=%d (expected active/low=0)\n", RS232_DCD_GPIO, ready_level); if (ready_level != 0) { printf("UART1 RTS is not reaching UART2 CTS; check DE-9 pin 7 -> pin 1.\n"); result = ESP_FAIL; goto cleanup; } int written = uart_write_bytes( RS232_TEST_GENERATOR_UART_PORT, transmitted, RTS_TEST_PAYLOAD_SIZE); if (written != RTS_TEST_PAYLOAD_SIZE) { printf("UART2 queued %d of %d bytes\n", written, RTS_TEST_PAYLOAD_SIZE); result = ESP_FAIL; goto cleanup; } int64_t fill_deadline_us = esp_timer_get_time() + (int64_t)RTS_TEST_FILL_TIMEOUT_MS * 1000; while (uart1_events.buffer_full_events == 0 && !uart_has_data_errors(&uart1_events)) { int64_t remaining_us = fill_deadline_us - esp_timer_get_time(); if (remaining_us <= 0) { break; } uint64_t remaining_ms = ((uint64_t)remaining_us + 999U) / 1000U; uart_event_t event; if (xQueueReceive( uart1_event_queue, &event, milliseconds_to_ticks(remaining_ms)) == pdTRUE) { classify_uart_event(&event, &uart1_events); } } bool rts_blocked = wait_for_gpio_level( RS232_DCD_GPIO, 1, RTS_TEST_BLOCK_TIME_MS); result = uart_get_buffered_data_len(RS232_UART_PORT, &ring_before_block); if (result != ESP_OK) { printf("Could not inspect UART1 ring before blocked wait: %s\n", esp_err_to_name(result)); goto cleanup; } esp_err_t generator_wait = uart_wait_tx_done( RS232_TEST_GENERATOR_UART_PORT, milliseconds_to_ticks(RTS_TEST_BLOCK_TIME_MS)); result = uart_get_buffered_data_len(RS232_UART_PORT, &ring_after_block); if (result != ESP_OK) { printf("Could not inspect UART1 ring after blocked wait: %s\n", esp_err_to_name(result)); goto cleanup; } bool blocked_phase_passed = uart1_events.buffer_full_events > 0 && rts_blocked && gpio_get_level(RS232_DCD_GPIO) == 1 && generator_wait == ESP_ERR_TIMEOUT && ring_before_block > 0 && ring_before_block < RTS_TEST_PAYLOAD_SIZE && !uart_has_data_errors(&uart1_events); printf("RTS blocked phase: buffer-full=%u DCD/CTS=%d TX-complete=%s ring=%u->%u: %s\n", uart1_events.buffer_full_events, gpio_get_level(RS232_DCD_GPIO), generator_wait == ESP_ERR_TIMEOUT ? "no (blocked)" : "yes/unexpected", (unsigned int)ring_before_block, (unsigned int)ring_after_block, blocked_phase_passed ? "PASS" : "FAIL"); if (!blocked_phase_passed) { result = ESP_FAIL; goto cleanup; } int64_t receive_deadline_us = esp_timer_get_time() + (int64_t)RTS_TEST_COMPLETE_TIMEOUT_MS * 1000; while (received_size < RTS_TEST_PAYLOAD_SIZE && !uart_has_data_errors(&uart1_events)) { int64_t remaining_us = receive_deadline_us - esp_timer_get_time(); if (remaining_us <= 0) { break; } size_t request = RTS_TEST_PAYLOAD_SIZE - received_size; if (request > RTS_TEST_READ_CHUNK_SIZE) { request = RTS_TEST_READ_CHUNK_SIZE; } uint64_t wait_ms = ((uint64_t)remaining_us + 999U) / 1000U; if (wait_ms > 100) { wait_ms = 100; } int count = uart_read_bytes( RS232_UART_PORT, received + received_size, request, milliseconds_to_ticks(wait_ms)); if (count < 0) { printf("UART1 read failed during RTS resume.\n"); result = ESP_FAIL; goto cleanup; } received_size += (size_t)count; collect_uart_events(uart1_event_queue, &uart1_events); } esp_err_t completion_wait = uart_wait_tx_done( RS232_TEST_GENERATOR_UART_PORT, milliseconds_to_ticks(1000)); vTaskDelay(pdMS_TO_TICKS(STATIC_SETTLE_TIME_MS)); result = uart_get_buffered_data_len(RS232_UART_PORT, &extra_bytes); if (result != ESP_OK) { printf("Could not inspect extra UART1 data: %s\n", esp_err_to_name(result)); goto cleanup; } collect_uart_events(uart1_event_queue, &uart1_events); bool rts_ready_again = wait_for_gpio_level( RS232_DCD_GPIO, 0, RTS_TEST_BLOCK_TIME_MS); size_t comparable = received_size < RTS_TEST_PAYLOAD_SIZE ? received_size : RTS_TEST_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 += RTS_TEST_PAYLOAD_SIZE - comparable; mismatches += extra_bytes; bool resumed_phase_passed = received_size == RTS_TEST_PAYLOAD_SIZE && completion_wait == ESP_OK && extra_bytes == 0 && mismatches == 0 && rts_ready_again && uart1_events.buffer_full_events > 0 && !uart_has_data_errors(&uart1_events); printf("RTS resume: sent=%d received=%u extra=%u mismatches=%u ready-again=%s\n", RTS_TEST_PAYLOAD_SIZE, (unsigned int)received_size, (unsigned int)extra_bytes, (unsigned int)mismatches, rts_ready_again ? "yes" : "no"); printf("UART1 events: data=%u frame=%u parity=%u FIFO-overflow=%u buffer-full=%u break=%u\n", uart1_events.data_events, uart1_events.frame_errors, uart1_events.parity_errors, uart1_events.fifo_overflows, uart1_events.buffer_full_events, uart1_events.breaks); printf("RTS hardware flow-control test: %s\n", resumed_phase_passed ? "PASS" : "FAIL"); result = resumed_phase_passed ? ESP_OK : ESP_FAIL; cleanup:; result = finish_flow_test(uart1_installed, generator_uart_installed, result); free(received); free(transmitted); return result; } static int command_rts_flow_test(int argc, char **argv) { (void)argc; (void)argv; printf("Disconnect every external peer; requires only DE-9 pin 4 -> pin 2 and pin 7 -> pin 1.\n"); return run_rts_flow_test() == ESP_OK ? 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 ", .hint = NULL, .func = &command_transceiver, .argtable = NULL, }, { .command = "drivers", .help = "Set static logic levels: drivers ", .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 [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, }, { .command = "cts-flow-test", .help = "Verify that UART1 CTS blocks and resumes an exact transmission", .hint = NULL, .func = &command_cts_flow_test, .argtable = NULL, }, { .command = "rts-flow-test", .help = "Verify automatic UART1 RTS backpressure with a UART2 generator", .hint = NULL, .func = &command_rts_flow_test, .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; }