driver: add rs485 half duplex interface support to uart driver (update after review)

An existing UART driver does not support RS485 half duplex mode.
This task adds this functionality to ESP_IDF UART driver.
driver/uart.c/h: updated to add support of RS485 half duplex mode
examples/peripherals/uart_echo_rs485/main/rs485_example.c: added test example
components/driver/test/test_uart.c: added test of RS485 half duplex mode
docs/en/api-reference/peripherals/uart.rst: updated documentation
test_uart.c: suppress GCC warnings about discarded const qualifiers
uart.rst: remove sphinx warning - "Duplicate explicit target name"
simple change in uart.h file
update (test_uart.c) after rebase from master
update uart.rst, uart.c, rs485_example.c
Update example description in file Readme.md
update uart.c/h, uart.rst, test_uart.c according to review results
update uart.h (uart_set_rx_timeout() description
test_uart.c remove ignore tag
uart.c/h: fix param errors
test_uart.c: Remove GCC warning supress
uart.rst: fix the notes
rs485_example.c: fix output

The tests are completed using RS485 adapters hardware connected to two ESP32 WROVER KITs.

TW#13812
Closes https://github.com/espressif/esp-idf/pull/667
Closes https://github.com/espressif/esp-idf/pull/1006
This commit is contained in:
Alex Lisitsyn 2018-06-27 16:48:14 +02:00
parent 0ec81181b4
commit ef5dc73f6f
5 changed files with 45 additions and 43 deletions

View file

@ -50,11 +50,11 @@ extern "C" {
* @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_UART = 0x00, /*!< mode: regular UART mode*/
UART_MODE_RS485_HALF_DUPLEX = 0x01, /*!< mode: half duplex RS485 UART mode control by RTS pin */
UART_MODE_IRDA = 0x02, /*!< mode: IRDA UART mode*/
UART_MODE_RS485_COLLISION_DETECT = 0x03, /*!< mode: RS485 collision detection UART mode (used for test purposes)*/
UART_MODE_RS485_APP_CTRL = 0x04, /*!< mode: application control RS485 UART mode (used for test purposes)*/
} uart_mode_t;
/**
@ -84,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;
@ -756,8 +756,8 @@ esp_err_t uart_pattern_queue_reset(uart_port_t uart_num, int queue_length);
* @param mode UART UART mode to set
*
* @return
* - ESP_OK Success
* - ESP_FAIL Parameter error
* - ESP_OK Success
* - ESP_ERR_INVALID_ARG Parameter error
*/
esp_err_t uart_set_mode(uart_port_t uart_num, uart_mode_t mode);
@ -771,8 +771,9 @@ esp_err_t uart_set_mode(uart_port_t uart_num, uart_mode_t mode);
* the TOUT feature is disabled.
*
* @return
* - ESP_OK Success
* - ESP_FAIL Parameter error
* - ESP_OK Success
* - ESP_ERR_INVALID_ARG Parameter error
* - ESP_ERR_INVALID_STATE Driver is not installed
*/
esp_err_t uart_set_rx_timeout(uart_port_t uart_num, const uint8_t tout_thresh);
@ -786,8 +787,8 @@ esp_err_t uart_set_rx_timeout(uart_port_t uart_num, const uint8_t tout_thresh);
* @param collision_flag Pointer to variable of type bool to return collision flag.
*
* @return
* - ESP_OK No collision
* - ESP_FAIL Parameter error
* - ESP_OK Success
* - ESP_ERR_INVALID_ARG Parameter error
*/
esp_err_t uart_get_collision_flag(uart_port_t uart_num, bool* collision_flag);

View file

@ -313,9 +313,5 @@ static void rs485_master()
* 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]", rs485_master, rs485_slave);
#pragma GCC diagnostic pop // require GCC

View file

@ -1441,7 +1441,7 @@ 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)
if ((mode == UART_MODE_RS485_COLLISION_DETECT) || (mode == UART_MODE_RS485_APP_CTRL)
|| (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);
@ -1455,7 +1455,7 @@ esp_err_t uart_set_mode(uart_port_t uart_num, uart_mode_t mode)
switch (mode) {
case UART_MODE_UART:
break;
case UART_MODE_RS485_A:
case UART_MODE_RS485_COLLISION_DETECT:
// This mode allows read while transmitting that allows collision detection
p_uart_obj[uart_num]->coll_det_flg = false;
// Transmitters output signal loop back to the receivers input signal
@ -1470,7 +1470,7 @@ esp_err_t uart_set_mode(uart_port_t uart_num, uart_mode_t mode)
| UART_RS485_FRM_ERR_INT_ENA
| UART_RS485_PARITY_ERR_INT_ENA);
break;
case UART_MODE_RS485_B:
case UART_MODE_RS485_APP_CTRL:
// Application software control, remove echo
UART[uart_num]->rs485_conf.rx_busy_tx_en = 1;
UART[uart_num]->rs485_conf.en = 1;
@ -1488,7 +1488,7 @@ esp_err_t uart_set_mode(uart_port_t uart_num, uart_mode_t mode)
UART[uart_num]->conf0.irda_en = 1;
break;
default:
UART_CHECK(1, "unsupported uart mode", ESP_FAIL);
UART_CHECK(1, "unsupported uart mode", ESP_ERR_INVALID_ARG);
break;
}
p_uart_obj[uart_num]->uart_mode = mode;
@ -1498,8 +1498,8 @@ esp_err_t uart_set_mode(uart_port_t uart_num, uart_mode_t mode)
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_CHECK((uart_num < UART_NUM_MAX), "uart_num error", ESP_ERR_INVALID_ARG);
UART_CHECK((tout_thresh < 127), "tout_thresh max value is 126", ESP_ERR_INVALID_ARG);
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
@ -1515,10 +1515,11 @@ esp_err_t uart_set_rx_timeout(uart_port_t uart_num, const uint8_t tout_thresh)
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_num < UART_NUM_MAX), "uart_num error", ESP_ERR_INVALID_ARG);
UART_CHECK((collision_flag != NULL), "wrong parameter pointer", ESP_ERR_INVALID_ARG);
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);
|| UART_IS_MODE_SET(uart_num, UART_MODE_RS485_COLLISION_DETECT)),
"wrong mode", ESP_ERR_INVALID_ARG);
*collision_flag = p_uart_obj[uart_num]->coll_det_flg;
return ESP_OK;
}

