How to use UART in ESP32 || Simple TX and RX

This is First tutorial in the ESP32 Series, and today we will see how to use UART to send and receive data. I will be using the  ESP32 WROOM Devkit for these tutorials along with the Espressif IDE (ESP-IDF).

I hope you guys know what UART is and how it works. If you don’t I would recommend you to first study the basics of these protocols as this website generally focuses on how to implement these protocols in microcontrollers.

ESP32 has three UART interfaces, i.e., UART0, UART1, and UART2, which provide asynchronous communication (RS232 and RS485) and IrDA support, communicating at a speed of up to 5 Mbps. UART provides hardware management of the CTS and RTS signals and software flow control (XON and XOFF). All of the interfaces can be accessed by the DMA controller or directly by the CPU.

This tutorial is going to be the PART 1 and today we will see the simple TX and RX methods. I am going to use the computer to communicate to the board using the UART. In the next PART of the UART we will see how to use the received buffer or data to control the LED connected to the ESP32.

I am going to use the UART2 and FT232 USB to UART module to communicate with the computer.

Let’s start with the IDE and we will create a new project.

Setting up the Project

We will first create a project using the examples provided in the IDE by default. Later we will modify it according to our need.

  • Fist we will create a new Espressif IDF project
  • Then give some name to this project and click next

  • check “Create project using one of the templates”
  • goto Peripherals -> uart and select the “uart_async_rxtxtasks”
  • The name of the project will be modified after this, so make sure you change it back
  • Click finish to generate the project

The example we chose above is simplest example to start with uart. The ESP32 also supports interrupt and DMA for uart, but in this tutorial we will do the simplest method of sending and receiving data, i.e using the blocking mode.

The code will generate and you can find the main.c file in the main folder. The pregenerated code will also work pretty well, but here I will modify it a little and explain you the entire code.




Connection

ESP32 have 3 uarts, UART0, 1 and 2. I am going to use the UART2 as the pins for UART2 are defined clearly on the board. This is shown in the picture below

As shown in the image above, the RX2 and TX2 pins, GPIO16 and GPIO17 respectively, represents the UART2 pins. I am going to connect them to the FT232 USB to UART device. The pins must be cross connected, Rx to TX and TX to RX. This is shown in the picture below.

  • We have the TX2 (GPIO_17) connected to the RX pin of the FTDI
  • The RX2 (GPIO_16) is connected to the TX pin of the FTDI
  • As I mentioned before, for the UART to communicate, the devices must be cross connected.


The CODE

Defines

static const int RX_BUF_SIZE = 128;

#define TXD_PIN (GPIO_NUM_17)
#define RXD_PIN (GPIO_NUM_16)

#define UART_NUM UART_NUM_2
  • First the Rx buffer size is defined as 128 Bytes
  • I have modified the Tx and Rx Pins as per my setup
  • As I mentioned before, I am going to use the UART2 in this tutorial, so I have defined the UART_NUM as UART_NUM_2
    • Now we can just simply use the UART_NUM wherever the uart number is needed to be provided.

Initialize the UART

void init(void) 
{
    const 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,
        .source_clk = UART_SCLK_DEFAULT,
    };

    // We won't use a buffer for sending data.
    uart_driver_install(UART_NUM, RX_BUF_SIZE * 2, 0, 0, NULL, 0);
    uart_param_config(UART_NUM, &uart_config);
    uart_set_pin(UART_NUM, TXD_PIN, RXD_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
}
uart_driver_install(UART_NUM, RX_BUF_SIZE * 2, 0, 0, NULL, 0);
  • Here First of all we will install the UART Driver. The uart_driver_install function takes the following argument
    • @uart_port_t, The UART Instance you are using, UART2 in this example
    • @int rx_buffer_size, The size of the RX Ring Buffer. It should be greater than minimum length, which is defined in the soc_caps.h file #define SOC_UART_FIFO_LEN (128). Here the RX_BUF_SIZE is multiplied by 2, so the Ring Buffer Size will be 256 Bytes.
    • @int tx_buffer_size, The size of the TX Ring Buffer. We are not using the TX FIFO, so the size is set to 0 in this example.
    • @int queue_size, The UART Queue size, 0 in this case since we are not using any queue in this tutorial
    • @QueueHandle_t *uart_queue, The UART queue, NULL in this example since we are not using the queue
    • @int intr_alloc_flags, The interrupt FLAG, 0 in this example since we are not using interrupt
uart_param_config(UART_NUM, &uart_config);
  • Then we will configure the UART parameters. The configuration used is 8-N-1 with the baud rate of 115200. Hardware flow control is disabled and the UART source clock is set to DEFAULT, which is actually the APB clock.
