简介这份资源面向STM32嵌入式开发初学者与需要实现可靠串口通信的工程师聚焦STM32F103C8T6的USART2带奇偶校验配置。内容围绕时钟使能、GPIO复用映射、USART控制寄存器中PCE与PS位设置、中断与DMA可选方案以及串口启动流程展开帮助读者理解奇偶校验在数据链路层的实际作用并可直接移植到传感器、调试工具等外设通信场景。压缩包共78个文件约308KB以34个h头文件与33个c源文件为主体涵盖标准外设库、CORE内核文件、SYSTEM与HARDWARE驱动模块、USER用户代码及Keil工程与hex目标文件结构完整便于对照编译。目前已有2445人学习下载。通过该工程读者可掌握串口2奇偶校验的寄存器级配置方法获得可直接烧录验证的完整代码框架并借助README与工程配置快速复现实验为后续多串口通信与协议解析打下基础。1. 串口通信里那个被忽略的校验位STM32F103 串口2奇偶校验到底怎么配很多人调 STM32 串口USART1 收发正常就以为大功告成等到把传感器或工控设备接到 USART2 上突然发现收到的数据偶尔错一个字节或者干脆整帧乱掉。回头翻手册才发现对方设备用的是 9 位数据加奇偶校验而自己初始化时只写了 8 位无校验。STM32F103 的 USART2 挂在 APB1 总线上和 USART1 的时钟来源不同配置奇偶校验时涉及数据位长度、校验位使能、停止位组合这几个寄存器字段的联动稍不留神就会掉进“校验位占了数据位”的坑里。这份资源围绕 USART2 的奇偶校验配置展开从寄存器层面讲清 PCE、PS、M 这几个位的配合关系再落到标准库和 HAL 库两套代码上适合正在做多串口通信、需要和 Modbus 或带校验的工业设备对接的嵌入式开发者。2. 奇偶校验的寄存器逻辑为什么 9 位数据位是个绕不开的坎2.1 校验位不是“额外加一位”而是从数据位里借STM32F103 的 USART 模块里数据位长度由 USART_CR1 寄存器的 M 位控制。M0 时数据位是 8 位M1 时是 9 位。奇偶校验使能位 PCE 在 USART_CR1 的 bit10校验选择位 PS 在 bit9PS0 选偶校验PS1 选奇校验。关键点在于当 PCE1 时校验位会占用数据帧里的最高位。也就是说如果你设了 M08 位数据又开了 PCE实际有效数据只有 7 位第 8 位被校验位吃掉了。很多人在这一步翻车写进去的数据明明是对的读出来却少了最高位就是因为没意识到校验位是从数据位里“借”的。所以正确做法是需要 8 位数据加奇偶校验时M 必须设成 1也就是 9 位数据位。这样 9 位里低 8 位是有效数据最高位是校验位。接收端同样按 9 位读取低 8 位就是真实数据。常见做法是在初始化结构体里把 WordLength 设成 UART_WORDLENGTH_9BParity 设成 UART_PARITY_EVEN 或 UART_PARITY_ODD。标准库那边对应的是 USART_WordLength_9b 和 USART_Parity_Even / USART_Parity_Odd。2.2 USART2 的时钟使能和引脚复用别搞混USART2 的时钟来自 APB1使能函数是 RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE)。USART1 用的是 APB2这是两个不同的总线写错了一边收不到数据一边还查不出原因。引脚方面USART2 默认映射在 PA2TX和 PA3RX。如果要用重映射得先开 AFIO 时钟再调 GPIO_PinRemapConfig。我一般会在初始化最开始就把 GPIO 和 USART 的时钟都打开避免后面调试时因为时钟没开导致寄存器写不进去。下面是一段标准库的 USART2 初始化代码带偶校验void USART2_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; // 使能 GPIOA 和 USART2 时钟注意 USART2 在 APB1 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); // PA2 复用推挽输出作为 TX 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); // PA3 浮空输入作为 RX GPIO_InitStructure.GPIO_Pin GPIO_Pin_3; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, GPIO_InitStructure); // 串口参数96009 位数据偶校验1 位停止位 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_9b 和 USART_Parity_Even 是绑定的不能拆开用。波特率 9600 只是示例实际要跟对端设备一致。停止位一般用 1 位如果对方是 2 位停止位这里也要改。2.3 发送和接收时的数据宽度处理初始化成 9 位之后发送函数要传 9 位数据。标准库的 USART_SendData 第二个参数是 uint16_t低 9 位有效。如果你只传 8 位数据最高位是 0校验位由硬件自动算这没问题。但接收时 USART_ReceiveData 返回的也是 9 位你得手动取低 8 位uint8_t ch; if (USART_GetFlagStatus(USART2, USART_FLAG_RXNE) ! RESET) { // 取低 8 位丢弃校验位 ch (uint8_t)(USART_ReceiveData(USART2) 0x00FF); }这里有个细节如果你不取低 8 位直接把返回值当 8 位用编译器会隐式截断结果可能碰巧对但代码可读性差换个人维护容易懵。我习惯显式写 0x00FF一眼就能看出是在丢校验位。3. HAL 库下的 USART2 奇偶校验配置CubeMX 生成之后还要改什么3.1 CubeMX 里选 9 位和校验位生成的代码长什么样用 CubeMX 配 USART2 时Parameter Settings 里 Word Length 选 9 BitsParity 选 Even 或 Odd。生成之后 MX_USART2_UART_Init 函数里会看到 huart2.Init.WordLength UART_WORDLENGTH_9B; 和 huart2.Init.Parity UART_PARITY_EVEN;。但 CubeMX 生成的代码有个地方要注意它默认把 huart2.Init.Parity 和 huart2.Init.WordLength 分开写如果你手动改代码时只改了 Parity 没改 WordLength编译能过运行起来数据就错。我一般会在初始化之后加一句断言或者打印确认这两个值匹配。HAL 库的收发函数和标准库不同发送用 HAL_UART_Transmit接收用 HAL_UART_Receive数据指针是 uint8_t*。但底层寄存器还是 9 位的HAL 库帮你做了截断。发送时你传 8 位缓冲区HAL 会把每个字节放到低 8 位校验位硬件算。接收时 HAL 把 9 位数据的低 8 位存进缓冲区校验位丢掉。// HAL 库发送pData 是 uint8_t 缓冲区 HAL_UART_Transmit(huart2, pData, len, 1000); // HAL 库接收同样用 uint8_t 缓冲区 HAL_UART_Receive(huart2, pData, len, 1000);这里有个坑HAL_UART_Receive 是阻塞式的超时参数单位是毫秒。如果你在中断里调用它超时设太长会卡住整个中断。我一般用 HAL_UART_Receive_IT 或者直接操作寄存器避免在中断上下文里用阻塞收发。3.2 中断接收时校验错误标志怎么处理开了奇偶校验之后接收端如果校验失败硬件会置位 USART_SR 的 PE 位Parity Error。标准库用 USART_GetFlagStatus(USART2, USART_FLAG_PE) 查HAL 库在中断回调里通过 huart2.ErrorCode 判断。很多人不处理 PE 标志结果校验错的那一帧数据被当成正常数据读走后面解析全乱。正确做法是在接收中断里先查 PE如果置位了读一次 USART_ReceiveData 把脏数据清掉然后直接返回不往缓冲区里存。void USART2_IRQHandler(void) { if (USART_GetITStatus(USART2, USART_IT_RXNE) ! RESET) { // 先查校验错误 if (USART_GetFlagStatus(USART2, USART_FLAG_PE) ! RESET) { // 读一次清标志丢弃这帧 (void)USART_ReceiveData(USART2); return; } uint8_t ch (uint8_t)(USART_ReceiveData(USART2) 0x00FF); // 存进环形缓冲区 RingBuffer_Put(rxBuf, ch); } }这段代码里先查 PE 再读数据顺序不能反。如果先读数据再查 PE标志可能已经被清了查不到。另外 PE 标志的清除方式是“先读 SR 再读 DR”标准库的 USART_GetFlagStatus 和 USART_ReceiveData 组合起来正好满足这个时序。3.3 波特率误差对校验的影响奇偶校验只能检错不能纠错如果波特率误差太大采样点偏移收到的数据位本身就错了校验位再对也白搭。STM32F103 在 9600 波特率下误差很小但跑到 115200 以上时如果 APB1 时钟不是标准值比如 36MHz 而不是 36.864MHz误差可能超过 2%这时候校验失败率会明显上升。我一般会在初始化之后用示波器或者逻辑分析仪抓一下 TX 波形量一个位宽算实际波特率。没有仪器的话可以发一个已知字节用另一块板子收看误码率。如果误码率高先降波特率试确认是时钟问题还是校验配置问题。4. 避坑与排查USART2 奇偶校验最常见的五个翻车现场4.1 现象数据最高位总是 0低 7 位正常原因WordLength 设成了 8 位Parity 开了。校验位占了第 8 位有效数据只剩 7 位最高位被硬件当校验位处理了。解决把 WordLength 改成 9 位。标准库用 USART_WordLength_9bHAL 用 UART_WORDLENGTH_9B。改完重新编译烧录用逻辑分析仪看波形数据位应该是 9 个。4.2 现象能发不能收或者能收不能发原因GPIO 模式配错。TX 要配成复用推挽输出RX 要配成浮空输入或上拉输入。有人把 TX 配成普通推挽输出结果波形出不来有人把 RX 配成推挽输出直接把对端 TX 拉死。解决检查 GPIO_InitStructure.GPIO_Mode。TX 用 GPIO_Mode_AF_PPRX 用 GPIO_Mode_IN_FLOATING。如果是 HAL 库CubeMX 一般会配好但手动改代码时容易漏。4.3 现象校验错误标志 PE 一直置位读不到正确数据原因对端设备的校验方式和你配的不一致。你配偶校验对方是奇校验或者对方根本没开校验。还有一种可能是停止位不匹配对方 2 位停止位你配 1 位帧边界对不上。解决拿示波器抓一帧完整波形数一下数据位和校验位。或者用串口调试助手把校验方式逐个试。确认一致后PE 标志应该只在真正出错时置位。4.4 现象中断里读数据偶尔丢帧原因中断服务函数里处理时间太长或者没清中断标志。USART2 的中断标志清除方式和 USART1 一样读 SR 再读 DR 自动清 RXNE。但如果你在中断里调用了 HAL_UART_Receive 这种阻塞函数会把中断卡住。解决中断里只做最少的操作——读数据、存缓冲区、清标志。解析和业务逻辑放到主循环。如果用了 HAL 库用 HAL_UART_Receive_IT 注册回调在回调里存数据。4.5 现象换到 USART1 正常USART2 就是不行原因时钟总线搞混了。USART1 在 APB2USART2 在 APB1。有人复制 USART1 的代码改端口号忘了把 RCC_APB2PeriphClockCmd 改成 RCC_APB1PeriphClockCmd结果 USART2 根本没时钟寄存器写不进去。解决检查时钟使能函数。USART2 用 RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE)。另外注意 APB1 的最高频率是 36MHz如果你系统时钟超了USART2 的波特率会算错。5. 用示波器验证校验位一个可以复用的排查习惯校验位这东西代码里配没配对光看寄存器值有时候不放心。我习惯在第一次调通之后用逻辑分析仪抓一帧完整波形手动数一下位。具体做法是让 USART2 发一个已知字节比如 0x55二进制 01010101偶校验时校验位应该是 0因为 1 的个数是 4偶数。抓到的波形应该是起始位 0然后 8 位数据 01010101然后校验位 0然后停止位 1。如果校验位不对比如发 0x55 抓出来校验位是 1那说明 PS 位配反了偶校验配成了奇校验。这时候改 USART_Parity_Odd 或者 UART_PARITY_ODD 就行。下面这个表格是我常用的几个测试字节和对应的偶校验位抓波形时可以直接对照发送字节二进制1 的个数偶校验位奇校验位0x00000000000010x55010101014010xAA101010104010xFF111111118010x01000000011100x0300000011201抓波形的时候触发条件设成下降沿起始位时基调到能看清一个位宽。9600 波特率下一个位宽约 104 微秒时基设 50 微秒每格比较合适。115200 波特率下一个位宽约 8.68 微秒时基要设到 2 微秒每格。还有一点如果你用的是 USB 转串口模块接 USART2模块本身可能不支持 9 位数据。很多 CH340 和 CP2102 的默认配置是 8 位无校验你发 9 位数据过去它可能把校验位当停止位处理导致接收端看到的数据错位。这种情况我一般直接用另一块 STM32 板子对收绕过 USB 转串口芯片。从那以后我每次配 USART2 奇偶校验都会先抓一帧波形确认校验位再跑数据。这个习惯帮我省了不少来回改代码的时间。希望帮到你。本文还有配套的精品资源点击获取