十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

STM8S003串口通信奇偶校验配置详解与实战避坑

STM8S003串口通信奇偶校验配置详解与实战避坑 简介面向 STM8S003 单片机开发者的串口通信奇偶校验示例工程包围绕串口初始化、波特率配置、数据收发与奇偶错误检测等关键环节展开适合正在学习 STM8 系列串口或需要为嵌入式项目添加校验逻辑的开发者。压缩包共 40 个文件大小约 459KB包含源码与头文件、IAR 工程文件、调试脚本、烧录文件及编译日志目录结构清晰可快速打开工程对照学习。示例对无校验、奇校验和偶校验三种模式均给出配置路径通过修改校验配置参数即可切换并可用状态标志读取函数检查奇偶错误标志验证错误检测效果帮助理解奇偶校验只能发现单比特错误的局限以及其在噪声环境下提升通信可靠性的作用。保留的原始备份文件和工程日志便于对比配置差异整体代码简洁适合作为串口模块基础模板也方便移植到其他型号。已有 1312 人学习该资源可作为 STM8S003 串口通信实验和产品开发的直接参考。 做单片机开发的朋友应该都遇到过这种情况两块板子用串口通信波特率、数据位、停止位都设成一样但收过来的数据就是偶尔出错或者干脆全是乱码。排查了半天最后发现是奇偶校验没对上。STM8S003这颗芯片在低成本项目里用得非常多它的串口模块支持完整的奇偶校验配置但很多人配置完发现校验位根本没生效或者不知道什么时候该用偶校验、什么时候该用奇偶校验。这篇文章我就以STM8S003为例把串口通信里的奇偶校验彻底讲清楚从原理到寄存器配置再到完整示例代码包括我在实际项目中踩过的坑一次性说透。先说清楚这篇文章适合谁看正在用STM8S003做项目、被串口通信乱码折磨的开发者刚入门单片机、想搞清楚校验位到底怎么回事的新手以及准备用标准外设库配置STM8串口、但不想走弯路的朋友。文章不会讲太虚的概念全部围绕实际工程中的配置和调试展开。1. 奇偶校验的本质不是加密是链路质量的探针1.1 校验位在串口帧里的位置和职责串口通信的每一帧数据标准结构是起始位1位、数据位8位或9位、校验位0位或1位、停止位1位或2位。很多人把校验位理解为“数据安全性检查”其实它没有那么强大它只能检测单比特错误连双比特错误都检测不出来。它的真实作用是在发送端根据数据位计算出一个额外的位接收端用同样的规则重新计算两边比对不一致就判定这帧数据有问题。奇校验的规则数据位中“1”的个数加上校验位本身总数必须是奇数。偶校验同理总数必须是偶数。举个例子数据字节是0x53二进制是0101 0011里面有4个1。如果是偶校验校验位就是0因为4已经是偶数不需要再加1如果是奇校验校验位就是1因为4加1等于5变成奇数。这个过程看起来简单但硬件执行时有一个关键点STM8S003的UART模块在启用校验位后校验位的值是硬件自动计算并填充的发送端和接收端都不需要软件参与计算。这正是很多人配置完没效果的原因——他们以为要自己在软件里算校验位然后塞进数据里发送其实完全不需要。你只需要配置好校验模式硬件帮你干完所有事。1.2 为什么说它适合“链路探针”而不是“数据保险”我见过很多项目需求里写着“串口数据必须奇偶校验”仿佛加了校验数据就万无一失。实际上奇偶校验能检测出的错误极其有限。如果信道噪声导致两个比特同时翻转奇偶校验完全识别不出来如果数据位顺序错乱校验也发现不了因为“1”的个数没变。那它有什么用三个场景最有价值第一检测线路接触不良导致的单比特丢帧比如杜邦线松动、焊点虚焊第二检测波特率轻微偏差导致的边界采样错误两边晶振精度不够时表现尤其明显第三作为协议层恶意篡改的低级屏障当然这个作用很弱真正要防篡改得上CRC。所以我的建议是奇偶校验够用但别神话它。项目里如果数据可靠性要求高必须再加帧头帧尾、长度字段和CRC校验奇偶校验只能作为第一道哨兵。2. STM8S003的UART硬件结构和校验相关寄存器2.1 UART1和UART3该用谁STM8S003芯片内部有两个串口模块UART1和UART3。注意没有UART2。这颗芯片引脚少UART1默认映射在PC1TX和PC2RXUART3映射在PD5TX和PD6RX但UART3的这两个引脚同时也是SWIM调试接口的引脚。这就产生了一个实际工程问题如果用了UART3你在线调试时SWIM接口就被占用了必须先把程序烧录进去再断开调试器跑非常蛋疼。所以我的项目里统一用UART1除非UART1被其他功能抢占。选UART1的另一个优势是它支持同步模式和智能卡模式虽然这颗芯片上这些功能用得少但寄存器资源更丰富后续扩展也方便。这里顺便提一句STM8S003的完整型号后缀不同Flash和RAM大小有差异但串口模块是一致的所以代码可以直接跨型号复用。2.2 CR1和CR2寄存器里校验位是怎么控制的STM8S003的串口校验配置集中在两个寄存器控制寄存器CR1和控制寄存器CR2。先看CR1的第10位PCEParity Control Enable这一位置1开启校验功能第9位PSParity Selection置0是偶校验置1是奇校验。再看CR2里面最核心的是REN接收使能和TEN发送使能以及RIEN接收中断使能、TCIEN发送完成中断使能等中断控制位。这里有一个特别容易踩坑的细节当PCE置1后数据位的长度会自动从8位变成9位——实际传输的有效数据还是8位最高位被校验位占据了。这意味着你在计算串口帧长时必须留出校验位的位置。很多人在配置后收到数据是错的就是因为他只改了校验相关的位但接收缓冲区或者协议解析仍然按8位数据去处理结果把校验位当成了数据位的一部分。UART3的寄存器名称是UART3_CR1、UART3_CR2UART1就是UART1_CR1、UART1_CR2配置逻辑一样就是前缀不同。这块我强烈建议直接看芯片参考手册RM0016的第23章寄存器位定义表列得非常清楚搜索引擎查来的配置代码十有八九是STM32的不能直接套。3. 开发环境与基础工程搭建3.1 用标准外设库还是纯寄存器STM8S003的开发方式基本有三种纯寄存器操作、意法半导体官方标准外设库SPL、以及近年推出的STM8CubeMX。官方标准外设库在STM8上最成熟网上资料最多国内绝大多数教程也基于它。CubeMX虽然能自动生成初始化代码但对这颗小芯片的支持还不完善生成的代码有时候还能把你原有的外设配置覆盖掉。我的推荐是项目初始化用标准外设库关键通信逻辑比如中断处理用寄存器操作。原因很简单STM8S003的Flash只有8KBRAM只有1KB标准外设库虽然封装得简洁但也会增加几十字节的代码在资源紧张的场合你得精打细算。核心收发逻辑用寄存器操作可以精确控制执行周期和代码体积。下面的示例代码我用标准外设库加少量寄存器操作混搭的方式写这也是实际项目里最常见的风格。3.2 时钟配置是串口波特率的地基STM8S003内部有一个16MHz的高速内部RC振荡器HSI经过8分频后作为主时钟所以默认fmaster是2MHz。如果你用外部晶振那就可以跑到16MHz甚至更高。串口波特率的计算直接依赖fmaster波特率寄存器BRR1和BRR2的数值由fmaster除以波特率再除以16得到。注意BRR2的高4位放的是分频系数的高4位BRR1放低8位这个拆分顺序非常反直觉我头一回配置时就在这里栽过跟头。举个实际计算例子fmaster16MHz目标波特率9600。16000000除以9600再除以16约等于104。把104换算成十六进制是0x68高4位是6低8位是0x08。所以BRR2写入0x06BRR1写入0x08。如果是2MHz时钟算出来约等于13BRR2写入0x00BRR1写入0x0D。这个计算逻辑不难但容错率低建议写成宏定义不要每次手算。后面我会在示例代码里给出完整的计算过程。4. 完整示例STM8S003串口偶校验收发4.1 初始化代码注意校验位的开启顺序下面这段代码是UART1的初始化波特率96008位数据偶校验1位停止位。在标准外设库里的配置顺序有讲究必须先设置UART1_CR1的PCE和PS位再初始化波特率最后使能发送和接收。如果先使能TEN再改PCE此时发送器已经开始工作校验位可能来不及配置导致第一帧数据异常。void UART1_Init(void) { // 开启UART1时钟 CLK_PeripheralClockConfig(CLK_PERIPHERAL_UART1, ENABLE); // 配置发送和接收引脚为复用推挽输出和浮空输入 GPIO_ExternalPullUpConfig(GPIOC, GPIO_PIN_2, ENABLE); // RX上拉 GPIO_Init(GPIOC, GPIO_PIN_1, GPIO_MODE_OUT_PP_HIGH_FAST); // TX // 先关闭UART再配置 UART1_Cmd(DISABLE); // 配置校验位偶校验使能校验功能 UART1_ParityConfig(UART1_PARITY_PARITY_EVEN); // 配置数据长度8位数据校验位硬件会自动处理 UART1_WordLengthConfig(UART1_WORDLENGTH_8D); // 波特率配置16MHz时钟9600 // 9600波特率分频系数 16000000 / 9600 / 16 104 0x0068 UART1_Init(9600, UART1_WORDLENGTH_8D, UART1_STOPBITS_1, UART1_PARITY_PARITY_EVEN, UART1_SYNCMODE_CLOCK_DISABLE, UART1_MODE_TXRX_ENABLE); // 使能接收中断 UART1_ITConfig(UART1_IT_RXNE_OR, ENABLE); // 使能UART UART1_Cmd(ENABLE); }这里要特别说明UART1_Init这个函数它内部会重新配置一次数据长度和停止位所以我在调用前先UART1_ParityConfig再调用UART1_Init确保校验位的配置不被覆盖。这是我从一个复杂项目里学到的教训标准外设库的UART1_Init函数内部会按照传入的参数统一设置CR1、CR2、BRR1、BRR2如果你先调用UART1_Init再调ParityConfig逻辑上也能工作但有些库版本在Init时会把PCE位清零导致你后面设置的校验位不生效。稳妥起见按代码里的顺序来。4.2 发送函数硬件自动算校验位软件只负责数据字节发送函数可以写得很简单因为校验位的计算完全由硬件完成。你只需要把数据字节写入发送数据寄存器UART1_DR等待发送完成标志TC置位就可以继续发下一个字节。void UART1_SendByte(uint8_t data) { // 等待发送数据寄存器为空 while (UART1_GetFlagStatus(UART1_FLAG_TXE) RESET); // 写入数据寄存器硬件自动计算校验位 UART1_SendData8(data); // 等待发送完成 while (UART1_GetFlagStatus(UART1_FLAG_TC) RESET); }注意一个细节UART1_FLAG_TC和UART1_FLAG_TXE是两个不同的标志。TXE是发送数据寄存器空表示数据已经移入移位寄存器TC是发送完成表示移位寄存器里的数据已经完整发送到线路上。如果只等TXE就直接发下一个字节中间只隔了一个移位周期在低波特率下问题不大但高速时会丢数据。我见过有人在9600波特率下一直输出乱码最后发现就是只等了TXE。如果你要用奇校验把初始化里的UART1_PARITY_PARITY_EVEN改成UART1_PARITY_PARITY_ODD就行发送和接收代码都不用动。硬件会根据CR1的PS位自动切换计算规则这是STM8串口设计非常方便的地方。4.3 中断接收校验错误标志必须手动清除否则死循环接收端是考验功力的地方尤其是开了中断接收后校验错误标志的处理直接决定系统稳不稳定。下面是UART1接收中断的服务函数我用的是IAR环境下定义的ISR函数名不同编译器可能有差异但处理逻辑是通用的。INTERRUPT_HANDLER(UART1_RX_IRQHandler, 18) { uint8_t data; // 检查接收数据寄存器非空 if (UART1_GetITStatus(UART1_IT_RXNE_OR) ! RESET) { // 必须先读SR再读DR才能清除RXNE标志 // UART1_ReceiveData8内部会依次读SR和DR data UART1_ReceiveData8(); // 检查校验错误 if (UART1_GetFlagStatus(UART1_FLAG_PE) ! RESET) { // 校验错误清除PE标志 UART1_ClearFlag(UART1_FLAG_PE); // 记录错误计数不处理数据 g_parity_error_count; return; } // 无错误数据入队 g_rx_buffer[g_rx_write_index] data; if (g_rx_write_index RX_BUFFER_SIZE) { g_rx_write_index 0; } } }这段代码里有几个关键点每个都是实际开发中容易踩的坑。校验错误标志PE不是自动清除的。你在中断里光读数据没用如果不清除PE标志它像狗皮膏药一样一直粘在那后面的帧全部会被误判为错误帧。清除PE标志的正确姿势是先读UART1_SR寄存器再读UART1_DR寄存器然后对PE位写0。标准外设库的UART1_ClearFlag函数就是这么干的你可以直接调。一定要“先读SR再读DR”。这是答STM8串口最经典的坑之一。UART1_GetITStatus内部会读SR寄存器UART1_ReceiveData8内部也会先读SR再读DR但如果你不调用这两个API而是直接用寄存器操作很容易漏读SR导致RXNE标志清不掉中断会一直触发系统看起来就像死机了。我调试过很多“单片机卡死在串口中断”的案例八成都是这个原因。校验错误标志在中断里检查的位置要在读取数据之后。因为PE标志的状态要和当前这个数据帧绑定如果你在读数据之前就检查PE读到的可能是上一帧遗留的错误状态。这种时序问题在低波特率下不明显波特率提高到115200以后第一个错误帧就可能让整个逻辑崩溃。4.4 双向通信主机轮询从机中断单工通信容易双工通信就要考虑双方的处理策略。我在STM8S003上做的典型架构是主机比如PC或另一个单片机以固定周期发送查询命令从机STM8S003在中断里接收命令、解析、执行、然后通过轮询方式发回响应数据。void main(void) { // 初始化系统时钟和GPIO CLK_Config(); GPIO_Config(); // 初始化UART1 UART1_Init(); // 开启全局中断 enableInterrupts(); while (1) { // 主循环里处理解析后的命令 if (g_cmd_ready_flag 1) { ProcessCommand(); g_cmd_ready_flag 0; } // 用空闲时间做其他事情比如ADC采样 ADC_SampleAndProcess(); } }中断里只收数据、置标志主循环里做命令解析和执行这是嵌入式系统里“中断快进快出”的基本原则。因为中断服务函数执行时间过长会阻塞其他中断甚至导致UART自身溢出——接收数据时如果上一字节还没读走下一字节就到了溢出标志ORE会被置位然后这一帧就丢了。5. 实测中遇到的几个坑和排查思路5.1 问题一明明开了偶校验收到的数据却是奇校验的效果这个现象很迷惑人我头一回遇到时排查了整整一个下午。现象描述A板和B板都配置成偶校验A发0x53B收到的数据校验结果却对不上用调试助手看原始字节又好像是正常的。最后定位到原因发送端A板用的UART1初始化里UART1_ParityConfig被放在了UART1_Init之后调用。也就是说UART1_Init把CR1寄存器初始化了一遍PCE位为0然后后面的ParityConfig虽然把PCE置1了但UART1_Init内部又对CR1执行了写操作把刚才设置的PCE位覆盖了。用调试器单步执行在初始化完成后查看UART1_CR1寄存器的值发现PCE位确实是0真相大白。所以遇到校验不生效的问题先别怀疑硬件第一件事是确认寄存器实际值。在IAR或STVD里打断点查看UART1_CR1寄存器的PCE位和PS位如果和你预期不一致说明初始化顺序有问题。这是最快的定位方法不用加打印信息不用量波形。5.2 问题二双机通信偶尔乱码但误码率不高这种情况几乎可以断定是地电位差或信号反射导致和奇偶校验本身关系不大。两块板子各自供电只用三根线TX、RX、GND连接时如果两个电源的地电位有偏差通信线上的逻辑电平判读就会出现临界值表现为偶发乱码。加上校验位后错误帧能检测出来但你不能指望校验位把信号质量修好。处理方案是用示波器或逻辑分析仪看UART引脚的波形确认高低电平是否干净特别是高电平是否达到VDD的70%以上。如果波形边缘有明显的振铃在RX线上串联一个100Ω到1kΩ的电阻终端到地再接一个10pF到100pF的电容可以显著改善。另外两块板子的GND一定要共地这是所有串口调试的基础。我用STM8S003做项目时最常踩的坑就是单独给板子供电然后忘了接GND线结果通信全靠偶然的电平感应维持乱码率高得离谱。5.3 问题三校验位导致数据位错乱接收到的字节值明显不对这个坑特别隐蔽。场景是从机配置了偶校验但主机端用的串口调试助手默认是“8位数据无校验”。主机发0x53从机收到的却是0xA6之类完全不对的数值。原因在于数据位长度。从机把PCE置1后硬件自动把数据帧加长到9位主机发8位数据从机按9位帧去解析——硬件根本停不下来它会把你发过来的8位数据和停止位的一部分误认为9位数据帧自然错乱。反过来也一样主机开了校验从机没开从机把校验位当成数据位的最高位数据值翻倍也不奇怪。解决思路就一条通信双方必须保证“数据位长度校验位”的配置完全一致。主机8N1对应从机8E1两边配置对齐。我习惯把配置宏写到程序开头#define UART_BAUDRATE 9600 #define UART_DATABITS 8 // 数据位不含校验位 #define UART_PARITY E // N-无校验 E-偶校验 O-奇校验 #define UART_STOPBITS 1调参时只改宏初始化函数自动适配能少很多不必要的麻烦。6. 项目里常用的变体奇偶校验配合协议帧6.1 简单帧格式帧头长度数据校验实际项目里不推荐裸发数据字节哪怕你的通信距离只有几十厘米也建议加一层简单的帧协议。我常用的帧格式是字节序号内容说明0帧头固定0xAA1长度数据区字节数含后续所有2~n1数据区业务数据n2校验字节对帧头到数据区末尾做CRC8或累加和奇偶校验在物理层把关帧协议在应用层把关两者是不同维度的可靠性措施不冲突。PC端用串口调试助手收发时把每一帧当做一个整体来看奇偶校验保证单字节不出错帧头帧尾和校验和保证数据完整不丢帧组合起来基本能满足工业场景的可靠性需求。6.2 数据量小的时候可以考虑9位数据加标志位STM8S003的UART支持9位数据模式如果你把PCE关了数据位可以配置成9位。在这个模式下第9位可以作为多机通信的地址/数据标志位比如Modbus的半双工方向控制就能利用这一位来判断是地址帧还是数据帧。这个做法的好处是省掉一整个字节的帧头开销但对通信双方的配置要求更严苛两边稍有差池直接全乱。我不建议新手一上来就用9位模式先把8位数据加校验的常规玩法玩透再说。7. 关于采集信号完整性和校验配合的一些补充心得最后再聊点经验之外的经验。串口通信这件事奇偶校验只是众多可靠性措施中的一环。STM8S003的UART其实还支持发送空闲帧检测、接收超时检测如果使用UART3以及硬件流控RTS/CTS在小项目里这些功能很多人一辈子都用不上但当你遇到高可靠性的需求时这些功能组合在一起远比单纯依赖奇偶校验靠谱得多。我现在的习惯是调试阶段用无校验8N1方便用调试助手直接看数据功能稳定后再把收发双方切到偶校验8E1开一次长测跑48小时观察错误计数器的数值。如果偶校验下错误计数为0链路基本是干净的如果偶校验频繁报错那就不是配置问题而是硬件设计本身需要优化比如布线、电源、接地或者干扰源。每次项目结束回头看真正有价值的不是那些能跑通的代码而是在排查过程中对芯片寄存器细节、时序边界、硬件特性的理解。STM8S003虽然是颗便宜的8位机但它的串口模块设计得非常完整吃透一个UART后续换到STM32、GD32甚至英飞凌的MCU配置逻辑一通百通。希望这篇文章能让你少走几步弯路直接把奇偶校验这个看似简单、实则细节满满的功能用明白。本文还有配套的精品资源点击获取
返回列表