ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

STM32F103串口2奇偶校验配置指南:从8E1到标准库实战

2026/9/10 3:12:45 拓冰建站 浏览量
STM32F103串口2奇偶校验配置指南:从8E1到标准库实战 简介面向STM32F103C8T6开发者的一份串口2USART2奇偶校验通信工程实例适合正在学习STM32标准外设库或HAL库的嵌入式爱好者。工程完整演示了从RCC时钟使能、GPIO复用映射到USART控制寄存器配置的全过程涵盖波特率、数据位、停止位以及PCE与PS位设置的奇偶校验选择并提及中断与DMA用于非阻塞收发的扩展方式同时对比标准外设库与HAL库的初始化思路。压缩包共78个文件以C源文件与H头文件为主辅以Keil工程文件、汇编启动文件、Hex烧录文件、调试配置文件及README说明可结合代码对照理解底层寄存器操作与库函数封装逻辑整体大小仅308KB结构清晰、便于查阅。目前已有2442人学习下载适合需要快速验证串口通信配置或参考工程模板的开发者。 最近在调一台工控设备的通信对方的协议里明确写着“8E1”结果我们的板子一直收乱码。排查了半天最后发现问题是串口2的奇偶校验根本没配置到位。这种问题在STM32F103开发里太典型了——不是不会配而是很多人对“奇偶校验到底改变了什么”理解得不够透导致配置漏掉关键步骤通信一挂就抓瞎。这篇文章我就围绕STM32F103的串口2USART2展开把奇偶校验的硬件机制、标准库配置方法、收发配合的细节以及调试排错经验一次讲清楚。适合刚接触单片机通信的初学者也适合那些用Modbus RTU、自定义协议做设备联调但总被校验位坑到的工程师参考。1. 奇偶校验到底在解决什么问题先搞懂原理再动手1.1 串口帧里的各位是怎么排的串口通信UART的每一帧数据从示波器上看就是一段固定的电平序列。一个完整的帧通常由这几部分组成起始位1位低电平告诉接收端“我要开始发数据了”数据位常见的是8位低位在前校验位可选1位停止位1位或2位高电平标志一帧结束我们常说的“8N1”指的就是8个数据位、无校验位None、1个停止位。而“8E1”则是8个数据位、偶校验Even、1个停止位。“8O1”对应奇校验Odd。1.2 奇偶校验的工作逻辑奇偶校验的原理非常简单统计一帧数据中“1”的个数然后根据约定给校验位赋值。偶校验数据位中“1”的个数加上校验位后总数必须为偶数奇校验数据位中“1”的个数加上校验位后总数必须为奇数举个例子如果数据是0x55也就是二进制01010101里面有4个“1”。偶校验时校验位填0让总数保持偶数奇校验时校验位填1让总数变成5个成为奇数接收端拿到数据后自己也算一遍“1”的个数再和校验位对比。对不上就说明这一帧在传输过程中被干扰了。1.3 奇偶校验能干什么不能干什么奇偶校验是一个开销极低、硬件自动完成的差错检测手段。它不需要额外的通信协议支持也不消耗额外的数据帧长度只要发送端和接收端约定一致即可。但它的检测能力非常有限——只能检测出奇数个位翻转。如果一帧数据里恰好有两个或多个偶数个位被干扰奇偶校验就“看不出来”。而且它只能报错不能纠错更不能判断是数据位错了还是校验位错了。所以在真实的项目中奇偶校验通常是和更高层的校验手段比如Modbus RTU的CRC16配合使用。底层校验负责尽早发现字节级错误上层校验负责保证整帧数据的完整性。2. STM32F103串口2的奇偶校验硬件机制数据位和校验位是怎么纠缠的2.1 串口2在芯片里的位置与引脚STM32F103的串口2USART2挂载在APB1总线上这是很多人在配置时钟时容易搞错的地方。APB1最高频率是36MHz而串口1USART1挂载在APB2上最高72MHz。使用标准库时串口2的时钟使能函数和串口1也不一样USART1RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE)USART2RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE)如果不小心把串口2的时钟用成了APB2使能那串口2就完全没有反应调试半天才发现是时钟没挂上。串口2默认复用引脚是PA2TX和PA3RX。也可以通过重映射功能把这两个引脚换到PD5和PD6但重映射需要额外开启AFIO时钟并且在配置完GPIO之后还要调用GPIO_PinRemapConfig(GPIO_Remap_USART2, ENABLE)。多数项目直接用PA2/PA3就足够了重映射用得比较少。2.2 M位、PCE位和PS位的组合关系这是整个奇偶校验配置里的重点也是很多人写代码出错的高发区。STM32F103的USART控制寄存器USART_CR1里有三个位和帧格式相关M字长选择。M0时字长为8位M1时字长为9位PCE奇偶校验使能。PCE0时无校验PCE1时有校验PS校验选择。PS0时偶校验PS1时奇校验关键来了——当PCE1时实际传输的帧长度会变成数据位加校验位。也就是说如果M位配置为08位字长开启校验后实际传输的是8位数据加1位校验位总共9位。这是最常见的使用方式对应“8E1”或“8O1”如果M位配置为19位字长开启校验后实际传输的是9位数据加1位校验位总共10位这种情况下数据位就应该是9位所以标准库配置中只要你想做8位数据加校验位就必须把数据结构体里的WordLength设置为9位数据模式。这一个操作就是为了给帧里的校验位腾出空间。所以写成WordLength USART_WordLength_9b并不是说你在发9位原始数据而是告诉硬件“发送或接收的完整帧要占9位其中8位是用户数据1位由硬件根据奇偶校验规则自动生成或解析”。很多初学者不理解这一点把WordLength设置成8位然后又开了校验位。硬件会把这8位当成9位帧来处理最后发送的数据格式和接收端的解析完全对不上通信必然混乱。2.3 硬件自动完成的工作开启奇偶校验后发送端用户不需要自己计算校验位硬件会在发送时根据数据位中的“1”的个数自动把校验位填好。接收端硬件也会自动检查校验位和数据位是否匹配如果发现不匹配硬件会置起PE标志Parity Error。PE标志位于USART_SR状态寄存器的第0位。这个标志一旦置起就说明接收端检测到了帧错误。此时如果没有做任何处理下一次接收可能还会被这个错误状态干扰。尤其是在中断接收场景下必须在读取数据之后手动清除PE标志位否则会出现中断风暴或者接收逻辑错乱。2.4 一个很多人不知道的细节USART_DR的第9位STM32F103的USART数据寄存器USART_DR是16位的但有效数据最多只有9位。当开启了校验位并选择9位字长时USART_DR的最高有效位存储的就是校验位对应的数据。表面上看这个校验位是给硬件用的软件一般不需要关心。但在某些调试场景下你可以通过直接读取USART_DR的第9位来观察硬件解析出来的校验值这在排查校验逻辑是否匹配时很有用。发送端也有类似机制。USART_SendData函数接收的是uint16_t类型的数据虽然在大部分例程里只使用低8位但在开启校验的场景下如果你手动把第9位也写进去这个值会覆盖硬件自动生成的校验位。普通的串口应用不建议这么干但在某些自定义协议里这种特性反而可以拿来快速组装特殊帧。3. 标准库V3.5配置串口2奇偶校验的完整代码3.1 GPIO和时钟的初始化使用标准外设库StdPeriph_Lib V3.5时串口2的初始化代码可以拆成三步GPIO配置、USART配置、NVIC中断配置。先看GPIO和时钟部分#include stm32f10x.h #include stm32f10x_gpio.h #include stm32f10x_rcc.h #include stm32f10x_usart.h #include misc.h void USART2_GPIO_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); GPIO_InitStructure.GPIO_Pin GPIO_Pin_2; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin GPIO_Pin_3; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, GPIO_InitStructure); }这里有个细节TX引脚必须配置为复用推挽输出GPIO_Mode_AF_PP而RX引脚配置为浮空输入GPIO_Mode_IN_FLOATING或上拉输入都可以浮空输入是最保守的选择。3.2 USART结构体里最关键的三行配置USART协议的参数都封装在USART_InitTypeDef结构体里。对于串口2奇偶校验场景核心配置如下void USART2_Init(void) { USART_InitTypeDef USART_InitStructure; USART_InitStructure.USART_BaudRate 9600; USART_InitStructure.USART_WordLength USART_WordLength_9b; USART_InitStructure.USART_StopBits USART_StopBits_1; USART_InitStructure.USART_Parity USART_Parity_Even; USART_InitStructure.USART_HardwareFlowControl USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode USART_Mode_RX | USART_Mode_TX; USART_Init(USART2, USART_InitStructure); USART_Cmd(USART2, ENABLE); }在这段代码里三条配置需要特别注意USART_WordLength USART_WordLength_9b前面解释过开启校验后必须用9位字长。这个不是预留数据位而是为校验位留位置USART_Parity USART_Parity_Even偶校验模式。如果要用奇校验改成USART_Parity_OddUSART_BaudRate 9600波特率。这个值和通信双方设备必须完全一致哪怕差百分之二长时间通信后也会出现偶发错帧很多工程项目中Modbus RTU默认使用偶校验所以上面的代码适用于大部分不改协议的场合。3.3 发送和接收函数怎么写配置好串口后发送函数的实现相对简单奇偶校验完全由硬件完成用户代码层面和普通串口一样只是在库函数参数类型上要注意一下void USART2_SendByte(uint16_t data) { USART_SendData(USART2, data); while (USART_GetFlagStatus(USART2, USART_FLAG_TXE) RESET); }这里USART_SendData的第二个参数类型是uint16_t和普通8位数据的写法看起来一样。不需要用户在软件里计算奇偶校验位硬件会自动填充。接收部分最简单的方案是轮询uint16_t USART2_ReceiveByte(void) { while (USART_GetFlagStatus(USART2, USART_FLAG_RXNE) RESET); return (uint16_t)USART_ReceiveData(USART2); }但工程上更推荐中断接收避免主循环被串口阻塞。3.4 接收中断和PE错误处理的完整套路开启接收中断时必须一起处理PE奇偶校验错误标志。否则一旦通信线路出现干扰或者双方校验配置不一致PE标志会一直置起导致后续数据完全无法接收。void USART2_NVIC_Config(void) { NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel USART2_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority 1; NVIC_InitStructure.NVIC_IRQChannelCmd ENABLE; NVIC_Init(NVIC_InitStructure); USART_ITConfig(USART2, USART_IT_RXNE, ENABLE); USART_ITConfig(USART2, USART_IT_PE, ENABLE); }中断服务函数里标准写法是先读SR再读DR顺序不能反void USART2_IRQHandler(void) { if (USART_GetITStatus(USART2, USART_IT_PE) ! RESET) { USART_ClearITPendingBit(USART2, USART_IT_PE); } if (USART_GetITStatus(USART2, USART_IT_RXNE) ! RESET) { uint16_t data USART_ReceiveData(USART2); // 这里放入自己的数据处理逻辑 } }如果只开了RXNE中断没有处理PE错误PE标志占着不释放整个中断系统的响应都会变得异常。4. 两端设备配合中的坑从串口助手到对端设备4.1 串口调试助手的校验位设置调试串口2奇偶校验时最常用的工具就是串口调试助手。在使用串口助手连接STM32F103开发板时需要保证软件底部的通信参数和单片机端完全一致。比如单片机设置了8位数据、偶校验、1位停止位那么串口助手里就要选择9600、8、E、1。很多串口助手在界面上的下拉框是分开的数据位一个下拉框校验位一个下拉框停止位一个下拉框。把校验位选成“Even”或“奇校验”即可。如果选错了串口助手发出来的帧格式就和单片机期望的不一致表现为单片机要么收不到数据要么收到的数据是错的。参数设置值波特率与单片机一致如9600数据位8校验位Even偶校验或 Odd奇校验停止位14.2 对端设备没有校验位时的表现在真实项目中最容易踩的坑是自己这边的单片机代码实现了奇偶校验但对端设备还是无校验设置。这时候会有什么现象假如对端设备用8N1格式发数据单片机用8E1格式接收。由于帧长度定义不同、校验位处理方式不同单片机会把对端发来的停止位或下一个帧的起始位当作校验位来解析轻则偶发错一个字节重则每帧都报错。反过来也一样单片机开了偶校验发送数据对端配置成无校验对端会收到9位长度的帧但按8位数据来解析导致每个字节的最后一位丢失数据整体错乱。4.3 示波器下的波形验证方法如果你有示波器最直观的验证方法是直接把TX引脚的电平波形抓出来看。发送一帧0x55的数据时开启偶校验后如果数据是01010101里面4个“1”那波形上应该能看到数据位后有第9位且这一位是低电平0。通过这个波形你可以一眼判断出硬件是否真的把校验位加上了也能判断停止位的位置是否正常。这个方法在排查“代码好像配置了但实际没生效”的问题时非常管用。4.4 自测回环方法如果手头没有第二块板子做一个最简单的自测把STM32F103串口2的TX和RX引脚短接然后在主循环里定时发送一包数据看接收端能否原样收到。如果收到数据和自己发送的一致说明串口2的硬件收发链路没问题如果收到的数据和发送的不一致且开启了校验位那就要检查奇偶校验模式是否匹配了。不过要注意自环测试只能验证本机收发链路不能验证两端设备之间通信参数的匹配关系因为单片机自己和自己通信两端参数肯定是一致的。5. 奇偶校验失败排查链路一个典型的调试验证过程5.1 先关校验验证基础链路我做串口排查时有个习惯第一步永远是先把校验位关闭用最基础的8N1格式跑一遍。这一步的目的不是让步而是为了切割问题域。如果8N1模式下数据收发完全正常说明波特率、GPIO复用、时钟配置这些都是好的问题大概率出在校验配置上。如果8N1模式本身都通不过那就别急着碰校验了先解决基础链路。5.2 开校验后收不到数据问题出在哪开校验之后最典型的现象就是完全收不到数据或者收上来一大片乱码。按照经验优先级最高的排查项是WordLength配置。因为很多人开校验后WordLength还是默认设置的8位字长导致硬件对帧长度的解析和实际发送方的帧结构不匹配。第二个排查项是PE中断。开着PE中断但没做标志清除会导致接收被PE中断卡死看起来就像“完全收不到数据”。处理办法是在中断服务函数里加上PE标志清除逻辑并且在主程序里把PE对应的错误状态打印出来看看是不是一直在周期性报错。5.3 偶发数据错误但又不经常复现这种情况最折磨人。现象是通信整体能工作但隔一段时间就错一帧。我通常按下面这个顺序排查先确认波特率是否准确。可以用示波器测量TX引脚实际波形的高电平宽度和理论值对比。如果单片机主频晶振偏了波特率会偏离长时间传输后偶发错帧概率很高再看线路质量和接地。校验位检测的是串行链路上的干扰如果线太长、没有接地回路偶发错误很难根治最后确认两端校验模式是否真正一致。特别注意大小写、伪配置等细节有些代码里写了USART_Parity_Even但结构体没有被真正调用或者被后边的代码覆盖了5.4 一个实际项目的调参经验我之前做过一个通过RS232和工业触摸屏通信的项目触摸屏协议配置界面里写的是“偶校验”我还特意确认过代码里也配置成了USART_Parity_Even。可现场就是通信异常。后来用串口助手单独接触摸屏模拟单片机发送数据触发响应再用逻辑分析仪抓触摸屏回发的数据一帧一帧地分析。最后发现触摸屏虽然界面显示偶校验但实际发出的数据帧里根本没有校验位也就是说它界面配置和实际行为不一致。把单片机这边的校验位关掉改成无校验后通信马上正常。这个经历给我的启发是不要完全相信对端设备的配置显示一定要从实际波形或者实际收发的帧结构里验证。仪器不会说谎。写在最后的几个实操建议如果需要我在文章里直接给出一个最通用的模板那我会建议先把8N1跑通再把校验位打开用串口助手和示波器双重验证最后再挂对端设备联调。这个顺序能帮你把“配置问题”“硬件问题”“对端兼容问题“这几个层面逐个隔离。在实际项目中我个人做串口链路时会坚持几个习惯一是所有通信参数集中定义成宏不要散落在代码里二是初始化完成后回读USART_InitTypeDef里的配置确认生效三是在量产前用校验位CRC24配合做一次长期老化通信测试。串口通信看起来简单但真正到了工业现场帧错误、乱码、偶发断线都是靠这些底层细节兜住的。奇偶校验只是其中一环把这一环吃透后面再接Modbus或者其他协议栈就会顺手很多。本文还有配套的精品资源点击获取