P87LPC761单片机UART自动地址识别与看门狗定时器深度应用指南
1. 项目概述与核心价值
在嵌入式开发领域,尤其是面对成本、功耗和引脚数量都极为敏感的应用时,像Philips(现NXP)的P87LPC761这类16引脚、2KB OTP的8位单片机,曾经是许多工程师手中的“瑞士军刀”。它麻雀虽小,五脏俱全,尤其内置了80C51内核的增强型UART模块和独立的看门狗定时器。今天,我们不谈那些泛泛的引脚定义和基础指令,而是聚焦于两个在实际项目中能极大提升系统可靠性和通信效率的核心高级功能:UART的自动地址识别与看门狗定时器的深度应用。
很多新手在搭建主从式多机通信系统时,习惯于在软件层面轮询解析地址,这不仅消耗宝贵的CPU周期,还增加了代码复杂度和响应延迟。而P87LPC761硬件集成的自动地址识别,能让你像配置硬件滤波器一样,让单片机自己“认领”属于自己的数据帧,将CPU从繁琐的地址比对中解放出来。另一方面,在严苛的工业环境或电池供电的野外设备中,程序因干扰“跑飞”或陷入死循环是致命的。这时,一个独立于CPU时钟、无法被意外关闭的硬件看门狗,就是系统最后的“守护神”。理解并正确配置它,是从“功能实现”迈向“产品可靠”的关键一步。
本文将结合我多年的实际项目经验,深入剖析这两个功能的硬件原理、寄存器配置、实战代码示例,并分享那些数据手册上不会写的配置陷阱和调试心得。无论你是正在评估这颗经典芯片,还是已经用它开发但想挖掘更深层的潜力,这篇文章都将提供可直接“抄作业”的干货。
2. UART自动地址识别:硬件级的通信过滤引擎
2.1 功能原理与核心价值
传统的多机UART通信,通常采用“广播+软件过滤”的模式。主机发送一帧包含目标从机地址的数据,所有从机都会收到并产生接收中断,然后在中断服务程序中,用软件比对接收到的地址是否与自身匹配。不匹配的从机丢弃该帧,匹配的则继续接收后续数据。这个过程,每个从机每收到一帧地址都要执行一次软件比对,在从机数量多或通信频繁时,无谓的中断和软件开销会相当可观。
P87LPC761的自动地址识别(Automatic Address Recognition)功能,其核心价值在于将“地址比对”这个动作从软件层面下沉到硬件层面。它允许你为UART模块预先设置一个“给定地址”和一个“广播地址”。当UART工作在模式2或模式3(即9位数据模式,第9位为1表示地址帧,为0表示数据帧)时,硬件会自动比较接收到的地址字节。只有当地址字节与预设的“给定地址”或“广播地址”匹配时,才会置位接收中断标志(RI),触发CPU中断。否则,硬件直接忽略该帧,RI不会置位,CPU完全不知情。
这就好比给你的单片机UART配备了一个智能门卫。门卫手里有一份白名单(给定地址)和一张通用通行证(广播地址)。只有持有白名单或通行证的人(数据帧)敲门,门卫才会通知主人(CPU)。其他人则直接被挡在门外,主人无需亲自接待和盘问每一个人,从而节省了大量精力(CPU资源)。
2.2 核心寄存器详解:SADDR与SADEN的“双剑合璧”
实现这一魔法的主角是两个特殊功能寄存器(SFR):SADDR(从机地址寄存器)和SADEN(地址掩码寄存器)。它们的地址分别是0xA9和0xB9。很多初学者会困惑于这两个寄存器如何配合,其实可以将其理解为一个地址模板系统。
- SADDR (Slave Address Register): 这定义了你的从机“理想”的地址,即你希望完全匹配的位。
- SADEN (Slave Address Enable Register): 这是一个掩码寄存器,用于定义SADDR中哪些位是必须严格匹配的(掩码位为1),哪些位是“无关位”(Don‘t Care,掩码位为0)。
“给定地址”的计算公式是:给定地址 = SADDR & SADEN(按位与)。经过与操作后,SADEN中为0的位在结果中会被强制为0,这些位就是“无关位”;SADEN中为1的位,则必须与SADDR中对应的位一致。
“广播地址”的计算公式是:广播地址 = SADDR | ~SADEN(按位或SADDR和SADEN的反码)。这通常会产生一个大部分位为1的地址(如0xFF),用于主机向所有从机发送命令。
关键提示:芯片复位后,SADDR和SADEN的默认值都是0x00。根据公式,给定地址 = 0x00 & 0x00 = 0x00,广播地址 = 0x00 | 0xFF = 0xFF。这意味着任何地址帧(第9位为1)只要地址字节是0x00或0xFF,都会触发中断。这并非禁用功能,而是将其置于一个特殊的“全匹配”状态。若要真正禁用自动地址识别,需要将SCON寄存器中的SM2位清零。此时,UART将工作在标准的8位/9位数据模式,不进行地址过滤。
2.3 灵活寻址实战:从理论到配置
数据手册中的例子可能有些抽象,我们结合几个更贴近实际项目的场景来理解。
场景一:区分同一网络中的两个同型号从机假设我们有两个P87LPC761从机(Slave 0和Slave 1),我们希望主机能分别对它们寻址,也能同时广播。
Slave 0 配置:
SADDR = 1100 0000(0xC0)SADEN = 1111 1101(0xFD) // 注意,bit 1为0,表示bit 1是“无关位”- 给定地址= 0xC0 & 0xFD =
1100 00X0(X表示无关位,可为0或1) - 这意味着Slave 0只关心地址的高6位(110000)和最低位(bit 0必须为0)。bit 1是什么它不在乎。
- 唯一地址示例:
1100 0010(0xC2)。因为bit 1=1,Slave 1不会响应(见下文);bit 0=0,满足Slave 0要求。
Slave 1 配置:
SADDR = 1100 0000(0xC0)SADEN = 1111 1110(0xFE) // bit 0为0,表示bit 0是“无关位”- 给定地址= 0xC0 & 0xFE =
1100 000X - Slave 1关心高7位(1100000),最低位bit 0是无关位。
- 唯一地址示例:
1100 0001(0xC1)。bit 0=1,Slave 0不会响应;高7位匹配Slave 1。
同时寻址两者:使用地址
1100 0000(0xC0)。对于Slave 0,bit 0=0满足要求;对于Slave 1,高7位1100000满足要求。因此两者都会响应。
场景二:更复杂的分组寻址假设有三个从机:一个温度传感器(Slave_T),一个湿度传感器(Slave_H),一个执行器(Slave_A)。我们希望主机可以单独控制执行器,也可以同时读取两个传感器。
Slave_T (温度):
SADDR = 1010 0000(0xA0) // 类型码1010表示传感器,00表示温度SADEN = 1111 1001(0xF9) // 低3位中,只关心bit 0(设为0),bit 2和bit 1无关- 给定地址 =
1010 0XX0
Slave_H (湿度):
SADDR = 1010 0000(0xA0) // 类型码1010表示传感器SADEN = 1111 1010(0xFA) // 只关心bit 1(设为0)- 给定地址 =
1010 0X0X
Slave_A (执行器):
SADDR = 1100 0000(0xC0) // 类型码1100表示执行器SADEN = 1111 1111(0xFF) // 全部位都必须严格匹配- 给定地址 =
1100 0000(0xC0),只有这个地址能唤醒它。
这样,主机发送1010 0000可同时寻址两个传感器(温度bit 0=0,湿度bit 1=0)。发送1010 0010则只寻址湿度传感器(bit 1=0满足,bit 0=1排除温度传感器)。发送1100 0000则单独控制执行器。这种方案实现了基于地址位的灵活分组,非常高效。
2.4 软件配置流程与示例代码
理解了原理,配置起来就很简单了。以下是初始化UART为模式3(9位UART,波特率可变)并启用自动地址识别的典型代码片段:
#include <REG761.H> // 包含P87LPC761的SFR定义 void UART_Init_AutoAddress(void) { // 1. 配置定时器1为波特率发生器(模式2,自动重载) TMOD &= 0x0F; // 清零定时器1模式位 TMOD |= 0x20; // 定时器1,模式2 (8位自动重载) // 假设使用11.0592MHz晶振,目标波特率9600 TH1 = 0xFD; // 重载值 TL1 = 0xFD; TR1 = 1; // 启动定时器1 // 2. 配置从机地址和掩码 (示例:Slave 0的配置) SADDR = 0xC0; // 从机地址 SADEN = 0xFD; // 地址掩码 // 3. 配置串口控制寄存器SCON // SM0 SM1 SM2 REN TB8 RB8 TI RI // 1 1 1 1 0 0 0 0 SCON = 0xF0; // 模式3 (SM0=1, SM1=1), 使能接收(REN=1), 使能地址识别(SM2=1) // 注意:在地址识别模式下,通常由主机设置TB8=1发送地址帧,TB8=0发送数据帧。 // 从机无需手动设置TB8,但RB8位在中断中用于判断是地址帧还是数据帧。 // 4. 使能串口中断(如果需要) ES = 1; // 使能串口中断 EA = 1; // 开启全局中断 } // 串口中断服务程序示例 void UART_ISR(void) interrupt 4 { if (RI == 1) { // 接收中断 RI = 0; // 软件清零接收中断标志 if (RB8 == 1) { // 接收到的第9位为1,表示这是地址帧 // 硬件已帮我们做了地址匹配,能进到这里说明地址匹配成功 // 可以在此处设置一个标志,通知主程序准备接收后续数据帧 address_matched_flag = 1; // 通常,在确认地址匹配后,需要清除SM2位,以便接收后续的数据帧(第9位为0) SM2 = 0; } else { // 接收到的第9位为0,表示这是数据帧 if (address_matched_flag) { // 处理数据 received_data = SBUF; // ... 你的数据处理逻辑 ... } // 一帧数据接收完成后,重新置位SM2,等待下一个地址帧 SM2 = 1; address_matched_flag = 0; } } if (TI == 1) { // 发送中断,处理略 TI = 0; } }实操心得与避坑指南:
- 模式选择:自动地址识别仅在UART模式2和模式3(9位数据)下有效。模式1和模式0不支持。
- SM2位的动态管理:这是最容易出错的地方。从机在接收到匹配的地址帧(RB8=1)后,必须手动清除SM2位(SM2=0),才能让UART在接收后续数据帧(RB8=0)时产生中断。在所有数据帧接收完毕后,必须重新置位SM2(SM2=1),以准备接收下一个地址帧。这个“置位-清零-再置位”的流程必须由软件严格管理。
- 广播地址的使用:广播地址通常用于系统复位、同步时钟或下发全局参数。从机对广播地址的响应逻辑应与给定地址一致,即也会触发RI。在中断程序中,可以通过判断接收到的地址值是否等于广播地址(通常是0xFF)来执行不同的逻辑。
- 地址帧与数据帧的区分:务必利用好RB8位。在中断中,首先检查RB8,这是区分当前字节是地址还是数据的唯一硬件标志。
- 上电初始状态:复位后SADDR/SADEN为0,且SM2默认为0。如果你的应用不需要地址识别,则无需操作它们。如果需要,必须在UART初始化时正确配置SADDR、SADEN和SCON寄存器。
3. 看门狗定时器:系统可靠性的终极守门员
3.1 看门狗的核心机制与独立价值
看门狗定时器(Watchdog Timer, WDT)本质上是一个独立的倒计时器。其设计哲学是:一个正常的程序,应该能在规定的时间间隔内,有规律地执行一段特定的“喂狗”代码。如果程序因为干扰跑飞、陷入死循环或发生其他不可预知的错误,导致无法按时“喂狗”,那么看门狗计时器就会溢出,并触发一个系统复位,强制将MCU拉回已知的初始状态,从而尝试从故障中恢复。
P87LPC761的看门狗强大之处在于其独立性:
- 独立的时钟源:它由一个片内独立的RC振荡器驱动,典型频率约为500kHz(公差±37%)。这意味着即使主CPU时钟(外部晶振)因故停振,看门狗依然在正常工作。这实现了真正的“振荡器失效检测”。
- 不可屏蔽的复位:一旦通过配置位启用,看门狗无法被软件关闭。这防止了故障程序意外禁用看门狗。
- 灵活的定时周期:提供从约25ms到3.2秒共8个可选的超时时间,适应不同任务的执行周期。
3.2 看门狗控制寄存器(WDCON)深度解析
看门狗的所有行为都由WDCON寄存器(地址0xA7)控制。每一位都至关重要:
| 位 | 符号 | 功能详解与注意事项 |
|---|---|---|
| 7:6 | — | 保留位。必须由用户程序写0,读值不确定。为未来兼容性考虑,务必不要置1。 |
| 5 | WDOVF | 看门狗溢出标志。这是一个只读位(尝试写无效)。当看门狗定时器溢出(无论导致复位还是作为间隔定时器中断)时,由硬件置1。它不会自动清零。清零方法:执行一次正确的“喂狗”序列。这个标志位非常有用,可以在程序启动时读取它,以判断上次复位是由看门狗引起的还是上电/外部复位引起的,从而执行不同的恢复逻辑。 |
| 4 | WDRUN | 看门狗运行控制。1=启动看门狗计数器;0=停止。但是!如果配置字节UCFG1.7(WDTE)被编程为0(即启用看门狗功能),则此位被强制为1,软件无法停止看门狗。只有在WDTE=1(看门狗禁用)时,软件才能通过此位控制看门狗作为普通间隔定时器的启停。 |
| 3 | WDCLK | 看门狗时钟选择。0=使用独立的内部RC振荡器(约500kHz);1=使用CPU时钟/6。同样地,如果WDTE=0(看门狗启用),此位被强制为0,即强制使用独立RC振荡器,确保最高可靠性。只有当WDTE=1时,才能选择CPU时钟分频作为时钟源。 |
| 2:0 | WDS2-WDS0 | 看门狗超时速率选择。这三位共同决定看门狗计数器的溢出周期。它们决定了20位计数器从哪个高位开始比较。 |
超时时间选择表(基于独立RC振荡器,典型值500kHz):
| WDS2 | WDS1 | WDS0 | 超时时钟数 | 最小时间 | 典型时间 | 最大时间 |
|---|---|---|---|---|---|---|
| 0 | 0 | 0 | 8,192 | 10 ms | 16 ms | 23 ms |
| 0 | 0 | 1 | 16,384 | 20 ms | 32 ms | 45 ms |
| 0 | 1 | 0 | 32,768 | 41 ms | 65 ms | 90 ms |
| 0 | 1 | 1 | 65,536 | 82 ms | 131 ms | 180 ms |
| 1 | 0 | 0 | 131,072 | 165 ms | 262 ms | 360 ms |
| 1 | 0 | 1 | 262,144 | 330 ms | 524 ms | 719 ms |
| 1 | 1 | 0 | 524,288 | 660 ms | 1.05 s | 1.44 s |
| 1 | 1 | 1 | 1,048,576 | 1.3 s | 2.1 s | 2.9 s |
重要计算与选型提示: 超时时间 = (计数值) / (看门狗时钟频率)。 以典型值500kHz计算,
2^13 = 8192个时钟周期对应的时间是8192 / 500000 ≈ 0.016384秒 = 16.384ms,与手册典型值16ms吻合。选型建议:选择超时时间时,应略大于你程序中最外层或最长的循环/任务执行周期。例如,如果你的主循环确保在100ms内能执行一遍,那么选择262ms或524ms是安全的。不要设置得过短,以免正常操作下误触发复位;也不要过长,以免故障后系统恢复太慢。
3.3 看门狗的启用、喂狗与初始化流程
第一步:硬件配置(OTP编程)看门狗功能的使能/禁用是通过对OTP配置字节UCFG1.7 (WDTE)进行编程来决定的。这是一个一次性操作,在芯片烧录时完成。
- WDTE = 0 (编程):启用看门狗功能。启用后,WDRUN和WDCLK被强制为1和0,看门狗将作为一个真正的、不可关闭的看门狗运行,必须定期喂狗,否则复位。
- WDTE = 1 (未编程/默认):禁用看门狗功能。此时看门狗可以作为一个可编程的间隔定时器使用,通过WDRUN控制启停,通过WDCLK选择时钟源,溢出时产生中断(需使能相应中断)而非复位。
第二步:软件初始化与喂狗序列无论看门狗是何种模式,其喂狗操作和部分初始化是相似的。
#include <REG761.H> // 看门狗初始化函数 (在WDTE=0,看门狗启用模式下) void WDT_Init_AsWatchdog(void) { // 注意:在WDTE=0时,WDRUN和WDCLK是只读的且固定为1和0。 // 我们只能配置超时时间和执行一次喂狗。 // 推荐的初始化顺序(见数据手册): // 1. 先执行一次喂狗,清除可能存在的WDOVF标志,并重置计数器。 WDT_Feed(); // 2. 然后配置WDCON寄存器(主要是WDS2-0位) // 假设我们选择典型值1.05秒的超时时间 (WDS2,1,0 = 1,1,0) WDCON = 0x06; // 二进制 0000 0110,即设置WDS2-0=110,其他位为0。 // 注意:在WDTE=0时,写WDCON只有WDS2-0位有效,WDRUN和WDCLK写无效。 // 之后,程序必须在1.05秒内再次执行喂狗,否则将复位。 } // 看门狗作为间隔定时器的初始化 (在WDTE=1,看门狗禁用模式下) void WDT_Init_AsIntervalTimer(void) { // 1. 停止看门狗 WDCON &= 0xEF; // 清除WDRUN位 (位4),停止计时。 // 2. 选择时钟源和超时时间,并清除溢出标志 // 假设使用CPU时钟/6,超时时间65ms (WDS2-0 = 010) // 同时将WDRUN置0,WDCLK置1 WDCON = 0x08 | 0x02; // 0x08是WDCLK=1, 0x02是WDS2-0=010 // 3. (可选)使能看门狗定时器中断 // 首先需要确认中断向量和使能位,P87LPC761的看门狗中断通常与定时器0/1共用或独立, // 需查阅具体手册。这里假设有独立使能位EWDT和中断号。 // EWDT = 1; // 使能看门狗定时器中断 // EA = 1; // 4. 启动定时器 WDCON |= 0x10; // 置位WDRUN (位4),启动计时。 } // *** 核心喂狗序列函数 *** // 无论看门狗是何种模式,喂狗操作序列是固定的。 void WDT_Feed(void) { // 第一步:向WDRST寄存器写入0x1E WDRST = 0x1E; // 第二步:向WDRST寄存器写入0xE1 WDRST = 0xE1; // 喂狗后,看门狗计数器被重置,WDOVF标志位被清除。 }喂狗序列的硬性规定与陷阱:
- 顺序绝对固定:必须是先写
0x1E,紧接着写0xE1。写0xE1再写0x1E无效,写其他值也无效。- 非连续性要求:这两条写指令不必是连续的汇编指令,中间可以插入其他操作。但必须在超时时间窗口内完成整个序列。
- 无即时反馈:执行一个错误的喂狗序列不会立即引发任何动作,看门狗只会静静地等待,直到超时后触发复位。这给调试带来了困难,因为你无法通过“试错”来感知喂狗是否正确。
- 超时窗口极紧:尤其是在使用低速内部RC振荡器作为主时钟时,从复位结束到第一次喂狗或配置WDCON之间的指令条数非常有限。务必在初始化代码的最前端,尽快完成看门狗的首次喂狗或配置。
3.4 看门狗在低功耗模式下的行为
这是一个关键且容易忽视的细节。当单片机进入Power Down模式(完全停止,功耗最低)时,主CPU时钟停止。此时:
- 如果看门狗由独立RC振荡器驱动(WDTE=0时的强制状态,或WDTE=1且WDCLK=0),那么看门狗将继续运行!这意味着,即使MCU在休眠,你仍然需要在超时前唤醒MCU并执行喂狗,否则看门狗溢出将产生一个复位,并唤醒处理器。
- 如果看门狗由CPU时钟/6驱动(仅WDTE=1且WDCLK=1时可能),那么进入Power Down模式后,看门狗时钟也停止了,因此不会溢出。
设计建议:对于需要长期深度休眠的应用,如果启用了看门狗(WDTE=0),你必须使用一个独立的低功耗定时器(如片内定时器或外部RTC)在看门狗超时前定期唤醒MCU,执行喂狗后可以再次进入休眠。或者,在进入休眠前,临时切换到一个更长的看门狗超时时间(如果支持),但P87LPC761的看门狗超时时间在运行中似乎不可动态更改(WDCON在WDTE=0时可能只允许写一次),因此前一种方案更可靠。
4. 系统配置与安全特性
4.1 配置字节(UCFG1 & UCFG2)的烧录决策
P87LPC761的许多关键功能在上电时就确定了,无法通过运行中的软件修改,这通过两个OTP配置字节UCFG1和UCFG2实现。烧录器在编程时会写入这些值。
UCFG1 (地址0xFD00) 关键位分析:
- WDTE (位7):如上所述,看门狗使能。强烈建议在最终产品中将其编程为0(启用)。在开发调试阶段,可以保持为1(禁用),以免频繁复位干扰调试。
- RPD (位6):复位引脚禁用。置1可使P1.5引脚变为普通输入口。警告:一旦禁用,你将失去外部复位能力,只能依靠上电复位、看门狗复位或软件复位。除非引脚极其紧张,否则不建议禁用。
- BOV (位4):掉电检测电压选择。0对应约3.8V,1对应约2.5V。选择取决于你的系统最低工作电压。如果设备用3.3V供电,选择2.5V更合理,避免在电压正常波动时误触发掉电复位。
- FOSC2-0 (位2-0):振荡器类型选择。这是硬件连接的决定性配置。你必须根据实际连接的晶振或时钟源来选择,选错可能导致单片机无法起振。例如,使用外部有源时钟信号输入到X1引脚,应选择
111。
UCFG2 (地址0xFD01) 与代码安全:
- SB1, SB2 (位7,6):EPROM安全位。这是保护你知识产权和固件逻辑的关键。
11(默认):无保护,可编程、可校验。10:编程锁定。写入此状态后,主代码区不能再被编程,但安全位2(SB2)仍可被编程。常用于批量生产:先烧录代码并验证,然后锁死。00:完全锁定。写入此状态后,禁止进一步的编程和校验。代码无法被读取或修改。用于最高级别的保护。
烧录流程建议:
- 开发阶段:保持安全位为
11,方便反复擦写(OTP不能擦除,但可多次编程直到位变为0)和调试。- 小批量试产:烧录代码后,将安全位设为
10。这样代码被保护,但万一发现严重BUG,仍有可能通过特殊手段(如果支持)再次编程SB2来完全锁死或尝试修复(需确认芯片具体支持情况)。- 最终量产:确认固件稳定后,直接烧录为
00状态,实现最高级别保护。
4.2 软件复位与双数据指针的妙用
软件复位(SRST):通过向AUXR1寄存器的位3写1,可以触发一个完整的硬件复位。这比“长跳转到0000H”更彻底,因为所有SFR都会被初始化。在程序需要完全重启的场合(如固件升级后、严重错误恢复)非常有用。使用时需极其小心,避免误操作。建议通过一个特定的、不易被意外满足的条件序列来触发。
void Trigger_Software_Reset(void) { // 示例:通过一个特定序列触发软件复位,避免意外 if (reset_key == 0xA55A) { // 一个特定的密钥 AUXR1 |= 0x08; // 置位SRST位(位3),触发复位 // 执行完这条指令后,处理器立即复位,下一条代码不会执行。 } }双数据指针(DPS):这是一个能显著提升块数据搬运效率的功能。通过AUXR1.0(DPS位)在两个16位数据指针DPTR0和DPTR1之间切换。在复制大块内存或访问两个非连续数据区域时,可以节省频繁重载DPTR值的时间。
; 汇编示例:使用双DPTR加速内存复制 MOV AUXR1, #00h ; 选择DPTR0 MOV DPTR, #SOURCE_ADDR MOV AUXR1, #01h ; 选择DPTR1 (通过INC AUXR1也可,因为位2恒为0) MOV DPTR, #DEST_ADDR MOV R7, #BLOCK_SIZE COPY_LOOP: MOV AUXR1, #00h ; 切回DPTR0 MOVX A, @DPTR ; 从源地址读 INC DPTR ; 源地址++ MOV AUXR1, #01h ; 切换到DPTR1 MOVX @DPTR, A ; 向目标地址写 INC DPTR ; 目标地址++ DJNZ R7, COPY_LOOP使用技巧:由于AUXR1.2位硬件连接为0,你可以通过简单的
INC AUXR1指令来切换DPS位(0->1或1->0),而不会影响AUXR1的其他位(如SRST)。这比用MOV指令操作更高效。
5. 实战集成与调试经验
将自动地址识别和看门狗集成到一个实际项目中,需要考虑它们的交互和系统稳定性。
系统初始化顺序(最佳实践):
- 上电/复位后立即操作:在
main()函数的最开始,甚至在任何复杂的初始化(如清RAM、设堆栈)之前,先读取WDCON中的WDOVF标志,判断复位来源。 - 看门狗首次喂狗/配置:紧接着,执行看门狗的第一次喂狗
WDT_Feed(),或者如果看门狗作为间隔定时器使用,则进行配置WDCON。这必须在看门狗首次超时前完成(对于最慢的16ms设置,时间非常紧张)。 - 初始化基本硬件:设置堆栈指针、初始化I/O口状态等。
- 初始化UART和自动地址识别:配置波特率、SADDR、SADEN、SCON等。
- 主循环设计:主循环中,确保在最坏情况下,执行路径都能在看门狗超时周期的一半以内经过喂狗点。例如,看门狗设为1秒,那么主循环中任何可能的分支或函数调用,其最大执行时间不应超过500ms,并在循环中合适位置调用
WDT_Feed()。 - 中断服务程序(ISR)注意事项:如果ISR执行时间很长,需要考虑在ISR内部也进行喂狗。但需注意,这可能导致主程序跑飞但ISR仍定期执行而“喂活”看门狗的情况。通常更推荐确保主循环定期喂狗。
调试与问题排查实录:
问题:启用看门狗后,程序总是莫名其妙复位。
- 排查:
- 检查喂狗序列是否正确(先0x1E后0xE1)。
- 检查喂狗函数是否被意外优化或未被调用。在调试阶段,可以在喂狗函数前后翻转一个IO口,用示波器观察喂狗间隔。
- 计算最坏执行时间:分析所有可能的分支、循环和中断,估算从一次喂狗到下一次喂狗的最大时间间隔,确保它小于看门狗超时时间。特别注意阻塞操作,如等待某个传感器响应、软件延时循环等。
- 检查看门狗超时时间配置是否过短。
- 排查:
问题:自动地址识别模式下,从机收不到数据。
- 排查:
- 确认SM2位管理:这是最常见错误。示波器或逻辑分析仪抓取UART波形,确认主机发送的地址帧第9位(TB8)为1,数据帧第9位为0。在从机中断中,打印或通过IO输出RB8的值,检查在收到地址帧后是否清除了SM2,在数据帧收完后是否重新置位了SM2。
- 检查SADDR和SADEN计算:手动计算“给定地址”,并与主机发送的地址对比。注意二进制、十六进制的转换。
- 确认UART模式:SCON寄存器是否配置为模式2或3(SM0, SM1 = 1,0 或 1,1)。
- 检查中断使能:ES和EA是否已置1。
- 排查:
问题:系统在进入休眠(Power Down)后不久复位。
- 排查:几乎可以肯定是看门狗在休眠期间溢出。确认看门狗是否由独立RC振荡器驱动(WDTE=0或WDCLK=0)。如果是,则必须在休眠期间定期唤醒喂狗。考虑使用片内定时器或外部RTC的周期性中断来唤醒系统。
最后的经验之谈:对于P87LPC761这类资源受限的单片机,每一个高级功能的使用都需要精打细算。自动地址识别省下的每一条指令,看门狗带来的每一分安心,都是产品稳定性的基石。在项目初期就规划好这些功能,并编写健壮的初始化、喂狗和状态恢复代码,远比后期出了问题再修补要高效得多。记住,可靠的系统不是偶然出现的,而是设计出来的。
