瀏覽代碼

add:增加780系列和8000系列的AirMICROSD_1000的demo

wangpenglin 5 月之前
父節點
當前提交
51e6001674

+ 0 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirMICROSD_1010/.keep


+ 81 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirMICROSD_1010/http_download_file.lua

@@ -0,0 +1,81 @@
+--[[
+@module http_download_file
+@summary http下载文件模块
+@version 1.0.0
+@date    2025.08.25
+@author  王棚嶙
+@usage
+本文件演示的功能为通过http下载文件进入TF卡中:
+1. 网络就绪检测
+2. 创建HTTP下载任务并等待完成
+3. 记录下载结果
+4. 获取并记录文件大小
+本文件没有对外接口,直接在main.lua中require "http_download_file"即可
+]] 
+
+
+local function http_download_file_task()
+
+    -- 阶段1: 网络就绪检测
+
+    while not socket.adapter(socket.dft()) do
+        log.warn("HTTP下载", "等待网络连接", socket.dft())
+        -- 等待IP_READY消息,超时设为1秒
+        sys.waitUntil("IP_READY", 1000)
+    end
+
+    -- 检测到了IP_READY消息
+    log.info("HTTP下载", "网络已就绪", socket.dft())
+
+    -- 在Air780EHM/EHV/EGH核心板上TF卡的的pin_cs为gpio8,spi_id为0.请根据实际硬件修改
+    spi_id, pin_cs = 0, 8
+    spi.setup(spi_id, nil, 0, 0, 400 * 1000)
+    -- 初始化后拉高pin_cs,准备开始挂载TF卡
+    gpio.setup(pin_cs, 1)
+
+
+    -- 挂载文件系统
+    local mount_ok = fatfs.mount(fatfs.SPI, "/sd", spi_id, pin_cs, 24 * 1000 * 1000)
+    if not mount_ok then
+        log.error("HTTP下载", "文件系统挂载失败")
+        fatfs.unmount("/sd")
+        spi.close(spi_id)
+        return
+    end
+
+    -- 阶段2: 执行下载任务
+    log.info("HTTP下载", "开始下载任务")
+
+    -- 核心下载操作开始 (支持http和https)
+    -- local code, headers, body = http.request("GET", "...", nil, nil, {dst = "/sd/1.mp3"}).wait()
+    -- 其中 "..."为url地址, 支持 http和https, 支持域名, 支持自定义端口。
+    local code, headers, body_size = http.request("GET",
+                                    "https://gitee.com/openLuat/LuatOS/raw/master/module/Air780EHM_Air780EHV_Air780EGH/demo/audio/1.mp3",
+                                    nil, nil, {dst = "/sd/1.mp3"}).wait()
+    -- 阶段3: 记录下载结果
+    log.info("HTTP下载", "下载完成", 
+        code==200 and "success" or "error", 
+        code, 
+        -- headers是下载的文件头信息
+        json.encode(headers or {}), 
+        -- body_size是下载的文件大小(字节数)
+        body_size) 
+        
+    if code == 200 then
+        -- 获取实际文件大小
+        local actual_size = io.fileSize("/sd/1.mp3")
+        log.info("HTTP下载", "文件大小验证", "预期:", body_size, "实际:", actual_size)
+        
+        if actual_size~= body_size then
+            log.error("HTTP下载", "文件大小不一致", "预期:", body_size, "实际:", actual_size)
+        end
+    end
+
+    -- 阶段4: 资源清理
+    fatfs.unmount("/sd")
+    spi.close(spi_id)
+    log.info("HTTP下载", "资源清理完成")
+end
+
+-- 创建下载任务
+sys.taskInit(http_download_file_task)

+ 90 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirMICROSD_1010/main.lua

