资讯动态

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

发布时间:2026/10/17 10:04:44 来源:尧图企业网站定制
简介这份资源面向STM32嵌入式开发初学者与需要实现可靠串口通信的工程师聚焦STM32F103C8T6的USART2配置解决带奇偶校验的串口通信问题。内容涵盖时钟使能、GPIO复用、USART控制寄存器中PCE与PS位的设置以及中断和DMA的可选方案帮助读者理解从底层寄存器到HAL库封装的完整配置链路。压缩包共78个文件以34个h头文件和33个c源文件为主另含Keil工程文件、启动汇编、HEX目标文件及批处理脚本整体约308KB目录按HARDWARE、SYSTEM、CORE、STM32F10x_FWLib、USER等模块划分结构清晰便于按层查阅。目前已有2445人学习下载。通过该工程读者可对照实际代码掌握串口2奇偶校验的初始化流程与调试方法并借助标准外设库与用户代码快速移植到自己的项目中。1. 串口通信的隐形防线为什么STM32F103的串口2需要奇偶校验很多刚接触STM32F103的朋友第一次调串口通信时都会觉得只要波特率对上了、引脚接对了数据就能稳稳地收发。直到某次在电机驱动板或者工业现场总线上发现偶尔会收到一个莫名其妙翻转了bit的字节导致整个控制逻辑跑飞才意识到通信链路里少了点什么。这个项目要解决的就是STM32F103的USART2在开启奇偶校验后如何正确配置数据位、校验位和停止位让串口通信在电磁环境复杂的场景下依然能识别出错误帧。它适合正在做RS485总线、Modbus从机、或者任何需要长距离串口传输的嵌入式开发者。串口2在F103上挂载在APB1总线和串口1的时钟来源不同配置时如果照搬串口1的代码很容易掉进坑里。2. 硬件与寄存器基础USART2的时钟、引脚和校验机制2.1 为什么串口2的配置不能直接抄串口1STM32F103的USART1挂在APB2高速总线上而USART2和USART3挂在APB1低速总线上。这个差异直接影响到波特率的计算和寄存器的写入时机。很多开发者在配置串口1时习惯用RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE)到了串口2如果忘了改成RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE)代码编译能过但串口就是没反应。更隐蔽的问题是APB1的默认时钟是36MHz而APB2是72MHz如果波特率计算时用错了总线频率实际波特率会偏差一倍通信自然失败。我一般会在初始化函数开头就把时钟使能写清楚并且把总线频率作为一个宏定义放在头文件里方便后续换芯片型号时统一修改。// 串口2时钟使能注意是APB1总线 RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); // GPIOA时钟使能USART2默认使用PA2(TX)和PA3(RX) RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 总线频率定义APB1默认为36MHz #define USART2_APB_CLOCK 36000000这里的USART2_APB_CLOCK就是后续计算波特率分频值的基准。如果系统时钟做了超频或者降频这个值必须同步更新否则波特率误差会累积到无法通信的程度。2.2 奇偶校验在数据帧里的位置串口的数据帧格式是起始位 数据位 校验位 停止位。奇偶校验位紧跟在数据位之后用来标识数据位中1的个数是奇数还是偶数。STM32的USART模块支持偶校验、奇校验和无校验三种模式通过USART_InitStructure.USART_Parity来设置。关键点在于开启校验位后数据位的长度会自动从8位变成7位或者9位具体取决于USART_WordLength的设置。很多人在CubeMX里勾选了奇偶校验但没注意数据位长度变成了9位结果上位机收到的数据总是多一个字节或者少一个字节。正确的做法是如果要用8位数据加1位校验USART_WordLength应该设为USART_WordLength_9b这样STM32会把校验位算在9位里实际有效数据还是8位。这个细节在参考手册的USART章节里有明确说明但容易被忽略。USART_InitStructure.USART_BaudRate 9600; USART_InitStructure.USART_WordLength USART_WordLength_9b; // 8位数据1位校验 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_WordLength_9b配合USART_Parity_Even实际传输的是8位数据加1位偶校验位。如果改成USART_Parity_Odd就是奇校验。如果不需要校验USART_WordLength要改回USART_WordLength_8bUSART_Parity设为USART_Parity_No。2.3 GPIO和中断的配套设置USART2的TX和RX引脚需要配置成复用推挽输出和浮空输入。TX用GPIO_Mode_AF_PPRX用GPIO_Mode_IN_FLOATING。如果开了接收中断还需要配置NVIC注意USART2的中断向量是USART2_IRQn优先级分组要和系统里其他中断协调好。GPIO_InitStructure.GPIO_Pin GPIO_Pin_2; // TX GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_Init(GPIOA, GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin GPIO_Pin_3; // RX GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, GPIO_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_IT_RXNE标志否则会反复进中断。如果开了校验错误中断还要处理USART_IT_PE这个在下一章会详细讲。3. 完整代码实现从初始化到收发校验的闭环3.1 初始化函数的完整写法把前面的时钟、GPIO、USART参数和中断配置串起来就是一个完整的USART2初始化函数。我习惯把波特率、校验方式作为参数传进来这样同一个函数可以复用在不同的通信场景里。void USART2_Init(uint32_t baudrate, uint16_t parity) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; // 1. 时钟使能 RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 2. GPIO配置 GPIO_InitStructure.GPIO_Pin GPIO_Pin_2; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; 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); // 3. USART参数配置 USART_InitStructure.USART_BaudRate baudrate; USART_InitStructure.USART_WordLength USART_WordLength_9b; USART_InitStructure.USART_StopBits USART_StopBits_1; USART_InitStructure.USART_Parity parity; USART_InitStructure.USART_HardwareFlowControl USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode USART_Mode_Rx | USART_Mode_Tx; USART_Init(USART2, USART_InitStructure); // 4. 中断配置 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); // 5. 开启接收中断和校验错误中断 USART_ITConfig(USART2, USART_IT_RXNE, ENABLE); USART_ITConfig(USART2, USART_IT_PE, ENABLE); // 6. 使能USART2 USART_Cmd(USART2, ENABLE); }这个函数里最关键的是第5步同时开了USART_IT_RXNE和USART_IT_PE。RXNE是接收数据寄存器非空中断PE是校验错误中断。开了PE之后如果收到的数据校验失败硬件会自动置位USART_FLAG_PE并且触发中断这样上层就能知道这一帧数据不可信。3.2 中断服务函数里怎么区分正常数据和错误帧中断服务函数是校验机制真正落地的地方。收到数据时先判断是不是校验错误再决定要不要把数据放进缓冲区。void USART2_IRQHandler(void) { uint8_t data; // 校验错误处理 if (USART_GetITStatus(USART2, USART_IT_PE) ! RESET) { USART_ClearITPendingBit(USART2, USART_IT_PE); // 读取DR寄存器可以清除PE标志 data USART_ReceiveData(USART2); // 这里可以记录错误计数或者丢弃该帧 parity_error_count; return; } // 正常接收处理 if (USART_GetITStatus(USART2, USART_IT_RXNE) ! RESET) { data USART_ReceiveData(USART2) 0xFF; // 只取低8位有效数据 // 把数据放入环形缓冲区 ring_buffer_write(rx_buffer, data); USART_ClearITPendingBit(USART2, USART_IT_RXNE); } }注意USART_ReceiveData返回的是16位值开启校验后高8位里可能包含校验位的信息所以要用 0xFF把有效数据截出来。parity_error_count是一个全局变量用来统计校验错误次数方便后续判断通信质量。如果校验错误频繁发生说明线路干扰严重或者波特率不匹配需要检查硬件接线和接地。3.3 发送端怎么生成正确的校验位STM32的USART硬件会自动生成校验位不需要软件计算。只要初始化时配置了USART_Parity_Even或者USART_Parity_Odd调用USART_SendData时硬件会自动把校验位填进去。但要注意发送的数据也要用9位格式否则硬件可能不会正确附加校验位。void USART2_SendByte(uint8_t data) { while (USART_GetFlagStatus(USART2, USART_FLAG_TXE) RESET); USART_SendData(USART2, (uint16_t)data); while (USART_GetFlagStatus(USART2, USART_FLAG_TC) RESET); }这里的data是8位有效数据硬件会根据配置自动在最高位后面插入校验位。如果上位机收到的数据总是多一位或者校验错误先检查上位机的串口配置是不是也设成了8位数据偶校验。两边配置必须完全一致否则校验永远对不上。4. 避坑与排查串口2奇偶校验的五个血泪教训4.1 现象串口能发不能收或者收全是乱码原因最常见的是GPIO模式配错。TX配成了普通推挽输出而不是复用推挽RX配成了推挽输出而不是浮空输入。另一个原因是APB1时钟没使能USART2根本没工作。解决用万用表量一下PA2和PA3的电压TX空闲时应该是高电平。如果TX一直是低电平检查GPIO配置和时钟使能。RX如果一直是高电平但收不到数据检查波特率是否匹配。4.2 现象开了奇偶校验后上位机收到的数据少了一位原因USART_WordLength设成了USART_WordLength_8b但开启了校验。这种情况下STM32会把校验位算在8位里实际有效数据只有7位上位机如果按8位解析就会出错。解决开启校验时USART_WordLength必须设为USART_WordLength_9b。这个9位是数据位校验位的总和不是有效数据位。4.3 现象校验错误中断频繁触发但数据看起来是对的原因波特率误差太大。APB1总线频率是36MHz如果波特率设得比较高比如115200分频系数可能不是整数导致实际波特率和理论值偏差超过3%校验位就会随机出错。解决用示波器量一下实际波特率或者换用更低的波特率比如9600。如果必须用高波特率可以考虑把系统时钟配置成72MHz然后调整APB1的分频系数让USART2的时钟更接近标准值。4.4 现象中断服务函数里清了PE标志但下次进中断还是PE原因USART_ClearITPendingBit对PE标志不一定有效PE标志的清除需要通过读USART_SR再读USART_DR这个序列来完成。解决在PE中断里先读USART_GetITStatus然后调用USART_ReceiveData读一次DR这样硬件会自动清除PE标志。不要只依赖USART_ClearITPendingBit。4.5 现象用DMA接收时校验错误的数据也被存进了缓冲区原因DMA传输不关心校验位只要RXNE置位就会搬数据。如果开了PE中断但DMA也在跑错误帧会混进正常数据里。解决要么在DMA传输完成后软件校验要么在PE中断里暂停DMA、丢弃当前帧、重新配置DMA。我一般会在PE中断里置一个标志位DMA传输完成后检查这个标志如果有错误就整帧丢弃。5. 二次开发与验证把校验机制用到自己的项目里5.1 怎么验证校验功能真的在工作最直接的方法是用串口调试助手手动发送一个校验位错误的数据帧。比如配置成偶校验然后发送一个1的个数为奇数的字节观察STM32是否触发了PE中断。可以在PE中断里翻转一个LED或者通过另一个串口打印错误计数。如果错误计数在增加说明校验机制生效了。另一个方法是用逻辑分析仪抓PA2上的波形数一下每个字节的位数。开启偶校验后每个字节应该是1个起始位8个数据位1个校验位1个停止位总共11位。如果抓到的是10位说明校验位没加上。5.2 把校验错误计数接入你的诊断系统在实际项目里我习惯把parity_error_count和总接收字节数一起上报给上位机算出一个误码率。如果误码率超过阈值就触发告警或者自动降低波特率。这个机制在RS485总线上特别有用因为总线冲突或者终端电阻不匹配都会导致误码率上升。typedef struct { uint32_t total_bytes; uint32_t parity_errors; float error_rate; } uart_diag_t; void uart_update_diag(uart_diag_t *diag) { if (diag-total_bytes 0) { diag-error_rate (float)diag-parity_errors / diag-total_bytes; } // 误码率超过1%就告警 if (diag-error_rate 0.01f) { system_alarm(ALARM_UART_QUALITY); } }这个结构体可以挂在定时任务里每秒更新一次也可以通过串口1打印出来。误码率是一个比单纯看波形更直观的指标能帮你快速判断是硬件问题还是软件配置问题。5.3 改造时要注意的兼容性问题如果你要把这套代码移植到STM32F4或者G0系列要注意USART的寄存器名字和库函数可能有变化。F4系列的库函数里USART_Init变成了USART_InitTypeDef结构体成员名基本一致但时钟使能函数变成了RCC_APB1PeriphClockCmd还是RCC_APB1ENR要看具体型号。G0系列用的是LL库或者HAL库配置方式完全不同但奇偶校验的原理是一样的数据位长度要包含校验位中断里要处理PE标志。还有一个容易翻车的地方是有些USB转串口芯片对奇偶校验的支持不完整比如CH340在某些驱动版本下会忽略校验位。如果发现STM32这边配置没问题但上位机就是收不对换一个FT232或者CP2102的模块试试。这个坑我踩过不止一次后来每次调校验功能都先用示波器确认波形再怀疑上位机。从那以后我每次配置串口校验都会先拿逻辑分析仪抓一帧波形确认位数和校验位都对得上再往下写业务代码。希望这些经验能帮到你少走一点弯路。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