STM32 串口通信:串口的接收和发送详解
STM32 串口通信串口的接收和发送详解1. 前言串口通信是 STM32 开发中最常用的通信方式之一。在实际项目中串口经常用于程序调试、数据打印、上位机通信、蓝牙模块、WiFi 模块、GPS 模块以及各种串口外设模块。本文主要基于STM32 标准外设库讲解 USART 串口的发送和接收过程。示例芯片以STM32F103 系列为例使用USART1对应引脚为功能引脚USART1_TXPA9USART1_RXPA10本文重点讲解以下内容串口通信的基本原理USART1 的初始化配置串口发送数据的实现串口接收数据的实现串口中断接收的实现常见标志位和常见问题。2. 串口通信的基本原理STM32 中常用的串口外设是USART。USART 全称是Universal Synchronous / Asynchronous Receiver Transmitter也就是通用同步 / 异步收发器。在普通单片机项目中我们大多数时候使用的是 USART 的异步通信模式也就是平时说的 UART 串口通信。串口通信最少需要三根线引脚作用TX发送数据RX接收数据GND共地连接时要注意交叉连接STM32_TX --- 外部设备_RX STM32_RX --- 外部设备_TX STM32_GND --- 外部设备_GND也就是说发送端要接对方的接收端接收端要接对方的发送端。3. 串口数据帧格式串口发送数据时并不是一次性发送一个完整字节而是按照固定的数据帧格式一位一位发送。常见的数据帧格式如下起始位 数据位 校验位 停止位最常见的配置是115200 8N1含义如下配置含义115200波特率88 位数据位N无校验位11 位停止位例如发送字符A它的 ASCII 码是0x41。USART 外设会根据设置好的波特率把这个字节拆成一位一位的数据从 TX 引脚发出。4. USART1 初始化流程使用 STM32 标准库配置 USART1一般需要完成以下步骤开启 GPIOA 和 USART1 外设时钟配置 PA9 为复用推挽输出配置 PA10 为浮空输入配置 USART1 的波特率、数据位、停止位、校验位使能 USART1如果使用中断接收还需要配置 NVIC 和 USART 接收中断。5. USART1 初始化代码下面给出 USART1 的完整初始化函数。#includestm32f10x.hvoidUSART1_Init(uint32_tbaudrate){GPIO_InitTypeDef GPIO_InitStructure;USART_InitTypeDef USART_InitStructure;/* 1. 开启 GPIOA 和 USART1 时钟 */RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);/* 2. 配置 PA9 为 USART1_TX复用推挽输出 */GPIO_InitStructure.GPIO_PinGPIO_Pin_9;GPIO_InitStructure.GPIO_ModeGPIO_Mode_AF_PP;GPIO_InitStructure.GPIO_SpeedGPIO_Speed_50MHz;GPIO_Init(GPIOA,GPIO_InitStructure);/* 3. 配置 PA10 为 USART1_RX浮空输入 */GPIO_InitStructure.GPIO_PinGPIO_Pin_10;GPIO_InitStructure.GPIO_ModeGPIO_Mode_IN_FLOATING;GPIO_Init(GPIOA,GPIO_InitStructure);/* 4. 配置 USART1 参数 */USART_InitStructure.USART_BaudRatebaudrate;USART_InitStructure.USART_WordLengthUSART_WordLength_8b;USART_InitStructure.USART_StopBitsUSART_StopBits_1;USART_InitStructure.USART_ParityUSART_Parity_No;USART_InitStructure.USART_HardwareFlowControlUSART_HardwareFlowControl_None;USART_InitStructure.USART_ModeUSART_Mode_Tx|USART_Mode_Rx;USART_Init(USART1,USART_InitStructure);/* 5. 使能 USART1 */USART_Cmd(USART1,ENABLE);}这个函数完成了 USART1 的基础配置。如果只需要轮询发送和轮询接收到这里就已经可以使用了。6. 串口发送一个字节标准库中串口发送数据使用USART_SendData(USART1,data);但是发送前后需要配合标志位判断。常用发送标志位有两个标志位含义USART_FLAG_TXE发送数据寄存器为空USART_FLAG_TC发送完成其中TXE表示数据寄存器空了可以写入下一个字节TC表示最后一位数据已经真正从 TX 引脚发送完成。发送一个字节的函数如下voidUSART1_SendByte(uint8_tdata){USART_SendData(USART1,data);/* 等待发送数据寄存器为空 */while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)RESET);}这个函数的作用是发送一个字节。当TXE置位后说明可以继续发送下一个字节。7. 串口发送字符串在实际调试中我们更常用的是发送字符串。voidUSART1_SendString(char*str){while(*str!\0){USART1_SendByte((uint8_t)(*str));str;}}使用示例USART1_SendString(Hello STM32\r\n);串口助手中会显示Hello STM32其中\r\n表示回车换行方便串口助手换行显示。8. 串口轮询接收一个字节串口接收数据时需要判断接收数据寄存器是否非空。常用接收标志位是标志位含义USART_FLAG_RXNE接收数据寄存器非空当RXNE置位时说明 USART 已经接收到一个字节可以读取。轮询接收一个字节的函数如下uint8_tUSART1_ReceiveByte(void){uint8_tdata;/* 等待接收数据寄存器非空 */while(USART_GetFlagStatus(USART1,USART_FLAG_RXNE)RESET);dataUSART_ReceiveData(USART1);returndata;}这个函数会一直等待直到串口收到一个字节。使用示例uint8_tch;chUSART1_ReceiveByte();USART1_SendByte(ch);这段代码可以实现最简单的串口回显电脑发送什么STM32 返回什么9. 轮询接收的缺点轮询接收的代码简单但它有一个明显缺点while(USART_GetFlagStatus(USART1,USART_FLAG_RXNE)RESET);程序会一直卡在这里等待数据。如果串口一直没有收到数据主程序就无法继续向下执行。所以轮询接收适合简单测试不适合复杂项目。在实际项目中如果系统还需要做按键扫描、传感器采集、OLED 刷新、电机控制就更推荐使用串口中断接收。10. USART1 中断接收配置使用串口中断接收需要在初始化时增加两部分配置配置 NVIC使能 USART 接收中断。完整初始化代码如下voidUSART1_IT_Init(uint32_tbaudrate){GPIO_InitTypeDef GPIO_InitStructure;USART_InitTypeDef USART_InitStructure;NVIC_InitTypeDef NVIC_InitStructure;/* 1. 开启 GPIOA、USART1 和 AFIO 时钟 */RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);/* 2. 配置 PA9 为 USART1_TX */GPIO_InitStructure.GPIO_PinGPIO_Pin_9;GPIO_InitStructure.GPIO_ModeGPIO_Mode_AF_PP;GPIO_InitStructure.GPIO_SpeedGPIO_Speed_50MHz;GPIO_Init(GPIOA,GPIO_InitStructure);/* 3. 配置 PA10 为 USART1_RX */GPIO_InitStructure.GPIO_PinGPIO_Pin_10;GPIO_InitStructure.GPIO_ModeGPIO_Mode_IN_FLOATING;GPIO_Init(GPIOA,GPIO_InitStructure);/* 4. 配置 USART1 参数 */USART_InitStructure.USART_BaudRatebaudrate;USART_InitStructure.USART_WordLengthUSART_WordLength_8b;USART_InitStructure.USART_StopBitsUSART_StopBits_1;USART_InitStructure.USART_ParityUSART_Parity_No;USART_InitStructure.USART_HardwareFlowControlUSART_HardwareFlowControl_None;USART_InitStructure.USART_ModeUSART_Mode_Tx|USART_Mode_Rx;USART_Init(USART1,USART_InitStructure);/* 5. 配置 NVIC */NVIC_InitStructure.NVIC_IRQChannelUSART1_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority1;NVIC_InitStructure.NVIC_IRQChannelSubPriority1;NVIC_InitStructure.NVIC_IRQChannelCmdENABLE;NVIC_Init(NVIC_InitStructure);/* 6. 使能 USART1 接收中断 */USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);/* 7. 使能 USART1 */USART_Cmd(USART1,ENABLE);}这里开启的是USART_IT_RXNE中断。它表示当串口接收数据寄存器非空时产生中断。11. USART1 中断服务函数中断服务函数名称必须和启动文件中的中断函数名一致。USART1 的中断服务函数一般写成voidUSART1_IRQHandler(void){uint8_tdata;if(USART_GetITStatus(USART1,USART_IT_RXNE)!RESET){dataUSART_ReceiveData(USART1);USART1_SendByte(data);USART_ClearITPendingBit(USART1,USART_IT_RXNE);}}这段代码的作用是判断是否发生了 RXNE 接收中断读取接收到的数据将收到的数据重新发送出去清除中断标志位。这样就实现了一个基于中断的串口回显功能。12. 关于 RXNE 中断清除的说明在 STM32 中RXNE标志通常可以通过读取USART_DR数据寄存器来清除。而USART_ReceiveData(USART1)本质上就是读取数据寄存器。所以在很多代码中会看到这样的写法dataUSART_ReceiveData(USART1);读取之后RXNE标志就会被清除。不过在标准库写法中也常见手动调用USART_ClearITPendingBit(USART1,USART_IT_RXNE);对于初学者来说保留这句更直观。但是需要理解真正关键的是及时读取接收数据寄存器。13. 完整示例代码下面给出一个完整的 USART1 中断接收回显示例。#includestm32f10x.hvoidUSART1_SendByte(uint8_tdata);voidUSART1_SendString(char*str);voidUSART1_IT_Init(uint32_tbaudrate);intmain(void){USART1_IT_Init(115200);USART1_SendString(USART1 Init OK\r\n);while(1){/* * 主循环可以执行其他任务。 * 串口接收由中断完成不会阻塞主程序。 */}}voidUSART1_IT_Init(uint32_tbaudrate){GPIO_InitTypeDef GPIO_InitStructure;USART_InitTypeDef USART_InitStructure;NVIC_InitTypeDef NVIC_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);GPIO_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);USART_InitStructure.USART_BaudRatebaudrate;USART_InitStructure.USART_WordLengthUSART_WordLength_8b;USART_InitStructure.USART_StopBitsUSART_StopBits_1;USART_InitStructure.USART_ParityUSART_Parity_No;USART_InitStructure.USART_HardwareFlowControlUSART_HardwareFlowControl_None;USART_InitStructure.USART_ModeUSART_Mode_Tx|USART_Mode_Rx;USART_Init(USART1,USART_InitStructure);NVIC_InitStructure.NVIC_IRQChannelUSART1_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority1;NVIC_InitStructure.NVIC_IRQChannelSubPriority1;NVIC_InitStructure.NVIC_IRQChannelCmdENABLE;NVIC_Init(NVIC_InitStructure);USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);USART_Cmd(USART1,ENABLE);}voidUSART1_SendByte(uint8_tdata){USART_SendData(USART1,data);while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)RESET);}voidUSART1_SendString(char*str){while(*str!\0){USART1_SendByte((uint8_t)(*str));str;}}voidUSART1_IRQHandler(void){uint8_tdata;if(USART_GetITStatus(USART1,USART_IT_RXNE)!RESET){dataUSART_ReceiveData(USART1);USART1_SendByte(data);USART_ClearITPendingBit(USART1,USART_IT_RXNE);}}14. 串口发送和接收的技术总结14.1 发送过程串口发送的核心流程如下用户数据 ↓ USART_SendData() ↓ 写入 USART_DR 数据寄存器 ↓ USART 外设移位发送 ↓ TX 引脚逐位输出发送时常用TXE和TC两个标志位标志位技术含义TXE发送数据寄存器为空可以写入下一个数据TC当前数据已经完整发送结束普通字符串发送一般判断TXE即可。如果是半双工通信、RS485 方向切换等场景则更需要关注TC标志。14.2 接收过程串口接收的核心流程如下RX 引脚接收电平变化 ↓ USART 外设按波特率采样 ↓ 接收完成一个字节 ↓ 数据进入 USART_DR ↓ RXNE 标志置位 ↓ CPU 读取 USART_DR接收时最常用的标志位是RXNE标志位技术含义RXNE接收数据寄存器非空说明已经收到一个字节只要RXNE置位就说明可以读取数据。15. 常见问题分析15.1 串口没有输出常见原因没有开启 GPIOA 时钟没有开启 USART1 时钟PA9 没有配置为复用推挽输出串口助手选择了错误的 COM 口TX 和 RX 连接错误GND 没有共地。15.2 串口接收乱码常见原因STM32 和串口助手波特率不一致数据位、停止位、校验位不一致系统时钟配置错误串口助手编码显示方式不正确使用了错误的晶振频率。15.3 中断不进入常见原因没有配置 NVIC没有使能USART_IT_RXNE中断函数名写错启动文件中中断名称和代码中的函数名不一致没有开启 USART 外设RX 引脚配置错误。15.4 数据量大时丢数据常见原因中断处理函数中执行了太多耗时操作波特率较高CPU 来不及处理没有及时读取USART_DR出现了 ORE 溢出错误接收缓冲区设计不合理。如果项目中串口数据量较大建议使用USART DMA 空闲中断这种方式更适合接收不定长数据和高频数据。16. 小结STM32 串口通信的关键是理解发送和接收背后的流程。发送时程序通过USART_SendData()将数据写入 USART 数据寄存器然后由 USART 外设通过 TX 引脚逐位发送。接收时USART 外设从 RX 引脚采样数据当收到一个完整字节后RXNE标志置位CPU 读取USART_DR得到数据。对于初学者来说可以按照下面的顺序学习串口 GPIO 配置 ↓ USART 参数配置 ↓ 轮询发送 ↓ 轮询接收 ↓ 中断接收 ↓ DMA 接收只要掌握了 USART 的初始化、TXE、TC、RXNE这些关键点STM32 串口通信的大部分问题都可以定位和解决。