跳转至

02 SD卡

作者:陈媛媛

一、TF卡概述

1.1 SD 卡

1.1.1 SD 卡简介

  • 定义:SD 卡(Secure Digital Card)是一种基于半导体快闪记忆器的新一代记忆设备,被广泛用于便携式设备中存储数据。
  • 特点:高存储容量、快速数据传输速度、体积小、重量轻、安全性高(支持数据加密)。

1.1.2 SD 卡类型与规格

  • 标准 SD 卡:原始 SD 卡规格。
  • miniSD 卡:缩小版的 SD 卡。
  • microSD 卡(又称 TF 卡):最小的 SD 卡规格,常用于智能手机和微型设备。

1.1.3 SD 卡工作原理

  • 文件系统:通常使用 FAT 文件系统(如 FAT16、FAT32)。
  • 通信协议:基于 SPI 协议进行数据传输。

冷知识:TF卡名字中的“T”代表“Tiny”(微型),而如今它已成为全球最小的通用存储卡标准。

1.2 TF卡没办法用,是怎么回事儿?

很多合宙的老朋友们的都有反馈过说自己的卡插进去了但是一直无法挂载成功怎么回事? 那么这里面就是涉及到一个兼容性的问题而TF卡兼容性是多维度的适配能力,涉及物理接口、传输协议、系统驱动及环境耐受性等。

1.2.1 设备兼容性

  • 工业设备支持:工业级TF卡需与工业相机、PLC控制器、机器人等专业设备兼容,确保在严苛环境下稳定工作。
  • 消费类设备:需适配智能手机、平板电脑、行车记录仪、无人机、监控摄像头等常见设备,例如:
  • 行车记录仪:实时保存高清视频数据,需支持循环录制。
  • 无人机/监控设备:保障长时间连续读写,如128G卡可支持25天1080P监控录制。
  • 扩展性要求:部分设备仅支持特定容量(如SDHC卡≤32GB,SDXC卡≥32GB),需匹配设备规范。

1.2.2 接口与协议兼容性

  • 速度标准:需符合设备支持的传输协议,例如:
  • UHS-I/UHS-III:提供104MB/s至624MB/s带宽,影响4K视频录制等场景的流畅性。
  • 视频速度等级(V30/V90):确保高帧率视频录制不丢帧,如V30卡可满足4K录制需求。
  • 应用性能等级(A1/A2):影响随机读写速度,A2级卡更适合安装应用或游戏(如Switch),减少加载延迟。

1.2.3 系统与驱动兼容性

  • 操作系统支持:
  • Linux系统:需内核驱动(如mmc模块)或用户空间驱动支持热插拔。
  • Windows/macOS/Android:需免驱即插即用,部分工业场景需定制驱动程序。
  • 文件系统适配:如FAT32/exFAT格式兼容不同设备,突发断电时需防护机制避免数据损坏。

1.2.4 环境与物理兼容性

  • 温度范围:
  • 工业级卡支持-25℃~85℃(如监控设备),消费级卡通常为0℃~70℃。
  • 耐用性:工业卡采用加固外壳和防震设计,适应车载、工厂等振动环境。

1.2.5 功能兼容性

  • 加密与安全:支持硬件加密技术,用于金融、安防等领域的数据保护。
  • 特殊功能:如Wi-Fi TF卡支持无线传输,需配套应用程序协同工作。

1.3 兼容性问题的常见表现与解决

  • 无法识别卡:检查驱动加载、卡槽物理损坏或文件系统错误。
  • 读写速度慢:升级高速读卡器(如支持UHS-II的型号),或更换高性能卡(如V30/A2级)。

1.4 我们现在测试有哪些TF卡能用

联想,闪迪,三星,三星EVO,金士顿等大品牌TF卡。所以在各位选择使用的TF卡时,我们建议不要使用白牌卡,尽量选择大品牌高速卡。

img

二、演示功能概述

本demo演示了在嵌入式环境中对TF卡(SD卡)的完整操作流程,覆盖了从文件系统挂载到高级文件操作的完整功能链。项目分为两个核心模块:

1、main.lua:主程序入口 <br 2、tfcard_app.lua:TF卡基础应用模块,实现文件系统管理、文件操作和目录管理功能<br 3、http_download_file.lua:HTTP下载模块,实现网络检测与文件下载到TF卡的功能

三、演示硬件环境

Air1601 开发板

1、Air1601 开发板一块

2、TYPE-C USB数据线一根

3、闪迪C10高速TF卡一张(即micro SD卡,即微型SD卡)

在我们 Air1601开发板上已经集成了 SD 卡卡座:

参考:硬件环境清单,准备以及组装好硬件环境

