Add CTS and RTS flow-control tests
This commit is contained in:
+742
-21
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user