Jelajahi Sumber

add:新增8101-flash-demo

马亚丹 2 bulan lalu
induk
melakukan
483ff9a498

+ 204 - 0
module/Air8101/demo/accessory_board/AirSPINORFLASH_1000/lf_fs.lua

@@ -0,0 +1,204 @@
+--[[
+@module  lf_fs
+@summary lf_fs测试功能模块
+@version 1.0
+@date    2025.9.05
+@author  马亚丹
+@usage
+本demo演示的功能为:使用Air8101核心板通过SPI核心库/lf核心库/io核心库实现对 NOR Flash的操作,演示读数据写数据、删除数据等操作。
+以Air8101核心板为例, 接线如下:
+
+Air8101核心板    AirSPINORFLASH_1000配件版
+GND(任意)               GND
+3.3V                    VCC
+SPI0_CS/p54/GPIO15      CS
+SPI0_SCK/p28/GPIO14     CLK
+SPI0_MOSI/p57/GPIO16    MOSI
+SPI0_MISO/p55/GPIO17    MISO
+
+使用SPI0,硬件SPI CS接在gpio15上
+
+运行核心逻辑:
+1.以对象的方式配置参数,初始化启用SPI,返回SPI对象
+2.用SPI对象初始化flash设备,返回flash设备对象
+3.用lf库挂载flash设备对象为LittleFS文件系统
+4.读取文件系统的信息,以确认内存足够用于文件操作
+5.操作文件读写,并验证写入一致性,追加文件等。
+
+]]
+
+-- SPI配置参数
+local SPI_ID = 0             -- SPI总线ID,根据实际情况修改
+local CS_PIN = 15            -- CS引脚,根据实际情况修改
+local CPHA = 0               -- 时钟相位
+local CPOL = 0               -- 时钟极性
+local data_Width = 8         -- 数据宽度(位)
+local bandrate = 4 * 1000 * 1000 -- 波特率(Hz),初始化为4MHz,8101最低支持4M
+gpio.setup(13, 1)            --air8101模组,gpio13控制ldo输出3.3v
+
+-- 1. 以对象方式设置并启用 SPI,返回设备对象
+local function spiDev_init_func()
+    log.info("lf_fs", "SPI_ID", SPI_ID, "CS_PIN", CS_PIN)
+
+    --以对象的方式初始化spi,高位在前,主模式,半双工模式
+    --spi  flash只支持半双工模式
+    local spi_device = spi.deviceSetup(SPI_ID, CS_PIN, CPHA, CPOL, data_Width, bandrate, spi.MSB, 1, 0)
+
+    log.info("硬件spi", "初始化,波特率:", spi_device, bandrate)
+    if not spi_device then
+        log.error("SPI初始化", "失败")
+        return nil
+    end
+    log.info("SPI初始化", "成功,波特率:", bandrate)
+    return spi_device
+end
+
+
+-- 2. 初始化Flash设备,返回设备对象
+local function init_flash_device(spi_device)
+    log.info("Flash初始化", "开始")
+    local flash_device = lf.init(spi_device)
+    if not flash_device then
+        log.error("Flash初始化", "失败")
+        return nil
+    end
+    log.info("Flash初始化", "成功,设备:", flash_device)
+    return flash_device
+end
+
+-- 3. 挂载文件系统
+local function mount_filesystem(flash_device, mount_point)
+    log.info("文件系统", "开始挂载:", mount_point)
+
+    -- 检查是否支持挂载功能
+    if not lf.mount then
+        log.error("文件系统", "lf模块不支持挂载功能")
+        return false
+    end
+
+    -- 尝试挂载
+    local mount_ok = lf.mount(flash_device, mount_point)
+    if not mount_ok then
+        log.warn("文件系统lf", "挂载失败,尝试重新挂载...")
+        mount_ok = lf.mount(flash_device, mount_point)
+        if not mount_ok then
+            log.error("文件系统", "仍挂载失败")
+            return false
+        end
+    end
+
+    log.info("文件系统", "挂载成功:", mount_point)
+    return true
+end
+
+-- 4. 打印文件系统信息
+local function print_filesystem_info(mount_point)
+    log.info("文件系统信息", "开始查询:", mount_point)
+
+    -- 获取文件系统详细信息,总块数/已用块数等
+    local ok, total_blocks, used_blocks, block_size, fs_type = fs.fsstat(mount_point)
+    if ok then
+        log.info("  总block数:", total_blocks)
+        log.info("  已用block数:", used_blocks)
+        log.info("  block大小:", block_size, "字节")
+        log.info("  文件系统类型:", fs_type)
+    else
+        log.warn("  无法获取详细信息")
+    end
+end
+
+-- 5. 执行文件操作测试
+local function test_file_operations(mount_point)
+    log.info("文件操作测试", "开始")
+
+    -- 测试写入文件
+    local test_file = mount_point .. "/test.txt"
+    local f, err = io.open(test_file, "w")
+    if not f then
+        log.error("  写入失败", test_file, "错误:", err)
+        return false
+    end
+    local write_data = "当前时间: " .. os.date()
+    f:write(write_data)
+    f:close()
+    log.info("  写入成功", test_file, "内容:", write_data)
+
+    -- 测试读取文件
+    local read_data, read_err = io.readFile(test_file)
+    if not read_data then
+        log.error("  读取失败", test_file, "错误:", read_err)
+        return false
+    end
+    log.info("  读取成功", test_file, "内容:", read_data)
+
+    -- 验证内容一致性
+    if read_data ~= write_data then
+        log.warn("  内容不一致", "写入:", write_data, "读取:", read_data)
+    end
+
+    -- 测试文件追加
+    local append_file = mount_point .. "/append.txt"
+    os.remove(append_file) -- 清除旧文件
+    io.writeFile(append_file, "LuatOS 测试") -- 初始写入
+
+    local f_append, append_err = io.open(append_file, "a+")
+    if not f_append then
+        log.error("  追加失败", append_file, "错误:", append_err)
+        return false
+    end
+    local append_data = " - 追加时间: " .. os.date()
+    f_append:write(append_data)
+    -- 执行完操作后,一定要关掉文件
+    f_append:close()
+
+    local final_data = io.readFile(append_file)
+    log.info("  追加后内容:", final_data)
+
+    log.info("文件操作测试", "完成")
+
+    return true
+end
+
+-- 7. 关闭SPI设备对象,成功返回true
+local function spi_close_func()
+    log.info("关闭spi", spi_device:close())
+end
+
+-- 主任务函数:按流程调用各功能函数
+local function spinor_test_func()
+    --1.判断SPI初始化
+    spi_device = spiDev_init_func()
+    if not spi_device then
+        log.error("主流程", "SPI初始化失败,终止")
+        return
+    end
+
+    -- 流程2:初始化Flash设备
+    local flash_device = init_flash_device(spi_device)
+    if not flash_device then
+        log.error("主流程", "Flash初始化失败,终止")
+        spi_close_func()
+        return
+    end
+
+    -- 流程3:挂载文件系统
+    local mount_point = "/little_flash"
+    if not mount_filesystem(flash_device, mount_point) then
+        log.error("主流程", "文件系统挂载失败,终止")
+        spi_close_func()
+        return
+    end
+
+    -- 流程4:打印文件系统信息
+    print_filesystem_info(mount_point)
+
+    -- 流程5:执行文件操作测试
+    if not test_file_operations(mount_point) then
+        log.warn("主流程", "文件操作测试部分失败")
+    end
+
+    -- 6.关闭SPI设备
+    spi_close_func()
+end
+
+sys.taskInit(spinor_test_func)

