STM32F103串口2奇偶校验配置与避坑指南

发布时间:2026/10/17 17:01:13
STM32F103串口2奇偶校验配置与避坑指南
简介这份资源面向STM32嵌入式开发初学者与需要调试串口通信的工程师聚焦STM32F103C8T6上USART2带奇偶校验的完整实现。内容涵盖时钟使能、GPIO复用配置、USART控制寄存器中PCE与PS位的奇偶校验设置、中断与DMA可选方案以及基于标准外设库的初始化流程可帮助读者理解串口通信底层寄存器操作与工程组织方式。压缩包共78个文件以34个h头文件与33个c源文件为主另含Keil工程文件、启动汇编、hex目标文件及编译批处理脚本整体约308KB目录按HARDWARE、SYSTEM、CORE、STM32F10x_FWLib、USER等模块划分结构清晰便于对照学习。目前已有2445人学习下载适合作为串口校验配置的参考范例与工程模板。1. 串口通信里的“隐形守门员”为什么这个 STM32F103 串口2奇偶校验项目值得拆一遍如果你调过 STM32 的串口大概率经历过这种场景PC 端发来一串数据MCU 收到的字节偶尔错一位程序照单全收后面解析全乱套。多数教程只讲波特率、数据位、停止位唯独把奇偶校验一笔带过。可一旦你对接的是电表、工控板、老式传感器这类设备对方协议里白纸黑字写着“偶校验、8 数据位、1 停止位”你不开校验通信就是玄学——时好时坏抓包也看不出所以然。这份 STM32F103 串口2带奇偶校验的工程解决的正是这个被很多人跳过的环节它把 USART2 配置成带奇偶校验的收发通道让你能直接对着协议要求把校验位打开而不是靠软件层自己算。适合正在做多串口项目、需要和外部设备硬碰硬对接的嵌入式开发者尤其是那些已经跑通串口1、准备把第二个串口也用起来的同学。2. 工程结构与串口2初始化从时钟到 GPIO 的完整配置链路2.1 目录里都有什么先看骨架再动手拿到一份 STM32F103 的串口工程别急着编译。先扫一眼目录判断它是标准库还是 HAL 库这决定了你后面改代码的方式。这个项目走的是标准外设库路线结构大致如下Project/ ├── CMSIS/ # 内核支持文件启动汇编在这里 ├── STM32F10x_StdPeriph_Driver/ │ ├── inc/ # 标准库头文件 │ └── src/ # 标准库源文件usart.c 就在这 ├── User/ │ ├── main.c # 主流程初始化 收发测试 │ ├── stm32f10x_it.c # 中断服务函数串口中断入口 │ └── usart2.c / usart2.h # 串口2的配置与收发封装 └── Project.uvprojx # Keil 工程文件关键点在于usart2.c和stm32f10x_it.c这两个文件。前者负责把 USART2 的寄存器配好后者决定收到数据后怎么处理。很多人移植失败不是配置写错而是中断函数名对不上——标准库里 USART2 的中断入口固定是USART2_IRQHandler你写成别的名字编译不报错但中断永远不进程序就卡在死等里。2.2 时钟、GPIO 与 USART2 的初始化顺序STM32F103 的 USART2 挂在 APB1 总线上这点和 USART1 挂在 APB2 上不同初学者最容易在这里翻车。初始化必须按“先开时钟、再配引脚、最后配串口”的顺序来顺序错了寄存器写不进去。void USART2_Init(u32 bound) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; // 1. 开时钟USART2 在 APB1GPIOA 在 APB2 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); // 2. 配置 TX(PA2) 为复用推挽输出 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); // 3. 配置 RX(PA3) 为浮空输入 GPIO_InitStructure.GPIO_Pin GPIO_Pin_3; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, GPIO_InitStructure); // 4. 串口参数重点在 Parity 这一行 USART_InitStructure.USART_BaudRate bound; USART_InitStructure.USART_WordLength USART_WordLength_8b; 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); // 5. 开接收中断并启动串口 USART_ITConfig(USART2, USART_IT_RXNE, ENABLE); USART_Cmd(USART2, ENABLE); }逐段说明第 1 步的时钟使能是整段代码的地基APB1 和 APB2 分属两条总线漏掉任何一个后面所有配置都是往空气里写。第 2、3 步的引脚模式不能互换TX 必须是复用推挽RX 必须是浮空或上拉输入配反了表现为“能发不能收”或“能收不能发”。第 4 步是这份工程的核心USART_Parity_Even把校验位打开此时数据帧变成“1 起始位 8 数据位 1 校验位 1 停止位”共 11 位。这里有个隐藏坑开了校验后USART_WordLength_8b指的是数据位仍是 8 位校验位是额外附加的不要误以为要改成 9 位。第 5 步的USART_IT_RXNE是接收非空中断每收到一个完整帧就触发一次。2.3 波特率与校验位的关系别只改一个波特率由bound参数传入常见值 9600、115200。开了奇偶校验后每帧多了一位实际有效数据吞吐会略降但波特率本身不用改——波特率描述的是位传输速率不是帧速率。真正要同步改的是 PC 端串口助手数据位选 8、校验选 Even、停止位选 1三处必须和 MCU 完全一致。我见过太多人只改了 MCU 的校验位PC 端还停在 None结果收到的全是乱码然后回头怀疑代码。校验位不匹配时STM32 会在状态寄存器里置 PE 标志如果你没读它错误就悄悄溜过去了。3. 收发逻辑与中断处理校验位打开后数据怎么进出3.1 发送一个字节等标志位再写数据发送逻辑比接收简单但也不能直接往 DR 寄存器里怼。必须等 TXE发送数据寄存器空标志置位才能写下一个字节否则会覆盖上一个还没发完的数据。void USART2_SendByte(u8 data) { // 等待发送数据寄存器空 while (USART_GetFlagStatus(USART2, USART_FLAG_TXE) RESET); // 写入数据寄存器硬件自动附加校验位 USART_SendData(USART2, data); } void USART2_SendString(u8 *buf, u16 len) { u16 i; for (i 0; i len; i) { USART2_SendByte(buf[i]); } // 等最后一个字节真正发完 while (USART_GetFlagStatus(USART2, USART_FLAG_TC) RESET); }USART_FLAG_TXE表示数据寄存器已空可以接受新数据USART_FLAG_TC表示整个帧含校验位和停止位已经移位完成。发字符串时循环里等 TXE循环外等 TC这个搭配能保证最后一个字节不会因为函数返回而被截断。校验位是硬件自动计算的你写进 DR 的始终是 8 位有效数据不需要手动算奇偶。3.2 接收中断读 DR 之前先看错误标志接收端是这份工程最需要小心的地方。开了校验后如果传输过程中校验失败硬件会置位 PEParity Error。标准库的USART_GetITStatus只判断 RXNE不会自动帮你清 PE所以正确做法是先读 SR 再读 DR用这个动作序列把错误标志一起清掉。void USART2_IRQHandler(void) { u8 recv; if (USART_GetITStatus(USART2, USART_IT_RXNE) ! RESET) { // 先读状态寄存器再读数据寄存器可清除 PE/FE/NE 等错误标志 recv USART_ReceiveData(USART2); // 这里可以把 recv 存入环形缓冲区交给主循环处理 RingBuffer_Put(uart2_rx_buf, recv); } }USART_ReceiveData内部就是读 DR 寄存器而读 DR 之前硬件要求先读 SR标准库函数已经帮你做了这个动作所以直接调用即可。但如果你用的是寄存器操作务必手动(void)USART2-SR; (void)USART2-DR;这个序列。中断里只做入队不做解析这是嵌入式收发的通用习惯——中断要短解析放主循环否则高波特率下容易丢字节。3.3 主循环里的数据解析与回环测试主循环负责从环形缓冲区取数据、判断帧头帧尾、执行命令。一个最小可用的回环测试如下int main(void) { u8 data; USART2_Init(9600); // 9600, 8, Even, 1 RingBuffer_Init(uart2_rx_buf); while (1) { if (RingBuffer_Get(uart2_rx_buf, data)) { // 收到什么原样发回用于验证校验链路 USART2_SendByte(data); } } }把这段烧进去PC 端串口助手设成 9600、8、Even、1发一个字符就应该收到同一个字符。如果收到的是乱码先查 PC 端校验设置如果一个都收不到查中断函数名和 NVIC 配置。回环通了说明校验链路完整再往上叠协议解析就稳了。4. 避坑与排查串口2开校验后最容易翻车的五个点4.1 现象PC 端能发MCU 收不到任何数据原因通常是中断没进。先确认stm32f10x_it.c里写的是USART2_IRQHandler不是USART1_IRQHandler再确认 NVIC 里使能了USART2_IRQn通道并且优先级分组配置正确。标准库里 NVIC 配置容易被漏掉因为编译不会报错只是中断静默失效。解决方式是在初始化末尾补上NVIC_Init通道号选USART2_IRQn。4.2 现象收到的数据整体偏移或全是 0xFF这多半是波特率不匹配或校验位不一致。检查 PC 端和 MCU 端的波特率是否都是 9600校验位是否都是 Even。特别注意有些串口助手把“校验位”藏在高级设置里默认是 None你不手动改它就一直按无校验发MCU 按偶校验收每个字节的校验位都对不上硬件直接丢弃或置错。解决方式是两端逐项核对波特率、数据位、校验、停止位四项全对齐。4.3 现象偶发丢字节发得快就乱原因是中断里做了耗时操作或者环形缓冲区太小。中断服务函数里只应该读 DR 和入队任何printf、延时、复杂判断都会拖慢响应。9600 波特率下每字节约 1ms看似宽裕但如果你在中断里调用了阻塞函数下一个字节到来时 RXNE 还没被读走就会触发溢出错误 ORE。解决方式是把解析逻辑全部挪到主循环中断只负责搬运缓冲区至少开到 64 字节。4.4 现象开了校验后某些字节总是收错先怀疑硬件连线。TX/RX 没有交叉、地线没共、线太长没屏蔽都会让校验位在传输中翻转。校验位的作用是检错不是纠错它发现错误后硬件置 PE 标志但数据本身已经错了。如果你的应用对可靠性要求高应该在软件层再加一层 CRC 或帧校验不能只依赖串口的奇偶校验。解决方式是缩短杜邦线、共地、降低波特率测试确认是电气问题还是配置问题。4.5 现象换到串口1就正常串口2死活不通这是最典型的“总线归属”坑。USART1 在 APB2USART2/3 在 APB1时钟使能函数不同。很多人复制串口1的代码改串口2只改了USART1到USART2却忘了把RCC_APB2PeriphClockCmd改成RCC_APB1PeriphClockCmd。时钟没开USART2 的寄存器全是复位值配置写进去也不生效。解决方式是逐行核对时钟使能那一句APB1 和 APB2 不能混。5. 二次开发改造把校验串口接进你自己的协议栈5.1 从回环测试到协议解析的改造路径回环跑通只是起点。真实项目里你收到的不再是单个字符而是带帧头、长度、命令字、校验和的完整数据包。改造思路是保留USART2_Init和中断入队逻辑不动把主循环里的“原样发回”替换成状态机解析。常见做法是定义一个帧结构体用状态机逐字节推进等帧头、收长度、收数据、验校验和、执行命令。这样串口2的校验能力就成了你协议栈的物理层保障上层不用再关心位错误。typedef struct { u8 head; // 帧头比如 0xAA u8 cmd; // 命令字 u8 len; // 数据长度 u8 data[32]; // 载荷 u8 checksum; // 累加和或 CRC } Uart2Frame; // 状态机解析示例在主循环中调用 void Uart2_ParseByte(u8 byte) { static u8 state 0, idx 0; static Uart2Frame frame; switch (state) { case 0: // 等帧头 if (byte 0xAA) { frame.head byte; state 1; } break; case 1: // 收命令字 frame.cmd byte; state 2; break; case 2: // 收长度 frame.len byte; idx 0; state (byte 0) ? 3 : 4; break; case 3: // 收载荷 frame.data[idx] byte; if (idx frame.len) state 4; break; case 4: // 收校验和并校验 frame.checksum byte; if (Uart2_CheckSum(frame)) { Uart2_Execute(frame); } state 0; break; default: state 0; break; } }这段状态机的关键在于它把“物理层校验”和“协议层校验”分开了。串口的奇偶校验负责发现位级错误状态机里的 checksum 负责发现帧级错误两层叠加可靠性比单开一层高得多。改造时注意data数组大小要按你实际协议的最大载荷来定别照抄 32否则长帧会溢出。5.2 参数怎么改波特率、校验模式与缓冲区如果你对接的设备要求的是奇校验而不是偶校验只改一行USART_InitStructure.USART_Parity USART_Parity_Odd;。如果要改成无校验设为USART_Parity_No同时 PC 端也要同步改。波特率改USART2_Init(115200)即可但高波特率下建议把中断优先级调高、缓冲区加大否则容易丢包。环形缓冲区的大小我一般按“最大帧长 × 4”来定给突发流量留余量。参数常见取值改动位置注意事项波特率9600 / 115200USART2_Init入参两端必须一致校验模式Even / Odd / NoUSART_Parity改完同步 PC 端数据位8bUSART_WordLength开校验后仍填 8b停止位1USART_StopBits老设备可能要求 2缓冲区64 / 128 字节RingBuffer定义按最大帧长放大5.3 验证方法用示波器和串口助手交叉确认改完代码别只信串口助手。我习惯先用示波器抓 TX 引脚波形数一下每帧是不是 11 位1 起始 8 数据 1 校验 1 停止校验位的高低电平是否符合偶校验规则。波形对了再开串口助手看数据。如果波形对但助手收错问题在 PC 端设置如果波形就不对问题在 MCU 配置。这个交叉验证能帮你快速定位是硬件层还是软件层的问题比反复烧录快得多。从那以后我每次开新串口都强制走一遍“时钟归属核对 → 引脚模式核对 → 两端参数核对 → 回环测试 → 波形确认”的流程少一步都可能在后面对接设备时翻车。希望帮到你。本文还有配套的精品资源点击获取