跳转至

03 fota核心库+文件升级/串口升级

作者:孟伟 | 最后修改:2026-04-09

一、FOTA 功能概述

FOTA 是固件远程升级的简称,用于设备固件的远程更新和维护。LuatOS 提供了灵活的 FOTA 升级方案,支持多种升级方式以适应不同的应用场景。

1.1 FOTA 有什么用?

FOTA 功能为物联网设备提供安全可靠的固件更新能力,主要优势包括:

  • 远程维护:无需现场操作即可完成设备固件更新
  • 故障修复:快速修复已部署设备的软件缺陷
  • 功能升级:为设备增加新功能,提升产品价值
  • 成本节约:大幅降低设备维护和升级成本
  • 安全保障:支持完整性校验,确保升级过程安全可靠

1.2 FOTA 与 FOTA2 选择指南

核心区别总结

fota(底层核心库)

定位: 基础升级,提供最核心的固件写入能力

核心能力:

支持两种写入方式:fota.run() 分段写入 和 fota.file() 文件直接升级

支持内部存储和外部 SPI Flash

提供完整的升级流程控制:init → wait → run/file → isDone → finish

代码特点:

_-- 需要手动控制每个步骤_
fota.init()
while not fota.wait() do sys.wait(100) end
fota.run(buf)  _-- 或 fota.file("/sd__/update.bin__")-- 自行检查状态和重启_
fota2(libfota2 扩展库)

定位: 完整的远程升级解决方案,开箱即用

核心能力:

自动处理 HTTP/HTTPS 网络下载

支持合宙 IoT 平台和自建服务器

内置版本检查、下载、验证全流程

提供详细错误码和回调函数

代码特点:

_-- 一行代码完成升级_
local function fota_cb(ret)
    if ret == 0 then
        log.info("升级包下载成功,重启模块")
        rtos.reboot()
    end
end
libfota2.request(fota_cb, opts)

适用场景推荐

选择 fota 的情况:

需要自定义升级数据源

通过串口接收升级包

通过 MQTT、TCP 等自定义协议传输

从 SD 卡、U 盘等外部存储读取

对升级流程有特殊控制需求

需要在升级前后执行特定操作

需要精细控制数据写入时机

需要自定义进度监控逻辑

资源极度受限环境

设备存储空间极小,内存紧张,无法加载额外库

开发测试阶段

需要调试升级过程的每个环节

需要验证自定义升级方案

选择 fota2 的情况:

标准的 HTTP 远程升级

从服务器下载升级包

使用合宙 IoT 平台服务

需要 HTTPS 安全下载

希望快速实现升级功能

不想处理网络下载细节

需要自动版本检查

希望简单的错误处理

生产环境部署

需要稳定的远程升级方案

需要详细的升级状态反馈

支持定时自动检查更新

实际选择建议

新手用户 → 直接选择 fota2

接口简单,学习成本低

内置完整错误处理

适合大多数物联网应用场景

高级用户 → 根据需求选择

标准网络升级 → fota2

自定义数据传输 → fota + 自定义逻辑

一句话总结:用 fota2 省心省力,用 fota 自由灵活

二、演示功能概述

本教程将使用 Air8101 核心板演示两种 FOTA 升级方式,主要包括:

(1)文件系统直接升级:挂载 sd 卡为文件系统,模组读取 sd 卡中的升级包文件直接升级;

(2)分段升级:通过分片段接收升级包数据并升级,demo 演示通过虚拟串口分段写入然后升级。

三、准备硬件环境

硬件清单:

  • Air8101 核心板一块
  • TYPE-C USB 数据线一根
  • AirMICROSD_1010 配件板,用于测试文件系统升级
  • 串口线一根,用于测试串口分包升级

硬件连接:

  • Air8000 核心板通过 TYPE-C USB 口供电
  • 外部供电/USB 供电拨动开关拨到 USB 供电一端
  • TYPE-C USB 数据线连接开发板和电脑 USB 口

Air8101 核心板和 AirMICROSD_1010 配件板的硬件接线方式为:

Air8101核心板 AirMICROSD_1010配件板
59/3V3 3V3
gnd gnd
65/GPIO2 spi_clk
67/GPIO4 spi_mosi
66/GPIO3 spi_cs
8/GPIO5 spi_miso

  • Air8101 核心板通过 TYPE-C USB 口供电(核心板背面的功耗测试开关拨到 OFF 一端),此种供电方式下,vbat 引脚为 3.3V,可以直接给 AirMICROSD_1010 配件板供电;
  • 为了演示方便,所以 Air8101 核心板上电后直接通过 vbat 引脚给 AirMICROSD_1010 配件板提供了 3.3V 的供电;

