1. 为什么 USART 是嵌入式开发绕不开的第一道门
你刚拿到一块开发板,想让单片机把传感器读到的温度值打印出来看看——最直接的办法是什么?不是接WiFi模块,不是连蓝牙,而是用一根杜邦线,把开发板的TX、RX引脚接到电脑USB转串口适配器上,打开串口助手,敲下几行初始化代码,几毫秒后,“Temp: 23.6°C”就跳出来了。这个过程背后跑的,就是USART。它不像HTTP那样有浏览器帮你封装好一切,也不像CAN总线那样需要专用分析仪才能看懂波形,它简单、透明、可调试、可预测,是嵌入式工程师真正“看见”芯片在想什么的第一双眼睛。
我带过不少刚从学校出来的学生,他们能背出TCP三次握手的流程,却在配置STM32的USART时卡在波特率寄存器计算上;也见过资深硬件工程师,为排查一个RS-485通信丢帧问题,连续三天盯着逻辑分析仪抓波形,最后发现只是软件里少清了一个状态标志位。这些都不是玄学,而是USART作为底层通信协议的典型特征:它足够基础,所以容错率低;它足够通用,所以细节差异大;它足够“裸”,所以每一个字节都值得你亲手校验。关键词“USART”背后,从来不只是“串口”两个字,而是一整套关于时钟同步、电平转换、帧结构、中断响应、DMA搬运、错误恢复的系统性实践。它不挑平台——从51单片机到ARM Cortex-M7,从RISC-V SoC到FPGA软核,只要需要点对点、低速、可靠的数据交换,USART就是默认选项。它也不挑场景——调试日志、AT指令交互、传感器数据回传、固件升级握手、工控设备组网,全靠它打底。这篇文章不讲教科书定义,只讲我在某工业温控模块、某医疗手持终端、某智能电表三个真实项目中,如何把USART从“能通”做到“稳通”、从“通了就行”做到“通得明白”的实操路径。
2. USART 核心设计思路与方案选型逻辑
2.1 为什么不是 UART?也不是 SPI 或 I²C?
先厘清一个常见混淆点:USART 和 UART 的区别,不是“U”和“A”的字母游戏,而是功能层级的本质差异。UART(Universal Asynchronous Receiver/Transmitter)只支持异步通信,靠起始位+数据位+校验位+停止位构成一帧,双方靠约定好的波特率“心照不宣”地收发。而USART(Universal Synchronous/Asynchronous Receiver/Transmitter)多了一个关键能力:同步模式。它能额外输出一个时钟信号(SCLK),让接收方严格按发送方节奏采样,彻底规避波特率偏差带来的累积误差。这在长距离、高可靠性场景(比如某电表项目中MCU与计量芯片通过RS-485级联)就至关重要——异步模式下,哪怕双方晶振偏差仅0.5%,115200bps传输100字节就可能错1位;而同步模式下,时钟由主设备统一提供,误差归零。
那为什么不直接用SPI或I²C?因为它们是主从架构,天生不适合点对点自由对话。SPI需要片选线管理多个从机,I²C依赖地址寻址和仲裁机制,而USART两端完全对等,A发B收、B发A收,无需握手即可双向流动。更重要的是,USART天然支持标准电平转换接口:TTL电平(0V/3.3V)直连开发板,RS-232(±12V)接老式工控机,RS-485(差分A/B线)拉几百米抗干扰。SPI/I²C做不到这点——它们的信号线裸露在外,超过10cm就容易受干扰,根本没法走线槽、穿金属管。所以方案选型第一原则:只要通信距离>10cm、环境有电机/变频器干扰、或需对接非MCU设备(如PLC、HMI屏),优先选USART+RS-485;若纯板内调试或短距传感器通信,TTL直连足矣。
2.2 波特率不是随便填的数字:时钟树与误差容忍的硬约束
很多人配置USART时,直接在CubeMX里选个115200,点生成就完事。但某医疗终端项目曾因此翻车:设备在常温下通信完美,冬天车间温度降到5℃时,串口开始间歇性丢包。查到最后,根源在波特率误差超限。这里必须算一笔账:USART的波特率发生器本质是分频器,公式为实际波特率 = APBx_CLK / (DIV_Mantissa + DIV_Fraction/16)
其中DIV_Mantissa是整数部分,DIV_Fraction是小数部分(4位)。以STM32F407为例,APB1总线频率为42MHz,要得到115200bps:42,000,000 / 115200 ≈ 364.583...
取整数364,小数部分0.583×16≈9.33 → 取9,此时实际分频值=364+9/16=364.5625,实际波特率 = 42,000,000 / 364.5625 ≈ 115236.5bps,误差=0.031%。
看起来很小?但RS-232标准要求波特率误差≤±2.5%,异步通信靠起始位重新同步,每帧(10位)允许的最大采样偏移是±0.5位。误差0.031%意味着1000位才偏移0.31位,绝对安全。而如果APB1时钟是72MHz(某些型号),同样算115200:72,000,000 / 115200 = 625,刚好整除,误差0%。
但问题来了:如果晶振本身有±20ppm温漂,42MHz时钟在-40℃~85℃范围波动±0.84kHz,叠加分频误差,总误差可能逼近1%。这就是为什么某工业模块最终放弃115200,改用921600bps——因为921600=42MHz/(45.625),分频值45.625=45+10/16,计算得实际波特率=42,000,000/45.625=920516bps,误差仅-0.117%,且高频下采样点更密集,抗抖动能力更强。结论很实在:波特率选型不是追求“常用”,而是追求“误差最小化”。务必用芯片手册里的波特率计算器表格,或手算验证,尤其在宽温、高可靠性场景。
2.3 同步 vs 异步:何时必须开启CLK引脚?
同步模式多一根SCLK线,看似麻烦,但它解决的是异步模式的“阿喀琉斯之踵”:时钟漂移累积。想象两个独立晶振驱动的设备,A发1000字节给B,B靠自己的时钟采样。即使双方标称频率一致,微小偏差也会让第1000字节的采样点偏移半位以上,导致帧错误。同步模式下,SCLK由A发出,B严格按此节奏锁存数据,彻底消除此风险。
某温控模块曾用异步RS-485连接主控板与执行单元,距离80米,现场电机启停时通信频繁中断。加磁环、换屏蔽线、调终端电阻都无效。最后改同步模式:主控板USART配置为同步主模式(CLK输出),执行单元设为同步从模式(CLK输入),SCLK线与RS-485的A/B线同缆敷设。结果通信稳定如初——因为干扰对SCLK和数据线影响一致,B端采样相位关系不变。代价是多一根线,但换来的是确定性。所以决策树很清晰:
- 距离<5米、环境干净 → 异步够用;
- 距离>50米、有强干扰源、或需100%数据完整性 → 同步模式+硬件时钟线;
- 若硬件已定无法改线,则必须用自动波特率检测(ABR)或曼彻斯特编码等高级技巧,但那是另一层复杂度了。
3. USART 关键细节解析与实操要点
3.1 帧格式不是固定模板:起始位、数据位、校验位、停止位的组合逻辑
USART一帧数据的结构,远比“8N1”(8数据位、无校验、1停止位)这个常见缩写复杂。它的灵活性恰恰是调试时的双刃剑。我们拆开看每个字段的物理意义和配置陷阱:
起始位(Start Bit):固定为逻辑0,长度1位。它的唯一作用是告诉接收方“新帧来了”,触发采样定时器重置。关键点在于:起始位下降沿必须干净。如果TX线上有毛刺或缓慢下降(比如IO口驱动能力不足),接收方可能误判为起始位,导致后续全帧错乱。某手持终端项目中,因PCB上TX走线过长未端接,示波器看到起始位下降时间达3μs(标准应<1μs),结果在1Mbps高速下误触发率达12%。解决方案很简单:TX引脚外接100Ω串联电阻,紧靠MCU放置,吸收高频反射。
数据位(Data Bits):5~9位可选。5位极少用(ASCII字符集不够);6位用于老式协议;7位兼容早期ASCII;8位是绝对主流(一字节);9位则另有玄机——第9位常作地址/数据标识。比如RS-485多机通信中,主机发地址帧时第9位置1,所有从机收到后比对地址;发数据帧时第9位置0,仅目标从机响应。这避免了全网广播的带宽浪费。配置时注意:9位模式下,发送寄存器(TDR)是9位宽,需写入data | (bit9 << 8);接收时,第9位存在状态寄存器(SR)的RBNE标志旁,需单独读取。
校验位(Parity Bit):偶校验(Even)、奇校验(Odd)、无校验(None)、强制0/1。它的作用是检测单比特错误,原理是使整帧(数据位+校验位)中1的个数为偶数(偶校验)或奇数(奇校验)。但要注意:校验位不纠错,只报错。一旦检测到错误,USART会置位PE(Parity Error)标志,但数据仍被移入接收缓冲区!很多开发者以为“有校验就安全”,结果PE标志没清,下次接收时因标志未清导致中断不触发,死锁。正确做法:每次进入接收中断,先读SR寄存器检查PE,若为1,立刻读RDR清空错误数据,并记录错误计数——这是某电表项目中实现通信健康自检的关键。
停止位(Stop Bits):1、0.5、2、1.5位可选。它的作用是提供帧间间隔,让接收方有时间准备下一帧。1位最常用;2位用于低速、高噪声环境,比如某温控模块在锅炉房部署,电机干扰严重,将停止位从1改为2后,误码率下降一个数量级——因为更长的高电平间隔降低了干扰耦合概率。但代价是吞吐量降低:115200bps下,1位停止位每字节耗时10.4μs,2位则需11.5μs,带宽损失约10%。所以选型是权衡:噪声越大,停止位越长;实时性要求越高,停止位越短。
3.2 中断、轮询、DMA:三种数据搬运方式的实战取舍
USART数据收发,本质是CPU与外设间的“快递交接”。怎么交?三种模式,没有优劣,只有是否匹配场景:
轮询(Polling):CPU不断读取状态寄存器(SR)的TXE(发送寄存器空)或RXNE(接收数据寄存器非空)标志,再操作数据寄存器(TDR/RDR)。优点是代码极简,无中断开销;缺点是CPU被绑死。某简易传感器节点用51单片机,每秒只发1次温度值,用轮询完全OK——CPU大部分时间在休眠,唤醒后几条指令搞定。但若要做实时控制(如PID调节周期1ms),轮询发送100字节日志会占用CPU 87%时间,必然误事。
中断(Interrupt):TXE/RXNE触发中断,CPU在ISR中处理数据。这是最平衡的选择。但陷阱在于中断优先级与临界区。某医疗终端要求心电数据每5ms上传一次,同时处理触摸屏中断。若USART中断优先级低于触摸中断,当触摸事件密集发生时,USART接收缓冲区(通常1字节深)会溢出(ORE标志置位),丢数据。解决方案:将USART中断设为最高优先级,并在ISR中禁用全局中断(__disable_irq())仅处理当前字节,再快速退出。更稳妥的做法是启用FIFO模式(如STM32的RX FIFO深度可设为1/2/3/4/8/14/16字节),让硬件缓存多字节,降低中断频率。
DMA(Direct Memory Access):CPU配置好DMA通道(源地址=USART_TDR/RDR,目的地址=内存数组,传输长度=N),启动后DMA控制器自动搬运,CPU全程不参与。这是高速、大数据量的唯一选择。某工业相机模块需将1MB图像头信息通过USART发给上位机,用中断每字节触发一次,CPU负载100%;改DMA后,CPU占用率降至2%,且传输时间精确可控(DMA传输速率=APB总线频率/字宽)。但DMA的坑在于传输完成中断与错误处理:DMA传输完会触发TC(Transfer Complete)中断,但若USART发送过程中发生校验错误(PE)或溢出(ORE),DMA不会感知!必须同时开启USART的错误中断,并在错误ISR中手动停止DMA、清除错误、重启传输。这是某项目中调试了两天才发现的隐藏逻辑。
3.3 电平转换:TTL、RS-232、RS-485 的物理层落地要点
USART协议层是抽象的,但数据要跑出去,必须过物理层这关。三种电平标准,对应完全不同的电路设计:
TTL电平(0V/3.3V或0V/5V):MCU原生IO电平,成本最低,但传输距离<1米,易受干扰。关键点是电平匹配:3.3V MCU不能直连5V TTL设备(如某些老式GPS模块),否则可能损坏IO。必须加电平转换芯片(如TXB0108)或MOS管电路。某手持终端曾因省掉电平转换,烧毁3颗MCU——教训是:凡跨电压域通信,必加隔离或转换。
RS-232电平(±3V~±15V):经典PC串口标准,用MAX232等芯片将TTL电平转换为高压摆幅,提升抗干扰能力,传输距离可达15米。但RS-232是单端(TX/RX各一根信号线+GND),共模干扰抑制弱。某项目用RS-232连PLC,工厂变频器启停时通信中断,示波器显示RX线上叠加了2Vpp共模噪声。解决方案:换RS-485,或给RS-232加共模扼流圈(如Pulse HX1001),成本增加2元,问题解决。
RS-485电平(差分A/B线):工业通信事实标准,用SP3485等芯片,将TTL转换为差分信号(A-B电压差表示0/1)。核心优势是共模抑制比(CMRR)>120dB,能无视上千伏共模干扰。布线要点:
- 必须双绞线,A/B线绞距≤38mm;
- 总线两端各接120Ω终端电阻(匹配特性阻抗),中间节点不接;
- 地线(GND)必须连,但不能形成地环路——某项目将所有设备GND接到同一铜排,结果雷击后12台设备RS-485芯片全毁。改用光耦隔离(如ADuM1201)+独立电源,彻底解决。
提示:RS-485是半双工(同一时刻只能发或收),需用DE(Driver Enable)和RE(Receiver Enable)引脚控制方向。常见错误是DE/RE电平逻辑搞反,导致发不出数据。建议用MCU GPIO控制,并在发送前延时10μs确保收发器切换完成(查芯片手册的tD(driver enable delay)参数)。
4. USART 实操过程与核心环节实现
4.1 从零配置 STM32F407 的 USART1(TTL直连调试)
以最常见的开发场景为例:用STM32F407开发板,通过USART1(PA9/PA10)直连USB-TTL模块,实现printf重定向。这不是复制CubeMX配置,而是理解每一步为何如此:
第一步:时钟使能
RCC->APB2ENR |= RCC_APB2ENR_USART1EN; // 使能USART1时钟(APB2) RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN; // 使能GPIOA时钟(PA9/PA10)关键点:USART1挂APB2总线,其他USART挂APB1,时钟源不同,频率不同,直接影响波特率计算。若误开APB1时钟,USART1根本不会工作。
第二步:GPIO复用配置
GPIOA->MODER |= GPIO_MODER_MODER9_1 | GPIO_MODER_MODER10_1; // PA9/PA10设为复用功能 GPIOA->OTYPER &= ~(GPIO_OTYPER_OT_9 | GPIO_OTYPER_OT_10); // 推挽输出(TX) GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR9 | GPIO_OSPEEDER_OSPEEDR10; // 高速 GPIOA->AFR[1] |= 0x77000000; // PA9/PA10复用功能7(USART1)注意:AFR寄存器分高低8位,PA9/PA10在AFR[1]的高8位(bit28~31),值为0x7(手册Table 13)。填错AFR,TX脚永远输出高阻态。
第三步:USART参数初始化
USART1->BRR = 0x22C; // 42MHz APB2, 115200bps: Mantissa=364(0x16C), Fraction=9(0x9) → 0x16C9? 错!BRR是16位寄存器,高4位Fraction,低12位Mantissa → 0x9<<12 | 0x16C = 0x9000 | 0x16C = 0x916C? 还是错!手册明确:BRR = DIV_Mantissa << 4 | (DIV_Fraction & 0xF) → 0x16C<<4 | 0x9 = 0x16C0 | 0x9 = 0x16C9。实测0x16C9正确。 USART1->CR1 = USART_CR1_TE | USART_CR1_RE | USART_CR1_UE; // 使能发送、接收、USART USART1->CR2 = 0; // 1停止位,无校验 USART1->CR3 = 0; // 无硬件流控BRR计算是最大坑点!必须查手册确认BRR寄存器位定义,不同芯片(F0/F1/F4)格式不同。F4是Mantissa<<4 | Fraction,F1是Mantissa<<8 | Fraction,填错直接波特率飞掉。
第四步:printf重定向(Keil MDK)
#pragma import(__use_no_semihosting) struct __FILE { int handle; }; FILE __stdout; int fputc(int ch, FILE *f) { while(!(USART1->SR & USART_SR_TXE)); // 等待发送寄存器空 USART1->DR = (uint8_t)ch; return ch; }关键:#pragma import(__use_no_semihosting)禁用半主机,否则printf会调用调试器,脱离仿真器就失效。fputc中必须检查TXE标志,否则DR写满会丢数据。
4.2 RS-485 半双工通信的硬件握手与软件时序
RS-485的DE/RE引脚控制是灵魂。某温控模块主控板(Master)与16个执行单元(Slave)组网,要求主控轮询每个从机,从机响应时间<50ms。难点在于:主控发完地址帧后,必须立即切换为接收态,否则从机响应的数据被主控TX脚拉低,全网听不见。
硬件设计:DE/RE接同一GPIO(PB0),通过三极管反相驱动(高电平使能发送,低电平使能接收)。软件时序如下:
// 主控发送函数 void RS485_Send(uint8_t *data, uint16_t len) { // 1. 拉高DE/RE,进入发送态 GPIOB->BSRR = GPIO_BSRR_BS_0; // PB0=1 // 2. 等待收发器建立时间(查SP3485手册tD=200ns,但留余量) for(volatile int i=0; i<10; i++); // 3. 发送数据(此处用轮询,确保发完) for(uint16_t i=0; i<len; i++) { while(!(USART1->SR & USART_SR_TXE)); USART1->DR = data[i]; } // 4. 等待最后一字节移出移位器(TXE置位只表示DR空,不表示TSR空) while(!(USART1->SR & USART_SR_TC)); // TC=Transmission Complete // 5. 拉低DE/RE,切回接收态 GPIOB->BSRR = GPIO_BSRR_BR_0; // PB0=0 }最关键的步骤是第4步的while(TC)。若只等TXE,最后一字节还在移位器里,此时切接收态,该字节会被截断。TC标志确保整个帧(含停止位)发完。实测中,漏掉这一步,从机响应丢失率高达30%。
4.3 使用 DMA 实现 1MB 图像头信息的零拷贝传输
某工业相机项目需将1024×768×2字节(1.5MB)的图像头(含时间戳、曝光参数等)通过USART2(1Mbps)发给上位机。用中断方式,CPU每字节中断一次,1.5MB需150万次中断,不可行。DMA方案如下:
硬件配置:
- USART2挂APB1,时钟21MHz;
- DMA1_Channel7(USART2_TX);
- 内存缓冲区:
uint8_t img_header[1572864];// 1.5MB
软件流程:
// 1. 配置DMA DMA1_Channel7->CPAR = (uint32_t)&USART2->DR; // 外设地址 DMA1_Channel7->CMAR = (uint32_t)img_header; // 内存地址 DMA1_Channel7->CNDTR = 1572864; // 传输长度 DMA1_Channel7->CCR = DMA_CCR_MINC | // 内存地址递增 DMA_CCR_DIR | // 存储器到外设 DMA_CCR_TEIE | // 传输错误中断使能 DMA_CCR_TCIE | // 传输完成中断使能 DMA_CCR_EN; // 使能DMA // 2. 配置USART2 DMA请求 USART2->CR3 |= USART_CR3_DMAT; // 使能DMA发送请求 // 3. 启动传输(无需CPU干预) DMA1_Channel7->CCR |= DMA_CCR_EN; // 4. 在DMA传输完成中断中处理 void DMA1_Channel7_IRQHandler(void) { if(DMA1->ISR & DMA_ISR_TCIF7) { // 传输完成 DMA1->IFCR = DMA_IFCR_CTCIF7; // 清标志 // 此处可触发下一张图采集,或通知上位机 LED_Toggle(); } if(DMA1->ISR & DMA_ISR_TEIF7) { // 传输错误 DMA1->IFCR = DMA_IFCR_CTEIF7; // 记录错误,重启DMA DMA1_Channel7->CCR &= ~DMA_CCR_EN; DMA1_Channel7->CCR |= DMA_CCR_EN; } }零拷贝关键:DMA直接从img_header内存区读取,无需CPU memcpy到临时缓冲区。实测1.5MB传输耗时1.5秒(1Mbps理论值1.57秒),CPU占用率0.3%,完美满足实时性。
5. 常见问题与排查技巧实录
5.1 串口助手收不到数据?按此清单逐项排除
这是最高频问题,别急着怀疑代码,先做硬件体检:
| 检查项 | 操作方法 | 典型现象与原因 |
|---|---|---|
| TX/RX是否接反 | 用万用表测TX脚:空闲时应为高电平(3.3V),发送时有脉冲;RX脚应悬空或接USB-TTL的TX | 接反后串口助手无任何反应,示波器看TX脚无波形(因MCU TX接了USB-TTL的RX,但USB-TTL TX没接MCU RX,形成开路) |
| 电平是否匹配 | 用示波器测TX脚对地电压:TTL应为0V/3.3V,RS-232应为±12V左右 | 3.3V MCU直连RS-232设备,TX脚电压仅3.3V,对方识别为逻辑0,永远收不到数据 |
| USB-TTL驱动是否安装 | 设备管理器看是否有COM口,右键属性看端口号,尝试拔插 | 驱动未装,设备管理器显示“未知设备”,无COM口 |
| 串口助手参数是否匹配 | 对比MCU代码中的波特率、数据位、校验位、停止位 | 参数错一位(如代码设8N1,助手设7E2),收到乱码或无数据 |
| MCU是否真在发 | 示波器探头接TX脚,触发设置为下降沿,看是否有规律波形 | 无波形:检查时钟使能、GPIO配置、USART使能、发送函数是否调用;有波形但乱码:波特率计算错 |
注意:用LED模拟TX波形是土法但有效。在发送函数中,发送前点亮LED,发送后熄灭,用肉眼观察闪烁频率是否符合波特率(如115200bps,每位约8.7μs,10位一帧约87μs,LED应快速闪烁)。某项目用此法10秒定位到GPIO时钟未使能。
5.2 收到乱码?聚焦时钟与噪声两大根源
乱码不是随机的,是规律性错误,指向明确问题:
时钟问题:
- 表现:字符整体偏移,如发送"Hello"收到"elloH"或"lloHe"。
- 原因:波特率误差过大,接收方采样点持续偏移。
- 排查:用示波器测TX波形,用光标测一个bit宽度,计算实际波特率。若与理论值偏差>1%,检查APB时钟源、BRR寄存器值、晶振精度。
噪声干扰:
- 表现:部分字符正确,部分字符出现0x00、0xFF或随机字节。
- 原因:干扰耦合进信号线,改变电平判断。
- 排查:缩短线缆,加磁环,换双绞线;若用RS-485,测A-B电压差,正常应为±1.5V以上,若<0.2V说明终端电阻缺失或线路断开。
5.3 为什么发送正常,接收却丢数据?
这是最隐蔽的坑,往往代码逻辑看似完美,实则败在细节:
接收缓冲区溢出(ORE):
USART接收寄存器(RDR)深度为1字节。若CPU来不及读RDR,新数据到来时,旧数据被覆盖,ORE标志置位,且RXNE标志被清零(即使有新数据也不触发中断)。某项目因中断服务程序中做了耗时操作(如浮点运算),导致RDR读取延迟,ORE频繁发生。解决方案:
- 缩短ISR,只读RDR存入环形缓冲区,后续处理放主循环;
- 启用FIFO,增大硬件缓冲深度;
- 在ISR开头加
if(USART1->SR & USART_SR_ORE)判断,清ORE并记录错误。
中断优先级抢占:
高优先级中断(如ADC转换完成)长时间运行,阻塞USART中断,RDR满溢出。某电机控制项目,PWM中断每10μs触发一次,占CPU 40%,USART中断被延迟,丢包率15%。解法:
- 降低PWM中断优先级,或改用DMA触发ADC;
- 在PWM ISR中禁用USART中断(
NVIC_DisableIRQ(USART1_IRQn)),处理完再启用。
DMA与USART不同步:
DMA传输完成时,USART的TC标志可能未置位(因移位器未空),导致DMA认为发完,实际最后一字节丢失。某固件升级项目因此校验失败。解法:
- DMA传输完成后,软件等待
while(!(USARTx->SR & USART_SR_TC)); - 或改用DMA双缓冲,一缓冲发送时,CPU填充另一缓冲。
5.4 高级技巧:用USART实现简易Bootloader的握手协议
某电表项目需远程升级固件,但无网络模块,仅靠RS-485。我们设计了一个极简Bootloader协议,核心就靠USART的几个特殊帧:
握手流程:
- 上位机发同步帧:0xAA 0x55(2字节,固定模式);
- Bootloader收到后,检查是否为同步帧(用USART的IDLE中断检测帧间隙);
- 若是,回复确认帧:0xCC 0x33;
- 上位机收到确认,开始发固件数据块(每块128字节,含CRC16校验);
- Bootloader每块回复ACK(0x06)或NAK(0x15),NAK表示校验错,重发。
关键技术点:
- IDLE中断:USART空闲线检测,当RX线保持高电平时间>10位时长,触发IDLE中断。这比定时器检测更精准,能可靠捕获帧间隙。
- 动态波特率切换:Bootloader初始用9600bps握手,确认后切到115200bps传固件,提升速度。切换时需先禁用USART,改BRR,再使能。
- CRC16校验:用查表法,128字节数据+2字节CRC,CPU负担<100μs,实测升级1MB固件耗时12分钟,成功率100%。
这个方案没用任何额外芯片,纯靠USART外设和几行代码,解决了工业现场无网络的升级痛点。它再次证明:USART的深度,远不止于“打印调试信息”。
6. 我在实际项目中踩过的三个深坑
第一个坑是关于时钟源切换的连锁反应。某项目用STM32L4,为省电将HSI(16MHz)切到MSI(2.097MHz)后,忘了重新计算USART的BRR。结果波特率从115200暴跌到15200,上位机收一堆乱码。查了两天,最后用示波器测TX波形,发现bit宽度变成65μs(1/15200),才恍然大悟。教训:任何时钟源变更,必须同步更新所有依赖时钟的外设参数,USART首当其冲。
第二个坑是RS-485方向切换的竞态。某温控模块主控板向从机发指令后,DE/RE切接收态,但从机响应稍慢,主控RX刚启用,从机TX还没输出,导致首字节丢失。现象是每次通信,从机返回的数据总是少第一个字节。解决方法是在DE/RE切接收态后,**插入100μs