1. 项目概述与核心价值
在嵌入式系统开发领域,尤其是基于Motorola(现NXP)MC68VZ328这类经典微控制器的项目中,有两项基础但至关重要的底层工作: 板载Flash存储器的编程 和 监控程序的初始化 。前者决定了你的固件如何被“烧录”进硬件,成为设备上电后执行的“灵魂”;后者则是在上电瞬间,为这个“灵魂”准备好一个可以正确运行的“身体”——即初始化所有关键硬件外设,建立稳定的运行环境。很多开发者拿到开发板后,直接使用厂商提供的编译好的镜像和下载工具,对背后的机制一知半解,一旦遇到需要定制Bootloader、修改内存映射或者调试启动失败的问题,就会束手无策。
我手头这份来自 M68VZ328ADS开发板用户手册 的附录代码,正是揭开这层神秘面纱的钥匙。它不是什么抽象的理论,而是Motorola官方工程师编写的、可直接编译运行的汇编源码。本文将带你深入这两段代码的肌理,不仅逐行解读其实现,更会结合我多年在类似68K平台上的调试经验,补充数据手册中未曾明说的细节、参数计算的来龙去脉以及实际烧录时可能遇到的“坑”。无论你是正在学习经典嵌入式架构的学生,还是需要为老旧设备维护或升级固件的工程师,理解这些底层操作,都将让你对系统的掌控力提升一个维度。
2. 板载Flash编程算法深度解析
Flash编程远非简单的内存写入。它本质上是一种需要特定“解锁”和“命令序列”才能触发的特殊操作模式。M68VZ328ADS板上通常搭载的是类似AMD Am29LV系列的NOR Flash。这类存储器将存储空间划分为多个扇区,在执行写操作前,往往需要先进行扇区擦除(变为全1状态),然后通过一系列特定的地址-数据命令字来进入编程状态。
2.1 Flash编程的基本原理与命令序列
NOR Flash的编程通常遵循一个标准的“命令集”协议,例如AMD的“解锁-编程”序列。核心思想是:向特定的“命令寄存器”地址写入特定的数据序列,来告知Flash芯片接下来要进行的操作(如擦除、编程、复位等)。这些命令寄存器并非物理上独立的寄存器,而是通过访问Flash内存空间中的特定偏移地址来模拟的。
在提供的代码中, OFFSET1 ( $AAA ) 和 OFFSET2 ( $554 ) 这两个常量至关重要。它们对应的就是Flash命令集中“解锁”周期所需的地址偏移。在16位数据总线、字节寻址的系统中, $AAA 和 $554 是经过字节地址换算后的值。以常见的Am29LV160为例,其命令序列要求向地址 0x555 写入 0xAA ,向地址 0x2AA 写入 0x55 。由于ADS板可能将Flash映射到 0x01000000 起始的地址空间,并且数据总线为16位, OFFSET1 和 OFFSET2 就是相对于Flash基址的、用于写入命令字的地址偏移。
代码中的 ENABLE 宏正是实现了这个解锁序列:
ENABLE MACRO
move.w #$00AA,(A5) ; 第一步:向解锁地址1写入0xAA
move.w #$0055,(A6) ; 第二步:向解锁地址2写入0x55
move.w #$00A0,(A5) ; 第三步:向解锁地址1写入编程命令0xA0
ENDM
这里 A5 和 A6 寄存器分别被初始化为Flash基址加上 OFFSET1 和 OFFSET2 。写入 0x00AA 和 0x0055 完成解锁,紧接着写入 0x00A0 即进入“字编程”模式。此后,向目标地址写入的数据才会被真正编程到Flash单元中。
注意 :不同品牌、型号的Flash芯片,其命令序列可能不同。代码注释也明确指出“Different brands of Flash memory may have different program command sequences”。因此,在实际替换板载Flash芯片时, 必须 查阅其数据手册,并可能需修改此
ENABLE宏及整个算法流程。这是移植代码到其他硬件平台的第一要务。
2.2 编程流程与数据校验机制详解
主编程循环 PROGRAM 的逻辑清晰体现了稳健性设计。其核心步骤是:
- 使能编程 :调用
ENABLE宏,让Flash进入编程状态。 - 写入数据 :
move.w (a2), (a3)将源地址(RAM中暂存的程序镜像)的一个字(16位)写入目标地址(Flash)。 - 轮询等待 :进入
POLLING循环,不断比较源数据和刚刚写入Flash的数据。这是因为Flash编程需要时间(典型值为几个到几十微秒),在此期间读取操作会返回一个“忙”状态。只有当编程完成,读出的数据才会与写入的数据一致。 - 超时判断 :循环中有一个计数器
d4与TIME($FFF) 比较,如果超时则认为编程失败,跳转到ERROR处理。这是防止因Flash损坏或硬件故障导致程序死锁的必要措施。 - 进度指示与循环 :每成功编程一个字,通过
ECHO宏(推测是向串口发送字符‘W’)输出进度指示,然后移动源和目标指针,检查是否完成所有数据的编程。
编程完成后,代码并没有直接结束,而是进入了 VERIFY (验证)阶段。这是一个 极其重要 的步骤。它从头开始,逐字比较源数据(RAM中)和目标数据(Flash中)是否完全一致。任何不一致都会导致跳转到 ERROR 。验证通过后,才输出“PASS”并跳转到 BOOTSTRAP 。
实操心得 :在实际批量生产或现场升级中, 验证环节绝对不能省略 。我遇到过因电源纹波过大,导致Flash个别位编程不稳定的情况,如果没有验证,有缺陷的固件就会被交付,造成设备功能异常甚至变砖。此外,
TIME超时值的设置需要根据具体Flash芯片的“字编程超时”参数来调整,手册中$FFF是一个经验值,但最稳妥的方式是查阅Flash数据手册中的“最大字编程时间”参数,并换算成CPU循环次数。
2.3 关键参数与地址映射分析
代码的 SECTION parameter 部分定义了编程过程所需的所有关键参数:
-
pSOURCE DC.L $00010000: 源数据在RAM中的起始地址。0x00010000是ADS板上SDRAM的典型映射地址。这意味着你的程序镜像需要先被加载到SDRAM的这一区域。 -
pTARGET DC.L $01000000: 目标Flash的起始地址。这是板上Flash内存被映射到的CPU地址空间位置。 -
pSIZE DC.L $00010000: 需要编程的数据大小,这里是64KB。 -
pFLASH DC.L $01000000: Flash的基地址,用于计算命令序列的偏移地址。
这些参数在调用编程例程前,通常需要根据你的实际镜像大小和内存布局进行调整。例如,如果你的程序只有32KB,那么 pSIZE 应改为 $00008000 。
避坑指南 :
pSOURCE地址的选择非常关键。你必须确保在编程例程运行时,存放程序镜像的那块RAM区域 不会被当前运行的程序本身覆盖 。这段编程代码本身通常是在RAM中运行(例如通过调试器下载到RAM并执行),因此pSOURCE指向的必须是另一块“安全”的RAM区域。一个常见的做法是,将编程代码和待烧录的镜像放在RAM中两个互不重叠的区间。
3. 监控初始化代码精读与硬件配置
监控初始化代码是系统上电后,在跳转到用户主程序 main() 之前,由CPU首先执行的一段底层汇编代码。它的任务是将芯片从“复位”后的原始状态,配置成一个可以运行高级语言程序(如C程序)的稳定环境。
3.1 系统基础初始化:关闭“双映射”与时钟配置
代码开头的一系列操作是搭建最基础的运行平台:
move.b #$18,SCR ; Disable Double Map
SCR (系统配置寄存器)的 #$18 操作,其核心目的是 禁用双映射 。MC68VZ328芯片在复位后,其内部寄存器和部分内存区域在地址空间中有两个映射地址(例如,一个在 0xFFFFF000 ,一个在 0x7FFFF000 )。禁用双映射可以简化内存布局,避免访问歧义,是系统初始化中非常标准且首要的一步。
move.w #$2480,PLLCR ; ??MHz Sysclk, enable clko
这行代码配置锁相环控制寄存器 PLLCR 。值 0x2480 是一个具体的配置字,它设置了系统时钟(SYSCLK)的频率和分频比,并开启了CLKO引脚输出。具体的频率需要根据外部晶振频率和 PLLCR 的位域来计算。例如,如果外部晶振是32.768kHz, 0x2480 这个配置可能将系统时钟倍频到某个特定值(如16.58MHz)。 这里手册注释为“??MHz”,在实际项目中,你必须根据板载晶振和所需系统频率,精确计算并填写这个值。
3.2 关键外设初始化:芯片选择、SDRAM与LCD
3.2.1 芯片选择(Chip Select)配置
这是初始化中最核心的部分之一,它决定了CPU如何访问外部存储器(Flash、SDRAM)和外设。
move.w #$0800,GRPBASEA ; GROUPA BASE(FLASH), Start add.=0x1000000
move.w #$0199,CSA ;
-
GRPBASEA(Group A Base Address Register): 设置为0x0800。这个值定义了Group A的基地址。0x0800左移16位(因为寄存器高16位有效)得到0x08000000,但注意代码注释和参数部分都指出Flash起始地址是0x01000000。这里可能存在歧义,需要结合芯片手册确认。一种可能是GRPBASEA的位定义并非直接是地址的高16位,而是包含其他控制位;另一种可能是代码中实际使用的是CSA寄存器的配置来最终确定地址。CSA(Chip Select A Register) 的值0x0199,其每一位定义了该片选区域的地址掩码、读写等待状态、端口大小(8/16位)等关键属性。例如,0x0199可能表示一个16位端口、启用、具有特定等待状态的配置。
深度解析 :
CSA寄存器值0x0199(二进制0000 0001 1001 1001)需要按位解读。参考MC68VZ328手册,该寄存器可能包含以下字段:AM(地址掩码)、R/W(读/写控制)、PS(端口大小)、WS(等待状态)、EN(使能)。你需要根据目标Flash的访问时序(如读周期时间、写脉冲宽度)和CPU时钟频率,来计算需要插入多少个等待状态(WS),以确保可靠读写。配置不当会导致读取数据错误,系统无法启动。
3.2.2 SDRAM控制器初始化
SDRAM的初始化比SRAM或Flash复杂得多,必须遵循严格的上电序列:
move.w #$0000,DRAMC ; Disable DRAM Controller
move.w #$C03F,SDCTRL
move.w #$4020,DRAMMC
move.w #$8000,DRAMC
- 禁用控制器 :首先关闭DRAM控制器,防止在配置过程中产生意外的访问。
- 配置模式寄存器 :
SDCTRL和DRAMMC(其实是同一地址$C00的不同功能视图)用于设置SDRAM的诸多参数,如行列地址位数、CAS延迟、突发长度、工作模式等。0xC03F和0x4020是具体配置值,它们定义了SDRAM的规格(如代码注释“64M-bit, Single Bank, Latency 2”)。 - 使能控制器 :将
0x8000写入DRAMC,可能是在使能控制器的同时,启动了初始化序列。
紧接着的代码执行了关键的SDRAM初始化命令序列:
move.w #$C83F,SDCTRL ; issue precharge comm
; ... 若干nop延时 ...
move.w #$D03F,SDCTRL ; enable refresh
; ... 若干nop延时 ...
move.w #$D43F,SDCTRL ; issue mode command
- 预充电(Precharge) :对所有Bank进行预充电,为后续操作做准备。
- 自动刷新(Auto Refresh) :执行至少两次(通常8次以上)自动刷新命令,以初始化SDRAM内部的刷新计数器。
- 加载模式寄存器(Load Mode Register) :将之前配置的模式参数(如CAS延迟、突发类型)写入SDRAM芯片的模式寄存器。
每个命令后都有一系列 nop 指令,这是为了满足SDRAM命令之间的最小时间间隔(如 tRP , tRFC )。 这些延时周期的数量必须根据SDRAM芯片的数据手册和系统时钟频率来精确计算 ,代码中写死的 nop 数量可能只适用于特定频率的时钟。
3.2.3 LCD控制器初始化
对于带LCD屏的应用,控制器初始化决定了显示是否正常。
move.l #$100403E,LSSA ; Screen Start Address
move.w #160,LXMAX ; Screen Width
move.w #239,LYMAX ; Screen Height
move.b #10,LVPW ; Virtual Page Width
-
LSSA:设置显存起始地址。0x100403E这个值表明显存位于SDRAM中(0x01000000之后),并且可能做了对齐。 -
LXMAX和LYMAX:设置屏幕的物理分辨率,这里是160x240(注意LYMAX是239,表示Y坐标从0到239)。 -
LVPW:虚拟页宽度,通常设置为比实际宽度稍大的值,用于硬件滚动等功能。
后续的 LPICF (面板接口配置)、 LPOLCF (极性配置)、 LPXCD (像素时钟分频器)等寄存器,则根据具体LCD屏的接口类型(如单色、4级灰度、TFT)、时序要求(行场同步脉冲宽度、前沿后沿)来配置。 LCKCON 寄存器的操作(先 0x00 禁用,再 0x80 使能)是标准的“先配置后启用”模式。
3.3 启动模式与调试接口选择
代码中有一段根据拨码开关(通过PD2、PD3端口读取)状态选择启动镜像和调试UART的逻辑:
ori.b #$0F,PDSEL ; 配置PD端口功能
move.b #$03,PDDIR ; 设置PD2, PD3为输入
move.b #$FF,PDPUEN ; 使能上拉电阻
move.b PDDATA,D0 ; 读取PD端口值
andi.b #$0C,D0 ; 屏蔽出PD2和PD3
根据PD2和PD3的电平组合,代码会跳转到不同的入口:
-
PD3=OFF, PD2=OFF->MW_UART1(Metrowerks 监控,使用UART1) -
PD3=OFF, PD2=ON->MW_UART2(Metrowerks 监控,使用UART2,并跳转到备用启动镜像0x01010000) -
PD3=ON, PD2=OFF->SDS_UART1(SDS 监控,使用UART1) -
PD3=ON, PD2=ON->SDS_UART2(SDS 监控,使用UART2,并跳转到备用启动镜像)
这个设计提供了极大的灵活性 :
- 双镜像启动 :主Flash镜像在
0x01000000,备用镜像在0x01010000。如果主镜像损坏,可以通过开关从备用镜像启动,为恢复系统提供了后路。 - 调试器选择 :支持Metrowerks CodeWarrior和SDS(Software Development System)两种不同的调试监控程序,通过不同的UART端口进行通信。
实操心得 :在产品开发中, 强烈建议保留并利用这种双镜像和调试端口选择机制 。它不仅是开发调试的利器,更是产品现场维护和固件升级失败的“救命稻草”。我曾经利用备用镜像启动,成功修复了因主镜像编程错误而“变砖”的设备。
4. 从理论到实践:完整的Flash烧录与启动流程
理解了代码之后,我们将其串联成一个完整的、可操作的流程。假设你正在使用一套像SDS或CodeWarrior这样的经典68K开发环境。
4.1 准备工作:生成与准备镜像
- 编译链接 :你的应用程序代码(通常是C语言混合汇编)经过编译器、汇编器、链接器处理,最终生成一个可执行的二进制文件(如
.elf或.abs格式)。 - 格式转换 :使用开发工具链中的工具(如
DOWN.EXE),将二进制文件转换为Motorola S-Record格式(.s19或.srec)。正如代码注释提示, 关键的一步是指定正确的偏移参数 。例如:
这里的DOWN.EXE your_program.abs -o your_program.srec -w 0x10000-w 0x10000参数,是因为你的程序在链接时,链接地址(Load Address)被设置为0x00010000(即SDRAM地址)。这个S-Record文件中的数据记录会包含这个地址信息。编程代码中的pSOURCE($00010000) 必须与此一致。 - 加载到RAM :通过调试器(如SDS Monitor),将上一步生成的S-Record文件下载到目标板的SDRAM中,起始地址就是
0x00010000。此时,你的程序镜像已经安静地躺在RAM里,等待被“烧录”进Flash。
4.2 执行编程:在监控程序中调用算法
- 定位编程代码 :将本文分析的Flash编程汇编代码(从
START标签开始)单独编译或作为一段子程序,同样通过调试器下载到目标板的RAM中另一个 不会与程序镜像冲突 的区域,例如0x00020000。 - 设置参数 :在调试器的内存查看/修改窗口中,找到编程代码中
SECTION parameter定义的参数区域。根据你的实际情况修改:-
pSIZE: 改为你程序镜像的实际大小。 - 确保
pSOURCE,pTARGET,pFLASH正确无误。
-
- 执行 :在调试器中,将程序计数器(PC)设置为编程代码的入口地址(即
START标签处的地址),然后运行。你将通过串口看到输出的‘W’(编程中)和‘V’(验证中)字符,最后看到“PASS”或“ERROR”。 - 处理错误 :如果输出“ERROR”,编程代码会将出错的地址存入
pERROR_ADDRESS。你需要检查:- 电源是否稳定?Flash编程对电压敏感。
- Flash芯片型号与命令序列是否匹配?
-
TIME超时值是否足够?尝试增大它。 - 目标地址是否在Flash的可写扇区内?有些Bootloader区域可能被保护。
4.3 验证与启动
- 复位验证 :编程成功后,复位开发板。确保启动模式开关(PD2/PD3)设置正确,指向你刚刚烧录的Flash镜像。
- 观察现象 :如果初始化代码中配置了LED(代码中似乎有对PB7的操作),观察LED是否按预期闪烁。如果有串口输出,检查是否有监控程序或你应用程序的启动信息。
- 调试 :如果系统没有启动,首先检查最基础的:
- 时钟 :用示波器测量CLKO引脚,看系统时钟是否正常起振,频率是否符合预期(由
PLLCR配置决定)。 - 复位信号 :确保复位引脚在上电后已稳定释放至高电平。
- 关键总线信号 :使用逻辑分析仪抓取Flash的片选(
~CSA0)、读(~OE)、写(~WE)信号,看CPU是否在尝试从Flash的起始地址(0x01000000)读取指令。如果没有活动,说明CPU可能没有正确执行Flash中的代码,需检查启动开关配置或Flash内容。
- 时钟 :用示波器测量CLKO引脚,看系统时钟是否正常起振,频率是否符合预期(由
5. 常见问题排查与调试技巧实录
即使按照手册操作,在实际硬件上也可能遇到各种问题。以下是我在类似项目中总结的一些典型问题及排查思路:
问题1:Flash编程总是失败,返回ERROR。
- 检查电源和时钟 :Flash编程对电源稳定性要求极高。用示波器检查Flash芯片的VCC引脚,确保在编程瞬间没有大的跌落或毛刺。同时,确认系统时钟频率稳定,过高的频率可能导致时序不满足。
- 确认命令序列和时序 :这是最常见的原因。仔细核对板载Flash芯片的型号,找到其数据手册中的“Software Command Definitions”章节。逐条对比代码中的
ENABLE宏命令序列、数据以及地址偏移是否完全一致。特别注意,有些Flash需要先执行“擦除”命令才能编程,而示例代码假设目标区域已是擦除状态。 - 检查硬件连接 :检查Flash芯片的
~WE(写使能)、~OE(输出使能)、~CE(片选)引脚与CPU的连接是否正确,上拉电阻是否已安装。虚焊或断线会导致信号异常。
问题2:程序烧录成功,但复位后不运行。
- 启动地址错误 :确认CPU的复位向量是否正确指向Flash起始地址。对于MC68VZ328,硬件复位后CPU会从地址
0x00000000(如果配置为从CS0启动)或0x01000000(如果配置为从CSA启动)读取初始SP和PC。你的程序链接脚本必须确保向量表位于这个地址。 - 初始化代码未正确配置内存 :最常见的是SDRAM初始化失败。如果应用程序代码链接到了SDRAM地址空间(如
0x00010000),但SDRAM控制器初始化不正确,那么CPU在跳转到SDRAM中执行时就会立即跑飞。 在监控初始化代码执行后,通过调试器手动读取SDRAM的测试地址(如0x00010000),写入一个值再读回,验证SDRAM是否可正常访问。 - 堆栈指针(SP)设置不当 :监控初始化代码中设置了
MON_STACKTOP($4100)。如果你的应用程序有自己的启动代码,需要确保在跳转到C语言main()函数前,SP被设置到一个有效的、有足够空间的RAM区域。
问题3:通过调试器可以运行,但独立启动不行。
- Bootloader与应用程序的衔接问题 :如果你的系统使用Bootloader来加载应用程序,确保Bootloader在跳转到应用程序前,已经正确关闭了可能影响应用程序的中断,或者将中断向量表重定向到了应用程序的向量表。
- 看门狗未禁用 :监控代码中有
move.w #$00,RTCWD禁用看门狗。如果你的应用程序没有喂狗逻辑,且看门狗被意外使能,系统会在短时间内复位。检查应用程序是否重新配置了看门狗。 - 中断冲突 :监控程序可能初始化了中断控制器(如设置
IVR,IMR)。如果你的应用程序使用了不同的中断处理方式,但没有妥善接管或屏蔽,可能导致一进入应用就触发异常。
问题4:LCD无显示。
- 时序参数不匹配 :
LPICF、LPOLCF、LPXCD等寄存器配置必须严格匹配你使用的LCD屏的数据手册时序要求。一个参数错误就可能导致无显示。建议先用示波器测量LCLK(像素时钟)、LFRM(帧同步)、LLP(行同步)等信号,看其频率和极性是否符合屏厂要求。 - 显存地址或格式错误 :
LSSA指向的显存区域必须确保是可读写的SDRAM,并且应用程序向该区域写入的像素数据格式(如单色、4位灰度、16位色)必须与LPICF寄存器中配置的显示模式一致。
调试技巧:利用“LED”和“串口”进行裸机调试 在没有高级调试器的情况下,GPIO控制的LED和UART串口是最可靠的调试伙伴。
- LED调试法 :在初始化代码的关键步骤后,增加控制LED亮灭的代码(如设置/清除
PBDATA的某一位)。通过观察LED的闪烁模式,可以判断代码执行到了哪个阶段(例如,SDRAM初始化前亮,初始化后灭)。 - 串口打印法 :在代码中尽早初始化一个UART(如UART1),然后在整个初始化流程中,通过串口发送不同的字符或字符串到PC终端。这是定位问题最有效的手段之一。示例代码中的
ECHO宏很可能就是用于此目的,你需要根据硬件连接,实现对应的串口发送函数。

2853


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



