09 AirCAMERA_1040
作者:沈园园 | 最后修改:2026-05-18
一、概述
AirCAMERA _1040 是合宙推出的一款SPI接口30W像素的摄像头的配件板,其中:
- 驱动IC:KC6001-V2.0,GC032A;
- 30万像素拍照;
- 适用于Air780系列/Air8000系列模组;
二、演示模块概述
本文主要是展示 AirCAMERA_1040 的使用,演示了拍照上传和扫码两种应用。
1、main.lua:主程序入口
2、take_photo_http_post.lua:本地执行拍照后通过 httpplus 扩展库将图片上传至 air32.com
3、netdrv_4g.lua:联网状态检测模块
4、scan_code.lua:扫码应用DEMO,支持一维码和二维码扫描
注意事项:
- 拍照或者扫描模式需要在摄像头初始化时确定
- 如使用拍照模式就无法使用扫描模式,扫描模式同理
- 需要拍照后执行扫描的话需要重新初始化
- 所以拍照和扫描不可同时使用,如需切换模式需重新初始化
三、演示功能概述
1、主程序入口模块(main.lua)
- 初始化项目信息和版本号
- 初始化看门狗,并定时喂狗
- 启动一个循环定时器,每隔 3 秒钟打印一次总内存,实时的已使用内存,历史最高的已使用内存情况方便分析内存使用是否有异常
- 加载 netdrv_4g 4G联网状态检测模块
- 根据需要加载对应模块:
- 加载 take_photo_http_post 模块(通过 require "take_photo_http_post"),拍照后通过httpplus上传到air32.cn
- 加载 photo_to_aircloud 模块(通过 require "photo_to_aircloud"),拍照后通过excloud上传到合宙IOT平台
- 加载 scan_code 模块(通过 require "scan_code"),扫码应用
2、4G联网状态检测模块(netdrv_4g.lua)
- 订阅"IP_READY"消息,收到消息后打印联网成功日志
- 订阅"IP_LOSE"消息,收到消息后打印联网失败日志
3、拍照上传业务模块(take_photo_http_post.lua)
- 每 30 秒触发一次拍照:AirCAMERA_1040_func()
- 每 3 秒打印一次系统和 LUA 的内存信息:memory_check()
- 配置摄像头信息表:spi_camera_param
- 初始化摄像头:excamera.open()
- 执行拍照:excamera.photo()
- 上传照片:httpplus.request()
- 关闭摄像头:excamera.close()
4、扫码应用业务模块(scan_code.lua)
- 每 30 秒触发一次拍照:AirCAMERA_1040_func()
- 每 3 秒打印一次系统和 LUA 的内存信息:memory_check()
- 配置摄像头信息表:spi_camera_param
- 初始化摄像头:excamera.open()
- 执行扫描:excamera.scan()
- 关闭摄像头:excamera.close()
5、拍照上传合宙IOT平台业务模块(photo_to_aircloud.lua)
- 每 60 秒触发一次拍照:AirCAMERA_1040_func()
- 每 3 秒打印一次系统和 LUA 的内存信息:memory_check()
- 配置摄像头信息表:spi_camera_param
- 初始化摄像头:excamera.open()
- 执行拍照:excamera.photo()
- 上传照片:通过 excloud 扩展库上传至合宙IOT平台
- 关闭摄像头:excamera.close()
四、准备硬件环境

