不能直接用硬件uart因低端单片机(如stm8s003)外设不足或已被占用,须软件模拟;接收依赖外部中断+定时器消抖确认起始位,发送靠定时器精确翻转gpio生成帧波形,全程需严控波特率误差(≤±2%)与中断时序。

为什么不能直接用硬件UART
很多低端单片机(比如STM8S003、GD32F1x0、部分8051或RISC-V裸机芯片)没有足够UART外设,或者UART已被占用,这时必须软件模拟。关键约束是:接收靠外部中断捕获起始位边沿,发送靠定时器精确控制每一位的电平持续时间——不是简单延时,否则波特率误差会随主频波动放大。
GPIO外部中断怎么可靠捕获起始位
起始位是下降沿,但机械抖动、信号噪声会导致误触发。不能一进中断就认为收到数据,必须加消抖和起始位确认逻辑:
-
EXTI配置为下降沿触发,但中断服务函数(ISR)里立刻关闭该中断线,避免重复进入 - 用定时器(如
TIM2)启动一个约1.5位宽的单次计时(例如9600bps下≈156μs),超时后读取GPIO电平:若仍为低,才确认是有效起始位 - 确认后重新开启中断,同时启动主接收定时器(每1位时间触发一次),开始采样数据位
- 务必关全局中断(
__disable_irq())或用临界区保护状态变量,避免接收状态被其他中断打断
定时器输出怎么生成准确的TX波形
发送不能用delay_ms()或for循环,必须用定时器更新事件(UEV)或比较匹配(CC)来翻转GPIO。典型做法是把TX引脚配置为推挽输出,初始为高(空闲态),然后:
- 发送前将引脚拉低(起始位),并启动定时器,设定第一次更新时间为1位时间(如104μs @9600bps)
- 在定时器中断中按顺序输出:8个数据位(LSB先发)、可选奇偶位、停止位(高电平),每次翻转后重载定时器周期为1位时间
- 使用
TIMx->ARR动态改周期不如预设固定ARR+用TIMx->CNT手动清零稳定;更稳妥的是用“单脉冲模式(OPM)”逐位触发 - 注意:最后停止位输出完,必须等待定时器完成该次计时再恢复引脚为高,否则可能截断停止位
波特率误差和时序容错怎么控制
软件UART对主频精度极度敏感。假设系统时钟为8MHz,目标9600bps,理论每位104.17μs,但定时器只能取整数计数值。若用16位定时器+72分频,实际周期可能是104μs或105μs——累积8位后误差达8μs,已接近采样窗口边缘。
- 接收端应在每位中间1/2处采样(即起始位后1.5、2.5、3.5…位时间点),而非边沿对齐
- 发送端建议预留至少20%的时序余量:例如标称104μs,实际按83–125μs范围设计定时器重载值
- 实测发现,当主频误差>±2% 或线路长度>30cm 时,即使逻辑正确也常丢帧——此时必须加外部施密特触发器整形输入信号
- 别忽略中断延迟:从GPIO下降沿到进入ISR通常有3–8个周期延迟,这部分必须计入起始位检测的1.5位偏移中
真正难的不是写通一帧收发,而是让同一套代码在不同温度、不同电源电压、不同编译优化等级下都保持<±3%波特率偏差。往往要手调定时器预分频和重载值,并用示波器抓TX波形反复校准。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











