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 的供电;
四、软件环境
在开始实践本示例之前,先筹备一下软件环境:
- 烧录工具: Luatools 工具;需要注意的是 luatools 工具版本必须为 3.1.10 及以上版本,否则制作的升级包没办法升级。
- 本demo开发测试时使用的固件为Air8101 V2001 版本固件,本demo对固件版本没有什么特殊要求,所以你如果要测试本demo时,可以直接使用最新版本的内核固件;如果发现最新版本的内核固件测试有问题,可以使用我们开发本demo时使用的内核固件版本来对比测试;
- Python 3 环境:用于运行串口升级的 Python 脚本
- LuatOS 需要的脚本和资源文件
准备好软件环境之后,接下来查看如何烧录项目文件到 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 串口通讯过程说明
串口升级采用简单的文本协议进行握手和数据传输控制:
协议流程:
- 上位机发送:
#FOTA\n - 设备回复:
#FOTA RDY\n - 上位机发送:256 字节数据包
- 设备回复:
#FOTA NEXT\n(请求下一包) - 重复步骤 3-4 直到所有数据发送完成
- 设备回复:
#FOTA OK\n(升级成功) - 设备自动重启
6.2.4 运行日志
串口分段升级日志解读:
- 串口 1 连接,收到#FOTA 起始指令
- 开始分段接收升级包,每次 256 字节,累计 5751 字节
- 所有数据包写入成功,MD5 校验通过
- 升级完成,重启
- 重启后新版本 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 文件系统升级操作步骤
- 修改配置:在
main.lua中取消require("fota_file")的注释,注释掉require("fota_uart") - 制作升级包:按照 6.1 章节流程来制作升级包
- 将升级包文件放到 sd 卡中
- 自动升级:设备启动后会自动挂载 sd 卡为文件系统,检测升级包并执行升级流程
注意:将制作好的升级包修改名字为 update.bin,然后放到 sd 卡中。
6.3.3 运行日志



七、总结
至此,本教程详细介绍了 LuatOS 的两种 FOTA 升级方式。通过文件系统直接升级和串口分段升级,可以满足不同场景下的固件更新需求。
FOTA 升级关键特性:
- 支持完整的升级流程:初始化、数据传输、校验、重启
- 提供多种升级方式适应不同应用场景
- 具备完整性校验机制,确保升级安全可靠
- 支持升级状态查询和错误处理