uart_set_pin(UART_NUM, TXD_PIN, RXD_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
  • Finally we will set the pins for the UART. The uart_set_pin function takes the following parameters
    • @uart_port_t, The UART_NUM you are using, UART2 in this example
    • @int tx_io_num, The TX Pin number, GPIO_NUM_17 in this example
    • @int rx_io_num, The RX Pin number, GPIO_NUM_16 in this example
    • @int rts_io_num and @int cts_io_num, The RTS and CTS Pin numbers. We are not using hardware flow control so these parameters are set to UART_PIN_NO_CHANGE (-1).

The TX Task

static void tx_task(void *arg)
{
    int num = 0;
    uint8_t* data = (uint8_t*) malloc(30);
    while (1) {
    	int len = sprintf ((char*)data, "Hello world %d\n", num++);
        uart_write_bytes(UART_NUM, data, len);
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
    free (data);
}

The TX task will send the data via the UART every 1 second.

  • First we will allocate the 30 bytes to the data buffer, where we will store the data to be transmitted.
  • The num variable will be updated every time the data is transmitted, so that we have a new data each time.
  • Next we will store the data into the data buffer. Here we will increment the value of the num variable so that the data will be different each time.
  • uart_write_bytes will be used to write the data. The function takes the following parameters
    • @uart_port_t, The UART Instance you are using, UART2 in this example.
    • @const void* src, The pointer to the source address, data in this example.
    • @size_t size, The length of the data to be transmitted, len in this example.
  • At last we will free the allocated memory to the data buffer.


The RX Task

static void rx_task(void *arg)
{
    static const char *RX_TASK_TAG = "RX_TASK";
    esp_log_level_set(RX_TASK_TAG, ESP_LOG_INFO);
    uint8_t* data = (uint8_t*) malloc(RX_BUF_SIZE+1);
    while (1) {
        const int rxBytes = uart_read_bytes(UART_NUM, data, RX_BUF_SIZE, 200 / portTICK_RATE_MS);
        if (rxBytes > 0) {
            data[rxBytes] = 0;
            ESP_LOGI(RX_TASK_TAG, "Read %d bytes: '%s'", rxBytes, data);
        }
    }
    free(data);
}
  • RX Task will be used to receive the data from the UART and then log the received data to the terminal.
  • Here we will first create the RX_TASK_TAG, which will be used to log the information data to the terminal.
  • Then allocate the memory to the data buffer, where the received data will be stored. The allocated memory is 1 byte higher than the RX_BUF_SIZE as we will add a ‘0’ at the end of this buffer, so to mark the termination of the received data.
  • Receive the data using the function uart_read_bytes. It takes the following parameters.
    • @uart_port_t, The UART Instance you are using, UART2 in this example.
    • @void* buf, The pointer to the buffer where the data will be saved, data in this example.
    • @uint32_t length, The length of the buffer, 128 bytes in this example.
    • @TickType_t ticks_to_wait, The time to wait for the data to arrive, 200 ms in this example.
    • @ESP_LOGI, is used to log the received data to the terminal.

If the required amount of data (RX_BUF_SIZE) does not arrive in 200ms, the timeout will occur. Whatever data was received in this 200ms will be stored in the data buffer. This data is then logged to the terminal.

At the end, we free the memory allocated to the data buffer.


The main function

void app_main(void)
{
    init();
    xTaskCreate(rx_task, "uart_rx_task", 1024*2, NULL, configMAX_PRIORITIES-1, NULL);
    xTaskCreate(tx_task, "uart_tx_task", 1024*2, NULL, configMAX_PRIORITIES-2, NULL);
}
  • In the main function, we will first initialize the UART
  • Then we are creating 2 tasks, rx_task and tx_task
    • The stack size for both the tasks is set to 2 Kilobytes
    • The priority of the receiver task is highest, and that of the transmitter task is 1 below the receiver task
  • After the Tasks has been created, the transmitter task will start sending the data to the UART and Receiver task will wait for the data to be received.


Result

Below is the output of the serial Terminal and the ESP32 Terminal

As you can see the Serial terminal is receiving the data sent by the ESP32 every 1 seconds. The data is updated with the new number each time it is sent.
When we send some data from the terminal to the ESP, it gets logged into the ESP terminal


Check out the Video Below









Info

You can help with the development by DONATING
To download the code, click DOWNLOAD button and view the Ad. The project will download after the Ad is finished.

5 Comments. Leave new

  • daksh kakadia
    May 15, 2024 2:12 PM

    Can UART be implement using intrupt method like other microcontrollers? If yes can you guide me please?

    Reply
  • i am trying to communicate with ec200 ucn gsm module, and sending AT commands via esp32 uart 0, getting response back but , not able to read proper reading of gsm module response because it takes time to respond back, so i am not able to when to send next at command , I simply wanted to send data to server via gsm moudle

    Reply
    • if you can manage socket command library(AT commands), I think you can use esp_modem. There is a few example like modem tcp client, simple cmux client etc. I have tested the mqtt connection over tcp/ip protocol it works!

      Reply
  • aaaaaaaaaaaa
    March 30, 2023 1:51 PM

    where is the free of Txdata ?

    Reply
  • Hi, Thanks for this. There is an interesting observation which I like to share:

    static const int RX_BUF_SIZE = 1024;
    

    My application had only 10bytes of rx data so I redefined RX_BUF_SIZE to 10. Apparently ESP32 driver install API doesnot like this. RX_BUF_SIZE must be always >128 bytes else it throws error.

    This might help someone!

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.

keyboard_arrow_up

Adblocker detected! Please consider reading this notice.

We've detected that you are using AdBlock Plus or some other adblocking software which is preventing the page from fully loading.

We don't have any banner, Flash, animation, obnoxious sound, or popup ad. We do not implement these annoying types of ads!

We need money to operate the site, and almost all of it comes from our online advertising.

Please add controllerstech.com to your ad blocking whitelist or disable your adblocking software.

×