ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

UART——STM32

UART——STM32 1. 引言UARTUniversal Asynchronous Receiver/Transmitter通用异步收发器是嵌入式开发中最常用的通信接口之一。无论是调试信息输出、传感器数据采集还是与上位机、蓝牙模块、WiFi 模块通信UART 都扮演着不可或缺的角色。本文将基于 STM32 系列单片机从 UART 的基础原理出发逐步讲解 HAL 库与标准外设库两种开发方式下的 UART 配置方法并结合实际代码示例帮助读者快速掌握 STM32 UART 的使用技巧。2. UART 基础原理2.1 什么是 UARTUART 是一种异步串行通信协议通过两根数据线TX 发送、RX 接收实现全双工通信。与 SPI、I2C 等同步通信协议不同UART 不需要时钟线通信双方通过约定波特率Baud Rate来同步数据采样时刻。2.2 数据帧格式一个标准的 UART 数据帧由以下部分组成起始位1 位逻辑低电平标志数据传输开始数据位通常为 8 位也可配置为 7 位或 9 位校验位可选奇校验或偶校验停止位1 位或 2 位逻辑高电平标志数据传输结束起始位 | D0 | D1 | D2 | D3 | D4 | D5 | D6 | D7 | 校验位(可选) | 停止位2.3 波特率波特率表示每秒传输的比特数常用值有 9600、115200、460800 等。发送方与接收方必须使用相同的波特率才能正确通信。STM32 的波特率由系统时钟经过分频计算得到计算公式如下波特率 时钟频率 / (16 × USARTDIV)3. STM32 UART 硬件资源3.1 常用 UART 外设STM32 系列芯片通常集成多个 UART/USART 外设例如USART1挂载在 APB2 总线时钟频率较高USART2、USART3挂载在 APB1 总线UART4、UART5仅支持异步通信不支持同步模式不同型号的 STM32 芯片 UART 数量不同具体以芯片数据手册为准。3.2 引脚复用UART 的 TX、RX 引脚通常与 GPIO 复用需要通过 GPIO 配置将引脚映射到对应的 UART 外设。例如 STM32F103 的 USART1 默认引脚为TXPA9RXPA104. 基于 HAL 库的 UART 配置4.1 CubeMX 图形化配置使用 STM32CubeMX 可以快速完成 UART 的初始化配置步骤如下在 Pinout 视图中选择 USART1模式选择Asynchronous配置波特率、数据位、停止位、校验位在 GPIO Settings 中确认 TX、RX 引脚分配生成工程代码4.2 初始化代码解析CubeMX 生成的 UART 初始化代码位于usart.c文件中核心结构如下UART_HandleTypeDef huart1;staticvoidMX_USART1_UART_Init(void){huart1.InstanceUSART1;huart1.Init.BaudRate115200;huart1.Init.WordLengthUART_WORDLENGTH_8B;huart1.Init.StopBitsUART_STOPBITS_1;huart1.Init.ParityUART_PARITY_NONE;huart1.Init.ModeUART_MODE_TX_RX;huart1.Init.HwFlowCtlUART_HWCONTROL_NONE;huart1.Init.OverSamplingUART_OVERSAMPLING_16;if(HAL_UART_Init(huart1)!HAL_OK){Error_Handler();}}4.3 GPIO 复用配置staticvoidMX_GPIO_Init(void){GPIO_InitTypeDef GPIO_InitStruct{0};__HAL_RCC_USART1_CLK_ENABLE();__HAL_RCC_GPIOA_CLK_ENABLE();GPIO_InitStruct.PinGPIO_PIN_9|GPIO_PIN_10;GPIO_InitStruct.ModeGPIO_MODE_AF_PP;GPIO_InitStruct.SpeedGPIO_SPEED_FREQ_HIGH;HAL_GPIO_Init(GPIOA,GPIO_InitStruct);}5. UART 数据收发5.1 阻塞式发送uint8_tdata[]Hello STM32!\r\n;HAL_UART_Transmit(huart1,data,sizeof(data)-1,1000);最后一个参数为超时时间单位为毫秒。若在指定时间内未完成发送函数返回HAL_TIMEOUT。5.2 阻塞式接收uint8_trx_data;HAL_UART_Receive(huart1,rx_data,1,1000);5.3 中断方式收发中断方式不占用 CPU 等待时间适合实时性要求较高的场景。// 启动中断接收HAL_UART_Receive_IT(huart1,rx_data,1);// 中断回调函数voidHAL_UART_RxCpltCallback(UART_HandleTypeDef*huart){if(huart-InstanceUSART1){// 处理接收到的数据HAL_UART_Transmit(huart1,rx_data,1,100);// 重新开启中断接收HAL_UART_Receive_IT(huart1,rx_data,1);}}5.3.1 中断 环形缓冲区完整接收示例上面的示例每次只接收 1 个字节数据稍多就容易丢失。实际工程中更推荐「中断接收 环形缓冲区」的组合中断只负责把字节快速写入缓冲区主循环再从缓冲区取走数据解析既不会丢数据也不会阻塞中断。#includestdint.h#includestring.h/* 环形缓冲区定义 */#defineRING_BUF_SIZE256// 缓冲区大小建议为 2 的幂便于取模typedefstruct{uint8_tbuffer[RING_BUF_SIZE];// 数据存储区volatileuint16_thead;// 写指针指向下一个待写入的位置volatileuint16_ttail;// 读指针指向下一个待读取的位置}RingBuffer_t;RingBuffer_t uart1_rx_ring;// USART1 接收环形缓冲区实例/* 缓冲区写入函数在中断回调中调用 */voidRingBuffer_Write(RingBuffer_t*ring,uint8_tdata){uint16_tnext(ring-head1)%RING_BUF_SIZE;// 计算下一个写位置// 若下一个写位置等于读指针说明缓冲区已满丢弃该字节可在此做溢出计数if(next!ring-tail){ring-buffer[ring-head]data;// 写入数据ring-headnext;// 更新写指针}}/* 缓冲区读取函数在主循环中调用 *//* 返回值1 表示成功读到 1 字节0 表示缓冲区为空 */uint8_tRingBuffer_Read(RingBuffer_t*ring,uint8_t*data){if(ring-headring-tail)// 读写指针相等说明缓冲区为空{return0;}*dataring-buffer[ring-tail];// 取出数据ring-tail(ring-tail1)%RING_BUF_SIZE;// 更新读指针return1;}/* 查询缓冲区中待处理的数据量 */uint16_tRingBuffer_Count(RingBuffer_t*ring){return(uint16_t)((ring-head-ring-tailRING_BUF_SIZE)%RING_BUF_SIZE);}/* 初始化清空缓冲区 */voidRingBuffer_Init(RingBuffer_t*ring){ring-head0;ring-tail0;memset(ring-buffer,0,RING_BUF_SIZE);}/* 中断回调每收到 1 字节就写入缓冲区 */voidHAL_UART_RxCpltCallback(UART_HandleTypeDef*huart){if(huart-InstanceUSART1){// 把刚收到的字节写入环形缓冲区RingBuffer_Write(uart1_rx_ring,rx_data);// 重新开启中断接收等待下一个字节HAL_UART_Receive_IT(huart1,rx_data,1);}}/* 主循环从缓冲区取数据并处理 */intmain(void){uint8_tch;HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_USART1_UART_Init();RingBuffer_Init(uart1_rx_ring);// 初始化环形缓冲区// 启动中断接收每次接收 1 字节触发回调后自动重新开启HAL_UART_Receive_IT(huart1,rx_data,1);while(1){// 主循环中轮询读取缓冲区不阻塞中断while(RingBuffer_Read(uart1_rx_ring,ch)){// 在这里对 ch 做业务处理例如回显、协议解析、命令分发等HAL_UART_Transmit(huart1,ch,1,100);}// 可执行其他低优先级任务}}要点说明读写指针分离head只由中断回调写入tail只由主循环读取二者互不干扰天然避免了临界区竞争。满则丢弃缓冲区写满时新数据会被丢弃避免覆盖尚未读取的旧数据如需保留最新数据可改为覆盖tail的策略。容量取模(head 1) % RING_BUF_SIZE实现环形回绕缓冲区大小取 2 的幂时可用位运算 (RING_BUF_SIZE - 1)进一步提速。中断开销极小回调里只做「写一个字节 重新开启接收」耗时极短不会影响其他中断的实时性。5.4 DMA 方式收发DMA 方式适合大批量数据传输可显著降低 CPU 负载。uint8_ttx_buffer[64];HAL_UART_Transmit_DMA(huart1,tx_buffer,64);6. 基于标准外设库的 UART 配置对于使用标准外设库Standard Peripheral Library的开发者UART 配置方式如下GPIO_InitTypeDef GPIO_InitStructure;USART_InitTypeDef USART_InitStructure;// 使能时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA,ENABLE);// 配置 GPIOGPIO_InitStructure.GPIO_PinGPIO_Pin_9;GPIO_InitStructure.GPIO_ModeGPIO_Mode_AF_PP;GPIO_InitStructure.GPIO_SpeedGPIO_Speed_50MHz;GPIO_Init(GPIOA,GPIO_InitStructure);GPIO_InitStructure.GPIO_PinGPIO_Pin_10;GPIO_InitStructure.GPIO_ModeGPIO_Mode_IN_FLOATING;GPIO_Init(GPIOA,GPIO_InitStructure);// 配置 USARTUSART_InitStructure.USART_BaudRate115200;USART_InitStructure.USART_WordLengthUSART_WordLength_8b;USART_InitStructure.USART_StopBitsUSART_StopBits_1;USART_InitStructure.USART_ParityUSART_Parity_No;USART_InitStructure.USART_ModeUSART_Mode_RX|USART_Mode_TX;USART_Init(USART1,USART_InitStructure);// 使能 USARTUSART_Cmd(USART1,ENABLE);7. 串口重定向 printf在调试过程中将 printf 重定向到串口可以极大方便日志输出。以 HAL 库为例#includestdio.hintfputc(intch,FILE*f){HAL_UART_Transmit(huart1,(uint8_t*)ch,1,100);returnch;}intfgetc(FILE*f){uint8_tch;HAL_UART_Receive(huart1,ch,1,100);returnch;}配置完成后即可直接使用printf(value %d\r\n, value);输出调试信息。8. 常见问题与注意事项8.1 乱码问题检查波特率是否与上位机一致检查系统时钟配置是否正确检查数据位、停止位、校验位设置是否匹配8.2 接收不到数据确认 RX 引脚配置为浮空输入或上拉输入检查中断优先级配置确认共地连接8.3 发送数据不完整检查 DMA 配置是否正确确认发送缓冲区大小足够注意发送完成后是否需要等待9. 实战案例串口控制 LED下面通过一个完整示例演示如何通过串口接收指令控制 LED 开关。#includemain.hUART_HandleTypeDef huart1;uint8_trx_data;intmain(void){HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_USART1_UART_Init();// 启动中断接收HAL_UART_Receive_IT(huart1,rx_data,1);while(1){// 主循环可执行其他任务}}voidHAL_UART_RxCpltCallback(UART_HandleTypeDef*huart){if(huart-InstanceUSART1){if(rx_data1){HAL_GPIO_WritePin(LED_GPIO_Port,LED_Pin,GPIO_PIN_SET);HAL_UART_Transmit(huart1,(uint8_t*)LED ON\r\n,8,100);}elseif(rx_data0){HAL_GPIO_WritePin(LED_GPIO_Port,LED_Pin,GPIO_PIN_RESET);HAL_UART_Transmit(huart1,(uint8_t*)LED OFF\r\n,9,100);}// 重新开启中断接收HAL_UART_Receive_IT(huart1,rx_data,1);}}##10.参考资料与延伸阅读 以下资料可帮助读者进一步深入理解 STM32 UART 的底层原理与工程实践-**STM32F103 参考手册RM0008**ST 官方发布的芯片级参考手册详细描述了 USART 外设的寄存器定义、工作模式与中断/DMA 触发机制是查阅 UART 硬件细节的第一手资料。适合需要深入理解底层实现、排查硬件问题的开发者。-**STM32 HAL 库用户手册UM1025**官方 HAL 库的完整使用说明涵盖 HAL_UART_Transmit、HAL_UART_Receive_IT、HAL_UART_Transmit_DMA 等 API 的调用规范与回调机制。适合使用 HAL 库进行应用层开发的读者。-**环形缓冲区Ring Buffer设计与实现**介绍环形缓冲区的指针管理、满/空判断、并发读写等核心设计要点与本文5.3.1小节的示例直接对应。适合需要设计高可靠串口接收模块、避免数据丢失的开发者。-**STM32 中断优先级与 DMA 配置指南**讲解 NVIC 中断优先级分组、抢占优先级与子优先级的设置方法以及 DMA 通道与中断的配合策略。适合在中断接收、DMA 收发场景下需要合理配置优先级、避免数据竞争的项目。-**STM32CubeMX 用户指南UM1718**官方图形化配置工具的使用手册涵盖引脚分配、时钟树配置与代码生成流程。适合刚接触 CubeMX、希望快速搭建工程骨架的初学者。 本文从 UART 的基本原理出发详细介绍了 STM32 平台下 UART 的配置与使用方法 ##11.总结 本文从 UART 的基本原理出发详细介绍了 STM32 平台下 UART 的配置与使用方法涵盖 HAL 库与标准外设库两种开发方式并提供了中断、DMA 等多种收发模式的实际代码示例。**核心要点回顾**UART 通过 TX/RX 两根数据线实现异步全双工通信通信双方需约定一致的波特率、数据位、停止位与校验位STM32 的 UART 引脚与 GPIO 复用需先完成时钟使能与引脚映射配置。**两种开发方式对比**HAL 库配合 CubeMX 图形化配置代码可读性好、移植性强适合快速开发与维护标准外设库寄存器操作更直接、占用资源更少适合对底层有深入理解或资源受限的旧工程。**收发模式选择建议**少量数据或调试场景用阻塞式简单直接实时性要求高时用中断方式配合环形缓冲区可避免数据丢失大批量数据传输则优先 DMA显著降低 CPU 负载。**常见调试技巧**使用逻辑分析仪抓取 TX/RX 波形可直观核对波特率与帧格式是否正确串口助手调试时注意勾选与固件一致的波特率、数据位等参数并善用十六进制显示排查非 ASCII 数据。 希望本文能帮助读者快速上手 STM32 UART 开发在实际项目中灵活运用这一经典通信接口。 涵盖 HAL 库与标准外设库两种开发方式并提供了中断、DMA 等多种收发模式的实际代码示例。 在实际项目中建议根据通信数据量、实时性要求等因素选择合适的收发方式-**少量数据、调试场景**阻塞式或中断方式-**大批量数据**DMA 方式-**实时性要求高**中断方式配合环形缓冲区 希望本文能帮助读者快速上手 STM32 UART 开发在实际项目中灵活运用这一经典通信接口。
返回列表