公司动态

51单片机串口通信实战:从寄存器配置到数据收发调试

📅 2026/7/15 8:34:01
51单片机串口通信实战:从寄存器配置到数据收发调试
1. 51单片机串口通信基础串口通信是51单片机最常用的外设功能之一也是初学者最容易上手的通信方式。我刚开始接触单片机时第一个成功调试的功能就是串口通信当时看到电脑上显示出Hello World的激动心情至今难忘。两根线搞定通信是串口最大的优势。只需要连接TXD发送和RXD接收两根线就能实现数据的收发。这里有个新手常犯的错误一定要记住单片机的RXD接外设的TXD单片机的TXD接外设的RXD交叉连接才是正确的。我第一次调试时就接反了结果死活收不到数据排查了半天才发现问题。串口通信分为同步和异步两种51单片机常用的是UART异步通信。它的特点是不需要时钟线同步通过起始位和停止位标识数据帧通信双方需约定相同的波特率一帧数据包含10位1位起始位低电平8位数据位低位在前1位停止位高电平2. 关键寄存器配置详解2.1 SCON串口控制寄存器SCON寄存器地址0x98是串口的核心控制器我习惯用以下配置SCON 0x50; // 方式1允许接收这个0x50是怎么来的呢我们拆解一下SM00, SM11选择工作方式18位UART波特率可变REN1允许接收数据其他位保持0即可实际项目中我遇到过一个问题RI标志位没有及时清零导致只能接收一次数据。后来发现必须在中断服务函数中手动清除RIif(RI){ RI 0; // 必须软件清零 // 处理接收数据 }2.2 波特率计算与定时器配置波特率是串口通信的语速通信双方必须一致。51单片机常用9600bps计算方法是TH1 TL1 256 - (11059200/12/32)/波特率;以11.0592MHz晶振、9600波特率为例TH1 TL1 256 - (11059200/12/32)/9600 253 0xFD;这里有个硬件坑点必须使用定时器1的模式28位自动重装并且不能开启定时器中断我早期就犯过开定时器中断的错误导致通信异常。完整初始化代码void UART_Init(){ TMOD | 0x20; // 定时器1模式2 TH1 0xFD; // 9600波特率 TL1 0xFD; TR1 1; // 启动定时器 SCON 0x50; // 串口模式1 ES 1; // 允许串口中断 EA 1; // 开总中断 }3. 数据收发实战代码3.1 发送字符串函数发送数据只需写入SBUF寄存器但要注意TI标志的处理void SendString(char *s){ while(*s){ SBUF *s; while(!TI); // 等待发送完成 TI 0; // 必须清零 } }实测发现如果不检查TI直接连续发送会导致数据丢失。我做过测试在12MHz晶振下发送一个字节大约需要1ms。3.2 中断接收处理推荐使用中断方式接收不占用CPU时间unsigned char rxData; // 接收缓冲区 void UART_ISR() interrupt 4{ if(RI){ RI 0; rxData SBUF; // 读取数据 // 可以在这里处理数据 } if(TI){ TI 0; // 发送中断处理 } }4. 常见问题排查指南4.1 收不到数据怎么办按照这个检查清单排查检查硬件连接TX-RX是否交叉连接用万用表测量TXD引脚是否有数据波形确认双方波特率一致误差不超过2%检查SCON寄存器配置是否正确确认中断是否开启EA和ES位4.2 数据乱码问题遇到乱码时我通常会先发送固定数据如0x55测试用示波器检查波形是否符合UART时序检查晶振频率是否准确尝试降低波特率测试如改为48004.3 通信距离受限标准UART的通信距离通常不超过1米。需要长距离传输时改用RS485电平最远1200米增加电平转换芯片如MAX485降低波特率提高抗干扰能力5. 进阶技巧与优化5.1 环形缓冲区实现为了避免数据丢失我常用环形缓冲区#define BUF_SIZE 64 unsigned char rxBuf[BUF_SIZE]; unsigned char rxHead 0, rxTail 0; void UART_ISR() interrupt 4{ if(RI){ RI 0; rxBuf[rxHead] SBUF; if(rxHead BUF_SIZE) rxHead 0; } } unsigned char GetByte(){ if(rxHead ! rxTail){ unsigned char c rxBuf[rxTail]; if(rxTail BUF_SIZE) rxTail 0; return c; } return 0; // 缓冲区空 }5.2 波特率自动识别对于需要自适应波特率的场景可以通过测量起始位宽度来识别unsigned int DetectBaud(){ unsigned int cnt 0; while(RXD); // 等待空闲 while(!RXD); // 等待起始位开始 while(RXD 0){ // 测量低电平时间 cnt; Delay1us(); } return 1000000/cnt; // 返回估算波特率 }5.3 数据包协议设计裸串口通信建议添加简单协议// 帧格式头(0xAA) 长度 数据 校验和 void SendPacket(unsigned char *data, unsigned char len){ unsigned char sum 0; SBUF 0xAA; while(!TI); TI0; SBUF len; while(!TI); TI0; for(int i0; ilen; i){ SBUF data[i]; while(!TI); TI0; sum data[i]; } SBUF sum; while(!TI); TI0; }6. 调试工具与技巧6.1 串口调试助手使用推荐几个好用的工具STC-ISP自带串口助手适合STC单片机SecureCRT支持多种协议和脚本Putty轻量级开源工具调试时建议开启十六进制显示可以直观看到原始数据。6.2 逻辑分析仪抓包当通信异常时我用Saleae逻辑分析仪抓取波形设置采样率至少4倍于波特率添加UART解码器检查起始位、数据位是否符合预期6.3 自制调试信息输出在代码关键位置添加调试输出#define DEBUG #ifdef DEBUG #define DBG(x) SendString(x) #else #define DBG(x) #endif void SomeFunction(){ DBG(Enter Function\r\n); // ... DBG(Exit Function\r\n); }