1. 项目概述与核心价值
在嵌入式开发的江湖里,P87LPC760这颗14脚的小芯片,对于很多做低成本、低功耗项目的朋友来说,绝对是个“老熟人”。它虽然个头小,但五脏俱全,尤其是它那套源自经典80C51架构的定时器和UART串口,是无数工控板、智能传感器、小家电控制板里的“心脏”和“嘴巴”。我这些年经手过不少用它做的项目,从简单的延时闪烁LED,到复杂的多机Modbus通信,都离不开对这两个核心外设的深度理解和精准配置。
很多人拿到芯片手册,看到TMOD、TCON、SCON这些寄存器,还有一堆模式、公式,头就大了。手册讲的是“是什么”,但实际开发中,我们更关心“怎么用”和“为什么这么用”。比如,为什么我的串口数据老是收不全?定时器中断怎么算都不准,差了几个毫秒?多机通信时,从机怎么老是不响应?这些问题,手册不会直接告诉你答案,得靠经验去踩坑、去调试。
这篇文章,我就结合自己十多年的实战经验,把P87LPC760的定时器和UART串口通信,从寄存器配置、工作模式原理,到波特率计算、多机通信实现,掰开揉碎了讲清楚。我会重点分享那些手册里一笔带过,但在实际调试中能让你少走弯路的细节和技巧。无论你是刚接触单片机的新手,还是想深入了解80C51内核外设的老手,相信都能从中找到可以直接“抄作业”的干货。
2. 定时器/计数器深度解析与模式实战
P87LPC760有两个通用定时器/计数器:Timer 0和Timer 1。它们向上兼容标准的80C51定时器,但增加了一个很实用的功能:Timer 0溢出时可以自动翻转T0引脚的电平。这个功能对于生成方波或者简单的PWM信号特别方便。
2.1 核心寄存器:TMOD与TCON
配置定时器,首先得搞定两个特殊功能寄存器(SFR):TMOD(模式控制)和TCON(控制)。
TMOD (地址:89h) - 定时器模式控制寄存器这个寄存器是只写的,不能位寻址。它决定了两个定时器的工作模式(Mode)和功能选择(Timer还是Counter)。
| 位 | 符号 | 功能说明 |
|---|---|---|
| 7,6 | - | 保留位。必须写0。 |
| 5,4 | M1, M0 (T1) | Timer 1模式选择。00=模式0,01=模式1,10=模式2,11=模式3(Timer 1停止)。 |
| 3 | GATE (T0) | Timer 0门控位。0=仅由TR0软件控制启停;1=由TR0和INT0引脚共同控制(TR0=1且INT0=1时启动)。这个功能常用于脉冲宽度测量。 |
| 2 | C/T (T0) | Timer 0功能选择。0=定时器模式(对内部系统时钟计数);1=计数器模式(对T0引脚外部脉冲计数)。 |
| 1,0 | M1, M0 (T0) | Timer 0模式选择。同Timer 1。 |
TCON (地址:88h) - 定时器控制寄存器这个寄存器可以位寻址,方便我们单独操作某一位。它主要负责控制定时器的运行和标志中断。
| 位 | 符号 | 功能说明 |
|---|---|---|
| 7 | TF1 | Timer 1溢出标志。硬件在Timer 1溢出时置1。进入中断服务程序后由硬件清零,或由软件清零。 |
| 6 | TR1 | Timer 1运行控制。软件置1启动Timer 1,清零停止Timer 1。 |
| 5 | TF0 | Timer 0溢出标志。功能同TF1。 |
| 4 | TR0 | Timer 0运行控制。功能同TR1。 |
| 3,2 | - | 保留位(必须为0)。 |
| 1 | IE0 | 外部中断0边沿标志(与定时器无关,但同属TCON)。 |
| 0 | IT0 | 外部中断0类型控制(与定时器无关)。 |
实操心得1:寄存器操作的“坑”新手常犯两个错误:一是混淆TMOD和TCON的功能,TMOD管“怎么干”(模式),TCON管“干不干”(启停和标志);二是忽略了TMOD的高4位(D7-D4)控制T1,低4位(D3-D0)控制T0。一次性配置两个定时器时,最好用
TMOD = 0x21;这样的赋值语句,而不是单独位操作,因为TMOD不能位寻址。例如,TMOD = 0x01;表示T0为模式1(16位定时器),T1为模式0(13位定时器)。
2.2 四大工作模式详解与选型指南
P87LPC760的定时器有4种工作模式(0-3),模式3比较特殊。
模式0:13位定时器/计数器这是为了兼容老旧的8048单片机而保留的模式。它把TLn的低5位和THn的8位组合成一个13位的计数器。TLn的高3位没用,读出来是随机值,忽略即可。
- 计数范围:0 ~ 8191 (2^13 - 1)。
- 应用场景:现在基本不用了,因为模式1更直观。如果你接手一个古老的、用8048代码移植过来的项目,可能会遇到。
- 注意事项:由于是13位,计算初值时比较别扭。例如,要定时X个机器周期,初值 = 8192 - X。但X必须是整数,且要注意TH和TL的赋值顺序(先TL后TH,防止在赋值间隙产生不必要的溢出)。
模式1:16位定时器/计数器这是最常用、最经典的定时模式。THn和TLn全部16位参与计数。
- 计数范围:0 ~ 65535 (2^16 - 1)。
- 应用场景:需要较长定时的场合。比如用12MHz晶振,机器周期为1μs,最大定时约65.535ms。通过软件累计中断次数,可以实现更长的定时(秒、分钟级)。
- 初值计算:这是重点。假设系统时钟频率为
Fosc,则机器周期T_machine = 12 / Fosc(对于标准80C51内核,P87LPC760的CPU时钟分频可能不同,需查手册确认分频系数,通常为6或12)。定时时间T所需计数值N = T / T_machine。定时器初值X = 65536 - N。然后THn = X / 256; TLn = X % 256;。 - 示例:Fosc=11.0592MHz(常用串口波特率晶振),假设机器周期为12个时钟(需确认),则
T_machine = 12 / 11.0592MHz ≈ 1.085μs。要定时50ms,N = 50000μs / 1.085μs ≈ 46080。X = 65536 - 46080 = 19456 (0x4C00)。所以TH0 = 0x4C; TL0 = 0x00;。
模式2:8位自动重载定时器/计数器这是产生固定频率脉冲或作为串口波特率发生器的首选模式。
- 工作原理:TLn作为8位计数器,THn存放重载值。当TLn计数溢出(从FF到00)时,不仅会置位TFn,还会自动将THn的值重新装入TLn,然后TLn从该值开始继续计数。THn的值保持不变。
- 计数范围:0 ~ 255 (2^8 - 1)。
- 优点:无需在中断服务程序中重装初值,定时非常精确,没有因软件重装初值引入的时间误差。
- 应用场景:
- 串口波特率发生器:这是模式2最典型的应用,后面UART部分会详细讲。
- 产生固定频率的脉冲或PWM:例如,用T0溢出自动翻转引脚的功能,结合模式2,可以轻松产生占空比50%的方波。
- 初值计算:
X = 256 - N,其中N是计数值。THn = X; TLn = X;(初始化时也要给TLn赋值)。
模式3:双8位定时器模式(仅Timer 0)这是一个“应急”模式,把Timer 0拆成两个独立的8位定时器。
- TL0:占用原Timer 0的全部资源(C/T, GATE, TR0, TF0, INT0引脚)。
- TH0:固定为定时器模式(对机器周期计数),但借用了Timer 1的控制位TR1和溢出标志TF1。
- Timer 1:在Timer 0处于模式3时,Timer 1停止计数。但它仍然可以设置成模式2,作为串口的波特率发生器(只要不开启它的中断即可)。
- 应用场景:当你需要三个定时器,但芯片只有两个时,可以用这个模式救急。但代价是失去了一个完整的16位定时器(Timer 0),且TH0没有外部计数功能。除非万不得已,否则不建议使用模式3。
实操心得2:模式选择与中断处理
- 长定时用模式1:记得在中断服务程序(ISR)中手动重装初值。重装顺序建议先
TL后TH,防止在赋值高字节后、低字节前,低字节已经溢出(虽然概率极低,但严谨的程序员会考虑)。- 精确定时/波特率用模式2:初始化时设置好
THn,之后就一劳永逸。中断服务程序里只需要清标志位,不用重装初值,代码简洁,定时精度高。- 中断标志位:
TF0和TF1在CPU响应中断、跳转到中断向量地址时,硬件会自动清零。但如果你用查询方式(而不是中断方式)使用定时器,就必须在软件中手动清零这些标志位。- 门控位GATE的妙用:将GATE置1,可以让定时器的启动受
INT0引脚电平控制。这可以用来测量外部正脉冲的宽度。方法是:设置TR0=1,GATE=1。当INT0引脚为高电平时,定时器开始计数;INT0变低时,停止计数。读取TH0、TL0的值,再乘以机器周期,就是脉冲宽度。这在测速、编码器计数等场景非常有用。
2.3 Timer 0溢出翻转输出功能
这是P87LPC760相对于标准80C51的一个增强功能。通过设置P2M1寄存器中的T0OE位,可以使Timer 0每次溢出时,自动翻转T0引脚(与计数器输入引脚复用)的电平。
- 有什么用?无需软件干预,直接生成一个频率固定的方波。方波频率 = Timer 0溢出频率 / 2。结合模式2(自动重载),可以产生非常稳定且不占用CPU资源的PWM或时钟信号。
- 配置步骤:
- 将T0引脚配置为准双向口或推挽输出(通过PxM1, PxM2寄存器)。
- 设置
P2M1寄存器中的T0OE位为1。 - 配置Timer 0为定时器模式,并设置好合适的初值(决定方波频率)。
- 启动Timer 0。
- 注意:启用此功能后,该引脚就不能再作为外部计数输入了。
3. UART串口通信原理与配置精讲
UART(通用异步收发器)是嵌入式系统中最基础、最常用的通信接口。P87LPC760的UART是80C51的增强版,支持帧错误检测和自动地址识别,非常实用。
3.1 UART的四种工作模式
串口模式由SCON寄存器中的SM0和SM1位决定。
| SM0 | SM1 | 模式 | 功能描述 | 波特率 |
|---|---|---|---|---|
| 0 | 0 | 0 | 同步移位寄存器模式 | 固定为Fcpu / 6 |
| 0 | 1 | 1 | 8位UART(最常用) | 可变,由Timer 1溢出率决定 |
| 1 | 0 | 2 | 9位UART | 固定为Fcpu / 32或Fcpu / 16 |
| 1 | 1 | 3 | 9位UART | 可变,由Timer 1溢出率决定 |
模式0:同步移位寄存器模式这不是真正的异步串行通信,而是同步串行扩展模式。
- 数据线:RxD (P3.0) 用于数据输入/输出,TxD (P3.1) 输出同步移位时钟。
- 数据格式:8位数据,LSB(最低位)先发,无起始位和停止位。
- 应用场景:用于扩展并行I/O口,比如连接74HC595(串入并出)或74HC165(并入串出)芯片。波特率固定且很高(Fcpu/6),适合短距离板级芯片间通信。
- 注意事项:此模式下,串口占用的是P3.0和P3.1的替代功能,与标准UART的引脚是复用的。
模式1:8位UART模式(最常用)这是我们平时说的“串口”最典型的模式。
- 帧格式:1位起始位(0) + 8位数据位(LSB先发) + 1位停止位(1)。共10位。
- 波特率:可变,由Timer 1的溢出率决定。这是配置的重点和难点。
- SCON寄存器相关位:
REN:接收使能。必须置1才能接收数据。RB8:在模式1中,如果SM2=0,RB8存放的是接收到的停止位。这可以用来做简单的帧校验。TI:发送中断标志。一帧数据发送完成后,由硬件置1。必须由软件清零。RI:接收中断标志。一帧数据接收完成(在停止位中间)时,由硬件置1。必须由软件清零。
模式2和模式3:9位UART模式这两种模式的帧格式相同:1起始位 + 8数据位 + 1可编程第9位 + 1停止位。共11位。
- 第9位(TB8/RB8):发送时,由
SCON寄存器的TB8位提供;接收时,存放到SCON的RB8位。这个位常用于多机通信或奇偶校验。 - 模式2与模式3的唯一区别:波特率生成方式不同。
- 模式2:波特率固定,为
Fcpu / 32或Fcpu / 16,由PCON寄存器中的SMOD1位选择(0对应/32,1对应/16)。 - 模式3:波特率可变,由Timer 1的溢出率决定,计算方式同模式1。
- 模式2:波特率固定,为
- 应用场景:主要用于多机通信。主机通过发送地址帧(第9位=1)唤醒所有从机,数据帧(第9位=0)则只被已寻址的从机接收。
3.2 核心寄存器:SCON与PCON
SCON (地址:98h) - 串口控制寄存器这是串口的大脑,控制工作模式、使能接收、存放第9位和中断标志。
| 位 | 符号 | 功能说明 |
|---|---|---|
| 7 | SM0 / FE | 双功能位。当PCON中的SMOD0=0时,它是SM0,用于选择模式。当SMOD0=1时,它是FE(帧错误标志),当检测到无效停止位时由硬件置1,必须软件清零。 |
| 6 | SM1 | 与SM0共同选择串口模式。 |
| 5 | SM2 | 多机通信使能位。在模式2/3下:1=只有收到第9位(RB8)为1(地址帧)时才置位RI;0=任何数据都置位RI。在模式1下:1=只有收到有效停止位才置位RI(用于帧校验)。模式0下应设为0。 |
| 4 | REN | 接收使能。1=允许接收;0=禁止接收。 |
| 3 | TB8 | 在模式2/3中,这是要发送的第9位数据。可由软件置1或清零,常用于标识地址/数据帧或奇偶校验位。 |
| 2 | RB8 | 在模式2/3中,这是接收到的第9位数据。在模式1中(若SM2=0),它存放接收到的停止位。 |
| 1 | TI | 发送中断标志。发送完一帧数据后硬件置1。必须软件清零。 |
| 0 | RI | 接收中断标志。接收完一帧数据后硬件置1。必须软件清零。 |
PCON (地址:87h) - 电源控制寄存器它的最高位SMOD1(有的资料也叫SMOD)直接影响串口模式2的波特率,以及模式1/3下波特率计算公式中的倍增因子。
SMOD1 = 0:波特率不加倍。SMOD1 = 1:波特率加倍。- 在模式2:波特率从
Fcpu/32变为Fcpu/16。 - 在模式1/3:波特率计算公式中的除数从192变为96(见下文)。
- 在模式2:波特率从
实操心得3:TI和RI标志位的“坑”这是新手最容易出错的地方!
TI和RI都是硬件置1,软件清零。常见的错误做法:
- 在发送函数里先清零TI再发送:
TI = 0; SBUF = data;这是错的!因为此时数据还没开始发送,TI本来就是0。正确的顺序是:SBUF = data; while(!TI); TI = 0;即发送后等待发送完成,再清零标志。- 在中断服务程序里忘了清零TI/RI:如果使用中断方式,必须在中断服务程序中用软件将已响应的中断标志清零,否则会连续进入中断。
- 查询RI时用
if(RI)而不是while(!RI):在接收时,我们通常用while(!RI);来等待一帧数据接收完成。如果用if(RI),程序不会等待,会直接跳过。- FE帧错误标志:这是一个非常实用的增强功能。当使能FE功能(
SMOD0=1)后,如果接收到的字符缺少有效的停止位(比如波特率不匹配、线路干扰),SCON.7(FE)会被置1。这比单纯依靠RB8(停止位)来判断帧完整性更可靠。记得在接收处理中检查并清零FE位。
3.3 波特率计算:理论与实战查表
波特率配置是串口通信稳定的基石。P87LPC760的波特率生成依赖于系统时钟Fosc(注意,这里是CPU时钟频率,可能等于外部晶振频率,也可能经过内部分频,需查具体型号手册确认分频关系,下文假设Fosc为系统时钟频率)。
1. 模式0波特率固定为Fosc / 6。例如,Fosc = 11.0592MHz,则波特率 = 1843200 bps。非常高,仅用于同步移位模式。
2. 模式2波特率固定为(2^SMOD1 / 32) * Fosc。
- 当
SMOD1 = 0时,波特率 =Fosc / 32 - 当
SMOD1 = 1时,波特率 =Fosc / 16例如,Fosc = 11.0592MHz,SMOD1=1,则波特率 = 691200 bps。
3. 模式1和模式3波特率(最复杂,也最常用)波特率由Timer 1的溢出率决定。公式为:波特率 = (2^SMOD1 / 32) * (Timer1溢出率)而Timer 1的溢出率,又取决于它的工作模式。最常用、最推荐的方式是将Timer 1配置为模式2(8位自动重载)的定时器模式。此时:Timer1溢出率 = Fosc / (12 * (256 - TH1))// 假设机器周期为12个时钟 代入波特率公式:波特率 = (2^SMOD1 / 32) * [Fosc / (12 * (256 - TH1))]化简后得到:TH1 = 256 - Fosc * (2^SMOD1) / (384 * 波特率)
实战查表法:手动计算容易出错,且TH1必须是整数,否则会产生误差。因此,工程师们总结出了常用晶振频率下的最佳TH1值。原厂数据手册中的表格(Tables 9 & 10)就是这个目的。我们以最常用的11.0592MHz晶振为例,列出几个标准波特率的配置:
| 期望波特率 | SMOD1 | Timer 1模式 | TH1重载值 | 实际波特率 | 误差 |
|---|---|---|---|---|---|
| 9600 | 0 | 2 | 0xFD (253) | 9600 | 0% |
| 19200 | 0 | 2 | 0xFA (250) | 19200 | 0% |
| 38400 | 0 | 2 | 0xF3 (243) | 38400 | 0% |
| 57600 | 1 | 2 | 0xFF (255) | 57600 | 0% |
| 115200 | 1 | 2 | 0xFD (253) | 115200 | 0% |
为什么是11.0592MHz?这是一个“魔法”频率。将它代入上面的公式计算
TH1时,对于9600, 19200, 38400, 57600, 115200这些标准波特率,TH1恰好是整数,因此波特率误差为0。如果用12MHz晶振,计算出的TH1往往不是整数,取整后会产生误差,在高速通信时可能导致数据错误。所以,在需要串口通信的项目中,11.0592MHz晶振是首选。
配置步骤总结(以模式1,9600波特率,11.0592MHz晶振为例):
- 确定Timer 1工作模式:模式2,自动重载。
TMOD的高4位设为0010B,即TMOD |= 0x20;(注意不要影响T0的配置)。 - 计算并设置TH1:查表得
TH1 = 0xFD。TL1也赋相同的值(虽然模式2下TL1溢出后会被TH1自动重载,但初始化时赋值是个好习惯)。 - 设置SMOD1:对于9600,
SMOD1=0。PCON &= 0x7F;(将最高位清零)。 - 配置串口模式:模式1,8位UART。
SCON = 0x50;(0101 0000B,即SM0=0, SM1=1, REN=1允许接收)。 - 启动Timer 1:
TR1 = 1;。 - (可选)开启中断:如果需要中断收发,设置
ES = 1;(串口中断使能)和EA = 1;(总中断使能)。
4. 多机通信与帧错误检测实战
P87LPC760的UART在模式2和模式3下支持硬件级的多机通信,这是一个非常高效的功能,避免了软件解析地址的复杂性和延时。
4.1 多机通信协议原理
其核心是利用了第9位数据(TB8/RB8)和SCON寄存器中的SM2位。
- 从机初始状态:所有从机的串口初始化为模式2或3,并设置
SM2=1,REN=1。此时,从机只有收到第9位为1的帧(地址帧)时,才会置位RI产生中断。对于第9位为0的帧(数据帧),硬件直接忽略,不产生中断。 - 主机寻址:主机要发送数据给某个从机前,先发送一帧地址帧。这帧数据的第9位
TB8设置为1,数据位内容为从机地址(例如0x01)。 - 从机响应:所有从机都收到这个地址帧,并产生中断。在中断服务程序中,每个从机将接收到的地址(在
SBUF中)与自己的地址比较。 - 目标从机准备接收数据:地址匹配的从机,将自己的
SM2位清零。这样,它就能接收后续第9位为0的数据帧了。 - 主机发送数据:主机接着发送数据帧,此时
TB8设为0。 - 数据接收:只有
SM2=0的目标从机能收到数据帧并产生中断。其他SM2仍为1的从机则忽略这些数据帧。 - 通信结束:本次通信结束后,目标从机应重新将
SM2置1,恢复为只监听地址帧的状态。
4.2 帧错误检测(FE)功能应用
帧错误标志FE(SCON.7)是一个强大的诊断工具。要使用它,需要先将PCON寄存器中的SMOD0位设为1。
- 何时置位:当UART接收器在预期的停止位位置检测到的是0(而不是1)时,
FE位被硬件置1。这通常意味着:- 通信双方波特率不匹配。
- 线路受到严重干扰。
- 发送方未正确发送停止位。
- 如何使用:
- 初始化时,设置
PCON |= 0x40;(SMOD0=1),使能FE功能。 - 在接收中断或查询到
RI=1后,先检查FE位。 - 如果
FE=1,说明这一帧数据可能损坏,应丢弃。然后必须用软件清零FE位:SCON &= 0x7F;。 - 如果
FE=0,则可以安全读取SBUF中的数据。
- 初始化时,设置
- 优势:比单纯检查
RB8(停止位)更可靠,因为FE是专门为检测帧结构错误设计的。
实操心得4:多机通信的稳定性技巧
- 地址帧与数据帧的间隔:主机发送完地址帧后,最好延迟几个字节的时间(例如,等待从机处理完地址比较并清除SM2),再发送数据帧。否则,第一个数据帧可能会被尚未准备好(SM2还未清零)的目标从机丢失。
- 广播地址:可以定义一个特殊的广播地址(如0xFF)。当从机收到广播地址时,不清零SM2,而是以SM2=1的状态接收后续数据。这样可以实现主机向所有从机广播消息。
- 从机状态恢复:目标从机在完成一包数据接收后,不要立即将SM2置1。应该等待一个通信超时(例如,10ms内未收到新数据),再恢复SM2=1。防止主机连续发送多包数据时被中断。
- FE与多机通信的结合:在多机通信中,也可以使能FE。如果从机收到一个FE错误的地址帧,可以直接忽略,避免因干扰导致的误寻址。
5. 完整示例代码与调试技巧
理论讲完了,我们来点实际的。下面是一个完整的示例,演示如何初始化P87LPC760的Timer 0用于50ms定时中断,以及UART以9600波特率进行中断收发,并包含简单的多机通信框架和帧错误处理。
#include <reg76x.h> // 包含P87LPC760的特殊功能寄存器定义 #define SLAVE_ADDR 0x02 // 本从机地址 unsigned char UART_Rx_Buffer[32]; unsigned char UART_Rx_Index = 0; bit Address_Matched = 0; // 地址匹配标志 void Timer0_Init(void) { // Timer0 模式1,16位定时器,用于系统时基 TMOD &= 0xF0; // 清零T0控制位 (高4位是T1,保持不变) TMOD |= 0x01; // 设置T0为模式1 (0000 0001) // 假设Fosc=11.0592MHz,机器周期为12个时钟,即1.085us // 定时50ms: N = 50000us / 1.085us ≈ 46080 // 初值 = 65536 - 46080 = 19456 = 0x4C00 TH0 = 0x4C; TL0 = 0x00; ET0 = 1; // 使能Timer0中断 TR0 = 1; // 启动Timer0 } void UART_Init(void) { // 1. 设置Timer1为模式2,自动重载,用于产生波特率 TMOD &= 0x0F; // 清零T1控制位 (低4位是T0,保持不变) TMOD |= 0x20; // 设置T1为模式2 (0010 0000) // 2. 波特率9600 @ 11.0592MHz, SMOD1=0 PCON &= 0x7F; // SMOD1 = 0 TH1 = 0xFD; // 重载值 TL1 = 0xFD; // 初始值 TR1 = 1; // 启动Timer1 // 3. 使能帧错误检测 FE PCON |= 0x40; // SMOD0 = 1, 使能FE位 // 4. 串口模式1,允许接收,SM2=1 (初始为监听地址状态) SCON = 0xF0; // 1111 0000: SM0=1(但SMOD0=1时此为FE), SM1=1(模式1?), 注意! // 更正:模式1应为SM0=0, SM1=1。且要使SM2=1,REN=1。 // 对于模式1,SM2用于校验停止位。多机通信常用模式3。 // 我们改用模式3进行多机通信示例: PCON &= ~0x40; // 先关闭FE,使用SM0位 SCON = 0xF0; // 1111 0000: SM0=1, SM1=1 (模式3), SM2=1, REN=1, TB8=0, RB8=0, TI=0, RI=0 // 5. 使能串口中断 ES = 1; EA = 1; // 开启总中断 } void Timer0_ISR(void) interrupt 1 { // 50ms定时中断服务程序 static unsigned int ms50_Count = 0; // 重装初值 TH0 = 0x4C; TL0 = 0x00; // 定时任务处理,例如: ms50_Count++; if(ms50_Count >= 20) { // 1秒到 ms50_Count = 0; // P1.0 翻转,指示系统运行 P1 ^= 0x01; } } void UART_ISR(void) interrupt 4 { if(RI) { RI = 0; // 必须软件清零接收标志 // 检查帧错误 if(PCON & 0x40) { // 如果使能了FE if(SCON & 0x80) { // FE位为1 SCON &= 0x7F; // 清除FE错误标志 // 可以在这里记录错误或丢弃本帧 return; // 帧错误,直接返回 } } // 判断当前是地址帧还是数据帧 (模式3下,RB8=1为地址帧) if(RB8 == 1) { // 收到地址帧 unsigned char addr = SBUF; if(addr == SLAVE_ADDR || addr == 0xFF) { // 匹配本机地址或广播地址 Address_Matched = 1; if(addr != 0xFF) { // 非广播地址,则准备接收数据 SM2 = 0; // 清零SM2,准备接收数据帧 } // 可以回送一个应答 TB8 = 0; // 数据帧 SBUF = 0xAA; // 应答信号 while(!TI); TI = 0; } else { Address_Matched = 0; // 不是本机地址,保持SM2=1,忽略后续数据 } } else { // 收到数据帧 if(Address_Matched && SM2 == 0) { // 是本机地址,且处于接收数据状态 UART_Rx_Buffer[UART_Rx_Index++] = SBUF; // 简单的缓冲区处理,例如以回车符结束 if(SBUF == '\n' || UART_Rx_Index >= 31) { UART_Rx_Buffer[UART_Rx_Index] = '\0'; // 字符串结束符 // 处理接收到的数据... UART_Rx_Index = 0; // 重置索引 // 一包数据接收完毕,恢复监听地址状态 SM2 = 1; Address_Matched = 0; } } // 如果不是本机数据,则自动被硬件忽略(因为SM2可能为1) } } if(TI) { TI = 0; // 必须软件清零发送标志 // 发送完成处理,如果有发送缓冲区,可以在此加载下一个字节 } } void UART_SendString(unsigned char *str) { TB8 = 0; // 发送数据帧 while(*str != '\0') { SBUF = *str++; while(!TI); // 等待发送完成 TI = 0; } } void main(void) { P1M1 = 0x00; P1M2 = 0x00; // 配置P1为准双向口 Timer0_Init(); UART_Init(); while(1) { // 主循环处理其他任务 // 例如,如果收到完整数据,可以在此处理 if(UART_Rx_Index == 0) { // 缓冲区已处理完 // 可以执行其他任务 } } }调试技巧与常见问题排查
- 收不到数据?
- 检查接线:TX接RX,RX接TX,GND共地。这是最常犯的错误!
- 检查波特率:双方波特率、数据位、停止位、校验位必须完全一致。用示波器测量TxD引脚波形,算一下位时间(1/波特率)是否准确。
- 检查初始化:
TR1=1启动了吗?REN=1接收使能了吗?ES和EA中断开了吗(如果用了中断)?- 检查SM2:如果用了多机通信,确认从机
SM2状态是否正确。- 收到乱码?
- 波特率误差太大:确认晶振频率是否正确,
TH1计算值是否准确。强烈建议使用11.0592MHz晶振。- 电气干扰:长距离通信时,考虑使用RS-232电平转换芯片(如MAX232)或RS-485收发器,并做好屏蔽。
- 电源噪声:确保单片机电源稳定,尤其在无线模块等大电流设备附近,加磁珠和滤波电容。
- 发送数据丢失?
- 未等待发送完成:在查询方式下,发送后必须用
while(!TI);等待。在中断方式下,要确保发送缓冲区管理正确,避免覆盖。- 中断冲突:如果定时器中断过于频繁,可能会打断串口发送,导致字节间间隔异常。优化中断服务程序,尽量短小精悍。
- 多机通信中,从机偶尔响应错误地址?
- 线路干扰:启用
FE帧错误检测,丢弃错误的地址帧。- 时序问题:增加主机发送地址帧与数据帧之间的延时。
- 从机软件逻辑:确保从机在非寻址状态下
SM2=1,且地址匹配判断逻辑严谨。- 使用仿真器或逻辑分析仪:这是最强大的调试工具。可以实时查看寄存器值、观察串口波形、设置断点跟踪程序流,能快速定位绝大多数问题。
通过以上从原理到寄存器,从配置公式到实战代码,再到调试技巧的梳理,相信你对P87LPC760这颗小芯片的定时器和UART功能已经有了透彻的理解。记住,嵌入式开发重在实践,把这些代码烧录进芯片,连接串口调试助手,亲眼看到数据收发成功,才是掌握这些知识的最好方式。在实际项目中,灵活运用定时器的不同模式和UART的多机通信、帧错误检测功能,可以构建出非常稳定可靠的嵌入式系统通信骨架。