当前位置: 首页 > article >正文

衡山派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示例开始跑通后再修改分区表适配自己的项目稳扎稳打肯定能搞定。

相关文章:

衡山派Baremetal与RTOS双平台MTD驱动设计说明:SPI NOR存储管理与SFUD集成

衡山派Baremetal与RTOS双平台MTD驱动设计说明:SPI NOR存储管理与SFUD集成 最近在衡山派平台上做项目,经常需要存储一些配置参数和日志数据,SPI NOR Flash是个不错的选择。但很多刚接触的朋友会问:在裸机(Baremetal&…...

Z-Image-Turbo-辉夜巫女生产环境:多用户Gradio前端+Xinference后端协同部署方案

Z-Image-Turbo-辉夜巫女生产环境:多用户Gradio前端Xinference后端协同部署方案 1. 引言:从单机玩具到生产级服务的跨越 如果你玩过AI绘画,大概率体验过那种“一人独享”的本地部署——打开WebUI,输入提示词,等待生成…...

3步解锁音乐自由:让加密音频重获新生的开源解决方案

3步解锁音乐自由:让加密音频重获新生的开源解决方案 【免费下载链接】qmcdump 一个简单的QQ音乐解码(qmcflac/qmc0/qmc3 转 flac/mp3),仅为个人学习参考用。 项目地址: https://gitcode.com/gh_mirrors/qm/qmcdump 在数字音…...

智能车竞赛实战指南:基于快马平台构建完整车辆控制应用

最近在准备智能车竞赛,发现很多同学在软件部分会遇到一个难题:如何快速搭建一个接近实战、能模拟真实车辆行为的综合控制程序?硬件调试固然重要,但一个稳定、逻辑清晰的软件框架是成功的基础。今天,我就结合自己的经验…...

雪花氛围灯:基于RH6618A的极简触控调光硬件设计

1. 项目概述雪花氛围灯是一款面向电子爱好者与嵌入式初学者设计的便携式装饰照明装置,其核心目标是通过极简硬件架构实现高感知价值的人机交互体验:在无外部供电条件下,仅凭指尖轻触即可完成开关控制与无级亮度调节,并支持个性化灯…...

Qwen3.5-27B开源多模态模型部署案例:中文Web对话+图片理解双接口落地

Qwen3.5-27B开源多模态模型部署案例:中文Web对话图片理解双接口落地 1. 引言:一个模型,两种能力 想象一下,你有一个智能助手,不仅能和你流畅地中文聊天,还能看懂你发过去的图片,告诉你图片里有…...

PCL2-CE社区版启动器:让Minecraft游戏体验不再受限于传统启动工具

PCL2-CE社区版启动器:让Minecraft游戏体验不再受限于传统启动工具 【免费下载链接】PCL-CE PCL2 社区版,可体验上游暂未合并的功能 项目地址: https://gitcode.com/gh_mirrors/pc/PCL-CE PCL2-CE社区版启动器是一款开源的Minecraft启动工具&#…...

Python flask 家乡周边旅游项目预约系统 微信小程序

目录技术栈选择数据库设计后端API开发微信小程序前端地图功能集成支付功能实现部署方案项目技术支持可定制开发之功能创新亮点源码获取详细视频演示 :文章底部获取博主联系方式!同行可合作技术栈选择 后端采用Python Flask框架,提供RESTful …...

AI辅助开发:让Kimi分析激活函数优劣,自动生成集成Swish等新函数的GRU情感分析模型

最近在做一个文本情感分析的项目,打算用循环神经网络(RNN)来做。大家都知道,像LSTM、GRU这类经典循环单元,内部隐藏状态的变换通常都默认使用Tanh激活函数。但我在想,现在有那么多新的、表现更好的激活函数…...

VisionPro中CogPMAlignTool图像匹配工具的高级应用与实战技巧

1. 从新手到高手:理解CogPMAlignTool的核心价值 如果你正在用康耐视的VisionPro做视觉项目,尤其是涉及到定位、对位或者识别,那你肯定绕不开CogPMAlignTool这个工具。很多新手朋友第一次用的时候,可能会觉得它就是个“找图”的工具…...

C# WinForm —— 高效Form初始化与动态布局实战

1. 从“慢吞吞”到“秒开”:Form初始化的那些事儿 不知道你有没有遇到过这种情况:打开一个WinForm程序,界面要“卡”一下才出来,或者点击按钮后,界面反应慢半拍。很多时候,这锅得甩给Form初始化没做好。我刚…...

Phi-3-mini-128k-instruct部署避坑指南:vLLM加载失败、Chainlit连接超时解决方案

Phi-3-mini-128k-instruct部署避坑指南:vLLM加载失败、Chainlit连接超时解决方案 你是不是也遇到过这种情况:兴致勃勃地部署一个最新的AI模型,结果卡在模型加载或者前端连接上,折腾半天也没搞定?今天咱们就来聊聊Phi-…...

基于ColorEasyDuino的GP2Y1014AU粉尘传感器数据采集与浓度计算实战

基于ColorEasyDuino的GP2Y1014AU粉尘传感器数据采集与浓度计算实战 最近在做一个室内空气质量监测的小项目,需要检测空气中的粉尘浓度,于是就用上了夏普的GP2Y1014AU粉尘传感器。这个传感器在创客圈里挺有名的,价格不贵,效果也不错…...

从模型到极限:深入解析信道容量与香农公式

1. 信道:信息的高速公路,但路况复杂 聊到通信,大家脑子里蹦出来的第一个画面,可能就是手机信号、Wi-Fi图标。但信号是怎么从你的手机跑到基站,再跑到朋友手机里的呢?这中间走过的“路”,就是我们…...

从零搭建局域网:eNSP模拟实验全流程解析

1. 为什么你需要一台“虚拟”交换机?从零认识eNSP 如果你对网络技术感兴趣,或者正在学习计算机网络课程,那么“动手实验”绝对是绕不开的一环。但现实是,我们很难在宿舍或家里摆满真实的交换机、路由器,更别说为了一个…...

Vue2与WebSocket实战:构建高效实时聊天室的全流程解析

1. 为什么需要WebSocket?从“轮询”到“长连接”的进化 想象一下,你正在和一个朋友用微信聊天。如果微信用的是传统的HTTP协议,那会是什么场景?你发一句“在吗?”,然后你的手机就得不停地、每隔一秒就问一次…...

解决AndroidX依赖冲突:appcompat-resources版本与compileSdkVersion不兼容问题

1. 从一次真实的构建失败说起 那天下午,我正在给一个老项目添加一个新功能,像往常一样点击了Android Studio那个绿色的“运行”按钮,满心期待地等着应用在模拟器上启动。结果,等来的不是熟悉的启动画面,而是一大段刺眼…...

VLSI设计基石——CMOS反相器的性能建模与优化

1. 从开关到基石:为什么CMOS反相器如此重要? 如果你刚开始接触芯片设计,可能会觉得“CMOS反相器”这个名字听起来既陌生又复杂。别担心,让我用一个简单的比喻来开场。你可以把整个复杂的数字芯片想象成一座宏伟的乐高城堡&#xf…...

AE Shutter Gain Check 笔记

和你一起终身学习,这里是程序员Android 经典好文推荐,通过阅读本文,您将收获以下知识点: 一、Camera Sensor 驱动shutter Check二、Exposure Time 转换为 shutter三、Camera Sensor 驱动GAIN Check 一、驱动 shutter Check 1.1 SENSOR_FEATURE_SET_ESHUTTER 每个AE 周期会根…...

Vue3 PrimeVue 后台管理系统开发实战:从零搭建高效UI框架

1. 为什么选择 Vue3 PrimeVue 来搭建后台管理系统? 如果你正在为下一个企业级后台管理项目选型,或者厌倦了重复造轮子,想找一个既强大又省心的UI框架,那么 Vue3 搭配 PrimeVue 的组合,绝对值得你花时间深入了解。我经…...

Navigating the Peer Review Process: A Personal Journey with Applied Energy

1. 从“秒拒”到“送审”:我的Applied Energy投稿心路 说实话,第一次收到Applied Energy的desk rejection(编辑直接拒稿)邮件时,我整个人是懵的。那感觉就像你精心准备了一场演讲,刚走上台,还没…...

从“Expected 96, got 88”报错出发:深度解析NumPy二进制兼容性陷阱与多版本环境治理

1. 从“Expected 96, got 88”说起:一个让开发者头疼的经典报错 如果你在运行一个Python科学计算项目,特别是用到了像gensim、scikit-learn、pandas这些依赖NumPy的库时,突然在控制台看到这么一串红字:numpy.ndarray size changed…...

动态调参实战:从理论到代码的深度优化指南

1. 为什么我们需要动态调参?从“手动挡”到“自动挡”的进化 如果你玩过摄影,肯定知道手动模式(M档)和自动模式(A档)的区别。手动模式让你能精细控制光圈、快门、ISO,拍出你想要的效果&#xff…...

提升mysql开发效率神器,快马平台ai自动生成优化代码和查询工具

最近在做一个数据量比较大的项目,数据库用的是MySQL。随着数据量增长,一些原本跑得飞快的查询开始变得“步履蹒跚”,慢查询日志里也开始出现一些“钉子户”。手动去分析每个慢SQL、看执行计划、琢磨怎么加索引,实在是费时费力&…...

快速验证模型性能:在快马平台一键生成openclaw更换模型的代码原型

最近在做一个图像相关的项目,需要评估不同骨干网络(Backbone)对模型性能的影响。我们的基础框架是OpenClaw,一个用于细粒度图像识别的开源项目。核心需求是快速验证,如果把OpenClaw默认的ResNet模型,换成Ef…...

误差函数(Error Function)的数值计算与工程实现

1. 误差函数:从数学定义到工程实现的桥梁 大家好,我是老张,在AI和科学计算领域摸爬滚打了十几年。今天我们不聊那些高深莫测的理论推导,而是来点实在的——聊聊误差函数(Error Function)在实际工程中到底怎…...

跨时钟域数据传输:异步FIFO中的格雷码应用与Verilog实现

1. 异步FIFO:跨时钟域通信的“安全缓冲区” 如果你做过数字电路设计,尤其是涉及多个时钟模块的系统,那你肯定遇到过这个头疼的问题:数据从一个时钟域传到另一个时钟域,怎么就出错了呢?我刚开始做项目的时候…...

Python类与对象进阶:解锁内建函数、私有化与授权的实战技巧

1. 别再死记硬背了:让内建函数成为你的“类型侦探” 刚开始学Python面向对象那会儿,我总觉得issubclass、isinstance这些名字又长又拗口,每次用都得翻文档,感觉它们离日常开发很远。直到有一次,我写一个处理多种数据源…...

保姆级教程:手把手教你用Qwen-Image在Dify实现图生图

保姆级教程:手把手教你用Qwen-Image在Dify实现图生图 你是不是也遇到过这样的烦恼?看到一张不错的图片,想让它换个风格,或者给里面加点新东西,但自己不会PS,找设计师又太麻烦。或者,你有一个绝…...

立创开源PocketServo:基于STM32G474的EtherCAT总线迷你FOC驱动器全解析

立创开源PocketServo:基于STM32G474的EtherCAT总线迷你FOC驱动器全解析 最近在做一个工业机械臂的小项目,需要用到体积小、性能强、还能接入工业总线的伺服驱动器。找了一圈,要么是体积太大,要么是价格太高,要么就是二…...