Air1601 开发板购买链接:https://item.taobao.com/item.htm?id=1044228452703&spm=a1z10.1-c-s.w4004-26072591152.4.48f01170WE0lGP

Air1601开发板SPI_SD卡功能 开关设置说明

  • V_SD 电源开关:拨至 V_SD 位置,为 SD 卡接口供电。

  • SPI/SD 通道拨码开关:所有通道拨向左侧"ON",将 SPI1 总线信号切换至 SD 卡接口,SD 卡使用 CS0 (GPIO8) 作为片选。

Air1601 本身无联网能力,如需通过 HTTP 协议进行文件上传、下载,可借助开发板上的 4G 模块 Air780EPM、WiFi 模块 Air6205 或以太网芯片 CH390H,分别实现 4G、WiFi、以太网三种联网方式。

Air1601开发板Airlink over uart Air780EPM 开关设置说明

  • 主控供电:将 V_Air1601 拨至 ON,为 Air1601 主控芯片供电

  • 模组供电:将 V_4G 拨至 ON,为 Air780EPM 模组供电。

  • 串口通道配置:将 4G_RX、4G_TX 拨至 ON,同时将 BLE、GNSS 相关的复用开关拨至 OFF,确保 Air1601 的 4G_UART2 通道仅与 Air780EPM 直连,避免信号冲突

Air1601开发板Airlink over uart Air6205 开关设置说明

  • S16 WIFI_TXD\WIFI_IRQ 开关:拨到 "ON"

  • S17 WIFI_RXD 开关:拨到 "ON"

  • S19 WIFI_EN 开关:拨到 "ON"

  • VBAT处的第三个V_4G开关:拨到"ON"

Air1601开发板以太网 CH390H 功能 开关设置说明

  • V_LAN 电源开关:拨至 ON,为以太网 PHY 芯片供电。

  • U5(SPI/ETH 通道拨码开关):所有通道拨向左侧"ON",打开以太网,以太网使用 CS1 (GPIO14) 作为片选。

  • S15(WAKEUP/LAN_INT 中断开关):

    单独使用以太网时:拨至 ON,连接 WAKEUP 信号到 LAN_INT,启用以太网中断功能。

    与触摸(TP)同时使用时:拨至 OFF,断开以太网中断,将 WAKEUP 信号留给 TP 使用。

四、准备软件环境

在开始实践本示例之前,先筹备一下软件环境:

4.1 烧录工具 Luatools

4.2 Air1601固件及脚本

  • 本demo开发测试时使用的固件为LuatOS-SoC_V1016_Air1601_101.soc,本demo对固件版本没有什么特殊要求,所以你如果要测试本demo时,可以直接使用最新版本的内核固件;如果发现最新版本的内核固件测试有问题,可以使用我们开发本demo时使用的内核固件版本来对比测试;

  • LuatOS 需要的脚本和资源文件:脚本资源文件

准备好软件环境之后,接下来查看如何烧录项目文件到Air1601开发板中,将本篇文章中演示使用的demo烧录到 Air1601 开发板中。

4.2 3、Air780EPM固件及脚本

  • 本demo开发测试时使用的固件为LuatOS-SoC_V2034_Air780EPM_8.soc,本本 demo 必须使用 V2034 及以上、支持 airlink over uart 的 8 号固件,你如果要测试本demo时,可以使用最新版本的8号固件;如果发现最新版本的内核固件测试有问题,可以使用我们开发本demo时使用的内核固件版本来对比测试;

  • LuatOS 需要的脚本和资源文件:脚本资源文件

准备好软件环境之后,接下来查看如何烧录项目文件到Air1601开发板中 3.2章节,将本篇文章中演示使用的demo烧录到 Air1601 开发板的 Air780EPM 模块中。

4.2 4、Air6205固件及脚本

  • 本demo开发测试时使用的固件为netdrv_1.0.5_LuatOS-SoC_V1022_Air6205.soc,本demo对固件版本没有什么特殊要求,所以你如果要测试本demo时,可以直接使用最新版本的内核固件;如果发现最新版本的内核固件测试有问题,可以使用我们开发本demo时使用的内核固件版本来对比测试;

准备好软件环境之后,接下来查看如何烧录项目文件到Air1601开发板中 3.3章节,将本篇文章中演示使用的demo烧录到 Air1601 开发板的 Air6205 模块中。

5、lib 脚本文件:使用 Luatools 烧录时,勾选 添加默认 lib 选项,使用默认 lib 脚本文件;

五、SD 卡软硬件参考

5.1 API 接口介绍

本教程使用 api 接口为:

fatfs_api地址

io_api地址

fs_api地址

注意:

