Universal Asynchronous Receiver-Transmitter (UART) is one of the most widely used serial communication protocols in embedded systems. It enables data exchange between microcontrollers, computers, sensors, GPS modules, Bluetooth modules, modems, and many other peripherals using asynchronous serial communication.
UART is a simple, hardware-supported serial communication method widely used in embedded systems because it needs only two main signal lines and does not require a clock line.
| IDLE | START | DATA (5-9 bits) | PARITY (optional) | STOP (1/2 bits) |
Baud rate defines the transmission speed in bits per second.
Both transmitter and receiver must use the same baud rate, data bits, parity, and stop bits. Otherwise, communication errors will occur.
The examples below cover all common combinations:
#include "driver/uart.h"
#include "driver/gpio.h"
#include "esp_err.h"
#include <string.h>
#define UART_PORT_NUM UART_NUM_1
#define UART_TX_PIN 17
#define UART_RX_PIN 16
#define UART_BAUD_RATE 115200
#define UART_BUF_SIZE 1024
#define UART_TIMEOUT_MS 1000
static esp_err_t uart_master_init(void)
{
const uart_config_t uart_config = {
.baud_rate = UART_BAUD_RATE,
.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,
};
ESP_ERROR_CHECK(uart_driver_install(UART_PORT_NUM, UART_BUF_SIZE, UART_BUF_SIZE, 0, NULL, 0));
ESP_ERROR_CHECK(uart_param_config(UART_PORT_NUM, &uart_config));
ESP_ERROR_CHECK(uart_set_pin(UART_PORT_NUM, UART_TX_PIN, UART_RX_PIN,
UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE));
return ESP_OK;
}
esp_err_t uart_write_byte(uint8_t data)
{
int written = uart_write_bytes(UART_PORT_NUM, (const char *)&data, 1);
return (written == 1) ? ESP_OK : ESP_FAIL;
}
esp_err_t uart_read_byte(uint8_t *data)
{
if (data == NULL) return ESP_ERR_INVALID_ARG;
int len = uart_read_bytes(UART_PORT_NUM, data, 1, pdMS_TO_TICKS(UART_TIMEOUT_MS));
return (len == 1) ? ESP_OK : ESP_ERR_TIMEOUT;
}
esp_err_t uart_write_string(const char *str)
{
if (str == NULL) return ESP_ERR_INVALID_ARG;
int len = strlen(str);
int written = uart_write_bytes(UART_PORT_NUM, str, len);
return (written == len) ? ESP_OK : ESP_FAIL;
}
esp_err_t uart_read_buffer(uint8_t *buf, size_t len, size_t *received_len)
{
if (buf == NULL || len == 0) return ESP_ERR_INVALID_ARG;
int rx_len = uart_read_bytes(UART_PORT_NUM, buf, len, pdMS_TO_TICKS(UART_TIMEOUT_MS));
if (received_len) {
*received_len = (rx_len > 0) ? (size_t)rx_len : 0;
}
return (rx_len > 0) ? ESP_OK : ESP_ERR_TIMEOUT;
}
esp_err_t uart_write_buffer(const uint8_t *buf, size_t len)
{
if (buf == NULL || len == 0) return ESP_ERR_INVALID_ARG;
int written = uart_write_bytes(UART_PORT_NUM, (const char *)buf, len);
return (written == (int)len) ? ESP_OK : ESP_FAIL;
}
esp_err_t uart_read_line(char *buf, size_t max_len)
{
if (buf == NULL || max_len == 0) return ESP_ERR_INVALID_ARG;
size_t index = 0;
uint8_t ch;
while (index < (max_len - 1)) {
int len = uart_read_bytes(UART_PORT_NUM, &ch, 1, pdMS_TO_TICKS(UART_TIMEOUT_MS));
if (len <= 0) {
break;
}
buf[index++] = (char)ch;
if (ch == '\n') {
break;
}
}
buf[index] = '\0';
return (index > 0) ? ESP_OK : ESP_ERR_TIMEOUT;
}
esp_err_t uart_echo_task_once(void)
{
uint8_t data[128];
int len = uart_read_bytes(UART_PORT_NUM, data, sizeof(data), pdMS_TO_TICKS(UART_TIMEOUT_MS));
if (len > 0) {
int written = uart_write_bytes(UART_PORT_NUM, (const char *)data, len);
return (written == len) ? ESP_OK : ESP_FAIL;
}
return ESP_ERR_TIMEOUT;
}
esp_err_t uart_send_packet(const uint8_t *payload, size_t len)
{
if (payload == NULL || len == 0) return ESP_ERR_INVALID_ARG;
uint8_t header = 0xAA;
uint8_t footer = 0x55;
if (uart_write_bytes(UART_PORT_NUM, (const char *)&header, 1) != 1) return ESP_FAIL;
if (uart_write_bytes(UART_PORT_NUM, (const char *)payload, len) != (int)len) return ESP_FAIL;
if (uart_write_bytes(UART_PORT_NUM, (const char *)&footer, 1) != 1) return ESP_FAIL;
return ESP_OK;
}
esp_err_t uart_receive_packet(uint8_t *buf, size_t len)
{
if (buf == NULL || len == 0) return ESP_ERR_INVALID_ARG;
int rx_len = uart_read_bytes(UART_PORT_NUM, buf, len, pdMS_TO_TICKS(UART_TIMEOUT_MS));
return (rx_len == (int)len) ? ESP_OK : ESP_ERR_TIMEOUT;
}
esp_err_t uart_clear_rx_buffer(void)
{
return uart_flush_input(UART_PORT_NUM);
}
void app_main(void)
{
ESP_ERROR_CHECK(uart_master_init());
uint8_t rx_byte;
uint8_t tx_buffer[] = {0x11, 0x22, 0x33, 0x44};
char line[64];
uart_write_byte(0x55);
uart_read_byte(&rx_byte);
uart_write_string("Hello UART\r\n");
uart_write_buffer(tx_buffer, sizeof(tx_buffer));
uart_read_line(line, sizeof(line));
}
STM32 HAL provides simple and efficient APIs for UART transmit and receive operations.
#include "stm32f1xx_hal.h" extern UART_HandleTypeDef huart1;
HAL_StatusTypeDef stm32_uart_write_byte(uint8_t data)
{
return HAL_UART_Transmit(&huart1, &data, 1, 100);
}
HAL_StatusTypeDef stm32_uart_read_byte(uint8_t *data)
{
return HAL_UART_Receive(&huart1, data, 1, 100);
}
HAL_StatusTypeDef stm32_uart_write_string(const char *str)
{
if (str == NULL) return HAL_ERROR;
return HAL_UART_Transmit(&huart1, (uint8_t *)str, strlen(str), 100);
}
HAL_StatusTypeDef stm32_uart_read_buffer(uint8_t *buf, uint16_t len)
{
return HAL_UART_Receive(&huart1, buf, len, 100);
}
HAL_StatusTypeDef stm32_uart_write_buffer(uint8_t *buf, uint16_t len)
{
return HAL_UART_Transmit(&huart1, buf, len, 100);
}
HAL_StatusTypeDef stm32_uart_write_dma(uint8_t *buf, uint16_t len)
{
return HAL_UART_Transmit_DMA(&huart1, buf, len);
}
HAL_StatusTypeDef stm32_uart_read_dma(uint8_t *buf, uint16_t len)
{
return HAL_UART_Receive_DMA(&huart1, buf, len);
}
HAL_StatusTypeDef stm32_uart_write_it(uint8_t *buf, uint16_t len)
{
return HAL_UART_Transmit_IT(&huart1, buf, len);
}
HAL_StatusTypeDef stm32_uart_read_it(uint8_t *buf, uint16_t len)
{
return HAL_UART_Receive_IT(&huart1, buf, len);
}
void example_uart_echo(void)
{
uint8_t ch;
if (HAL_UART_Receive(&huart1, &ch, 1, 100) == HAL_OK) {
HAL_UART_Transmit(&huart1, &ch, 1, 100);
}
}
void example_uart_transactions(void)
{
uint8_t rx_byte;
uint8_t tx_data[] = {0x10, 0x20, 0x30, 0x40};
stm32_uart_write_byte(0x55);
stm32_uart_read_byte(&rx_byte);
stm32_uart_write_string("Hello STM32 UART\r\n");
stm32_uart_write_buffer(tx_data, sizeof(tx_data));
stm32_uart_read_buffer(tx_data, 4);
}
When software writes data to UART:
Example frame for sending 0x41 ('A') using 8N1:
IDLE = 1 START = 0 DATA = 10000010 (LSB first for 0x41) STOP = 1
When data arrives on RX pin:
The start bit tells the receiver that a new frame is beginning.
LINE goes from HIGH to LOW
Stop bit marks the end of frame and returns line to idle HIGH state.
LINE returns HIGH
Parity is optional and used for basic error checking.
UART transfers raw bytes only. It does not understand:
Those formats must be handled by software protocol design.
Examples:
"HELLO" means sending bytes one by one0x1234 may require 2 bytesSo always define:
This tutorial is designed for embedded engineers working with ESP32, STM32, and similar MCUs.
End of Document