1. 项目概述与核心价值
在嵌入式系统开发中,模拟比较器、看门狗定时器和Flash编程是三个看似独立,实则紧密关联、共同构筑系统健壮性的核心模块。很多工程师在项目初期可能只关注主控芯片的CPU性能和IO数量,却忽略了这些“配角”模块的深度应用,导致系统在抗干扰、数据可靠性或异常恢复能力上存在短板。P89LPC912/913/914这款经典的8位微控制器,以其高集成度和丰富的片上外设,为我们提供了一个绝佳的实践平台。它内置的双模拟比较器、可灵活配置的看门狗定时器以及支持在应用编程(IAP-Lite)的Flash存储器,正是我们深入理解这些关键技术的绝佳案例。
模拟比较器的作用远不止于比较两个电压谁高谁低。在电池供电设备中,它是电量监测的“哨兵”;在传感器接口中,它是将连续模拟量转换为离散事件信号的“触发器”;在电源管理中,它又是过压/欠压保护的“保险丝”。而看门狗定时器,则是系统可靠性的最后一道防线,它能将程序从跑飞、死锁的异常状态中“拽”回来。至于Flash的IAP功能,则赋予了产品在出厂后仍能升级固件、存储关键运行参数(如校准数据、用户设置)的能力,极大地提升了产品的生命周期和灵活性。
本文将从一个资深嵌入式工程师的视角,手把手带你拆解P89LPC912/913/914的这三个模块。我不会仅仅复述数据手册的寄存器描述,而是结合我多年在工控、消费电子领域踩过的坑和积累的经验,重点剖析 如何配置 、 为何这样配置 以及 实际应用中会遇到哪些陷阱 。我们会从模拟比较器的电路连接和中断服务程序编写讲起,接着深入看门狗那套严谨到近乎苛刻的“喂狗”序列和低功耗唤醒机制,最后攻克Flash字节级编程的步骤和防数据丢失策略。目标是让你读完本文后,不仅能在这颗芯片上实现这些功能,更能建立起一套适用于大多数嵌入式场景的、关于信号链处理、系统监控和非易失存储的通用设计方法论。
2. 模拟比较器:从硬件连接到软件响应
模拟比较器是连接模拟世界与数字系统的桥梁。P89LPC912/913/914提供了两个独立的比较器(Comparator 1和2),其灵活性在于输入源和输出路径均可配置。
2.1 硬件架构与配置逻辑
每个比较器的核心是一个开环高增益放大器。当正输入端(CINnA)电压高于负输入端电压时,输出为逻辑高电平(‘1’),反之则为低电平(‘0’)。芯片的巧妙之处在于,负输入端可以选择来自外部引脚(CMPREF)或内部一个约1.23V的基准电压(Vref)。比较器1的输出(CO1)还可以选择是否直接驱动到CMP1(P0.6)引脚上,实现硬件级的快速响应。
配置的核心是 CMP1 (地址ACh)和 CMP2 (地址ADh)这两个控制寄存器。我们以Comparator 1的 CMP1 寄存器为例,逐位解读其设计意图:
- CE1 (位5) : 比较器使能位。这是总开关。 关键经验 :手册明确提到,该位置‘1’后,需要至少等待10微秒(µs)比较器输出才能稳定。在这10µs内,绝对不要开启比较器中断,否则可能因输出抖动误触发中断。这是很多新手容易忽略的硬件初始化时序要求。
- CN1 (位3) : 负输入选择位。
0选择外部CMPREF引脚,1选择内部Vref。选择内部基准可以节省一个外部参考源,但1.23V的电压是固定的,需根据实际检测阈值来决定。 - OE1 (位2) : 输出使能位。置‘1’时,CO1将连接到CMP1引脚。 注意 :这个输出是异步的,不经过CPU时钟同步,因此响应速度极快,适合用于需要快速硬件响应的场合,如紧急关断。
- CO1 (位1) : 比较器输出状态位。这是一个只读位,反映了同步到CPU时钟后的比较器输出结果,供软件查询。
- CMF1 (位0) : 比较器中断标志位。当CO1状态发生变化(上升沿或下降沿)时,此位由硬件自动置‘1’。如果比较器中断已全局开启(
EC和EA位),则会触发中断。 必须由软件写‘0’来清除 ,这是所有中断标志位的通用操作。
Comparator 2的 CMP2 寄存器类似,但没有输出到引脚的功能(OE位),因此配置更简单,主要用于内部信号监测。
2.2 实战配置与中断处理
假设我们需要用Comparator 1来监控电源电压,当电压低于某个阈值(由CMPREF引脚上的分压电阻设定)时产生中断。同时,我们希望比较结果能实时反映到CMP1引脚上,供外部电路或逻辑分析仪观测。
步骤一:硬件设计与引脚初始化 首先,需要将用到的模拟功能引脚(CIN1A, CMPREF)的数字输入/输出功能禁用,防止数字端口电路干扰微弱的模拟信号。
MOV PT0AD, #030h ; 禁用P0.4(CIN1A)和P0.5(CMPREF)的数字输入功能
ANL P0M2, #0CFh ; 禁用P0.4和P0.5的数字输出驱动器(设为开漏或高阻)
ORL P0M1, #030h ; 将P0.4和P0.5配置为模拟输入模式
步骤二:配置比较器并处理稳定时间 接着,配置 CMP1 寄存器。我们希望:使能比较器(CE1=1),负端选择外部CMPREF引脚(CN1=0),输出到CMP1引脚(OE1=1)。对应的二进制值为 0010 0100 ,即 24h 。
MOV CMP1, #024h ; 配置比较器1
CALL delay10us ; **关键!** 必须等待至少10µs,等待比较器输出稳定
这里的 delay10us 是一个需要你根据系统时钟(CCLK)精确编写的延时子程序。如果系统主频是12MHz,一个机器周期是1µs(12时钟周期),那么一个简单的10周期空循环就能满足。不满足此时序是导致初期调试中比较器输出不稳定或中断误发的常见原因。
步骤三:中断系统配置 在比较器稳定后,清除可能因上电或配置过程产生的虚假中断标志,然后开启中断。
ANL CMP1, #0FEh ; 清除CMF1中断标志位(写0清除)
SETB EC ; 使能比较器中断(在IEN1寄存器中)
SETB EA ; 使能全局中断
步骤四:编写中断服务程序(ISR) 两个比较器共享一个中断向量。因此,在ISR入口,必须首先检查是哪个比较器触发了中断。
COMP_ISR:
PUSH ACC ; 保护现场
MOV A, CMP1
JNB ACC.0, CHECK_CMP2 ; 检查CMF1标志
; Comparator 1 中断处理逻辑
ANL CMP1, #0FEh ; **必须**清除CMF1标志
... ; 执行你的处理代码,例如设置报警标志、切换系统状态等
CHECK_CMP2:
MOV A, CMP2
JNB ACC.0, ISR_EXIT ; 检查CMF2标志
; Comparator 2 中断处理逻辑
ANL CMP2, #0FEh ; **必须**清除CMF2标志
ISR_EXIT:
POP ACC ; 恢复现场
RETI
重要陷阱 :在禁用比较器(CE1清0)之前, 务必先禁用其中断 。因为当比较器被禁用时,其输出COn会强制变为高电平。如果之前输出是低电平,这个跳变会置位CMFn标志,如果此时中断是使能的,将立即触发一个你并不期望的中断。正确的顺序是: CLR EC -> 修改CMPn寄存器(禁用比较器)-> 清除CMFn标志 。
2.3 低功耗模式下的考量
在电池供电设备中,低功耗至关重要。P89LPC912/913/914的模拟比较器在**空闲(Idle) 和 掉电(Power-down)**模式下可以保持工作。这意味着你可以让CPU休眠,而由比较器监控外部电压,一旦超过阈值就产生中断唤醒CPU,实现极低功耗的待机监测。
然而,这里有两点必须注意:
- 功耗 :比较器本身在工作时会消耗电流(具体值需查数据手册)。在深度节能设计中,如果不需要监控,应彻底关闭比较器(CE=0),并考虑将
PCONA.5置‘1’以关闭其模拟电路电源。 - 输出引脚配置 :如果使能了比较器输出到引脚(OE1=1),并且希望在Power-down模式下该引脚仍能快速响应, 必须将该引脚(P0.6)配置为推挽(Push-pull)输出模式 。原因是,在准双向口模式下,引脚电平切换时需要一个短暂的内置强上拉,而在Power-down模式下振荡器停止,这个强上拉无法工作,会导致输出切换速度极慢甚至失败。
3. 看门狗定时器:系统守护神的精密逻辑
看门狗定时器(WDT)是嵌入式系统的“生命线”。它的逻辑比看起来要严谨得多,理解不透彻极易导致程序被意外复位或看门狗根本不起作用。
3.1 工作模式与安全机制
WDT有两种基本模式,由 UCFG1.7 (WDTE)和 UCFG1.4 (WDSE)位共同决定:
| WDTE | WDSE | 功能描述 |
|---|---|---|
| 0 | X | 定时器模式 :WDT溢出不会导致系统复位,但会置位 WDTOF 标志,并可配置中断( IEN0.6 )。可用于产生周期性中断。 |
| 1 | 0 | 看门狗模式(标准) :WDT溢出将触发系统复位。用户可自由选择时钟源( WDCLK )。 |
| 1 | 1 | 看门狗模式(增强安全) :WDT溢出触发复位。 强制 使用内部400kHz看门狗振荡器作为时钟源,且 WDRUN 位被强制为1(无法停止), WDCON 和 WDL 寄存器只能写入一次。此模式用于防止软件意外禁用或修改看门狗,安全性最高。 |
设计启示 :对于高可靠性产品,强烈建议在芯片编程时(通过IAP或编程器)将 UCFG1 配置为 WDTE=1, WDSE=1 。这样就从硬件层面杜绝了软件跑飞后恶意关闭看门狗的可能性。当然,这也意味着你在软件中必须精心设计“喂狗”逻辑,因为一旦开始就无法停止。
3.2 时钟源、预分频与超时计算
WDT的时钟可以来自内部约400kHz的专用看门狗振荡器,也可以来自 PCLK (CPU时钟分频后的外设时钟)。选择 PCLK 意味着当CPU因进入空闲或掉电模式而停止时钟时,WDT也会停止,这失去了看门狗在睡眠时监控的意义。因此,在需要低功耗唤醒的应用中, 必须选择内部看门狗振荡器( WDCLK=1 ) 。
超时时间由13位预分频器和8位递减计数器共同决定。计算公式为: t_clks = (2^(5+PRE)) * (WDL + 1) + 1 其中, PRE 是 WDCON[7:5] 的预分频值(0-7), WDL 是重载值(0-255)。
举个例子,如果你想设置一个大约1秒的超时时间(假设看门狗振荡器为标称400kHz,周期2.5µs):
- 先确定总时钟周期数:
1秒 / 2.5µs = 400,000个时钟周期。 - 尝试
PRE=7(最大分频2^(5+7)=4096),则WDL需要约为400000 / 4096 ≈ 97。 - 代入公式验证:
t_clks = 4096 * (97+1) + 1 = 4096*98+1 = 401,409个周期。 - 对应时间:
401,409 * 2.5µs ≈ 1.0035秒,符合要求。
所以配置为: PRE=111B (7) , WDL=97 。这个计算过程必须在软件初始化时完成。
3.3 “喂狗”序列:不容出错的仪式
“喂狗”是WDT应用中最核心、最易出错的操作。其本质是将 WDL 和 WDCON 寄存器中的新值,通过一个特定的“仪式”安全地加载到实际的硬件计数器和控制寄存器中。
标准喂狗序列(汇编示例) :
CLR EA ; **关键第一步**:禁用全局中断
MOV WFEED1, #0A5h ; 第一步:写入魔法数字A5h
MOV WFEED2, #05Ah ; 第二步:写入魔法数字5Ah
SETB EA ; 重新使能中断
为什么必须这样?
- 禁用中断 :如果在两条
MOV指令之间发生了中断,而中断服务程序里又恰巧有写SFR的操作,就会破坏喂狗序列,导致立即触发看门狗复位!这是最隐蔽的坑之一。如果你的系统中断频繁,或者你不能保证所有ISR都绝对不写任何SFR,那么CLR EA和SETB EA就是 必须的 。 - 顺序与数值 :必须是先
A5h后5Ah,且必须连续写入。任何错误的顺序、数值,或在两步之间插入其他 写SFR 的操作(读操作允许),都会导致立即复位。 - 模式差异 :
- 看门狗模式(WDTE=1) : 任何对
WDCON寄存器的写操作,都必须紧跟着一个喂狗序列 。否则,写操作本身就会触发复位!例如,你想启动看门狗(SETB WDRUN),流程是:读WDCON -> 修改WDRUN位 -> 写回WDCON -> 立即执行喂狗序列。 - 定时器模式(WDTE=0) :写
WDCON不需要喂狗(它每个CCLK周期都会更新),但 要重载计数器值(WDL),仍然需要喂狗序列 。
- 看门狗模式(WDTE=1) : 任何对
3.4 低功耗唤醒与时钟切换陷阱
WDT在低功耗设计中扮演着周期性唤醒定时器的角色。在Power-down模式下,只有内部看门狗振荡器(约50µA)可以运行,因此必须设置 WDCLK=1 。
一个致命的陷阱:时钟源切换与掉电时序 。 假设系统当前使用 PCLK 作为WDT时钟源( WDCLK=0 ),现在你想进入Power-down模式,并希望WDT用内部振荡器继续工作以定时唤醒。你的操作可能是:
- 设置
WDCLK=1(选择内部振荡器)。 - 执行喂狗序列。
- 立即进入Power-down模式。
这样做会导致WDT失效! 原因在于时钟源切换不是立即生效的,它需要在新值被喂狗序列加载后,再经过 2个旧时钟周期 + 2个新时钟周期 的同步时间。如果你在喂狗后立即关闭 CCLK (进入Power-down),旧的 PCLK 已经停了,新的看门狗振荡器还没来得及被选中,WDT就“失能”了。
正确的安全操作顺序 :
- 设置
WDCLK=1。 - 执行喂狗序列。
- 等待至少2个PCLK周期 (对于12MHz CCLK,即4个CPU周期)。可以插入几条NOP指令。
- 此时,新的时钟源已稳定切换。
- 执行进入Power-down模式的指令。
4. Flash存储器IAP编程:数据存储的实战艺术
利用Flash存储可变数据(如参数、日志)是许多嵌入式产品的需求。P89LPC912/913/914的IAP-Lite功能允许你在程序运行时,对未加密的扇区进行字节级的擦除和编程。
4.1 IAP-Lite机制精解
与整扇区擦除再写入的传统方式不同,IAP-Lite引入了“页寄存器(Page Register)”的概念,实现了对Flash中单个字节的精确更新,而不会影响同页其他字节的数据。一页大小为16字节。
其核心操作流程围绕四个特殊功能寄存器(SFR)展开:
- FMCON (Flash控制寄存器) :命令写入和状态读取的端口。写入命令(如
00H加载,68H擦写),读取状态(HVA,HVE,SV,OI)。 - FMDATA (Flash数据寄存器) :要编程数据的临时存放点,写入的数据会进入页寄存器。
- FMADRH, FMADRL (Flash地址寄存器) :
FMADRL[3:0]用于寻址页寄存器内的16个字节之一(0-15)。FMADRH和FMADRL[7:4]共同指定要操作的Flash内存中的哪一页(256字节为一扇区,16字节为一页)。
关键机制 :每个页寄存器字节都有一个“更新标志(Update Flag)”。当你向 FMDATA 写入数据时,数据被存入页寄存器由 FMADRL[3:0] 指定的位置, 并且该位置的更新标志被置位 。执行擦写命令( 68H )时, 只有那些更新标志被置位的字节 ,其对应的Flash存储单元才会被擦除并写入新数据。同页内其他未更新标志的字节保持不变。
4.2 单字节编程实战与避坑指南
假设我们要将用户设置 0xAA 保存到Flash地址 0x1000 处。地址 0x1000 对应页地址高8位 FMADRH=0x10 ,页内偏移 FMADRL[7:4]=0x0 ,页寄存器内地址 FMADRL[3:0]=0x0 。
步骤一:发送加载命令,清空页寄存器
MOV FMCON, #00H ; 发送LOAD命令。这会清除整个页寄存器及其所有更新标志。
步骤二:设置目标地址并写入数据
MOV FMADRH, #10h ; 设置Flash页地址高字节 (0x10)
MOV FMADRL, #00h ; 设置Flash页地址低字节和页寄存器内偏移(0x00)
MOV FMDATA, #0AAh ; 将数据0xAA写入页寄存器0号位置。写入后,FMADRL[3:0]会自动加1变为0x01。
步骤三:执行擦除与编程命令
MOV FMCON, #68H ; 发送擦除-编程(ERASE-PROGRAM)命令。
此时,CPU会暂停(进入编程空闲状态),等待内部高压产生电路对Flash单元进行擦写操作,这个过程大约需要4ms。在此期间,CPU不执行指令。
步骤四:检查操作状态
MOV A, FMCON ; 读取状态寄存器
ANL A, #0FH ; 仅保留低4位状态位(HVA, HVE, SV, OI)
JNZ PROGRAM_ERROR ; 如果任何状态位不为0,则表示出错
必须避开的几个大坑:
-
中断打断 : 擦写操作的4ms期间,绝对不能被打断! 如果在此期间发生任何中断,擦写操作会被中止(
OI位置1),并且目标Flash单元的数据可能处于不确定的损坏状态。因此,在执行MOV FMCON, #68H之前, 必须禁用全局中断(CLR EA) ,并在操作完成后、检查状态无误后再开启。 -
页边界跨越 :IAP-Lite操作 不能跨页 。所有要在一轮操作中更新的字节,必须位于同一16字节的页内。如果你要写入的多个字节地址跨越了页边界(例如从
0x100F写到0x1010),你必须分成两次独立的LOAD-ERASE/PROGRAM流程。 -
重复写入同一位置 :在一条LOAD命令之后,对页寄存器内的同一个位置(由
FMADRL[3:0]指定) 只能写入一次 。第二次写入会被忽略,其更新标志也不会被再次设置。这意味着你不能通过重复写入来“修改”一个即将被编程的字节。如果你要修改,需要重新开始整个流程。 -
电压稳定性 :Flash编程需要芯片供电电压(VDD)稳定在正常工作范围内。如果在编程过程中发生电压跌落(Brown-out),
HVA位会被置位,操作失败。对于可靠性要求高的应用,在启动Flash写操作前,应确保电源稳定,或者使能并监控片内掉电检测(BOD)功能。
4.3 数据存储策略建议
基于IAP-Lite的特性,在设计Flash存储数据时,我推荐以下策略:
- 扇区规划 :专门划出几个扇区(如最后的几个扇区)用于数据存储,与程序代码区隔离。并通过设置Flash安全位,保护代码区不被意外修改。
- 页内打包 :将相关的配置参数打包在一个16字节的结构体内,一次性写入一页,以提高写入效率和数据一致性。
- 磨损均衡 :Flash有擦写次数限制(典型10万次)。对于频繁更新的数据(如运行时间计数器),不要固定在一个地址写。可以设计一个小的循环缓冲区,在多个页地址间轮转写入,并在页头记录索引或版本号。
- 操作原子性 :一次LOAD-ERASE/PROGRAM流程应尽可能完成一组相关数据的更新。如果更新过程被中断(
OI=1),要有重试或恢复机制。通常的做法是,在RAM中准备好所有要写入页寄存器的数据,然后禁用中断,一次性完成整个流程。
5. 键盘中断与软件复位:系统交互与控制的补充
虽然项目正文主要聚焦于三大模块,但P89LPC912/913/914的键盘中断(KBI)和软件复位(SRST)也是构建完整系统不可或缺的部分。
5.1 键盘中断的灵活应用
键盘中断并非只能用于矩阵键盘。它的本质是一个 端口模式匹配中断 。你可以通过 KBPATN 寄存器设置一个期望的模式(Pattern),通过 KBMASK 寄存器选择参与比较的端口位(P0.2, P0.4, P0.5, P0.6),并通过 PATN_SEL 位选择触发条件是“相等”还是“不相等”。
一个经典的非键盘应用:多路开关状态监测 。假设你有4个拨码开关连接到P0.2, P0.4, P0.5, P0.6,上拉电阻确保开关断开时为高电平‘1’,闭合时为低电平‘0’。你想在 任何 一个开关状态发生变化时(即端口值与预设模式不符)唤醒处于Power-down模式的CPU。
- 初始化时,读取一次开关状态,存入
KBPATN寄存器。 - 将
KBMASK设置为0x74(二进制0111 0100),使能P0.6, P0.5, P0.2(注意P0.4也被使能了,但我们的例子中它连接了开关)。 - 设置
PATN_SEL=0(不相等时触发)。 - 使能键盘中断(
SETB EKBI)。 - 当CPU进入Power-down后,任何开关动作改变了被监测位的状态,导致端口值不等于
KBPATN,就会立即触发中断唤醒CPU。在中断服务程序中,重新读取开关状态并更新KBPATN,为下一次监测做准备。
5.2 软件复位的安全使用
AUXR1.3 (SRST)位提供了软件复位的能力。向该位写‘1’会立即引发一个等同于硬件复位的系统复位。这在系统检测到严重错误、需要彻底重启时非常有用。
然而,使用它必须极其小心 :
- 意外复位 :在对
AUXR1寄存器进行写操作时(例如切换数据指针DPS),必须确保写入的值不会意外地将SRST位置‘1’。通常采用“读-修改-写”或位操作指令来避免。 - 复位前处理 :执行软件复位前,应尽可能完成必要的清理工作,如关闭外设、保存关键状态到非易失存储器(如果可能)等。但要注意,复位过程非常快,留给你的时间窗口很小。
- 与看门狗复位的区别 :软件复位是同步的、立即的。而看门狗复位是系统最后的手段。在故障处理逻辑上,可以分层设计:可恢复的局部错误尝试局部恢复;严重的全局错误,先尝试记录日志到Flash,再触发软件复位;只有软件完全死锁时,才由看门狗强制复位。
6. 系统集成与调试心得
将模拟比较器、看门狗、Flash编程以及键盘中断等功能集成到一个实际项目中,是对开发者系统设计能力的综合考验。这里分享几条从实际项目中总结出的经验。
第一条:初始化顺序至关重要。 一个稳健的初始化流程应该是:关闭中断( CLR EA ) -> 配置IO口模式(特别是模拟功能引脚)-> 配置并稳定外设(如比较器等待10µs)-> 清除所有可能悬而未决的中断标志 -> 配置中断优先级和使能位 -> 最后才打开全局中断( SETB EA )。这个顺序能最大程度避免上电瞬间或配置过程中产生的毛刺信号误触发中断。
第二条:喂狗程序必须视为临界区。 我强烈建议将喂狗操作封装成一个独立的函数 Feed_WDT() ,并在其开头和结尾严格进行中断的禁用与使能。并且,在整个工程中 只保留这一处喂狗调用 。将其放在主循环的固定位置,或者放在一个由系统定时器触发的、周期绝对稳定的后台任务中。绝对要避免在多个中断服务程序或条件分支中随意喂狗,那会彻底破坏看门狗监测程序流混乱的初衷。
第三条:Flash操作是“慢动作”,必须耐心等待。 4ms的擦写时间在微秒级的MCU世界里是一段“漫长”的时光。除了之前强调的关中断,还要考虑这4ms内系统如何响应。如果你的应用有实时性要求(比如PWM输出、通信超时),需要评估这4ms的阻塞是否可接受。一种高级技巧是,在进入Flash写操作前,将关键的控制信号(如PWM输出)设置为安全状态(如固定电平),或者利用硬件看门狗(如果时钟源独立)来确保即使主程序卡在Flash操作中,系统也能被复位恢复。
第四条:充分利用双数据指针(DPTR)。 AUXR1.0 (DPS)位允许你在两个16位数据指针间切换。这在处理Flash编程、数据搬移等需要频繁操作地址的场景下非常高效。例如,你可以用DPTR0指向源数据区(RAM),DPTR1指向目标地址(Flash页寄存器地址 FMADRL ),通过切换 DPS 来快速完成数据加载。切换DPTR最简洁的方法就是 INC AUXR1 ,因为 AUXR1.2 固定为0,递增操作只会翻转 DPS 位。
最后,调试这类高度依赖硬件时序和状态机的嵌入式系统,逻辑分析仪是你的最佳伙伴。用它来抓取比较器输出引脚的波形、看门狗复位信号、以及Flash编程期间IO口的活动,可以直观地验证你的软件逻辑是否与硬件行为严格同步。很多时候,代码看起来没问题,但逻辑分析仪上几个微秒的时序偏差,就是问题所在。
261

被折叠的 条评论
为什么被折叠?



