diff --git a/components/driver/include/driver/uart.h b/components/driver/include/driver/uart.h index 08c398f61f..914d1d8eb5 100644 --- a/components/driver/include/driver/uart.h +++ b/components/driver/include/driver/uart.h @@ -46,6 +46,17 @@ extern "C" { #define UART_INVERSE_TXD (UART_TXD_INV_M) /*!< UART TXD output inverse*/ #define UART_INVERSE_RTS (UART_RTS_INV_M) /*!< UART RTS output inverse*/ +/** + * @brief UART mode selection + */ +typedef enum { + UART_MODE_UART = 0x0, /*!< mode: regular UART mode*/ + UART_MODE_RS485_A = 0x01, /*!< mode: RS485 collision detection UART mode*/ + UART_MODE_RS485_B = 0x02, /*!< mode: application control RS485 UART mode*/ + UART_MODE_RS485_HALF_DUPLEX = 0x03, /*!< mode: half duplex RS485 UART mode control by RTS pin */ + UART_MODE_IRDA = 0x4, /*!< mode: IRDA UART mode*/ +} uart_mode_t; + /** * @brief UART word length constants */ @@ -54,7 +65,7 @@ typedef enum { UART_DATA_6_BITS = 0x1, /*!< word length: 6bits*/ UART_DATA_7_BITS = 0x2, /*!< word length: 7bits*/ UART_DATA_8_BITS = 0x3, /*!< word length: 8bits*/ - UART_DATA_BITS_MAX = 0X4, + UART_DATA_BITS_MAX = 0x4, } uart_word_length_t; /** @@ -73,7 +84,7 @@ typedef enum { typedef enum { UART_NUM_0 = 0x0, /*!< UART base address 0x3ff40000*/ UART_NUM_1 = 0x1, /*!< UART base address 0x3ff50000*/ - UART_NUM_2 = 0x2, /*!< UART base address 0x3ff6e000*/ + UART_NUM_2 = 0x2, /*!< UART base address 0x3ff6E000*/ UART_NUM_MAX, } uart_port_t; @@ -249,8 +260,8 @@ esp_err_t uart_get_baudrate(uart_port_t uart_num, uint32_t* baudrate); * @param uart_num UART_NUM_0, UART_NUM_1 or UART_NUM_2 * @param inverse_mask Choose the wires that need to be inverted. * Inverse_mask should be chosen from - UART_INVERSE_RXD / UART_INVERSE_TXD / UART_INVERSE_RTS / UART_INVERSE_CTS, - combined with OR operation. + * UART_INVERSE_RXD / UART_INVERSE_TXD / UART_INVERSE_RTS / UART_INVERSE_CTS, + * combined with OR operation. * * @return * - ESP_OK Success @@ -474,7 +485,7 @@ esp_err_t uart_set_dtr(uart_port_t uart_num, int level); esp_err_t uart_set_tx_idle_num(uart_port_t uart_num, uint16_t idle_num); /** -* @brief Set UART configuration parameters. + * @brief Set UART configuration parameters. * * @param uart_num UART_NUM_0, UART_NUM_1 or UART_NUM_2 * @param uart_config UART parameter settings @@ -486,7 +497,7 @@ esp_err_t uart_set_tx_idle_num(uart_port_t uart_num, uint16_t idle_num); esp_err_t uart_param_config(uart_port_t uart_num, const uart_config_t *uart_config); /** -* @brief Configure UART interrupts. + * @brief Configure UART interrupts. * * @param uart_num UART_NUM_0, UART_NUM_1 or UART_NUM_2 * @param intr_conf UART interrupt settings @@ -552,8 +563,8 @@ esp_err_t uart_wait_tx_done(uart_port_t uart_num, TickType_t ticks_to_wait); * @note This function should only be used when UART TX buffer is not enabled. * * @param uart_num UART_NUM_0, UART_NUM_1 or UART_NUM_2 - * @param buffer data buffer address - * @param len data length to send + * @param buffer data buffer address + * @param len data length to send * * @return * - (-1) Parameter error @@ -571,8 +582,8 @@ int uart_tx_chars(uart_port_t uart_num, const char* buffer, uint32_t len); * UART ISR will then move data from the ring buffer to TX FIFO gradually. * * @param uart_num UART_NUM_0, UART_NUM_1 or UART_NUM_2 - * @param src data buffer address - * @param size data length to send + * @param src data buffer address + * @param size data length to send * * @return * - (-1) Parameter error @@ -581,9 +592,9 @@ int uart_tx_chars(uart_port_t uart_num, const char* buffer, uint32_t len); int uart_write_bytes(uart_port_t uart_num, const char* src, size_t size); /** - * @brief Send data to the UART port from a given buffer and length. + * @brief Send data to the UART port from a given buffer and length, * - * If the UART driver's parameter 'tx_buffer_size' is set to zero: +* If the UART driver's parameter 'tx_buffer_size' is set to zero: * This function will not return until all the data and the break signal have been sent out. * After all data is sent out, send a break signal. * @@ -641,9 +652,10 @@ esp_err_t uart_flush(uart_port_t uart_num); esp_err_t uart_flush_input(uart_port_t uart_num); /** - * @brief UART get RX ring buffer cached data length - * @param uart_num UART port number. - * @param size Pointer of size_t to accept cached data length + * @brief UART get RX ring buffer cached data length + * + * @param uart_num UART port number. + * @param size Pointer of size_t to accept cached data length * * @return * - ESP_OK Success @@ -652,9 +664,9 @@ esp_err_t uart_flush_input(uart_port_t uart_num); esp_err_t uart_get_buffered_data_len(uart_port_t uart_num, size_t* size); /** - * @brief UART disable pattern detect function. - * Designed for applications like 'AT commands'. - * When the hardware detects a series of one same character, the interrupt will be triggered. + * @brief UART disable pattern detect function. + * Designed for applications like 'AT commands'. + * When the hardware detects a series of one same character, the interrupt will be triggered. * * @param uart_num UART port number. * @@ -737,6 +749,48 @@ int uart_pattern_get_pos(uart_port_t uart_num); */ esp_err_t uart_pattern_queue_reset(uart_port_t uart_num, int queue_length); +/** + * @brief UART set communication mode + * @note This function must be executed after uart_driver_install(), when the driver object is initialized. + * @param uart_num Uart number to configure + * @param mode UART UART mode to set + * + * @return + * - ESP_OK Success + * - ESP_FAIL Parameter error + */ +esp_err_t uart_set_mode(uart_port_t uart_num, uart_mode_t mode); + +/** + * @brief UART set threshold timeout for TOUT feature + * + * @param uart_num Uart number to configure + * @param tout_thresh This parameter defines timeout threshold in uart symbol periods. + * tout_thresh = 1, defines TOUT interrupt timeout equal to transmission time of one symbol (~11 bit) on current baudrate. + * If the time is expired the UART_RXFIFO_TOUT_INT interrupt is triggered. If tout_thresh == 0, + * the TOUT feature is disabled. + * + * @return + * - ESP_OK Success + * - ESP_FAIL Parameter error + */ +esp_err_t uart_set_rx_timeout(uart_port_t uart_num, const uint8_t tout_thresh); + +/** + * @brief Returns collision detection flag for RS485 mode + * Function returns the collision detection flag into variable pointed by collision_flag. + * *collision_flag = true, if collision detected else it is equal to false. The maximum value of threshold is 126. + * This function should be executed when actual transmission is completed (after uart_write_bytes()). + * + * @param uart_num Uart number to configure + * @param collision_flag Pointer to variable of type bool to return collision flag. + * + * @return + * - ESP_OK No collision + * - ESP_FAIL Parameter error + */ +esp_err_t uart_get_collision_flag(uart_port_t uart_num, bool* collision_flag); + #ifdef __cplusplus } #endif diff --git a/components/driver/test/test_uart.c b/components/driver/test/test_uart.c index f49c73ef7c..b9f8be9eef 100644 --- a/components/driver/test/test_uart.c +++ b/components/driver/test/test_uart.c @@ -1,7 +1,9 @@ #include #include "unity.h" -#include "driver/uart.h" +#include "test_utils.h" // unity_send_signal +#include "driver/uart.h" // for the uart driver access #include "esp_log.h" +#include "esp_system.h" // for uint32_t esp_random() #define UART_TAG "Uart" #define UART_NUM1 (UART_NUM_1) @@ -14,6 +16,92 @@ #define UART_TOLERANCE_CHECK(val, uper_limit, lower_limit) ( (val) <= (uper_limit) && (val) >= (lower_limit) ) +// RTS for RS485 Half-Duplex Mode manages DE/~RE +#define UART1_RTS_PIN (18) + +// Number of packets to be send during test +#define PACKETS_NUMBER (10) + +// Wait timeout for uart driver +#define PACKET_READ_TICS (1000 / portTICK_RATE_MS) + +// The table for fast CRC16 calculation +static const uint8_t crc_hi[] = { + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, + 0x00, 0xC1, 0x81, + 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, + 0x40, 0x01, 0xC0, + 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, + 0x81, 0x40, 0x01, + 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, + 0xC0, 0x80, 0x41, + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, + 0x00, 0xC1, 0x81, + 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, + 0x41, 0x01, 0xC0, + 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, + 0x80, 0x41, 0x01, + 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, + 0xC1, 0x81, 0x40, + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, + 0x00, 0xC1, 0x81, + 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, + 0x40, 0x01, 0xC0, + 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, + 0x81, 0x40, 0x01, + 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, + 0xC0, 0x80, 0x41, + 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, + 0x00, 0xC1, 0x81, + 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, + 0x40, 0x01, 0xC0, + 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, + 0x80, 0x41, 0x01, + 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, + 0xC0, 0x80, 0x41, + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, + 0x00, 0xC1, 0x81, + 0x40 +}; + +static const uint8_t crc_low[] = { + 0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, + 0x05, 0xC5, 0xC4, + 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, + 0x0B, 0xC9, 0x09, + 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, + 0xDF, 0x1F, 0xDD, + 0x1D, 0x1C, 0xDC, 0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, + 0x12, 0x13, 0xD3, + 0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32, + 0x36, 0xF6, 0xF7, + 0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, + 0xFE, 0xFA, 0x3A, + 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, + 0x2A, 0xEA, 0xEE, + 0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, + 0xE7, 0xE6, 0x26, + 0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1, + 0x63, 0xA3, 0xA2, + 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, + 0x6D, 0xAF, 0x6F, + 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, + 0xB9, 0x79, 0xBB, + 0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, + 0x74, 0x75, 0xB5, + 0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0, + 0x50, 0x90, 0x91, + 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, + 0x54, 0x9C, 0x5C, + 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, + 0x58, 0x98, 0x88, + 0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, + 0x4D, 0x4C, 0x8C, + 0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83, + 0x41, 0x81, 0x80, + 0x40 +}; + static void uart_config(uint32_t baud_rate, bool use_ref_tick) { uart_config_t uart_config = { @@ -45,4 +133,189 @@ TEST_CASE("test uart get baud-rate","[uart]") TEST_ASSERT(UART_TOLERANCE_CHECK(baud_rate1, (1.0 + TOLERANCE)*UART_BAUD_11520, (1.0 - TOLERANCE)*UART_BAUD_11520)) TEST_ASSERT(UART_TOLERANCE_CHECK(baud_rate2, (1.0 + TOLERANCE)*UART_BAUD_115200, (1.0 - TOLERANCE)*UART_BAUD_115200)) ESP_LOGI(UART_TAG, "get baud-rate test passed ....\n"); -} \ No newline at end of file +} + +// Calculate buffer checksum using tables +// The checksum CRC16 algorithm is specific +// for Modbus standard and uses polynomial value = 0xA001 +static uint16_t get_buffer_crc16( uint8_t * frame_ptr, uint16_t length ) +{ + TEST_ASSERT( frame_ptr != NULL); + + uint8_t crc_hi_byte = 0xFF; + uint8_t crc_low_byte = 0xFF; + int index; + + while ( length-- ) + { + index = crc_low_byte ^ *(frame_ptr++); + crc_low_byte = crc_hi_byte ^ crc_hi[index]; + crc_hi_byte = crc_low[index]; + } + return ((crc_hi_byte << 8) | crc_low_byte); +} + +// Fill the buffer with random numbers and apply CRC16 at the end +static uint16_t buffer_fill_random(uint8_t *buffer, size_t length) +{ + TEST_ASSERT( buffer != NULL); + uint8_t *byte_buffer = (uint8_t *)buffer; + uint32_t random; + // Pcket is too short + if (length < 4) { + return 0; + } + for (int i = 0; i < length; i++) { + if (i == 0 || i % 4 == 0) { + // Generates random int32_t number + random = esp_random(); + } + + // Place each byte of the uint32_t random number into buffer + byte_buffer[i] = random >> ((i % 4) * 8); + } + // Get checksum of the buffer + uint16_t crc = get_buffer_crc16((uint8_t*)byte_buffer, (length - 2)); + // Apply checksum bytes into packet + byte_buffer[length - 2] = (uint8_t)(crc & 0xFF); // Set Low byte CRC + byte_buffer[length - 1] = (uint8_t)(crc >> 8); // Set High byte CRC + return crc; +} + +static void rs485_init() +{ + uart_config_t uart_config = { + .baud_rate = UART_BAUD_115200, + .data_bits = UART_DATA_8_BITS, + .parity = UART_PARITY_DISABLE, + .stop_bits = UART_STOP_BITS_1, + .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, + .rx_flow_ctrl_thresh = 122, + }; + printf("RS485 port initialization...\r\n"); + // Configure UART1 parameters + uart_param_config(UART_NUM1, &uart_config); + // Set UART1 pins(TX: IO4, RX: I05, RTS: IO18, CTS: IO19) + uart_set_pin(UART_NUM1, UART1_TX_PIN, UART1_RX_PIN, UART1_RTS_PIN, UART_PIN_NO_CHANGE); + // Install UART driver (we don't need an event queue here) + uart_driver_install(UART_NUM1, BUF_SIZE * 2, 0, 0, NULL, 0); + // Setup rs485 half duplex mode + //uart_set_rs485_hd_mode(uart_num, true); + uart_set_mode(UART_NUM1, UART_MODE_RS485_HALF_DUPLEX); +} + +static esp_err_t print_packet_data(const char *str, uint8_t *buffer, uint16_t buffer_size) +{ + TEST_ASSERT( buffer != NULL); + TEST_ASSERT( str != NULL); + + // Calculate the checksum of the buffer + uint16_t crc16_calc = get_buffer_crc16(buffer, (buffer_size - 2)); + uint16_t crc16_in = ((uint16_t)(buffer[buffer_size - 1]) << 8) | buffer[buffer_size - 2]; + const char* state_str = (crc16_in != crc16_calc) ? "incorrect " : "correct "; + // Print an array of data + printf("%s%s RS485 packet = [ ", str, state_str); + for (int i = 0; i < buffer_size; i++) { + printf("0x%.2X ", (uint8_t)buffer[i]); + } + printf(" ]\r\n"); + printf("crc_in = 0x%.4X\r\n", (uint16_t)crc16_in); + printf("crc_calc = 0x%.4X\r\n", (uint16_t)crc16_calc); + esp_err_t result = (crc16_in != crc16_calc) ? ESP_ERR_INVALID_CRC : ESP_OK; + return result; +} + +// Slave test case for multi device +static void rs485_slave() +{ + rs485_init(); + uint8_t* slave_data = (uint8_t*) malloc(BUF_SIZE); + uint16_t err_count = 0, good_count = 0; + printf("Start recieve loop.\r\n"); + unity_send_signal("Slave_ready"); + unity_wait_for_signal("Master_started"); + for(int pack_count = 0; pack_count < PACKETS_NUMBER; pack_count++) { + //Read slave_data from UART + int len = uart_read_bytes(UART_NUM1, slave_data, BUF_SIZE, (PACKET_READ_TICS * 2)); + //Write slave_data back to UART + if (len > 2) { + esp_err_t status = print_packet_data("Received ", slave_data, len); + + // If recieved packet is correct then send it back + if (status == ESP_OK) { + uart_write_bytes(UART_NUM1, (char*)slave_data, len); + good_count++; + } else { + printf("Incorrect packet received.\r\n"); + err_count++; + } + } else { + printf("Incorrect data packet[%d] received.\r\n", pack_count); + err_count++; + } + } + printf("Test completed. Received packets = %d, errors = %d\r\n", good_count, err_count); + // Wait for packet to be sent + uart_wait_tx_done(UART_NUM1, PACKET_READ_TICS); + free(slave_data); + uart_driver_delete(UART_NUM1); + TEST_ASSERT(err_count < 2); +} + +// Master test of multi device test case. +// It forms packet with random data, apply generated CRC16 and sends to slave. +// If response recieved correctly from slave means RS485 channel works. +static void rs485_master() +{ + uint16_t err_count = 0, good_count = 0; + rs485_init(); + uint8_t* master_buffer = (uint8_t*) malloc(BUF_SIZE); + uint8_t* slave_buffer = (uint8_t*) malloc(BUF_SIZE); + // The master test case should be synchronized with slave + unity_wait_for_signal("Slave_ready"); + unity_send_signal("Master_started"); + printf("Start recieve loop.\r\n"); + for(int i = 0; i < PACKETS_NUMBER; i++) { + // Form random buffer with CRC16 + buffer_fill_random(master_buffer, BUF_SIZE); + // Print created packet for debugging + esp_err_t status = print_packet_data("Send ", master_buffer, BUF_SIZE); + TEST_ASSERT(status == ESP_OK); + uart_write_bytes(UART_NUM1, (char*)master_buffer, BUF_SIZE); + // Read translated packet from slave + int len = uart_read_bytes(UART_NUM1, slave_buffer, BUF_SIZE, (PACKET_READ_TICS * 2)); + // Check if the received packet is too short + if (len > 2) { + // Print received packet and check checksum + esp_err_t status = print_packet_data("Received ", slave_buffer, len); + if (status == ESP_OK) { + good_count++; + printf("Received: %d\r\n", good_count); + } else { + err_count++; + printf("Errors: %d\r\n", err_count); + } + } + else { + printf("Incorrect answer from slave.\r\n"); + err_count++; + } + } + // Free the buffer and delete driver at the end + free(master_buffer); + uart_driver_delete(UART_NUM1); + TEST_ASSERT(err_count <= 1); + printf("Test completed. Received packets = %d, errors = %d\r\n", (uint16_t)good_count, (uint16_t)err_count); +} + +/* + * This multi devices test case verifies RS485 mode of the uart driver and checks + * correctness of RS485 interface channel communication. It requires + * RS485 bus driver hardware to be connected to boards. +*/ +// The lines below are required to suppress GCC warnings about discarded const qualifiers +// of function pointers in unity macro expansion. These warnings may be treated as errors during compilation. +#pragma GCC diagnostic push // required for GCC +#pragma GCC diagnostic ignored "-Wdiscarded-qualifiers" +TEST_CASE_MULTIPLE_DEVICES("RS485 half duplex uart multiple devices test.", "[driver][ignore]", rs485_master, rs485_slave); +#pragma GCC diagnostic pop // require GCC diff --git a/components/driver/uart.c b/components/driver/uart.c index 94f3c4c655..d1f5715b48 100644 --- a/components/driver/uart.c +++ b/components/driver/uart.c @@ -52,6 +52,9 @@ static const char* UART_TAG = "uart"; #define UART_ENTER_CRITICAL(mux) portENTER_CRITICAL(mux) #define UART_EXIT_CRITICAL(mux) portEXIT_CRITICAL(mux) +// Check actual UART mode set +#define UART_IS_MODE_SET(uart_number, mode) ((p_uart_obj[uart_number]->uart_mode == mode)) + typedef struct { uart_event_type_t type; /*!< UART TX data type */ struct { @@ -73,6 +76,9 @@ typedef struct { int queue_size; /*!< UART event queue size*/ QueueHandle_t xQueueUart; /*!< UART queue handler*/ intr_handle_t intr_handle; /*!< UART interrupt handle*/ + uart_mode_t uart_mode; /*!< UART controller actual mode set by uart_set_mode() */ + bool coll_det_flg; /*!< UART collision detection flag */ + //rx parameters int rx_buffered_len; /*!< UART cached data length */ SemaphoreHandle_t rx_mux; /*!< UART RX data mutex*/ @@ -724,7 +730,7 @@ static void uart_rx_intr_handler_default(void *param) p_uart->tx_waiting_fifo = false; xSemaphoreGiveFromISR(p_uart->tx_fifo_sem, &HPTaskAwoken); if(HPTaskAwoken == pdTRUE) { - portYIELD_FROM_ISR() ; + portYIELD_FROM_ISR(); } } else { //We don't use TX ring buffer, because the size is zero. @@ -754,7 +760,7 @@ static void uart_rx_intr_handler_default(void *param) //We have saved the data description from the 1st item, return buffer. vRingbufferReturnItemFromISR(p_uart->tx_ring_buf, p_uart->tx_head, &HPTaskAwoken); if(HPTaskAwoken == pdTRUE) { - portYIELD_FROM_ISR() ; + portYIELD_FROM_ISR(); } }else if(p_uart->tx_ptr == NULL) { //Update the TX item pointer, we will need this to return item to buffer. @@ -771,8 +777,16 @@ static void uart_rx_intr_handler_default(void *param) if (p_uart->tx_len_tot > 0 && p_uart->tx_ptr && p_uart->tx_len_cur > 0) { //To fill the TX FIFO. int send_len = p_uart->tx_len_cur > tx_fifo_rem ? tx_fifo_rem : p_uart->tx_len_cur; - for(buf_idx = 0; buf_idx < send_len; buf_idx++) { - WRITE_PERI_REG(UART_FIFO_AHB_REG(uart_num), *(p_uart->tx_ptr++) & 0xff); + // Set RS485 RTS pin before transmission if the half duplex mode is enabled + if (UART_IS_MODE_SET(uart_num, UART_MODE_RS485_HALF_DUPLEX)) { + UART_ENTER_CRITICAL_ISR(&uart_spinlock[uart_num]); + uart_reg->conf0.sw_rts = 0; + uart_reg->int_ena.tx_done = 1; + UART_EXIT_CRITICAL_ISR(&uart_spinlock[uart_num]); + } + for (buf_idx = 0; buf_idx < send_len; buf_idx++) { + WRITE_PERI_REG(UART_FIFO_AHB_REG(uart_num), + *(p_uart->tx_ptr++) & 0xff); } p_uart->tx_len_tot -= send_len; p_uart->tx_len_cur -= send_len; @@ -781,7 +795,7 @@ static void uart_rx_intr_handler_default(void *param) //Return item to ring buffer. vRingbufferReturnItemFromISR(p_uart->tx_ring_buf, p_uart->tx_head, &HPTaskAwoken); if(HPTaskAwoken == pdTRUE) { - portYIELD_FROM_ISR() ; + portYIELD_FROM_ISR(); } p_uart->tx_head = NULL; p_uart->tx_ptr = NULL; @@ -885,7 +899,7 @@ static void uart_rx_intr_handler_default(void *param) UART_EXIT_CRITICAL_ISR(&uart_spinlock[uart_num]); } if(HPTaskAwoken == pdTRUE) { - portYIELD_FROM_ISR() ; + portYIELD_FROM_ISR(); } } else { uart_disable_intr_mask(uart_num, UART_RXFIFO_FULL_INT_ENA_M | UART_RXFIFO_TOUT_INT_ENA_M); @@ -943,7 +957,7 @@ static void uart_rx_intr_handler_default(void *param) } else { xSemaphoreGiveFromISR(p_uart->tx_brk_sem, &HPTaskAwoken); if(HPTaskAwoken == pdTRUE) { - portYIELD_FROM_ISR() ; + portYIELD_FROM_ISR(); } } } else if(uart_intr_status & UART_TX_BRK_IDLE_DONE_INT_ST_M) { @@ -952,12 +966,33 @@ static void uart_rx_intr_handler_default(void *param) } else if(uart_intr_status & UART_AT_CMD_CHAR_DET_INT_ST_M) { uart_reg->int_clr.at_cmd_char_det = 1; uart_event.type = UART_PATTERN_DET; + } else if ((uart_intr_status & UART_RS485_CLASH_INT_ST_M) + || (uart_intr_status & UART_RS485_FRM_ERR_INT_ENA) + || (uart_intr_status & UART_RS485_PARITY_ERR_INT_ENA)) { + // RS485 collision or frame error interrupt triggered + uart_clear_intr_status(uart_num, UART_RS485_CLASH_INT_CLR_M); + UART_ENTER_CRITICAL_ISR(&uart_spinlock[uart_num]); + uart_reset_rx_fifo(uart_num); + // Set collision detection flag + p_uart_obj[uart_num]->coll_det_flg = true; + UART_EXIT_CRITICAL_ISR(&uart_spinlock[uart_num]); + uart_event.type = UART_EVENT_MAX; } else if(uart_intr_status & UART_TX_DONE_INT_ST_M) { uart_disable_intr_mask(uart_num, UART_TX_DONE_INT_ENA_M); uart_clear_intr_status(uart_num, UART_TX_DONE_INT_CLR_M); + // If RS485 half duplex mode is enable then reset FIFO and + // reset RTS pin to start receiver driver + if (UART_IS_MODE_SET(uart_num, UART_MODE_RS485_HALF_DUPLEX)) { + UART_ENTER_CRITICAL_ISR(&uart_spinlock[uart_num]); + uart_reg->conf0.rxfifo_rst = 1; // Workaround to clear phantom 00 characters + uart_reg->conf0.rxfifo_rst = 0; // received after transmission. + uart_reset_rx_fifo(uart_num); // Allows to avoid hardware issue with the RXFIFO reset + uart_reg->conf0.sw_rts = 1; + UART_EXIT_CRITICAL_ISR(&uart_spinlock[uart_num]); + } xSemaphoreGiveFromISR(p_uart_obj[uart_num]->tx_done_sem, &HPTaskAwoken); - if(HPTaskAwoken == pdTRUE) { - portYIELD_FROM_ISR() ; + if (HPTaskAwoken == pdTRUE) { + portYIELD_FROM_ISR(); } } else { uart_reg->int_clr.val = uart_intr_status; /*simply clear all other intr status*/ @@ -969,7 +1004,7 @@ static void uart_rx_intr_handler_default(void *param) ESP_EARLY_LOGV(UART_TAG, "UART event queue full"); } if(HPTaskAwoken == pdTRUE) { - portYIELD_FROM_ISR() ; + portYIELD_FROM_ISR(); } } uart_intr_status = uart_reg->int_st.val; @@ -1026,7 +1061,12 @@ static int uart_fill_fifo(uart_port_t uart_num, const char* buffer, uint32_t len uint8_t tx_fifo_cnt = UART[uart_num]->status.txfifo_cnt; uint8_t tx_remain_fifo_cnt = (UART_FIFO_LEN - tx_fifo_cnt); uint8_t copy_cnt = (len >= tx_remain_fifo_cnt ? tx_remain_fifo_cnt : len); - for(i = 0; i < copy_cnt; i++) { + // Set the RTS pin if RS485 mode is enabled + if (UART_IS_MODE_SET(uart_num, UART_MODE_RS485_HALF_DUPLEX)) { + UART[uart_num]->conf0.sw_rts = 0; + UART[uart_num]->int_ena.tx_done = 1; + } + for (i = 0; i < copy_cnt; i++) { WRITE_PERI_REG(UART_FIFO_AHB_REG(uart_num), buffer[i]); } return copy_cnt; @@ -1055,6 +1095,7 @@ static int uart_tx_all(uart_port_t uart_num, const char* src, size_t size, bool //lock for uart_tx xSemaphoreTake(p_uart_obj[uart_num]->tx_mux, (portTickType)portMAX_DELAY); + p_uart_obj[uart_num]->coll_det_flg = false; if(p_uart_obj[uart_num]->tx_buf_size > 0) { int max_size = xRingbufferGetMaxItemSize(p_uart_obj[uart_num]->tx_ring_buf); int offset = 0; @@ -1257,6 +1298,8 @@ esp_err_t uart_driver_install(uart_port_t uart_num, int rx_buffer_size, int tx_b return ESP_FAIL; } p_uart_obj[uart_num]->uart_num = uart_num; + p_uart_obj[uart_num]->uart_mode = UART_MODE_UART; + p_uart_obj[uart_num]->coll_det_flg = false; p_uart_obj[uart_num]->tx_fifo_sem = xSemaphoreCreateBinary(); xSemaphoreGive(p_uart_obj[uart_num]->tx_fifo_sem); p_uart_obj[uart_num]->tx_done_sem = xSemaphoreCreateBinary(); @@ -1393,3 +1436,89 @@ portMUX_TYPE *uart_get_selectlock() { return &uart_selectlock; } +// Set UART mode +esp_err_t uart_set_mode(uart_port_t uart_num, uart_mode_t mode) +{ + UART_CHECK((p_uart_obj[uart_num]), "uart driver error", ESP_ERR_INVALID_STATE); + UART_CHECK((uart_num < UART_NUM_MAX), "uart_num error", ESP_ERR_INVALID_ARG); + if ((mode == UART_MODE_RS485_A) || (mode == UART_MODE_RS485_B) + || (mode == UART_MODE_RS485_HALF_DUPLEX)) { + UART_CHECK((UART[uart_num]->conf1.rx_flow_en != 1), + "disable hw flowctrl before using RS485 mode", ESP_ERR_INVALID_ARG); + } + UART_ENTER_CRITICAL(&uart_spinlock[uart_num]); + UART[uart_num]->rs485_conf.en = 0; + UART[uart_num]->rs485_conf.tx_rx_en = 0; + UART[uart_num]->rs485_conf.rx_busy_tx_en = 0; + UART[uart_num]->conf0.irda_en = 0; + UART[uart_num]->conf0.sw_rts = 0; + switch (mode) { + case UART_MODE_UART: + break; + case UART_MODE_RS485_A: + // This mode allows read while transmitting that allows collision detection + p_uart_obj[uart_num]->coll_det_flg = false; + // Transmitter’s output signal loop back to the receiver’s input signal + UART[uart_num]->rs485_conf.tx_rx_en = 0 ; + // Transmitter should send data when its receiver is busy + UART[uart_num]->rs485_conf.rx_busy_tx_en = 1; + UART[uart_num]->rs485_conf.en = 1; + // Enable collision detection interrupts + uart_enable_intr_mask(uart_num, UART_RXFIFO_TOUT_INT_ENA + | UART_RXFIFO_FULL_INT_ENA + | UART_RS485_CLASH_INT_ENA + | UART_RS485_FRM_ERR_INT_ENA + | UART_RS485_PARITY_ERR_INT_ENA); + break; + case UART_MODE_RS485_B: + // Application software control, remove echo + UART[uart_num]->rs485_conf.rx_busy_tx_en = 1; + UART[uart_num]->rs485_conf.en = 1; + break; + case UART_MODE_RS485_HALF_DUPLEX: + // Enable receiver, sw_rts = 1 generates low level on RTS pin + UART[uart_num]->conf0.sw_rts = 1; + UART[uart_num]->rs485_conf.en = 1; + // Must be set to 0 to automatically remove echo + UART[uart_num]->rs485_conf.tx_rx_en = 0; + // This is to void collision + UART[uart_num]->rs485_conf.rx_busy_tx_en = 1; + break; + case UART_MODE_IRDA: + UART[uart_num]->conf0.irda_en = 1; + break; + default: + UART_CHECK(1, "unsupported uart mode", ESP_FAIL); + break; + } + p_uart_obj[uart_num]->uart_mode = mode; + UART_EXIT_CRITICAL(&uart_spinlock[uart_num]); + return ESP_OK; +} + +esp_err_t uart_set_rx_timeout(uart_port_t uart_num, const uint8_t tout_thresh) +{ + UART_CHECK((uart_num < UART_NUM_MAX), "uart_num error", ESP_FAIL); + UART_CHECK((tout_thresh < 127), "tout_thresh max value is 126", ESP_FAIL); + UART_ENTER_CRITICAL(&uart_spinlock[uart_num]); + // The tout_thresh = 1, defines TOUT interrupt timeout equal to + // transmission time of one symbol (~11 bit) on current baudrate + if (tout_thresh > 0) { + UART[uart_num]->conf1.rx_tout_thrhd = (tout_thresh & UART_RX_TOUT_THRHD_V); + UART[uart_num]->conf1.rx_tout_en = 1; + } else { + UART[uart_num]->conf1.rx_tout_en = 0; + } + UART_EXIT_CRITICAL(&uart_spinlock[uart_num]); + return ESP_OK; +} + +esp_err_t uart_get_collision_flag(uart_port_t uart_num, bool* collision_flag) +{ + UART_CHECK((uart_num < UART_NUM_MAX), "uart_num error", ESP_FAIL); + UART_CHECK((collision_flag != NULL), "wrong parameter pointer", ESP_FAIL); + UART_CHECK((UART_IS_MODE_SET(uart_num, UART_MODE_RS485_HALF_DUPLEX) + || UART_IS_MODE_SET(uart_num, UART_MODE_RS485_A)), "wrong mode", ESP_FAIL); + *collision_flag = p_uart_obj[uart_num]->coll_det_flg; + return ESP_OK; +} diff --git a/docs/en/api-reference/peripherals/uart.rst b/docs/en/api-reference/peripherals/uart.rst index ea2e9615b7..492af2d913 100644 --- a/docs/en/api-reference/peripherals/uart.rst +++ b/docs/en/api-reference/peripherals/uart.rst @@ -4,11 +4,10 @@ UART Overview -------- -An Universal Asynchronous Receiver/Transmitter (UART) is a component known to handle the timing requirements for a variety of widely-adapted protocols (RS232, RS485, RS422, ...). An UART provides a widely adopted and cheap method to realize full-duplex data exchange among different devices. +A Universal Asynchronous Receiver/Transmitter (UART) is a component known to handle the timing requirements for a variety of widely-adapted interfaces (RS232, RS485, RS422, ...). A UART provides a widely adopted and cheap method to realize full-duplex or half-duplex data exchange among different devices. There are three UART controllers available on the ESP32 chip. They are compatible with UART-enabled devices from various manufacturers. All UART controllers integrated in the ESP32 feature an identical set of registers for ease of programming and flexibility. In this documentation, these controllers are referred to as UART0, UART1, and UART2. - Functional Overview ------------------- @@ -40,6 +39,22 @@ The alternate way is to configure specific parameters individually by calling de * Parity control - :cpp:func:`uart_set_parity` selected out of :cpp:type:`uart_parity_t` * Number of stop bits - :cpp:func:`uart_set_stop_bits` selected out of :cpp:type:`uart_stop_bits_t` * Hardware flow control mode - :cpp:func:`uart_set_hw_flow_ctrl` selected out of `uart_hw_flowcontrol_t` + * Communication mode - :cpp:func:`uart_set_mode` selected out of :cpp:type:`uart_mode_t` + +Configuration example: :: + + const int uart_num = UART_NUM_1; + uart_config_t uart_config = { + .baud_rate = 115200, + .data_bits = UART_DATA_8_BITS, + .parity = UART_PARITY_DISABLE, + .stop_bits = UART_STOP_BITS_1, + .flow_ctrl = UART_HW_FLOWCTRL_CTS_RTS, + .rx_flow_ctrl_thresh = 122, + }; + // Configure UART parameters + ESP_ERROR_CHECK(uart_param_config(uart_num, &uart_config)); + All the above functions have a ``_get_`` equivalent to retrieve the current setting, e.g. :cpp:func:`uart_get_baudrate`. @@ -51,8 +66,10 @@ Setting Communication Pins In next step, after configuring communication parameters, we are setting physical GPIO pin numbers the other UART will be connected to. This is done in a single step by calling function :cpp:func:`uart_set_pin` and providing it with GPIO numbers, that driver should use for the Tx, Rx, RTS and CTS signals. -Instead of GPIO pin number we can enter a macro :cpp:type:`UART_PIN_NO_CHANGE` and the currently allocated pin will not be changed. The same macro should be entered if certain pin will not be used. +Instead of GPIO pin number we can enter a macro :cpp:type:`UART_PIN_NO_CHANGE` and the currently allocated pin will not be changed. The same macro should be entered if certain pin will not be used. :: + // Set UART pins(TX: IO16, RX: IO17, RTS: IO18, CTS: IO19) + ESP_ERROR_CHECK(uart_set_pin(uart_num, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, 18, 19)); .. _uart-api-driver-installation: @@ -66,6 +83,15 @@ Once configuration of driver is complete, we can install it by calling :cpp:func * the event queue handle and size * flags to allocate an interrupt +Example: :: + + // Setup UART buffered IO with event queue + const int uart_buffer_size = (1024 * 2); + QueueHandle_t uart_queue; + // Install UART driver using an event queue here + ESP_ERROR_CHECK(uart_driver_install(uart_num, uart_buffer_size, \ + uart_buffer_size, 10, &uart_queue, 0)); + If all above steps have been complete, we are ready to connect the other UART device and check the communication. @@ -82,17 +108,35 @@ Transmitting The basic API function to write the data to Tx FIFO buffer is :cpp:func:`uart_tx_chars`. If the buffer contains not sent characters, this function will write what fits into the empty space and exit reporting the number of bytes actually written. -There is a 'companion' function :cpp:func:`uart_wait_tx_done` that waits until all the data are transmitted out and the Tx FIFO is empty. +There is a 'companion' function :cpp:func:`uart_wait_tx_done` that waits until all the data are transmitted out and the Tx FIFO is empty. :: -An easier to work with function is :cpp:func:`uart_write_bytes`. It sets up an intermediate ring buffer and exits after copying the data to this buffer. When there is an empty space in the FIFO, the data are moved from the ring buffer to the FIFO in the background by an ISR. + // Wait for packet to be sent + const int uart_num = UART_NUM_1; + ESP_ERROR_CHECK(uart_wait_tx_done(uart_num, 100)); // wait timeout is 100 RTOS ticks (TickType_t) -There is a similar function as above that adds a serial break signal after sending the data - :cpp:func:`uart_write_bytes_with_break`. The 'serial break signal' means holding TX line low for period longer than one data frame. +An easier to work with function is :cpp:func:`uart_write_bytes`. It sets up an intermediate ring buffer and exits after copying the data to this buffer. When there is an empty space in the FIFO, the data are moved from the ring buffer to the FIFO in the background by an ISR. The code below demonstrates using of this function. :: + + // Write data to UART. + char* test_str = "This is a test string.\n"; + uart_write_bytes(uart_num, (const char*)test_str, strlen(test_str)); + +There is a similar function as above that adds a serial break signal after sending the data - :cpp:func:`uart_write_bytes_with_break`. The 'serial break signal' means holding TX line low for period longer than one data frame :: + + // Write data to UART, end with a break signal. + uart_write_bytes_with_break(uart_num, "test break\n",strlen("test break\n"), 100); Receiving """"""""" -To retrieve the data received by UART and saved in Rx FIFO, use function :cpp:func:`uart_read_bytes`. You can check in advance what is the number of bytes available in Rx FIFO by calling :cpp:func:`uart_get_buffered_data_len`. +To retrieve the data received by UART and saved in Rx FIFO, use function :cpp:func:`uart_read_bytes`. You can check in advance what is the number of bytes available in Rx FIFO by calling :cpp:func:`uart_get_buffered_data_len`. Below is the example of using this function:: + + // Read data from UART. + const int uart_num = UART_NUM_1; + uint8_t data[128]; + int length = 0; + ESP_ERROR_CHECK(uart_get_buffered_data_len(uart_num, (size_t*)&length)); + length = uart_read_bytes(uart_num, data, length, 100); If the data in Rx FIFO is not required and should be discarded, call :cpp:func:`uart_flush`. @@ -103,6 +147,15 @@ Software Flow Control When the hardware flow control is disabled, then use :cpp:func:`uart_set_rts` and :cpp:func:`uart_set_dtr` to manually set the levels of the RTS and DTR signals. +Communication Mode Selection +"""""""""""""""""""""""""""" + +The UART controller supports set of communication modes. The selection of mode can be performed using function :cpp:func:`uart_set_mode`. Once the specific mode is selected the UART driver will handle behavior of external peripheral according to mode. As an example it can control RS485 driver chip over RTS line to allow half-duplex RS485 communication. :: + + // Setup UART in rs485 half duplex mode + ESP_ERROR_CHECK(uart_set_mode(uart_num, UART_MODE_RS485_HALF_DUPLEX)); + + .. _uart-api-using-interrupts: Using Interrupts @@ -118,7 +171,6 @@ The API provides a convenient way to handle specific interrupts discussed above * **Pattern detection** - an interrupt triggered on detecting a 'pattern' of the same character being sent number of times. The functions that allow to configure, enable and disable this interrupt are :cpp:func:`uart_enable_pattern_det_intr` and cpp:func:`uart_disable_pattern_det_intr`. - Macros ^^^^^^ @@ -133,6 +185,96 @@ Deleting Driver If communication is established with :cpp:func:`uart_driver_install` for some specific period of time and then not required, the driver may be removed to free allocated resources by calling :cpp:func:`uart_driver_delete`. +Overview of RS485 specific communication options +------------------------------------------------- + +Note: Here and below the notation UART_REGISTER.UART_OPTION_BIT will be used to describe register options of UART. See the ESP32 Technical Reference Manual for more information. + +- UART_RS485_CONF_REG.UART_RS485_EN = 1, enable RS485 communication mode support. +- UART_RS485_CONF_REG.UART_RS485TX_RX_EN, transmitter's output signal loop back to the receiver's input signal when this bit is set. +- UART_RS485_CONF_REG.UART_RS485RXBY_TX_EN, when bit is set the transmitter should send data when its receiver is busy (remove collisions automatically by hardware). + +The on chip RS485 UART hardware is able to detect signal collisions during transmission of datagram and generate an interrupt UART_RS485_CLASH_INT when it is enabled. The term collision means that during transmission of datagram the received data is different with what has been transmitted out or framing errors exist. Data collisions are usually associated with the presence of other active devices on the bus or due to bus errors. The collision detection feature allows suppressing the collisions when its interrupt is activated and triggered. The UART_RS485_FRM_ERR_INT and UART_RS485_PARITY_ERR_INT interrupts can be used with collision detection feature to control frame errors and parity errors accordingly in RS485 mode. This functionality is supported in the UART driver and can be used with selected UART_MODE_RS485_A mode (see :cpp:func:`uart_set_mode` function). The collision detection option can work with circuit A and circuit C (see below) which allow collision detection. In case of using circuit number A or B, control of RTS pin connected to DE pin of bus driver should be provided manually by application. The function :cpp:func:`uart_get_collision_flag` allows to get collision detection flag from driver. + +The ESP32 UART hardware is not able to control automatically the RTS pin connected to ~RE/DE input of RS485 bus driver to provide half duplex communication. This can be done by UART driver software when UART_MODE_RS485_HALF_DUPLEX mode is selected using :cpp:func:`uart_set_mode` function. The UART driver software automatically asserts the RTS pin (logic 1) once the host writes data to the transmit FIFO, and deasserts RTS pin (logic 0) once the last bit of the data has been transmitted. To use this mode the software would have to disable the hardware flow control function. This mode works with any of used circuit showed below. + + +Overview of RS485 interface connection options +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Note: The example schematics below are prepared for just demonstration of basic aspects of RS485 interface connection for ESP32 and may not contain all required elements. + + +The circuit A: Collision detection circuit +"""""""""""""""""""""""""""""""""""""""""" + +:: + + VCC ---------------+ + | + +-------x-------+ + RXD <------| R | + | B|----------<> B + TXD ------>| D ADM483 | + ESP32 | | RS485 bus side + RTS ------>| DE | + | A|----------<> A + +----| /RE | + | +-------x-------+ + | | + GND GND + +This circuit is preferred because it allows collision detection and is simple enough. The receiver in the line driver is constantly enabled that allows UART to monitor the RS485 bus. Echo suppression is done by the ESP32 chip hardware when the UART_RS485_CONF_REG.UART_RS485TX_RX_EN bit is enabled. + + +The circuit B: manual switching of transmitter/receiver without collision detection +""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" + +:: + + VCC ---------------+ + | + +-------x-------+ + RXD <------| R | + | B|-----------<> B + TXD ------>| D ADM483 | + ESP32 | | RS485 bus side + RTS --+--->| DE | + | | A|-----------<> A + +----| /RE | + +-------x-------+ + | + GND + +This circuit does not allow collision detection. It suppresses the null bytes receive by hardware when UART_RS485_CONF_REG.UART_RS485TX_RX_EN is set. The bit UART_RS485_CONF_REG.UART_RS485RXBY_TX_EN is not applicable in this case. + + +The circuit C: auto switching of transmitter/receiver +""""""""""""""""""""""""""""""""""""""""""""""""""""" + +:: + + VCC1<-------------------+-----------+ +-------------------+----> VCC2 + 10K ____ | | | | + +---|____|--+ +---x-----------x---+ 10K ____ | + | | | +---|____|--+ GND2 + RX <----------+-------------------| RXD | | + 10K ____ | A|---+---------------<> A (+) + +-------|____|------| PV ADM2483 | | ____ 120 + | ____ | | +---|____|---+ RS485 bus side + VCC1<--+--|____|--+------->| DE | | + 10K | | B|---+------------+--<> B (-) + ---+ +-->| /RE | | ____ + 10K | | | | +---|____|---+ + ____ | /-C +---| TXD | 10K | + TX >---|____|--B___|/ NPN | | | | + |\ | +---x-----------x---+ | + | \-E | | | | + | | | | | + GND1 GND1 GND1 GND2 GND2 + +This galvanic isolated circuit does not require RTS pin control by software application or driver because it controls transceiver direction automatically. However it requires removing null bytes during transmission by setting UART_RS485_CONF_REG.UART_RS485RXBY_TX_EN = 1, UART_RS485_CONF_REG.UART_RS485TX_RX_EN = 0. This variant can work in any RS485 UART mode or even in UART_MODE_UART. + Application Examples -------------------- @@ -144,6 +286,7 @@ Transmitting and receiveing with the same UART in two separate FreeRTOS tasks: : Using synchronous I/O multiplexing for UART file descriptors: :example:`peripherals/uart_select`. +Setup of UART driver to communicate over RS485 interface in half-duplex mode: :example:`peripherals/uart_echo_rs485`. This example is similar to uart_echo but provide communication through RS485 interface chip connected to ESP32 pins. API Reference ------------- diff --git a/examples/peripherals/uart_echo_rs485/Makefile b/examples/peripherals/uart_echo_rs485/Makefile new file mode 100644 index 0000000000..8557313046 --- /dev/null +++ b/examples/peripherals/uart_echo_rs485/Makefile @@ -0,0 +1,9 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := echo_rs485 + +include $(IDF_PATH)/make/project.mk + diff --git a/examples/peripherals/uart_echo_rs485/README.md b/examples/peripherals/uart_echo_rs485/README.md new file mode 100644 index 0000000000..a71dc5597f --- /dev/null +++ b/examples/peripherals/uart_echo_rs485/README.md @@ -0,0 +1,39 @@ +# UART RS485 Echo Example + +This is an example which echoes any data it receives on UART2 back to the sender. + +## Setup +This example uses external RS485 interface. The MAX485 line driver can be used for example. + +RS485 example connection circuit schematic: + + VCC ---------------+ +--------------- VCC + | | + +-------x-------+ +-------x-------+ + RXD <------| RO | | RO|-----> RXD + | B|---------------|B | + TXD ------>| DI MAX485 | \ / | MAX485 DI|<----- TXD +ESP32 WROVER KIT | | RS-485 side | | SERIAL ADAPTER + RTS --+--->| DE | / \ | DE|---+ + | | A|---------------|A | | + +----| /RE | | /RE|---+-- RTS + +-------x-------+ +-------x-------+ + | | + --- --- + +1. Connect an external RS485 serial interface to an ESP32 board. + ---------------------------------------------------------------------- + | ESP32 Interface | #define | ESP32 Pin | External RS485 | + | ----------------------|---------------|-----------| Driver Pin | + | Transmit Data (TxD) | ECHO_TEST_TXD | GPIO23 | DI | + | Receive Data (RxD) | ECHO_TEST_RXD | GPIO22 | RO | + | Request To Send (RTS) | ECHO_TEST_RTS | GPIO18 | ~RE/DE | + | Ground | n/a | GND | GND | + ---------------------------------------------------------------------- +2. Connect USB to RS485 adapter to computer and connect its D+, D- output lines with the D+, D- lines of RS485 driver connected to ESP32. +3. Compile and load the example to the ESP32 board +4. Refer to the example and set up a serial terminal program to the same settings as of UART in ESP32 +5. Open the external serial interface in the terminal. +6. By default if no any symbols received the application sends character "." to check transmission side. When typing message and push send button in the terminal you should see the message "RS485 Received: [ your message ], where your message is the message you sent from terminal. +7. Verify if echo indeed comes from ESP32 by disconnecting either 'TxD' or 'RxD' pin. There should be no any "." once TxD pin is disconnected. + diff --git a/examples/peripherals/uart_echo_rs485/main/component.mk b/examples/peripherals/uart_echo_rs485/main/component.mk new file mode 100644 index 0000000000..d31083f65b --- /dev/null +++ b/examples/peripherals/uart_echo_rs485/main/component.mk @@ -0,0 +1,3 @@ +# +# Main Makefile. This is basically the same as a component makefile. +# \ No newline at end of file diff --git a/examples/peripherals/uart_echo_rs485/main/rs485_example.c b/examples/peripherals/uart_echo_rs485/main/rs485_example.c new file mode 100644 index 0000000000..bd815c5d55 --- /dev/null +++ b/examples/peripherals/uart_echo_rs485/main/rs485_example.c @@ -0,0 +1,121 @@ +/* Uart Events Example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_system.h" +#include "nvs_flash.h" +#include "driver/uart.h" +#include "freertos/queue.h" +#include "esp_log.h" +#include "soc/uart_struct.h" + +/** + * This is a example example which echos any data it receives on UART back to the sender. + * + * - port: UART2 + * - rx buffer: on + * - tx buffer: off + * - flow control: off + * + * This example has been tested on a 3 node RS485 Serial Bus + * + */ + +// Note io16 , io17 does not work on wrover module +// because these pins connected to PSRAM +#define ECHO_TEST_TXD (23) //(17) +#define ECHO_TEST_RXD (22) //(16) + +// RTS for RS485 Half-Duplex Mode manages DE/~RE +#define ECHO_TEST_RTS (18) + +// CTS is not used in RS485 Half-Duplex Mode +#define ECHO_TEST_CTS UART_PIN_NO_CHANGE + +#define BUF_SIZE (127) +#define BAUD_RATE (115200) + +// Read packet timeout +#define PACKET_READ_TICS (100 / portTICK_RATE_MS) +#define ECHO_TASK_STACK_SIZE (2048) +#define ECHO_TASK_PRIO (10) +#define ECHO_UART_PORT (UART_NUM_2) + +// An example of echo test with hardware flow control on UART +static void echo_task() +{ + const int uart_num = ECHO_UART_PORT; + uart_config_t uart_config = { + .baud_rate = BAUD_RATE, + .data_bits = UART_DATA_8_BITS, + .parity = UART_PARITY_DISABLE, + .stop_bits = UART_STOP_BITS_1, + .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, + .rx_flow_ctrl_thresh = 122, + }; + + printf("Start RS485 application test.\r\n"); + printf("Configure UART.\r\n"); + + // Configure UART parameters + uart_param_config(uart_num, &uart_config); + + printf("UART set pins.\r\n"); + // Set UART1 pins(TX: IO23, RX: I022, RTS: IO18, CTS: IO19) + uart_set_pin(uart_num, ECHO_TEST_TXD, ECHO_TEST_RXD, ECHO_TEST_RTS, ECHO_TEST_CTS); + + // Install UART driver (we don't need an event queue here) + // In this example we don't even use a buffer for sending data. + uart_driver_install(uart_num, BUF_SIZE * 2, 0, 0, NULL, 0); + + // Set RS485 half duplex mode + uart_set_mode(uart_num, UART_MODE_RS485_HALF_DUPLEX); + + // Allocate buffers for UART + uint8_t* data = (uint8_t*) malloc(BUF_SIZE); + + printf("Start recieve loop.\r\n"); + uart_write_bytes(uart_num, "Start RS485 UART test.\r\n", 24); + + while(1) { + //Read data from UART + int len = uart_read_bytes(uart_num, data, BUF_SIZE, PACKET_READ_TICS); + + //Write data back to UART + if (len > 0) { + uart_write_bytes(uart_num, "\r\n", 2); + char prefix[] = "RS485 Received: ["; + uart_write_bytes(uart_num, prefix, (sizeof(prefix) - 1)); + + printf("Received [ "); + for (int i = 0; i < len; i++) { + printf("0x%.2X ", (uint8_t)data[i]); + uart_write_bytes(uart_num, (const char*)&data[i], 1); + // Add a Newline character if you get a return charater from paste (Paste tests multibyte receipt/buffer) + if (data[i] == '\r') { + uart_write_bytes(uart_num, "\n", 1); + } + } + printf("] \n"); + uart_write_bytes(uart_num, "]\r\n", 3); + } else { + // Echo a "." to show we are alive while we wait for input + uart_write_bytes(uart_num, ".", 1); + } + } +} + +void app_main() +{ + //A uart read/write example without event queue; + xTaskCreate(echo_task, "uart_echo_task", ECHO_TASK_STACK_SIZE, NULL, ECHO_TASK_PRIO, NULL); +}