ESP32 IDF – Serial Communication Tutorial

Hi all… Now we will see ESP32 IDF Serial Communication Tutorial. ESP32 has three Serial ports. UART0, UART1, and UART2.

ESP32 IDF Serial Communication Tutorial

Suggest to Read

First, we will take the example Program from the example directory. Before coding, I will explain the API and Structures which we are using in our code.

ESP32 IDF Serial Communication

APIs Used

  • uart_param_config()
  • uart_set_pin()
  • uart_driver_install()
  • uart_read_bytes()
  • uart_write_bytes()

uart_param_config()

This API is used to set the UART configuration like baud rate, stop bits, etc.  This API contains Two arguments. Have a look at this API below.

esp_err_t uart_param_config(uart_port_t uart_num, const uart_config_t *uart_config)

Return

  • ESP_OK Success
  • ESP_FAIL Parameter error

Parameters

  • uart_num: UART_NUM_0, UART_NUM_1 or UART_NUM_2
  • uart_config: UART parameter settings

Here Second argument is a structure’s address. That structure is used to set the configuration settings. 

structuart_config_t

Members Description
int baud_rate UART baudrate
uart_word_length_t data_bits UART byte size
uart_parity_t parity UART parity mode
uart_stop_bits_t stop_bits UART stop bits
uart_hw_flowcontrol_t flow_ctrl UART HW flow control mode(cts/rts)
uint8_t rx_flow_ctrl_thresh UART HW RTS threshold

uart_set_pin()

This function is used to configure the UART pin in ESP32. Using this function we can select any pin as a TX, RX, CTS, or RTS. This function contains five arguments. The internal signal can be output to multiple GPIO pads. Only one GPIO pad can connect with the input signal.

esp_err_t uart_set_pin(uart_port_t uart_num, int tx_io_num, int rx_io_num, int rts_io_num, int cts_io_num)

Return

  • ESP_OK Success
  • ESP_FAIL Parameter error

Parameters

  • uart_num: UART_NUM_0, UART_NUM_1 or UART_NUM_2
  • tx_io_num: UART TX pin GPIO number, if set to UART_PIN_NO_CHANGE, use the current pin.
  • rx_io_num: UART RX pin GPIO number if set to UART_PIN_NO_CHANGE, use the current pin.
  • rts_io_num: UART RTS pin GPIO number if set to UART_PIN_NO_CHANGE, use the current pin.
  • cts_io_num: UART CTS pin GPIO number, if set to UART_PIN_NO_CHANGE, use the current pin.

uart_driver_install()

This API is used to install the uart_driver. This API contains six arguments. UART ISR handler will be attached to the same CPU core that this function is running on. Users should know which CPU is running and then pick an INUM that is not used by the system. We can find the information on INUM and interrupt level in soc.h.

esp_err_t uart_driver_install(uart_port_t uart_num, int rx_buffer_size, int tx_buffer_size, int queue_size, int uart_intr_num, void *uart_queue) 

Return

  • ESP_OK Success
  • ESP_FAIL Parameter error

Parameters

  • uart_num: UART_NUM_0, UART_NUM_1 or UART_NUM_2
  • rx_buffer_size: UART RX ring buffer size
  • tx_buffer_size: UART TX ring buffer size. If set to zero, the driver will not use the TX buffer, TX function will block the task until all data have been sent out.
  • queue_size: UART event queue size/depth.
  • uart_intr_num: UART interrupt number, check the info in soc.h, and please refer to core-isa.h for more details
  • uart_queue: UART event queue handle, if set NULL, the driver will not use an event queue.

uart_read_bytes()

This API is used to read the data from the UART buffer. This contains four arguments.

int uart_read_bytes(uart_port_t uart_num, uint8_t *buf, uint32_t length, TickType_t ticks_to_wait)

Return

  • (-1) Error
  • Others return char data from uart fifo.

Parameters

  • uart_num: UART_NUM_0, UART_NUM_1 or UART_NUM_2
  • buf: pointer to the buffer.
  • length: data length
  • ticks_to_wait: timeout, count in RTOS ticks

uart_write_bytes()

This API is used to write the data into the UART port from a given buffer and length. This has three arguments. 

int uart_write_bytes(uart_port_t uart_num, const char *src, size_t size)

If parameter tx_buffer_size is set to zero: This function will not return until all the data have been sent out or at least pushed into TX FIFO.

Otherwise, if tx_buffer_size > 0, this function will return after copying all the data to the tx ring buffer, then, UART ISR will move data from the ring buffer to TX FIFO gradually.

