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 toUART_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