diff --git a/README.md b/README.md index 2d8d45f..58716c7 100644 --- a/README.md +++ b/README.md @@ -44,9 +44,11 @@ loopback-b valid-test uart-loopback [8N1|8E1|8O1|8N2|7E1|7O1] [bytes] uart-suite +cts-flow-test +rts-flow-test ``` -`uart-loopback` defaults to `8N1` and 256 bytes. Its accepted payload range is 1–512 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. +`uart-loopback` defaults to `8N1` and 256 bytes. Its accepted payload range is 1–512 bytes. `uart-suite` covers 300 through 250000 baud and all supported frame formats. `cts-flow-test` verifies transmit gating and exact resumption, while `rts-flow-test` uses UART2 as an internal traffic generator to verify automatic receive backpressure. Follow the command-specific loopback wiring in [`wiring.md`](wiring.md) before invoking any test. The onboard RGB LED reports the most recent test-harness state: diff --git a/src/board_pins.h b/src/board_pins.h index 7841304..ff3cb56 100644 --- a/src/board_pins.h +++ b/src/board_pins.h @@ -13,6 +13,10 @@ #define BOARD_RGB_LED_GPIO GPIO_NUM_48 #define RS232_UART_PORT UART_NUM_1 + +/* UART2 is used only as an internal traffic generator during flow-control tests. */ +#define RS232_TEST_GENERATOR_UART_PORT UART_NUM_2 + #define RS232_TX_GPIO GPIO_NUM_17 #define RS232_RX_GPIO GPIO_NUM_18 #define RS232_RTS_GPIO GPIO_NUM_15 diff --git a/src/rs232_hw_test.c b/src/rs232_hw_test.c index 567abf4..d5f7448 100644 --- a/src/rs232_hw_test.c +++ b/src/rs232_hw_test.c @@ -27,6 +27,27 @@ #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, @@ -48,6 +69,7 @@ typedef struct { } serial_format_t; typedef struct { + unsigned int data_events; unsigned int frame_errors; unsigned int parity_errors; unsigned int fifo_overflows; @@ -185,6 +207,15 @@ static esp_err_t set_driver_levels(int tx, int dtr, int 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) { @@ -476,34 +507,95 @@ static void generate_payload(uint8_t *payload, size_t payload_size, uint8_t 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) { - 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; - } + 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. */ @@ -795,6 +887,621 @@ static int command_uart_suite(int argc, char **argv) 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; @@ -866,6 +1573,20 @@ esp_err_t rs232_hw_test_register_console_commands(void) .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) { diff --git a/wiring.md b/wiring.md index c13048b..68d6e65 100644 --- a/wiring.md +++ b/wiring.md @@ -138,6 +138,47 @@ 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. +### Configuration C: CTS transmit gating + +Disconnect every external DE-9 cable or peer, power the ESP32 and breakout down, and remove every previous jumper. Confirm that the two jumpers below are the only connections to these RS-232 pins before powering up again. + +| From driven output | To receiver input | Test purpose | +|---|---|---| +| DE-9 pin 3, `TX` | DE-9 pin 2, `RX` | Return UART1 transmitted data for exact comparison | +| DE-9 pin 4, `DTR` | DE-9 pin 8, `CTS` | Let software-controlled DTR present inactive and active CTS states | + +```text +DE-9 pin 3 TX ─────> pin 2 RX +DE-9 pin 4 DTR ─────> pin 8 CTS +``` + +Run `cts-flow-test`. The test operates UART1 at 115200 baud with hardware CTS enabled and performs two phases: + +1. DTR logic 1 produces CTS logic 1, the inactive/high state. The firmware queues 512 bytes and verifies that transmission does not complete and no byte reaches RX during a 250 ms observation period. +2. DTR changes to logic 0, producing active/low CTS. The queued transmission must resume automatically, and all 512 bytes must return through RX without missing, extra, or corrupted data and without UART errors. + +A buffered UART transmitter is used so the console command itself cannot deadlock while CTS is blocking transmission. The firmware does not manipulate the UART transmit queue between the blocked and resumed phases. + +### Configuration D: RTS receive backpressure + +Disconnect every external DE-9 cable or peer, power the ESP32 and breakout down, and remove every previous jumper. Confirm that the two jumpers below are the only connections to these RS-232 pins before powering up again. + +| From driven output | To receiver input | Test purpose | +|---|---|---| +| DE-9 pin 4, `DTR` | DE-9 pin 2, `RX` | Carry UART2-generated test data into UART1 RX | +| DE-9 pin 7, `RTS` | DE-9 pin 1, `DCD` | Return UART1 RTS through DCD to UART2 CTS | + +```text +UART2 TX / DE-9 pin 4 DTR ─────> pin 2 RX / UART1 RX +UART1 RTS / DE-9 pin 7 RTS ─────> pin 1 DCD / UART2 CTS +``` + +Run `rts-flow-test`. GPIO7 is temporarily routed from UART2 TX through the MAX3243 DTR driver. GPIO4 receives UART1 RTS through the DCD receiver and is simultaneously routed to UART2 CTS. This creates a complete hardware-controlled flow loop without an external serial peer. + +The test queues 4096 bytes from UART2 but initially does not read UART1. UART1's receive ring eventually fills, its hardware FIFO crosses the configured threshold, and UART1 automatically deasserts RTS. That state passes through the physical pin 7 to pin 1 jumper and blocks UART2 through CTS. The firmware then drains UART1, which must automatically reassert RTS and allow UART2 to finish. + +`UART_BUFFER_FULL` is expected during this deliberate pressure test. It means the UART ISR could not fit its current received-data batch into the software RX ring, confirming receive-side resource pressure. The driver preserves that batch while hardware RTS stops the sender. `UART_FIFO_OVF`, framing errors, parity errors, breaks, missing bytes, extra bytes, or data mismatches are failures. + ### 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`): @@ -163,8 +204,10 @@ Each output should be at a negative RS-232 voltage. Exact values vary with suppl 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`. +8. Power down, install configuration C, power up, and run `cts-flow-test`. +9. Power down, install configuration D, power up, and run `rts-flow-test`. -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. +RTS and CTS remain ordinary GPIO signals during static and basic UART loopback tests. Only the two dedicated flow-control commands hand them to UART peripherals. Every test shuts the MAX3243 down while changing GPIO-matrix routing and restores all outputs to static logic 1 afterward. ## Future hardware profiles