Сводка
UART или универсальный асинхронный приемопередатчик данных, является одним из основных интерфейсов для передачи данных между устройствами. В данной статье мы рассмотрим, что такое UART, его применение, а также приведем примеры программирования этого интерфейса на языках Verilog под ПЛИС и C++ под stm32 и esp32.
UART: Что это такое?
UART – это аппаратно-программный интерфейс, предназначенный для асинхронной передачи данных между устройствами. Он позволяет обмениваться информацией по одному биту за раз, без необходимости использования внешнего тактового сигнала для синхронизации передачи и приема данных.
Типичные устройства, использующие UART, включают в себя компьютеры, микроконтроллеры, модемы, GPS-приемники и другие периферийные устройства. UART позволяет соединять эти устройства для передачи данных в различных форматах и с различными скоростями передачи.
Применение UART
UART широко применяется во встраиваемых системах, микроконтроллерах и других устройствах, где необходима простая и надежная передача данных. Он используется для коммуникации между микроконтроллерами и периферийными устройствами, такими как сенсоры, дисплеи, модули связи и другие.
STM32 и ESP32 — это популярные микроконтроллеры, которые поддерживают UART. С их помощью можно легко организовать обмен данными между устройствами через UART интерфейс.
Недостатки и Преимущества
Недостатками UART являются низкая скорость передачи данных по сравнению с другими интерфейсами, такими как Ethernet или USB, а также отсутствие поддержки многих функций, таких как обнаружение ошибок и контроль уровня передачи. Однако UART является простым и удобным в использовании интерфейсом для простых проектов.
Преимуществами UART являются его универсальность, поддержка большинства микроконтроллеров и микропроцессоров, а также простота реализации и отладки.
Принцип Работы UART
Основной принцип работы UART заключается в применении асинхронной передачи по линиям tx и rx, что означает отсутствие общего тактового сигнала для синхронизации устройств. Вместо этого UART использует стартовый бит и стоп-биты для разграничения передаваемых данных.
Процесс передачи данных начинается с отправки стартового бита, который сигнализирует начало передачи байта данных. Далее биты данных передаются последовательно по одному, с заданной скоростью передачи данных (скорость в бодах). Приемное устройство считывает данные, синхронизируя свой внутренний такт с полученными данными.

Порт TX (Transmit) в UART представляет собой часть интерфейса, отвечающую за передачу данных из устройства. Он выполняет функцию передачи битов данных в формируемом кадре, начиная с отправки стартового бита и заканчивая стоп-битами.
Порт RX (Receive) в UART ответственный за прием данных от других устройств. Он играет ключевую роль в процессе приема и обработки информации, поступающей от передающего устройства или контроллера. Он обеспечивает прием битов данных в принимаемом кадре, начиная с считывания стартового бита и заканчивая проверкой наличия стоп-битов.

