
衡山派平台SPI NAND Flash驱动设计说明Baremetal与RTOS双环境下的MTD架构与接口详解最近在衡山派ArtInChip平台上做项目要用到SPI NAND Flash存储数据。很多刚接触这块的朋友可能会觉得底层驱动、MTD架构这些概念听起来就头大。别担心今天我就以工程师的视角带大家把衡山派的SPI NAND驱动设计捋清楚重点对比在裸机Baremetal和RTOS以RT-Thread为例两种环境下这套驱动是怎么工作的咱们应该怎么用。这篇文章适合正在使用衡山派平台需要操作SPI NAND Flash的开发者。无论你是想在裸机环境下直接操控存储还是想在RT-Thread上挂载文件系统都能从这里找到清晰的路径和关键的接口说明。1. 驱动源码在哪里—— 文件结构一览拿到SDK第一步肯定是先找代码在哪。衡山派的SPI NAND驱动代码组织得很清晰根据你运行的环境裸机还是RTOS路径完全不同。1.1 裸机Baremetal环境源码路径如果你是在没有操作系统的裸机环境下开发所有驱动代码都在bsp/目录下。具体到SPI NAND和MTD主要关注这几个地方应用测试示例bsp/examples_bare/mtd.c提示这是官方提供的测试代码你开发时可以把它当作参考模板。另外bsp/examples_bare/test-spinand/spinand.c也是一个重要的参考文件。MTD核心层bsp/artinchip/drv_bare/mtd/这里实现了MTD设备的核心管理比如设备注册、查找等。SPI NAND驱动层bsp/artinchip/drv_bare/spinand/spinand_port.c: 硬件端口层负责QSPI总线配置等硬件相关操作。spinand_mtd.c: MTD接口适配层将底层SPI NAND的操作封装成标准的MTD操作如mtd_read,mtd_write。SPI NAND通用库bsp/peripheral/spinand/这里是与具体主控芯片无关的SPI NAND通用操作库比如读ID、读页、写页、擦除块等。spinand.c是核心文件。1.2 RTOS以RT-Thread为例环境源码路径如果你在RT-Thread系统下开发驱动代码会分为通用组件和平台适配两部分RT-Thread通用MTD框架kernel/rt-thread/components/drivers/mtd/mtd_nand.c这是RT-Thread系统自带的、与平台无关的MTD设备驱动框架。衡山派平台适配层bsp/artinchip/drv/spinand/spinand_port.c: 对接RT-Thread MTD框架的端口层实现rt_hw_spinand_register等注册函数。spinand_block.c: 实现块设备blk接口用于支持像FatFS这样的文件系统。底层SPI NAND库bsp/peripheral/spinand/和裸机共用同一套底层操作库确保硬件行为一致。QSPI驱动bsp/artinchip/drv/qspi/drv_qspi.c提供QSPI总线控制。注意在RTOS环境下MTD设备通常用于挂载UFFS专为NAND设计的文件系统而BLK设备用于挂载FatFS。驱动已经为我们做好了这两套接口。2. 驱动是如何工作的—— 关键流程剖析理解了文件结构咱们来看看代码跑起来时关键的执行流程是怎样的。这里对比着看裸机和RTOS的初始化过程差异就很明显了。2.1 初始化流程对比裸机Baremetal初始化流程在裸机下你需要主动调用初始化函数来启动整个存储系统。核心函数是mtd_probe()。// 应用层调用 mtd_probe(); // 位于 bsp/artinchip/drv_bare/mtd/mtd_probe.c这个函数内部会依次执行探测SPI NAND设备spinand_probe(AIC_BOOTLOADER_SPINAND_QSPI_ID)配置硬件获取QSPI总线对象并配置时钟、DMA等参数qspi_configure。初始化Flash读取芯片信息初始化坏块表spinand_flash_init-spinand_info_read,nand_bbt_init。解析分区表获取Flash的分区信息mtd_parts_parse。挂载操作函数将底层的SPI NAND操作函数如擦除、读、写、坏块检查赋值给MTD设备的结构体mtd-ops。注册MTD设备将初始化好的MTD分区添加到系统设备列表中mtd_add_device。RTOSRT-Thread初始化流程在RT-Thread下驱动通常以“设备”的形式在系统启动时自动注册。核心是rt_hw_spinand_register函数。// 系统初始化阶段调用通常在 board.c 中 rt_hw_spinand_register(); // 位于 bsp/artinchip/drv/spinand/spinand_port.c它的执行路径是附着设备到总线创建并注册QSPI总线设备aic_qspi_bus_attach_device。注册MTD设备调用rt_hw_mtd_spinand_register。初始化Flash同样调用spinand_flash_init完成芯片初始化和坏块表建立。解析分区并双注册调用rt_mtd_nand_register_device注册为MTD设备供UFFS使用。调用rt_blk_nand_register_device注册为块设备BLK供FatFS使用。重要提示在裸机环境下开发应用强烈建议在MTD层进行使用mtd_开头的API。直接在SPINAND层操作可能会破坏SPL启动加载程序或系统预留分区的数据。2.2 数据读取流程对比无论裸机还是RTOS最终落到硬件上的读取操作都是一样的但上层调用路径不同。裸机下的读数据mtd_read(mtd, offset, data, len); // 用户调用MTD接口 - mtd_spinand_read(...) // MTD层转到SPI NAND适配层 - spinand_read(...) // 调用通用SPI NAND库 - spinand_block_isbad(...) // 检查坏块 - spinand_read_page(...) // 读取页数据RTOS下的读数据MTD路径// 通过RT-Thread的设备操作接口读 rt_device_read(mtd_device, ...); - mtd_read(...) // RT-Thread通用MTD框架 - spinand_mtd_read(...) // 平台适配层函数 - spinand_read(...) // 同样调用通用SPI NAND库 // ... 后续与裸机相同RTOS下的读数据BLK路径如果你将SPI NAND当作块设备比如挂载FatFS读取会走另一条路// 块设备操作 rt_spinand_read(...) // 块设备接口实现 - spinand_mtd_read(...) // 复用MTD适配层的读取函数 - spinand_read(...) // 最终落到相同的硬件操作可以看到虽然上层接口和调用链不同但最底层的硬件操作spinand_read是共享的这保证了驱动行为的一致性。3. 核心数据结构长啥样驱动里用几个关键的结构体来管理SPI NAND设备理解它们对调试和高级应用很有帮助。3.1struct aic_spinand- 设备管理核心这个结构体代表了一个SPI NAND控制器实例包含了运行时所需的所有信息。struct aic_spinand { const struct aic_spinand_info *info; // 指向芯片静态信息型号、容量等 struct spinand_id id; // 芯片ID void *user_data; // 用户自定义数据指针 void *lock; // 用于多线程访问的锁RTOS下重要 u8 use_continuous_read; // 是否启用连续读取模式部分芯片支持 u8 qspi_dl_width; // QSPI总线数据线宽度1/2/4线 u8 IsInited; // 初始化标志位 u8 *databuf; // 数据缓冲区指针 u8 *oobbuf; // OOB冗余区缓冲区指针 struct nand_bbt bbt; // 坏块表Bad Block Table信息 };info链接到芯片的“身份证”和规格书。bbt这是NAND Flash管理的关键驱动会扫描并记录哪些块是坏的读写时自动跳过。lock在RTOS环境下这个锁保证了多个任务同时访问Flash时的数据安全。3.2struct aic_spinand_info- 芯片规格描述这个结构体描述了具体SPI NAND芯片的硬件参数通常是常量数据。struct aic_spinand_info { u32 devid; // 设备ID u16 page_size; // 页大小如2048字节 u16 oob_size; // 每页的OOB冗余区大小如64字节 u32 block_per_lun; // 每个LUN逻辑单元的块数 u32 pages_per_eraseblock; // 每个擦除块包含的页数 u8 is_die_select; // 是否支持Die选择多晶片封装 const char *sz_description; // 芯片描述字符串 struct spi_nand_cmd_cfg *cmd; // 指向该芯片专用命令集的指针 };这些参数直接决定了你操作Flash时的基本单位按页读写按块擦除。3.3struct spi_nand_cmd_cfg- 命令格式这个结构体定义了发送给SPI NAND芯片的指令格式。struct spi_nand_cmd_cfg { u8 opcode; // 操作码如读数据是0x03 u8 opcode_bits; // 操作码位数 u8 addr_bytes; // 地址字节数 u8 addr_bits; // 地址位数 u8 dummy_bytes; // dummy周期等待字节数 u8 data_bits; // 数据位宽1/2/4线 };驱动根据这个配置来拼装发送给Flash的指令序列。不同厂家的芯片这个配置可能不同。4. 我该调用哪些函数—— 关键API详解了解了流程和结构最后来看看我们开发时最常打交道的API。这里分为底层库接口和应用层接口。4.1 底层SPI NAND库接口bsp/peripheral/spinand/这些函数是驱动的基础通常由MTD层调用在裸机下你也可以直接使用需谨慎。函数原型功能说明关键注意事项int spinand_read(struct aic_spinand *flash, u8 *buf, u32 offset, u32 size);从Flash读取数据offset必须按页大小对齐int spinand_write(struct aic_spinand *flash, u8 *buf, u32 offset, u32 size);向Flash写入数据offset必须按页大小对齐且目标区域必须已擦除int spinand_erase(struct aic_spinand *flash, u32 offset, u32 size);擦除Flash区域offset和size必须按块大小对齐int spinand_block_isbad(struct aic_spinand *flash, u16 blk);检查指定块是否为坏块参数是块号不是字节偏移int spinand_block_markbad(struct aic_spinand *flash, u16 blk);标记指定块为坏块谨慎使用标记后该块将不再被使用4.2 裸机Baremetal应用接口MTD层对于大多数裸机应用强烈建议使用MTD层提供的接口它们封装了坏块管理更安全易用。函数原型功能说明关键注意事项int mtd_probe(void);初始化所有MTD设备必须先调用此函数才能使用其他MTD功能struct mtd_dev *mtd_get_device(const char *name);通过分区名获取MTD设备句柄参数是分区表里定义的名字如rootfsint mtd_read(struct mtd_dev *mtd, u32 offset, u8 *data, u32 len);从MTD分区读取数据遇到坏块会自动跳过offset需按页对齐int mtd_write(struct mtd_dev *mtd, u32 offset, u8 *data, u32 len);向MTD分区写入数据遇到坏块会自动跳过offset需按页对齐目标区域需已擦除int mtd_erase(struct mtd_dev *mtd, u32 offset, u32 len);擦除MTD分区区域offset和len必须按块大小对齐裸机开发示例#include mtd.h // 包含MTD头文件 void test_spinand(void) { struct mtd_dev *mtd; u8 buffer[2048]; // 1. 初始化MTD系统必须 if (mtd_probe() ! 0) { printf(MTD probe failed!\n); return; } // 2. 通过分区名获取设备句柄 mtd mtd_get_device(userdata); // 假设有个叫userdata的分区 if (mtd NULL) { printf(Get MTD device failed!\n); return; } // 3. 擦除一个块例如从0地址开始擦除一个块 if (mtd_erase(mtd, 0, mtd-erasesize) ! 0) { printf(Erase failed!\n); } // 4. 写入数据写入一页数据 memset(buffer, 0x55, sizeof(buffer)); // 填充测试数据 if (mtd_write(mtd, 0, buffer, mtd-writesize) ! 0) { printf(Write failed!\n); } // 5. 读取数据 memset(buffer, 0, sizeof(buffer)); // 清空缓冲区 if (mtd_read(mtd, 0, buffer, mtd-writesize) ! 0) { printf(Read failed!\n); } else { printf(Read data OK.\n); } }4.3 RTOSRT-Thread下的使用在RT-Thread下驱动初始化后SPI NAND会注册为MTD设备和BLK设备。你可以通过RT-Thread标准的设备操作接口rt_device_xxx来访问或者直接让文件系统如UFFS、FatFS去挂载它。挂载文件系统示例思路// 挂载UFFS到MTD设备上假设设备名为 spinand0 dfs_mount(spinand0, /, uffs, 0, 0); // 或者挂载FatFS到BLK设备上假设块设备名为 spinand_blk0 dfs_mount(spinand_blk0, /sd, elm, 0, 0);具体的挂载参数和初始化流程需要参考RT-Thread和相应文件系统的文档。5. 总结与实战要点通过上面的梳理我们可以看到衡山派的SPI NAND驱动设计采用了清晰的分层架构底层硬件库(bsp/peripheral/spinand/)提供芯片基本操作与运行环境无关。中间适配层在裸机下是spinand_mtd.c在RTOS下是spinand_port.c负责将底层操作适配到对应的框架MTD或RT-Thread设备驱动模型。上层应用接口裸机下是mtd_开头的APIRTOS下是标准的设备操作或文件系统接口。给开发者的几个实用建议首选MTD层API在裸机下做应用开发尽量使用mtd_read/mtd_write等接口它们帮你处理了坏块管理更省心。注意地址对齐NAND Flash操作有严格的对齐要求。读/写必须以页为单位擦除必须以块为单位。调用API前务必检查mtd-writesize(页大小) 和mtd-erasesize(块大小)。擦除后再写入NAND Flash的特性决定了在写入数据前目标区域必须是已擦除状态全为0xFF。直接向未擦除的区域写入会失败。利用好示例代码bsp/examples_bare/mtd.c和spinand.c是最好的学习材料里面有完整的初始化和读写擦除流程。RTOS下的选择根据你要使用的文件系统类型决定是操作MTD设备UFFS还是BLK设备FatFS。驱动已经为你准备好了两套接口。希望这篇详解能帮你理顺衡山派SPI NAND驱动的脉络。在实际项目中从示例代码开始结合这里的流程和API说明一步步调试很快就能上手。