1、Air780EPM开发板一块
2、TYPE-C USB数据线一根
3、Air780EPM开发板和数据线的硬件接线方式为
- Air780EPM开发板通过TYPE-C USB口连接TYPE-C USB 数据线,数据线的另外一端连接电脑的USB口;
- 开发板正面的 USB供电/外部供电 拨动开关 拨到USB供电一端;
4、合宙标准配件 AirCAMERA_1040 一个
- AirCAMERA_1040 配件板插入Air780EPM 开发板的SPI摄像头座子中
五、准备软件环境
5.1 软件环境
在开始实践本示例之前,先筹备一下软件环境:
1、烧录工具:Luatools 下载调试工具
2、本demo开发测试时使用的固件为LuatOS-SoC_V2028_Air780EPM,本demo对固件版本没有什么特殊要求,所以你如果要测试本demo时,可以直接使用最新版本的内核固件Air780EPM固件,Air780EHM固件;如果发现最新版本的内核固件测试有问题,可以使用我们开发本demo时使用的内核固件版本来对比测试;
3、脚本文件:https://gitee.com/openLuat/LuatOS/tree/master/module/Air780EPM/demo/accessory_board/AirCAMERA_1040
4、lib脚本文件:使用Luatools烧录时,勾选 添加默认lib 选项,使用默认lib脚本文件
准备好软件环境之后,接下来查看如何烧录项目文件到Air780EPM开发板,将本篇文章中演示使用的项目文件烧录到Air780EPM开发板中。
5.2 API 介绍
excamera 扩展库:https://docs.openluat.com/osapi/ext/excamera/
exmux 扩展库:https://docs.openluat.com/osapi/ext/exmux/
六、程序结构
AirCAMERA_1040/
│── main.lua
│── netdrv_4g.lua
│── scan_code.lua
│── take_photo_http_post.lua
│── photo_to_aircloud.lua
│── readme.md
6.1 文件说明
main.lua:主程序入口文件。netdrv_4g.lua:联网状态检测模块。scan_code.lua:扫描码应用DEMO。take_photo_http_post.lua:执行拍照后上传照片至 air32.com。photo_to_aircloud.lua:拍照后通过 excloud 扩展库上传图片至合宙IOT平台。
七、代码详解
7.1 main.lua
主程序文件 main.lua 是整个项目的入口点。它负责初始化系统环境。
7.2 netdrv_4g.lua
本文件为4G网卡驱动模块,核心业务逻辑为:监听"IP_READY"和"IP_LOSE",在日志中进行打印。
local function ip_ready_func(ip, adapter)
if adapter == socket.LWIP_GP then
log.info("netdrv_4g.ip_ready_func", "IP_READY", socket.localIP(socket.LWIP_GP))
end
end
local function ip_lose_func(adapter)
if adapter == socket.LWIP_GP then
log.warn("netdrv_4g.ip_lose_func", "IP_LOSE")
end
end
-- 此处订阅"IP_READY"和"IP_LOSE"两种消息
-- 在消息的处理函数中,仅仅打印了一些信息,便于实时观察4G网络的连接状态
-- 也可以根据自己的项目需求,在消息处理函数中增加自己的业务逻辑控制,例如可以在连网状态发生改变时更新网络图标
sys.subscribe("IP_READY", ip_ready_func)
sys.subscribe("IP_LOSE", ip_lose_func)
-- 设置默认网卡为socket.LWIP_GP
-- 在Air8000上,内核固件运行起来之后,默认网卡就是socket.LWIP_GP
-- 在单4G网卡使用场景下,下面这一行代码加不加都没有影响,为了和其他网卡驱动模块的代码风格保持一致,所以加上了
socket.dft(socket.LWIP_GP)
7.3 scan_code.lua
使用AirCAMERA_1040 gc032a摄像头完成扫描码任务,每30S触发一次扫描。
-- 摄像头扩展库模块
-- 功能:提供摄像头初始化、扫描和资源管理功能
-- 引入excamera扩展库模块
local excamera = require "excamera"
-- 引入exmux扩展库模块
local exmux = require "exmux"
-- 硬件I2C/SPI配置,当您使用合宙开发板时,请根据具体的开发板版本选择对应的变量,
-- exmux库将会自动处理开发板上的I2C/SPI外设,确保总线通讯正常
-- 当您使用自己的制作的板子,请参考exmux库的文档,配置对应的变量:https://docs.openluat.com/osapi/ext/exmux/
-- local HARDWARE_ENV = "DEV_BOARD_8000_V2.0"
local HARDWARE_ENV = "DEV_BOARD_780_V1.2"
-- local HARDWARE_ENV = "DEV_BOARD_780_V1.3"
-- 扫描功能函数
-- 作用:循环监听扫描事件,执行摄像头初始化、扫描和资源释放
local function scan_code_func()
-- 定义变量用于存储操作结果和数据
local result, data
-- 初始化外设分组开关状态
exmux.setup(HARDWARE_ENV)
-- 无限循环,持续等待扫描事件
while true do
-- 配置gc032a摄像头参数表
local spi_camera_param = {
id = "gc032a", -- SPI摄像头仅支持"gc032a"、"gc0310"、"bf30a2",请带引号填写
i2c_id = 1, -- 模块上使用的I2C编号
work_mode = 1, -- 工作模式,0为拍照模式,1为扫描模式
save_path = nil, -- 扫描结果为字符串返回,使用变量赋值既可
camera_pwr = 2, -- 摄像头使能管脚,填写GPIO号即可,无则填nil
camera_pwdn = 5, -- 摄像头pwdn开关脚,填写GPIO号即可,无则填nil
camera_light = nil -- 摄像头补光灯控制管脚,填写GPIO号即可,无则填nil
}
-- 等待外部触发扫描事件(SCAN_CODE)
sys.waitUntil("SCAN_CODE")
-- 打开外设分组
exmux.open("i2c1")
-- 初始化摄像头,传入配置参数
result = excamera.open(spi_camera_param)
-- 记录摄像头初始化状态
log.info("初始化状态", result)
-- 判断摄像头初始化是否成功,不成功则直接关闭,成功则启动扫描
if result then
-- 执行扫描操作,5秒超时
result, data = excamera.scan(5000)
-- 扫描执行完成则上传,否则关闭摄像头
if result then
log.info("Scan result :", data )
end
end
-- 关闭摄像头,释放资源
excamera.close()
-- 关闭外设分组
exmux.close("i2c1")
end
end
-- 内存检查函数
-- 作用:定期监控系统内存使用情况
local function memory_check()
-- 无限循环,定期检查内存
while true do
-- 等待3秒
sys.wait(3000)
-- 打印系统内存使用信息
log.info("sys ram", rtos.meminfo("sys"))
-- 打印Lua虚拟机内存使用信息
log.info("lua ram", rtos.meminfo("lua"))
end
end
-- AirCAMERA_1040 DEMO应用触发函数,每30S触发一次扫描
local function AirCAMERA_1040_func()
while true do
sys.publish("SCAN_CODE")
sys.wait(30000)
end
end
-- 创建扫描功能任务
-- 作用:在单独的任务中运行扫描逻辑
sys.taskInit(scan_code_func)
-- 创建内存监控任务
-- 作用:在单独的任务中运行内存监控逻辑
sys.taskInit(memory_check)
-- 创建扫描触发任务
-- 作用:每30秒触发一次扫描二维码业务
sys.taskInit(AirCAMERA_1040_func)
7.4 take_photo_http_post.lua
执行拍照后上传照片至 air32.com,每30S触发一次拍照
-- 功能:提供摄像头初始化、拍照和资源管理功能
-- 引入excamera扩展库模块
local excamera = require "excamera"
-- 引入httpplus扩展库模块
local httpplus = require "httpplus"
-- 引入exmux扩展库模块
local exmux = require "exmux"
-- 硬件I2C/SPI配置,当您使用合宙开发板时,请根据具体的开发板版本选择对应的变量,
-- exmux库将会自动处理开发板上的I2C/SPI外设,确保总线通讯正常
-- 当您使用自己的制作的板子,请参考exmux库的文档,配置对应的变量:https://docs.openluat.com/osapi/ext/exmux/
-- local HARDWARE_ENV = "DEV_BOARD_8000_V2.0"
local HARDWARE_ENV = "DEV_BOARD_780_V1.2"
-- local HARDWARE_ENV = "DEV_BOARD_780_V1.3"
-- 定义照片保存方式,有三种类型:
-- 1、ZBUFF保存,输入"ZBUFF"即可,excamera库会自动处理ZBUFF
-- 2、保存到内存文件系统中,路径名需指向/ram/文件夹
-- 3、保存到内置FLASH文件系统中
-- 选择其中一个即可,注释另两个路径变量
local save_method = "ZBUFF"
-- local save_method = "/ram/test.jpg"
-- local save_method = "/test.jpg"
-- 拍照功能函数
-- 作用:循环监听拍照事件,执行摄像头初始化、拍照和资源释放
local function capture_func()
-- 定义变量用于存储操作结果和数据
local result, data
-- 初始化外设分组开关状态
exmux.setup(HARDWARE_ENV)
-- 无限循环,持续等待拍照事件
while true do
-- 配置gc032a摄像头参数表
local spi_camera_param = {
id = "gc032a", -- SPI摄像头仅支持"gc032a"、"gc0310"、"bf30a2",请带引号填写
i2c_id = 1, -- 模块上使用的I2C编号
work_mode = 0, -- 工作模式,0为拍照模式,1为扫描模式
save_path = save_method, -- 拍照结果存储路径,可用"ZBUFF"交由excamera库内部管理
camera_pwr = 2, -- 摄像头使能管脚,填写GPIO号即可,无则填nil
camera_pwdn = 5, -- 摄像头pwdn开关脚,填写GPIO号即可,无则填nil
camera_light = nil -- 摄像头补光灯控制管脚,填写GPIO号即可,无则填nil
}
-- 等待外部触发拍照事件(ONCE_CAPTURE)
sys.waitUntil("ONCE_CAPTURE")
-- 打开外设分组
exmux.open("i2c1")
-- 初始化摄像头,传入配置参数
result = excamera.open(spi_camera_param)
-- 记录摄像头初始化状态
log.info("初始化状态", result)
-- 判断摄像头初始化是否成功,不成功则直接关闭,成功则启动拍照
if result then
-- 执行拍照操作
result, data = excamera.photo()
-- 拍照执行完成则上传,否则关闭摄像头
if result then
-- 通过网卡状态判断WIFI是否连接成功,WIFI连接成功后再运行照片上传任务。
while not socket.adapter(socket.dft()) do
-- 在此处阻塞等待WIFI连接成功的消息"IP_READY",避免联网过快,丢失了"IP_READY"信息而导致一直被卡住。
-- 或者等待30秒超时退出阻塞等待状态
log.warn("tcp_client_main_task_func", "wait IP_READY")
sys.waitUntil("IP_READY", 30000)
end
if type(data) == "userdata" then
data = data:query()
else
data = io.readFile(data)
end
-- 通过网卡(本demo使用的是socket.LWIP_STA网卡)将拍摄到的照片数据result上传到服务器air32.cn
-- 如果上传成功,电脑上浏览器打开https://www.air32.cn/upload/jpg/,打开对应的测试日期目录,点击具体的测试时间照片,可以查看摄像头拍照上传的照片
-- 执行httpplus.request后,等待服务器的http应答,此处会阻塞当前task,等待整个过程成功结束或者出现错误异常结束
-- code表示结果,number类型,详细说明参考API手册,一般来说:
-- 200表示成功
-- 小于0的值表示出错,例如-8表示超时错误
-- 其余结果值参考API手册
local code = httpplus.request({
url = "http://upload.air32.cn/api/upload/jpg",
method = "POST",
body = data
})
-- 打印http传输状态
log.info("http_upload_photo_task_func", "httpplus.request", code)
end
end
-- 判断是否ZBUFF存储方式,如果是文件系统保存则删除本地文件
if save_method ~= "ZBUFF" then
os.remove(spi_camera_param.save_path)
end
-- 关闭摄像头,释放资源
-- 使用ZBUFF存储方式时,close传入true后,excamera内部创建的ZBUFF会缩减至0字节,放出内存但是不释放ZBUFF,便于下次拍照时调用;
-- 重复申请和释放ZBUFF会导致垃圾内存堆积,影响系统内存;
excamera.close(true)
-- 关闭外设分组
exmux.close("i2c1")
end
end
-- 内存检查函数
-- 作用:定期监控系统内存使用情况
local function memory_check()
-- 无限循环,定期检查内存
while true do
-- 等待3秒
sys.wait(3000)
-- 打印系统内存使用信息
log.info("sys ram", rtos.meminfo("sys"))
-- 打印Lua虚拟机内存使用信息
log.info("lua ram", rtos.meminfo("lua"))
end
end
-- AirCAMERA_1040 DEMO应用触发函数,每30S触发一次拍照
local function AirCAMERA_1040_func()
while true do
sys.publish("ONCE_CAPTURE")
sys.wait(30000)
end
end
-- 创建拍照功能任务
-- 作用:在单独的任务中运行拍照逻辑
sys.taskInit(capture_func)
-- 创建内存监控任务
-- 作用:在单独的任务中运行内存监控逻辑
sys.taskInit(memory_check)
-- 创建拍照触发任务
-- 作用:每30秒触发一次拍照上传业务
sys.taskInit(AirCAMERA_1040_func)
7.5 photo_to_aircloud.lua
执行拍照后通过excloud扩展库上传至IOT.Luatos.com,每30S触发一次拍照或单击一次BOOT键拍照
--[[
@module photo_to_aircloud
@summary AirCAMERA_1040 gc032a摄像头拍照上传应用模块
@version 1.0
@date 2026.4.30
@author 陈取德
@usage
本demo主要使用AirCAMERA_1040 gc032a摄像头完成拍照上传任务
Air780EPM的sys ram内存资源只有2.3M,在使用camera功能时,需要非常谨慎,避免内存泄漏导致系统崩溃,需要注意以下情况:
1、exmcamera业务执行时,必须要确保有连续的内存空间,否则会导致excamera.open()初始化失败,需要的空间计算方式为:
( 摄像头像素高 X 摄像头像素宽 X 2 ),用“GC032A”举例,内存空间为:640 * 480 * 2 = 614400 (B) ≈ 620KB
2、当使用ZBUFF方式存储照片,或者将照片放到 /ram/ 路径下保存时,必须确保有更多的内存连续空间,需要的总空间大小计算方式为:
( 摄像头像素高 X 摄像头像素宽 X 3.5 ),用“GC032A”举例,内存空间为:640 * 480 * 3.5 = 1075200 (B) ≈ 1MB
3、本DEMO为常规模式下,摄像头稳定供电使用,DEMO逻辑为excamera.open()初始化完摄像头后,只需要重复调用excamera.photo()拍照即可,不需要反复的初始化关闭摄像头;
4、在实际应用中,务必将摄像头初始化的优先级排前,因为摄像头业务占用内存过大,尽早的初始化可以确保摄像头初始化时拿到足够的连续内存,确保后续拍照时能够正常执行;
5、Air780EPM的内存非常有限,在摄像头需求高的产品设计中,请选择Air780EHM/EGH/EHV、Air8000、Air700ECH这类 8MB + 8MB 的SOC,更大的内存才是摄像头业务稳定运行的最优解;
6、低功耗模式下使用excamera请参考lowpower DEMO,链接:https://docs.openluat.com/air780epm/luatos/app/lowpower/sleep/#_1
]] --
-- 摄像头拍照模块
-- 功能:提供摄像头初始化、拍照和资源管理功能
-- 引入excamera扩展库模块
local excamera = require "excamera"
-- 引入httpplus扩展库模块
local httpplus = require "httpplus"
-- 引入exmux扩展库模块
local exmux = require "exmux"
-- 导入excloud库
local excloud = require "excloud"
-- pm.ioVol(pm.IOVOL_ALL_GPIO, 2800)
-- 配置excloud参数
local project_auth_key = "123456"
-- 硬件I2C/SPI配置,当您使用合宙开发板时,请根据具体的开发板版本选择对应的变量,
-- exmux库将会自动处理开发板上的I2C/SPI外设,确保总线通讯正常
-- 当您使用自己的制作的板子,请参考exmux库的文档,配置对应的变量:https://docs.openluat.com/osapi/ext/exmux/
-- local HARDWARE_ENV = "DEV_BOARD_8000_V2.0"
local HARDWARE_ENV = "DEV_BOARD_780_V1.2"
-- local HARDWARE_ENV = "DEV_BOARD_780_V1.3"
-- 定义照片保存方式,有三种类型:
-- 1、ZBUFF保存,输入"ZBUFF"即可,excamera库会自动处理ZBUFF
-- 2、保存到内存文件系统中,路径名需指向/ram/文件夹
-- 3、保存到内置FLASH文件系统中
-- 选择其中一个即可,注释另两个路径变量
local save_method = "ZBUFF"
-- local save_method = "/ram/test.jpg"
-- local save_method = "/test.jpg"
--[[
excloud事件回调函数
参数:
event: 事件类型字符串
data: 事件数据,根据事件类型不同而不同
事件类型说明:
connect_result: 连接结果
auth_result: 认证结果
disconnect: 断开连接
reconnect_failed: 重连失败
]]
function on_excloud_event(event, data)
-- 打印事件信息
log.info("用户回调函数", event, json.encode(data))
-- 处理连接结果事件
if event == "connect_result" then
if data.success then
log.info("连接成功")
-- 发布连接成功消息,通知其他任务
sys.publish("aircloud_connected")
else
log.info("连接失败: " .. (data.error or "未知错误"))
end
-- 处理认证结果事件
elseif event == "auth_result" then
if data.success then
log.info("认证成功")
else
log.info("认证失败: " .. data.message)
end
-- 处理断开连接事件
elseif event == "disconnect" then
log.warn("与服务器断开连接")
-- 处理重连失败事件
elseif event == "reconnect_failed" then
log.info("重连失败,已尝试 " .. data.count .. " 次")
end
end
-- 注册excloud事件回调函数
excloud.on(on_excloud_event)
--[[
excloud任务函数
功能:
1. 等待网络连接就绪
2. 配置excloud参数
3. 初始化并开启excloud服务
4. 启动自动心跳
]]
local function excloud_task_func()
-- 如果当前时间点设置的默认网卡还没有连接成功,一直在这里循环等待
while not socket.adapter(socket.dft()) do
log.warn("excloud_task_func", "wait IP_READY", socket.dft())
-- 在此处阻塞等待默认网卡连接成功的消息"IP_READY"
-- 或者等待1秒超时退出阻塞等待状态;
-- 注意:此处的1000毫秒超时不要修改的更长;
-- 因为当使用exnetif.set_priority_order配置多个网卡连接外网的优先级时,会隐式的修改默认使用的网卡
-- 当exnetif.set_priority_order的调用时序和此处的socket.adapter(socket.dft())判断时序有可能不匹配
-- 此处的1秒,能够保证,即使时序不匹配,也能1秒钟退出阻塞状态,再去判断socket.adapter(socket.dft())
sys.waitUntil("IP_READY", 1000)
end
-- 检查是否使用默认的示例key
if project_auth_key == "123" or project_auth_key == "123456" then
log.warn("photo_to_aircloud",
"请改为自己的key,如不知道对应key的可以查看main.lua 54-57行的指导进行添加key的操作")
end
local ok, err_msg = excloud.setup({
use_getip = true, -- 使用getip服务
device_type = 1, -- 4G设备
auth_key = project_auth_key, -- 认证密钥
transport = "tcp", -- 使用TCP传输
auto_reconnect = true, -- 自动重连
reconnect_interval = 10, -- 重连间隔(秒)
max_reconnect = 5, -- 最大重连次数
mtn_log_enabled = true, -- 启用运维日志
mtn_log_blocks = 2, -- 日志文件块数
mtn_log_write_way = excloud.MTN_LOG_CACHE_WRITE -- 缓存写入方式
})
-- 检查初始化是否成功
if not ok then
log.info("初始化失败: " .. err_msg)
return
end
log.info("excloud初始化成功")
-- 开启excloud服务
local ok, err_msg = excloud.open()
if not ok then
log.info("开启excloud服务失败: " .. err_msg)
return
end
log.info("excloud服务已开启")
-- 启动自动心跳,默认5分钟一次的心跳
excloud.start_heartbeat()
log.info("自动心跳已启动")
end
--[[
照片上传任务函数
功能:
1. 等待excloud连接建立
2. 等待图片数据
3. 上传图片到云端
4. 处理上传结果
]]
function upload_image_fun(image)
if excloud.status().is_connected then
log.info("开始上传图片")
if image then
local ok, err = excloud.upload_image(image, "test.jpg")
if ok then
log.info("图片上传成功")
return true
else
log.error("图片上传失败:", err)
return false
end
else
log.warn("图片数据为空")
return false
end
end
log.info("excloud连接已断开,等待重连")
return false
end
-- 拍照功能函数
-- 作用:循环监听拍照事件,执行摄像头初始化、拍照和资源释放
local function capture_func()
-- 定义变量用于存储操作结果和数据
local result, data, err
-- 增加重试次数,最多重试5次,避免日志量过大
local retry_count = 0
-- 初始化开发板
exmux.setup(HARDWARE_ENV)
-- 出现异常后重新初始化,最多重试5次
while retry_count < 5 do
-- 配置gc032a摄像头参数表
local spi_camera_param = {
id = "gc032a", -- SPI摄像头仅支持"gc032a"、"gc0310"、"bf30a2",请带引号填写
i2c_id = 1, -- 模块上使用的I2C编号
work_mode = 0, -- 工作模式,0为拍照模式,1为扫描模式
save_path = save_method, -- 拍照结果存储路径,可用"ZBUFF"交由excamera库内部管理
camera_pwr = 2, -- 摄像头使能管脚,填写GPIO号即可,无则填nil
camera_pwdn = 5, -- 摄像头pwdn开关脚,填写GPIO号即可,无则填nil
camera_light = nil -- 摄像头补光灯控制管脚,填写GPIO号即可,无则填nil
}
-- 打开外设分组
exmux.open("i2c1")
-- 初始化摄像头,传入配置参数
result = excamera.open(spi_camera_param)
-- 记录摄像头初始化状态
log.info("初始化状态", result)
-- 判断摄像头初始化是否成功,不成功则直接关闭,成功则启动拍照
-- 无限循环,持续等待拍照事件
while result do
-- 等待外部触发拍照事件(ONCE_CAPTURE)
sys.waitUntil("ONCE_CAPTURE")
-- 执行拍照操作
result, data = excamera.photo()
-- 拍照执行完成则上传,否则关闭摄像头
if result then
-- 通过网卡状态判断WIFI是否连接成功,WIFI连接成功后再运行照片上传任务。
while not socket.adapter(socket.dft()) do
-- 在此处阻塞等待WIFI连接成功的消息"IP_READY",避免联网过快,丢失了"IP_READY"信息而导致一直被卡住。
-- 或者等待30秒超时退出阻塞等待状态
log.warn("tcp_client_main_task_func", "wait IP_READY")
sys.waitUntil("IP_READY", 30000)
end
upload_image_fun(data)
end
-- 判断是否ZBUFF存储方式,如果是文件系统保存则删除本地文件
if save_method ~= "ZBUFF" then
os.remove(spi_camera_param.save_path)
end
end
-- 关闭摄像头,释放资源
-- 使用ZBUFF存储方式时,close传入true后,excamera内部创建的ZBUFF会缩减至0字节,放出内存但是不释放ZBUFF,便于下次拍照时调用;
-- 重复申请和释放ZBUFF会导致垃圾内存堆积,影响系统内存;
excamera.close(true)
-- 关闭外设分组
exmux.close("i2c1")
-- 拍照出错,等待5秒,重试摄像头初始化
sys.wait(5000)
-- 重试次数增加
retry_count = retry_count + 1
log.info("retry_count", retry_count)
end
-- 重试5次后,提示用户检查摄像头连接或重启设备
log.info("camera init failed, please check the camera connection or reboot, retry_count:", retry_count)
end
-- 内存检查函数
-- 作用:定期监控系统内存使用情况
local function memory_check()
-- 无限循环,定期检查内存
while true do
-- 等待3秒
sys.wait(3000)
-- 打印系统内存使用信息
log.info("sys ram", rtos.meminfo("sys"))
-- 打印Lua虚拟机内存使用信息
log.info("lua ram", rtos.meminfo("lua"))
end
end
-- AirCAMERA_1040 DEMO应用触发函数,每30S触发一次拍照
local function AirCAMERA_1040_func()
while true do
sys.wait(30000)
sys.publish("ONCE_CAPTURE")
end
end
-- 按键触发函数,用于手动拍照拍照
local function press_key()
log.info("boot press")
sys.publish("ONCE_CAPTURE")
end
-- 配置BOOT按键引脚为输入模式,下拉电阻,上升沿触发
gpio.setup(0, press_key, gpio.PULLDOWN, gpio.RISING)
gpio.debounce(0, 100, 1)
-- 启动excloud连接任务
sys.taskInit(excloud_task_func)
-- 创建拍照功能任务
-- 作用:在单独的任务中运行拍照逻辑
sys.taskInit(capture_func)
-- 创建内存监控任务
-- 作用:在单独的任务中运行内存监控逻辑
sys.taskInit(memory_check)
-- 创建拍照触发任务
-- 作用:每30秒触发一次拍照上传业务
sys.taskInit(AirCAMERA_1040_func)
八、运行结果展示
8.1 拍照后上传
”take_photo_http_post.lua“ DEMO中,因为Air32.com平台已经不开放使用了,所以该DEMO仅作上传照片至服务器的演示作用,使用时请将上传URL修改为您自己的服务器地址;
如下为”photo_to_aircloud.lua“的DEMO演示结果展示:
1、搭建硬件环境;
2、获取合宙IOT平台的项目KEY,