1. luatos 的 api 接口是通用的。

2. 通常只使用 fatfs.mount 挂载 tf/sd 卡,其他操作走 io 库就可以了。

3. 挂载成 fatfs 后,可以通过 fs.fsstat 来获取文件系统信息,或 fs.fsize 来获取文件大小。

5.2 SD 硬件设计

SD 硬件设计参考电路:

TF卡 硬件设计参考电路

六、示例代码和功能展示

6.1 流程介绍

1、搭建好硬件环境

2、Luatools 烧录内核固件和 demo 脚本代码

3、烧录成功后,自动开机运行,查看打印日志,如果正常运行,会打印 SD卡 初始化、文件系统挂载、文件系统信息、写入读取文件等。

6.2 代码解析

6.2.1 主程序 (main.lua)

--[[
@module  main
@summary LuatOS用户应用脚本文件入口,总体调度应用逻辑 
@version 001.999.000
@date    2026.04.21
@author  陈媛媛
@usage
本 Demo 完整覆盖了 TF 卡操作的核心到高级流程,HTTP下载功能包括:
1. 基础操作:
   netdrv_device:配置连接外网使用的网卡,目前支持以下五种选择(五选一)
   (1) netdrv_4g:4G网卡
   (2) netdrv_wifi:WIFI STA网卡
   (3) netdrv_eth_spi:通过SPI外挂CH390H芯片的以太网卡
   (4) netdrv_multiple:支持以上三种网卡,可以配置三种网卡的优先级
   (5) netdrv_pc:pc模拟器上的网卡
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这三位数字必须存在,但是没有任何用处,可以一直写为999
        如果不使用合宙iot.openluat.com进行远程升级,根据自己项目的需求,自定义格式即可
]]
PROJECT = "tfcard"
VERSION = "001.999.000"


-- 在日志中打印项目名和项目版本号
log.info("main", PROJECT, VERSION)



-- 如果内核固件支持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)




--联网说明:
-- 如果只测试TF卡操作,不需要联网,请注释掉下面的网络相关模块
-- 如果需要 HTTP 下载或者上传功能时,需先完成联网配置,请取消注释网络相关模块
-- 加载网络驱动设备功能模块,在netdrv_device.lua文件中修改自己使用的联网方式
-- require"netdrv_device"