四、软件环境

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

  1. 烧录工具: Luatools 工具;需要注意的是 luatools 工具版本必须为 3.1.10 及以上版本,否则制作的升级包没办法升级。
  2. 本demo开发测试时使用的固件为Air8101 V2001 版本固件,本demo对固件版本没有什么特殊要求,所以你如果要测试本demo时,可以直接使用最新版本的内核固件;如果发现最新版本的内核固件测试有问题,可以使用我们开发本demo时使用的内核固件版本来对比测试;
  3. Python 3 环境:用于运行串口升级的 Python 脚本
  4. LuatOS 需要的脚本和资源文件

脚本和资源文件:https://gitee.com/openLuat/LuatOS/tree/master/module/Air8101/demo/fota/fota(%E4%BD%BF%E7%94%A8fota%E6%A0%B8%E5%BF%83%E5%BA%93)

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

五、API 接口说明

详细 FOTA API 文档请参考:https://docs.openluat.com/osapi/core/fota/

六、代码示例介绍

6.1 升级包制作

Air8101 系列升级包制作:

因为此系列都是全量升级,所以在 luatools 中生成一次量产文件就行,生成的量产包如下:

其中 full_fota 文件夹下为 core+script 的全量升级包,script_ota 目录下是单脚本升级的升级包

6.2 串口分段升级

6.2.1 核心代码实现 (fota_uart.lua)

--[[
-- 串口FOTA升级功能
-- 提供通过串口分段接收升级包数据进行固件升级的功能

用法:
1. 先把脚本和固件烧录到模块里, 并确认开机正常
2. 在电脑端操作:进入命令行程序,执行 `python main.py` 进行升级,需要保证升级文件名字为 `fota_uart.bin`,并且和 `main.py` 在同一目录下
    注意:运行`python main.py`需要确保电脑安装了Python环境。
3. 观察luatools的输出和main.py的输出
4. 模块接收正确的升级数据后,会提示1秒后重启
5. 本demo自带的脚本升级包,仅加了一条打印和修改版本号

串口通讯过程说明
串口升级采用简单的文本协议进行握手和数据传输控制:
协议流程:
1. 上位机发送:#FOTA\n
2. 设备回复:#FOTA RDY\n
3. 上位机发送:256字节数据包
4. 设备回复:#FOTA NEXT\n(请求下一包)
5. 重复步骤3-4直到所有数据发送完成
6. 设备回复:#FOTA OK\n(升级成功)
7. 设备自动重启

注意:
- 本demo默认是走main uart串口进行升级
- 升级过程中如果发生错误,串口会自动关闭,设备会重新初始化进入升级模式
- 升级成功设备会自动重启

本文件没有对外接口,直接在main.lua中require "fota_uart"就可以加载运行;
]]

-- 定义所需要的UART编号
uart_id = 1    -- UART1, 通常也是MAIN_UART

-- 全局变量
local uart_zbuff = nil
local uart_fota_state = 0
local uart_rx_counter = 0
local uart_fota_writed = 0

-- 初始化串口 - 开机时自动执行
local function init_uart_fota()
    log.info("FOTA_UART", "开机自动启动串口升级模式")
    -- 初始化串口和缓冲区
    uart_zbuff = zbuff.create(1024)
    uart.setup(uart_id, 115200)
    uart.on(uart_id, "receive", uart_cbfun)
    uart_fota_state = 0
    uart_rx_counter = 0
    uart_fota_writed = 0
    -- 发布事件,唤醒升级任务
    sys.publish("UART_UPGRADE_START")
end

-- 清理资源的函数
local function cleanup_resources()
    if uart_zbuff then
        uart_zbuff:del()
        uart_zbuff = nil
    end
    uart.close(uart_id)  -- 关闭串口
    uart_fota_state = 0
    log.info("FOTA_UART", "资源已清理,串口已关闭")
end