View file

@ -43,7 +43,7 @@ The alternate way is to configure specific parameters individually by calling de
Configuration example: ::
const int uart_num = UART_NUM_1;
const int uart_num = UART_NUM_2;
uart_config_t uart_config = {
.baud_rate = 115200,
.data_bits = UART_DATA_8_BITS,
@ -68,8 +68,8 @@ In next step, after configuring communication parameters, we are setting physica
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));
// Set UART pins(TX: IO16 (UART2 default), RX: IO17 (UART2 default), RTS: IO18, CTS: IO19)
ESP_ERROR_CHECK(uart_set_pin(UART_NUM_2, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, 18, 19));
.. _uart-api-driver-installation:
@ -89,7 +89,7 @@ Example: ::
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, \
ESP_ERROR_CHECK(uart_driver_install(UART_NUM_2, 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.
@ -111,7 +111,7 @@ The basic API function to write the data to Tx FIFO buffer is :cpp:func:`uart_tx
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. ::
// Wait for packet to be sent
const int uart_num = UART_NUM_1;
const int uart_num = UART_NUM_2;
ESP_ERROR_CHECK(uart_wait_tx_done(uart_num, 100)); // wait timeout is 100 RTOS ticks (TickType_t)
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. ::
@ -132,7 +132,7 @@ 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`. Below is the example of using this function::
// Read data from UART.
const int uart_num = UART_NUM_1;
const int uart_num = UART_NUM_2;
uint8_t data[128];
int length = 0;
ESP_ERROR_CHECK(uart_get_buffered_data_len(uart_num, (size_t*)&length));
@ -188,7 +188,7 @@ If communication is established with :cpp:func:`uart_driver_install` for some sp
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.
.. 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.
@ -202,8 +202,7 @@ The ESP32 UART hardware is not able to control automatically the RTS pin connect
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.
.. 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 Analog Devices ADM483 & ADM2483 are examples of common RS485 transceivers and other similar transceivers can also be used.
The circuit A: Collision detection circuit
""""""""""""""""""""""""""""""""""""""""""

View file

@ -30,10 +30,10 @@
*
*/
// Note io16 , io17 does not work on wrover module
// Note: UART2 default pins IO16, IO17 do not work on ESP32-WROVER module
// because these pins connected to PSRAM
#define ECHO_TEST_TXD (23) //(17)
#define ECHO_TEST_RXD (22) //(16)
#define ECHO_TEST_TXD (23)
#define ECHO_TEST_RXD (22)
// RTS for RS485 Half-Duplex Mode manages DE/~RE
#define ECHO_TEST_RTS (18)
@ -50,6 +50,8 @@
#define ECHO_TASK_PRIO (10)
#define ECHO_UART_PORT (UART_NUM_2)
static const char *TAG = "RS485_ECHO_APP";
// An example of echo test with hardware flow control on UART
static void echo_task()
{
@ -63,13 +65,15 @@ static void echo_task()
.rx_flow_ctrl_thresh = 122,
};
printf("Start RS485 application test.\r\n");
printf("Configure UART.\r\n");
// Set UART log level
esp_log_level_set(TAG, ESP_LOG_INFO);
ESP_LOGI(TAG, "Start RS485 application test and configure UART.");
// Configure UART parameters
uart_param_config(uart_num, &uart_config);
printf("UART set pins.\r\n");
ESP_LOGI(TAG, "UART set pins, mode and install driver.");
// 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);
@ -83,7 +87,7 @@ static void echo_task()
// Allocate buffers for UART
uint8_t* data = (uint8_t*) malloc(BUF_SIZE);
printf("Start recieve loop.\r\n");
ESP_LOGI(TAG, "UART start recieve loop.\r\n");
uart_write_bytes(uart_num, "Start RS485 UART test.\r\n", 24);
while(1) {
@ -96,7 +100,8 @@ static void echo_task()
char prefix[] = "RS485 Received: [";
uart_write_bytes(uart_num, prefix, (sizeof(prefix) - 1));
printf("Received [ ");
ESP_LOGI(TAG, "Received %u bytes:", len);
printf("[ ");
for (int i = 0; i < len; i++) {
printf("0x%.2X ", (uint8_t)data[i]);
uart_write_bytes(uart_num, (const char*)&data[i], 1);