MC68HC912 Flash与EEPROM底层编程:SST算法与AUTO模式详解
1. 项目概述:深入MC68HC912的非易失性存储器操作
在嵌入式开发的底层世界里,直接与微控制器(MCU)的Flash和EEPROM存储器打交道,是每个资深工程师必须掌握的硬核技能。这不仅仅是调用一个库函数那么简单,它关乎到你的固件能否被正确烧录、设备参数能否在断电后幸存,以及整个系统长期运行的可靠性。我接触过不少项目,因为对这部分理解不透彻,导致产品在现场出现莫名其妙的“失忆”或者根本无法启动,排查起来极其痛苦。
今天,我们就以Freescale(现NXP)经典的HC12家族成员——MC68HC912DT128A/DG128A为例,彻底拆解其Flash和EEPROM编程擦除的汇编级实现。这份来自原厂的应用笔记代码,就像一份珍贵的“武功秘籍”,直接展示了如何通过最底层的寄存器操作和精确到微秒级的延时控制,来驯服这些非易失性存储器。我们将聚焦于两个核心算法:用于Flash的SST(SuperFlash)编程算法,和用于EEPROM的AUTO模式操作。理解这些代码,不仅能让你在HC12平台上游刃有余,其背后关于时序、电压控制、总线操作的思想,对于理解其他架构的MCU存储控制器也大有裨益。
2. 核心硬件与原理深度解析
在动手写代码之前,我们必须先搞清楚MC68HC912DT128A/DG128A内部存储器的“脾气”。这块芯片的Flash和EEPROM并非简单的存储阵列,它们内部集成了一套需要特定“仪式”才能激活的编程/擦除电路。
2.1 Flash存储器结构与SST算法本质
MC68HC912的Flash模块组织为页(Page)和行(Row)。根据数据手册,一次擦除的最小单位通常是一个页(例如2KB或4KB),而编程的最小单位是一个行,在提供的代码中,一行是64字节(32个字)。这里有个关键点:Flash的编程只能将位从‘1’变为‘0’,而擦除操作则是将整个页的位全部恢复为‘1’。因此,标准的操作流程永远是“先擦后写”。
SST算法,全称可能是“SuperFlash”或类似专有技术,其核心目标是在满足芯片严格时序要求的前提下,最小化编程一个行所需的总时间。它并不是一个复杂的软件协议,而是一系列精心编排的硬件寄存器操作和精确延时。算法的精髓在于对几个关键控制位的顺序操作:
- PGM/ERAS位:告诉Flash控制器,“我要开始编程或擦除了”。
- HVEN位:控制内部高压泵的开关。给浮栅晶体管注入或移除电荷需要高于VDD的电压,这个高压就是由HVEN使能后内部生成的。
- 向特定地址的写入操作:这并非写入数据,而是向地址线发送一个特定的“命令”,触发内部状态机的下一步动作。
代码中所有的延时,如tNVS、tERAS、tPGS,都是芯片数据手册中规定的、必须满足的最小时间间隔。例如,tNVS是“命令建立时间”,tERAS是“擦除脉冲宽度”。不满足这些时序,轻则编程失败,重则可能损伤存储单元。
2.2 EEPROM与AUTO模式的便利性
相比之下,EEPROM的操作对开发者友好得多。它支持字节/字编程,并且通常不需要先擦除整个大块(尽管擦除操作依然存在)。MC68HC912的EEPROM控制器提供了一个非常方便的AUTO模式。
在AUTO模式下,你只需要做三件事:
- 配置
EEPROG寄存器,一次性设置好操作模式(擦除/编程)、操作粒度(BULK/ROW/WORD)并拉高EELAT(锁存地址)和AUTO位。 - 向目标地址执行一次普通的写操作(这实际上将数据和地址锁存)。
- 拉高
EEPGM位启动自动过程。
之后,硬件会自动接管,按照内置的时序发生器完成高压产生、脉冲施加、验证等所有步骤,并在完成后自动清除EEPGM位。开发者只需轮询EEPGM位等待其变低即可。这大大简化了软件负担,也降低了因软件延时不准而导致操作失败的风险。代码中需要配置EEDIV寄存器,就是为了设置这个内部时序发生器的时钟分频,以确保其时间基准符合芯片要求(例如代码注释中提到的35us)。
关键原理提示:无论是Flash的SST算法还是EEPROM的AUTO模式,其底层物理过程都是通过 Fowler-Nordheim 隧穿或热电子注入等方式,改变浮栅晶体管的阈值电压,从而表示‘0’或‘1’。软件代码的本质,是严格按照时序要求,为这一物理过程提供正确的电信号控制序列。
3. SST Flash编程算法汇编代码逐行精讲
提供的SSTflash.mrt和SSTflash.srt文件构成了完整的Flash操作范例。我们跳过文件头部的版权信息,直接切入核心。
3.1 主程序SSTflash.mrt:流程搭建与数据准备
主程序位于SSTflash.mrt中,它清晰地展示了使用SST算法对Flash进行编程的标准工作流。
org RAM+$50 ;代码起始地址,避开RAM底部用作缓冲区 Start: lds #$4000 ;设置堆栈指针 movb #$8F,COPCTL ;使能时钟监控功能开头是标准的初始化:设置堆栈、配置看门狗(COPCTL)。这里有个细节:使能时钟监控(Clock Monitor)在Flash操作期间尤为重要。因为编程和擦除对时钟稳定性有要求,如果时钟丢失,监控电路可以触发复位,防止芯片执行在异常时钟下的错误操作,这可能损坏Flash。
ldx #$00 ldaa #$1 Data_load: staa DATA,x ;向RAM缓冲区DATA填充数据01,02,03... inca inx cmpa #!65 ;注意:这里是65,因为填充了1到64 bne Data_load这段代码在RAM中准备要编程的数据。DATA是在SSTflash.var文件中定义的一个64字节缓冲区。这里它被填充为1到64的序列值。在实际项目中,这里应该是你的应用程序数据,比如固件代码段、配置参数等。
movb #$01,PPAGE ;选择PPAGE=1,即Flash的页1 bclr FEEMCR,BOOTP. ;如果此页中的引导块被保护,清除BOOTP位 ldx #$8002 ;加载页内任意一个对齐的字地址 jsr FlashErase ;调用子程序,擦除整个选定页接下来是擦除操作。首先通过PPAGE寄存器选择要操作的Flash页(该芯片采用分页机制扩展地址空间)。FEEMCR是Flash和EEPROM控制寄存器,BOOTP位用于保护引导块,在擦除前需要确保它被清除(如果该页包含引导块且被保护,则无法擦除)。然后,将页内的一个字对齐地址(如$8002)加载到X寄存器,并调用FlashErase子程序。为什么是任意地址?因为擦除是以页为单位的,只要地址落在目标页内即可,硬件不关心具体的偏移。
ldx #$BF40 ;加载编程行的起始地址(必须是$xx00, $xx40, $xx80, $xxC0之一) ldy #DATA ;加载RAM缓冲区起始地址 jsr ProgRow ;调用子程序,编程一行(32个字)擦除完成后进行编程。这里有一个关键约束:行起始地址必须是64字节对齐的(即末两位十六进制为00,40,80,C0)。代码中$BF40符合要求。Y寄存器指向准备好的数据缓冲区。调用ProgRow完成一行数据的编程。
Verify: movb #!32,COUNTER ;一行有32个字 ldy #DATA ldx #$BF40 Verify_Loop: ldd ,X ;从Flash地址读取一个字 cpd ,Y ;与RAM缓冲区中的数据比较 bne Error ;不相等则跳转到错误处理 dec COUNTER beq Success inx inx ;X加2,指向下一个字地址 iny iny ;Y加2,指向下一个数据 bra Verify_Loop Success: bra * ;编程成功,原地循环 Error: bra * ;编程失败,原地循环(实际应跳转到错误处理程序)编程后的验证环节至关重要。代码逐字比较Flash中读出的数据和RAM缓冲区中的原始数据。任何不匹配都会跳转到错误处理。在实际产品代码中,Success和Error标签后绝不能是简单的bra *死循环,而应该返回成功/错误标志,或者触发系统复位/告警。
3.2 核心子程序SSTflash.srt:时序的精确舞蹈
真正的魔法发生在SSTflash.srt中。我们以FlashErase(页擦除)子程序为例,看它如何与硬件共舞。
FlashErase: sei ;步骤1 - 禁止可屏蔽中断 bset FEECTL,ERAS. ;步骤1 - 设置ERAS位 std ,X ;步骤2 - 向Flash地址(X指向)写入任意对齐字数据第一步就拉高了中断屏蔽位SEI。这是强制要求!因为在接下来的精确延时过程中,任何中断的插入都会破坏时序,导致擦除失败。FEECTL是Flash和EEPROM控制寄存器,设置ERAS位表明这是一次擦除操作。随后向目标地址(X寄存器指向的地址)执行一次写操作(std ,X)。注意,这里写入的数据(D寄存器的值)是无关紧要的,这次写入的作用是向Flash控制器提交“命令”,触发内部状态机进入擦除序列。
ldaa #$1B ;步骤3 - 等待tNVS时间(10.25us) dbne A,* ; 1 + (3 x 27) = 82个周期 @ 8MHz bset FEECTL,HVEN. ;步骤4 - 设置HVEN位(开启高压)步骤3是一个精确的软件延时循环,等待tNVS(命令建立时间)。dbne A,*指令会循环递减A寄存器直到为0。计算如下:dbne指令本身不跳转时1个周期,跳转时3个周期。循环27次(#$1B是27),总周期数 = 1 + 3 * 27 = 82个周期。在8MHz总线频率下(周期125ns),82 * 125ns = 10.25us,正好满足tNVS要求。之后,才允许开启高压(HVEN)。
movb #!8,TIMES ;步骤5 - 等待tERAS时间(8.0ms) jsr ms_delay bclr FEECTL,ERAS. ;步骤6 - 清除ERAS位步骤5等待主要的擦除脉冲时间tERAS,长达8ms。这里调用了ms_delay子程序,通过传入TIMES=8来实现8ms延时。这个延时必须足够,它是电荷从浮栅移走的关键时间。之后清除ERAS位。
ldd #$010B ;步骤7 - 等待tNVHL时间(100.25us) dbne D,* ; 1 + (3 x 267) = 802 cycles bclr FEECTL,HVEN. ;步骤8 - 清除HVEN位(关闭高压)步骤7等待tNVHL(高压关闭后到下一次操作前的恢复时间)。这里用了双字节循环dbne D,*,循环次数为$010B(267)次,产生约100us的延时。然后才能安全地关闭高压。
ldaa #$03 ;步骤9 - 等待tRCV时间(1.25us) dbne A,* cli ;重新使能中断 FlashErase_End: rts最后等待一个很短的恢复时间tRCV,然后重新打开中断(CLI),子程序返回。整个流程就像一场精心编排的芭蕾,每一步的顺序和节奏都至关重要。
ProgRow(行编程)子程序的结构与此类似,但更复杂一些,因为它需要在开启高压后,循环写入64字节数据,并且要保证每个字写入的间隔时间tFPGM(约30us)在规定的30-40us窗口内。代码中通过一个包含movw和特定延时循环的Copy_Loop来实现这一点。
3.3 延时子程序ms_delay:周期级精度
ms_delay是生成毫秒级延时的通用子程序。其精妙之处在于循环次数的计算,以确保在8MHz下精确产生TIMES毫秒的延时。代码注释中已经给出了计算公式:延时 = [{1 + (2 + 3) * 1597 + 2 + 1 + 3 + 1 + 4 + 3} * (TIMES - 1) + {1 + (2 + 3) * 1597 + 2 + 1 + 3 + 1 + 4 + 1 +5}] / 8MHz = (8000 * TIMES + 3) / 8MHz这体现了在资源受限的嵌入式环境中,如何用汇编语言实现精确计时。在实际使用中,如果总线频率不是8MHz,你必须根据公式重新计算循环常数,否则所有时序都会错乱。
4. EEPROM AUTO模式汇编代码详解
EEPROM的AUTO模式代码位于AutoEEPROM.mrt和AutoEEPROM.srt中,其思路与Flash不同,更侧重于寄存器的正确配置。
4.1 主程序AutoEEPROM.mrt:配置与调用
movw #$0230,EEDIVH ;振荡器频率16MHz时,设置EEDIV为$0230以获得35us时基 movw #$55AA,DATA ;将要编程的数据字$55AA存入RAM缓冲区 ldx #$0E02 ;加载要对齐的字地址 bclr EEPROT,BPROT3. ;解除目标块的保护 ldaa #auto_WORDerase. ;选择字擦除模式(AUTO, BYTE, ERASE, EELAT) jsr AutoRoutine ;调用AUTO模式例程执行擦除 ldaa #auto_WORDprogram. ;选择字编程模式(AUTO, EELAT) jsr AutoRoutine ;调用AUTO模式例程执行编程 bset EEPROT,BPROT3. ;重新保护已编程的块主程序流程非常清晰:
- 配置时钟分频器
EEDIV:这是AUTO模式正常工作的关键。代码中根据16MHz振荡器频率,计算并设置EEDIVH:EEDIVL为$0230,以确保内部编程/擦除时序的时间基准为35us。如果你用的晶振不是16MHz,这个值必须重算!计算公式参考芯片数据手册。 - 准备数据与地址:将想要写入的数据(例如
$55AA)存入DATA变量(2字节),并将目标对齐字地址加载到X寄存器。 - 解除块保护:通过清除
EEPROT寄存器相应的BPROT位,允许对目标EEPROM区域进行操作。这是一个重要的安全措施,防止意外写操作破坏关键数据。 - 执行擦除与编程:分别加载擦除和编程的模式字到A寄存器,并调用同一个
AutoRoutine子程序。模式字是在文件开头用equ定义的常量,它们组合了EEPROG寄存器的各个控制位(AUTO,EELAT,ERASE,BYTE等)。
4.2 核心子程序AutoRoutine:AUTO模式的执行引擎
AutoRoutine子程序是AUTO模式的核心,它极其简洁,因为复杂的工作都交给了硬件。
AutoRoutine: sei ;步骤1 - 禁止中断 staa EEPROG ;步骤1 - 将模式字(A寄存器)写入EEPROG寄存器 ldd DATA ;步骤2 - 从RAM缓冲区取数据 std ,X ;步骤2 - 写入X指向的地址(锁存地址和数据) bset EEPROG,EEPGM. ;步骤3 - 设置EEPGM位,启动自动过程 Clear_EEPGM: brset EEPROG,EEPGM.,Clear_EEPGM ;步骤4 - 轮询等待EEPGM位自动清除 bclr EEPROG,EELAT. ;步骤5 - 清除EELAT位 movb #$80,EEPROG ;向EEPROG写入复位值(可选,良好习惯) cli ;重新使能中断 AutoRoutine_End: rts流程解读:
- 禁止中断:同样是为了防止时序被打断。
- 配置并启动:
staa EEPROG一举两得。一方面,它根据调用者传入的模式字(A寄存器)配置了操作类型(擦除/编程、块/行/字);另一方面,该指令本身也设置了AUTO和EELAT位,使EEPROM控制器进入“地址/数据锁存”状态。 - 触发操作:紧接着的
std ,X执行了一次向目标地址的写操作。在EELAT有效的情况下,这个写操作并不会立即改变存储单元,而是将目标地址和要写入的数据(来自DATA)锁存到EEPROM控制器内部。 - 启动自动序列:
bset EEPROG,EEPGM.置位EEPGM位。这个信号告诉硬件:“地址和数据都已就绪,开始自动执行编程或擦除流程吧”。此后,硬件会内部使能高压、施加脉冲、进行验证,全部完成后自动清除EEPGM位。 - 等待完成:
brset指令循环检测EEPGM位,直到硬件将其清除,表明操作完成。 - 清理现场:清除
EELAT位,退出锁存状态。将EEPROG恢复为复位值$80(这是一个好习惯,确保寄存器处于已知状态)。最后重新使能中断并返回。
AUTO模式的优势与陷阱:优势是简单可靠,硬件保证了时序。但陷阱在于
EEDIV的配置和块保护状态。代码注释中特别用<CAUTION>标出:如果EEDIV被误设为00:00,或者目标地址处于保护状态(批量擦除除外),硬件将不会自动清除EEPGM位,导致软件在此处无限循环。因此,在调用AutoRoutine前,必须确保EEDIV值正确且目标区域未受保护。
5. 工程实践:从示例代码到产品级应用
原厂示例代码提供了一个可靠的起点,但直接照搬到产品中是不够的。下面分享一些我将这些代码投入实际项目时的经验和关键修改点。
5.1 关键参数适配与计算
首先,总线频率是生命线。示例代码所有延时都基于8MHz总线频率计算。你的系统频率是多少?12MHz?16MHz?还是内部RC振荡器?必须根据实际频率重新计算所有延时循环常数和EEDIV值。
对于Flash的SST算法,你需要根据数据手册中的tNVS、tERAS、tPGS、tFPGM、tNVHL、tRCV等时间参数,以及你的总线周期,重新计算dbne循环的次数。公式是:所需周期数 = 时间要求 / 总线周期时间。ms_delay子程序也需要调整其核心循环常数(代码中的#063E),以确保1ms延时的准确性。
对于EEPROM的AUTO模式,关键是计算EEDIV。公式通常为:EEDIV = (fOSC * tBASE / 128) - 1,其中fOSC是振荡器频率,tBASE是芯片要求的基础时间单位(如35us)。具体公式请查阅最新的数据手册。
5.2 增强鲁棒性:错误处理与状态检查
示例代码中的错误处理几乎是空白(Error: bra *)。在产品中,我们必须做得更好。
对于Flash操作:
- 验证失败处理:
Verify循环发现错误后,不应死循环。可以重试几次(例如3次),如果仍然失败,则记录错误码到特定RAM区域或备份寄存器,并触发系统复位或进入安全模式。 - 编程前检查:在调用
ProgRow前,可以增加对目标地址区域的读取检查,确保该区域已被擦除(全为$FFFF)。如果未擦除就编程,会导致数据错误。 - 超时机制:虽然SST算法是硬延时,但可以在外层调用时加入软件看门狗(WDOG)刷新,防止程序跑飞卡死在某个循环中。
对于EEPROM的AUTO模式:
- EEPGM超时检测:
brset EEPROG,EEPGM.,Clear_EEPGM这个轮询循环必须加入超时机制。例如,循环计数达到一个最大值(对应远大于最大编程/擦除时间)后,如果EEPGM仍未清除,则判定为操作失败,执行错误恢复。ldy #TIMEOUT_VALUE Poll_EEPGM: dbne Y, NotTimeout ; 超时处理:记录错误,清除EEPGM/EELAT,退出 bra EEPROM_Error NotTimeout: brset EEPROG,EEPGM.,Poll_EEPGM - 操作保护检查:在尝试编程/擦除前,读取
EEPROT寄存器,确认目标区域是否真的被解锁。这是一个防御性编程的好习惯。
5.3 代码封装与可移植性
为了在不同项目间复用,应将Flash和EEPROM操作封装成独立的、带明确接口的函数。
Flash驱动接口建议:
// 伪代码示例 typedef enum { FLASH_RESULT_OK, FLASH_RESULT_NOT_ERASED, FLASH_RESULT_VERIFY_FAIL, FLASH_RESULT_TIMEOUT, FLASH_RESULT_WRITE_PROTECTED } FlashResult_t; FlashResult_t Flash_ErasePage(uint16_t page_number); FlashResult_t Flash_ProgramRow(uint16_t row_address, const uint8_t *data); uint16_t Flash_CalculateChecksum(uint32_t start_addr, uint32_t length);对应的汇编函数应接受参数,返回状态,并且内部集成完整的擦除、编程、验证、错误处理流程。
EEPROM驱动接口建议:
typedef enum { EEPROM_RESULT_OK, EEPROM_RESULT_PROTECTED, EEPROM_RESULT_TIMEOUT, EEPROM_RESULT_INVALID_PARAM } EEPROM_Result_t; EEPROM_Result_t EEPROM_WriteWord(uint16_t address, uint16_t data); EEPROM_Result_t EEPROM_EraseWord(uint16_t address); EEPROM_Result_t EEPROM_EraseRow(uint16_t address); // 注意:EEPROM通常不需要先擦后写,写操作会自动处理封装时,注意将EEDIV的初始化、保护位的管理等内容也集成进去,提供一个EEPROM_Init()函数。
5.4 实际项目中的避坑指南
- 中断与低功耗模式:Flash/EEPROM操作期间,必须禁止中断。此外,也要避免进入STOP或WAIT等低功耗模式,因为内部高压泵可能需要活跃的系统时钟。
- 电压稳定性:编程和擦除对供电电压(VDD)有要求。确保在操作期间电压在芯片规格书规定的范围内。在电池供电设备中,如果电压过低,应禁止存储操作。
- 代码自身在Flash中的执行:当你要擦写当前代码所在的Flash页时(比如IAP,在应用编程),情况变得复杂。你必须将擦写函数和相关的数据缓冲区完整地复制到RAM中执行。因为Flash在擦写期间,该页是无法读取指令的。这需要精心设计链接脚本和启动代码。
- EEPROM的耐久性与数据管理:EEPROM有擦写次数限制(通常10万-100万次)。避免频繁写入同一地址。对于频繁更新的数据(如运行时间计数器),应采用磨损均衡算法,或者配合FRAM(铁电存储器)使用。
- 使用调试器:在开发阶段,利用调试器(如P&E Multilink)的单步、断点功能,结合示波器测量代码中注释提到的“测量点”(如Port A bit 0),可以直观地验证延时时序是否准确。这是调试Flash/EEPROM驱动最有效的方法。
6. 常见问题与调试技巧实录
即使理解了原理和代码,第一次上手调试也难免踩坑。下面是我在实际项目中遇到的一些典型问题及解决方法。
6.1 Flash编程验证失败
现象:ProgRow执行后,验证循环Verify_Loop失败,读回的数据与写入的不符。
排查思路:
- 检查擦除是否成功:在编程之前,先读取目标行地址的数据。擦除后的Flash位应全为‘1’(即读取值为
$FFFF)。如果发现不是$FFFF,说明擦除步骤就失败了。重点检查FlashErase子程序:- 传入的地址
X是否在目标页内且字对齐? PPAGE寄存器设置是否正确?FEEMCR寄存器的BOOTP位是否已清除(如果该页包含引导块)?- 用示波器或调试器检查
ms_delay子程序是否真的产生了8ms延时?总线频率设置对吗?
- 传入的地址
- 检查编程时序:如果擦除成功但编程失败,问题很可能在
ProgRow的tFPGM时序上。tFPGM是连续两个字编程操作之间的时间,必须在30-40us之间。代码中使用了一个ldaa #$4C的循环来产生约28.625us的延时,加上循环外的指令,总时间应在要求范围内。如果总线频率不是8MHz,这个循环常数必须调整。使用调试器在Copy_Loop内设置断点,或使用IO口翻转并配合示波器,测量两次movw指令之间的实际时间。 - 检查数据对齐与缓冲区:确保Y寄存器指向的RAM缓冲区
DATA确实包含了你想编程的64字节数据,并且没有其他代码意外修改了这块内存。确保X寄存器指向的行起始地址是64字节对齐的(末两位hex为00,40,80,C0)。
6.2 EEPROM的AUTO模式卡死
现象:程序执行到AutoRoutine中的brset EEPROG,EEPGM.,Clear_EEPGM处无限循环,EEPGM位始终为1。
排查步骤:
- 检查EEDIV寄存器:这是最常见的原因。确认
EEDIVH:EEDIVL的值是根据你的实际振荡器频率计算得出的。如果误设为0000,硬件时序发生器不工作,EEPGM位永远不会被清除。计算EEDIV是第一步,也是必须做对的一步。 - 检查块保护状态:确认在调用
AutoRoutine前,已经通过bclr EEPROT, BPROTx.正确解除了目标地址所在块的保护。如果地址被保护,对于非批量擦除的操作,EEPGM位也不会被自动清除。可以尝试先执行一次批量擦除(如果支持),看是否能通过,来帮助判断是否是保护问题。 - 检查电源电压:EEPROM编程/擦除对VDD电压有最低要求。如果电压过低,操作可能无法完成,导致
EEPGM位挂起。测量系统电压是否在芯片工作范围内。 - 加入超时跳出:如前所述,在产品代码中一定要为这个轮询循环加入超时机制。超时后,应执行错误恢复:先尝试清除
EEPGM和EELAT位(向EEPROG写入$00或复位值$80),然后报告错误。这可以防止系统永久死锁。
6.3 代码在RAM中运行的问题
现象:当尝试擦写当前代码所在的Flash页时,程序跑飞或进入异常状态。
解决方案:
- 链接脚本配置:在编译器/汇编器的链接脚本(.lcf, .ld文件)中,明确将Flash驱动函数(
FlashErase,ProgRow,ms_delay)以及其使用的变量(DATA,COUNTER,TIMES)分配到RAM区域的一个固定段。 - 启动代码复制:在
main函数开始前,在启动代码中,将这些函数和变量的二进制代码从Flash(例如,放在一个特殊的、不会被擦除的“引导加载程序”区域)复制到RAM中指定的地址。 - 函数指针调用:在C语言中,你可以将这些RAM中的函数地址赋值给函数指针,然后通过函数指针来调用。在汇编中,你需要知道这些例程在RAM中的确切入口地址,并使用
jsr跳转到那个地址。 - 最小化RAM代码:只将绝对必要的代码(擦除、编程、关键延时循环)复制到RAM。像数据准备、验证等逻辑可以留在Flash中。
这个过程比较繁琐,但却是实现固件自更新(IAP)功能的基础。建议在项目初期就规划好内存布局。
6.4 时序测量与验证技巧
在没有逻辑分析仪的情况下,如何验证延时是否准确?
IO口调试法:代码中已经给出了范例(注释掉的bset/bclr PORTA,PA0.)。你可以取消这些注释,将一个IO口(如PORTA0)配置为输出。在延时循环的开始将其拉低,在结束时拉高。然后用示波器测量这个低电平脉冲的宽度,即可得到实际延时时间。这是调试底层驱动最直观、最可靠的方法。
软件计数法:在已知频率下,用一个全局变量在延时函数中计数。在main函数中运行这个延时一段时间(比如1秒),然后通过调试器查看这个变量的值,可以反推延时函数的准确性。
最后,请务必、务必、务必仔细阅读你所使用的MC68HC912DT128A/DG128A芯片最新版本的数据手册(Data Sheet)和技术参考手册(Technical Reference Manual)。应用笔记(Application Note)中的代码是基于某个特定版本的芯片和工具链,而数据手册中的参数和约束才是永恒的真理。任何与手册描述不符的操作,都可能带来不可预知的风险。