-- 串口接收回调函数
function uart_cbfun(id, len)
    if not uart_zbuff then
        return
    end

    -- 防御缓冲区超标的情况
    if uart_zbuff:used() > 8192 then
        log.warn("fota", "uart_zbuff待处理的数据太多了,强制清空")
        uart_zbuff:del()
    end

    while true do
        local len = uart.rx(id, uart_zbuff)
        if len <= 0 then
            break
        end
        uart_rx_counter = uart_rx_counter + len
        log.info("uart", "收到数据", len, "累计", uart_rx_counter)
        -- 首次收到数据即发布事件,唤醒升级任务
        if uart_fota_state == 0 then
            sys.publish("UART_FOTA")
            log.info("UART_FOTA", "首次收到数据,发布升级信号")
        end
    end
end

-- 串口升级任务
local function uartUpgradeTask()
    local fota_state = 0 -- 0还没开始, 1进行中

    -- 开机时自动启动升级模式
    sys.wait(1000)  -- 等待1秒确保系统稳定
    init_uart_fota()

    while true do
        -- 等待升级启动信号
        sys.waitUntil("UART_UPGRADE_START")
        log.info("FOTA_UART", "升级任务已启动,等待数据...")

        while true do
            -- 等待升级数据到来
            sys.waitUntil("UART_FOTA")
            log.info("UART_FOTA", "等待数据...")

            local used = uart_zbuff and uart_zbuff:used() or 0
            if used > 0 then
                if fota_state == 0 then
                    -- 等待FOTA的状态
                    if used > 5 then
                        local data = uart_zbuff:query()
                        uart_zbuff:del()
                        -- 如果接受到 #FOTA\n 代表数据要来了
                        if data:startsWith("#FOTA") and data:endsWith("\n") then
                            fota_state = 1
                            log.info("fota", "检测到fota起始标记,进入FOTA状态", data)
                            if fota.init() then
                                -- 固件数据发送端应该在收到#FOTA RDY\n之后才开始发送数据
                                uart.write(uart_id, "#FOTA RDY\n")
                            else
                                log.error("FOTA_UART", "FOTA初始化失败")
                                cleanup_resources()
                                break
                            end
                        end
                    end
                else
                    -- 已进入升级状态:把收到的数据喂给fota.run
                    uart_fota_writed = uart_fota_writed + used
                    log.info("准备写入fota包", used, "累计写入", uart_fota_writed)
                    local result, isDone, cache = fota.run(uart_zbuff)
                    log.debug("fota.run", result, isDone, cache)
                    uart_zbuff:del() -- 清空缓冲区

                    if not result then
                        -- 写入失败,退出升级状态并通知上位机
                        log.error("fota", "出错了", result, isDone, cache)
                        uart.write(uart_id, "#FOTA ERR\n")
                        -- 调用fota.finish(false)结束升级流程,参数false表示升级流程失败。
                        fota.finish(false)
                        cleanup_resources()
                        fota_state = 0
                        break
                    elseif isDone then
                        -- 全部数据写入完成,等待底层校验结束
                        local success = false
                        for i = 1, 30 do  -- 最多等待3秒
                            sys.wait(100)
                            local succ, fotaDone = fota.isDone()
                            if not succ then
                                log.error("fota", "校验过程出错")
                                uart.write(uart_id, "#FOTA ERR\n")
                                fota.finish(false)
                                cleanup_resources()
                                fota_state = 0
                                break
                            end
                            if fotaDone then
                                uart_fota_state = 1
                                -- 升级文件成功写入flash中的fota分区,准备重启设备;
                                log.info("fota", "已完成,1s后重启")
                                -- 调用fota.finish(true)结束升级流程,参数true表示正确走完流程。
                                fota.finish(true)
                                -- 反馈给上位机
                                uart.write(uart_id, "#FOTA OK\n")
                                sys.wait(1000)
                                success = true
                                rtos.reboot()
                                break
                            end
                        end
                        if not success then
                            log.error("fota", "校验超时")
                            uart.write(uart_id, "#FOTA ERR\n")
                            fota.finish(false)
                            cleanup_resources()
                            fota_state = 0
                        end
                        break
                    else
                        -- 单包写入成功,通知上位机继续下发
                        log.info("fota", "单包写入完成", used, "等待下一个包")
                        uart.write(uart_id, "#FOTA NEXT\n")
                    end
                end
            end
        end

        -- 重置状态,等待下次升级
        fota_state = 0
        uart_fota_state = 0
        uart_rx_counter = 0
        uart_fota_writed = 0

        -- 升级流程结束后,重新初始化串口等待下次升级
        sys.wait(1000)
        init_uart_fota()
    end
end

-- 启动串口升级任务
sys.taskInit(uartUpgradeTask)

6.2.2 串口升级操作步骤

