i.MX25平台WinCE 6.0 BSP中NAND Flash驱动移植实战指南
1. 项目概述
在嵌入式产品开发中,硬件平台的迭代和元器件选型的变更是家常便饭。我最近就遇到了一个典型的场景:一个基于飞思卡尔i.MX25处理器的工控设备项目,原先使用的三星K9LBG08U0M型号NAND Flash芯片因为停产或成本原因,需要更换为另一款同容量但型号不同的NAND Flash,例如K9LAG08U0M。设备运行的是Windows Embedded CE 6.0操作系统,这意味着我们需要对板级支持包(BSP)中的NAND Flash驱动进行适配和移植。这可不是简单地换个芯片就能启动的,BSP作为连接硬件与操作系统的桥梁,其驱动必须精确匹配硬件的电气特性和访问时序。对于NAND Flash这类有坏块管理、需要特定命令序列的存储介质,驱动适配更是重中之重。如果你也正在为i.MX25平台WinCE 6.0的BSP定制,尤其是NAND Flash驱动移植而头疼,那么这篇基于飞思卡尔官方应用笔记AN4111的深度实践指南,将为你梳理出一条清晰的路径。我会结合自己踩过的坑,详细拆解从读懂芯片手册到最终生成可启动镜像的全过程,让你不仅能“照着做”,更能“懂得为什么这么做”。
2. 核心原理与架构解析
2.1 BSP与驱动分层模型
在深入动手修改之前,我们必须先理解WinCE 6.0下i.MX25 BSP中NAND驱动的架构。这绝不是一堆散乱代码的堆砌,而是一个层次分明、各司其职的软件模型。理解这个模型,是后续一切修改工作的基础。
整个驱动栈可以清晰地分为两个主要部分:操作系统运行时(WinCE NK)和引导程序(Eboot)。有趣的是,它们共享了最底层的硬件抽象——Flash Media Driver(FMD)。FMD层是驱动移植的核心战场,它向上为文件系统(如TFAT、exFAT)或Flash分区驱动提供统一的块设备接口,向下则直接操作i.MX25内部的NAND Flash控制器(NFC)和具体的NAND芯片。这种设计的精妙之处在于,当我们需要支持一款新的NAND芯片时,绝大部分修改都集中在FMD这一层,上层的文件系统和下层的硬件控制器驱动通常无需变动。
注意:这里有一个关键例外,那就是Xloader(XLDR)。XLDR是i.MX25 BSP中一个非常早期的引导阶段,它负责初始化最基础的硬件(包括NAND控制器),并从NAND的特定位置加载Eboot。由于XLDR极度精简,它并没有使用完整的FMD层,而是直接包含了访问NAND所需的最基本代码。因此,移植新NAND型号时,XLDR的代码也需要进行相应的适配,这是我们后面会重点处理的一个环节。
2.2 NAND Flash关键参数详解
驱动移植的本质,是让软件“认识”并“正确操作”新的硬件。对于NAND Flash,这种“认识”就体现在一系列关键参数上。这些参数必须从新芯片的数据手册(Datasheet)中精准提取,任何一个参数的误读都可能导致系统无法启动或数据读写错误。
制造商与设备ID(NAND MARKER & DEVICE ID):这是芯片的“身份证”。系统上电后,驱动会发送
0x90命令读取这两个字节。例如,三星的制造商ID通常是0xEC。设备ID则定义了芯片的内部架构、容量和电压等详细信息。驱动代码中会有一个ID匹配表,我们必须将新芯片的ID加入其中,否则驱动会认为找不到支持的设备。容量与结构参数:这组参数定义了芯片的物理布局。
NAND_BLOCK_COUNT:芯片总共有多少个块(Block)。块是NAND擦除操作的最小单位。NAND_PAGE_COUNT:每个块包含多少个页(Page)。页是读写操作的基本单位。NAND_PAGE_SIZE:每个页的“主数据区”大小,常见的有2048字节(2KB)和4096字节(4KB)。NAND_SPARE_SIZE:每个页的“备用区”(Spare Area/OOB)大小,用于存放ECC校验码、坏块标记等元数据,常见的有64字节或128+字节。
坏块信息(BBI)参数:这是NAND驱动中最为棘手但也最关键的部分。NAND Flash允许出厂时存在坏块,并且在生命周期内也可能产生新的坏块。驱动必须知道如何识别它们。
BBI_MAIN_ADDR:坏块标记(Bad Block Marker)在页内的主数据区地址偏移。这里最容易出错。它不是简单的备用区起始地址(如2048),因为i.MX25的NFC内部会将一个大页分割成多个512字节的扇区进行处理。计算这个地址需要考虑NFC的内部缓冲区划分。官方文档给出了计算公式:Page_Size - ((512 + Spare_Per_Sector) * (Sectors_Per_Page - 1))。例如,对于一个2KB页、64字节备用区的芯片,NFC将其分为4个扇区(512+16),那么BBI_MAIN_ADDR = 2048 - ((512+16)*3) = 464。BBI_NUM:在一个块中,有多少个页包含坏块标记。通常是1(标记在第一页或最后一页),但有些芯片可能用2个页来标记以提高可靠性。BBIMarkPage[]:这是一个数组,指明坏块标记具体位于一个块中的哪一(几)页。例如,对于128页/块的芯片,如果标记在最后一页,那么这个值就是{127};如果标记在第一页,就是{0}。
总线宽度(NAND_BUS_WIDTH):数据总线是8位还是16位。i.MX25通常支持这两种模式,需要根据硬件板子的实际布线来设定。
2.3 移植工作的核心思路
基于以上理解,我们的移植工作可以归结为三个核心步骤,其逻辑关系如下图所示(概念性描述):
- 参数提取与定义:从新NAND芯片的数据手册中,准确提取上述所有关键参数。
- 代码集成:在BSP源代码树中,为新的NAND型号创建专属的头文件(
.h)和汇编包含文件(.inc),并将这些参数填入。然后修改BSP的配置文件,让编译系统知道这个新型号的存在,并能在编译时通过环境变量选择它。 - 环境配置与构建:在Platform Builder集成开发环境中,设置对应的环境变量,然后完整地重新构建BSP、XLDR、Eboot和NK镜像。
整个过程的挑战不在于代码有多复杂,而在于对细节的准确把握和对BSP编译系统的理解。下面,我们就进入实战环节。
3. 实战移植:一步步适配新NAND Flash
假设我们要将NAND Flash从K9LBG08U0M更换为K9LAG08U0M。请确保你已安装好i.MX25的BSP包和Platform Builder(或相应的VS2005/2008开发环境)。
3.1 第一步:创建芯片专属定义文件
这是最基础的一步,我们需要为新的芯片创建两个文件:一个C语言头文件(.h)供FMD驱动使用,一个汇编包含文件(.inc)供XLDR使用。
1. 创建头文件K9LAG08U0M.h这个文件应放置在BSP的公共NAND驱动头文件目录下,典型路径是:\WINCE600\PLATFORM\COMMON\SRC\SOC\COMMON_FSL_V2_PDK1_x\NAND\INC\
文件内容模板如下,你需要用从数据手册查到的实际值替换所有XXX标记的部分:
#ifndef __K9LAG08U0M_H__ #define __K9LAG08U0M_H__ // NAND Flash Chip CMD #define CMD_READID (0x90) // Read ID #define CMD_READ (0x00) // Read data 1st cycle #define CMD_READ2 (0x30) // Read data 2nd cycle #define CMD_RESET (0xFF) // Reset #define CMD_ERASE (0x60) // Erase setup #define CMD_ERASE2 (0xD0) // Erase #define CMD_WRITE (0x80) // Sequential data input #define CMD_WRITE2 (0x10) // Program #define CMD_STATUS (0x70) // Read status // NAND Flash Chip Size #define NAND_BLOCK_CNT (8192) // 例如:总块数 #define NAND_PAGE_CNT (128) // 例如:每块页数 #define NAND_PAGE_SIZE (2048) // 例如:页大小 2KB #define NAND_SPARE_SIZE (64) // 例如:备用区大小 64字节 #define NAND_BUS_WIDTH (8) // 例如:8位总线 // NAND Flash Chip #define NAND_NUM_OF_CS (1) // 片选数量,通常为1 // NAND Flash Chip ID #define NAND_MAKER_CODE (0xEC) // 制造商ID,三星为0xEC #define NAND_DEVICE_CODE (0xD3) // 设备ID,需查手册 #define NAND_ID_CODE ((NAND_DEVICE_CODE << 8) | NAND_MAKER_CODE) // NAND Flash Chip Operation Status #define NAND_STATUS_ERROR_BIT (0) // 状态寄存器错误位 #define NAND_STATUS_BUSY_BIT (6) // 状态寄存器忙位 // SWAP BBI (坏块信息) #define BBI_MAIN_ADDR (464) // 根据公式计算得出 #define BBI_NUM (1) // 通常为1 BYTE BBIMarkPage[1] = {127}; // 例如:坏块标记在每块的最后一页 #endif2. 创建汇编包含文件K9LAG08U0M.inc这个文件同样放在上述INC目录,供XLDR的汇编代码使用。注意这里有些参数需要以2的幂次方形式(左移位数,LSH)表示。
;------------------------------------------------------------------------------ ; File: K9LAG08U0M.inc ; Contains definitions for K9LAG08U0M NAND flash memory device. ;------------------------------------------------------------------------------ CMD_READID EQU 0x90 ; Read ID CMD_READ EQU 0x00 ; Read data field CMD_READ2CYCLE EQU 0x30 ; Read CMD second cycle CMD_READ2 EQU 0x50 ; Read spare field CMD_RESET EQU 0xFF ; Reset CMD_ERASE EQU 0x60 ; Erase setup CMD_ERASE2 EQU 0xD0 ; Erase CMD_WRITE EQU 0x80 ; Sequential data input CMD_WRITE2 EQU 0x10 ; Program CMD_STATUS EQU 0x70 ; Read status ; 注意:以下参数以2的幂次方形式定义,便于汇编进行移位操作 NAND_PAGE_CNT_LSH EQU (7) ; 2^7 = 128 页/块 NAND_PAGE_SIZE_LSH EQU (11) ; 2^11 = 2048 字节/页 NAND_BLOCK_SIZE_LSH EQU (NAND_PAGE_CNT_LSH+NAND_PAGE_SIZE_LSH) ; 2^18 = 256KB/块 ; 实际值的常量定义 NAND_PAGE_CNT EQU (1 << NAND_PAGE_CNT_LSH) ; 128 NAND_PAGE_SIZE EQU (1 << NAND_PAGE_SIZE_LSH) ; 2048 NAND_BLOCK_SIZE EQU (1 << NAND_BLOCK_SIZE_LSH) ; 262144 (256KB) NAND_BLOCK_CNT EQU (8192) ; 总块数 NAND_SPARE_SIZE EQU (64) ; 备用区大小 BBI_PAGE_NUM EQU (1) ; BBI_NUM BBI_PAGE_ADDR_1 EQU (NAND_PAGE_CNT - 1) ; 最后一页,即127 BBI_PAGE_ADDR_2 EQU (NAND_PAGE_CNT - 1) ; 如果BBI_NUM为2,这里可能是另一页地址 NUM_OF_NAND_DEVICES EQU 1 ; NAND器件数量 NUM_OF_NAND_DEVICES_LSH EQU 0 ; NAND_BUS_WIDTH EQU 8 ; 总线宽度实操心得:在创建这两个文件时,最忌讳的就是“想当然”。
NAND_PAGE_CNT_LSH和NAND_PAGE_SIZE_LSH必须严格按照2^N = 实际值来计算。例如,页大小2048字节是2^11,那么LSH就是11。这个值用于XLDR中快速的地址计算(通过移位代替乘除法)。务必对照数据手册反复核对,特别是设备ID和BBI相关参数。
3.2 第二步:修改BSP配置文件以集成新芯片
创建好定义文件后,我们需要告诉BSP编译系统:“现在多了一个芯片选项可供选择”。这需要通过修改几个关键的配置文件来实现。
1. 修改FMD层的头文件包含逻辑找到文件:\WINCE600\PLATFORM\iMX25-3DS-PDK1_x\SRC\COMMON\NANDFMD\nandbsp.h这个文件是FMD驱动选择芯片型号的“总开关”。我们需要在条件编译指令中添加对新芯片头文件的引用。
#ifndef __NANDBSP_H__ #define __NANDBSP_H__ #ifdef BSP_NAND_K9LBG08U0M #include "K9LBG08U0M.h" #elif defined(BSP_NAND_K9LAG08U0M) // 修改为#elif,并添加新条件 #include "K9LAG08U0M.h" #else #error "No NAND flash type defined!" #endif #endif // __NANDBSP_H__2. 修改XLDR的汇编包含逻辑找到文件:\WINCE600\PLATFORM\iMX25-3DS-PDK1_x\src\BOOTLOADER\XLDR\NAND\nandchip.inc这是XLDR选择芯片汇编定义的“总开关”。
; Include definitions for selected NAND flash device IF :DEF: BSP_NAND_K9LBG08U0M INCLUDE K9LBG08U0M.inc ELIF :DEF: BSP_NAND_K9LAG08U0M ; 添加新的判断分支 INCLUDE K9LAG08U0M.inc ; 包含新芯片的定义 ELSE ; 可以保留一个默认选项,或者报错 INCLUDE K9LBG08U0D.inc ; 或者其他默认芯片 ; 或者使用: ERROR “No NAND type defined!” ENDIF3. 修改FMD驱动的Sources文件找到文件:\WINCE600\PLATFORM\iMX25-3DS-PDK1_x\SRC\COMMON\NANDFMD\sources这个文件控制着编译FMD库时的预处理器定义。我们需要添加一个新的条件编译块。
!IF "$(BSP_NAND_K9LAG08U0M)" == "1" CDEFINES=$(CDEFINES) -DBSP_NAND_K9LAG08U0M !ENDIF !IF "$(BSP_NAND_K9LBG08U0M)" == "1" CDEFINES=$(CDEFINES) -DBSP_NAND_K9LBG08U0M !ENDIF4. 修改XLDR的Sources文件找到文件:\WINCE600\PLATFORM\iMX25-3DS-PDK1_x\src\BOOTLOADER\XLDR\NAND\sources这个文件控制着编译XLDR时的汇编器定义。
!IF "$(BSP_NAND_K9LBG08U0M)" == "1" ADEFINES=$(ADEFINES) -pd "BSP_NAND_K9LBG08U0M SETL {TRUE}" !ELIF "$(BSP_NAND_K9LAG08U0M)" == "1" ; 添加新的判断 ADEFINES=$(ADEFINES) -pd "BSP_NAND_K9LAG08U0M SETL {TRUE}" !ELSE ADEFINES=$(ADEFINES) -pd "BSP_NAND_K9LBG08U0D SETL {TRUE}" ; 或其他默认 !ENDIF注意事项:修改这些配置文件时,务必注意语法。
.h和.inc文件是C和汇编语法,而sources文件是Windows CE构建系统(Build System)的语法。!IF、!ELIF、!ENDIF以及环境变量引用$(VAR)的格式必须正确,否则会导致构建失败。建议在修改前备份原文件。
3.3 第三步:在开发环境中配置与构建
代码修改完成后,最后一步是在Platform Builder中配置环境并触发构建。
设置环境变量:
- 在VS2005/Platform Builder中,打开你的OS设计项目。
- 点击菜单
Project->Properties。 - 在属性对话框中,导航到
Configuration Properties->Environment。 - 点击
New...按钮。 - 在
Variable name中输入BSP_NAND_K9LAG08U0M(必须与sources文件中定义的名称完全一致)。 - 在
Variable value中输入1。 - 点击确定保存。这个操作相当于在构建系统中定义了一个宏,当构建FMD和XLDR时,
!IF "$(BSP_NAND_K9LAG08U0M)" == "1"条件成立,从而启用对新芯片的支持。
执行清理与构建:
- 强烈建议先执行清理:在
Build菜单下,选择Clean Solution,然后选择Rebuild Solution。这能确保所有中间文件和旧的目标文件被清除,避免因缓存导致的链接错误。 - 构建整个BSP:光构建NK镜像是不够的,因为XLDR和Eboot也需要重新编译。正确的方法是:点击
Build菜单 ->Advanced Build Commands->Build Current BSP and Subprojects。这个命令会按顺序编译XLDR、Eboot以及内核相关的所有BSP组件,确保一致性。
- 强烈建议先执行清理:在
生成最终镜像:完成BSP构建后,再执行一次
Build Solution来生成最终的NK.bin和Eboot.bin等镜像文件。
4. 深度排查:常见问题与解决实录
即使严格按照步骤操作,在实际移植过程中也难免会遇到各种问题。下面是我总结的几个典型故障场景及其排查思路。
4.1 问题一:系统无法启动,停留在XLDR阶段
这是最令人紧张的情况。可能的原因和排查点如下:
- 症状:串口无输出,或输出乱码后停止。
- 排查思路:
- 检查XLDR汇编文件:首先确认
K9LAG08U0M.inc文件中的参数,特别是NAND_PAGE_CNT_LSH和NAND_PAGE_SIZE_LSH计算是否正确。这两个值错误会导致XLDR计算NAND物理地址时完全错乱,无法读取后续的Eboot代码。 - 检查BBI参数:确认
BBI_MAIN_ADDR计算是否正确。如果地址算错,XLDR在检查坏块时会读到错误的数据,可能误将好块标记为坏块而跳过,导致找不到有效的Eboot镜像。 - 检查芯片ID:确认
NAND_MAKER_CODE和NAND_DEVICE_CODE是否与数据手册完全一致。可以用示波器或逻辑分析仪抓取上电后NAND芯片ID引脚的波形,与驱动发送的0x90命令序列对比,看是否匹配。 - 检查硬件连接:确认新的NAND芯片与i.MX25的硬件连接(如数据线、控制线、上拉电阻)是否与原理图一致,特别是
RB(Ready/Busy)引脚和WP(Write Protect)引脚的电平状态。
- 检查XLDR汇编文件:首先确认
4.2 问题二:Eboot可以启动,但加载NK时失败或系统启动后文件系统错误
- 症状:Eboot能正常从串口输出信息,但在加载NK.bin时卡住,报告读取错误;或者系统能启动但无法访问文件系统。
- 排查思路:
- 对比FMD与XLDR参数:这是最常见的原因。务必确保
K9LAG08U0M.h和K9LAG08U0M.inc中关于容量、页大小、块大小、备用区大小的定义完全一致。一个在C代码中是2048,另一个在汇编中是4096,必然导致灾难。 - 检查FMD驱动中的时序或命令:虽然大部分NAND命令是标准的,但有些芯片可能需要特定的时序延迟或额外的命令序列。查看FMD层驱动代码(如
nandbsp.cpp)中是否有针对特定芯片的初始化、读、写、擦除函数。你可能需要参考原芯片的驱动,为新芯片添加类似的特殊处理。 - 验证ECC配置:i.MX25的NFC硬件支持ECC(纠错码)。不同的页大小和备用区大小可能对应不同的ECC强度(如4位、8位ECC)。检查BSP中关于ECC模式的配置(可能在
nandbsp.cpp或平台配置头文件中),确保其与新NAND芯片的要求匹配。BBI_MAIN_ADDR的计算就与ECC模式强相关(见附录A.2.2)。 - 使用Eboot的格式化命令:如果怀疑是旧的坏块表或分区信息不兼容,可以尝试在Eboot命令行中,使用
f命令对NAND进行低级格式化。警告:此操作会清空所有数据!
- 对比FMD与XLDR参数:这是最常见的原因。务必确保
4.3 问题三:编译过程中出现链接错误或未定义符号
- 症状:构建时在命令行输出中报告“undefined symbol”或“link error”。
- 排查思路:
- 检查环境变量名:确认在Platform Builder中设置的环境变量名,与
sources文件中!IF判断的条件(如BSP_NAND_K9LAG08U0M)严格一致,包括大小写。Windows CE构建系统对此是敏感的。 - 检查文件路径:确认新建的
.h和.inc文件是否放入了正确的INC目录,并且该目录已被包含在sources文件的INCLUDES路径中。 - 执行完全重建:删除整个工程的
obj和cesysgen等输出目录,然后从“Build Current BSP and Subprojects”开始重新构建。这能彻底清除陈旧的中间文件。
- 检查环境变量名:确认在Platform Builder中设置的环境变量名,与
4.4 问题速查表
为了便于快速定位,我将常见问题、可能原因和解决动作整理成下表:
| 问题现象 | 可能原因 | 优先排查点 |
|---|---|---|
| XLDR阶段无输出/死机 | 1. XLDR汇编参数错误 2. 芯片ID不匹配 3. 硬件连接问题 | 1. 核对.inc中LSH值计算2. 核对 .h中制造商/设备ID3. 测量关键引脚电平 |
| Eboot启动失败 | 1. FMD与XLDR参数不一致 2. BBI地址计算错误 3. 坏块标记页定义错误 | 1. 对比.h和.inc所有尺寸参数2. 重新计算 BBI_MAIN_ADDR3. 检查 BBIMarkPage数组 |
| NK加载失败/文件系统错误 | 1. ECC配置不匹配 2. FMD驱动中芯片特定时序问题 3. 旧分区信息干扰 | 1. 检查BSP中ECC设置 2. 对比新旧芯片数据手册时序图 3. 在Eboot中格式化NAND |
| 编译错误(未定义符号) | 1. 环境变量未设置或名称错误 2. 头文件路径未包含 3. 源代码语法错误 | 1. 检查项目属性中的环境变量 2. 检查 sources文件INCLUDES3. 检查新创建的 .h文件语法 |
5. 进阶思考与经验延伸
完成一次基本的驱动移植后,我们还可以从更深的层次去思考和优化,这些经验往往在官方文档中不会提及。
5.1 关于BBI地址计算的再理解
官方文档附录A给出的BBI_MAIN_ADDR计算公式(Page_Size - ((512 + Spare_Per_Sector) * (Sectors_Per_Page - 1)))其核心逻辑是:找到NFC内部最后一个数据扇区所对应的主数据区地址。因为NFC硬件在读写时,是按“扇区(512字节主数据+若干字节备用区)”为单位处理的。坏块标记物理上位于一个页的备用区起始处,但驱动需要通过操作NFC的特定缓冲区来访问它。这个计算就是为了将物理的备用区地址,映射到NFC内部缓冲区的正确偏移。理解这一点,就能举一反三,即使未来遇到页大小、备用区大小不同的芯片,也能自己推导出公式,而不是死记硬背几个魔法数字。
5.2 多芯片支持与运行时检测
我们上述修改是基于编译时通过环境变量选择单一芯片型号。这是一种静态配置方法,简单可靠。但在更复杂的场景下,比如一个硬件平台可能搭载不同供应商的NAND芯片(出于供应链安全考虑),我们就需要实现驱动在运行时自动检测芯片型号。
思路是:在FMD驱动的初始化函数(如FMD_Init)中,发送0x90命令读取芯片的制造商ID和设备ID。然后在一个预定义的芯片参数表中进行查找匹配。这个表可以包含我们为K9LBG08U0M、K9LAG08U0M等芯片定义的所有关键参数(块数、页大小、BBI信息等)。匹配成功后,驱动动态使用对应的参数集。这样,一个BSP镜像就能适配多种NAND Flash,大大提高了硬件的灵活性和软件的可维护性。实现此功能需要对FMD驱动代码有更深入的掌握。
5.3 性能与可靠性调优
驱动移植成功只是第一步,让系统稳定高效运行才是目标。有两个方面值得关注:
时序调整:i.MX25的NFC控制器有多个时序寄存器(如
NFCFG、NFACCCFG1/2/3等),用于配置命令、地址、数据的建立、保持和等待时间。数据手册中给出的典型值可能不是最优的。如果发现读写NAND时偶尔出错,或者速度不理想,可以尝试在驱动中微调这些时序参数,并在不同温度下进行长时间读写压力测试,以找到最稳定可靠的配置。ECC策略:硬件ECC能极大提高数据可靠性。除了确保ECC强度与芯片匹配,还可以在驱动中增加对ECC错误的统计和日志。当某个块的ECC纠错次数超过阈值时,可以主动将其标记为坏块,并将数据迁移到好块中。这种主动坏块管理策略能有效预防数据损毁,对于工业级产品至关重要。
移植i.MX25 WinCE 6.0的NAND Flash驱动,是一项对耐心和细致度要求极高的工作。它不像开发一个应用功能那样立竿见影,但却是整个系统稳定运行的基石。我的体会是,成功的关键在于三点:一是对硬件数据手册的精确解读,二是对BSP驱动架构的清晰理解,三是在修改和调试过程中保持严谨的工程习惯——每次只改一个地方,做好备份,充分测试。当你看到新的NAND芯片被系统正确识别并稳定运行时,那种解决底层技术难题的成就感,是其他工作难以替代的。希望这份详细的指南和其中的经验总结,能帮助你顺利跨过这道坎。