ПРИМЕЧАНИЕ: Бит Четности может бы и вместо 7 бита. Главное, чтоб приемник и передатчик использовали одну структуру.
Посылка начинается со стартового бита, который равен 0. Далее следуют биты данных и необязательный бит контроля четности для обнаружения ошибок. Завершается передача стоп-битом, указывающим на конец передачи байта данных. После этого можно начать передачу следующего байта.
Примеры
Пример UART на языке Verilog под ПЛИС
Код реализует контроллер UART на языке Verilog(документация) под ПЛИС. При это является многофункциональным и максимально оптимизированным. Данный контроллер имеет следующие порты:
- clk — тактовый сигнал плис
- rx_serial — линия rx для соединения с другим устройством на порт tx
- rx_data — линия прочитанных данных
- rx_vd — линия валидации rx_data
- tx_serial — линия tx для соединения с другим устройством на порт rx
- tx_data — посылаемые данные
- tx_valid — валидация данных tx_data
- ready_tx — готовность принять новые данные tx_data
В коде объявлены множество generate конструкций, что дает больший функционал через изменяемые параметры:
- TX_ENABLE — значение 1 дает возможность UART отправлять данные через линию TX. Значение 0 — отключает линию TX и перестает учитывать логические элементы, которые затрачивались на передачу.
- RX_ENABLE — значение 1 позволяет UART получать данные, а значение 0 — запрещает получение и не учитывает затрачиваемые ресурсы.
- on_count_rate — рассчитываемый параметр счетчика для установление на нужной частоте UART. Расчет приведен в начале программы через комментарии.
- PARITY_ENABLE — Значение 1 добавляет стоп бит после кадра данных не уменьшая его, а знач. 0 не учитывает.
Контроллер UART написан не используя конечные автоматы, потому что затрачивало больше ресурсов ПЛИС. Логика RTL реализована на последовательно—параллельных и параллельно-последовательных регистрах, что на 20% эффективнее. Временная диаграмма и отчет компиляциях приведены на рисунке 3 ниже.
module UART_VERILOG
#(
parameter TX_ENABLE = 1, // контроллер может отправлять данные
parameter RX_ENABLE = 1, // контроллер может принимать данные
// on_count_rate = (CLK_FREQ/BAUDRATE)
// CLK_FREQ - Это частота clk
// BAUDRATE - бит/сек
// Моя частота 200_000kHz и 9600 бит/сек (12000 бод)
// on_count_rate = 200_000 / 9600 = 20,83333. Округляем до 21
parameter on_count_rate = 21,
parameter PARITY_ENABLE = 1 // учитывать 8 битом бит четности
)
(
input wire clk, // тактовый сигнал
input wire rx_serial, // rx линия
output wire [7:0] rx_data, // принятые данные
output wire rx_vd, // Валидация данных
output wire tx_serial, // tx линия
input wire [7:0] tx_data, // посылаемые данные
input wire tx_vd, // Валидация данных
output wire ready_tx // готовность контроллера
);
// Счетчик подсчета тактового сигнала
reg [7:0] counter_rate = 0;
// Счетчик подсчета принятых битов
reg [3:0] counter_bits_rx = 0;
// Счетчик подсчета посланных битов
reg [3:0] counter_bits_tx = 0;
// тактовый сигнал UART
reg clk_br = 0;
// регистр первого принятого бита
reg _first_bit_rx = 0;
// регистр принимающий через clk_br
reg [11:0] tx_data_reg = 0;
// регистр принимающий через clk
reg [7:0] tx_termclk = 0;
// регистр готовности контроллера
reg ready_trns = 1;
// [7:0] - без бита четности. [8:0] - с битом четности
reg [8:0] rx_data_reg;
// регистр бита четности при передачи
reg parity = 0;
// флаг полученных данных через clk
reg flag_start_tx = 0;
// флаг получение инф о лог. 1 на валидации
reg first_clk_vd = 0;
// ограничитель сигнала валидации в один такт
reg limit_vd = 0;
// флаг контроля лог. уровня сигнала clk_br
reg edge_clk_br = 0;
wire wire_vd_rx;
assign rx_data = rx_data_reg[7:0],
rx_vd = limit_vd;
generate
if (PARITY_ENABLE == 1) begin
assign wire_vd_rx = ~parity & (counter_bits_rx == 9)
& rx_serial & ~clk_br;
end else begin
assign wire_vd_rx = counter_bits_rx[3] & rx_serial;
end
endgenerate
assign ready_tx = ready_trns,
tx_serial = tx_data_reg[0] | ready_trns;
always @(posedge clk) begin
if ((on_count_rate - 1) != counter_rate) begin
counter_rate <= counter_rate + 1;
clk_br <= 0;
end else begin
counter_rate <= 0;
clk_br <= 1;
end
if (tx_vd & ready_trns) begin
tx_termclk <= tx_data;
flag_start_tx <= 1;
if (clk_br)
edge_clk_br <= 1;
else
edge_clk_br <= 0;
end else begin
if (clk_br & ~edge_clk_br) begin
flag_start_tx <= 0;
end
if (~clk_br & edge_clk_br) begin
edge_clk_br <= 0;
end
end
if (wire_vd_rx) begin
if (~first_clk_vd) begin
first_clk_vd <= 1;
limit_vd <= 1;
end else begin
limit_vd <= 0;
end
end else begin
first_clk_vd <= 0;
end
end
integer i;
generate
if (RX_ENABLE) begin
always @(posedge clk_br) begin
// последовательно-параллельный регистр
if (PARITY_ENABLE == 1) begin
rx_data_reg[8] <= rx_serial;
for (i = 1; i < 9; i = i + 1) begin
rx_data_reg[i - 1] <= rx_data_reg[i];
end
end else begin
rx_data_reg[7] <= rx_serial;
for (i = 1; i < 8; i = i + 1) begin
rx_data_reg[i - 1] <= rx_data_reg[i];
end
end
// первый бит передачи
if (~rx_serial & ~_first_bit_rx) begin
_first_bit_rx <= 1;
end else if (_first_bit_rx) begin
if (PARITY_ENABLE == 0) begin
if (~counter_bits_rx[3]) begin
counter_bits_rx <= counter_bits_rx + 1;
end else begin
counter_bits_rx <= 0;
_first_bit_rx <= 0;
end
end else begin
if (counter_bits_rx < 10) begin
counter_bits_rx <= counter_bits_rx + 1;
parity <= parity ^ rx_serial;
end else begin
counter_bits_rx <= 0;
parity <= 0;
_first_bit_rx <= 0;
end
end
end
end
end
endgenerate
integer j;
generate
if (TX_ENABLE) begin
always @(posedge clk_br) begin
if (flag_start_tx) begin
if (PARITY_ENABLE == 1) begin
tx_data_reg[0] <= 0;
tx_data_reg[8:1] <= tx_termclk;
tx_data_reg[9] <= ^ tx_termclk;
tx_data_reg[10] <= 1;
tx_data_reg[11] <= 1;
end else begin
tx_data_reg[0] <= 0;
tx_data_reg[8:1] <= tx_termclk;
tx_data_reg[9] <= 1;
tx_data_reg[10] <= 1;
end
ready_trns <= 0;
end
if (~ready_trns & ~tx_vd) begin
if (PARITY_ENABLE == 0) begin
for (j = 0; j < 10; j = j + 1) begin
tx_data_reg[j] <= tx_data_reg[j + 1];
end
if (counter_bits_tx == 9) begin
ready_trns <= 1;
end
end else begin
for (j = 0; j < 11; j = j + 1) begin
tx_data_reg[j] <= tx_data_reg[j + 1];
end
if (counter_bits_tx == 10) begin
ready_trns <= 1;
end
end
counter_bits_tx = counter_bits_tx + 1;
end else begin
counter_bits_tx = 0;
end
end
end
endgenerate
endmodule