1、搭建好演示硬件环境

2、修改 demo 脚本代码,确保 main.lua 中已注释 require("fota_file"),取消 require("fota_uart") 的注释

3、制作升级包:按照 6.1 章节流程来制作升级包,将制作好的升级包放在 main.py 同级目录下

4、Luatools 烧录内核固件和修改前的 demo 脚本代码,烧录成功后,自动开机运行。

5、确认设备连接到 uart1 所接的串口工具上。

6、运行 Python 脚本发送升级包:

python main.py -p COM13

7、脚本会自动寻找指定的串口号,发送升级命令并传输 fota_uart.bin 文件,注意自己生成的升级包要改成这个名字。

8、设备接收并验证升级包,升级成功后会自动重启

6.2.3 串口通讯过程说明

串口升级采用简单的文本协议进行握手和数据传输控制:

协议流程:

  1. 上位机发送:#FOTA\n
  2. 设备回复:#FOTA RDY\n
  3. 上位机发送:256 字节数据包
  4. 设备回复:#FOTA NEXT\n(请求下一包)
  5. 重复步骤 3-4 直到所有数据发送完成
  6. 设备回复:#FOTA OK\n(升级成功)
  7. 设备自动重启

6.2.4 运行日志

串口分段升级日志解读

  1. 串口 1 连接,收到#FOTA 起始指令
  2. 开始分段接收升级包,每次 256 字节,累计 5751 字节
  3. 所有数据包写入成功,MD5 校验通过
  4. 升级完成,重启
  5. 重启后新版本 1.0.2 运行,新增日志确认升级成功

结果:串口 FOTA 升级完全成功,版本从 1.0.0 升级到 1.0.2。

Python 脚本输出:

D:\gitee_hz\LuatOS_demo_v2_temp\module\Air8101\demo\fota\fota(使用fota核心库)>python main.py -p COM13
使用串口: COM13
设备响应 b'#FOTA RDY\n'
发送升级包数据 1024
设备响应 b'#FOTA NEXT\n#FOTA NEXT\n'
发送升级包数据 1024
设备响应 b'#FOTA NEXT\n'
发送升级包数据 1024
设备响应 b'#FOTA NEXT\n'
发送升级包数据 1024
设备响应 b'#FOTA NEXT\n'
发送升级包数据 1024
设备响应 b'#FOTA NEXT\n'
发送升级包数据 1024
设备响应 b'#FOTA NEXT\n'
发送升级包数据 1024
设备响应 b'#FOTA NEXT\n'
发送升级包数据 1024
设备响应 b'#FOTA NEXT\n'
发送升级包数据 1024
设备响应 b'#FOTA NEXT\n'
发送升级包数据 1024
设备响应 b'#FOTA NEXT\n'
发送升级包数据 1024
设备响应 b'#FOTA NEXT\n'
发送升级包数据 1024
设备响应 b'#FOTA NEXT\n'
发送升级包数据 1024
设备响应 b'#FOTA NEXT\n'
发送升级包数据 1024
设备响应 b'#FOTA NEXT\n'
发送升级包数据 1024
设备响应 b'#FOTA NEXT\n'
发送升级包数据 1024
设备响应 b'#FOTA NEXT\n'
发送升级包数据 1024
设备响应 b'#FOTA NEXT\n'
发送升级包数据 1024
设备响应 b'#FOTA NEXT\n'
发送升级包数据 1024
设备响应 b'#FOTA NEXT\n'
发送升级包数据 1024
设备响应 b'#FOTA NEXT\n'
发送升级包数据 1024
设备响应 b'#FOTA NEXT\n'
发送升级包数据 1024
设备响应 b'#FOTA NEXT\n'
发送升级包数据 1024
设备响应 b'#FOTA NEXT\n'
发送升级包数据 1024
设备响应 b'#FOTA NEXT\n'
发送升级包数据 1024
设备响应 b'#FOTA NEXT\n'
发送升级包数据 456
设备响应 b'#FOTA OK\n'
发送完毕,退出

6.3 文件系统直接升级

6.3.1 核心代码实现 (fota_file.lua)

--[[
-- 文件系统FOTA升级功能
-- 提供从文件系统直接读取升级包进行固件升级的功能
-- 可以使用luatools工具的烧录系统文件功能将升级包直接烧录到文件系统中,

本文件没有对外接口,直接在main.lua中require "fota_file"就可以加载运行;
]]