--[[在加载以下三个功能时,建议分别打开进行测试,因为文件操作,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()之后后面不要加任何语句!!!!!

6.2.2 TF卡核心演示模块(tfcard_app.lua)

调用fatfs库挂载TF卡到/sd路径

--[[
@module  tfcard_app
@summary TF卡文件操作测试模块
@version 1.0.0
@date    2026.04.23
@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初始化 ##########
    spi_id, pin_cs = 1, 8
    spi.setup(spi_id, nil, 0, 0,8,400000)
    -- 设置片选引脚同一spi总线上的所有从设备在初始化时必须要先拉高CS脚,防止从设备之间互相干扰。
    gpio.setup(pin_cs, 1)

    -- ########## 开始进行tf卡挂载 ##########
    -- 挂载失败默认格式化,
    -- 如无需格式化应改为fatfs.mount(fatfs.SPI, "/sd", spi_id, pin_cs, 24000000, nil, 1, false),
    -- 一般是在测试硬件是否有问题的时候把格式化取消掉
    mount_ok, mount_err = fatfs.mount(fatfs.SPI, "/sd", spi_id, pin_cs, 24000000)
    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) -- 若挂载失败,可以尝试打开调试信息,查找原因.(设置调试模式)

    log.info("文件操作", "===== 开始文件操作 =====")

    dir_path = "/sd/io_test"

    -- 1. 创建目录
    if not io.dexist(dir_path) then
        if io.mkdir(dir_path) then
            log.info("io.mkdir", "目录创建成功", "路径:" .. dir_path)
        else
            log.error("io.mkdir", "目录创建失败", "路径:" .. dir_path)
            goto resource_cleanup
        end
    else
        log.warn("io.mkdir", "目录已存在,跳过创建", "路径:" .. dir_path)
    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)

6.2.3 TF卡大文件httpplus上传模块(http_upload_file.lua)

    --[[
@module http_upload_file
@summary TF卡大文件httpplus上传模块
@version 1.0.0
@date 2026.04.23
@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卡文件系统初始化
    spi_id, pin_cs = 1, 8
    spi.setup(spi_id, nil, 0, 0,8,400000)
    -- 同一spi总线上的所有从设备在初始化时必须要先拉高CS脚,防止从设备之间互相干扰。
    gpio.setup(pin_cs, 1)

    local mount_ok = fatfs.mount(fatfs.SPI, "/sd", spi_id, pin_cs, 24000000)
    if not mount_ok then
        log.error("HTTP上传", "文件系统挂载失败")
        fatfs.unmount("/sd")
        spi.close(spi_id)
        return
    end

    -- 阶段3: 检查要上传的文件是否存在
    -- 替换为实际的文件路径
    local upload_file_path = "/sd/3_23MB.bin" 
    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接口支持单文件上传、多文件上传、单文本上传、多文本上传、单/多文本+单/多文件上传
    -- https://airtest.luatos.com/iot/luat_test_file/add 只支持单文件上传或者单文件+单文本上传
    -- 要求上传的文件name必须使用"f",上传的文本name必须使用"params"
    -- 此处仅演示单文件上传功能,并且"f"不能改成其他名字,否则会出现上传失败的应答
    -- 测试接口的响应说明:
        -- 成功:HTTP 200 OK:{"code":0,"value":"上传成功"};
        -- 失败:HTTP 状态码非 200 OK 或是 200 OK 但 code 不为 0
    -- 如果你自己的http服务支持更多类型的文本/文件混合上传,可以自行添加代码进行测试
    local code, response = httpplus.request({
        url = "https://airtest.luatos.com/iot/luat_test_file/add",
        files = {
            -- 服务器要求文件名必须为"f"
            ["f"] = 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)

6.2.4 http下载文件模块(http_download_file.lua)

--[[
@module http_download_file
@summary http下载文件模块
@version 1.0.0
@date    2026.04.23
@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初始化
    spi_id, pin_cs = 1, 8
    spi.setup(spi_id, nil, 0, 0,8,400000)
    -- 同一spi总线上的所有从设备在初始化时必须要先拉高CS脚,防止从设备之间互相干扰。
    gpio.setup(pin_cs, 1)
    -- 挂载文件系统
    local mount_ok = fatfs.mount(fatfs.SPI, "/sd", spi_id, pin_cs, 24000000)
    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/3_23MB.bin"}).wait()
    -- 其中 "..."为url地址, 支持 http和https, 支持域名, 支持自定义端口。
    local code, headers, body_size = http.request("GET",
                                    "https://cdn.openluat-erp.openluat.com/erp_site_file/product_file/AirM2M_780EHT_V2017_LTE_AT.dfota.bin",
                                    nil, nil, {dst = "/sd/3_23MB.bin"}).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/3_23MB.bin")
        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)

七、日志展示

1、搭建好硬件环境

2、通过Luatools将demo与固件烧录到核心板或开发板中

3、烧录好后,板子开机将会在Luatools上看到如下打印:

1TF卡初始化与挂载
[2026-05-15 11:06:53.763][LTOS/N][000000000.067]:I/user.fatfs.mount 挂载成功 0
[2026-05-15 11:06:53.765][LTOS/N][000000000.068]:I/user.fatfs getfree {"free_sectors":7727312,"total_kb":3863660,"free_kb":3863656,"total_sectors":7727320}
[2026-05-15 11:06:53.767][LTOS/N][000000000.068]:I/user.fs lsmount [{"fs":"soc","path":""},{"fs":"inline","path":"/lua/"},{"fs":"luadb","path":"/luadb/"},{"fs":"ram","path":"/ram/"},{"fs":"fatfs","path":"/sd"}]

(2)文件操作演示

[2026-05-15 11:06:53.769][LTOS/N][000000000.068]:I/user.文件操作 ===== 开始文件操作 =====
[2026-05-15 11:06:53.792][LTOS/N][000000000.118]:I/user.io.mkdir 目录创建成功 路径:/sd/io_test
[2026-05-15 11:06:53.795][LTOS/N][000000000.132]:I/user.文件创建 文件写入成功 路径:/sd/io_test/boottime
[2026-05-15 11:06:53.797][LTOS/N][000000000.133]:I/user.io.exists 文件存在 路径:/sd/io_test/boottime
[2026-05-15 11:06:53.799][LTOS/N][000000000.135]:I/user.io.fileSize 文件大小:41字节 路径:/sd/io_test/boottime
[2026-05-15 11:06:53.823][LTOS/N][000000000.137]:I/user.文件读取 路径:/sd/io_test/boottime 内容:这是io库API文档示例的测试内容
[2026-05-15 11:06:53.825][LTOS/N][000000000.139]:I/user.启动计数 文件内容: 这是io库API文档示例的测试内容 十六进制: E8BF99E698AF696FE5BA93415049E69687E6A1A3E7A4BAE4BE8BE79A84E6B58BE8AF95E58685E5AEB9 82
[2026-05-15 11:06:53.827][LTOS/N][000000000.139]:I/user.启动计数 当前值: 0
[2026-05-15 11:06:53.830][LTOS/N][000000000.139]:I/user.启动计数 更新值: 1
[2026-05-15 11:06:53.831][LTOS/N][000000000.157]:I/user.文件写入 路径:/sd/io_test/boottime 内容: 1
[2026-05-15 11:06:53.854][LTOS/N][000000000.174]:I/user.文件创建 路径:/sd/io_test/test_a 初始内容:ABC
[2026-05-15 11:06:53.856][LTOS/N][000000000.181]:I/user.文件追加 路径:/sd/io_test/test_a 追加内容:def
[2026-05-15 11:06:53.858][LTOS/N][000000000.183]:I/user.文件验证 路径:/sd/io_test/test_a 内容:ABCdef 结果: 成功
[2026-05-15 11:06:53.885][LTOS/N][000000000.200]:I/user.文件创建 路径:/sd/io_test/testline 写入3行文本
[2026-05-15 11:06:53.888][LTOS/N][000000000.202]:I/user.按行读取 路径:/sd/io_test/testline 1: abc
[2026-05-15 11:06:53.890][LTOS/N][000000000.202]:I/user.按行读取 路径:/sd/io_test/testline 2: 123
[2026-05-15 11:06:53.892][LTOS/N][000000000.202]:I/user.按行读取 路径:/sd/io_test/testline 3: wendal
[2026-05-15 11:06:53.896][LTOS/N][000000000.206]:I/user.os.rename 文件重命名成功 原路径:/sd/io_test/test_a 新路径:/sd/io_test/renamed_file.txt
[2026-05-15 11:06:53.898][LTOS/N][000000000.209]:D/fatfs f_open /io_test/test_a 4
[2026-05-15 11:06:53.899][LTOS/N][000000000.209]:D/vfs fopen /sd/io_test/test_a r not found
[2026-05-15 11:06:53.901][LTOS/N][000000000.209]:I/user.验证结果 重命名验证成功 新文件存在 原文件不存在
[2026-05-15 11:06:53.903][LTOS/N][000000000.209]:I/user.目录操作 ===== 开始目录列举 =====
[2026-05-15 11:06:53.905][LTOS/N][000000000.214]:I/user.fs lsdir [{"name":"boottime","size":1,"type":0},{"name":"testline","size":15,"type":0},{"name":"renamed_file.txt","size":6,"type":0}]
[2026-05-15 11:06:53.907][LTOS/N][000000000.222]:I/user.os.remove 文件删除成功 路径:/sd/io_test/renamed_file.txt
[2026-05-15 11:06:53.908][LTOS/N][000000000.223]:D/fatfs f_open /io_test/renamed_file.txt 4
[2026-05-15 11:06:53.910][LTOS/N][000000000.223]:D/vfs fopen /sd/io_test/renamed_file.txt r not found
[2026-05-15 11:06:53.912][LTOS/N][000000000.224]:I/user.验证结果 renamed_file.txt文件删除验证成功
[2026-05-15 11:06:53.916][LTOS/N][000000000.231]:I/user.os.remove testline文件删除成功 路径:/sd/io_test/testline
[2026-05-15 11:06:53.918][LTOS/N][000000000.232]:D/fatfs f_open /io_test/testline 4
[2026-05-15 11:06:53.921][LTOS/N][000000000.232]:D/vfs fopen /sd/io_test/testline r not found
[2026-05-15 11:06:53.922][LTOS/N][000000000.232]:I/user.验证结果 testline文件删除验证成功
[2026-05-15 11:06:53.924][LTOS/N][000000000.240]:I/user.os.remove 文件删除成功 路径:/sd/io_test/boottime
[2026-05-15 11:06:53.925][LTOS/N][000000000.241]:D/fatfs f_open /io_test/boottime 4
[2026-05-15 11:06:53.927][LTOS/N][000000000.241]:D/vfs fopen /sd/io_test/boottime r not found
[2026-05-15 11:06:53.929][LTOS/N][000000000.241]:I/user.验证结果 boottime文件删除验证成功
[2026-05-15 11:06:53.931][LTOS/N][000000000.249]:I/user.io.rmdir 目录删除成功 路径:/sd/io_test
[2026-05-15 11:06:53.933][LTOS/N][000000000.250]:D/fatfs f_open /io_test 4
[2026-05-15 11:06:53.934][LTOS/N][000000000.250]:D/vfs fopen /sd/io_test r not found
[2026-05-15 11:06:53.937][LTOS/N][000000000.250]:I/user.验证结果 目录删除验证成功
[2026-05-15 11:06:53.939][LTOS/N][000000000.250]:I/user.文件操作 ===== 文件操作完成 =====
[2026-05-15 11:06:53.941][LTOS/N][000000000.250]:I/user.结束 开始执行关闭操作...
[2026-05-15 11:06:53.942][LTOS/N][000000000.251]:I/user.文件系统 卸载成功
[2026-05-15 11:06:53.944][LTOS/N][000000000.251]:I/user.SPI接口 已关闭

(3)网络连接与HTTP下载 网络连接与HTTP下载

[2026-05-15 18:48:54.264][CAPP/N][000000000.050]:Uart_ChangeBR 385:uart3 波特率 目标 2000000 实际 2000000
[2026-05-15 18:48:54.266][LTOS/N][000000000.050]:D/airlink 配置UART ID为 3
[2026-05-15 18:48:54.268][LTOS/N][000000000.050]:D/airlink 初始化AirLink
[2026-05-15 18:48:54.270][LTOS/N][000000000.050]:I/user.创建桥接网络设备
[2026-05-15 18:48:54.273][LTOS/N][000000000.051]:D/airlink 启动AirLink UART模式
[2026-05-15 18:48:54.275][CAPP/N][000000000.051]:soc_create_event_task 180:task airlink have 0 isr_event, total 64 static event
[2026-05-15 18:48:54.277][CAPP/N][000000000.056]:soc_create_event_task 180:task uart_transfer have 0 isr_event, total 64 static event
[2026-05-15 18:48:54.280][CAPP/N][000000000.057]:soc_create_event_task 180:task uart_receive have 0 isr_event, total 64 static event
[2026-05-15 18:48:54.282][LTOS/N][000000000.057]:I/netdrv 设置IP[15] 192.168.111.1 255.255.255.0 192.168.111.2 ret 0
[2026-05-15 18:48:54.284][LTOS/N][000000000.057]:I/user.netdrv 订阅socket连接状态变化事件 airlink_4G
[2026-05-15 18:48:54.286][LTOS/N][000000000.057]:I/user.airlink_4G网卡已开启 15
[2026-05-15 18:48:54.289][LTOS/N][000000000.058]:I/user.设置网卡 airlink_4G
[2026-05-15 18:48:54.292][LTOS/N][000000000.058]:I/user.exnetif publish network status airlink_4G 15
[2026-05-15 18:48:54.293][LTOS/N][000000000.064]:W/user.HTTP下载 等待网络连接 15 15
[2026-05-15 18:48:55.249][LTOS/N][000000001.065]:W/user.HTTP下载 等待网络连接 15 15
……
[2026-05-15 18:49:09.661][LTOS/N][000000015.476]:D/airlink 4G代理网卡上线了
[2026-05-15 18:49:09.663][LTOS/N][000000015.476]:D/netdrv 网卡(15)设置为UP
[2026-05-15 18:49:09.666][LTOS/N][000000015.476]:D/net network ready 15, setup dns server
[2026-05-15 18:49:09.668][LTOS/N][000000015.476]:D/netdrv IP_READY 15 192.168.111.1
[2026-05-15 18:49:09.670][LTOS/N][000000015.477]:D/net 设置DNS服务器 id 15 index 0 ip 223.5.5.5
[2026-05-15 18:49:09.673][LTOS/N][000000015.477]:D/net 设置DNS服务器 id 15 index 1 ip 114.114.114.114
[2026-05-15 18:49:09.675][LTOS/N][000000015.477]:I/user.netdrv_4g.ip_ready_func IP_READY 192.168.111.1 255.255.255.0 192.168.111.2 nil
[2026-05-15 18:49:09.677][LTOS/N][000000015.477]:I/user.dnsproxy 开始监听
[2026-05-15 18:49:09.680][LTOS/N][000000015.478]:I/user.HTTP下载 网络已就绪 15 15
[2026-05-15 18:49:09.682][CAPP/N][000000015.478]:spi_set_new_config 386:spi1 目标速度400000 实际速度375000 BR 45
[2026-05-15 18:49:09.685][LTOS/N][000000015.478]:D/fatfs init sdcard at spi=1 cs=8
[2026-05-15 18:49:09.693][CAPP/N][000000015.521]:spi_set_new_config 386:spi1 目标速度24000000 实际速度20000000 BR 20
[2026-05-15 18:49:09.695][LTOS/N][000000015.521]:D/SPI_TF 卡容量 3872256KB
[2026-05-15 18:49:09.698][LTOS/N][000000015.521]:D/SPI_TF sdcard init OK OCR:0xc0ff8000!
[2026-05-15 18:49:09.701][LTOS/N][000000015.525]:I/fatfs mount success at fat32
[2026-05-15 18:49:09.702][LTOS/N][000000015.526]:I/user.HTTP下载 开始下载任务
……
[2026-05-15 18:49:30.504][LTOS/N][000000036.320]:I/http http close 1c3cefe4
[2026-05-15 18:49:30.525][LTOS/N][000000036.321]:I/user.HTTP下载 下载完成 success 200 {"Content-Disposition":"attachment; filename=\"1.mp3\"","Accept-Ranges":"bytes","Date":"Fri, 15 May 2026 10:49:12 GMT","Content-Type":"audio/mpeg","Connection":"close","MD5":"EAA4E71C01B46EDE53D4E4BF9322058D","Vary":"Access-Control-Request-Headers","Content-Length":"411922"} 411922
[2026-05-15 18:49:30.531][LTOS/N][000000036.322]:I/user.HTTP下载 文件大小验证 预期: 411922 实际: 411922
[2026-05-15 18:49:30.536][LTOS/N][000000036.322]:I/user.HTTP下载 资源清理完成

4、(3)网络连接与HTTP上传 +

-15 17:03:50.113][LTOS/N][000000000.016]:I/user.main tfcard 001.999.000
[2026-05-15 17:03:50.114][CAPP/N][000000000.050]:Uart_ChangeBR 385:uart3 波特率 目标 2000000 实际 2000000
[2026-05-15 17:03:50.117][LTOS/N][000000000.050]:D/airlink 配置UART ID为 3
[2026-05-15 17:03:50.119][LTOS/N][000000000.050]:D/airlink 初始化AirLink
[2026-05-15 17:03:50.120][LTOS/N][000000000.050]:I/user.创建桥接网络设备
[2026-05-15 17:03:50.122][LTOS/N][000000000.051]:D/airlink 启动AirLink UART模式
[2026-05-15 17:03:50.124][CAPP/N][000000000.051]:soc_create_event_task 180:task airlink have 0 isr_event, total 64 static event
[2026-05-15 17:03:50.126][CAPP/N][000000000.056]:soc_create_event_task 180:task uart_transfer have 0 isr_event, total 64 static event
[2026-05-15 17:03:50.127][CAPP/N][000000000.056]:soc_create_event_task 180:task uart_receive have 0 isr_event, total 64 static event
[2026-05-15 17:03:50.129][LTOS/N][000000000.057]:I/netdrv 设置IP[15] 192.168.111.1 255.255.255.0 192.168.111.2 ret 0
[2026-05-15 17:03:50.130][LTOS/N][000000000.057]:I/user.netdrv 订阅socket连接状态变化事件 airlink_4G
[2026-05-15 17:03:50.133][LTOS/N][000000000.057]:I/user.airlink_4G网卡已开启 15
[2026-05-15 17:03:50.135][LTOS/N][000000000.057]:I/user.设置网卡 airlink_4G
[2026-05-15 17:03:50.136][LTOS/N][000000000.058]:I/user.exnetif publish network status airlink_4G 15
[2026-05-15 17:03:50.137][LTOS/N][000000000.075]:W/user.HTTP上传 等待网络连接 15 15
[2026-05-15 17:03:51.131][LTOS/N][000000001.076]:W/user.HTTP上传 等待网络连接 15 15
[2026-05-15 17:03:52.117][LTOS/N][000000002.076]:W/user.HTTP上传 等待网络连接 15 15
[2026-05-15 17:03:53.104][LTOS/N][000000003.077]:W/user.HTTP上传 等待网络连接 15 15
[2026-05-15 17:03:54.124][LTOS/N][000000004.077]:W/user.HTTP上传 等待网络连接 15 15
[2026-05-15 17:03:55.110][LTOS/N][000000005.078]:W/user.HTTP上传 等待网络连接 15 15
[2026-05-15 17:03:56.120][LTOS/N][000000006.078]:W/user.HTTP上传 等待网络连接 15 15
[2026-05-15 17:03:57.133][LTOS/N][000000007.079]:W/user.HTTP上传 等待网络连接 15 15
[2026-05-15 17:03:58.118][LTOS/N][000000008.079]:W/user.HTTP上传 等待网络连接 15 15
[2026-05-15 17:03:59.107][LTOS/N][000000009.079]:W/user.HTTP上传 等待网络连接 15 15
[2026-05-15 17:04:00.131][LTOS/N][000000010.080]:W/user.HTTP上传 等待网络连接 15 15
[2026-05-15 17:04:00.979][LTOS/N][000000010.930]:D/airlink 4G代理网卡上线了
[2026-05-15 17:04:00.981][LTOS/N][000000010.930]:D/netdrv 网卡(15)设置为UP
[2026-05-15 17:04:00.982][LTOS/N][000000010.930]:D/net network ready 15, setup dns server
[2026-05-15 17:04:00.984][LTOS/N][000000010.930]:D/netdrv IP_READY 15 192.168.111.1
[2026-05-15 17:04:00.988][LTOS/N][000000010.931]:I/user.dnsproxy 开始监听
[2026-05-15 17:04:00.990][LTOS/N][000000010.931]:D/net 设置DNS服务器 id 15 index 0 ip 223.5.5.5
[2026-05-15 17:04:00.992][LTOS/N][000000010.931]:D/net 设置DNS服务器 id 15 index 1 ip 114.114.114.114
[2026-05-15 17:04:00.993][LTOS/N][000000010.931]:I/user.netdrv_4g.ip_ready_func IP_READY 192.168.111.1 255.255.255.0 192.168.111.2 nil
[2026-05-15 17:04:00.996][LTOS/N][000000010.931]:I/user.HTTP上传 网络已就绪 15 15
[2026-05-15 17:04:00.997][CAPP/N][000000010.932]:spi_set_new_config 386:spi1 目标速度400000 实际速度375000 BR 45
[2026-05-15 17:04:01.000][LTOS/N][000000010.932]:D/fatfs init sdcard at spi=1 cs=8
[2026-05-15 17:04:01.001][CAPP/N][000000010.952]:spi_set_new_config 386:spi1 目标速度24000000 实际速度20000000 BR 20
[2026-05-15 17:04:01.003][LTOS/N][000000010.952]:D/SPI_TF 卡容量 3872256KB
[2026-05-15 17:04:01.004][LTOS/N][000000010.952]:D/SPI_TF sdcard init OK OCR:0xc0ff8000!
[2026-05-15 17:04:01.006][LTOS/N][000000010.954]:I/fatfs mount success at fat32
[2026-05-15 17:04:01.010][LTOS/N][000000010.955]:I/user.HTTP上传 准备上传文件 /sd/1.mp3 大小: 411922 字节
[2026-05-15 17:04:01.012][LTOS/N][000000010.955]:I/user.HTTP上传 开始上传任务
[2026-05-15 17:04:01.013][LTOS/N][000000010.957]:D/socket connect to airtest.openluat.com,2900
[2026-05-15 17:04:01.015][LTOS/N][000000010.957]:D/DNS airtest.openluat.com state 0 id 1 ipv6 0 use dns server0, try 0
[2026-05-15 17:04:01.016][LTOS/N][000000010.957]:D/net adatper 15 dns server 223.5.5.5
[2026-05-15 17:04:01.018][LTOS/N][000000010.958]:D/net dns udp sendto 223.5.5.5:53 from 192.168.111.1
[2026-05-15 17:04:01.117][LTOS/N][000000011.062]:I/DNS dns all done ,now stop
[2026-05-15 17:04:01.119][LTOS/N][000000011.063]:D/net adapter 15 connect 47.96.229.157:2900 TCP
[2026-05-15 17:04:07.213][LTOS/N][000000017.171]:I/user.httpplus 服务器已完成响应
[2026-05-15 17:04:07.215][LTOS/N][000000017.171]:I/user.HTTP上传 上传完成 success 200
[2026-05-15 17:04:07.216][LTOS/N][000000017.172]:I/user.HTTP上传 服务器响应头 {"Content-Type":"text/plain;charset=UTF-8","Connection":"close","Content-Length":"20","Vary":"Access-Control-Request-Headers","Date":"Fri, 15 May 2026 09:04:09 GMT"}
[2026-05-15 17:04:07.218][LTOS/N][000000017.172]:I/user.HTTP上传 服务器响应体长度 20
[2026-05-15 17:04:07.219][LTOS/N][000000017.172]:I/user.HTTP上传 服务器响应内容 uploadFileToStaticOK
[2026-05-15 17:04:07.221][LTOS/N][000000017.172]:I/user.HTTP上传 资源清理完成

八、常见问题

8.1 为什么 air1601 不能识别新购买的 sd 卡

文件系为 FAT32 格式(windows、linux 都可以正常识别),所以非 FAT 格式的 SD 卡会挂载失败,而无法正常识别。

8.2 SD 卡的读写路径是什么?

SD 卡文件访问通过路径前加上"/sd",如果 sd 卡中有一个文件 test.txt ,那这个文件的路径就是"/sd/test.txt"。

8.3 http 下载的文件可以直接保存到 sd 卡里吗?

支持,http.request 接口支持直接下载到文件系统中,下载到 sd 卡中的时候只需要注意路径设置。参考 demo 中注释掉的部分。

8.4 注意事项

建议可以先阅读readme文档以熟悉整体流程,再根据具体需求进行修改。