+ 90 - 0
module/Air8101/demo/accessory_board/AirSPINORFLASH_1000/main.lua

@@ -0,0 +1,90 @@
+
+--[[
+@module  main
+@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑 
+@version 001.000.000
+@date    2025.9.05
+@author  马亚丹
+@usage
+本demo是演示合宙AirSPINORFLASH_1000配件版的功能使用,可通过以下三种方式驱动flash模块,任选一种即可。
+1. raw_spi
+通过原始spi接口对flash模块进行读写数据操作,详细逻辑请看raw_spi.lua 文件
+2. lf_fs
+通过littleFS文件系统,对flash模块以文件系统的方式进行读写数据操作,详细逻辑请看lf_fs.lua 文件
+3. sfud_test
+通过sfud核心库和io文件系统,对flash模块以文件系统的方式进行读写数据操作,详细逻辑请看sfud.lua 文件
+
+]]
+
+
+
+
+--[[
+必须定义PROJECT和VERSION变量,Luatools工具会用到这两个变量,远程升级功能也会用到这两个变量
+PROJECT:项目名,ascii string类型
+        可以随便定义,只要不使用,就行
+VERSION:项目版本号,ascii string类型
+        如果使用合宙iot.openluat.com进行远程升级,必须按照"XXX.YYY.ZZZ"三段格式定义:
+            X、Y、Z各表示1位数字,三个X表示的数字可以相同,也可以不同,同理三个Y和三个Z表示的数字也是可以相同,可以不同
+            因为历史原因,YYY这三位数字必须存在,但是没有任何用处,可以一直写为000
+        如果不使用合宙iot.openluat.com进行远程升级,根据自己项目的需求,自定义格式即可
+]]
+
+PROJECT = "AirSPINORFLASH_1000"
+VERSION = "001.000.000"
+
+
+-- 在日志中打印项目名和项目版本号
+log.info("main", PROJECT, VERSION)
+
+
+-- 如果内核固件支持wdt看门狗功能,此处对看门狗进行初始化和定时喂狗处理
+-- 如果脚本程序死循环卡死,就会无法及时喂狗,最终会自动重启
+if wdt then
+    --配置喂狗超时时间为9秒钟
+    wdt.init(9000)
+    --启动一个循环定时器,每隔3秒钟喂一次狗
+    sys.timerLoopStart(wdt.feed, 3000)
+end
+
+
+-- 如果内核固件支持errDump功能,此处进行配置,【强烈建议打开此处的注释】
+-- 因为此功能模块可以记录并且上传脚本在运行过程中出现的语法错误或者其他自定义的错误信息,可以初步分析一些设备运行异常的问题
+-- 以下代码是最基本的用法,更复杂的用法可以详细阅读API说明文档
+-- 启动errDump日志存储并且上传功能,600秒上传一次
+-- if errDump then
+--     errDump.config(true, 600)
+-- end
+
+
+-- 使用LuatOS开发的任何一个项目,都强烈建议使用远程升级FOTA功能
+-- 可以使用合宙的iot.openluat.com平台进行远程升级
+-- 也可以使用客户自己搭建的平台进行远程升级
+-- 远程升级的详细用法,可以参考fota的demo进行使用
+
+
+-- 启动一个循环定时器
+-- 每隔3秒钟打印一次总内存,实时的已使用内存,历史最高的已使用内存情况
+-- 方便分析内存使用是否有异常
+-- sys.timerLoopStart(function()
+--     log.info("mem.lua", rtos.meminfo())
+--     log.info("mem.sys", rtos.meminfo("sys"))
+-- end, 3000)
+
+
+
+-- 加载raw_spi功能模块
+require "raw_spi"
+
+-- 加载lf_fs功能模块
+-- require"lf_fs"
+
+-- 加载sfud_test功能模块
+-- require"sfud_test"
+
+
+
+-- 用户代码已结束---------------------------------------------
+-- 结尾总是这一句
+sys.run()
+-- sys.run()之后不要加任何语句!!!!!因为添加的任何语句都不会被执行