local function fileUpgradeTask()
    -- 等待系统稳定后再开始升级
    sys.wait(10000)
    gpio.setup(13, 1) -- TF卡供电控制(AIR8101专用)
    local mount_ok, mount_err = fatfs.mount(fatfs.SDIO, "/sd", 24 * 1000 * 1000)
    if mount_ok then
        log.info("fatfs.mount", "挂载成功", mount_err)
        local data, err = fatfs.getfree("/sd") -- 获取SD卡剩余空间信息
        if data then
            -- table: 若成功会返回table, 否则返回nil
            -- table 中包含 total_sectors(总扇区数量), free_sectors(空闲扇区数量), total_kb(总字节数,单位kb), free_kb(空闲字节数, 单位kb)
            log.info("fatfs", "getfree", json.encode(data))
        else
            -- err: 导致失败的底层返回值
            log.info("fatfs", "err", err)
        end
        log.info("FOTA_FILE", "=== 开始文件系统升级 ===")
    else
        log.error("fatfs.mount", "挂载失败", mount_err)
        log.error("FOTA_FILE", "SD卡挂载失败,无法进行文件系统升级")
        -- 调用fota.finish(false)结束升级流程,参数false表示升级流程失败
        fota.finish(false)
        -- 尝试卸载(如果需要)
        fatfs.unmount("/sd")
        return
    end

    -- 步骤1: 初始化FOTA流程
    log.info("FOTA_FILE", "初始化FOTA...")
    if not fota.init() then
        log.error("FOTA_FILE", "FOTA初始化失败")
        return
    end

    -- 步骤2: 等待底层准备就绪
    log.info("FOTA_FILE", "等待底层准备...")
    -- while not fota.wait() do
    --     sys.wait(100)
    -- end
    log.info("FOTA_FILE", "底层准备就绪")

    -- 步骤3: 从文件系统读取升级包并启动升级
    local filePath = "/sd/update.bin"
    log.info("FOTA_FILE", "开始读取升级文件:", filePath)
    local result, isDone, cache = fota.file(filePath)
    log.info("FOTA_FILE", "升级文件写入flash中的fota分区结果", result, isDone, cache)

    -- 步骤4: 结束写入fota分区
    log.info("FOTA_FILE", "结束写入fota分区...")
    local result, isDone = fota.isDone()
    log.info("FOTA_FILE", "写入fota分区状态", "结果:", result, "完成:", isDone)
    if result then
        -- 步骤5: 处理写入结果
        if isDone then
            -- 升级文件成功写入flash中的fota分区,准备重启设备;
            -- 设备重启后,在初始化阶段的运行过程中会自动应用fota分区中的数据完成升级,最终升级结果可以通过观察日志中的版本号来区分。
            log.info("FOTA_FILE", "升级成功,准备重启设备")
            -- 调用fota.finish(true)结束升级流程,参数true表示正确走完流程。
            fota.finish(true)
            -- 可选:删除升级包文件
            -- os.remove("/update.bin")
            sys.wait(2000)
            rtos.reboot()
        else
            log.error("FOTA_FILE", "升级失败")
            -- -- 调用fota.finish(false)结束升级流程,参数false表示升级流程失败。
            fota.finish(false)
        end
    else
        log.error("FOTA_FILE", "升级失败:检查写入状态失败")
        -- -- 调用fota.finish(false)结束升级流程,参数false表示升级流程失败。
        fota.finish(false)
    end
end

-- 启动文件升级任务
sys.taskInit(fileUpgradeTask)

6.3.2 文件系统升级操作步骤

  1. 修改配置:在 main.lua 中取消 require("fota_file") 的注释,注释掉 require("fota_uart")
  2. 制作升级包:按照 6.1 章节流程来制作升级包
  3. 将升级包文件放到 sd 卡中
  4. 自动升级:设备启动后会自动挂载 sd 卡为文件系统,检测升级包并执行升级流程

注意:将制作好的升级包修改名字为 update.bin,然后放到 sd 卡中。

6.3.3 运行日志

七、总结

至此,本教程详细介绍了 LuatOS 的两种 FOTA 升级方式。通过文件系统直接升级和串口分段升级,可以满足不同场景下的固件更新需求。

FOTA 升级关键特性:

  • 支持完整的升级流程:初始化、数据传输、校验、重启
  • 提供多种升级方式适应不同应用场景
  • 具备完整性校验机制,确保升级安全可靠
  • 支持升级状态查询和错误处理