i.MX35平台WinCE 6.0 NAND Flash驱动移植实战指南

📅 2026/6/21 19:27:22
i.MX35平台WinCE 6.0 NAND Flash驱动移植实战指南
1. 项目概述与核心挑战在基于i.MX35这类嵌入式处理器的定制硬件开发中我们经常会遇到一个非常实际的问题原厂提供的BSP板级支持包里其NAND Flash驱动只支持有限的几种型号。然而出于成本、供货周期或者性能优化的考虑硬件工程师在设计板卡时往往会选用与原厂开发板不同的NAND Flash芯片。这就导致了一个尴尬的局面——你精心设计的硬件板子却无法被Windows Embedded CE 6.0这个嵌入式操作系统识别和启动。我经历过不止一次在项目初期硬件板卡焊接回来兴冲冲地准备烧录系统时却发现Eboot引导程序连最基本的Flash ID都读不出来系统镜像更是无从谈起。这时候驱动移植和BSP定制就成了打通硬件与软件“任督二脉”的关键一步。这个过程的核心就是围绕Flash Media Driver展开的。FMD是WinCE中一个抽象层它向上为文件系统和分区驱动提供统一的Flash操作接口向下则直接与具体的Flash硬件控制器和芯片打交道。i.MX35 BSP的巧妙之处在于Eboot和最终的NK内核系统镜像共享同一套底层的FMD驱动。这意味着我们只需要在FMD这一层完成对新Flash芯片的支持就能同时让引导程序和操作系统都认识这块“新硬盘”。听起来很美好但实际操作中你需要像一名侦探一样从Flash芯片的数据手册里提取关键“身份信息”并准确地“教”给BSP。这不仅仅是填几个参数那么简单它涉及到对BSP编译框架、驱动架构的深入理解任何一个参数的误填或步骤的遗漏都可能导致系统无法启动甚至损坏Flash。接下来我将结合飞思卡尔官方应用笔记AN3976的指引以及我多次在i.MX35平台上实战的经验为你拆解从零开始移植一个新NAND Flash驱动的完整流程和避坑要点。2. 驱动架构解析与准备工作2.1 WinCE 6.0 NAND驱动架构深度剖析要成功移植驱动首先必须吃透其架构。图1所示的驱动架构图是理解整个工作的基石。我们可以把它分为两个相对独立但又共享基础的部分Eboot引导程序侧和WinCE操作系统侧。在WinCE 6.0侧自上而下是文件系统如FATFS、TFAT -Flash分区驱动负责管理分区表 -Flash抽象层FAL提供坏块管理、ECC校验等高级功能 -Flash媒体驱动FMD最底层的硬件操作接口。而在Eboot侧为了精简它通常只包含Eboot抽象层和底层的Flash媒体驱动。关键在于Eboot和WinCE NK镜像所使用的FMD驱动是同一套代码、同一个编译生成的库文件。这个设计极大地简化了我们的工作我们不需要分别修改两套驱动只需要搞定FMD这一层就能一劳永逸。FMD层具体做什么呢它封装了所有对NAND Flash硬件进行原始操作的函数比如FMD_ReadSector/FMD_WriteSector: 按扇区通常对应Flash的页读写数据。FMD_EraseBlock: 擦除整个块NAND Flash写入前必须先擦除。FMD_GetInfo: 获取Flash的几何参数页大小、块数量等。FMD_Init: 初始化Flash控制器和识别芯片。我们的移植工作90%都集中在让FMD_Init和FMD_GetInfo等函数能正确识别和配置新的Flash芯片。2.2 环境与工具准备清单在动手修改代码之前一个稳定、正确配置的开发环境是成功的一半。以下是基于AN3976和实际项目经验整理的必备清单软件开发环境:Platform Builder for CE 6.0 R3: 这是定制和编译WinCE系统的核心IDE必须安装。通常它集成在Visual Studio 2005中。i.MX35 BSP 1.5: 确保你拥有飞思卡尔官方发布的针对i.MX35 PDK 1.5的BSP包。这是所有工作的基础。你需要将其正确安装到%_WINCEROOT%\PLATFORM目录下。硬件与文档:目标硬件板卡: 确保你的定制板卡上i.MX35与NAND Flash的硬件连接如数据总线、控制引脚是正确的。驱动软件无法修复硬件设计错误。新NAND Flash芯片的数据手册: 这是你的“圣经”。必须找到完整、准确的英文版数据手册。我们所需的所有关键参数都来源于此。关键参数提取表: 在阅读数据手册时你需要像填写调查表一样提取出以下信息。我强烈建议你制作一个表格避免混淆参数宏定义对应数据手册中的描述示例值 (以K9LAG08U0M为例)提取要点与常见坑点NAND_MAKER_CODEManufacturer ID (Read ID 1st Byte)0xEC (三星)通过0x90读ID命令获得的第一字节。NAND_DEVICE_CODEDevice ID (Read ID 2nd Byte)0xD5通过0x90读ID命令获得的第二字节。注意有些芯片第3、4字节也包含重要信息但BSP通常只使用前两字节做匹配。NAND_BLOCK_CNTTotal number of blocks4096计算方式总容量 / (块大小)。块大小 页大小 * 每块页数。务必确认单位。NAND_PAGE_CNTNumber of pages per block128直接查找“Pages per Block”参数。NAND_PAGE_SIZEMain area size per page (Bytes)4096 (4KB)这是主数据区大小不包括备用区(Spare Area)。NAND_SPARE_SIZESpare area size per page (Bytes)128也叫OOB(Out-Of-Band)区用于存放ECC校验码、坏块标记等。NAND_BUS_WIDTHData I/O bus width8通常是8位也有16位的NAND。必须与硬件设计匹配。CMD_READIDRead ID Command Code0x90命令码一般是固定的但最好核对。BBI_MAIN_ADDRBad Block Marker offset in Spare464这是最容易出错的地方坏块标记在备用区中的字节偏移。不同厂家、不同容量的芯片位置可能不同需仔细查阅数据手册的“Bad Block Marker”章节。注意NAND_BLOCK_CNT和NAND_PAGE_CNT的命名在代码中可能容易引起误解。实际上NAND_PAGE_CNT指的是每个块有多少页而不是Flash的总页数。总页数 NAND_BLOCK_CNT*NAND_PAGE_CNT。在理解代码逻辑时务必清晰。3. 核心移植步骤详解3.1 创建新Flash型号的头文件这是移植工作的第一步也是将硬件参数“翻译”成软件定义的关键。根据AN3976的指引我们需要在\WINCE600\PLATFORM\COMMON\SRC\SOC\COMMON_FSL_V2_PDK1_5\NAND\INC\目录下创建一个新的头文件。我强烈建议以芯片的完整型号命名例如K9LAG08U0M.h。头文件的内容是一个标准的C语言宏定义集合其结构是模板化的。下面我以一个虚构的“MT29F4G08ABA”芯片为例展示一个填充完整并带有详细注释的示例这比官方文档的空模板要实用得多#ifndef __MT29F4G08ABA_H__ // 防止头文件重复包含宏名称必须与文件名对应 #define __MT29F4G08ABA_H__ // // NAND Flash 命令码定义 (通常JEDEC标准但需确认) // #define CMD_READID (0x90) // 读ID命令发送后从数据端口连续读字节 #define CMD_READ (0x00) // 读数据周期1用于发送列地址 #define CMD_READ2 (0x30) // 读数据周期2触发内部数据传输到缓存 #define CMD_RESET (0xFF) // 复位芯片用于从错误状态恢复 #define CMD_ERASE (0x60) // 块擦除设置命令 #define CMD_ERASE2 (0xD0) // 块擦除确认命令 #define CMD_WRITE (0x80) // 写数据编程周期1 #define CMD_WRITE2 (0x10) // 写数据编程周期2触发内部编程操作 #define CMD_STATUS (0x70) // 读状态寄存器命令 // // NAND Flash 物理几何参数 (核心必须从数据手册精确获取) // // 总块数4Gb 512MB, 块大小128页*4KB512KB, 总块数512MB/512KB1024 #define NAND_BLOCK_CNT (1024) // Total number of blocks // 每块页数根据手册该芯片为128 pages per block #define NAND_PAGE_CNT (128) // Number of pages per block // 页大小主数据区4KB #define NAND_PAGE_SIZE (4096) // Main data area size per page (Bytes) // 备用区大小每页128字节用于存放ECC、坏块标记等 #define NAND_SPARE_SIZE (128) // Spare area size per page (Bytes) // 总线宽度8位 #define NAND_BUS_WIDTH (8) // Data bus width (bits) // // 芯片选择与ID // #define NAND_NUM_OF_CS (1) // 通常板子上只有一片NAND片选为1 // 制造商ID美光(Micron)通常为0x2C #define NAND_MAKER_CODE (0x2C) // Manufacturer ID (1st byte of READID) // 设备ID需查手册假设为0xDC #define NAND_DEVICE_CODE (0xDC) // Device ID (2nd byte of READID) // 组合ID用于驱动中快速比对 (设备ID左移8位 | 制造商ID) #define NAND_ID_CODE ((NAND_DEVICE_CODE 8) | NAND_MAKER_CODE) // // 状态寄存器位定义 (通常为标准定义) // #define NAND_STATUS_ERROR_BIT (0) // 状态寄存器Bit0: 1表示操作出错 #define NAND_STATUS_BUSY_BIT (6) // 状态寄存器Bit6: 1表示芯片忙 // // 坏块信息(BBI)配置 (极易出错) // // 坏块标记在主数据区内的地址偏移针对某些文件系统如BinFS。 // 通常不使用设为0。FAL层通常管理备用区的坏块标记。 #define BBI_MAIN_ADDR (0) // Bad block info address offset in main area // 坏块标记在备用区中的位置。这是关键 // 对于此型号芯片手册指出坏块标记位于备用区的第0页的**第0字节**也可能是第1字节。 // 但BSP驱动中这个偏移量是相对于整个“页数据备用区”的缓冲区来计算的。 // 计算方式页大小 备用区中的偏移。 // 假设标记在备用区第0字节偏移 4096 0 4096 // 假设标记在备用区第1字节常见偏移 4096 1 4097 // **必须根据实际手册和FAL层预期来设置** 这里假设为第1字节。 #define BBI_SPARE_ADDR (4097) // 实际计算出的偏移供FAL使用此宏名可能需查看BSP具体实现 // BBI数量通常为1表示一个标记字节 #define BBI_NUM (1) // 坏块标记的值通常非0xFF即表示坏块如0x00 BYTE BBMarkPage[1] {0x00}; // 坏块标记的数值 #endif // __MT29F4G08ABA_H__实操心得备份原文件在修改任何BSP文件前先备份整个SRC目录或使用版本控制工具如SVN, Git。这是血的教训。精确计算偏移BBI_MAIN_ADDR和BBI_SPARE_ADDR的理解是难点。在i.MX35 BSP的FAL实现中它期望的地址是从页数据开始算起的绝对偏移。例如页数据是4096字节备用区从4096开始如果坏块标记在备用区的第1个字节那么偏移就是4096。但有些文档或代码注释可能表述不清。最可靠的方法是参考BSP中已有型号的头文件如K9LBG08U0M.h看它是如何计算的并对照其数据手册验证。验证命令集虽然NAND命令大多标准化但有些芯片可能会有细微差别如某些镁光芯片的读ID命令可能是0xEC。务必在数据手册的“Command Set”章节确认。3.2 修改BSP配置文件以包含新头文件创建好头文件后我们需要告诉BSP编译系统“嘿现在有这么一个新芯片的选项了”。这需要修改两个文件。第一步修改nandbsp.h这个文件位于你的具体平台目录下例如\WINCE600\PLATFORM\iMX35-3DS-PDK1_5\SRC\COMMON\NANDFMD\nandbsp.h。它的作用是根据不同的环境变量包含不同的芯片定义头文件。你需要添加针对新芯片的#ifdef条件编译指令。找到类似#ifdef BSP_NAND_K9LBG08U0M的地方在其后添加你的芯片定义#ifndef __NANDBSP_H__ #define __NANDBSP_H__ #ifdef BSP_NAND_K9LBG08U0M #include K9LBG08U0M.h #elif defined(BSP_NAND_MT29F4G08ABA) // 添加你的新芯片条件分支 #include MT29F4G08ABA.h #else #error NAND Flash model not selected or defined in build environment! #endif #endif // __NANDBSP_H__第二步修改sources文件这个文件位于同一目录下...\NANDFMD\sources它控制着这个驱动库的编译选项。我们需要在这里定义环境变量与编译宏的映射关系。找到定义CDEFINES的部分仿照现有格式添加你的芯片!IF $(BSP_NAND_K9LBG08U0M) 1 CDEFINES$(CDEFINES) -DBSP_NAND_K9LBG08U0M !ENDIF !IF $(BSP_NAND_MT29F4G08ABA) 1 # 添加你的新芯片判断 CDEFINES$(CDEFINES) -DBSP_NAND_MT29F4G08ABA # 定义对应的编译宏 !ENDIF注意事项严格匹配sources文件中的环境变量名BSP_NAND_MT29F4G08ABA必须与nandbsp.h中#ifdef使用的宏名完全一致包括大小写。语法检查sources文件对空格和换行敏感。确保!IF、!ENDIF语句成对出现并且等号两边没有多余空格“$(VAR)” “1”。3.3 配置Platform Builder编译环境代码修改完成后我们需要在Visual Studio 2005的Platform Builder中激活对新芯片的支持。这一步的本质是设置一个环境变量让编译系统知道该编译哪个芯片的配置。在Platform Builder中打开你的OSDesign项目。点击菜单Build-Properties。在属性页中导航到Configuration Properties-Environment。点击New…按钮。在弹出的对话框中Variable name: 输入你在sources文件中定义的环境变量名例如BSP_NAND_MT29F4G08ABA。Variable value: 设置为1。点击确定保存。重要提示这里添加的是项目级环境变量。你也可以在平台的platform.bib或platform.reg中通过SET指令设置但在PB属性中设置最为直观和常用。3.4 编译与验证环境变量设置好后就可以进行编译了。但这里有一个关键步骤官方文档可能没有强调不要直接点击“Build and Sysgen”来编译整个系统。首先单独编译BSP点击菜单Build-Advanced Build Commands-Build Current BSP and Subprojects。这个命令只会编译平台相关的代码包括我们刚修改的NAND FMD驱动速度很快。如果编译报错可以快速定位到是我们修改的文件出了问题比如头文件语法错误、sources文件格式错误等。检查编译输出在Output窗口中观察编译过程。确保在编译nandfmd_lib这个库时没有错误和警告。你可以搜索“-DBSP_NAND_MT29F4G08ABA”这个宏定义确认它已被传递给编译器。生成完整系统镜像在BSP编译无误后再执行Build and Sysgen或“Sysgen”来生成包含新驱动的完整Eboot和NK镜像。验证驱动是否被包含一个简单的验证方法是查看生成的Eboot.bin和NK.bin的大小。如果新的Flash芯片参数尤其是块大小、页大小与旧的不同且FAL/BINFS分区配置也随之正确调整了那么最终镜像的大小可能会发生变化。更彻底的方法是在FMD_Init函数中添加调试打印信息DEBUGMSG在Eboot启动时通过串口输出确认它读取到的ID码与你设置的NAND_ID_CODE一致。4. 高级调试与疑难排查即使严格按照步骤操作第一次成功启动的概率也可能只有一半。以下是我在多个项目中总结的常见问题与排查技巧。4.1 常见编译与链接问题问题编译BSP时提示“无法打开包含文件‘xxx.h’”排查检查你创建的新头文件路径是否正确是否确实放在了\COMMON\SRC\SOC\...\NAND\INC\目录下。检查nandbsp.h中#include的文件名拼写是否正确大小写是否匹配Windows文件系统不区分但编译器可能区分。问题链接错误提示某些FMD函数重复定义或找不到排查这通常是因为sources文件中的条件编译逻辑有误导致同一个芯片的宏被定义了多次或者没有芯片被定义触发了#error。仔细检查!IF和!ENDIF的配对以及环境变量名是否拼写正确。确保在Platform Builder的环境变量设置中只激活了一个NAND芯片的变量设为1其他类似的变量应删除或设为0。4.2 系统启动失败问题这是最棘手的情况Eboot可能卡住或者无法加载NK镜像。问题Eboot启动后串口无任何输出或很快停止输出排查硬件连接首先用万用表或示波器检查NAND Flash的电源、复位信号是否正常。检查数据线、命令线、地址线ALE/CLE与i.MX35的连接是否正确有无虚焊。芯片ID读取失败这是最常见的原因。在FMD_Init函数的最开始添加详细的调试信息打印出发送的读ID命令和读回来的所有ID字节。对比数据手册看是否匹配。如果不匹配可能是时序问题i.MX35的NAND控制器时序配置在nandbsp.cpp的NF_Init函数中与新芯片不匹配。需要根据数据手册的AC特性表调整NF_SetTiming等相关函数的参数。硬件位宽不匹配代码中NAND_BUS_WIDTH设为8但硬件可能是16位连接或者反之。坏块标记逻辑错误如果Eboot能读ID但在尝试读取启动块通常是Block 0时失败。重点检查头文件中的BBI_MAIN_ADDR或BBI_SPARE_ADDR以及BBMarkPage的值。如果坏块标记位置或值设置错误FAL可能会将好的块误判为坏块导致无法找到有效的启动代码。建议初期调试时可以在FAL层代码中暂时禁用坏块检查或者将其调试信息打开看它是如何判断坏块的。问题Eboot可以启动并能识别Flash但下载或烧写NK镜像时失败排查几何参数错误NAND_PAGE_SIZE,NAND_SPARE_SIZE,NAND_BLOCK_CNT这些参数如果填错会导致Eboot计算出的擦除/写入地址完全错误。例如页大小填成2048而实际是4096那么写入第二页数据时就会覆盖第一页的后半部分造成数据混乱。务必反复核对数据手册。ECC校验不匹配i.MX35的NAND控制器硬件支持ECC错误校验与纠正。不同的Flash芯片其ECC算法要求和校验位在备用区的存放位置可能不同。BSP中的ECC配置在nandbsp.cpp中可能需要调整。如果写入和读取的ECC校验码不匹配操作就会失败。查看BSP中关于NF_ECC_xxx的配置并参考新芯片数据手册的ECC章节。驱动状态机错误仔细阅读FMD_WriteSector和FMD_EraseBlock函数。它们需要遵循严格的命令序列写操作是CMD_WRITE- 送地址 - 送数据 -CMD_WRITE2- 等待RDY擦除操作是CMD_ERASE- 送块地址 -CMD_ERASE2- 等待RDY。确保代码中的序列与数据手册完全一致。4.3 性能与稳定性优化当驱动基本调通后可以考虑以下优化调整时序参数在nandbsp.cpp的NF_SetTiming函数中有TACLS,TWRPH0,TWRPH1等参数它们对应NAND接口的建立、保持和等待时间。适当收紧这些参数在满足芯片最低要求的前提下可以提升读写速度。但收紧过度会导致读写不稳定。建议先用保守值较大值确保功能正常再逐步尝试优化。启用硬件ECC确保硬件ECC已正确启用且模式与Flash芯片兼容。硬件ECC能极大减轻CPU负担并提高可靠性。验证全盘读写编写一个简单的Eboot命令或应用程序对Flash进行全盘读写校验确保所有块都能被正确访问没有“软坏块”可擦写但ECC错误率高的块。移植NAND Flash驱动是一项需要耐心和细致的工作它连接着最底层的硬件和最上层的系统。每一次成功的移植都建立在对芯片数据手册的精确解读、对BSP代码结构的清晰理解以及大量的调试验证之上。当你看到Eboot的启动日志中正确打印出新Flash的ID和容量并成功加载起Windows CE的桌面时那种成就感是对之前所有繁琐工作的最好回报。记住保存好你修改的所有文件记录和调试笔记它们会成为你团队乃至整个公司的宝贵知识资产。