跳转至

02 SD卡

作者:王棚嶙

一、TF卡概述

1、SD 卡

1.1 SD 卡简介

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

1.2 SD 卡类型与规格

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

1.3 SD 卡工作原理

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

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

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

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

2.1 设备兼容性

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

2.2 接口与协议兼容性

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

2.3 系统与驱动兼容性

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

2.4 环境与物理兼容性

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

2.5 功能兼容性

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

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

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

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卡的功能

三、演示硬件环境(二选一)

Air6101开发板

1、Air6101开发板一块

1、Air6101_B11开发板一块

2、TYPE-C USB数据线一根

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

4、Air6101开发板和数据线的硬件接线方式为

  • Air6101开发板通过串口小板上的TYPE-C USB口供电。(USB旁边的开关拨到ON)
  • TYPE-C USB数据线直接插到Air6101开发板配套的外接串口小板的的TYPE-C USB座子,另外一端连接电脑USB口

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

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

Air6101开发板淘宝购买链接:[点击购买](https://item.taobao.com/item.htm?id=878723455951&pisk=gqst7cg1nWVg0FhZBs43m2j__F2HWyXa91WSmIAil6Cdh1OMiIOsRv9vBcM65t9XD610QCXb_sIvOt7gQEVZHwCNFFfA3RJXGs1viN43qOWw0nNkDuqlQSP3mRftfd9QhK9SGJY_-d4AbnNuZopj4MFDcsXy7O1IRLRBCKO65JLBhKtXCC9_pB9vUIi1GnwLpKv6CcTX5XtBFKKjGcijOe9XFVgs5dtQpKRBCj1XcJKBTKtXGstjism9MAOrDNTNn-jTCYm-2Mp9WInD9mplX0Y6NuREDmd9GFUGCBnjcMQxuTwy1zkHghAN3dC44c-6kN19dnExAhCNCiTdmWqkWT9fghjQhVO5S1xCfaHjc9K91EjCtfFpNGWC4HT3V8BCY1XNvtDbcpfkOOS6DuwyXhO6vpS0TcOARZsM7nl_6n6phgWcqgCTGbXkMVwLpqu25p5Hce13KTFwNpduL6uquepepQ2Lpqu25pJpZJ8Euq8pL&skuId=5719536796269&spm=a1z10.3-c-s.w4002-24045920836.17.4a756ee582s5vf)

Air6101核心板

1、Air6101核心板一块

2、TYPE-C USB数据线一根

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

4、AirMICROSD_1000配件板一块

5、Air6101核心板和数据线的硬件接线方式为

  • Air6101核心板通过板上的TYPE-C USB口供电。(正面的开关拨到3.3V,背面的开关拨到off)
  • TYPE-C USB数据线直接插到Air6101核心板的TYPE-C USB座子,另外一端连接电脑USB口;

6、Air6101核心板与AirMICROSD_1000配件板直插,对应管脚为

Air6101/Air6101核心板 AirMICROSD_1000配件板
59/3V3 3V3
gnd gnd
9/GPIO6 CD
67/GPIO4 D0
66/GPIO3 CMD
65/GPIO2 CLK

Air6101核心板淘宝购买链接:点击购买

AirMICROSD_1000配件板淘宝购买链接:点击购买

四、准备软件环境

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

1、烧录工具 Luatools

2、内核固件文件(底层 core 固件文件):点击获取底层固件

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

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

准备好软件环境之后,接下来查看如何烧录项目文件到 Air6101 开发板 - luatos@air6101 - 合宙文档中心,将本篇文章中演示使用的项目文件烧录到 Air6101 开发板/核心板中。

五、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 SDIO 硬件设计

SDIO 硬件设计参考电路:

六、演示核心步骤

1、挂载前准备

开启tf卡供电(GPIO13高电平)

 --gpio13为6101TF卡的供电控制引脚,在挂载前需要设置为高电平,不能省略
    gpio.setup(13, 1)

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

2.1 文件系统挂载

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

local mount_ok, mount_err = fatfs.mount(fatfs.SDIO, "/sd", 24 * 1000 * 1000)
    if mount_ok then
        log.info("fatfs.mount", "挂载成功", mount_err)
    else
        log.error("fatfs.mount", "挂载失败", mount_err)
        goto resource_cleanup
    end

2.2 12大文件操作实战

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("文件操作", "===== 文件操作完成 =====")

3、HTTP下载功能 (http_download_file.lua)

local function http_download_file_task()
    -- 阶段1: 网络就绪检测

    -- 要连接的WIFI路由器名称
    local ssid ="茶室-降功耗,找合宙!"
    -- 要连接的WIFI路由器密码
    local password = "Air123456"
    log.info("wifi", ssid, password)
    wlan.init()
    -- 连接WIFI
    wlan.connect(ssid, password, 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())
    -- TF卡供电控制(AIR6101专用)
    gpio.setup(13, 1)  
    -- 挂载文件系统
    local mount_ok = fatfs.mount(fatfs.SDIO , "/sd",  24 * 1000 * 1000)
    if not mount_ok then
        log.error("HTTP下载", "文件系统挂载失败")
        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、安全卸载流程

 ::resource_cleanup::

    log.info("结束", "开始执行关闭操作...")  
    -- 如已挂载需先卸载文件系统
    if mount_ok then
        if fatfs.unmount("/sd") then
            log.info("文件系统", "卸载成功")
        else
            log.error("文件系统", "卸载失败")
        end
    end

七、日志展示

1、搭建好硬件环境 2、通过Luatools将demo与固件烧录到核心板或开发板中 3、烧录好后,板子开机将会在Luatools上看到如下打印:

1TF卡初始化与挂载
[2025-09-14 12:59:05.009] I/user.fatfs.mount  挂载成功  0
[2025-09-14 12:59:05.133] I/user.fatfs  getfree {"free_sectors":244262144,"total_kb":122132480,"free_kb":122131072,"total_sectors":244264960}
[2025-09-14 12:59:05.133] I/user.fs lsmount [{"fs":"lfs2","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.文件系统 卸载成功

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下载 资源清理完成

八、常见问题

1、为什么 air6101 不能识别新购买的 sd 卡

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

2、SD 卡的读写路径是什么?

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

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

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

4、注意事项

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