Return

  • (-1) Parameter error
  • OTHERS(>=0) The number of data that were pushed to the TX FIFO

Parameters

  • uart_num: UART_NUM_0, UART_NUM_1 or UART_NUM_2
  • src: data buffer address
  • size: data length to send

Example Code 1

This is an example that echos any data it receives on UART1 back to the sender, with hardware flow control turned off. It does not use the UART driver event queue. 

  • port: UART1
  • RX buffer: on
  • TXbuffer: off
  • flow control: off
  • event queue: off
  • pin assignment: txd(io4), rxd(io5)
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#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"

#define ECHO_TEST_TXD  (4)
#define ECHO_TEST_RXD  (5)

#define BUF_SIZE (1024)

//an example of echo test without hardware flow control on UART1
static void echo_task()
{
    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_DISABLE,    
        .rx_flow_ctrl_thresh = 122,
    };
    //Configure UART1 parameters
    uart_param_config(uart_num, &uart_config);

    //Set UART1 pins(TX: IO4, RX: I05)
    uart_set_pin(uart_num, ECHO_TEST_TXD, ECHO_TEST_RXD, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
    
    //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);

    uint8_t* data = (uint8_t*) malloc(BUF_SIZE);
    while(1) {
        //Read data from UART
        int len = uart_read_bytes(uart_num, data, BUF_SIZE, 20 / portTICK_RATE_MS);
        //Write data back to UART
        uart_write_bytes(uart_num, (const char*) data, len);
    }
}

void app_main()
{
    //A uart read/write example without event queue;
    xTaskCreate(echo_task, "uart_echo_task", 1024, NULL, 10, NULL);
}

Output

ESP32 IDF Serial Communication

Example Code 2

In this example, I’m going to use two UARTs. Whatever I’m typing in UART 0 it sends to UART1 and UART0.

UART 0:

  • port: UART0
  • rx buffer: on
  • tx buffer: off
  • flow control: off
  • event queue: off
  • pin assignment: txd(default), rxd(default)

UART 1:

  • port: UART1
  • rx buffer: on
  • tx buffer: off
  • flow control: off
  • event queue: off
  • pin assignment: txd(io4), rxd(io5)
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#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"

#define ECHO_TEST_TXD  (4)
#define ECHO_TEST_RXD  (5)

#define BUF_SIZE (1024)


//an example of echo test with hardware flow control on UART1
static void echo_task()
{
    const int uart_num0 = UART_NUM_0;
    uart_config_t uart_config0 = {
        .baud_rate = 115200,
        .data_bits = UART_DATA_8_BITS,
        .parity = UART_PARITY_DISABLE,
        .stop_bits = UART_STOP_BITS_1,
        .flow_ctrl = UART_HW_FLOWCTRL_DISABLE,    //UART_HW_FLOWCTRL_CTS_RTS,
        .rx_flow_ctrl_thresh = 122,
    };

    const int uart_num1 = UART_NUM_1;
    uart_config_t uart_config1 = {
        .baud_rate = 115200,
        .data_bits = UART_DATA_8_BITS,
        .parity = UART_PARITY_DISABLE,
        .stop_bits = UART_STOP_BITS_1,
        .flow_ctrl = UART_HW_FLOWCTRL_DISABLE,    //UART_HW_FLOWCTRL_CTS_RTS,
        .rx_flow_ctrl_thresh = 122,
    };
    //Configure UART1 parameters
    uart_param_config(uart_num0, &uart_config0);
    uart_param_config(uart_num1, &uart_config1);

    uart_set_pin(uart_num0, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
    uart_set_pin(uart_num1, ECHO_TEST_TXD, ECHO_TEST_RXD, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
    
    //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_num0, BUF_SIZE * 2, 0, 0, NULL, 0);
    uart_driver_install(uart_num1, BUF_SIZE * 2, 0, 0, NULL, 0);

    uint8_t* data = (uint8_t*) malloc(BUF_SIZE);
    while(1) {
        //Read data from UART
        int len = uart_read_bytes(uart_num0, data, BUF_SIZE, 20 / portTICK_RATE_MS);
        //Write data back to UART
        uart_write_bytes(uart_num1, (const char*) data, len);
        uart_write_bytes(uart_num0, (const char*) data, len);
    }
}

void app_main()
{
    //A uart read/write example without event queue;
    xTaskCreate(echo_task, "uart_echo_task", 1024, NULL, 10, NULL);
}

Output

Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

3 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Table of Contents