@@ -0,0 +1,90 @@
+--[[
+@module  main
+@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑
+@version 001.000.000
+@date    2025.08.25
+@author  王棚嶙
+@usage
+本 Demo 完整覆盖了 TF 卡操作的核心到高级流程,HTTP下载功能包括:
+1. 基础操作:
+   - CH390 供电控制
+   - 看门狗守护机制 
+2. 挂载及文件操作:
+   - 文件系统挂载/卸载
+   - TF卡空间信息查询
+   - 文件创建/读写/追加
+   - 目录创建/删除
+   - 文件重命名/删除
+   - 文件存在性检查与大小获取
+3. 下载功能:
+   - 网络检测与HTTP文件下载
+更多说明参考本目录下的readme.md文件
+]]
+
+
+--[[
+必须定义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 = "tfcard"
+VERSION = "001.000.000"
+
+
+
+
+-- 在日志中打印项目名和项目版本号
+log.info("main", PROJECT, VERSION)
+
+
+--添加硬狗防止程序卡死
+if wdt then
+    wdt.init(9000)--初始化watchdog设置为9s
+    sys.timerLoopStart(wdt.feed, 3000)--3s喂一次狗
+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)
+
+
+
+
+--[[在加载以下两个功能时,建议分别打开进行测试,因为文件操作和http下载功能是异步操作。
+放到一个项目中,如果加载的时间点是随机的,就会出现tfcard_app在spi.setup和fatfs挂载文件系统之后,
+还没有释放资源,然后http_download_file又去重复spi.setup和fatfs挂载文件系统了,不符合正常的业务逻辑,用户在参考编程的时候也要注意。]]
+
+--加载tf卡测试应用模块
+require "tfcard_app"
+--加载HTTP下载存入TF卡功能演示模块
+--require "http_download_file"
+
+
+-- 用户代码已结束---------------------------------------------
+-- 结尾总是这一句
+sys.run()
+-- sys.run()之后后面不要加任何语句!!!!!

+ 52 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirMICROSD_1010/pins_Air780EHM.json

@@ -0,0 +1,52 @@
+{
+  "model": "Air780EHM",
+  "pins": [
+    [7, "PWR_KEY", ""],
+    [16, "GPIO27", ""],
+    [17, "UART1_RXD", ""],
+    [18, "UART1_TXD", ""],
+    [19, "GPIO22", ""],
+    [20, "PWM1", ""],
+    [22, "PWM0", ""],
+    [23, "ONEWIRE", ""],
+    [25, "CAN_TXD", ""],
+    [26, "PWM4", ""],
+    [28, "UART2_RXD", ""],
+    [29, "UART2_TXD", ""],
+    [30, "GPIO29", ""],
+    [31, "GPIO30", ""],
+    [32, "GPIO31", ""],
+    [33, "GPIO32", ""],
+    [38, "DBG_RXD", ""],
+    [39, "DBG_TXD", ""],
+    [49, "LCD_RST", ""],
+    [50, "LCD_SDA", ""],
+    [51, "LCD_RS", ""],
+    [52, "LCD_CS", ""],
+    [53, "LCD_CLK", ""],
+    [54, "CAM_MCLK", ""],
+    [55, "CAM_RX0", ""],
+    [56, "CAM_RX1", ""],
+    [57, "UART3_TXD", ""],
+    [58, "UART3_RXD", ""],
+    [61, "VBUS", ""],
+    [66, "I2C1_SDA", ""],
+    [67, "I2C1_SCL", ""],
+    [78, "GPIO28", ""],
+    [79, "USIM_DET", ""],
+    [80, "CAM_BCLK", ""],
+    [81, "CAM_CS", ""],
+    [82, "USB_BOOT", ""],
+    [83, "SPI0_CS", ""],
+    [84, "SPI0_MISO", ""],
+    [85, "SPI0_MOSI", ""],
+    [86, "SPI0_CLK", ""],
+    [97, "GPIO16", ""],
+    [99, "GPIO23", ""],
+    [100, "GPIO17", ""],
+    [101, "WAKEUP0", ""],
+    [102, "GPIO20", ""],
+    [106, "CAN_RXD", ""],
+    [107, "GPIO21", ""]
+  ]
+}

+ 190 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirMICROSD_1010/readme.md

@@ -0,0 +1,190 @@
+## **功能模块介绍**
+
+本demo演示了在嵌入式环境中对TF卡(SD卡)的完整操作流程,覆盖了从文件系统挂载到高级文件操作的完整功能链。项目分为两个核心模块:
+
+1、main.lua:主程序入口 <br> 
+2、tfcard_app.lua:TF卡基础应用模块,实现文件系统管理、文件操作和目录管理功能。<br> 
+3、http_download_file.lua:HTTP下载模块,实现网络检测与文件下载到TF卡的功能
+
+## **演示功能概述**
+
+### 1、主程序入口模块(main.lua)
+
+- 初始化项目信息和版本号
+- 初始化看门狗,并定时喂狗
+- 启动一个循环定时器,每隔3秒钟打印一次总内存,实时的已使用内存,历史最高的已使用内存情况方便分析内存使用是否有异常
+- 加载tfcard_app模块(通过require "tfcard_app")
+- 加载http_download_file模块(通过require "http_download_file")
+- 最后运行sys.run()。
+
+
+
+### 3、TF卡核心演示模块(tfcard_app.lua)
+
+#### 文件系统管理
+
+- SPI初始化与挂载:
+  - 配置SPI接口参数(频率400kHz)
+  - 挂载FAT32文件系统到`/sd`路径
+  - 自动格式化检测与处理
+- 空间信息获取:
+  - 实时查询TF卡可用空间
+  - 输出详细存储信息(总空间/剩余空间)
+#### 文件操作
+- 创建目录:io.mkdir("/sd/io_test")
+- 创建/写入文件: io.open("/sd/io_test/boottime", "wb")
+- 检查文件存在: io.exists(file_path)
+- 获取文件大小:io.fileSize(file_path)
+- 读取文件内容: io.open(file_path, "rb"):read("*a")
+- 启动计数文件: 记录设备启动次数
+- 文件追加: io.open(append_file, "a+")
+- 按行读取: file:read("*l")
+- 文件关闭: file:close()
+- 文件重命名: os.rename(old_path, new_path)
+- 列举目录: io.lsdir(dir_path)
+- 删除文件: os.remove(file_path)
+- 删除目录: io.rmdir(dir_path)
+
+#### 结果处理
+
+- 资源清理(卸载/SPI关闭)
+
+### 4、HTTP下载功能 (http_download_file.lua)
+
+#### 文件系统管理
+
+- SPI初始化与挂载
+
+#### 网络就绪检测
+
+- 1秒循环等待IP就绪
+- 网络故障处理机制
+
+#### 安全下载
+
+- HTTP下载
+
+
+#### 结果处理
+
+- 下载状态码解析
+- 自动文件大小验证
+- 资源清理(卸载/spi关闭)
+
+
+## 演示硬件环境(二选一)
+
+### 1、Air780EHM核心板演示环境
+
+1、Air780EHM核心板一块(Air780EHM/780EGH/780EHV三种模块的核心板接线方式相同,这里以Air780EHM为例)
+
+2、TYPE-C USB数据线一根
+
+3、AirMICROSD_1010模块一个和SD卡一张
+
+4、Air780EHM/780EGH/780EHV核心板和数据线的硬件接线方式为
+
+- Air780EHM核心板通过TYPE-C USB口供电;(核心板USB旁边的开关拨到on一端)
+
+- TYPE-C USB数据线直接插到核心板的TYPE-C USB座子,另外一端连接电脑USB口;
+
+5、Air780EHM核心板和AirMICROSD_1010模块接线方式
+
+|   Air780EHM     |    AirMICROSD_1010    |
+| --------------- | --------------------- |
+|  GND(任意)      |          GND          |
+|  VDD_EXT        |          3V3         |
+|  GPIO8/SPI0_CS  |        spi_cs       |
+|  SPI0_SLK       |        spi_clk,时钟       |
+|  SPI0_MOSI      |  spi_mosi,主机输出,从机输入|
+|  SPI0_MISO      |  spi_miso,主机输入,从机输出|
+
+
+## 演示软件环境
+
+1、Luatools下载调试工具: https://docs.openluat.com/air780epm/common/Luatools/
+
+2、内核固件版本:
+Air780EHM:https://docs.openluat.com/air780epm/luatos/firmware/version/
+Air780EGH:https://docs.openluat.com/air780egh/luatos/firmware/version/
+Air780EHV:https://docs.openluat.com/air780ehv/luatos/firmware/version/
+
+
+## 演示核心步骤
+1、搭建好硬件环境
+
+2、通过Luatools将demo与固件烧录到核心板或开发板中
+
+3、烧录好后,板子开机将会在Luatools上看到如下打印:
+
+```lua
+(1)TF卡初始化与挂载
+[2025-08-24 19:51:24.152][000000001.389] SPI_HWInit 552:spi1 speed 2000000,1994805,154
+[2025-08-24 19:51:24.213][000000002.390] D/fatfs init sdcard at spi=1 cs=20
+[2025-08-24 19:51:24.286][000000002.390] SPI_SetNewConfig 996:spi1 speed 400000,400000
+[2025-08-24 19:51:24.329][000000002.408] SPI_SetNewConfig 996:spi1 speed 24000000,25600000
+[2025-08-24 19:51:24.383][000000002.408] D/SPI_TF 卡容量 122138624KB
+[2025-08-24 19:51:24.430][000000002.408] D/SPI_TF sdcard init OK OCR:0xc0ff8000!
+[2025-08-24 19:51:24.477][000000002.412] I/user.fatfs.mount 挂载成功 0
+[2025-08-24 19:51:24.535][000000002.617] I/user.fatfs getfree {"free_sectors":244262144,"total_kb":122132480,"free_kb":122131072,"total_sectors":244264960}
+[2025-08-24 19:51:24.583][000000002.618] I/user.fs lsmount [{"fs":"ec7xx","path":""},{"fs":"inline","path":"\/lua\/"},{"fs":"ram","path":"\/ram\/"},{"fs":"luadb","path":"\/luadb\/"},{"fs":"fatfs","path":"\/sd"}]
+
+
+(2)文件操作演示
+[2025-08-24 19:51:24.685][000000002.619] I/user.文件操作 ===== 开始文件操作 =====
+[2025-08-24 19:51:25.145][000000003.032] I/user.io.mkdir 目录创建成功 路径:/sd/io_test
+[2025-08-24 19:51:25.231][000000003.043] I/user.文件创建 文件写入成功 路径:/sd/io_test/boottime
+[2025-08-24 19:51:25.297][000000003.046] I/user.io.exists 文件存在 路径:/sd/io_test/boottime
+[2025-08-24 19:51:25.376][000000003.049] I/user.io.fileSize 文件大小:41字节 路径:/sd/io_test/boottime
+[2025-08-24 19:51:25.467][000000003.052] I/user.文件读取 路径:/sd/io_test/boottime 内容:这是io库API文档示例的测试内容
+[2025-08-24 19:51:25.547][000000003.056] I/user.启动计数 文件内容: 这是io库API文档示例的测试内容 十六进制: E8BF99E698AF696FE5BA93415049E69687E6A1A3E7A4BAE4BE8BE79A84E6B58BE8AF95E58685E5AEB9 82
+[2025-08-24 19:51:25.616][000000003.056] I/user.启动计数 当前值: 0
+[2025-08-24 19:51:25.693][000000003.057] I/user.启动计数 更新值: 1
+[2025-08-24 19:51:25.736][000000003.068] I/user.文件写入 路径:/sd/io_test/boottime 内容: 1
+[2025-08-24 19:51:25.795][000000003.081] I/user.文件创建 路径:/sd/io_test/test_a 初始内容:ABC
+[2025-08-24 19:51:25.852][000000003.088] I/user.文件追加 路径:/sd/io_test/test_a 追加内容:def
+[2025-08-24 19:51:25.909][000000003.091] I/user.文件验证 路径:/sd/io_test/test_a 内容:ABCdef 结果: 成功
+[2025-08-24 19:51:25.954][000000003.102] I/user.文件创建 路径:/sd/io_test/testline 写入3行文本
+[2025-08-24 19:51:26.001][000000003.106] I/user.按行读取 路径:/sd/io_test/testline 第1行: abc
+[2025-08-24 19:51:26.048][000000003.106] I/user.按行读取 路径:/sd/io_test/testline 第2行: 123
+[2025-08-24 19:51:26.093][000000003.107] I/user.按行读取 路径:/sd/io_test/testline 第3行: wendal
+[2025-08-24 19:51:26.140][000000003.112] I/user.os.rename 文件重命名成功 原路径:/sd/io_test/test_a 新路径:/sd/io_test/renamed_file.txt
+[2025-08-24 19:51:26.188][000000003.116] D/fatfs f_open /io_test/test_a 4
+[2025-08-24 19:51:26.238][000000003.116] D/vfs fopen /sd/io_test/test_a r not found
+[2025-08-24 19:51:26.312][000000003.117] I/user.验证结果 重命名验证成功 新文件存在 原文件不存在
+[2025-08-24 19:51:26.367][000000003.117] I/user.目录操作 ===== 开始目录列举 =====
+[2025-08-24 19:51:26.424][000000003.121] I/user.fs lsdir [{"name":"boottime","size":0,"type":0},{"name":"testline","size":0,"type":0},{"name":"renamed_file.txt","size":0,"type":0}]
+[2025-08-24 19:51:26.478][000000003.127] I/user.os.remove 文件删除成功 路径:/sd/io_test/renamed_file.txt
+[2025-08-24 19:51:26.539][000000003.129] D/fatfs f_open /io_test/renamed_file.txt 4
+[2025-08-24 19:51:26.593][000000003.130] D/vfs fopen /sd/io_test/renamed_file.txt r not found
+[2025-08-24 19:51:26.656][000000003.130] I/user.验证结果 renamed_file.txt文件删除验证成功
+[2025-08-24 19:51:26.734][000000003.137] I/user.os.remove testline文件删除成功 路径:/sd/io_test/testline
+[2025-08-24 19:51:26.856][000000003.139] D/fatfs f_open /io_test/testline 4
+[2025-08-24 19:51:26.922][000000003.140] D/vfs fopen /sd/io_test/testline r not found
+[2025-08-24 19:51:27.113][000000003.140] I/user.验证结果 testline文件删除验证成功
+[2025-08-24 19:51:27.197][000000003.147] I/user.os.remove 文件删除成功 路径:/sd/io_test/boottime
+[2025-08-24 19:51:27.251][000000003.149] D/fatfs f_open /io_test/boottime 4
+[2025-08-24 19:51:27.302][000000003.150] D/vfs fopen /sd/io_test/boottime r not found
+[2025-08-24 19:51:27.365][000000003.150] I/user.验证结果 boottime文件删除验证成功
+[2025-08-24 19:51:27.407][000000003.158] I/user.io.rmdir 目录删除成功 路径:/sd/io_test
+[2025-08-24 19:51:27.461][000000003.159] D/fatfs f_open /io_test 4
+[2025-08-24 19:51:27.536][000000003.159] D/vfs fopen /sd/io_test r not found
+[2025-08-24 19:51:27.610][000000003.159] I/user.验证结果 目录删除验证成功
+[2025-08-24 19:51:27.668][000000003.160] I/user.文件操作 ===== 文件操作完成 =====
+[2025-08-24 19:51:27.712][000000003.160] I/user.系统清理 开始执行关闭操作...
+[2025-08-24 19:51:27.772][000000003.160] I/user.文件系统 卸载成功
+[2025-08-24 19:51:27.867][000000003.160] I/user.SPI接口 已关闭
+
+
+(3)网络连接与HTTP下载
+[2025-08-24 20:31:49.405][000000006.268] I/user.HTTP下载 开始下载任务
+[2025-08-24 20:31:49.438][000000006.275] dns_run 674:gitee.com state 0 id 1 ipv6 0 use dns server2, try 0
+[2025-08-24 20:31:49.471][000000006.277] D/mobile TIME_SYNC 0
+[2025-08-24 20:31:49.503][000000006.297] dns_run 691:dns all done ,now stop
+[2025-08-24 20:31:54.800][000000012.080] I/user.HTTP下载 下载完成 success 200 
+[2025-08-24 20:31:54.872][000000012.080] {"Age":"0","Cache-Control":"public, max-age=60","Via":"1.1 varnish","Transfer-Encoding":"chunked","Date":"Sun, 24 Aug 2025 12:31:49 GMT","Access-Control-Allow-Credentials":"true","Vary":"Accept-Encoding","X-Served-By":"cache-ffe9","X-Gitee-Server":"http-pilot 1.9.21","Connection":"keep-alive","Server":"ADAS\/1.0.214","Access-Control-Allow-Headers":"Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,User-Agent,X-Requested-With,X-CustomHeader,Content-Range,Range,Set-Language","Content-Security-Policy":"default-src 'none'; style-src 'unsafe-inline'; sandbox","X-Request-Id":"1f7e4b55-53c8-440a-9806-8894aa823f50","Accept-Ranges":"bytes","Etag":"W\/\"6ea36a6c51a48eaba0ffbc01d409424e7627bc56\"","Content-Type":"text\/plain; charset=utf-8","Access-Control-Allow-Methods":"GET, POST, PUT, PATCH, DELETE, OPTIONS","X-Frame-Options":"DENY","X-Cache":"MISS","Set-Cookie":"BEC=1f1759df3ccd099821dcf0da6feb0357;Path=\/;Max-Age=126000"}
+[2025-08-24 20:31:54.910][000000012.080]  411922
+[2025-08-24 20:31:54.936][000000012.082] I/user.HTTP下载 文件大小验证 预期: 411922 实际: 411922
+[2025-08-24 20:31:54.979][000000012.083] I/user.HTTP下载 资源清理完成
+
+```

+ 333 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirMICROSD_1010/tfcard_app.lua

@@ -0,0 +1,333 @@
+--[[
+@module  tfcard_app
+@summary TF卡文件操作测试模块
+@version 1.0.0
+@date    2025.08.25
+@author  王棚嶙
+@usage
+本文件为TF卡的文件操作测试流程:
+1. 创建目录
+2. 创建并写入文件
+3. 检查文件是否存在
+4. 获取文件大小
+5. 读取文件内容
+6. 启动计数文件操作
+7. 文件追加测试
+8. 按行读取测试
+9. 读取后关闭文件
+10. 文件重命名
+11. 列举目录内容
+12. 删除文件
+13. 删除目录
+本文件没有对外接口,直接在main.lua中require "tfcard_app"就可以加载运行
+]] 
+
+
+
+
+
+local function tfcard_main_task() -- 开始进行主测试流程。
+    -- ##########  SPI初始化 ##########
+    -- 在Air780EHM/EHV/EGH核心板上TF卡的的pin_cs为gpio8,spi_id为0.请根据实际硬件修改
+    spi_id, pin_cs = 0, 8
+    spi.setup(spi_id, nil, 0, 0, 400 * 1000)
+    --初始化后拉高pin_cs,准备开始挂载TF卡
+    gpio.setup(pin_cs, 1)
+
+
+    -- ########## 开始进行tf卡挂载 ##########
+    -- 挂载失败默认格式化,
+    -- 如无需格式化应改为fatfs.mount(fatfs.SPI, "/sd", spi_id, pin_cs, 24 * 1000 * 1000, nil, 1, false),
+    -- 一般是在测试硬件是否有问题的时候把格式化取消掉
+    mount_ok, mount_err = fatfs.mount(fatfs.SPI, "/sd", spi_id, pin_cs, 24 * 1000 * 1000)
+    if mount_ok then
+        log.info("fatfs.mount", "挂载成功", mount_err)
+    else
+        log.error("fatfs.mount", "挂载失败", mount_err)
+        goto resource_cleanup
+    end
+
+    -- ########## 获取SD卡的可用空间信息并打印。 ########## 
+    data, err = fatfs.getfree("/sd")
+    if data then
+        --打印SD卡的可用空间信息
+        log.info("fatfs", "getfree", json.encode(data))
+    else
+        --打印错误信息
+        log.info("fatfs", "getfree", "err", err)
+        goto resource_cleanup
+    end
+
+    -- 列出所有挂载点,如不需要,可注释掉。
+    data = io.lsmount()
+    log.info("fs", "lsmount", json.encode(data))
+
+    -- ########## 功能: 启用fatfs调试模式 ##########
+    -- fatfs.debug(1) -- 若挂载失败,可以尝试打开调试信息,查找原因.(设置调试模式)
+
+    -- 执行tfcard文件操作演示
+    log.info("文件操作", "===== 开始文件操作 =====")
+
+    dir_path = "/sd/io_test"
+
+    -- 1. 创建目录
+    if io.mkdir(dir_path) then
+        log.info("io.mkdir", "目录创建成功", "路径:" .. dir_path)
+    else
+        -- 检查是否目录已存在
+        if io.exists(dir_path) then
+            log.warn("io.mkdir", "目录已存在,跳过创建", "路径:" .. dir_path)
+        else
+            log.error("io.mkdir", "目录创建失败且目录不存在", "路径:" .. dir_path)
+            goto resource_cleanup
+        end
+    end
+
+    -- 2. 创建并写入文件
+    file_path = dir_path .. "/boottime"
+    file = io.open(file_path, "wb")
+    if file then
+        file:write("这是io库API文档示例的测试内容")
+        file:close()
+        --在LuatOS文件操作中,执行file:close()是必须且关键的操作,它用于关闭文件句柄,释放资源,并确保数据被正确写入磁盘。
+        -- 如果不执行file:close(),可能会导致数据丢失、文件损坏或其他不可预测的问题。
+        log.info("文件创建", "文件写入成功", "路径:" .. file_path)
+    else
+        log.error("文件创建", "文件创建失败", "路径:" .. file_path)
+        goto resource_cleanup
+    end
+
+    -- 3. 检查文件是否存在
+    if io.exists(file_path) then
+        log.info("io.exists", "文件存在", "路径:" .. file_path)
+    else
+        log.error("io.exists", "文件不存在", "路径:" .. file_path)
+        goto resource_cleanup
+    end
+
+    -- 4. 获取文件大小
+    file_size = io.fileSize(file_path)
+    if file_size then
+        log.info("io.fileSize", "文件大小:" .. file_size .. "字节", "路径:" .. file_path)
+    else
+        log.error("io.fileSize", "获取文件大小失败", "路径:" .. file_path)
+        goto resource_cleanup
+    end
+
+    -- 5. 读取文件内容
+    file = io.open(file_path, "rb")
+    if file then
+        content = file:read("*a")
+        log.info("文件读取", "路径:" .. file_path, "内容:" .. content)
+        file:close()
+    else
+        log.error("文件操作", "无法打开文件读取内容", "路径:" .. file_path)
+        goto resource_cleanup
+    end
+
+    -- 6. 启动计数文件操作
+    count = 0
+    --以只读模式打开文件
+    file = io.open(file_path, "rb")
+    if file then
+        data = file:read("*a")
+        log.info("启动计数", "文件内容:", data, "十六进制:", data:toHex())
+        count = tonumber(data) or 0
+        file:close()
+    else
+        log.warn("启动计数", "文件不存在或无法打开")
+
+    end
+
+    log.info("启动计数", "当前值:", count)
+    count=count + 1
+    log.info("启动计数", "更新值:", count)
+
+    file = io.open(file_path, "wb")
+    if file then
+        file:write(tostring(count))
+        file:close()
+        log.info("文件写入", "路径:" .. file_path, "内容:", count)
+    else
+        log.error("文件写入", "无法打开文件", "路径:" .. file_path)
+        goto resource_cleanup
+    end
+
+    -- 7. 文件追加测试
+    append_file = dir_path .. "/test_a"
+    -- 清理旧文件
+    os.remove(append_file)
+
+    -- 创建并写入初始内容
+    file = io.open(append_file, "wb")
+    if file then
+        file:write("ABC")
+        file:close()
+        log.info("文件创建", "路径:" .. append_file, "初始内容:ABC")
+    else
+        log.error("文件创建", "无法创建文件", "路径:" .. append_file)
+        goto resource_cleanup
+    end
+
+    -- 追加内容
+    file = io.open(append_file, "a+")
+    if file then
+        file:write("def")
+        file:close()
+        log.info("文件追加", "路径:" .. append_file, "追加内容:def")
+    else
+        log.error("文件追加", "无法打开文件进行追加", "路径:" .. append_file)
+        goto resource_cleanup
+
+    end
+
+    -- 验证追加结果
+    file = io.open(append_file, "r")
+    if file then
+        data = file:read("*a")
+        log.info("文件验证", "路径:" .. append_file, "内容:" .. data, "结果:",
+            data == "ABCdef" and "成功" or "失败")
+        file:close()
+    else
+        log.error("文件验证", "无法打开文件进行验证", "路径:" .. append_file)
+        goto resource_cleanup
+    end
+
+    -- 8. 按行读取测试
+    line_file = dir_path .. "/testline"
+    file = io.open(line_file, "w")
+    if file then
+        file:write("abc\n")
+        file:write("123\n")
+        file:write("wendal\n")
+        file:close()
+        log.info("文件创建", "路径:" .. line_file, "写入3行文本")
+    else
+        log.error("文件创建", "无法创建文件", "路径:" .. line_file)
+        goto resource_cleanup
+    end
+
+    -- 按行读取文件
+    file = io.open(line_file, "r")
+    if file then
+        log.info("按行读取", "路径:" .. line_file, "第1行:", file:read("*l"))
+        log.info("按行读取", "路径:" .. line_file, "第2行:", file:read("*l"))
+        log.info("按行读取", "路径:" .. line_file, "第3行:", file:read("*l"))
+        file:close()
+    else
+        log.error("按行读取", "无法打开文件", "路径:" .. line_file)
+        goto resource_cleanup
+    end
+
+    -- 9. 文件重命名
+    old_path = append_file
+    new_path = dir_path .. "/renamed_file.txt"
+    success, err = os.rename(old_path, new_path)
+    if success then
+        log.info("os.rename", "文件重命名成功", "原路径:" .. old_path, "新路径:" .. new_path)
+
+        -- 验证重命名结果
+        if io.exists(new_path) and not io.exists(old_path) then
+            log.info("验证结果", "重命名验证成功", "新文件存在", "原文件不存在")
+        else
+            log.error("验证结果", "重命名验证失败")
+        end
+    else
+        log.error("os.rename", "重命名失败", "错误:" .. tostring(err), "原路径:" .. old_path)
+        goto resource_cleanup
+    end
+
+    -- 10. 列举目录内容
+    log.info("目录操作", "===== 开始目录列举 =====")
+
+    ret, data = io.lsdir(dir_path, 50, 0) -- 50表示最多返回50个文件,0表示从目录开头开始
+    if ret then
+        log.info("fs", "lsdir", json.encode(data))
+    else
+        log.info("fs", "lsdir", "fail", ret, data)
+        goto resource_cleanup
+    end
+
+    -- 11. 删除文件测试
+    -- 测试删除renamed_file.txt文件
+    if os.remove(new_path) then
+        log.info("os.remove", "文件删除成功", "路径:" .. new_path)
+
+        -- 验证renamed_file.txt删除结果
+        if not io.exists(new_path) then
+            log.info("验证结果", "renamed_file.txt文件删除验证成功")
+        else
+            log.error("验证结果", "renamed_file.txt文件删除验证失败")
+        end
+    else
+        log.error("io.remove", "renamed_file.txt文件删除失败", "路径:" .. new_path)
+        goto resource_cleanup
+    end
+
+    -- 测试删除testline文件
+    if os.remove(line_file) then
+        log.info("os.remove", "testline文件删除成功", "路径:" .. line_file)
+
+        -- 验证删除结果
+        if not io.exists(line_file) then
+            log.info("验证结果", "testline文件删除验证成功")
+        else
+            log.error("验证结果", "testline文件删除验证失败")
+        end
+    else
+        log.error("io.remove", "testline文件删除失败", "路径:" .. line_file)
+        goto resource_cleanup
+    end
+
+    if os.remove(file_path) then
+        log.info("os.remove", "文件删除成功", "路径:" .. file_path)
+
+        -- 验证删除结果
+        if not io.exists(file_path) then
+            log.info("验证结果", "boottime文件删除验证成功")
+        else
+            log.error("验证结果", "boottime文件删除验证失败")
+        end
+    else
+        log.error("io.remove", "boottime文件删除失败", "路径:" .. file_path)
+        goto resource_cleanup
+    end
+
+    -- 12. 删除目录(不能删除非空目录,所以在删除目录前要确保目录内没有文件或子目录)
+    if io.rmdir(dir_path) then
+        log.info("io.rmdir", "目录删除成功", "路径:" .. dir_path)
+
+        -- 验证删除结果
+        if not io.exists(dir_path) then
+            log.info("验证结果", "目录删除验证成功")
+        else
+            log.error("验证结果", "目录删除验证失败")
+        end
+    else
+        log.error("io.rmdir", "目录删除失败", "路径:" .. dir_path)
+        goto resource_cleanup
+    end
+
+    log.info("文件操作", "===== 文件操作完成 =====")
+
+    -- ########## 功能: 收尾功能演示##########
+    -- 卸载文件系统和关闭SPI
+    ::resource_cleanup::
+
+    log.info("结束", "开始执行关闭操作...")  
+    -- 如已挂载需先卸载文件系统,未挂载直接关闭SPI
+    if mount_ok then
+        if fatfs.unmount("/sd") then
+            log.info("文件系统", "卸载成功")
+        else
+            log.error("文件系统", "卸载失败")
+        end
+    end
+
+    -- 2. 关闭SPI接口
+    spi.close(spi_id)
+    log.info("SPI接口", "已关闭")
+
+end
+
+sys.taskInit(tfcard_main_task)

+ 16 - 17
module/Air780EHM_Air780EHV_Air780EGH/demo/tf_card/readme.md

@@ -87,7 +87,7 @@
 
 2、TYPE-C USB数据线一根
 
-3、AirMICROSD_1000模块一个和SD卡一张
+3、AirMICROSD_1010模块一个和SD卡一张
 
 4、Air780EHM/780EGH/780EHV核心板和数据线的硬件接线方式为
 
@@ -95,16 +95,16 @@
 
 - TYPE-C USB数据线直接插到核心板的TYPE-C USB座子,另外一端连接电脑USB口;
 
-5、Air780EHM核心板和AirMICROSD_1000模块接线方式
+5、Air780EHM核心板和AirMICROSD_1010模块接线方式
 
-|   Air780EHM     |    AirMICROSD_1000    |
+|   Air780EHM     |    AirMICROSD_1010    |
 | --------------- | --------------------- |
 |  GND(任意)      |          GND          |
-|  VDD_EXT        |          VCC          |
-|  GPIO8/SPI0_CS  |        CS,片选        |
-|  SPI0_SLK       |        CLK,时钟       |
-|  SPI0_MOSI      |  MOSI,主机输出,从机输入|
-|  SPI0_MISO      |  MISO,主机输入,从机输出|
+|  VDD_EXT        |          3V3         |
+|  GPIO8/SPI0_CS  |        spi_cs       |
+|  SPI0_SLK       |        spi_clk,时钟       |
+|  SPI0_MOSI      |  spi_mosi,主机输出,从机输入|
+|  SPI0_MISO      |  spi_miso,主机输入,从机输出|
 
 ### 2、Air780EHM开发板演示环境
 
@@ -112,7 +112,7 @@
 
 2、TYPE-C USB数据线一根
 
-3、AirMICROSD_1000模块一个和SD卡一张
+3、AirMICROSD_1010模块一个和SD卡一张
 
 4、Air780EHM/780EGH/780EHV开发板和数据线的硬件接线方式为
 
@@ -120,16 +120,15 @@
 
 - TYPE-C USB数据线直接插到核心板的TYPE-C USB座子,另外一端连接电脑USB口;
 
-5、Air780EHM开发板和AirMICROSD_1000模块接线方式
-
-|   Air780EHM     |    AirMICROSD_1000    |
+5、Air780EHM开发板和AirMICROSD_1010模块接线方式
+|   Air780EHM     |    AirMICROSD_1010    |
 | --------------- | --------------------- |
 |  GND(任意)      |          GND          |
-|  VDD_EXT        |          VCC          |
-|  GPIO16/SPI0_CS  |        CS,片选        |
-|  SPI0_SLK       |        CLK,时钟       |
-|  SPI0_MOSI      |  MOSI,主机输出,从机输入|
-|  SPI0_MISO      |  MISO,主机输入,从机输出|
+|  VDD_EXT        |          3V3         |
+|  GPIO16/SPI0_CS  |        spi_cs       |
+|  SPI0_SLK       |        spi_clk,时钟       |
+|  SPI0_MOSI      |  spi_mosi,主机输出,从机输入|
+|  SPI0_MISO      |  spi_miso,主机输入,从机输出|
 
 ## 演示软件环境
 

+ 0 - 0
module/Air8000/demo/accessory_board/AirMICROSD_1010/.keep


+ 78 - 0
module/Air8000/demo/accessory_board/AirMICROSD_1010/http_download_file.lua

@@ -0,0 +1,78 @@
+--[[
+@module http_download_file
+@summary http下载文件模块
+@version 1.0.0
+@date    2025.08.25
+@author  王棚嶙
+@usage
+本文件演示的功能为通过http下载文件进入TF卡中:
+1. 网络就绪检测
+2. 创建HTTP下载任务并等待完成
+3. 记录下载结果
+4. 获取并记录文件大小
+本文件没有对外接口,直接在main.lua中require "http_download_file"即可
+]] 
+
+
+local function http_download_file_task()
+
+    -- 阶段1: 网络就绪检测
+
+    while not socket.adapter(socket.dft()) do
+        log.warn("HTTP下载", "等待网络连接", socket.dft())
+        -- 等待IP_READY消息,超时设为1秒
+        sys.waitUntil("IP_READY", 1000)
+    end
+
+    -- 检测到了IP_READY消息
+    log.info("HTTP下载", "网络已就绪", socket.dft())
+
+    -- 进行SPI初始化,Air8000核心板TF卡的CS脚为:SPI1,GPIO12
+    local spi_id, pin_cs = 1, 12 
+    spi.setup(spi_id, nil, 0, 0, 400 * 1000)
+    gpio.setup(pin_cs, 1)
+    -- 挂载文件系统
+    local mount_ok = fatfs.mount(fatfs.SPI, "/sd", spi_id, pin_cs, 24 * 1000 * 1000)
+    if not mount_ok then
+        log.error("HTTP下载", "文件系统挂载失败")
+        fatfs.unmount("/sd")
+        spi.close(spi_id)
+        return
+    end
+
+    -- 阶段2: 执行下载任务
+    log.info("HTTP下载", "开始下载任务")
+
+    -- 核心下载操作开始 (支持http和https)
+    --local code, headers, body = http.request("GET", "...", nil, nil, {dst = "/sd/1.mp3"}).wait()
+    -- 其中 "..."为url地址, 支持 http和https, 支持域名, 支持自定义端口。
+    local code, headers, body_size = http.request("GET",
+                                    "https://gitee.com/openLuat/LuatOS/raw/master/module/Air780EHM_Air780EHV_Air780EGH/demo/audio/1.mp3",
+                                    nil, nil, {dst = "/sd/1.mp3"}).wait()
+    -- 阶段3: 记录下载结果
+    log.info("HTTP下载", "下载完成", 
+        code==200 and "success" or "error", 
+        code, 
+        -- headers是下载的文件头信息
+        json.encode(headers or {}), 
+        -- body_size是下载的文件大小(字节数)
+        body_size) 
+        
+    if code == 200 then
+        -- 获取实际文件大小
+        local actual_size = io.fileSize("/sd/1.mp3")
+        log.info("HTTP下载", "文件大小验证", "预期:", body_size, "实际:", actual_size)
+        
+        if actual_size~= body_size then
+            log.error("HTTP下载", "文件大小不一致", "预期:", body_size, "实际:", actual_size)
+        end
+    end
+
+    -- 阶段4: 资源清理
+    fatfs.unmount("/sd")
+    spi.close(spi_id)
+    log.info("HTTP下载", "资源清理完成")
+end
+
+-- 创建下载任务
+sys.taskInit(http_download_file_task)

+ 96 - 0
module/Air8000/demo/accessory_board/AirMICROSD_1010/http_upload_file.lua

@@ -0,0 +1,96 @@
+--[[
+@module http_upload_file
+@summary TF卡大文件httpplus上传模块
+@version 1.0.0
+@date 2025.08.25
+@author 王棚嶙
+@usage
+本文件演示通过httpplus库将TF卡中的大文件上传到HTTP服务器:
+1. 网络就绪检测
+2. TF卡文件系统挂载
+3. 大文件上传功能
+4. 上传结果记录
+本文件没有对外接口,直接在main.lua中require "http_upload_file"即可
+]]
+-- 加载httpplus扩展库,不可省略
+local httpplus = require "httpplus"
+
+local function http_upload_task()
+    -- 阶段1: 网络就绪检测
+    while not socket.adapter(socket.dft()) do
+        log.warn("HTTP上传", "等待网络连接", socket.dft())
+        -- 待IP_READY消息,超时设为1秒
+        sys.waitUntil("IP_READY", 1000)
+    end
+
+    -- 检测到了IP_READY消息
+    log.info("HTTP上传", "网络已就绪", socket.dft())
+
+    -- 阶段2: TF卡文件系统初始化
+    local spi_id, pin_cs = 1, 12
+    spi.setup(spi_id, nil, 0, 0, 400 * 1000)
+    gpio.setup(pin_cs, 1)
+    
+    local mount_ok = fatfs.mount(fatfs.SPI, "/sd", spi_id, pin_cs, 24 * 1000 * 1000)
+    if not mount_ok then
+        log.error("HTTP上传", "文件系统挂载失败")
+        fatfs.unmount("/sd")
+        spi.close(spi_id)
+        return
+    end
+
+    -- 阶段3: 检查要上传的文件是否存在
+    -- 替换为实际的文件路径
+    local upload_file_path = "/sd/30M_test.txt" 
+    if not io.exists(upload_file_path) then
+        log.error("HTTP上传", "要上传的文件不存在", upload_file_path)
+        fatfs.unmount("/sd")
+        spi.close(spi_id)
+        return
+    end
+
+    -- 获取文件大小
+    local file_size = io.fileSize(upload_file_path)
+    log.info("HTTP上传", "准备上传文件", upload_file_path, "大小:", file_size, "字节")
+
+    -- 阶段4: 执行文件上传
+    log.info("HTTP上传", "开始上传任务")
+    
+    -- 使用httpplus库上传文件,参考httpplus_app_post_file的实现
+    -- hhtplus.request接口支持单文件上传、多文件上传、单文本上传、多文本上传、单/多文本+单/多文件上传
+    -- http://airtest.openluat.com:2900/uploadFileToStatic 仅支持单文件上传,并且上传的文件name必须使用"uploadFile"
+    -- 所以此处仅演示了单文件上传功能,并且"uploadFile"不能改成其他名字,否则会出现上传失败的应答
+    local code, response = httpplus.request({
+        url = "http://airtest.openluat.com:2900/uploadFileToStatic",
+        files = {
+            -- 服务器要求文件名必须为"uploadFile"
+            ["uploadFile"] = upload_file_path, 
+        },
+    })
+
+    -- 阶段5: 记录上传结果
+    log.info("HTTP上传", "上传完成", 
+        code == 200 and "success" or "error", 
+        code)
+    
+    if code == 200 then
+        log.info("HTTP上传", "服务器响应头", json.encode(response.headers or {}))
+        local body = response.body and response.body:query()
+        log.info("HTTP上传", "服务器响应体长度", body and body:len() or 0)
+        
+        -- 可以进一步解析服务器响应
+        if body then
+            log.info("HTTP上传", "服务器响应内容", body:len() > 512 and "内容过长,不显示" or body)
+        end
+    else
+        log.error("HTTP上传", "上传失败", code)
+    end
+
+    -- 阶段6: 资源清理
+    fatfs.unmount("/sd")
+    spi.close(spi_id)
+    log.info("HTTP上传", "资源清理完成")
+end
+
+-- 创建上传任务
+sys.taskInit(http_upload_task)

+ 92 - 0
module/Air8000/demo/accessory_board/AirMICROSD_1010/main.lua

@@ -0,0 +1,92 @@
+--[[
+@module  main
+@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑
+@version 001.000.000
+@date    2025.08.25
+@author  王棚嶙
+@usage
+本 Demo 完整覆盖了 TF 卡操作的核心到高级流程,HTTP下载功能包括:
+1. 基础操作:
+   - CH390 供电控制
+   - 看门狗守护机制 
+2. 挂载及文件操作:
+   - 文件系统挂载/卸载
+   - TF卡空间信息查询
+   - 文件创建/读写/追加
+   - 目录创建/删除
+   - 文件重命名/删除
+   - 文件存在性检查与大小获取
+3. 下载功能:
+   - 网络检测与HTTP文件下载
+更多说明参考本目录下的readme.md文件
+]]
+
+
+--[[
+必须定义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 = "tfcard"
+VERSION = "001.000.000"
+
+
+
+
+-- 在日志中打印项目名和项目版本号
+log.info("main", PROJECT, VERSION)
+
+
+--添加硬狗防止程序卡死
+if wdt then
+    wdt.init(9000)--初始化watchdog设置为9s
+    sys.timerLoopStart(wdt.feed, 3000)--3s喂一次狗
+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)
+
+
+
+
+--[[在加载以下三个功能时,建议分别打开进行测试,因为文件操作,http下载功能和http大文件上传功能是异步操作。
+放到一个项目中,如果加载的时间点是随机的,就会出现tfcard_app在spi.setup和fatfs挂载文件系统之后,
+还没有释放资源,然后http_download_file或http_upload_file又去重复spi.setup和fatfs挂载文件系统了,
+不符合正常的业务逻辑,用户在参考编程的时候也要注意。]]
+
+--加载tf卡测试应用模块
+require "tfcard_app"
+--加载HTTP下载存入TF卡功能演示模块
+--require "http_download_file"
+--加载HTTP上传文件到服务器的功能演示模块
+--require "http_upload_file"
+
+-- 用户代码已结束---------------------------------------------
+-- 结尾总是这一句
+sys.run()
+-- sys.run()之后后面不要加任何语句!!!!!

+ 54 - 0
module/Air8000/demo/accessory_board/AirMICROSD_1010/pins_Air8000.json

@@ -0,0 +1,54 @@
+{
+  "model": "Air8000",
+  "pins": [
+    [1, "USB_BOOT", ""],
+    [2, "VBUS", ""],
+    [14, "PWR_KEY", ""],
+    [16, "UART1_TXD", ""],
+    [17, "UART1_RXD", ""],
+    [18, "I2S_BCLK", ""],
+    [19, "I2S_LRCK", ""],
+    [20, "I2S_DIN", ""],
+    [21, "I2S_DOUT", ""],
+    [22, "I2S_MCLK", ""],
+    [23, "GPIO20", ""],
+    [24, "GPIO21", ""],
+    [25, "LCD_CLK", ""],
+    [26, "LCD_CS", ""],
+    [27, "LCD_RST", ""],
+    [28, "LCD_SDA", ""],
+    [29, "LCD_RS", ""],
+    [30, "GPIO2", ""],
+    [31, "GPIO1", ""],
+    [35, "CAN_STB", ""],
+    [36, "CAN_TXD", ""],
+    [37, "CAN_RXD", ""],
+    [38, "SPI1_SCLK", "配置为spi1_sclk"],
+    [39, "SPI1_MISO", "配置为spi1_miso"],
+    [40, "SPI1_MOSI", "配置为spi1_mosi"],
+    [41, "SPI1_CS", "配置为spi1_cs"],
+    [43, "WAKEUP6", ""],
+    [44, "WAKEUP0", ""],
+    [46, "DBG_TXD", ""],
+    [47, "DBG_RXD", ""],
+    [48, "UART11_RXD", ""],
+    [49, "UART11_TXD", ""],
+    [52, "GPIO153", ""],
+    [53, "GPIO147", ""],
+    [54, "GPIO146", ""],
+    [55, "GPIO141", ""],
+    [56, "GPIO140", ""],
+    [59, "UART12_RXD", ""],
+    [60, "UART12_TXD", ""],
+    [66, "I2C1_SCL", ""],
+    [67, "I2C1_SDA", ""],
+    [73, "GPIO162", ""],
+    [74, "GPIO164", ""],
+    [80, "I2C0_SCL", ""],
+    [81, "I2C0_SDA", ""],
+    [82, "GPIO17", ""],
+    [83, "GPIO16", ""],
+    [96, "GPIO160", ""],
+    [98, "GPIO3", ""]
+  ]
+}

+ 238 - 0
module/Air8000/demo/accessory_board/AirMICROSD_1010/readme.md

@@ -0,0 +1,238 @@
+## **功能模块介绍**
+
+本demo演示了在嵌入式环境中对TF卡(SD卡)的完整操作流程,覆盖了从文件系统挂载到高级文件操作的完整功能链。项目分为两个核心模块:
+
+1、main.lua:主程序入口 <br> 
+2、tfcard_app.lua:TF卡基础应用模块,实现文件系统管理、文件操作和目录管理功能<br> 
+3、http_download_file.lua:HTTP下载模块,实现网络检测与文件下载到TF卡的功能<br>
+4、http_upload_file.lua:HTTP下载模块,实现网络检测与tf卡内大文件上传服务器的功能
+
+## **演示功能概述**
+
+### 1、主程序入口模块(main.lua)
+
+- 初始化项目信息和版本号
+- 初始化看门狗,并定时喂狗
+- 启动一个循环定时器,每隔3秒钟打印一次总内存,实时的已使用内存,历史最高的已使用内存情况方便分析内存使用是否有异常
+- 加载tfcard_app模块(通过require "tfcard_app")
+- 加载http_download_file模块(通过require "http_download_file")
+- 最后运行sys.run()。
+
+### 3、TF卡核心演示模块(tfcard_app.lua)
+
+#### 文件系统管理
+
+- SPI初始化与挂载:
+  - 配置SPI接口参数(频率400kHz)
+  - 挂载FAT32文件系统到`/sd`路径
+  - 自动格式化检测与处理
+- 空间信息获取:
+  - 实时查询TF卡可用空间
+  - 输出详细存储信息(总空间/剩余空间)
+#### 文件操作
+- 创建目录:io.mkdir("/sd/io_test")
+- 创建/写入文件: io.open("/sd/io_test/boottime", "wb")
+- 检查文件存在: io.exists(file_path)
+- 获取文件大小:io.fileSize(file_path)
+- 读取文件内容: io.open(file_path, "rb"):read("*a")
+- 启动计数文件: 记录设备启动次数
+- 文件追加: io.open(append_file, "a+")
+- 按行读取: file:read("*l")
+- 文件关闭: file:close()
+- 文件重命名: os.rename(old_path, new_path)
+- 列举目录: io.lsdir(dir_path)
+- 删除文件: os.remove(file_path)
+- 删除目录: io.rmdir(dir_path)
+
+#### 结果处理
+
+- 资源清理(卸载/SPI关闭)
+
+### 4、HTTP下载功能 (http_download_file.lua)
+
+#### 文件系统管理
+
+- SPI初始化与挂载
+
+#### 网络就绪检测
+
+- 1秒循环等待IP就绪
+- 网络故障处理机制
+
+#### 安全下载
+
+- HTTP下载
+
+#### 结果处理
+
+- 下载状态码解析
+- 自动文件大小验证
+- 资源清理(卸载/spi关闭)
+
+### 5、HTTP上传功能 (http_download_file.lua)
+
+#### 加载扩展库
+
+- require("httpplus")
+
+#### 网络就绪检测
+
+- 1秒循环等待IP就绪
+
+#### 文件系统管理
+
+- SPI初始化与挂载
+
+- 确认文件存在
+
+
+#### 安全上传
+
+- HTTP上传
+
+#### 结果处理
+
+- 解析服务器响应
+- 资源清理(卸载/spi关闭)
+
+## **演示硬件环境**
+
+1、Air8000核心板一块(Air8000系列模块的核心板接线方式相同,这里以Air8000为例)
+
+2、TYPE-C USB数据线一根
+
+3、AirMICROSD_1010模块一个和SD卡一张
+
+4、Air8000系列核心板和数据线的硬件接线方式为
+
+- Air8000核心板通过TYPE-C USB口供电;(核心板USB旁边的开关拨到供电一端)
+
+- Air8000核心板背面的拨码开关拨到USB ON
+
+- TYPE-C USB数据线直接插到核心板的TYPE-C USB座子,另外一端连接电脑USB口;
+
+5、Air8000核心板和AirMICROSD_1010模块接线方式
+
+|   Air8000核心板    |    AirMICROSD_1010    |
+| --------------- | --------------------- |
+|  GND(任意)      |          GND          |
+|  VDD_EXT        |          3V3         |
+|  GPIO12/SPI1_CS  |        spi_cs         |
+|  SPI1_SLK       |        spi_clk,时钟       |
+|  SPI1_MOSI      |  spi_mosi,主机输出,从机输入|
+|  SPI1_MISO      |  spi_miso,主机输入,从机输出|
+
+## **演示软件环境**
+
+1、Luatools下载调试工具:https://docs.openluat.com/air780epm/common/Luatools/
+
+2、内核固件版本:https://docs.openluat.com/air8000/luatos/firmware/
+
+## **演示核心步骤**
+
+1、搭建好硬件环境
+
+2、通过Luatools将demo与固件烧录到核心板中
+
+3、烧录好后,板子开机将会在Luatools上看到如下打印
+
+```lua
+(1)TF卡初始化与挂载
+[2025-08-24 19:51:24.152][000000001.389] SPI_HWInit 552:spi1 speed 2000000,1994805,154
+[2025-08-24 19:51:24.213][000000002.390] D/fatfs init sdcard at spi=1 cs=20
+[2025-08-24 19:51:24.286][000000002.390] SPI_SetNewConfig 996:spi1 speed 400000,400000
+[2025-08-24 19:51:24.329][000000002.408] SPI_SetNewConfig 996:spi1 speed 24000000,25600000
+[2025-08-24 19:51:24.383][000000002.408] D/SPI_TF 卡容量 122138624KB
+[2025-08-24 19:51:24.430][000000002.408] D/SPI_TF sdcard init OK OCR:0xc0ff8000!
+[2025-08-24 19:51:24.477][000000002.412] I/user.fatfs.mount 挂载成功 0
+[2025-08-24 19:51:24.535][000000002.617] I/user.fatfs getfree {"free_sectors":244262144,"total_kb":122132480,"free_kb":122131072,"total_sectors":244264960}
+[2025-08-24 19:51:24.583][000000002.618] I/user.fs lsmount [{"fs":"ec7xx","path":""},{"fs":"inline","path":"\/lua\/"},{"fs":"ram","path":"\/ram\/"},{"fs":"luadb","path":"\/luadb\/"},{"fs":"fatfs","path":"\/sd"}]
+
+
+(2)文件操作演示
+[2025-08-24 19:51:24.685][000000002.619] I/user.文件操作 ===== 开始文件操作 =====
+[2025-08-24 19:51:25.145][000000003.032] I/user.io.mkdir 目录创建成功 路径:/sd/io_test
+[2025-08-24 19:51:25.231][000000003.043] I/user.文件创建 文件写入成功 路径:/sd/io_test/boottime
+[2025-08-24 19:51:25.297][000000003.046] I/user.io.exists 文件存在 路径:/sd/io_test/boottime
+[2025-08-24 19:51:25.376][000000003.049] I/user.io.fileSize 文件大小:41字节 路径:/sd/io_test/boottime
+[2025-08-24 19:51:25.467][000000003.052] I/user.文件读取 路径:/sd/io_test/boottime 内容:这是io库API文档示例的测试内容
+[2025-08-24 19:51:25.547][000000003.056] I/user.启动计数 文件内容: 这是io库API文档示例的测试内容 十六进制: E8BF99E698AF696FE5BA93415049E69687E6A1A3E7A4BAE4BE8BE79A84E6B58BE8AF95E58685E5AEB9 82
+[2025-08-24 19:51:25.616][000000003.056] I/user.启动计数 当前值: 0
+[2025-08-24 19:51:25.693][000000003.057] I/user.启动计数 更新值: 1
+[2025-08-24 19:51:25.736][000000003.068] I/user.文件写入 路径:/sd/io_test/boottime 内容: 1
+[2025-08-24 19:51:25.795][000000003.081] I/user.文件创建 路径:/sd/io_test/test_a 初始内容:ABC
+[2025-08-24 19:51:25.852][000000003.088] I/user.文件追加 路径:/sd/io_test/test_a 追加内容:def
+[2025-08-24 19:51:25.909][000000003.091] I/user.文件验证 路径:/sd/io_test/test_a 内容:ABCdef 结果: 成功
+[2025-08-24 19:51:25.954][000000003.102] I/user.文件创建 路径:/sd/io_test/testline 写入3行文本
+[2025-08-24 19:51:26.001][000000003.106] I/user.按行读取 路径:/sd/io_test/testline 第1行: abc
+[2025-08-24 19:51:26.048][000000003.106] I/user.按行读取 路径:/sd/io_test/testline 第2行: 123
+[2025-08-24 19:51:26.093][000000003.107] I/user.按行读取 路径:/sd/io_test/testline 第3行: wendal
+[2025-08-24 19:51:26.140][000000003.112] I/user.os.rename 文件重命名成功 原路径:/sd/io_test/test_a 新路径:/sd/io_test/renamed_file.txt
+[2025-08-24 19:51:26.188][000000003.116] D/fatfs f_open /io_test/test_a 4
+[2025-08-24 19:51:26.238][000000003.116] D/vfs fopen /sd/io_test/test_a r not found
+[2025-08-24 19:51:26.312][000000003.117] I/user.验证结果 重命名验证成功 新文件存在 原文件不存在
+[2025-08-24 19:51:26.367][000000003.117] I/user.目录操作 ===== 开始目录列举 =====
+[2025-08-24 19:51:26.424][000000003.121] I/user.fs lsdir [{"name":"boottime","size":0,"type":0},{"name":"testline","size":0,"type":0},{"name":"renamed_file.txt","size":0,"type":0}]
+[2025-08-24 19:51:26.478][000000003.127] I/user.os.remove 文件删除成功 路径:/sd/io_test/renamed_file.txt
+[2025-08-24 19:51:26.539][000000003.129] D/fatfs f_open /io_test/renamed_file.txt 4
+[2025-08-24 19:51:26.593][000000003.130] D/vfs fopen /sd/io_test/renamed_file.txt r not found
+[2025-08-24 19:51:26.656][000000003.130] I/user.验证结果 renamed_file.txt文件删除验证成功
+[2025-08-24 19:51:26.734][000000003.137] I/user.os.remove testline文件删除成功 路径:/sd/io_test/testline
+[2025-08-24 19:51:26.856][000000003.139] D/fatfs f_open /io_test/testline 4
+[2025-08-24 19:51:26.922][000000003.140] D/vfs fopen /sd/io_test/testline r not found
+[2025-08-24 19:51:27.113][000000003.140] I/user.验证结果 testline文件删除验证成功
+[2025-08-24 19:51:27.197][000000003.147] I/user.os.remove 文件删除成功 路径:/sd/io_test/boottime
+[2025-08-24 19:51:27.251][000000003.149] D/fatfs f_open /io_test/boottime 4
+[2025-08-24 19:51:27.302][000000003.150] D/vfs fopen /sd/io_test/boottime r not found
+[2025-08-24 19:51:27.365][000000003.150] I/user.验证结果 boottime文件删除验证成功
+[2025-08-24 19:51:27.407][000000003.158] I/user.io.rmdir 目录删除成功 路径:/sd/io_test
+[2025-08-24 19:51:27.461][000000003.159] D/fatfs f_open /io_test 4
+[2025-08-24 19:51:27.536][000000003.159] D/vfs fopen /sd/io_test r not found
+[2025-08-24 19:51:27.610][000000003.159] I/user.验证结果 目录删除验证成功
+[2025-08-24 19:51:27.668][000000003.160] I/user.文件操作 ===== 文件操作完成 =====
+[2025-08-24 19:51:27.712][000000003.160] I/user.系统清理 开始执行关闭操作...
+[2025-08-24 19:51:27.772][000000003.160] I/user.文件系统 卸载成功
+[2025-08-24 19:51:27.867][000000003.160] I/user.SPI接口 已关闭
+
+
+(3)网络连接与HTTP下载
+[2025-08-24 20:31:49.405][000000006.268] I/user.HTTP下载 开始下载任务
+[2025-08-24 20:31:49.438][000000006.275] dns_run 674:gitee.com state 0 id 1 ipv6 0 use dns server2, try 0
+[2025-08-24 20:31:49.471][000000006.277] D/mobile TIME_SYNC 0
+[2025-08-24 20:31:49.503][000000006.297] dns_run 691:dns all done ,now stop
+[2025-08-24 20:31:54.800][000000012.080] I/user.HTTP下载 下载完成 success 200 
+[2025-08-24 20:31:54.872][000000012.080] {"Age":"0","Cache-Control":"public, max-age=60","Via":"1.1 varnish","Transfer-Encoding":"chunked","Date":"Sun, 24 Aug 2025 12:31:49 GMT","Access-Control-Allow-Credentials":"true","Vary":"Accept-Encoding","X-Served-By":"cache-ffe9","X-Gitee-Server":"http-pilot 1.9.21","Connection":"keep-alive","Server":"ADAS\/1.0.214","Access-Control-Allow-Headers":"Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,User-Agent,X-Requested-With,X-CustomHeader,Content-Range,Range,Set-Language","Content-Security-Policy":"default-src 'none'; style-src 'unsafe-inline'; sandbox","X-Request-Id":"1f7e4b55-53c8-440a-9806-8894aa823f50","Accept-Ranges":"bytes","Etag":"W\/\"6ea36a6c51a48eaba0ffbc01d409424e7627bc56\"","Content-Type":"text\/plain; charset=utf-8","Access-Control-Allow-Methods":"GET, POST, PUT, PATCH, DELETE, OPTIONS","X-Frame-Options":"DENY","X-Cache":"MISS","Set-Cookie":"BEC=1f1759df3ccd099821dcf0da6feb0357;Path=\/;Max-Age=126000"}
+[2025-08-24 20:31:54.910][000000012.080]  411922
+[2025-08-24 20:31:54.936][000000012.082] I/user.HTTP下载 文件大小验证 预期: 411922 实际: 411922
+[2025-08-24 20:31:54.979][000000012.083] I/user.HTTP下载 资源清理完成
+
+(4)网络连接与HTTP上传
+[2025-09-24 18:07:54.587][000000000.360] I/user.main tfcard 001.000.000
+[2025-09-24 18:07:54.601][000000000.396] W/user.HTTP上传 等待网络连接 1 3
+[2025-09-24 18:07:56.758][000000004.693] D/mobile cid1, state0
+[2025-09-24 18:07:56.763][000000004.694] D/mobile bearer act 0, result 0
+[2025-09-24 18:07:56.774][000000004.695] D/mobile NETIF_LINK_ON -> IP_READY
+[2025-09-24 18:07:56.779][000000004.696] I/user.HTTP上传 网络已就绪 1 3
+[2025-09-24 18:07:56.791][000000004.696] SPI_HWInit 552:spi1 speed 2000000,1994805,154
+[2025-09-24 18:07:56.796][000000004.697] D/fatfs init sdcard at spi=1 cs=20
+[2025-09-24 18:07:56.809][000000004.716] D/SPI_TF 卡容量 122138624KB
+[2025-09-24 18:07:56.828][000000004.716] D/SPI_TF sdcard init OK OCR:0xc0ff8000!
+[2025-09-24 18:07:56.833][000000004.725] I/user.HTTP上传 准备上传文件 /sd/30M_test.txt 大小: 31467520 字节
+[2025-09-24 18:07:56.852][000000004.725] I/user.HTTP上传 开始上传任务
+[2025-09-24 18:07:56.860][000000004.730] D/socket connect to airtest.openluat.com,2900
+[2025-09-24 18:07:56.865][000000004.731] dns_run 676:airtest.openluat.com state 0 id 1 ipv6 0 use dns server2, try 0
+[2025-09-24 18:07:56.869][000000004.799] dns_run 693:dns all done ,now stop
+[2025-09-24 18:07:56.885][000000004.881] soc_cms_proc 2189:cenc report 1,51,1,15
+[2025-09-24 18:07:57.063][000000005.068] D/mobile NETIF_LINK_ON -> IP_READY
+[2025-09-24 18:07:58.364][000000006.364] D/mobile ims reg state 0
+[2025-09-24 18:07:58.375][000000006.365] D/mobile LUAT_MOBILE_EVENT_CC status 0
+[2025-09-24 18:07:58.385][000000006.365] D/mobile LUAT_MOBILE_CC_READY
+[2025-09-24 18:09:32.042][000000100.039] I/user.httpplus 等待服务器完成响应
+[2025-09-24 18:09:32.135][000000100.135] I/user.httpplus 等待服务器完成响应
+[2025-09-24 18:09:32.982][000000100.973] I/user.httpplus 服务器已完成响应,开始解析响应
+[2025-09-24 18:09:32.997][000000100.998] I/user.HTTP上传 上传完成 success 200
+[2025-09-24 18:09:33.004][000000100.998] I/user.HTTP上传 服务器响应头 {"Content-Type":"text\/plain;charset=UTF-8","Connection":"close","Content-Length":"20","Vary":"Access-Control-Request-Headers","Date":"Wed, 24 Sep 2025 10"}
+[2025-09-24 18:09:33.011][000000100.999] I/user.HTTP上传 服务器响应体长度 20
+[2025-09-24 18:09:33.021][000000101.000] I/user.HTTP上传 服务器响应内容 uploadFileToStaticOK
+[2025-09-24 18:09:33.027][000000101.000] I/user.HTTP上传 资源清理完成
+```

+ 331 - 0
module/Air8000/demo/accessory_board/AirMICROSD_1010/tfcard_app.lua

@@ -0,0 +1,331 @@
+--[[
+@module  tfcard_app
+@summary TF卡文件操作测试模块
+@version 1.0.0
+@date    2025.08.25
+@author  王棚嶙
+@usage
+本文件为TF卡的文件操作测试流程:
+1. 创建目录
+2. 创建并写入文件
+3. 检查文件是否存在
+4. 获取文件大小
+5. 读取文件内容
+6. 启动计数文件操作
+7. 文件追加测试
+8. 按行读取测试
+9. 读取后关闭文件
+10. 文件重命名
+11. 列举目录内容
+12. 删除文件
+13. 删除目录
+本文件没有对外接口,直接在main.lua中require "tfcard_app"就可以加载运行
+]] 
+
+function tfcard_main_task() -- 开始进行主测试流程。
+
+    -- ##########  SPI初始化 ##########
+    -- Air8000整机核心板上TF卡的的pin_cs为gpio20,spi_id为1.请根据实际硬件修改
+    spi_id, pin_cs = 1, 12
+    spi.setup(spi_id, nil, 0, 0, 400 * 1000)
+    --设置片选引脚同一spi总线上的所有从设备在初始化时必须要先拉高CS脚,防止从设备之间互相干扰。
+    gpio.setup(pin_cs, 1)
+
+    -- ########## 开始进行tf卡挂载 ##########
+    --挂载失败默认格式化,
+    -- 如无需格式化应改为fatfs.mount(fatfs.SPI, "/sd", spi_id, pin_cs, 24 * 1000 * 1000, nil, 1, false),
+    -- 一般是在测试硬件是否有问题的时候把格式化取消掉
+    mount_ok, mount_err = fatfs.mount(fatfs.SPI, "/sd", spi_id, pin_cs, 24 * 1000 * 1000)
+    if mount_ok then
+        log.info("fatfs.mount", "挂载成功", mount_err)
+    else
+        log.error("fatfs.mount", "挂载失败", mount_err)
+        goto resource_cleanup
+    end
+
+    -- ########## 获取SD卡的可用空间信息并打印。 ########## 
+    data, err = fatfs.getfree("/sd")
+    if data then
+        --打印SD卡的可用空间信息
+        log.info("fatfs", "getfree", json.encode(data))
+    else
+        --打印错误信息
+        log.info("fatfs", "getfree", "err", err)
+        goto resource_cleanup
+    end
+
+    -- 列出所有挂载点,如不需要,可注释掉。
+    data = io.lsmount()
+    log.info("fs", "lsmount", json.encode(data))
+
+    -- ########## 功能: 启用fatfs调试模式 ##########
+    -- fatfs.debug(1) -- 若挂载失败,可以尝试打开调试信息,查找原因.(设置调试模式)
+
+    -- 执行tfcard文件操作演示
+    log.info("文件操作", "===== 开始文件操作 =====")
+
+    dir_path = "/sd/io_test"
+
+    -- 1. 创建目录
+    if io.mkdir(dir_path) then
+        log.info("io.mkdir", "目录创建成功", "路径:" .. dir_path)
+    else
+        -- 检查是否目录已存在
+        if io.exists(dir_path) then
+            log.warn("io.mkdir", "目录已存在,跳过创建", "路径:" .. dir_path)
+        else
+            log.error("io.mkdir", "目录创建失败且目录不存在", "路径:" .. dir_path)
+            goto resource_cleanup
+        end
+    end
+
+    -- 2. 创建并写入文件
+    file_path = dir_path .. "/boottime"
+    file = io.open(file_path, "wb")
+    if file then
+        file:write("这是io库API文档示例的测试内容")
+        file:close()
+        --在LuatOS文件操作中,执行file:close()是必须且关键的操作,它用于关闭文件句柄,释放资源,并确保数据被正确写入磁盘。
+        -- 如果不执行file:close(),可能会导致数据丢失、文件损坏或其他不可预测的问题。
+        log.info("文件创建", "文件写入成功", "路径:" .. file_path)
+    else
+        log.error("文件创建", "文件创建失败", "路径:" .. file_path)
+        goto resource_cleanup
+    end
+
+    -- 3. 检查文件是否存在
+    if io.exists(file_path) then
+        log.info("io.exists", "文件存在", "路径:" .. file_path)
+    else
+        log.error("io.exists", "文件不存在", "路径:" .. file_path)
+        goto resource_cleanup
+    end
+
+    -- 4. 获取文件大小
+    file_size = io.fileSize(file_path)
+    if file_size then
+        log.info("io.fileSize", "文件大小:" .. file_size .. "字节", "路径:" .. file_path)
+    else
+        log.error("io.fileSize", "获取文件大小失败", "路径:" .. file_path)
+        goto resource_cleanup
+    end
+
+    -- 5. 读取文件内容
+    file = io.open(file_path, "rb")
+    if file then
+        content = file:read("*a")
+        log.info("文件读取", "路径:" .. file_path, "内容:" .. content)
+        file:close()
+    else
+        log.error("文件操作", "无法打开文件读取内容", "路径:" .. file_path)
+        goto resource_cleanup
+    end
+
+    -- 6. 启动计数文件操作
+    count = 0
+    --以只读模式打开文件
+    file = io.open(file_path, "rb")
+    if file then
+        data = file:read("*a")
+        log.info("启动计数", "文件内容:", data, "十六进制:", data:toHex())
+        count = tonumber(data) or 0
+        file:close()
+    else
+        log.warn("启动计数", "文件不存在或无法打开")
+
+    end
+
+    log.info("启动计数", "当前值:", count)
+    count=count + 1
+    log.info("启动计数", "更新值:", count)
+
+    file = io.open(file_path, "wb")
+    if file then
+        file:write(tostring(count))
+        file:close()
+        log.info("文件写入", "路径:" .. file_path, "内容:", count)
+    else
+        log.error("文件写入", "无法打开文件", "路径:" .. file_path)
+        goto resource_cleanup
+    end
+
+    -- 7. 文件追加测试
+    append_file = dir_path .. "/test_a"
+    -- 清理旧文件
+    os.remove(append_file)
+
+    -- 创建并写入初始内容
+    file = io.open(append_file, "wb")
+    if file then
+        file:write("ABC")
+        file:close()
+        log.info("文件创建", "路径:" .. append_file, "初始内容:ABC")
+    else
+        log.error("文件创建", "无法创建文件", "路径:" .. append_file)
+        goto resource_cleanup
+    end
+
+    -- 追加内容
+    file = io.open(append_file, "a+")
+    if file then
+        file:write("def")
+        file:close()
+        log.info("文件追加", "路径:" .. append_file, "追加内容:def")
+    else
+        log.error("文件追加", "无法打开文件进行追加", "路径:" .. append_file)
+        goto resource_cleanup
+
+    end
+
+    -- 验证追加结果
+    file = io.open(append_file, "r")
+    if file then
+        data = file:read("*a")
+        log.info("文件验证", "路径:" .. append_file, "内容:" .. data, "结果:",
+            data == "ABCdef" and "成功" or "失败")
+        file:close()
+    else
+        log.error("文件验证", "无法打开文件进行验证", "路径:" .. append_file)
+        goto resource_cleanup
+    end
+
+    -- 8. 按行读取测试
+    line_file = dir_path .. "/testline"
+    file = io.open(line_file, "w")
+    if file then
+        file:write("abc\n")
+        file:write("123\n")
+        file:write("wendal\n")
+        file:close()
+        log.info("文件创建", "路径:" .. line_file, "写入3行文本")
+    else
+        log.error("文件创建", "无法创建文件", "路径:" .. line_file)
+        goto resource_cleanup
+    end
+
+    -- 按行读取文件
+    file = io.open(line_file, "r")
+    if file then
+        log.info("按行读取", "路径:" .. line_file, "第1行:", file:read("*l"))
+        log.info("按行读取", "路径:" .. line_file, "第2行:", file:read("*l"))
+        log.info("按行读取", "路径:" .. line_file, "第3行:", file:read("*l"))
+        file:close()
+    else
+        log.error("按行读取", "无法打开文件", "路径:" .. line_file)
+        goto resource_cleanup
+    end
+
+    -- 9. 文件重命名
+    old_path = append_file
+    new_path = dir_path .. "/renamed_file.txt"
+    success, err = os.rename(old_path, new_path)
+    if success then
+        log.info("os.rename", "文件重命名成功", "原路径:" .. old_path, "新路径:" .. new_path)
+
+        -- 验证重命名结果
+        if io.exists(new_path) and not io.exists(old_path) then
+            log.info("验证结果", "重命名验证成功", "新文件存在", "原文件不存在")
+        else
+            log.error("验证结果", "重命名验证失败")
+        end
+    else
+        log.error("os.rename", "重命名失败", "错误:" .. tostring(err), "原路径:" .. old_path)
+        goto resource_cleanup
+    end
+
+    -- 10. 列举目录内容
+    log.info("目录操作", "===== 开始目录列举 =====")
+
+    ret, data = io.lsdir(dir_path, 50, 0) -- 50表示最多返回50个文件,0表示从目录开头开始
+    if ret then
+        log.info("fs", "lsdir", json.encode(data))
+    else
+        log.info("fs", "lsdir", "fail", ret, data)
+        goto resource_cleanup
+    end
+
+    -- 11. 删除文件测试
+    -- 测试删除renamed_file.txt文件
+    if os.remove(new_path) then
+        log.info("os.remove", "文件删除成功", "路径:" .. new_path)
+
+        -- 验证renamed_file.txt删除结果
+        if not io.exists(new_path) then
+            log.info("验证结果", "renamed_file.txt文件删除验证成功")
+        else
+            log.error("验证结果", "renamed_file.txt文件删除验证失败")
+        end
+    else
+        log.error("io.remove", "renamed_file.txt文件删除失败", "路径:" .. new_path)
+        goto resource_cleanup
+    end
+
+    -- 测试删除testline文件
+    if os.remove(line_file) then
+        log.info("os.remove", "testline文件删除成功", "路径:" .. line_file)
+
+        -- 验证删除结果
+        if not io.exists(line_file) then
+            log.info("验证结果", "testline文件删除验证成功")
+        else
+            log.error("验证结果", "testline文件删除验证失败")
+        end
+    else
+        log.error("io.remove", "testline文件删除失败", "路径:" .. line_file)
+        goto resource_cleanup
+    end
+
+    if os.remove(file_path) then
+        log.info("os.remove", "文件删除成功", "路径:" .. file_path)
+
+        -- 验证删除结果
+        if not io.exists(file_path) then
+            log.info("验证结果", "boottime文件删除验证成功")
+        else
+            log.error("验证结果", "boottime文件删除验证失败")
+        end
+    else
+        log.error("io.remove", "boottime文件删除失败", "路径:" .. file_path)
+        goto resource_cleanup
+    end
+
+    -- 12. 删除目录(不能删除非空目录,所以在删除目录前要确保目录内没有文件或子目录)
+    if io.rmdir(dir_path) then
+        log.info("io.rmdir", "目录删除成功", "路径:" .. dir_path)
+
+        -- 验证删除结果
+        if not io.exists(dir_path) then
+            log.info("验证结果", "目录删除验证成功")
+        else
+            log.error("验证结果", "目录删除验证失败")
+        end
+    else
+        log.error("io.rmdir", "目录删除失败", "路径:" .. dir_path)
+        goto resource_cleanup
+    end
+
+    log.info("文件操作", "===== 文件操作完成 =====")
+
+    -- ########## 功能: 收尾功能演示##########
+    -- 卸载文件系统和关闭SPI
+    ::resource_cleanup::
+
+    log.info("结束", "开始执行关闭操作...")  
+    -- 如已挂载需先卸载文件系统,未挂载直接关闭SPI
+    if mount_ok then
+        if fatfs.unmount("/sd") then
+            log.info("文件系统", "卸载成功")
+        else
+            log.error("文件系统", "卸载失败")
+        end
+    end
+
+    -- 2. 关闭SPI接口
+    spi.close(spi_id)
+    log.info("SPI接口", "已关闭")
+
+end
+
+sys.taskInit(tfcard_main_task)
+
+