WinCE 6.0 i.MX51平台NAND Flash驱动移植实战指南
1. 项目概述与核心挑战在基于i.MX51这类嵌入式处理器的硬件开发中我们经常会遇到一个非常实际的问题原厂提供的参考设计或开发板EVK上使用的NAND Flash型号与我们在实际产品中选用的型号不一致。飞思卡尔Freescale现NXP的板级支持包BSP通常只预置了少数几款NAND Flash的驱动支持比如文档中提到的K9G8G08U0M、MT29F32G08等。但产品设计时出于成本、供货周期、性能或特定容量的考虑我们往往会选择其他供应商的NAND Flash例如三星的K9LAG08U0M。这就引出了本次分享的核心如何在Windows Embedded CE 6.0以下简称WinCE 6.0的BSP中为i.MX51平台适配一款全新的NAND Flash驱动。这不仅仅是改几个参数那么简单它涉及到对WinCE存储驱动架构的理解、对NAND Flash物理特性的精确把握以及对BSP构建流程的熟悉。整个过程本质上是在飞思卡尔提供的驱动框架内为新的Flash芯片“办理入职手续”让操作系统和BootloaderEboot都能正确识别并可靠地读写它。如果你正在为自定义硬件上的WinCE系统无法从NAND启动或存储不稳定而头疼那么接下来的内容应该能为你提供一条清晰的解决路径。2. WinCE 6.0 NAND驱动架构与FMD层解析在动手修改之前我们必须先搞清楚WinCE 6.0下NAND驱动的“工作链条”。这能让你明白我们修改的每一个文件究竟在哪个环节起作用避免“盲人摸象”。2.1 分层驱动模型WinCE的存储驱动采用分层架构自上而下对硬件进行抽象每一层各司其职。对于NAND Flash其驱动栈通常如下文件系统层File System最上层如FATFS、TFAT等负责管理文件和目录结构。它不关心底层是NAND还是SD卡。闪存分区驱动层Flash Partition Driver管理NAND Flash上的分区例如将一块物理Flash划分为Eboot区、内核NK区、用户文件系统区等。闪存抽象层Flash Abstraction Layer, FAL这是微软提供的一个中间层它定义了一套标准的接口API用于读写、擦除、获取Flash信息等。FAL的作用是隔离上层文件系统/分区驱动与底层具体的Flash硬件差异。闪存媒体驱动层Flash Media Driver, FMD这是最关键的一层也是我们需要修改的核心。FMD是直接与NAND Flash硬件控制器在i.MX51上就是其内部的NAND Flash控制器NFC打交道的驱动程序。它需要实现FAL层定义的所有接口将标准的擦写读命令翻译成具体的寄存器操作和时序。硬件层Flash Hardware即i.MX51的NAND Flash控制器和外部连接的NAND Flash芯片。文档中的图1清晰地揭示了一个关键点WinCE系统NK和BootloaderEboot共享同一个FMD驱动库。这意味着我们只需要修改并编译一次FMD层的代码生成新的驱动库那么无论是编译Eboot还是编译NK系统镜像它们都会自动链接到这个支持了新Flash型号的库。这是一个非常重要的设计它保证了系统启动阶段Eboot加载内核和运行阶段系统访问存储对Flash操作的一致性。2.2 FMD层的核心任务FMD驱动需要完成哪些具体工作呢它绝不仅仅是传递数据。根据我的经验一个完整的FMD驱动必须妥善处理以下核心任务任何一项的疏漏都可能导致系统不稳定甚至无法启动芯片识别Identification通过发送Read ID命令通常是0x90读取Flash芯片的制造商ID和设备ID并与驱动中预定义的型号进行匹配确认当前连接的Flash是否被支持。基本操作Basic Operations实现页编程Page Program对应CMD_WRITE和CMD_WRITE2、页读取Page Read对应CMD_READ和CMD_READ2、块擦除Block Erase对应CMD_ERASE和CMD_ERASE2等底层函数。这些函数需要配置好NFC的寄存器满足芯片数据手册上规定的时序要求。坏块管理Bad Block Management, BBM这是NAND Flash驱动中最复杂、也最容易出问题的一环。NAND Flash允许出厂时存在坏块并且在生命周期中也可能产生新的坏块。FMD驱动必须能识别坏块通过读取每个块Spare Area中的坏块标记信息即BBI并在上层进行读写操作时主动跳过这些坏块。飞思卡尔的BSP通常已经实现了一套坏块管理策略我们需要做的是正确配置坏块信息的位置。纠错码Error Correction Code, ECCNAND Flash存在位翻转Bit Flip的可能性。i.MX51的NFC硬件集成了ECC计算单元支持4位或8位BCH编码。FMD驱动需要在写操作时计算并写入ECC数据到Spare Area在读操作时进行校验和纠错。ECC的强度4-bit/8-bit需要与Flash芯片的要求及Spare Area的大小相匹配。电源管理与状态查询实现复位CMD_RESET和状态查询CMD_STATUS等功能。我们本次的移植工作大部分内容并不需要从头实现这些复杂的函数。飞思卡尔的BSP已经提供了一个相对完善的FMD驱动框架nandbsp.cpp等文件。我们的核心任务是为这个框架“注入”新Flash芯片的“身份证”和“规格说明书”即所有关键的物理参数和操作指令让框架里的代码能正确地驱动这块新芯片。3. 关键参数详解从数据手册到驱动定义这是整个移植过程中最需要耐心和细心的部分。参数定义错误轻则导致性能下降、寿命缩短重则直接无法识别芯片或损坏数据。我们必须像查字典一样仔细翻阅新NAND Flash芯片的数据手册Datasheet。3.1 必须提取的十大参数文档1.1节列出了需要配置的所有参数。我们来逐一拆解它们的含义和查找方法NAND MARKER (制造商ID)一个字节的十六进制数代表芯片制造商。例如三星Samsung通常是0xEC海力士Hynix是0xAD美光Micron是0x2C。在数据手册的“Device ID”或“Read ID”章节可以找到。NAND DEVICE ID (设备ID)一个字节的十六进制数与制造商ID组合唯一标识一款具体的芯片型号。它定义了芯片的容量、内部架构、电压等关键信息。同样在“Device ID”章节查找。NAND BLOCK COUNT (总块数)整块NAND Flash包含的物理块Block总数。这个值决定了Flash的总容量。计算公式通常是总容量 (Bytes) / (页大小 * 每块页数)。但最可靠的方法是直接看数据手册的“Memory Organization”表格。NAND PAGE COUNT (每块页数)一个物理块Block内包含的页Page数量。常见的有64、128、256等。这也是芯片的固定物理属性。NAND PAGE SIZE (页大小)一个页Page的主数据区大小单位是字节。注意这不包括Spare Area。典型值有20482KB、40964KB、81928KB等。随着技术发展页越来越大。NAND SPARE SIZE (备用区大小)每个页Page附带的备用区Spare Area/OOB Area大小单位是字节。这个区域用于存放ECC校验码、坏块标记、文件系统元数据等。典型值有64、128、218等。这个参数至关重要它必须足够容纳ECC数据和坏块信息。NAND BUS WIDTH (总线宽度)数据总线位宽通常是8位或16位。i.MX51的NFC支持这两种模式需要根据硬件原理图的实际连接来设定。BBI MAIN ADDR (坏块信息主地址)这是一个计算值而非直接从手册获取。它表示在NFC的内部数据缓冲区视角下坏块标记字节所在的偏移地址。文档附录A.2给出了详细的计算方法我们后面会重点讨论。BBI NUM (坏块信息页数)在一个块Block中有多少个页Page包含坏块标记信息。绝大多数芯片只在每个块的第一页或最后一页的Spare Area中存放坏块标记因此此值通常为1。少数芯片可能有两处标记需要查阅手册确认。BBIMarkPage (坏块标记页数组)一个数组指明在一个块内具体是哪一几个页的Spare Area存放了坏块标记。例如对于128页/块的芯片如果标记在第一页数组就是{0}如果在最后一页数组就是{127}。实操心得参数提取核对表建议你创建一个表格来整理这些参数并与数据手册反复核对。特别是BLOCK COUNT、PAGE SIZE、SPARE SIZE这三个直接关系到总容量和ECC布局一旦填错后续所有地址计算都会出错。我习惯在提取后用计算器验证一下总容量 BLOCK COUNT * PAGE COUNT * PAGE SIZE看结果是否与芯片标称容量一致注意1KB1024B厂商常用1000进制会有细微差别以手册为准。3.2 深度解析BBI_MAIN_ADDR的计算逻辑这是最容易出错的地方。为什么需要这个计算因为i.MX51的NAND Flash控制器NFC内部有固定的数据缓冲区结构而不同页大小的NAND Flash其Spare Area的起始物理地址不同。NFC需要知道从它内部缓冲区的哪个位置去读写坏块标记。文档给出了两个计算例子我们以更常见的2KB页64字节Spare Area为例再详细解释一遍已知条件PAGE_SIZE 2048,SPARE_SIZE 64。物理地址对于这颗Flash一个页的主数据区是0-2047字节紧接着的Spare Area是2048-2111字节。所以坏块标记假设在Spare Area第一个字节的物理地址是2048。NFC内部视角i.MX51的NFC以这个BSP版本为例内部将大页拆分为多个512字节的“子页”来处理。对于2KB页它拆成4个“子页”每个“子页”有512字节主数据 16字节Spare因为总Spare 64字节 / 4 16字节。计算目标我们需要找到从NFC内部最后一个“子页”的Spare Area开始向前回溯多少个字节才能对应到物理地址2048那个位置。计算过程NFC内部最后一个“子页”的Spare Area起始地址可以看作是“子页”的末尾。我们需要计算到这个“子页”开头即主数据开始的偏移。每个“子页”大小是512 16 528字节。从物理地址2048回溯到最后一个“子页”的开头2048 - (528 * 3) 2048 - 1584 464。这里的3是因为有4个子页索引0,1,2,3最后一个子页的索引是3我们要回溯到它的开头就需要减去前面3个完整子页的大小528*3。所以BBI_MAIN_ADDR被定义为464。这个值告诉NFC驱动“当你处理完主数据准备访问Spare Area时请从你内部缓冲区的第464字节处开始寻找坏块标记”。对于4KB页4096字节的Flash计算原理相同只是拆分的子页数更多8个。文档给出的经验值464(2KB页)、400(4KB页/128字节Spare)、330(4KB页/218字节Spare)在大多数对应规格的芯片上是通用的但最严谨的做法还是根据你的PAGE_SIZE和SPARE_SIZE手动验算一遍。4. 移植实操一步步集成新Flash型号理论清晰后我们进入实战环节。假设我们要为一块采用“K9LAG08U0M”芯片的板卡移植驱动。请确保你已安装好Windows Embedded CE 6.0的Platform Builder和对应的i.MX51 BSP例如PDK1.6。4.1 第一步创建芯片专属头文件这是为新芯片建立“身份证”档案。根据文档指引我们需要在以下路径创建新头文件\WINCE600\PLATFORM\COMMON\SRC\SOC\COMMON_FSL_V2_PDK1_6\NAND\INC\文件命名为K9LAG08U0M.h。内容模板文档已经给出我们需要用从数据手册中提取的真实参数替换掉所有的XXXX和XXX。假设我们从K9LAG08U0M的数据手册中查到如下信息此处为示例请务必以实际手册为准总容量1Gb (128MB) 块数1024 每块页数64 页大小2048字节 Spare区64字节 总线宽度8位。制造商ID0xEC(三星) 设备ID0xD5。BBI信息标记在每个块的第一页Page 0的Spare Area第一个字节。BBI_NUM 1。那么我们填充后的头文件关键部分如下#ifndef __K9LAG08U0M_H__ #define __K9LAG08U0M_H__ // NAND Flash Chip CMD #define CMD_READID (0x90) #define CMD_READ (0x00) #define CMD_READ2 (0x30) #define CMD_RESET (0xFF) #define CMD_ERASE (0x60) #define CMD_ERASE2 (0xD0) #define CMD_WRITE (0x80) #define CMD_WRITE2 (0x10) #define CMD_STATUS (0x70) // NAND Flash Chip Size #define NAND_BLOCK_CNT (1024) // 总块数 #define NAND_PAGE_CNT (64) // 每块页数 #define NAND_PAGE_SIZE (2048) // 页大小 (字节) #define NAND_SPARE_SIZE (64) // 备用区大小 (字节) #define NAND_BUS_WIDTH (8) // 总线宽度 (位) // NAND Flash Chip #define NAND_NUM_OF_CS (1) // 片选数量通常为1 // NAND Flash Chip ID #define NAND_MAKER_CODE (0xEC) // 制造商ID: 三星 #define NAND_DEVICE_CODE (0xD5) // 设备ID #define NAND_ID_CODE ((NAND_DEVICE_CODE 8) | NAND_MAKER_CODE) // 组合ID // NAND Flash Chip Operation Status #define NAND_STATUS_ERROR_BIT (0) // 状态寄存器错误位 #define NAND_STATUS_BUSY_BIT (6) // 状态寄存器忙位 // SWAP BBI (坏块信息) #define BBI_MAIN_ADDR (464) // 对于2KB页64字节Spare此为典型值 #define BBI_NUM (1) // 每块只有一个页含BBI BYTE BBIMarkPage[1] {0}; // BBI位于每个块的第0页 #endif // __K9LAG08U0M_H__注意事项命令集CMD的陷阱文档中给出的命令集0x90,0x00/0x30等是NAND Flash的标准命令对于大多数SLC/MLC芯片是通用的。但是对于一些较新的芯片尤其是TLC或3D NAND或者使用ONFI、Toggle模式高速接口的芯片命令集可能有细微差别。例如多平面操作Multi-plane的命令、读缓存命令、或状态查询命令的格式可能不同。务必在数据手册的“Command Set”章节进行核对。如果命令不对驱动可能无法初始化或进行读写操作。4.2 第二步修改平台配置文件nandbsp.h这个文件位于你的具体平台目录下例如\WINCE600\PLATFORM\iMX51-EVK-PDK1_6\SRC\COMMON\NANDFMD\nandbsp.h它的作用是根据编译时定义的环境变量决定包含哪个芯片的头文件。我们需要在原有的条件编译链中加入对新芯片的支持。找到如下代码段#ifdef BSP_NAND_MT29F32G08 #include MT29F32G08.h #elif BSP_NAND_K9MCG08U5M #include K9MCG08U5M.h #elif BSP_NAND_K9LBG08U0M #include K9LBG08U0M.h #elif BSP_NAND_K9LAG08U0M // 新增条件判断 #include K9LAG08U0M.h // 包含我们刚创建的头文件 #elif BSP_NAND_K9G8G08U0M #include K9G8G08U0M.h #elif BSP_SI_VER_TO2 #include MT29F32G08.h #else #include K9G8G08U0M.h // 默认头文件 #endif我们在BSP_NAND_K9LBG08U0M和BSP_NAND_K9G8G08U0M之间插入我们新芯片的判断和包含语句。注意条件编译的宏BSP_NAND_K9LAG08U0M必须与下一步在sources文件中定义的变量名完全一致。4.3 第三步修改编译配置文件sources这是告诉构建系统“当某个环境变量被设置时请为编译过程定义对应的宏”。需要修改两个地方的sources文件驱动库的sources文件\WINCE600\PLATFORM\iMX51-EVK-PDK1_6\SRC\COMMON\NANDFMD\DRIVER\sourcesBootloader库的sources文件\WINCE600\PLATFORM\iMX51-EVK-PDK1_6\SRC\COMMON\NANDFMD\BOOT\sources两个文件的内容结构几乎一样。我们需要在已有的环境变量判断块中添加对新芯片的支持。找到类似下面这段代码!IF $(BSP_NAND_K9G8G08U0M) 1 CDEFINES$(CDEFINES) -DBSP_NAND_K9G8G08U0M !ENDIF !IF $(BSP_NAND_K9LBG08U0M) 1 CDEFINES$(CDEFINES) -DBSP_NAND_K9LBG08U0M !ENDIF在这段代码之后添加我们新芯片的配置!IF $(BSP_NAND_K9LAG08U0M) 1 CDEFINES$(CDEFINES) -DBSP_NAND_K9LAG08U0M !ENDIF关键点!IF后面的环境变量名BSP_NAND_K9LAG08U0M必须与我们在nandbsp.h中新增的条件编译宏BSP_NAND_K9LAG08U0M以及下一步在Platform Builder中设置的环境变量名完全一致。这个字符串就是连接三者的“钥匙”。4.4 第四步在Platform Builder中配置环境变量现在我们需要在具体的OS设计项目OS Design中告诉Platform Builder“这次编译请使用我们新添加的K9LAG08U0M驱动”。打开你的OS Design工程。点击菜单栏的Build-Properties。在弹出的属性页中左侧选择Configuration Properties-Environment。点击New按钮。在Variable name输入框中填入BSP_NAND_K9LAG08U0M。在Variable value输入框中填入1。点击确定保存。这个操作相当于在本次构建的全局环境中设置了一个开关变量。当构建系统处理到之前修改的sources文件时!IF $(BSP_NAND_K9LAG08U0M) 1这个条件就会成立从而将-DBSP_NAND_K9LAG08U0M这个宏定义添加到编译参数中。进而在编译nandbsp.cpp等源文件时因为定义了BSP_NAND_K9LAG08U0M宏预处理器就会根据nandbsp.h中的条件编译指令包含我们创建的K9LAG08U0M.h头文件。4.5 第五步编译与构建环境变量设置好后不能直接进行普通的“Build”或“Sysgen”。为了确保修改的FMD驱动被正确编译并链接到Eboot和NK中需要执行一个特殊的构建命令在Platform Builder中点击菜单栏的Build-Advanced Build Commands。选择Build Current BSP and Subprojects。这个命令会强制重新编译当前平台BSP下的所有子项目其中就包括我们修改过的NANDFMD目录下的驱动库。它会分别生成用于Eboot的nandfmd_boot.lib和用于NK的nandfmd_driver.lib。之后再构建Eboot和NK系统镜像时就会链接到这些新的、支持了新Flash型号的库文件。5. 调试、验证与常见问题排查代码修改和编译只是第一步真正的考验在于将新编译的镜像烧录到硬件上运行。这个过程大概率不会一帆风顺。5.1 验证步骤与预期现象编译通过性验证首先确保修改后Build Current BSP and Subprojects能无错误通过。这是基础。Eboot启动与Flash识别将新编译的Eboot通常是eboot.nb0通过SD卡或USB OTG等方式下载到板卡的RAM中运行具体方法参考i.MX51 EVK用户指南。在Eboot的启动串口日志中你应该能看到关键的几行信息NAND ID: Manufacturer Code: 0xEC, Device Code: 0xD5—— 这证明驱动成功读取了Flash的ID并且与头文件中定义的NAND_ID_CODE匹配。NAND Geometry: Block Count: 1024, Page per Block: 64, Page Size: 2048, Spare Size: 64—— 这证明驱动正确获取了Flash的几何参数。如果能看到这些信息并且Eboot能正常进入菜单说明底层FMD驱动初始化基本成功。格式化与烧录测试在Eboot菜单中尝试对NAND Flash进行格式化Format操作。然后通过Eboot的USB或以太网下载功能将新的NK镜像nk.bin烧录到NAND中。如果烧录过程顺利没有报告坏块错误或写失败则说明写操作正常。系统启动与文件读写配置Eboot从NAND启动重启板卡。如果NK镜像能正常加载并启动WinCE桌面则说明读操作正常。进入系统后可以尝试创建文件、复制文件进行简单的读写压力测试。5.2 常见问题与排查技巧实录以下是我在多次移植中踩过的坑和总结的排查思路问题现象可能原因排查思路与解决方案Eboot启动时卡在“NAND Init”或直接复位1.命令集错误头文件中的基本命令如CMD_READID与芯片不符。2.时序或初始化序列不匹配部分Flash需要特定的上电初始化序列或配置周期tADL, tWHR等。3.硬件连接问题数据线、命令锁存使能CLE、地址锁存使能ALE等信号连接错误或上拉电阻问题。1.核对命令集用示波器或逻辑分析仪抓取NAND总线看Eboot发出的第一个命令应是0xFF复位或0x90读ID是否与芯片手册一致。重点检查sources文件中是否正确定义了芯片宏导致包含错误的头文件。2.检查BSP底层配置查看nandbsp.cpp中FMD_Init函数看是否有针对特定芯片的初始化和延时配置。有时需要根据芯片手册调整NFC控制器的时序参数寄存器如NFC_TIMING_CFG。3.硬件复查核对原理图确保NAND Flash的引脚尤其是CLE,ALE,WE,RE,WP与i.MX51的对应引脚正确连接且上拉电阻值合适。能识别ID但报告错误的Geometry块数、页大小1.头文件参数填写错误NAND_BLOCK_CNT,NAND_PAGE_SIZE等值填错。2.ID解析逻辑不匹配有些Flash的Device ID字节需要复杂解析才能得到几何参数而BSP中的解析函数可能只支持特定型号。1.双重检查头文件逐项核对头文件中的每一个#define值确保与数据手册“Memory Organization”章节完全一致。计算总容量进行反推验证。2.深入代码查看nandbsp.cpp中读取ID后解析Geometry的函数可能是GetNandGeometry。对比新老芯片的ID看解析逻辑是否需要扩展。有时需要根据Device ID的不同位手动计算并返回几何参数。格式化或烧录时提示大量坏块或写操作失败1.BBI参数错误BBI_MAIN_ADDR,BBIMarkPage设置错误导致驱动在错误的位置读取坏块标记把好块误判为坏块。2.ECC配置不匹配Flash要求的ECC强度如8-bit与BSP中配置的如4-bit不符导致ECC校验失败操作被中止。3.真正的坏块过多芯片质量或焊接问题。1.复查BBI计算这是高发区。再次确认PAGE_SIZE和SPARE_SIZE并严格按照附录A.2的方法计算BBI_MAIN_ADDR。确认BBIMarkPage数组指向的页号是否正确第一页是0还是1最后一页是NAND_PAGE_CNT-1。2.检查ECC设置在nandbsp.h或平台配置头文件中查找NAND_ECC_MODE之类的定义。确保其与Flash芯片要求的ECC模式及Spare Area大小匹配。例如218字节的Spare通常是为8-bit ECC准备的。3.硬件诊断尝试用原厂支持的另一款Flash芯片测试排除硬件问题。用编程器读取Flash原始内容查看Spare Area的坏块标记位置通常是第0页Spare Area的第0或第1个字节非0xFF表示坏块以验证驱动判断逻辑。系统启动后文件系统不稳定随机读写错误1.驱动线程安全问题FMD函数可能被多线程调用如果函数内部没有做好临界区保护会导致随机错误。2.电源或信号完整性NAND Flash在工作时电流波动较大电源纹波过大可能导致操作失败。3.驱动中的细微时序问题1.代码审查检查nandbsp.cpp中的关键函数如FMD_WriteSector,FMD_ReadSector看是否使用了EnterCriticalSection/LeaveCriticalSection或互斥量进行保护。飞思卡尔的参考驱动通常已考虑但自定义修改时可能引入问题。2.硬件测量用示波器测量NAND Flash的VCC电源引脚在读写操作瞬间是否有大幅跌落。检查信号线是否有过冲、振铃现象必要时调整串联电阻或端接。3.增加调试信息在驱动的读写函数中增加详细的日志输出记录操作地址、状态寄存器返回值定位出错的具体操作。踩坑心得善用“原始读/写”功能调试在Eboot中通常会有一些底层调试命令比如直接读取NAND物理地址、直接写入测试数据等。当驱动行为异常时不要只依赖高级的格式化/烧录功能。使用这些底层命令可以帮你隔离问题是命令发错了是地址算错了还是数据读回来就不对这能极大缩小排查范围。另外准备好一个NAND Flash编程器如RT809H作为“终极裁判”也非常有用它可以独立于你的驱动验证Flash芯片本身的好坏以及原始数据的正确性。移植完成后如果一切稳定建议对Flash进行一轮完整的坏块扫描和读写压力测试确保在产品的整个生命周期内存储系统都能可靠工作。这个过程虽然繁琐但却是嵌入式硬件定制化道路上必须掌握的扎实技能。每一次成功的移植都是对硬件特性和系统底层理解的一次深化。