并将其添加到photo_to_aircloud.lua中;
-- 配置excloud参数
local project_auth_key = "123456"
3、烧录 DEMO 代码;
4、等待30S或者单击一次BOOT键完成拍照后上传IOT.Luatos.com平台,LUATOOLS会有如下打印;
[2026-05-13 15:02:05.725][000000000.336] I/user.exmux 开发板 DEV_BOARD_780_V1.2 初始化成功
[2026-05-13 15:02:05.728][000000000.336] I/user.exmux 设置引脚 pwr2 (23) 为高电平
[2026-05-13 15:02:05.729][000000000.337] I/user.exmux 设置引脚 pwr1 (2) 为高电平
[2026-05-13 15:02:05.731][000000000.337] I/user.exmux 分组 i2c1 打开成功
[2026-05-13 15:02:05.732][000000000.337] I2C_MasterSetup 426:I2C1, Total 65 HCNT 22 LCNT 40
[2026-05-13 15:02:05.734][000000000.338] CSPI_Setup 1924:APB MP 102400000
[2026-05-13 15:02:05.735][000000000.406] I/user.初始化状态 true
[2026-05-13 15:02:06.156][000000001.336] W/user.excloud_task_func wait IP_READY 1 1
[2026-05-13 15:02:06.936][000000002.097] I/mobile sim0 sms ready
[2026-05-13 15:02:06.938][000000002.098] D/mobile cid1, state0
[2026-05-13 15:02:06.939][000000002.099] D/mobile bearer act 0, result 0
[2026-05-13 15:02:06.940][000000002.099] D/mobile NETIF_LINK_ON -> IP_READY
[2026-05-13 15:02:06.942][000000002.100] I/user.netdrv_4g.ip_ready_func IP_READY 10.209.19.118 255.255.255.255 0.0.0.0 nil
[2026-05-13 15:02:06.943][000000002.101] W/user.photo_to_aircloud 请改为自己的key,如不知道对应key的可以查看main.lua 54-57行的指导进行添加key的操作
[2026-05-13 15:02:06.945][000000002.104] I/user.[excloud]4G设备 IMEI: 867920071472378 MUID: 20250604131416A755490A2989432885
[2026-05-13 15:02:06.946][000000002.122] I/user.exmtn 读取索引 1
[2026-05-13 15:02:06.948][000000002.123] I/user.exmtn 读取块数配置 2
[2026-05-13 15:02:06.949][000000002.123] I/user.exmtn 读取写入方式配置 0
[2026-05-13 15:02:06.951][000000002.124] I/user.exmtn 配置变化 false
[2026-05-13 15:02:06.952][000000002.127] I/user.exmtn 配置未变化,文件存在,继续写入
[2026-05-13 15:02:06.954][000000002.131] I/user.exmtn 初始化成功: 每个文件 8.00 KB (2 块 × 4096 字节), 总空间 32.00 KB (4 个文件)
[2026-05-13 15:02:06.955][000000002.131] I/user.[excloud]运维日志初始化成功
[2026-05-13 15:02:06.957][000000002.132] I/user.[excloud]excloud.setup 初始化成功 设备ID: 867920071472378
[2026-05-13 15:02:06.958][000000002.132] I/user.excloud初始化成功
[2026-05-13 15:02:06.960][000000002.132] I/user.[excloud]首次连接,获取服务器信息...
[2026-05-13 15:02:06.961][000000002.133] I/user.[excloud]excloud.getip 类型: 3 key: 123456-867920071472378
[2026-05-13 15:02:06.964][000000002.143] D/socket connect to gps.openluat.com,443
[2026-05-13 15:02:06.965][000000002.143] dns_run 676:gps.openluat.com state 0 id 1 ipv6 0 use dns server2, try 0
[2026-05-13 15:02:06.966][000000002.145] D/mobile TIME_SYNC 0 tm 1778655726
[2026-05-13 15:02:07.006][000000002.185] dns_run 693:dns all done ,now stop
[2026-05-13 15:02:07.851][000000003.031] I/user.httpplus 服务器已完成响应
[2026-05-13 15:02:07.855][000000003.034] I/user.[excloud]excloud.getip响应 HTTP Code: 200 Body: {"msg":"ok","conninfo":{"ipv4":"124.71.128.165","port":9108},"imginfo":{"url":"https://gps.openluat.com/iot/air_up/image","data_...
[2026-05-13 15:02:07.856][000000003.035] I/user.[excloud]获取到TCP/UDP连接信息 host: 124.71.128.165 port: 9108 key: nil
[2026-05-13 15:02:07.858][000000003.036] I/user.[excloud]获取到图片上传信息
[2026-05-13 15:02:07.859][000000003.036] I/user.[excloud]获取到音频上传信息
[2026-05-13 15:02:07.861][000000003.036] I/user.[excloud]获取到运维日志上传信息
[2026-05-13 15:02:07.862][000000003.037] I/user.[excloud]获取到二维码信息
[2026-05-13 15:02:07.864][000000003.037] I/user.[excloud]excloud.getip 更新配置: 124.71.128.165 9108
[2026-05-13 15:02:07.865][000000003.037] I/user.[excloud]excloud.getip 成功: true
[2026-05-13 15:02:07.866][000000003.038] I/user.[excloud]服务器信息获取成功 host: 124.71.128.165 port: 9108 transport: tcp
[2026-05-13 15:02:07.868][000000003.038] I/user.[excloud]获取到二维码信息
[2026-05-13 15:02:07.869][000000003.039] I/user.[excloud]创建TCP连接
[2026-05-13 15:02:07.871][000000003.040] D/socket connect to 124.71.128.165,9108
[2026-05-13 15:02:07.872][000000003.041] I/user.[excloud]TCP连接结果 true false
[2026-05-13 15:02:07.874][000000003.041] I/user.[excloud]excloud service started
[2026-05-13 15:02:07.875][000000003.042] I/user.excloud服务已开启
[2026-05-13 15:02:07.876][000000003.042] I/user.[excloud]excloud 自动心跳已启动,间隔 300 秒
[2026-05-13 15:02:07.878][000000003.043] I/user.自动心跳已启动
[2026-05-13 15:02:07.918][000000003.099] I/user.[excloud]socket cb userdata: 0C183598 33554449 0
[2026-05-13 15:02:07.920][000000003.100] I/user.[excloud]socket TCP连接成功
[2026-05-13 15:02:07.921][000000003.100] I/user.用户回调函数 connect_result {"success":true}
[2026-05-13 15:02:07.923][000000003.100] I/user.连接成功
[2026-05-13 15:02:07.924][000000003.104] I/user.[excloud]构建发送数据 16 3 123456-867920071472378-20250604131416A755490A2989432885
[2026-05-13 15:02:07.926][000000003.105] I/user.[excloud]tlv发送数据长度4 62
[2026-05-13 15:02:07.927][000000003.106] I/user.[excloud]构建消息头 y r7
[2026-05-13 15:02:07.929][000000003.108] I/user.用户回调函数 send_result {"sequence_num":1,"success":true,"error_msg":"Send successful"}
[2026-05-13 15:02:07.930][000000003.109] I/user.[excloud]数据发送成功 78 字节
[2026-05-13 15:02:08.017][000000003.198] I/user.[excloud]socket cb userdata: 0C183598 33554450 0
[2026-05-13 15:02:08.019][000000003.198] I/user.[excloud]socket 发送完成
[2026-05-13 15:02:09.794][000000004.974] I/user.boot press
[2026-05-13 15:02:09.799][000000004.975] CSPI_Rx 2022:block len 7680, total block 80
[2026-05-13 15:02:09.801][000000004.976] I/user.照片存储路径 ZBUFF*: 0C19A668
[2026-05-13 15:02:09.802][000000004.976] luat_camera_capture_config 748:0,0,0,0
[2026-05-13 15:02:10.000][000000005.180] I/user.摄像头数据 43062
[2026-05-13 15:02:10.002][000000005.181] I/user.拍照完成
[2026-05-13 15:02:10.004][000000005.182] I/user.开始上传图片
[2026-05-13 15:02:10.005][000000005.182] I/user.[excloud]开始ZBUFF上传 类型: 1 文件: test.jpg 大小: 43062
[2026-05-13 15:02:10.007][000000005.183] I/user.[excloud]构建发送数据 23 0 0
[2026-05-13 15:02:10.008][000000005.184] I/user.[excloud]构建发送数据 784 0 1
[2026-05-13 15:02:10.010][000000005.186] I/user.[excloud]构建发送数据 785 3 test.jpg
[2026-05-13 15:02:10.011][000000005.187] I/user.[excloud]构建发送数据 786 0 43062
[2026-05-13 15:02:10.013][000000005.188] I/user.[excloud]tlv发送数据长度4 48
[2026-05-13 15:02:10.014][000000005.190] I/user.[excloud]构建消息头 y r7
[2026-05-13 15:02:10.016][000000005.192] I/user.用户回调函数 send_result {"sequence_num":2,"success":true,"error_msg":"Send successful"}
[2026-05-13 15:02:10.017][000000005.192] I/user.[excloud]数据发送成功 64 字节
[2026-05-13 15:02:10.019][000000005.193] I/user.[excloud]开始发送HTTP请求 URL: https://gps.openluat.com/iot/air_up/image
[2026-05-13 15:02:10.021][000000005.199] D/socket connect to gps.openluat.com,443
[2026-05-13 15:02:10.022][000000005.199] dns_run 676:gps.openluat.com state 0 id 2 ipv6 0 use dns server2, try 0
[2026-05-13 15:02:10.148][000000005.328] dns_run 693:dns all done ,now stop
[2026-05-13 15:02:10.198][000000005.378] I/user.[excloud]socket cb userdata: 0C183598 33554450 0
[2026-05-13 15:02:10.199][000000005.379] I/user.[excloud]socket 发送完成
[2026-05-13 15:02:10.888][000000006.068] I/zbuff create large size: 64 kbyte, trigger force GC
[2026-05-13 15:02:12.053][000000007.232] I/user.httpplus 服务器已完成响应
[2026-05-13 15:02:12.057][000000007.235] I/user.[excloud]文件上传响应 HTTP Code: 200 Body: {"info":"iot./iot/air_up/image","code":0,"trace":"iot./iot/air_up/image trcace:","log":"^^^","value":{"uri":"/vsa/aircloud_image...
[2026-05-13 15:02:12.059][000000007.236] I/user.[excloud]文件上传成功 URL: /vsa/aircloud_image/75M7MfNfjKqoXRq47dW38B/2026-05/867920071472378/20260513150211_test.jpg
[2026-05-13 15:02:12.060][000000007.237] I/user.[excloud]构建发送数据 24 0 0
[2026-05-13 15:02:12.062][000000007.238] I/user.[excloud]构建发送数据 784 0 1
[2026-05-13 15:02:12.063][000000007.240] I/user.[excloud]构建发送数据 785 3 test.jpg
[2026-05-13 15:02:12.065][000000007.241] I/user.[excloud]构建发送数据 787 0 0
[2026-05-13 15:02:12.066][000000007.243] I/user.[excloud]tlv发送数据长度4 48
[2026-05-13 15:02:12.068][000000007.244] I/user.[excloud]构建消息头 y r7
[2026-05-13 15:02:12.069][000000007.246] I/user.用户回调函数 send_result {"sequence_num":3,"success":true,"error_msg":"Send successful"}
[2026-05-13 15:02:12.071][000000007.247] I/user.[excloud]数据发送成功 64 字节
[2026-05-13 15:02:12.073][000000007.252] I/user.[excloud]文件上传完成
[2026-05-13 15:02:12.074][000000007.252] I/user.图片上传成功
5、登录 https://iot.luatos.com/ ;

就可以根据您设备的IMEI号和上传时间查看您所上传的照片了;

8.2 扫码功能
1、搭建硬件环境;
2、在main.lua做如下修改,根据需要导入对应模块,只保留一个需要的模块,注释其他两个;
-- 只能选择一个模块使用,取消注释对应模块即可
-- 拍照上传到air32.cn应用DEMO
--require "take_photo_http_post"
-- 拍照上传到合宙IOT平台应用DEMO(通过exmux扩展库)
--require "photo_to_aircloud"
-- 导入scan_code扫描二维码应用DEMO
require "scan_code"
3、烧录 DEMO 代码
4、二维码扫描,打开 https://cli.im/text 网址,生成一个二维码。

等待自动扫描任务完成后,LUATOOLS会有如下打印;
[2025-11-21 15:19:17.310][000000000.269] I2C_MasterSetup 426:I2C1, Total 65 HCNT 22 LCNT 40
[2025-11-21 15:19:17.313][000000000.332] I/user.初始化状态 true
[2025-11-21 15:19:17.316][000000000.332] CSPI_Rx 2000:block len 7680, total block 40
[2025-11-21 15:19:17.319][000000000.541] I/user.扫码结果 Air780EPM
[2025-11-21 15:19:17.323][000000000.542] I/user.扫描完成,扫描结果为: Air780EPM
[2025-11-21 15:19:17.327][000000000.542] I/user.Scan result : Air780EPM
5、一维码扫描,打开https://yours.tools/zh/barcode.html 网址,生成一个一维码。

等待自动扫描任务完成后,LUATOOLS会有如下打印;
[2025-12-11 15:02:16.255][000000030.707] I/user.扫码结果 yours.tools
[2025-12-11 15:02:16.255][000000030.708] I/user.扫描完成,扫描结果为: yours.tools
[2025-12-11 15:02:16.255][000000030.709] I/user.Scan result : yours.tools
九、总结
通过本文学习,你可以学习到使用Air780EPM开发板 +AirCAMERA_1040的扫码和拍照并上传到http服务器功能。