1. 项目概述与核心价值
在嵌入式开发的江湖里,80C51系列单片机绝对是绕不开的“老前辈”。虽然如今各种ARM Cortex-M内核的MCU大行其道,但51内核因其结构简单、资料丰富、成本低廉,依然在大量对成本敏感、功能专一的工业控制、消费电子和教学领域占据一席之地。而在这个经典架构中,Timer 2和UART这对“黄金搭档”的协同工作,往往是项目成败的关键。Timer 2不仅仅是简单的计时工具,它集成了捕获、自动重载和波特率发生器三种模式,功能之强大远超Timer 0和Timer 1。UART则是设备间通信的“普通话”,其通信质量直接依赖于波特率的精准度。
很多工程师在初次接触80C51的Timer 2时,往往会被其寄存器配置和多种模式搞得晕头转向。数据手册上冰冷的文字和时序图,如果没有实际操作的“手感”和“踩坑”经验作为注解,理解起来总是隔着一层纱。这篇文章,我将结合自己十多年在工控和通信模块开发中积累的经验,为你彻底拆解80C51单片机中Timer 2与UART的协同工作机制。我们不仅会看懂手册上的原理图,更会深入到实际编程配置、参数计算和那些手册上不会写的调试技巧中去。无论你是正在学习51单片机的新手,还是需要在老项目中维护或优化代码的工程师,相信这篇从理论到实战的深度解析,都能让你对这对核心外设有全新的、透彻的认识。
2. Timer 2:超越基础定时的多功能引擎
与Timer 0和Timer 1相比,80C51的Timer 2是一个16位的定时器/计数器,它在标准51架构上进行了显著增强。理解它的关键在于掌握其三种核心工作模式:捕获模式、自动重载模式和波特率发生器模式。这三种模式分别应对了精确测量、周期性任务和通信时钟三大经典需求。
2.1 核心控制寄存器:T2CON与T2MOD
任何对Timer 2的操作都始于对控制寄存器的正确配置。T2CON(地址C8H)是主控制寄存器,每一位都至关重要。
// T2CON (地址 C8H) 位定义示例 (基于C语言描述) sfr T2CON = 0xC8; sbit TF2 = T2CON^7; // Timer 2 溢出标志 sbit EXF2 = T2CON^6; // Timer 2 外部标志 sbit RCLK = T2CON^5; // 接收时钟标志 sbit TCLK = T2CON^4; // 发送时钟标志 sbit EXEN2 = T2CON^3; // Timer 2 外部使能标志 sbit TR2 = T2CON^2; // Timer 2 启动/停止控制 sbit C_T2 = T2CON^1; // 定时器/计数器选择 sbit CP_RL2= T2CON^0; // 捕获/重载标志关键位解析与配置逻辑:
- TF2 (溢出标志):当Timer 2从FFFFH溢出到0000H时,硬件自动置1。注意:在波特率发生器模式下(RCLK或TCLK=1),此位不会被置位。需要软件清零。
- EXF2 (外部标志):在捕获或自动重载模式下,如果
EXEN2=1且外部引脚T2EX(P1.1)出现下降沿,此位置1。它和TF2共享同一个中断向量,需要在中断服务程序中查询是哪个标志触发了中断。重要提示:在自动重载且DCEN=1(双向计数)模式下,EXF2会在Timer 2上溢或下溢时翻转,但不会产生中断,此时它可以被当作第17位分辨率来使用。 - RCLK/TCLK (接收/发送时钟选择):这是Timer 2与UART联动的关键。置1时,UART的接收或发送时钟源将切换为Timer 2的溢出脉冲,否则使用Timer 1。这允许串口的收发使用不同的波特率。
- CP/RL2 (捕获/重载选择):此位与
RCLK/TCLK共同决定模式。0为自动重载,1为捕获。但有一个例外:只要RCLK或TCLK中任意一个为1,Timer 2就被强制为波特率发生器模式,此位被忽略。 - TR2:简单的启动/停止开关。
1启动,0停止。务必注意:在访问Timer 2的计数寄存器(TH2, TL2)或重载/捕获寄存器(RCAP2H, RCAP2L)之前,应先关闭Timer 2(TR2=0),尤其是在波特率发生器模式下,以避免读写时计数器正在变化导致数据错误。
另一个辅助寄存器是T2MOD(地址C9H),它主要控制两个高级功能。
sfr T2MOD = 0xC9; sbit T2OE = T2MOD^1; // Timer 2 输出使能 (某些型号有P1.0/T2引脚输出) sbit DCEN = T2MOD^0; // 向下计数使能- DCEN:这是Timer 2独有的魅力所在。默认为0,Timer 2向上计数。当
DCEN=1时,Timer 2变为可逆计数器,其计数方向由T2EX(P1.1)引脚的电平决定:高电平向上,低电平向下。这在电机控制、编码器计数等需要双向计数的场合非常有用。
2.2 捕获模式:精准的时刻“抓拍”
捕获模式的本质,是在一个特定事件(通常是外部引脚T2EX的下降沿)发生的瞬间,“冻结”并记录下Timer 2当前计数值(TH2和TL2),并将其保存到专用的捕获寄存器(RCAP2H和RCAP2L)中。这就像用高速相机抓拍运动物体的瞬间位置。
工作流程与配置:
- 模式选择:设置
T2CON寄存器,CP/RL2 = 1,选择捕获模式。 - 触发源选择:通过
C/T2选择时钟源。0为内部机器周期(振荡器频率/12或/6),1为外部T2(P1.0)引脚下降沿计数。 - 外部事件使能:
EXEN2位是关键。EXEN2 = 0:Timer 2仅作为普通16位定时器/计数器,溢出时置位TF2,可申请中断。EXEN2 = 1:Timer 2除了上述功能外,额外增加了捕获功能。当T2EX引脚出现下降沿时,硬件会立即将当前的(TH2, TL2)拷贝到(RCAP2H, RCAP2L),并置位EXF2标志。TF2和EXF2都可以触发同一个Timer 2中断。
- 启动:设置
TR2 = 1,Timer 2开始计数。
实战配置代码(捕获模式,测量脉冲宽度):假设我们需要测量一个输入到T2EX引脚的正脉冲宽度。
void Timer2_Capture_Init(void) { T2CON = 0x09; // 0000 1001B: 捕获模式(CP/RL2=1),允许T2EX触发(EXEN2=1),定时模式(C/T2=0),先停止(TR2=0) T2MOD = 0x00; // DCEN=0, 默认向上计数 TH2 = 0x00; // 计数器从0开始 TL2 = 0x00; RCAP2H = 0x00; // 捕获寄存器清零,实际无用,但习惯性初始化 RCAP2L = 0x00; ET2 = 1; // 允许Timer 2中断 EA = 1; // 开总中断 TR2 = 1; // 启动Timer 2 } void Timer2_ISR(void) interrupt 5 { // Timer 2中断向量号为5 unsigned int capture_value; if (TF2) { TF2 = 0; // 软件清除溢出标志 // 处理溢出,如果脉冲宽度可能超过65535个计数,需要软件扩展计数 } if (EXF2) { EXF2 = 0; // 软件清除外部标志 capture_value = (RCAP2H << 8) | RCAP2L; // 读取捕获到的时刻值 // 这里,capture_value就是下降沿发生时的Timer 2计数值 // 结合之前可能保存的上升沿捕获值,即可计算出脉冲宽度 // 注意:Timer 2在捕获后继续计数,不会停止或重置 } }关键经验:在捕获模式下,Timer 2的计数器是连续自由运行的,捕获动作不会影响其计数。因此,为了测量绝对时间间隔,你需要记录两次捕获事件之间的计数值差,并考虑可能发生的计数器溢出。如果脉冲很宽,可能发生多次
TF2溢出,需要在中断服务程序中用软件变量扩展为32位或更长的计数器。
2.3 自动重载模式:精准的周期性心跳
自动重载模式是产生精确时间基准最常用的模式。当计数器溢出(从FFFFH到0000H)时,硬件会自动将预装在(RCAP2H, RCAP2L)中的16位值重新加载到(TH2, TL2)中,然后从该值开始继续计数,从而实现周期性的溢出中断。
工作流程与配置:
- 模式选择:设置
T2CON,CP/RL2 = 0,选择自动重载模式。 - 计数方向:由
T2MOD的DCEN位和T2EX引脚共同决定。DCEN = 0(默认):Timer 2只能向上计数。溢出时,TF2置位,并触发重载。DCEN = 1:Timer 2变为双向计数器。T2EX引脚为高电平时向上计数,溢出时重载(RCAP2H, RCAP2L)的值;为低电平时向下计数,当(TH2, TL2)减到等于(RCAP2H, RCAP2L)时,发生“下溢”,TF2置位,并重载为FFFFH。EXF2在每次上溢或下溢时翻转。
- 重载触发:
EXEN2位控制是否允许外部引脚触发重载。EXEN2 = 0:仅溢出触发重载。EXEN2 = 1:溢出或T2EX下降沿均可触发重载,并置位EXF2。
- 计算重载值:这是核心步骤。假设系统时钟
Fosc = 12MHz,工作在12时钟模式(机器周期为1us),我们希望产生一个10ms的定时中断。- 所需计数值 = 定时时间 / 机器周期 = 10ms / 1us = 10000。
- 重载值 = 65536 - 所需计数值 = 65536 - 10000 = 55536 = 0xD8F0。
- 因此,
RCAP2H = 0xD8,RCAP2L = 0xF0。
实战配置代码(10ms定时中断):
void Timer2_AutoReload_Init(void) { // 计算重载值 55536 -> 0xD8F0 RCAP2H = 0xD8; RCAP2L = 0xF0; TH2 = RCAP2H; // 初始化计数器与重载值相同,第一次溢出时间准确 TL2 = RCAP2L; T2CON = 0x00; // 0000 0000B: 自动重载(CP/RL2=0),禁止外部触发(EXEN2=0),定时模式(C/T2=0),先停止(TR2=0) T2MOD = 0x00; // 向上计数 ET2 = 1; // 开Timer 2中断 EA = 1; TR2 = 1; // 启动定时器 } void Timer2_ISR(void) interrupt 5 { TF2 = 0; // 必须软件清除标志 // 每10ms执行一次的任务... }避坑指南:自动重载模式下,中断响应后必须手动清除
TF2标志。虽然重载是硬件自动完成的,但中断标志不会自动清除,如果忘记清零,会导致中断持续触发,程序卡死在中断中。这是一个非常常见且隐蔽的错误。
2.4 波特率发生器模式:UART的时钟心脏
这是Timer 2最独特且重要的模式之一。在此模式下,Timer 2的溢出脉冲不再产生中断标志TF2,而是直接作为UART的发送(TXD)和/或接收(RXD)时钟源。其最大的优点是能产生非常精确且不占用CPU中断资源的波特率。
工作原理的精髓:
- 模式激活:当
T2CON中的RCLK或TCLK任意一位为1时,Timer 2立即进入波特率发生器模式,CP/RL2位被忽略。 - 时钟源与计数行为改变:
- 当
C/T2=0(定时模式)时,其计数脉冲不再是标准的机器周期(Fosc/12),而是变成了振荡器频率的二分频(Fosc/2)。这是与普通定时模式最根本的区别,也是它能产生非标准波特率的关键。 - 计数溢出时,自动从(RCAP2H, RCAP2L)重载,但不置位
TF2,也不产生中断。
- 当
- 波特率计算公式:这是核心中的核心。
- 对于内部定时模式(
C/T2=0):波特率 = Fosc / [ n * (65536 - (RCAP2H, RCAP2L)) ]其中,n = 32(12时钟模式)或n = 16(6时钟模式)。 - 对于外部计数模式(
C/T2=1),时钟来自T2引脚:波特率 = (Timer 2溢出率) / 16Timer 2溢出率 = T2引脚输入频率 / [65536 - (RCAP2H, RCAP2L)]
- 对于内部定时模式(
- 重载值计算:由公式变形可得:
(RCAP2H, RCAP2L) = 65536 - Fosc / (n * 波特率)这里的(RCAP2H, RCAP2L)是一个16位无符号整数。
实战配置:用Timer 2产生9600波特率假设Fosc = 11.0592MHz(这是串口通信的经典晶振,因为它能产生精确的整数波特率),工作在12时钟模式。
n = 32- 计算重载值:
RCAP2 = 65536 - 11059200 / (32 * 9600) = 65536 - 36 = 65500 = 0xFFDC - 因此,
RCAP2H = 0xFF,RCAP2L = 0xDC。
配置代码:
void UART_Init_Timer2(void) { SCON = 0x50; // 串口模式1,8位UART,允许接收(REN=1) // 配置Timer 2为波特率发生器 T2CON = 0x34; // 0011 0100B: RCLK=1(接收用T2), TCLK=1(发送用T2), 忽略CP/RL2, EXEN2=0, TR2=1(启动) // 注意:这里直接设置了TR2=1,因为波特率发生器模式下对TH2/TL2的读写有限制,最好在启动前设置好重载值。 // 设置重载值产生9600波特率 (11.0592MHz, 12-clock) RCAP2H = 0xFF; RCAP2L = 0xDC; TH2 = RCAP2H; // 初始化计数器 TL2 = RCAP2L; // 不需要开Timer 2中断(ET2=0),因为在此模式下TF2不会置位 ES = 1; // 开串口中断(如果需要) EA = 1; }致命陷阱与操作铁律:在波特率发生器模式下,绝对不要在Timer 2运行(
TR2=1)时去读取或写入TH2和TL2!因为此时计数器由硬件以Fosc/2的高速驱动,你的读写操作可能会发生在计数器变化的瞬间,导致读到错误值或写入被破坏。同样,RCAP2H和RCAP2L可以安全读取,但写入也应在TR2=0时进行。标准操作流程是:停止Timer 2 (TR2=0) -> 修改重载值 (RCAP2H/L) -> 可选地同步计数器 (TH2=RCAP2H, TL2=RCAP2L) -> 启动Timer 2 (TR2=1)。
3. UART:异步串行通信的基石
80C51的UART是一个全双工、带接收缓冲的异步串行通信接口。其工作模式灵活,但最常用的是模式1(8位UART,可变波特率)和模式3(9位UART,可变波特率)。
3.1 串口控制寄存器SCON与模式解析
SCON(地址98H)是UART的总指挥。
sfr SCON = 0x98; sbit SM0 = SCON^7; // 与FE复用 sbit SM1 = SCON^6; sbit SM2 = SCON^5; sbit REN = SCON^4; sbit TB8 = SCON^3; sbit RB8 = SCON^2; sbit TI = SCON^1; sbit RI = SCON^0;- SM0/SM1:共同决定4种工作模式。
- SM2:多机通信使能位。在模式2和3中,当
SM2=1时,只有接收到的第9位数据(RB8)为1(表示地址帧)时,RI才会被置位,从而产生中断。这用于多主机-多从机网络中的地址过滤。 - REN:接收使能。
1允许接收,0禁止。 - TB8/RB8:在模式2/3中,分别是发送和接收的第9位数据。可用于奇偶校验或多机通信的地址/数据标识。
- TI/RI:发送/接收中断标志。必须由软件清零。这是新手最容易犯错的地方之一,忘记清零会导致中断持续触发。
四种模式速览:
- 模式0:同步移位寄存器模式。波特率固定为Fosc/12(12时钟模式)。数据从RXD进出,TXD输出移位时钟。常用于扩展I/O口(如74HC595)。
- 模式1:最常用的8位UART。10位帧:1起始位(0) + 8数据位(LSB先) + 1停止位(1)。波特率可变,由Timer 1或Timer 2溢出率决定。
- 模式2:9位UART。11位帧:1起始位 + 8数据位 + 1可编程第9位 + 1停止位。波特率固定为Fosc/64或Fosc/32(由PCON中的SMOD位决定)。
- 模式3:9位UART。帧结构同模式2,但波特率可变,同模式1。模式2和3的第9位(TB8/RB8)为实现多机通信或硬件奇偶校验提供了可能。
3.2 波特率生成的两种方案:Timer 1 vs Timer 2
为UART提供波特率时钟是Timer的核心任务之一。80C51提供了Timer 1和Timer 2两个选择。
方案一:使用Timer 1(经典但有限制)这是最传统的方法。通常将Timer 1配置为模式2(8位自动重载),以产生稳定的溢出率。
- 波特率公式:
波特率 = (2^SMOD / 32) * (Fosc / (12 * [256 - TH1]))(12时钟模式) - 缺点:
- 误差:由于公式中除法的存在,对于很多标准波特率(如9600),使用11.0592MHz晶振才能得到精确值,使用12MHz晶振会有误差。
- 占用中断:如果Timer 1用于波特率发生器,它就不能再用于其他定时任务(除非用模式1做16位定时并软件重载,但会引入抖动)。
- 收发同源:接收和发送必须使用同一个波特率。
方案二:使用Timer 2(推荐,高精度且灵活)如前所述,Timer 2的波特率发生器模式是更优的选择。
- 优点:
- 高精度:计算公式更直接,即使使用12MHz晶振,也能为许多波特率计算出整数重载值,误差极小。
- 独立时钟:通过设置
RCLK和TCLK,可以让接收和发送使用不同的波特率(例如,接收用Timer 2的9600,发送用Timer 1的19200),这在某些特殊协议中非常有用。 - 不占用中断:作为波特率发生器时,Timer 2不置位
TF2,不产生中断,可以“安静”地工作。 - 释放Timer 1:Timer 1可以被解放出来用于其他PWM、输入捕获等任务。
选择建议:在新设计或对波特率精度有要求的项目中,优先使用Timer 2作为波特率发生器。Timer 1留作它用。
3.3 增强型UART功能:帧错误检测与自动地址识别
一些增强型的80C51变种(如提供的资料中提到的Philips型号)提供了非常有用的增强功能。
帧错误检测 (Framing Error Detection)在标准UART中,如果接收方由于噪声或波特率失配未能正确检测到停止位(应为高电平,但采样到低电平),这个错误往往难以被察觉。增强型UART通过FE位(与SM0复用,由PCON.6即SMOD0选择)来标记此类错误。
- 当
SMOD0=1时,SCON.7作为FE位。如果接收到的停止位为0,硬件会自动将FE置1。 FE位不会被有效的帧自动清除,必须由软件清零。- 应用价值:在噪声环境或通信双方时钟有轻微偏差时,帧错误检测能有效提示通信链路质量,便于上层协议采取重发等纠错措施。
自动地址识别 (Automatic Address Recognition)这是多机通信的硬件加速器。在模式2或3(9位数据)下,当SM2=1时,从机UART硬件会自动比较接收到的地址字节与自身预设的地址。
- 需要配置两个特殊功能寄存器:
SADDR(从机地址)和SADEN(地址掩码)。 SADEN用于定义SADDR中哪些位是必须匹配的(掩码位为1),哪些是“无关位”(掩码位为0)。逻辑与SADDR & SADEN得到“给定地址”。- 只有当接收到的第9位(RB8)为1(表示是地址帧)且接收到的8位地址与“给定地址”匹配时,
RI才会被置位,产生中断。 - 从机在中断中判断是本机地址后,清除
SM2位,准备接收后续的数据帧(此时RB8=0的数据帧也能触发中断)。数据接收完毕后,再置位SM2,等待下一个地址帧。 - 优势:极大减轻了CPU负担。主设备广播地址帧时,所有从设备硬件比较地址,只有匹配的从机才会被中断,CPU无需软件轮询判断每一个地址,提高了系统效率。
4. 从理论到实践:一个完整的串口通信工程示例
让我们整合以上知识,构建一个实际的应用场景:一个基于80C51的设备,使用Timer 2产生精确的115200波特率进行串口通信,并利用自动重载模式实现一个1ms的系统时钟节拍。
4.1 系统设计与初始化
目标:
- UART:模式1,8位数据,无校验,1停止位,波特率115200,使用Timer 2作为波特率发生器。
- Timer 2:同时作为系统1ms定时器(自动重载模式)和UART波特率发生器。注意:这是不可能的!一个Timer 2不能同时用于两种模式。因此我们需要调整设计。修正设计:
- Timer 2:专用于UART波特率发生器(模式)。
- Timer 0或Timer 1:用于产生1ms系统时钟节拍。这里我们选择Timer 0,模式1(16位定时),中断实现软件重载。
假设条件:Fosc = 11.0592MHz,12时钟模式。
计算与配置:
Timer 2 波特率计算 (115200):
- 公式:
RCAP2 = 65536 - Fosc / (32 * Baud) RCAP2 = 65536 - 11059200 / (32 * 115200) = 65536 - 3 = 65533 = 0xFFFDRCAP2H = 0xFF,RCAP2L = 0xFD- 验证:
Baud = 11059200 / (32 * (65536-65533)) = 11059200 / 96 = 115200。完美匹配。
- 公式:
Timer 0 1ms定时计算:
- 机器周期 = 12 / 11.0592MHz ≈ 1.085us。
- 1ms需要的机器周期数 = 1000us / 1.085us ≈ 922。
- Timer 0初值 = 65536 - 922 = 64614 = 0xFC66。
TH0 = 0xFC,TL0 = 0x66。
4.2 代码实现与详细注释
#include <reg52.h> // 包含80C51 SFR定义的头文件 #define FOSC 11059200L #define BAUD 115200L // 全局变量 unsigned int system_ticks = 0; // 系统滴答计数器 /* 函数声明 */ void UART_Init(void); void Timer0_Init(void); void UART_SendByte(unsigned char dat); void UART_SendString(const char *str); /** * @brief 串口初始化函数,使用Timer 2作为波特率发生器 * @param 无 * @retval 无 */ void UART_Init(void) { // 1. 配置串口模式 SCON = 0x50; // 0101 0000B: 模式1 (8位UART), 允许接收(REN=1) // 2. 配置Timer 2为波特率发生器 (必须先停止定时器!) T2CON &= 0xFD; // 清除TR2,停止Timer 2 (T2CON.2 = 0) // 设置重载值以产生115200波特率 RCAP2H = (65536 - (FOSC/(32L*BAUD))) >> 8; // 计算高字节 RCAP2L = (65536 - (FOSC/(32L*BAUD))) & 0x00FF; // 计算低字节 TH2 = RCAP2H; // 初始化计数器 TL2 = RCAP2L; // 3. 启动Timer 2为波特率发生器,收发均使用T2 T2CON = 0x34; // 0011 0100B: RCLK=1, TCLK=1, 忽略CP/RL2, EXEN2=0, TR2=1(启动) // 4. 使能串口中断(如果需要中断方式接收) // ES = 1; // EA = 1; } /** * @brief Timer 0初始化,用于产生1ms系统时钟节拍 * @param 无 * @retval 无 */ void Timer0_Init(void) { // 配置Timer 0为模式1,16位定时器 TMOD &= 0xF0; // 清零Timer 0相关位 (TMOD低4位) TMOD |= 0x01; // 设置Timer 0为模式1 (0000 0001B) // 设置1ms定时初值 (Fosc=11.0592MHz) TH0 = 0xFC; TL0 = 0x66; // 启动Timer 0并开启中断 TR0 = 1; ET0 = 1; EA = 1; } /** * @brief Timer 0中断服务函数 * @param 无 * @retval 无 */ void Timer0_ISR(void) interrupt 1 { // 重载1ms初值 TH0 = 0xFC; TL0 = 0x66; // 系统滴答计数器递增 system_ticks++; } /** * @brief 串口发送一个字节(查询方式) * @param dat: 要发送的数据字节 * @retval 无 */ void UART_SendByte(unsigned char dat) { SBUF = dat; // 将数据写入发送缓冲区,启动发送 while(TI == 0); // 等待发送完成标志 TI = 0; // **必须软件清零** } /** * @brief 串口发送字符串 * @param str: 要发送的字符串指针 * @retval 无 */ void UART_SendString(const char *str) { while(*str != '\0') { UART_SendByte(*str++); } } /** * @brief 主函数 */ void main(void) { UART_Init(); // 初始化串口 Timer0_Init(); // 初始化系统时钟 UART_SendString("System Booted.\r\n"); while(1) { // 主循环,基于system_ticks实现定时任务 static unsigned int last_tick = 0; if(system_ticks - last_tick >= 1000) { // 约1秒 last_tick = system_ticks; UART_SendString("System running... Ticks: "); // 这里可以添加发送system_ticks值的代码(需转换为字符串) UART_SendString("\r\n"); } // 其他任务... } } /** * @brief 串口中断服务函数(示例,查询方式接收) * @note 如果使用中断接收,需将ES=1,并实现此函数 */ /* void UART_ISR(void) interrupt 4 { if(RI) { RI = 0; // 清除接收中断标志 unsigned char received_data = SBUF; // 读取接收到的数据 // 处理接收到的数据... UART_SendByte(received_data); // 示例:回显 } if(TI) { // 发送中断,如果使用中断发送需要处理 TI = 0; } } */4.3 调试心得与避坑指南
波特率不准?先查晶振和模式:
- 确保实际使用的晶振频率与代码中
FOSC定义一致。11.0592MHz和12MHz计算结果天差地别。 - 确认单片机是工作在6时钟模式还是12时钟模式。这直接影响
n的值(32或16)。很多新型号51单片机默认是6时钟模式(速度更快),如果误用12时钟的公式,波特率会差一倍。 - 使用示波器测量TXD引脚输出的波形,计算实际比特宽度,是验证波特率最直接的方法。
- 确保实际使用的晶振频率与代码中
通信乱码或丢数据:
- 检查中断冲突:如果UART和定时器都用了中断,确保中断服务函数执行时间足够短,没有阻塞太久导致丢失后续数据或定时不准。特别是串口中断中不要做复杂运算或长时间延时。
- 检查缓冲区:80C51的UART只有一个字节的硬件接收缓冲区。如果采用查询方式,必须在下一个字节到来前读取
SBUF。中断方式更可靠,但中断服务程序必须迅速读取数据并存入自定义的软件环形缓冲区。 - 检查
TI/RI标志清除:这是最常见错误!发送和接收中断标志必须在中断服务程序中用软件清零。
Timer 2配置的“顺序”很重要:
- 对于波特率发生器,推荐的初始化顺序是:停止Timer 2 (
TR2=0) -> 设置重载值(RCAP2H/L) -> 可选设置计数器(TH2/TL2) -> 配置T2CON(包含启动位TR2=1)。这样可以避免在计数器运行时写入寄存器。 - 不要在程序中随意开关
TR2来调整波特率,如果必须动态修改,务必遵循“先停后改再启”的原则。
- 对于波特率发生器,推荐的初始化顺序是:停止Timer 2 (
多机通信的要点:
- 如果使用自动地址识别,务必正确配置
SADDR和SADEN。SADEN的“无关位”设置为0,这给了地址分配很大的灵活性。 - 从机在接收到地址帧并确认匹配后,一定要清除
SM2位,否则将无法接收后续的数据帧(因为数据帧的RB8=0)。数据接收完成后,应立即将SM2置回1,等待下一个地址帧。
- 如果使用自动地址识别,务必正确配置
功耗与EMI考虑:
- 资料中提到可以通过设置
AUXR寄存器的AO位来关闭ALE信号输出,这在不需要外部扩展存储器且对电磁干扰敏感的应用中能降低噪声。 - 在低速或空闲时,可以考虑进入空闲或掉电模式,并通过串口或外部中断唤醒,以大幅降低系统功耗。
- 资料中提到可以通过设置
通过以上从寄存器位到代码行,从理论公式到调试技巧的层层剖析,相信你已经对80C51的Timer 2和UART这对核心外设有了立体而深入的理解。它们不仅仅是数据手册上的几段描述,更是构建稳定、可靠嵌入式系统的基石。掌握其精髓,就能在资源有限的51平台上,游刃有余地应对各种定时与通信挑战。