DocsTech
/
Интерфейсы
/

~ cd uart: обзор, примеры на verilog и c++

Сводка

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 использует стартовый бит и стоп-биты для разграничения передаваемых данных.

Процесс передачи данных начинается с отправки стартового бита, который сигнализирует начало передачи байта данных. Далее биты данных передаются последовательно по одному, с заданной скоростью передачи данных (скорость в бодах). Приемное устройство считывает данные, синхронизируя свой внутренний такт с полученными данными.

UART Соединение устройств
Рисунок 1. Соединение устройств UART

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

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

Структура передачи UART
Рисунок 2. Структура передачи по UART

ПРИМЕЧАНИЕ: Бит Четности может бы и вместо 7 бита. Главное, чтоб приемник и передатчик использовали одну структуру.

Посылка начинается со стартового бита, который равен 0. Далее следуют биты данных и необязательный бит контроля четности для обнаружения ошибок. Завершается передача стоп-битом, указывающим на конец передачи байта данных. После этого можно начать передачу следующего байта.

Примеры

Пример UART на языке Verilog под ПЛИС

Код реализует контроллер UART на языке Verilog(документация) под ПЛИС. При это является многофункциональным и максимально оптимизированным. Данный контроллер имеет следующие порты:

В коде объявлены множество generate конструкций, что дает больший функционал через изменяемые параметры:

Контроллер 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 на языке Verilog
Рисунок 3. Временная диаграмма и отчет о компиляции контроллера UART на языке Verilog

Пример UART на языке C++ под stm32

Приведу пример кода на языке C++ для инициализации и использования UART на микроконтроллере STM32F407VGT6 под управлением среды разработки CubeMX и HAL библиотеки. В данном примере мы будем использовать UART2. Baud rate 9600 и будет периодически отправлять «Hello!» через UART2. Не забудьте настроить CubeMX и добавить HAL файлы для работы с UART.
...
Копировать
#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

Пример кода на языке С для настройки и использования UART на микроконтроллере ESP32. Этот код инициализирует UART на ESP32, устанавливает скорость передачи данных 9600 бод, указывает пины для приема (RX_PIN) и передачи (TX_PIN), создает задачу для чтения данных из UART и обратной отправки этих данных.
...
Копировать
#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.

Главная
Курсы
Вебинары
Описание интерфейса I2C: принцип работы, протокол и применение
15 наиболее используемых аппаратных интерфейсов
Интерфейс DMX512: описание и принцип работы
RS485: обзор, примеры на Verilog и С++
UART: обзор, примеры на Verilog и C++
SPI: обзор, примеры на Verilog и C++
Закрыть