跳转至

09 AirCAMERA_1040

作者:沈园园 | 最后修改:2026-05-18

一、概述

AirCAMERA _1040 是合宙推出的一款SPI接口30W像素的摄像头的配件板,其中:

  1. 驱动IC:KC6001-V2.0,GC032A;
  2. 30万像素拍照;
  3. 适用于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 文件说明

  1. main.lua:主程序入口文件。
  2. netdrv_4g.lua:联网状态检测模块。
  3. scan_code.lua:扫描码应用DEMO。
  4. take_photo_http_post.lua:执行拍照后上传照片至 air32.com。
  5. 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,

alt text

并将其添加到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/ ;

alt text

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

alt text

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服务器功能。