Пример UART на языке C++ под stm32
#include "stm32f4xx.h"
#include "stm32f4xx_hal.h"
UART_HandleTypeDef huart2;
void SystemClock_Config(void);
void Error_Handler(void);
int main(void)
{
HAL_Init();
SystemClock_Config();
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_USART2_CLK_ENABLE();
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.Pin = GPIO_PIN_2 | GPIO_PIN_3;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF7_USART2;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
huart2.Instance = USART2;
huart2.Init.BaudRate = 9600;
huart2.Init.WordLength = UART_WORDLENGTH_8B;
huart2.Init.StopBits = UART_STOPBITS_1;
huart2.Init.Parity = UART_PARITY_NONE;
huart2.Init.Mode = UART_MODE_TX_RX;
huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
if (HAL_UART_Init(&huart2) != HAL_OK)
{
Error_Handler();
}
while (1)
{
uint8_t sendData[10] = "Hello!\n";
HAL_UART_Transmit(&huart2, sendData, sizeof(sendData), HAL_MAX_DELAY);
HAL_Delay(1000);
}
}
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct;
RCC_ClkInitTypeDef RCC_ClkInitStruct;
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
RCC_OscInitStruct.PLL.PLLM = 16;
RCC_OscInitStruct.PLL.PLLN = 336;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV4;
RCC_OscInitStruct.PLL.PLLQ = 7;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK
| RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
{
Error_Handler();
}
}
void Error_Handler(void) {
while(1) {
}
}
Пример UART на языке C++ под esp32
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/uart.h"
#define UART_NUM UART_NUM_1
#define TX_PIN 17
#define RX_PIN 16
#define BUF_SIZE (1024)
void init_uart() {
uart_config_t uart_config = {
.baud_rate = 9600,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE
};
uart_param_config(UART_NUM, &uart_config);
uart_set_pin(UART_NUM, TX_PIN, RX_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
uart_driver_install(UART_NUM, BUF_SIZE * 2, 0, 0, NULL, 0);
}
void uart_task(void *pvParameters) {
uint8_t* data = (uint8_t*) malloc(BUF_SIZE);
while (1) {
int rxBytes = uart_read_bytes(UART_NUM, data, BUF_SIZE, 1000 / portTICK_RATE_MS);
if (rxBytes > 0) {
uart_write_bytes(UART_NUM, (const char*)data, rxBytes);
}
}
free(data);
}
void app_main() {
init_uart();
xTaskCreate(uart_task, "uart_task", 1024*4, NULL, 10, NULL);
}
Заключение
Статья о протоколе UART представляет ценный источник информации как для опытных специалистов, так и для новичков в области микроконтроллеров и коммуникационных интерфейсов. Для новичков она станет отличным руководством для понимания основ работы с UART и возможностей его применения, в то время как опытные специалисты найдут в ней полезные примеры реализации на популярных языках программирования, таких как Verilog и C++. Статья поможет как начинающим разработчикам освоить новые навыки, так и профессионалам дополнить свои знания и углубить понимание работы с протоколом UART.