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(документация) под ПЛИС. При это является многофункциональным и максимально оптимизированным. Данный контроллер имеет следующие порты:

  • 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 на языке 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.