衡山派Baremetal与RTOS双平台MTD驱动设计说明:SPI NOR存储管理与SFUD集成
衡山派Baremetal与RTOS双平台MTD驱动设计说明SPI NOR存储管理与SFUD集成最近在衡山派平台上做项目经常需要存储一些配置参数和日志数据SPI NOR Flash是个不错的选择。但很多刚接触的朋友会问在裸机Baremetal和RTOS比如RT-Thread两种环境下怎么去驱动和管理这块Flash呢今天我就结合官方资料给大家手把手梳理一下衡山派平台下基于SFUD通用驱动框架的MTD驱动设计让你一次搞懂两种环境下的存储栈搭建。简单来说MTDMemory Technology Device是Linux里管理存储设备如Flash的一套抽象层在嵌入式裸机和RTOS里也常借鉴这个思想。而SFUDSerial Flash Universal Driver是一个开源的SPI Flash通用驱动库它能自动识别不同厂家的Flash芯片大大简化了我们的驱动工作。咱们的目标就是在衡山派上用SFUD驱动SPI NOR Flash并封装成MTD接口方便上层应用读写。1. 整体设计思路与源码结构在开始动手前咱们先看看“地图”。衡山派的SDK为Baremetal和RTOS两种环境提供了不同的源码组织方式但底层的硬件操作QSPI驱动和核心的SFUD库是共用的。1.1 Baremetal裸机环境裸机环境下没有操作系统的调度和驱动模型我们需要自己初始化硬件并管理设备。相关源码主要分布在以下几个地方模块源码路径说明应用测试示例bsp/examples_bare/mtd.c官方提供的测试代码是我们学习和验证的起点。MTD核心层bsp/artinchip/drv_bare/mtd/实现了MTD设备的管理、添加、查找等核心逻辑。SFUD适配层bsp/artinchip/drv_bare/spinor/sfud_port.c关键文件。它把衡山派的QSPI硬件操作接口“对接”给SFUD库。SFUD库本身bsp/peripheral/spinor_sfud通用的SFUD驱动库源码负责Flash的识别和基础命令。硬件抽象层(HAL)bsp/artinchip/hal/qspi/hal_qspi.c最底层的QSPI控制器驱动负责配置时钟、DMA、收发数据。提示开发时你可以直接参考bsp/examples_bare/mtd.c这个文件来了解如何调用MTD接口。如果想更底层地测试QSPI和Flash可以参考同目录下的test-spinor/spinor.c。1.2 RTOS以RT-Thread为例环境在RT-Thread这类操作系统中已经有了完善的设备驱动框架。我们的工作主要是将SFUD和衡山派的QSPI驱动按照RT-Thread的设备模型进行“注册”和“对接”。模块源码路径说明RT-Thread MTD设备kernel/rt-thread/components/drivers/mtd/mtd_nor.cRT-Thread系统自带的MTD设备驱动实现。SFUD在RT-Thread下的适配bsp/artinchip/drv/spinor/spinor_sfud.c关键文件。它创建QSPI总线设备并调用RT-Thread提供的SFUD探测函数。QSPI设备驱动bsp/artinchip/drv/qspi/drv_qspi.cRT-Thread设备驱动框架下的QSPI驱动实现。底层HALbsp/artinchip/hal/qspi/hal_qspi.c与裸机共用同一份硬件抽象层代码。注意在RTOS环境下MTD设备通常用于挂载LittleFS这类为Flash设计的文件系统而块设备blk device则用于挂载FatFS。这是由文件系统的特性决定的选择时需要注意。2. Baremetal环境下的驱动详解咱们先啃硬骨头看看在没有操作系统帮忙的情况下整个驱动栈是怎么一层层搭建起来的。2.1 初始化流程从硬件到MTD设备整个初始化的调用链比较长我把它拆解成几个阶段并用一个流程图来帮你理解mtd_probe() // 我们调用的入口函数 | |--- sfud_probe() // 进入SFUD探测流程 | | | |--- get_qspi_by_index() // 获取衡山派QSPI总线对象 | |--- hal_qspi_master_init() // 初始化QSPI控制器基本配置 | |--- hal_qspi_master_dma_config() // 配置DMA提升传输效率 | |--- hal_qspi_master_set_bus_freq() // 设置QSPI通信时钟频率 | | | |--- sfud_device_init() // SFUD库核心初始化函数 | | | |--- hardware_init() // 硬件初始化实际调用上述QSPI初始化 | |--- sfud_spi_port_init() // 关键挂接读写函数指针 | | |--- flash-spi.wr spi_write_read; // 标准SPI读写 | | |--- flash-spi.qspi_read qspi_read; // QSPI快速读 | | | |--- read_jedec_id() // 读取Flash芯片的JEDEC ID | |--- sfud_read_sfdp() // 高级功能读取SFDP表自动识别参数 | |--- sfud_qspi_fast_read_enable() // 使能QSPI的快速读取模式如4线模式 | |--- mtd_parts_parse() // 解析MTD分区表比如从某个头文件或配置中 | |--- 绑定操作函数 // 将SFUD的读写擦除函数赋给MTD设备 | mtd-ops.erase sfud_mtd_erase; | mtd-ops.read sfud_mtd_read; | mtd-ops.write sfud_mtd_write; | |--- mtd_add_device() // 将这个初始化好的MTD分区设备添加到系统链表我来解释一下几个关键点函数指针挂接在sfud_spi_port_init中spi_write_read和qspi_read这两个衡山派平台实现的函数被赋值给了SFUD库里的结构体。这样SFUD库里所有需要操作硬件的指令如读ID、写使能、页编程等最终都会调用到我们提供的这两个函数。这是驱动适配的核心。自动识别sfud_read_sfdp是SFUD库的强大之处。SFDPSerial Flash Discoverable Parameters是Flash芯片内部的一个标准信息表包含了容量、页大小、块大小、擦写时间等所有关键参数。SFUD读取它后就能自动适配不同型号的Flash无需我们手动修改代码。MTD封装初始化完Flash硬件后我们创建了一个struct mtd_dev对象并把SFUD提供的sfud_mtd_read/write/erase函数赋值给它。这样上层应用只需要面对统一的mtd_read等接口而不用关心底层是SFUD还是其他驱动。2.2 数据读取流程理解了初始化再看读数据就简单了。当应用调用mtd_read时发生如下调用mtd_read() // 应用调用MTD统一接口 | v sfud_mtd_read() // MTD层调用绑定的SFUD函数 | v sfud_read() // 进入SFUD库的通用读取函数 | v sfud_read_data() // SFUD内部根据地址、模式等处理 | v qspi_read() // 最终落到我们提供的硬件读取函数上这个过程体现了软件分层的好处应用层只关心“从A分区偏移100字节处读50个字节”MTD层负责找到对应的Flash设备和分区偏移SFUD层负责将读写请求翻译成标准的SPI Flash命令序列最后硬件层执行具体的QSPI传输。2.3 Baremetal MTD 应用接口手册驱动搭好了我们怎么用呢下面这几个是裸机环境下最常用的API我结合自己的使用经验给你说明一下。函数原型功能说明参数与返回值注意事项踩坑点int mtd_probe(void);驱动初始化总入口。必须第一个调用它会扫描并初始化所有SPI NOR Flash并解析分区表。返回值0成功其他失败。这个函数内部会操作Flash如果板子上已经有Bootloader或系统镜像要确保分区表配置正确别把系统区给擦写了。int mtd_add_device(struct mtd_dev *mtd);手动添加一个MTD设备到管理链表。通常mtd_probe内部会调用我们很少直接用。mtd: 初始化好的MTD设备对象。一般用于动态创建特殊分区时使用。u32 mtd_get_device_count(void);获取当前系统中注册的MTD设备分区总数。返回值MTD设备个数。可以用来遍历所有分区。struct mtd_dev *mtd_get_device_by_id(u32 id);通过索引号从0开始获取MTD设备句柄。id: 设备索引。返回值成功返回句柄失败返回NULL。索引顺序取决于mtd_probe或mtd_add_device的添加顺序。struct mtd_dev *mtd_get_device(const char *name);最常用的接口。通过分区名字获取MTD设备句柄。name: 分区名如logo,recovery。返回值成功返回句柄失败返回NULL。分区名在分区表中定义调用前要确认名字正确。int mtd_read(struct mtd_dev *mtd, u32 offset, u8 *data, u32 len);从MTD分区读取数据。mtd: 设备句柄。offset: 分区内的偏移量。data: 存放数据的缓冲区指针。len: 要读取的长度。返回值0成功其他失败。offset最好与 Flash 的页Page大小对齐虽然SFUD可能支持非对齐读但对齐访问效率最高。int mtd_erase(struct mtd_dev *mtd, u32 offset, u32 len);擦除MTD分区的一块区域。Flash必须先擦除才能写入。mtd: 设备句柄。offset: 擦除起始偏移。len: 擦除长度。返回值0成功其他失败。最重要offset和len必须与 Flash 的块Block/Sector大小严格对齐不对齐会导致擦除失败或误擦其他数据。擦除时间较长是阻塞操作。int mtd_write(struct mtd_dev *mtd, u32 offset, u8 *data, u32 len);向MTD分区写入数据。参数同mtd_read。offset最好与页Page大小对齐。写入前目标区域必须已经被擦除。SFUD内部会处理跨页写入。3. RT-Thread环境下的驱动集成在RT-Thread下事情变得更有条理了因为我们可以利用操作系统提供的设备驱动框架。核心初始化函数是rt_hw_spi_flash_with_sfud_init。它的初始化流程可以概括如下rt_hw_spi_flash_with_sfud_init() // 硬件初始化函数在board.c中调用 | |--- aic_qspi_bus_attach_device(qspi0, spi0, ...) // 1. 创建并注册QSPI总线设备 | |--- rt_sfud_flash_probe(norflash0, spi0) // 2. 探测并挂载Flash设备 | |--- rt_sfud_flash_probe_ex(...) | |--- rt_qspi_configure() // 配置QSPI设备参数 | |--- sfud_device_init() // 初始化SFUD与裸机流程类似 | | | |--- hardware_init() | |--- sfud_spi_port_init() // 挂接RT-Thread提供的spi_write_read等 | |--- read_jedec_id() | |--- sfud_read_sfdp() | |--- sfud_qspi_fast_read_enable() // 使能快速读 | |--- rt_device_register() // 3. 将Flash设备注册到RT-Thread设备框架这个过程和裸机的核心思想一致但多了两步RT-Thread特有的操作总线设备创建首先将衡山派的QSPI控制器注册为RT-Thread下的一个qspi总线设备。设备探测与注册通过rt_sfud_flash_probe将Flash芯片作为rt_device注册到系统。注册成功后我们就可以通过RT-Thread标准的设备操作接口rt_device_read/write/control来访问Flash也可以使用rt_mtd_nor接口或者直接挂载文件系统。提示在RT-Thread下SFUD的硬件适配函数如spi_write_read是由RT-Thread的SPI设备驱动框架提供的通用函数它内部会调用我们注册的qspi0总线驱动。这种分层使得驱动更加通用和可移植。4. 实战心得与常见问题最后分享几个在实际项目中容易遇到的问题希望能帮你避坑。1. 分区表是重中之重无论是裸机还是RTOS在mtd_probe或初始化时都会解析分区表。这个表定义了每个分区如boot、kernel、rootfs、userdata的起始地址和大小。务必确保这个表和你Flash的实际布局、以及Bootloader的期望完全一致否则很容易破坏已有的系统镜像。2. 对齐对齐对齐这是Flash操作的老生常谈但新手必踩坑。擦除Erase必须按块Block/Sector常见64KB对齐。试图擦除一个块的中间部分是不可能的。写入Write建议按页Page常见256B/4KB对齐。虽然很多Flash支持非对齐写入会先读出该页在内存中合并数据再写回但这有风险且影响寿命和性能。读取Read通常可以任意字节读取但按页对齐效率更高。3. 注意操作阻塞时间擦除一个64KB的块可能需要几十到几百毫秒写入一页也可能要几毫秒。在裸机环境下这些是阻塞操作CPU会死等。在RTOS环境下如果是在高优先级线程或中断中执行可能会影响系统实时性。可以考虑在RTOS中创建低优先级线程专门处理存储任务。4. 利用SFUD的调试信息SFUD库有很好的调试输出功能。在开发初期务必打开调试宏如SFUD_DEBUG_MODE它会在初始化时打印出检测到的Flash型号、容量、页大小、块大小等关键信息。这能帮你快速确认硬件连接和驱动是否正常。好了关于衡山派平台下SPI NOR Flash的MTD驱动设计从裸机到RTOS的整套逻辑就梳理完了。核心就是理解SFUD如何作为硬件和MTD之间的桥梁以及两种环境下初始化流程的差异。动手时先从官方的mtd.c示例开始跑通后再修改分区表适配自己的项目稳扎稳打肯定能搞定。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2409880.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!