+ 267 - 0
module/Air8101/demo/accessory_board/AirSPINORFLASH_1000/raw_spi.lua

@@ -0,0 +1,267 @@
+--[[
+@module  raw_spi
+@summary raw_spi测试功能模块
+@version 1.0
+@date    2025.9.05
+@author  马亚丹
+@usage
+本demo演示的功能为:使用Air8101核心板通过SPI核心库原始接口实现对Flash的操作,演示读数据写数据、删除数据等操作。
+以 Air8101核心板为例, 接线如下:
+
+Air8101核心板    AirSPINORFLASH_1000配件版
+GND(任意)               GND
+3.3V                    VCC
+SPI0_CS/p44/GPIO22      CS
+SPI0_SCK/p28/GPIO14     CLK
+SPI0_MOSI/p57/GPIO16    MOSI
+SPI0_MISO/p55/GPIO17    MISO
+
+使用SPI0,硬件SPI CS接在gpio15上
+
+运行核心逻辑:
+1.初始化并启用spi,如果初始化失败,退出程序
+2.spi启用后读取并验证flash芯片ID,如果验证失败,退出程序
+3.验证flash芯片后读取寄存器状态,确认芯片就绪
+4.擦除扇区,为写入数据做准备
+5.擦除扇区后,写数据到扇区,并读取扇区数据与写入数据进行验证
+6.关闭写使能并关闭SPI。
+
+]]
+
+
+-- SPI配置参数
+local SPI_ID = 0                    -- SPI总线ID,根据实际情况修改
+local CS_PIN = 15                   -- CS引脚,根据实际情况修改
+local CPHA = 0                      -- 时钟相位
+local CPOL = 0                      -- 时钟极性
+local data_Width = 8                -- 数据宽度(位)
+local bandrate = 4 * 1000 * 1000    -- 波特率(Hz),初始化为4MHz,8101最低支持4M
+local timeout = 1000                -- 操作超时时间(ms)
+local cspin = gpio.setup(CS_PIN, 1) --CS脚置于高电平
+gpio.setup(13, 1)                   --air8101模组,gpio13控制ldo输出3.3v
+
+
+-- 1. 设置并启用 SPI
+local function spiDev_init_func()
+    log.info("raw_spi", "SPI_ID", SPI_ID, "CS_PIN", CS_PIN)
+    local spiDevice = spi.setup(SPI_ID, nil, CPHA, CPOL, data_Width, bandrate,
+        spi.MSB,    --高低位顺序    可选,默认高位在前
+        spi.master, --主模式     可选,默认主
+        spi.half    --半双工       nand只支持半双工
+    )
+
+    log.info("硬件spi", "初始化,波特率:", spiDevice, bandrate)
+    return true
+end
+
+
+-- 2. 定义功能函数:发送和接收数据
+local function spi_transfer_func(sendData, recvLen)
+    -- 选中设备
+    cspin(0)
+
+    if sendData then
+        -- 发送数据
+        spi.send(SPI_ID, sendData)
+    end
+
+    local recvData = ""
+    if recvLen and recvLen > 0 then
+        -- 接收数据
+        recvData = spi.recv(SPI_ID, recvLen)
+    end
+
+    -- 取消选中
+    cspin(1)
+    return recvData
+end
+
+-- 3. 定义功能函数:读取并验证芯片ID
+local function spi_readChipId_func()
+    --0x9F指令读取JEDEC ID
+    local id = spi_transfer_func(string.char(0x9F), 3)
+    --读取成功会返回 3 字节(制造商 + 设备 ID)
+    if #id == 3 then
+        local b1, b2, b3 = id:byte(1, 3)
+        log.info("spi", "芯片ID: 0x%02X 0x%02X 0x%02X", b1, b2, b3)
+
+        -- 验证是否为W25Q系列:
+        --制造商 ID(第 1 字节): 0xEF(代表 Winbond)
+        -- 设备类型(第 2 字节): 0x40(表示 W25Q 系列 NOR Flash)
+        -- 容量代码(第 3 字节): 0x18(对应 128Mbit = 16MB 容量)
+        if b1 == 0xEF and (b2 == 0x40 or b2 == 0x18) then
+            return true
+        end
+    end
+
+    log.error("spi", "读取芯片ID失败")
+    return false
+end
+
+-- 4. 定义功能函数:写数据使能
+local function spi_writeEnable_func()
+    --0x06指令设置Write Enable
+    spi_transfer_func(string.char(0x06))
+    return 0
+end
+
+-- 5. 定义功能函数:写数据禁用
+local function spi_writeDisable_func()
+    --0x04指令设置Write Disable
+    spi_transfer_func(string.char(0x04))
+end
+
+-- 6. 定义功能函数:读取状态寄存器
+local function spi_readStatus_func()
+    --0x05指令读取寄存器状态
+    local status = spi_transfer_func(string.char(0x05), 1)
+    if #status == 1 then
+        --返回0 表示WIP=0芯片就绪且未使能写操作
+        return status:byte(1)
+    end
+end
+
+
+--7.  等待写入完成
+local function spi_waitForWriteComplete_func()
+    while timeout > 0 do
+        local status = spi_readStatus_func()
+        -- WIP位为0表示写入完成
+        if bit.band(status, 0x01) == 0 then
+            return true
+        end
+        sys.wait(10)
+        timeout = timeout - 10
+    end
+
+    log.error("spi", "等待写入超时")
+    return false
+end
+
+--8. 扇区擦除,根据需要修改
+-- 0x20:扇区擦除(4KB)
+-- 0xD8:块擦除(64KB)
+-- 0xC7:整片擦除
+--address是要擦除的扇区的起始地址,本demo演示扇区擦除,块擦除和整片擦除可自行研究
+local function spi_erase_sector(address)
+    -- 使能写操作
+    if not spi_writeEnable_func() then
+        log.error("SPI", "写使能失败")
+        return false
+    end
+    -- 发送扇区擦除指令
+    local result = spi_transfer_func(string.char(0x20) ..
+        string.char(bit.rshift(address, 16) & 0xFF) ..
+        string.char(bit.rshift(address, 8) & 0xFF) ..
+        string.char(address & 0xFF))
+    if not result then
+        log.error("SPI", "发送扇区擦除指令失败")
+        return false
+    end
+    -- 等待写入完成
+    return spi_waitForWriteComplete_func()
+end
+
+--9. 页编程(写入数据到指定地址)
+local function spi_pageProgram_func(address, data)
+    -- 检查数据长度(不能超过256字节)
+    local len = #data
+    if len == 0 or len > 256 then
+        log.error("spi", "数据长度无效:", len)
+        return false
+    end
+    -- 准备写入命令和地址
+    local cmd = string.char(0x02) ..
+        string.char(bit.rshift(address, 16) & 0xFF) ..
+        string.char(bit.rshift(address, 8) & 0xFF) ..
+        string.char(address & 0xFF)
+    -- 写数据使能
+    spi_writeEnable_func()
+    -- 发送写命令和数据
+    spi_transfer_func(cmd .. data)
+    -- 等待写入完成
+    return spi_waitForWriteComplete_func()
+end
+
+-- 10. 读取数据
+local function spi_readData_func(address, length)
+    if length <= 0 then
+        return ""
+    end
+    -- 准备读取命令和地址
+    local cmd = string.char(0x03) ..
+        string.char(bit.rshift(address, 16) & 0xFF) ..
+        string.char(bit.rshift(address, 8) & 0xFF) ..
+        string.char(address & 0xFF)
+    -- 发送读取命令并接收数据
+    return spi_transfer_func(cmd, length)
+end
+
+
+-- 11. 关闭SPI设备,成功返回0
+local function spi_close_func()
+    log.info("关闭spi", spi.close(SPI_ID))
+end
+
+
+
+--12. 功能演示核心函数
+local function spi_test_func()
+    --1.判断SPI初始化
+    if not spiDev_init_func() then
+        return
+    end
+    --2.判断flash芯片ID
+    if not spi_readChipId_func() then
+        spi_close_func()
+        return
+    end
+
+    -- 3.读取寄存器状态
+    local status = spi_readStatus_func()
+    --返回状态0表示芯片就绪且未使能写操作
+    log.info("spi", "寄存器状态为: 0x%02X", status)
+
+    -- 4.擦除扇区(4KB)
+    --0x000000是要擦除的扇区的起始地址,即第一个存储单元的位置,
+    --擦除从地址0x000000开始的整个 4KB 扇区,该扇区包含地址0x000000到0x000FFF的所有存储单元,可按需修改
+    log.info("spi", "擦除扇区 0x000000...")
+    if not spi_erase_sector(0x000000) then
+        log.error("spi", "擦除失败")
+        spi_close_func() --
+        return
+    end
+
+    -- 5.读取擦除后的数据(应为0xFF)
+    local erasedData = spi_readData_func(0x000000, 16)
+    log.info("spi", "擦除后数据:", string.toHex(erasedData))
+
+    -- 测试数据
+    local testData = "Hello, SPI Flash! "
+
+    -- 6.写入数据到地址0x000000
+    log.info("spi", "写入数据:", testData)
+    if spi_pageProgram_func(0x000000, testData) then
+        -- 读取数据
+        log.info("spi", "正在验证数据...")
+        local readData = spi_readData_func(0x000000, #testData)
+
+        -- 验证数据
+        if readData == testData then
+            log.info("spi", "数据验证成功!,读取到数据为:" .. readData)
+        else
+            log.error("spi", "数据验证失败!")
+            log.info("spi", "预期读到的数据是:", testData)
+            log.info("spi", "实际读取的数据是:", string.toHex(readData))
+        end
+    else
+        log.error("spi", "写入操作失败")
+    end
+    -- 7.禁用写操作
+    spi_writeDisable_func()
+    -- 8.关闭SPI设备
+    spi_close_func()
+end
+
+
+sys.taskInit(spi_test_func)

+ 186 - 0
module/Air8101/demo/accessory_board/AirSPINORFLASH_1000/readme.md

@@ -0,0 +1,186 @@
+## 功能模块介绍:
+
+1. main.lua:主程序入口,以下三个脚本按自己的需求选择其一使用即可,另外两个注释。
+
+2. raw_spi:通过原始spi接口对flash模块进行读写数据操作,详细逻辑请看raw_spi.lua 文件
+
+3. lf_fs:通过littleFS文件系统,对flash模块以文件系统的方式进行读写数据操作,详细逻辑请看lf_fs.lua 文件
+
+4. sfud_test:通过sfud核心库和io文件系统,对flash模块以文件系统的方式进行读写数据操作,详细逻辑请看sfud_test.lua 文件
+
+## 演示功能概述:
+
+### raw_spi:
+
+1.初始化并启用 spi,如果初始化失败,退出程序
+
+2.spi 启用后读取并验证 flash 芯片 ID,如果验证失败,退出程序
+
+3.验证 flash 芯片后读取寄存器状态,确认芯片就绪
+
+4.擦除扇区,为写入数据做准备
+
+5.擦除扇区后,写数据到扇区,并读取扇区数据与写入数据进行验证
+
+6.关闭写使能并关闭 SPI。
+
+### lf_fs:
+
+1.以对象的方式配置参数,初始化启用 SPI,返回 SPI 对象
+
+2.用 SPI 对象初始化 flash 设备,返回 flash 设备对象
+
+3.用 lf 库挂载 flash 设备对象为LittleFS文件系统
+
+4.读取文件系统的信息
+
+5.操作文件读写,并验证写入一致性,追加文件等。
+
+### sfud_test.lua:
+
+1.以对象的方式配置参数,初始化启用SPI,返回SPI对象
+
+2.用SPI对象初始化sfud,
+
+3.用sfud库挂载flash设备为FatFS文件系统
+
+4.读取文件系统的信息
+
+5.操作文件读写,擦除,并验证写入一致性,追加文件等。
+
+## 演示硬件环境:
+
+![](https://docs.openluat.com/accessory/AirSPINORFLASH_1000/image/8101.jpg)
+
+![](https://docs.openluat.com/accessory/AirSPINORFLASH_1000/image/norflash.jpg)
+
+1. 合宙 Air8101 核心板一块
+
+2. 合宙 AirSPINORFLASH_1000 一块
+
+3. TYPE-C USB 数据线一根 ,Air8101 核心板和数据线的硬件接线方式为:
+- Air8101核心板通过 TYPE-C USB 口供电;(背面usb开关拨到off)
+
+- TYPE-C USB 数据线直接插到开发板的 TYPE-C USB 座子,另外一端连接电脑 USB 口;
+4. 杜邦线 6 根
+
+   Air8101 核心板与 AirSPINORFLASH_1000 按以下方式接线:
+
+<table>
+<tr>
+<td>  Air8101核心板<br/></td><td>AirSPINORFLASH_1000配件版<br/></td></tr>
+<tr>
+<td>GND(任意)          <br/></td><td>GND<br/></td></tr>
+<tr>
+<td>3.3V<br/></td><td>VCC<br/></td></tr>
+<tr>
+<td>SPI0_CS/p54/GPIO15<br/></td><td>CS<br/></td></tr>
+<tr>
+<td>SPI0_SCK/p28/GPIO14<br/></td><td>SCK<br/></td></tr>
+<tr>
+<td>SPI0_MOSI/p57/GPIO16<br/></td><td>MOSI<br/></td></tr>
+<tr>
+<td>SPI0_MISO/p55/GPIO17<br/></td><td>MISO<br/></td></tr>
+</table>
+
+## 演示软件环境:
+
+1. Luatools 下载调试工具
+
+2. 固件版本:LuatOS-SoC_V1006_Air8101_1.soc,固件地址,如有最新固件请用最新 [https://docs.openluat.com/air8101/luatos/firmware/version/](https://docs.openluat.com/air8101/luatos/firmware/)
+
+3. pc 系统 win11(win10 及以上)
+
+## 演示核心步骤:
+
+1. 搭建好硬件环境
+2. main.lua 中加载需要用的功能模块,三个功能模块同时只能选择一个使用,另两个注释。
+3. Luatools 烧录内核固件和修改后的 demo 脚本代码
+4. 烧录成功后,代码会自动运行,查看打印日志,如果正常运行,会打印相关信息,spi 初始化,数据读写,文件操作等。
+5. raw_spi.lua 如下 log 显示:
+
+```bash
+[2025-11-19 14:47:55.490] luat:U(253):I/user.main AirSPINORFLASH_1000 001.000.000
+[2025-11-19 14:47:55.490] luat:U(258):I/user.raw_spi SPI_ID 0 CS_PIN 15
+[2025-11-19 14:47:55.490] luat:I(259):spi:spi初始化为主机模式 id=0
+[2025-11-19 14:47:55.490] luat:D(259):spi:半双工模式 0 0
+[2025-11-19 14:47:55.490] luat:D(259):spi:SPI(0) gpio init : wire 3 clk 14 cs 15 mosi 16 miso 17
+[2025-11-19 14:47:55.490] luat:U(260):I/user.硬件spi 初始化,波特率: 0 4000000
+[2025-11-19 14:47:55.490] luat:U(260):I/user.spi 芯片ID: 0x%02X 0x%02X 0x%02X 239 64 23
+[2025-11-19 14:47:55.490] luat:U(261):I/user.spi 寄存器状态为: 0x%02X 0
+[2025-11-19 14:47:55.490] luat:U(261):I/user.spi 擦除扇区 0x000000...
+[2025-11-19 14:47:55.577] luat:U(330):I/user.spi 擦除后数据: FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF 32
+[2025-11-19 14:47:55.577] luat:U(330):I/user.spi 写入数据: Hello, SPI Flash! 
+[2025-11-19 14:47:55.577] luat:U(338):I/user.spi 正在验证数据... 18
+[2025-11-19 14:47:55.577] luat:U(339):I/user.spi 数据验证成功!,读取到数据为:Hello, SPI Flash! 
+[2025-11-19 14:47:55.589] luat:U(339):I/user.关闭spi 0
+
+
+
+
+```
+
+6. lf_fs.lua 如下 log 显示:
+
+```bash
+[2025-11-19 14:45:07.840] luat:U(254):I/user.main AirSPINORFLASH_1000 001.000.000
+[2025-11-19 14:45:07.854] luat:U(259):I/user.lf_fs SPI_ID 0 CS_PIN 15
+[2025-11-19 14:45:07.854] luat:D(259):spi:SPI(0) gpio init : wire 3 clk 14 cs 15 mosi 16 miso 17
+[2025-11-19 14:45:07.854] luat:U(260):I/user.硬件spi 初始化,波特率: SPI*: 60C7E6D0 4000000
+[2025-11-19 14:45:07.854] luat:U(260):I/user.SPI初始化 成功,波特率: 4000000
+[2025-11-19 14:45:07.854] luat:U(261):I/user.Flash初始化 开始
+[2025-11-19 14:45:07.854] luat:I(261):little_flash:Welcome to use little flash V0.0.1 .
+[2025-11-19 14:45:07.854] luat:I(261):little_flash:Github Repositories https://github.com/Dozingfiretruck/little_flash .
+[2025-11-19 14:45:07.854] luat:I(261):little_flash:Gitee Repositories https://gitee.com/Dozingfiretruck/little_flash .
+[2025-11-19 14:45:07.854] luat:I(261):little_flash:Found SFDP Header. The Revision is V1.5, NPN is 0, Access Protocol is 0xFF.
+[2025-11-19 14:45:07.854] luat:I(262):little_flash:Parameter Header is OK. The Parameter ID is 0xFF00, Revision is V5.1, Length is 16,Parameter Table Pointer is 0x000080.
+[2025-11-19 14:45:07.854] luat:I(262):little_flash:Found a flash chip. Size is 8388608 bytes.
+[2025-11-19 14:45:07.948] luat:U(361):I/user.Flash初始化 成功,设备: userdata: 60C8B6B8
+[2025-11-19 14:45:07.948] luat:U(361):I/user.文件系统 开始挂载: /little_flash
+[2025-11-19 14:45:07.961] luat:D(384):little_flash:lfs_mount 0
+[2025-11-19 14:45:07.961] luat:D(384):little_flash:vfs mount /little_flash ret 0
+[2025-11-19 14:45:07.961] luat:U(385):I/user.文件系统 挂载成功: /little_flash
+[2025-11-19 14:45:07.961] luat:U(385):I/user.文件系统信息 开始查询: /little_flash
+[2025-11-19 14:45:07.978] luat:U(405):I/user.  总block数: 2048
+[2025-11-19 14:45:07.978] luat:U(405):I/user.  已用block数: 2
+[2025-11-19 14:45:07.978] luat:U(405):I/user.  block大小: 4096 字节
+[2025-11-19 14:45:07.978] luat:U(405):I/user.  文件系统类型: lfs
+[2025-11-19 14:45:07.978] luat:U(406):I/user.文件操作测试 开始
+[2025-11-19 14:45:08.001] luat:U(419):I/user.  写入成功 /little_flash/test.txt 内容: 当前时间: Sat Jan  1 00:00:00 2000
+[2025-11-19 14:45:08.001] luat:U(430):I/user.  读取成功 /little_flash/test.txt 内容: 当前时间: Sat Jan  1 00:00:00 2000
+[2025-11-19 14:45:08.088] luat:U(490):I/user.  追加后内容: LuatOS 测试 - 追加时间: Sat Jan  1 00:00:00 2000
+[2025-11-19 14:45:08.088] luat:U(491):I/user.文件操作测试 完成
+[2025-11-19 14:45:08.088] luat:U(491):I/user.关闭spi true
+
+
+```
+
+7. sfud_test.lua 如下 log 显示:
+
+```bash
+[2025-11-19 14:39:12.075] luat:I(205):sfud:LuatOS-sfud flash device initialized successfully.
+[2025-11-19 14:39:12.082] luat:U(205):I/user.获取flash设备信息表: userdata: 280032B4
+[2025-11-19 14:39:12.082] luat:U(205):I/user.获取 Flash 容量和page大小: 8388608 4096
+[2025-11-19 14:39:12.169] luat:U(285):I/user.擦除一个块的数据: 0
+[2025-11-19 14:39:12.169] luat:U(287):I/user.写入数据: 0
+[2025-11-19 14:39:12.178] luat:U(299):I/user.读取数据: luat:U(299):testdatluat:U(300):
+
+[2025-11-19 14:39:12.262] luat:U(379):I/user.先擦除再写入数据: 0
+[2025-11-19 14:39:12.262] luat:U(379):I/user.文件系统 开始挂载: /sfud_flash
+[2025-11-19 14:39:12.307] luat:D(432):sfud:lfs_mount 0
+[2025-11-19 14:39:12.307] luat:D(432):sfud:vfs mount /sfud_flash ret 0
+[2025-11-19 14:39:12.307] luat:U(432):I/user.文件系统 挂载成功: /sfud_flash
+[2025-11-19 14:39:12.307] luat:U(433):I/user.文件系统信息 开始查询: /sfud_flash
+[2025-11-19 14:39:12.354] luat:U(478):I/user.  总block数: 2048
+[2025-11-19 14:39:12.354] luat:U(478):I/user.  已用block数: 2
+[2025-11-19 14:39:12.354] luat:U(479):I/user.  block大小: 4096 字节
+[2025-11-19 14:39:12.354] luat:U(479):I/user.  文件系统类型: lfs
+[2025-11-19 14:39:12.354] luat:U(479):I/user.文件操作测试 开始
+[2025-11-19 14:39:12.365] luat:U(508):I/user.  写入成功 /sfud_flash/test.txt 内容: 当前时间: Sat Jan  1 00:00:00 2000
+[2025-11-19 14:39:12.416] luat:U(536):I/user.  读取成功 /sfud_flash/test.txt 内容: 当前时间: Sat Jan  1 00:00:00 2000
+[2025-11-19 14:39:12.571] luat:U(693):I/user.  追加后内容: LuatOS 测试 - 追加时间: Sat Jan  1 00:00:00 2000
+[2025-11-19 14:39:12.571] luat:U(693):I/user.文件操作测试 完成
+[2025-11-19 14:39:12.571] luat:U(693):I/user.关闭spi true
+
+
+```

+ 221 - 0
module/Air8101/demo/accessory_board/AirSPINORFLASH_1000/sfud_test.lua

@@ -0,0 +1,221 @@
+--[[
+0@module  sfud_test
+@summary sfud_test测试功能模块
+@version 1.0
+@date    2025.10.11
+@author  马亚丹
+@usage
+本demo演示的功能为:使用Air8101核心板通过SPI核心库/sfud核心库实现对 NOR Flash的操作,演示读数据写数据、删除数据等操作。
+以Air8101核心板为例, 接线如下:
+
+Air8101核心板    AirSPINORFLASH_1000配件版
+GND(任意)               GND
+3.3V                    VCC
+SPI0_CS/p44/GPIO22      CS
+SPI0_SCK/p28/GPIO14     CLK
+SPI0_MOSI/p57/GPIO16    MOSI
+SPI0_MISO/p55/GPIO17    MISO
+
+使用SPI0,硬件SPI CS接在gpio15上
+
+运行核心逻辑:
+1.以对象的方式配置参数,初始化启用SPI,返回SPI对象
+2.用SPI对象初始化sfud,
+3.用sfud库挂载flash设备为FatFS文件系统
+4.读取文件系统的信息
+5.操作文件读写,擦除,并验证写入一致性,追加文件等。
+
+]]
+
+-- SPI配置参数
+local SPI_ID = 0        -- SPI总线ID,根据实际情况修改
+local CS_PIN = 15       -- CS引脚,根据实际情况修改
+local CPHA = 0          -- 时钟相位
+local CPOL = 0          -- 时钟极性
+local data_Width = 8    -- 数据宽度(位)
+local bandrate = 4*1000*1000 -- 波特率(Hz),初始化为4MHz,8101最低支持4M
+gpio.setup(13, 1)        --air8101模组,gpio13控制ldo输出3.3v
+-- flash操作起始地址(示例值,需根据需求调整)
+local erase_addr = 4096 
+-- 擦除数据的大小(示例值,需匹配 Flash block 大小)
+local erase_size = 4096   
+--需要操作的数据(示例值,需根据需求调整)
+local data = "testdata"
+
+-- 1. 以对象方式设置并启用 SPI,返回设备对象
+local function spiDev_init_func()
+    log.info("sfud", "SPI_ID", SPI_ID, "CS_PIN", CS_PIN)
+
+    --以对象的方式初始化spi,高位在前,主模式,半双工模式
+     --spi  flash只支持半双工模式
+    spi_device = spi.deviceSetup(SPI_ID, CS_PIN, CPHA, CPOL, data_Width, bandrate, spi.MSB, 1, 0)    
+    log.info("硬件spi", "初始化,波特率:", spi_device, bandrate)
+    if not spi_device then
+        log.error("SPI初始化", "失败")
+        return nil
+    end
+    log.info("SPI初始化", "成功,波特率:",bandrate)
+    return spi_device
+end
+
+
+-- 2. 初始化Flash设备,返回设备对象
+local function init_sfud_device(spi_device)
+    log.info("sfud初始化", "开始")
+    local sfud_flash_device = sfud.init(spi_device)
+    if not sfud_flash_device then
+        log.error("Flash初始化", "失败")        
+    else 
+        return true
+    end
+    
+end
+
+-- 3. 挂载文件系统
+local function mount_filesystem(sfud_device, mount_point)
+    log.info("文件系统", "开始挂载:", mount_point)
+
+    -- 检查是否支持挂载功能
+    if not sfud.mount then
+        log.error("文件系统", "不支持挂载功能")
+        return false
+    end
+
+    -- 尝试挂载
+    local mount_ok = sfud.mount(sfud_device, mount_point)
+    if not mount_ok then
+        log.warn("文件系统", "挂载失败,尝试重新挂载...")
+        mount_ok = sfud.mount(sfud_device, mount_point)
+        if not mount_ok then
+            log.error("文件系统", "仍挂载失败")
+            return false
+        end
+    end
+
+    log.info("文件系统", "挂载成功:", mount_point)
+    return true
+end
+
+-- 4. 打印文件系统信息
+local function print_filesystem_info(mount_point)
+    log.info("文件系统信息", "开始查询:", mount_point)
+
+    -- 获取文件系统详细信息,总块数/已用块数等
+    local ok, total_blocks, used_blocks, block_size, fs_type = io.fsstat(mount_point)
+    if ok then
+        log.info("  总block数:", total_blocks)
+        log.info("  已用block数:", used_blocks)
+        log.info("  block大小:", block_size, "字节")
+        log.info("  文件系统类型:", fs_type)
+    else
+        log.warn("  无法获取详细信息")
+    end
+end
+
+-- 5. 执行文件操作测试
+local function test_file_operations(mount_point)
+    log.info("文件操作测试", "开始")
+
+    -- 测试写入文件
+    local test_file = mount_point .. "/test.txt"
+    local f, err = io.open(test_file, "w")
+    if not f then
+        log.error("  写入失败", test_file, "错误:", err)
+        return false
+    end
+    local write_data = "当前时间: " .. os.date()
+    f:write(write_data)
+    f:close()
+    log.info("  写入成功", test_file, "内容:", write_data)
+
+    -- 测试读取文件
+    local read_data, read_err = io.readFile(test_file)
+    if not read_data then
+        log.error("  读取失败", test_file, "错误:", read_err)
+        return false
+    end
+    log.info("  读取成功", test_file, "内容:", read_data)
+
+    -- 验证内容一致性
+    if read_data ~= write_data then
+        log.warn("  内容不一致", "写入:", write_data, "读取:", read_data)
+    end
+
+    -- 测试文件追加
+    local append_file = mount_point .. "/append.txt"
+    os.remove(append_file) -- 清除旧文件
+    io.writeFile(append_file, "LuatOS 测试") -- 初始写入
+
+    local f_append, append_err = io.open(append_file, "a+")
+    if not f_append then
+        log.error("  追加失败", append_file, "错误:", append_err)
+        return false
+    end
+    local append_data = " - 追加时间: " .. os.date()
+    f_append:write(append_data)
+    -- 执行完操作后,一定要关掉文件
+    f_append:close()
+
+    local final_data = io.readFile(append_file)
+    log.info("  追加后内容:", final_data)
+
+    log.info("文件操作测试", "完成")
+
+    return true
+end
+
+-- 7. 关闭SPI设备,成功返回0
+local function spi_close_func()    
+    log.info("关闭spi", spi_device:close())
+end
+
+-- 主任务函数:按流程调用各功能函数
+local function spinor_test_func()
+    --1.判断SPI初始化  
+    spi_device = spiDev_init_func()
+    if not spi_device then
+        log.error("主流程", "SPI初始化失败,终止")
+        spi_close_func()
+        return
+    end
+
+    -- 流程2:初始化sfud设备
+    local sfud_init = init_sfud_device(spi_device)
+    if not sfud_init then
+        log.error("主流程", "sfud 初始化失败,终止")
+        spi_close_func()
+        return
+    end
+
+    -- 流程3:获取Flash设备,并进行数据擦除、读写操作
+    local sfud_device = sfud.getDeviceTable()    
+    log.info("获取flash设备信息表:", sfud_device)
+    log.info("获取 Flash 容量和page大小:", sfud.getInfo(sfud_device))
+    log.info("擦除一个块的数据:", sfud.erase(sfud_device, erase_addr, erase_size))   
+    log.info("写入数据:", sfud.write(sfud_device, erase_addr, data))    
+    log.info("读取数据:", sfud.read(sfud_device, erase_addr, erase_size ))    
+    log.info("先擦除再写入数据:", sfud.eraseWrite(sfud_device, erase_addr, data))
+    --sys.wait (1000)
+    
+
+    -- 流程4:挂载flash为文件系统
+    local mount_point = "/sfud_flash"
+    if not mount_filesystem(sfud_device, mount_point) then
+        log.error("主流程", "文件系统挂载失败,终止")
+        spi_close_func()
+        return
+    end
+
+    -- 流程5:打印文件系统信息
+    print_filesystem_info(mount_point)
+
+    -- 流程6:执行文件操作测试
+    if not test_file_operations(mount_point) then
+        log.warn("主流程", "文件操作测试部分失败")
+    end  
+ 
+     -- 流程7:关闭SPI设备
+    spi_close_func()
+end
+
+sys.taskInit(spinor_test_func)