资讯动态

51单片机串口(UART)详解:寄存器配置、波特率计算与初始化

发布时间:2026/10/17 1:04:49 来源:尧图企业网站定制
学51单片机打卡第七天前六天基本都在跟LED、按键、数码管、外部中断打交道到了今天终于轮到串口了。串口UART大概是整个51学习过程中最实用的一块内容不管是调试打印、连接各种无线模块还是跟电脑通信几乎都绕不开它。围绕80C51串行口今天要弄清楚四件事内部结构、SCON和PCON寄存器、四种工作方式、波特率计算以及串口使用之前的初始化流程。串口看起来就是一根TX一根RX但真正用起来寄存器配置、波特率匹配、中断处理每一个细节都可能让人折腾半天。我见过不少同学卡在乱码和收不到数据上到最后发现都是些很小的配置问题。这篇就把这些内容掰开揉碎讲一遍。1. 先把串口内部的框架捋清楚1.1 核心就是两个SBUF但地址只有一个80C51的串行口从结构上看是一个全双工UART也就是能同时收发。芯片内部有接收缓冲器和发送缓冲器名字都叫SBUF物理上是两个完全独立的寄存器但地址只有一个0x99。往这个地址写进的是发送缓冲器从这地址读出来的是接收缓冲器。很多初学者第一次接触会被这个设计绕晕其实记住一句话就行写SBUF就是发读SBUF就是收。除了SBUF串口模块还包括移位寄存器、波特率发生器通常由定时器1承担、中断控制逻辑以及控制寄存器。RXD对应P3.0TXD对应P3.1。接收数据时外部信号从RXD引脚进来经过移位寄存器逐位移入组装成一帧放到接收SBUF发送时反过来从发送SBUF取数据逐位移出从TXD引脚送出去。整个过程CPU不需要一位一位干预你只要往SBUF写一个字节硬件自动帮你发完。1.2 结构里几个容易忽略的细节看教材上的串口结构框图一眼扫过去全是方框但有三个细节很容易被忽略却直接影响使用。第一发送控制器和接收控制器是分开的各自有各自的中断标志TI和RI。这意味着发送完成和接收完成是两个独立事件处理时要分开判断。第二接收部分有双重缓冲机制。接收SBUF前面还有一个移位寄存器做缓冲这样你正在处理当前这帧数据的时候下一帧还能继续进入移位寄存器不至于因为处理不及时丢数据。这是串口能在中断方式下稳定工作的硬件基础。第三波特率并非全部来自定时器1。方式0和方式2的波特率是固定的由晶振频率直接分频得到只有方式1和方式3才用定时器1的溢出率。这一点在算波特率的时候特别容易搞混。这一节不用死记结构图有个整体框架感就行真正要对齐细节的是下一节的寄存器配置。2. 两个决定一切的寄存器SCON和PCON2.1 SCON逐位拆解SCON是串行口控制寄存器地址0x98属于可位寻址的SFR既能整体赋值也能用sbit或者位操作单独设置。复位值是0x00代表串口默认什么都不干。它的8个位的作用如下表位名称含义SCON.7SM0工作方式选择位高位SCON.6SM1工作方式选择位低位SCON.5SM2多机通信控制位SCON.4REN允许接收位1使能接收SCON.3TB8方式2/3中发送的第9位数据SCON.2RB8方式2/3中接收的第9位数据SCON.1TI发送中断标志发送完一帧硬件置1需软件清零SCON.0RI接收中断标志接收完一帧硬件置1需软件清零SM0和SM1组合决定四种工作方式后面专门展开。SM2在方式2和方式3中和第九位数据配合用于多机通信的地址帧和数据帧区分单机通信时保持0就行。REN是接收总开关很多同学发数据后单片机收不到十有八九就是REN没置1引脚根本没在监听。TB8和RB8在不用第九位的场合保持0不用多管。TI和RI是硬件置位、软件清零尤其是做中断接收时服务程序里忘了清RI程序就会反复进入中断要么数据错乱要么一次性把缓冲读空。2.2 PCON寄存器SMOD才是主角PCON是电源控制寄存器地址0x87不可位寻址只能整体读写或者用位运算修改复位值0x00。它的最高位SMOD是串口波特率加倍位和串口直接相关。SMOD1时方式1、2、3的波特率都会翻倍方式0不受影响。其余位里GF1、GF0是两个通用标志位PD是掉电模式控制位IDL是空闲模式控制位低功耗设计才会用到普通串口调试不用关心。有个很典型的坑别人给的代码里写PCON | 0x80意思就是把SMOD置1波特率翻倍。如果你上位机软件配的还是原来的波特率两边速率不一致结果必然是乱码。所以看例程的时候SMOD这个位必须跟波特率计算一起看单独看哪一行都是对的组合起来可能就错了。另外提醒一句PCON操作尽量用|和不要直接整体赋值。PCON里还有掉电控制位如果对整体字节赋值不看其他位的当前状态容易把不该改的位改了在低功耗场景下会出莫名其妙的问题。2.3 配置SCON和PCON的快速思路实际写初始化代码SCON用一句整体赋值置好。比如最常用的方式1加允许接收就是SCON 0x50对应SM00、SM11、REN1。PCON一般只动SMOD想加倍就PCON | 0x80不想加倍就PCON 0x7F。这一步其实没什么玄学关键是先确定自己的工作方式再确定波特率寄存器自然就出来了。3. 四种工作方式按需选择3.1 方式0不是UART是同步移位寄存器方式0下串口并不是标准UART而是一个同步移位寄存器接口。数据从RXD引脚输入或者输出TXD引脚输出移位时钟波特率固定为晶振频率的1/12。这种方式不用于两个设备之间通信而是用来扩展并行IO口。典型接法就是外接74HC595做输出扩展或者74LS165做输入扩展。用起来很有意思往SBUF写一个字节硬件按低位在前自动移出8位。做数码管动态扫描的时候经常用这种方式省IO口。初次接触可能会疑惑串口怎么还能接数码管其实就是把串口当移位寄存器用把串行数据转成并行输出去驱动外设。方式0的收发都在RXD上TXD只是时钟信号所以在这种方式下RXD被占用TXD只是辅助线。3.2 方式18位UART出场率最高方式1是标准8位异步通信格式一帧包含1位起始位、8位数据位、1位停止位波特率由定时器1的溢出率决定。这就是日常用串口助手调试时默认的格式8数据位、无校验、1停止位。发送时往SBUF写一个字节硬件自动加上起始位和停止位发出去接收时REN置1后硬件持续监听RXD引脚收到完整帧后把数据放到接收SBUF同时RI置1。方式1下的RB8存放的是停止位这个细节有印象就行正常用不到。绝大多数上位机通信、模块调试方式1基本就够了配置最简单兼容性也最好。3.3 方式29位UART波特率固定方式2的帧格式是1位起始位、8位数据位、1位可编程的第9位、1位停止位。这个第9位就是TB8和RB8了。发送时TB8的值会作为第9位发出去接收时收到的第9位会存入RB8。方式2的波特率是固定的等于2的SMOD次方除以64再乘以晶振频率不受定时器控制。听起来有个第9位可以做校验或者多机通信但正因为波特率固定、不能随便调实际项目里用得并不多。如果晶振频率不合适它生成的波特率可能不是标准值反而添乱。所以方式2更多是教学意义告诉你UART还可以有第9位这种玩法。3.4 方式39位UART波特率可调方式3的帧格式和方式2完全一样区别只有一点波特率由定时器1的溢出率决定可以灵活设置。想要9位数据帧格式又想用自己计算的波特率那就选方式3。方式2和方式3搭配SM2可以实现多机通信这也是第九位数据的主要意义。主机发送地址帧时TB8置1发送数据帧时TB8置0从机通过判断第9位来确定这一帧是否跟自己相关。初学阶段先记住方式3和方式2帧格式一致、波特率机制不同等以后真做多机通信了再深入不迟。3.5 四种方式怎么选整理一张表对着看更直观方式帧格式波特率来源典型用途方式0同步移位数据fosc/12固定扩展并行IO口方式18位数据启停位定时器1溢出率普通双机通信方式29位数据启停位fosc固定分频多机通信、校验方式39位数据启停位定时器1溢出率多机通信、校验日常调试和绝大多数应用方式1就够简单可靠。多机通信或者需要奇偶校验的场景方式3更实用。方式0另类只在扩展IO时用。方式2基本是教学和特定场景。4. 波特率计算别硬背公式要学会推导4.1 波特率的来源分两条线波特率就是每秒传输的位数单位bps。计算之前先分清楚当前工作方式的波特率来自哪里。方式0固定是fosc/12方式2固定是2的SMOD次方除以64再乘fosc。方式1和方式3的波特率则由定时器1溢出率决定公式是波特率 (2^SMOD / 32) × 定时器1溢出率定时器1本身可以工作在多种方式下溢出率不一样。最常用的做法是把定时器1配置成方式2也就是8位自动重装。这种模式下定时器每计数256减TH1个机器周期就溢出一次溢出率就是晶振频率除以12再除以计数值溢出率 fosc / (12 × (256 - TH1))把两个公式合并起来就是波特率 (2^SMOD × fosc) / (384 × (256 - TH1))反过来要算TH1的初值TH1 256 - (2^SMOD × fosc) / (384 × 波特率)这个推导过程比死记公式有用因为实际项目里晶振变了、波特率变了套公式才知道每个数字是从哪来的。4.2 为什么11.0592MHz晶振成了行业标准用最常见的9600波特率、SMOD0来算一笔账。如果晶振是11.0592MHzTH1 256 - 11059200 / (384 × 9600)分母384 × 9600 368640011059200除以3686400正好等于3。于是TH1 256 - 3 253也就是0xFD。初值是整数波特率分毫不差。如果晶振换成12MHzTH1 256 - 12000000 / (384 × 9600) ≈ 256 - 3.255 ≈ 253初值只能取整数TH1取253后实际波特率就变成12000000 / (384 × 3) 10416.7bps跟9600差了大约8.5%。这个误差在通信上是致命的收发双方一位一位地比对时间长了必然错位表现出来就是偶尔收到一两个对的字节紧接着全是乱码。所以很多开发板坚持用11.0592MHz晶振不是因为看起来专业而是它恰好能被标准波特率整除干净。4.3 常用波特率初值速查假设晶振11.0592MHz、定时器1工作在方式2常用配置如下波特率SMODTH1/TL1误差情况480000xFA无误差960000xFD无误差1920010xFD无误差5760010xFE有微小误差注意19200和9600的初值都是0xFD区别就在SMOD。SMOD1时波特率在同一个初值下翻倍。57600这种高速率初值取0xFE附近实际值和理论值会有偏差对晶振精度和线路质量更敏感实际项目里如果跑这个速率还不稳定优先降低速率排查。4.4 误差容忍度到底在哪个人经验是波特率误差在2%以内通常还能凑合通信超过3%基本就是乱码。但这个容忍度跟通信距离、帧格式、双方容错都有关不能一概而论。实验中出现乱码第一步永远先核对波特率不要急着怀疑硬件电路。12MHz晶振也不是不能用只要双方约定一个12MHz能整除的速率就行但很多上位机工具不认非标准波特率所以最省心的方案还是换11.0592MHz晶振或者选带独立波特率发生器的增强型单片机。5. 串口初始化实操从寄存器到代码5.1 初始化要回答五个问题初始化串口的本质就是回答几个问题用哪种工作方式、波特率多少、要不要接收、要不要中断。落实到寄存器上一共五步。第一步配置定时器1为方式2。设置TMOD高四位TMOD对低四位对应定时器0操作时不要影响定时器0的配置所以用TMOD 0x0F先清高四位再TMOD | 0x20把定时器1设为方式2。第二步装初值。TH1和TL1都写入计算好的值因为是自动重装模式TH1是重装值TL1是当前计数初值两者通常写一样的。第三步启动定时器1TR1 1。这一步特别容易忘定时器不跑波特率就为0串口自然不工作。第四步设置SCON。方式1加允许接收就是SCON 0x50对应SM00、SM11、REN1SM2、TB8、RB8、TI、RI都为0。第五步设置PCON的SMOD位然后根据需求开中断ES置1、EA置1。5.2 参考代码下面给出一份完整的基础初始化代码晶振11.0592MHz波特率9600#include reg52.h void UART_Init(void) { TMOD 0x0F; // 只动高四位不影响定时器0 TMOD | 0x20; // 定时器1工作方式28位自动重装 TH1 0xFD; // 9600bps 11.0592MHzSMOD0 TL1 0xFD; TR1 1; // 启动定时器1 SCON 0x50; // 方式1REN1允许接收 PCON 0x7F; // SMOD0波特率不加倍 ES 1; // 开串口中断 EA 1; // 开总中断 } void UART_SendByte(unsigned char dat) { SBUF dat; // 写入发送缓冲启动发送 while (!TI); // 等待发送完成 TI 0; // 清零发送中断标志 } void UART_ISR(void) interrupt 4 { if (RI) // 接收中断 { unsigned char dat SBUF; RI 0; // 软件清零否则反复进中断 // 在这里处理收到的数据 } if (TI) // 发送中断 { TI 0; // 软件清零 } }这段代码里有个细节容易踩坑发送函数用的是查询方式while(!TI)但ES又开着所以TI置1时一样会进中断中断服务程序里如果只处理RI不处理TI发送完一帧后TI没清程序就会反复跳进串口中断。所以ISR里RI和TI分支都要写完整。5.3 查询方式怎么用如果暂时不想用中断最简配置是SCON 0x50ES 0。发送还是上面的查询方式。收数据时主循环里轮询RIif (RI) { unsigned char dat SBUF; RI 0; UART_SendByte(dat); // 回显验证链路 }调试阶段我强烈建议先用查询方式把收发调通再改成中断方式。查询方式逻辑清楚出问题好定位中断方式效率高但新手刚上手容易被各种标志位绕晕。6. 常见问题与排查思路实录6.1 收不到数据收不到数据按这个顺序查REN是不是1ES和EA有没有开TR1有没有启动SCON的工作方式是不是自己以为的那个方式最后用示波器或者逻辑分析仪看RXD引脚上有没有实际波形。还有一个很隐蔽的坑不少开发板上串口引脚可能跟其他外设共用比如某个LED或者按键占了P3.0、P3.1或者USB转TTL模块的跳线帽没接对。芯片本身是好的但信号根本进不去。这种情况在硬件学习板上特别常见。6.2 收到乱码乱码优先怀疑波特率。对比晶振频率和TH1初值是否匹配对比上位机波特率设置是否一致对比PCON里的SMOD有没有被无意置1。这三个地方任何一处不一致结果都是乱码但具体是不是波特率问题需要逐项排除。第二个常见原因是没共地。USB转TTL模块和单片机开发板之间如果没有把GND连在一起信号电平没有共同参考基准数据都是乱的。这个问题在接外部模块时发生率极高。第三个原因是接反了线USB转TTL的TXD要接单片机的RXDRXD接单片机的TXD交叉连接很多同学这里会接成直连自然收不到。6.3 数据错位或者丢字节如果两边波特率有微小误差短时间通信看不出问题数据量一大就错位。解决方法就是选择能整除的晶振或者降低波特率。另一个原因是接收处理太慢中断服务程序里干了太多耗时的事情比如在中断里做数码管动态扫描导致下一帧到来时上一帧还没处理完数据就被新数据覆盖了。串口中断里尽量只做收数据和置标志位具体业务逻辑放到主循环里去跑。6.4 调试心得调试串口我的个人做法是分两层第一层先用查询方式做回环测试也就是收到什么发什么上位机能看到自己发出去的内容说明收发链路通了第二层再逐步加入业务逻辑比如解析固定的协议帧。回环测试通过后面的问题基本都是业务逻辑问题跟串口本身无关。另外提一个建议能打印调试信息是串口最大的价值。平时写代码养成往串口丢状态的习惯比如进入某个函数打印一个标志程序跑到哪里、状态对不对一眼就能看出来。第七天学到串口后面做综合项目时你才会发现这东西是调试利器比那几颗LED好用太多了。

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

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

免费获取报价 →
↑