工程提交

This commit is contained in:
2026-03-31 15:46:04 +08:00
parent 75f512a5b4
commit da4e944bca
2841 changed files with 4822938 additions and 1 deletions

View File

@@ -0,0 +1,425 @@
--[[
@module excamera
@summary excamera扩展库
@version 1.0
@date 2025.10.21
@author 陈取德
@usage
用法实例
注意excamera.lua适用的产品范围
Air780系列、Air700系列、Air8000系列支持SPI摄像头
Air8101系列支持USB摄像、DVP摄像头
合宙所有型号的soc产品都仅支持一路摄像头所以excamera库不需要管理camera id只需要调用摄像头的开关和拍照功能即可
使用excamera库时会有两种应用场景
1、拍照模式使用拍照模式时
按照实际使用的摄像头类型填写配置表 - 创建摄像头excamera.open() - 拍照excamera.photo() - 关闭摄像头 excamera.close()的逻辑使用
2、扫描模式当前USB和DVP摄像头不支持扫描模式仅SPI摄像头可使用
按照实际使用的摄像头类型填写配置表 - 创建摄像头excamera.open() - 扫描excamera.scan() - 关闭摄像头 excamera.close()的逻辑使用
local excamera = require "excamera"
local spi_camera_param = {
id = "gc032a", -- SPI摄像头仅支持"gc032a"、"gc0310"、"bf30a2",请带引号填写
i2c_id = 1, -- 模块上使用的I2C编号
work_mode = 0, -- 工作模式0为拍照模式1为扫描模式
save_path = "ZBUFF", -- 拍照结果存储路径,可用"ZBUFF"交由excamera库内部管理
camera_pwr = 2 , -- 摄像头使能管脚填写GPIO号即可无则填nil
camera_pwdn = 5 , -- 摄像头pwdn开关脚填写GPIO号即可无则填nil
camera_light = 25 -- 摄像头补光灯控制管脚填写GPIO号即可无则填nil
}
local usb_camera_param = {
id = camera.USB , -- 摄像头类型默认camera.USB
sensor_width = 1280, -- 摄像头像素宽度,根据摄像头实际参数填写数值
sensor_height = 720, -- 摄像头像素高度,根据摄像头实际参数填写数值
usb_port = 1 ,
save_path = "/ram/test.jpg"
}
local dvp_camera_param = {
id = camera.DVP, -- 摄像头类型默认camera.DVP
sensor_width = 1280, -- 摄像头像素宽度,根据摄像头实际参数填写数值
sensor_height = 720, -- 摄像头像素高度,根据摄像头实际参数填写数值
save_path = "/ram/test.jpg"
}
sys.taskInit(function()
local camera_id
while true do
sys.waitUntil("ONCE_CAPTURE")
camera_id = excamera.open(spi_camera_param)
log.info("初始化状态", camera_id)
local result ,data = excamera.photo()
log.info("拍完了",data)
excamera.close()
end
end)
sys.run()
]] --
local excamera = {}
local h, w
local camera_id, path, camera_buff, camera_i2c, data, result
local cam_pwr, cam_pwdn, cam_light
-- 设备打开函数:初始化指定类型的摄像头设备
-- 参数camera_param - 摄像头配置参数表包含id、i2c_id、work_mode等配置
-- 返回值成功返回camera_id失败返回false
-- 支持SPI摄像、USB摄像头、DVP摄像头使用
-- 自动处理异步回调函数,将摄像头业务流程改为同步流程
-- 支持ZBUFF处理照片支持文件路径处理照片
function excamera.open(camera_param)
-- 判断摄像头类型是否为字符串类型(用于支持不同型号的摄像头模块)
if type(camera_param.id) == "string" then
-- 判断是否需要管理供电使能
if type(camera_param.camera_pwr) == "number" then
cam_pwr = gpio.setup(camera_param.camera_pwr, 1)
end
-- 判断是否需要管理摄像头pwdn开关
if type(camera_param.camera_pwdn) == "number" then
cam_pwdn = gpio.setup(camera_param.camera_pwdn, 0)
-- 为8000暂时兼容后续版本会移除
sys.wait(10)
end
-- 配置I2C接口用于与摄像头通信
if i2c.setup(camera_param.i2c_id, i2c.FAST) then
-- 保存I2C接口ID到camera_i2c用于局内调用
camera_i2c = camera_param.i2c_id
-- 保护执行配置文件加载并赋值给camera_module便于后续调用配置表信息
local result, camera_module = pcall(require, camera_param.id)
if not result then
log.error("excamera.open", camera_param.id .. ".lua文件加载失败")
return false
end
-- 通过摄像头配置表信息初始化摄像头
camera_id = camera.init(1, 24000000, camera_module.mode, camera_module.is_msb, camera_module.rx_bit,
camera_module.seq_type, camera_module.is_ddr, camera_param.work_mode, camera_param.work_mode,
camera_module.width, camera_module.height)
if not camera_id then
log.error("excamera.open", "camera.init失败")
return false
end
-- 通过I2C向摄像头发送配置信息
for i = 1, #camera_module.init_cmds do
result = i2c.send(camera_param.i2c_id, camera_module.i2c_slave_addr, camera_module.init_cmds[i], 1)
if not result then
log.error("excamera.open", "i2c.send失败")
return false
end
end
else
-- I2C配置失败记录错误日志
log.info("I2C配置错误,请确认I2C接口配置是否正确")
return false
end
else
-- 如果不是SPI摄像头则按照DVP/USB摄像头的初始化方式处理
-- 如果既不是SPI摄像头也不是DVP/USB摄像头则返回错误
if not camera.init(camera_param) then
log.info(
"配置表中“id”参数未配置正确,DVP/USB摄像头请使用camera.USB or camera.DVP这样的常量,不需要加引号,请检查配置表,选择正确类型的配置表填写")
return false
end
camera_id = camera_param.id
end
-- 注册摄像头事件回调处理
camera.on(camera_id, "scanned", function(id, str)
-- 如果返回字符串,表示扫码成功并获得结果
if type(str) == 'string' then
log.info("扫码结果", str)
sys.publish("SCAN_DONE", str)
-- 如果返回false表示摄像头没有有效数据
elseif str == false then
log.error("摄像头没有数据")
-- 如果返回true或数字表示成功捕获到图像文件大小
elseif str == true or type(str) == 'number' then
log.info("摄像头数据", str)
-- 发布CAPTURE_DONE事件通知其他任务拍照已完成
sys.publish("CAPTURE_DONE", true)
end
end)
-- 停止摄像头当前采集,释放内存空间
camera.stop(camera_id)
-- 处理图像保存路径,支持内存缓冲区(ZBUFF)或文件路径
if camera_param.save_path == "ZBUFF" then
-- 根据摄像头型号设置图像分辨率
if camera_param.id == "bf30a2" then
h, w = 240, 320 -- BF30A2摄像头分辨率
elseif camera_param.id == "gc032a" or "gc0310" then
h, w = 640, 480 -- GC032A/GC0310摄像头分辨率
elseif camera_param.id == camera.USB or camera.DVP then
-- USB或DVP摄像头使用传入的分辨率参数
h, w = camera_param.sensor_height, camera_param.sensor_width
end
-- 创建ZBUFF内存缓冲区用于存储图像数据
-- 参数1: 缓冲区大小(宽*高*22字节/像素)
-- 参数2: 对齐方式
camera_buff = zbuff.create(h * w * 2, 0)
if camera_buff == nil then
-- 缓冲区创建失败
log.info("ZBUFF创建失败")
return false
else
-- 缓冲区创建成功保存到path变量
path = camera_buff
end
else
-- 如果是文件路径则赋值到path便于后面调用
path = camera_param.save_path
end
-- 判断是否需要管理摄像头补光灯
if type(camera_param.camera_light) == "number" then
cam_light = gpio.setup(camera_param.camera_light, 0)
end
-- 返回初始化动作结果
return true
end
-- 拍照函数:使用指定摄像头拍摄照片并保存
-- 参数x, y, w, h - 可选,指定拍摄区域的起始坐标和尺寸(裁剪区域)
-- 返回值:成功返回(true, 保存路径)失败返回false
-- 使用ZBUFF处理照片时每次调用该接口为了避免内存爆满会覆盖写入ZBUFF区保证ZBUFF区始终只有一张照片处理上传或者存储后再调用该接口避免照片丢失
function excamera.photo(x, y, w, h)
if not camera_id then
log.info("摄像头初始化失败,请重新确认软硬件配置")
return false
end
-- 开始摄像头图像采集
camera.start(camera_id)
-- 如果使用内存缓冲区保存,重置缓冲区位置指针到开始位置
if type(path) == "userdata" then
camera_buff:seek(0)
end
-- 保护执行打开补光灯,如果上面没有配置补光灯,该函数也不会报错
pcall(cam_light, 1)
log.info("照片存储路径", path)
-- 执行拍照操作,保存到指定路径
if camera.capture(camera_id, path, 1, x, y, w, h) then
-- 等待拍照完成事件超时时间5000ms
result = sys.waitUntil("CAPTURE_DONE", 5000)
-- 保护执行关闭补光灯,如果上面没有配置补光灯,该函数也不会报错
pcall(cam_light, 0)
-- 停止摄像头采集,释放内存空间
camera.stop(camera_id)
if result then
-- 拍照成功
log.info("拍照完成")
else
-- 拍照超时
log.info("拍照成功,无照片生成")
return false
end
else
-- 保护执行关闭补光灯,如果上面没有配置补光灯,该函数也不会报错
pcall(cam_light, 0)
-- 停止摄像头采集,释放内存空间
camera.stop(camera_id)
-- 拍照操作失败
log.info("拍照失败,请重试")
return false
end
-- 返回成功状态和照片保存路径
return true, path
end
-- 扫描函数:使用摄像头进行扫描(如二维码/条形码扫描)
-- 参数扫描时长ms单位毫秒
-- 返回值:成功返回(true, 扫描数据)超时未有扫描结果返回false
function excamera.scan(ms)
if not camera_id then
log.info("摄像头初始化失败,请重新确认软硬件配置")
return false
end
-- 开始摄像头图像采集
camera.start(camera_id)
-- 保护执行打开补光灯,如果上面没有配置补光灯,该函数也不会报错
pcall(cam_light, 1)
-- 等待SCAN_DONE事件超时时间根据用户配置
result, data = sys.waitUntil("SCAN_DONE", ms)
-- 停止摄像头采集,释放内存空间
camera.stop(camera_id)
-- 保护执行关闭补光灯,如果上面没有配置补光灯,该函数也不会报错
pcall(cam_light, 0)
if result then
log.info("扫描完成,扫描结果为:", data)
else
log.info(ms .. "秒内未扫描成功,请将摄像头对准二维码")
return false
end
-- 返回成功状态和扫描到的数据
return true, data
end
-- 录像函数使用指定摄像头录制视频并存入tf卡中
-- 参数:
-- file_path - 视频保存路径,如"/sd/video.mp4"
-- duration - 录制时长,单位毫秒
-- fps - 可选,帧率配置
-- 返回值:成功返回(true, 保存路径)失败返回false
-- 注意在使用此函数前需要先使用excamera.open配置摄像头
-- spi_id,pin_cs
local function fatfs_spi_pin()
local rtos_bsp = rtos.bsp()
if rtos_bsp == "AIR101" then
return 0, pin.PB04
elseif rtos_bsp == "AIR103" then
return 0, pin.PB04
elseif rtos_bsp == "AIR105" then
return 2, pin.PB03
elseif rtos_bsp == "ESP32C3" then
return 2, 7
elseif rtos_bsp == "ESP32S3" then
return 2, 14
elseif rtos_bsp == "EC618" then
return 0, 8
elseif string.find(rtos_bsp,"EC718") then
return 0, 8
elseif string.find(rtos_bsp,"Air810") then
gpio.setup(13, 1, gpio.PULLUP)
gpio.setup(28, 1, gpio.PULLUP)
return 0, 3, fatfs.SDIO
else
log.info("main", "bsp not support")
return
end
end
-- TF卡挂载函数
local function mount_tf_card()
-- 检查TF卡是否已经挂载
local result = io.open("/sd/test.txt", "w")
if result then
result:close()
os.remove("/sd/test.txt")
log.info("excamera.mount_tf_card", "TF卡已经挂载")
return true
end
-- 尝试挂载TF卡
local spi_id, pin_cs, tp = fatfs_spi_pin()
if not spi_id then
log.error("excamera.mount_tf_card", "不支持的平台")
return false
end
-- SPI模式需要初始化SPI总线
if tp and tp == fatfs.SPI then
spi.setup(spi_id, nil, 0, 0, 8, 400 * 1000)
gpio.setup(pin_cs, 1)
end
-- 挂载TF卡
local ret = fatfs.mount(tp or fatfs.SPI, "/sd", spi_id, pin_cs, 24 * 1000 * 1000)
if ret then
log.info("excamera.mount_tf_card", "TF卡挂载成功")
-- 检查空间
local free_info = fatfs.getfree("/sd")
if free_info then
log.info("excamera.mount_tf_card", "剩余空间:", free_info.free_kb/1024, "MB")
end
return true
else
log.error("excamera.mount_tf_card", "TF卡挂载失败")
return false
end
end
function excamera.video(file_path, duration, fps)
if not file_path or not duration then
log.error("excamera.video", "参数错误")
return false
end
if not camera_id then
log.error("excamera.video", "摄像头未初始化")
return false
end
-- 如果文件路径以/sd开头确保TF卡已挂载
if string.sub(file_path, 1, 4) == "/sd/" then
if not mount_tf_card() then
log.error("excamera.video", "TF卡挂载失败无法录制视频")
return false
end
end
log.info("excamera.video", "开始录制视频到", file_path)
-- 如果指定了帧率,则设置摄像头帧率
if fps and fps > 0 then
camera.config(camera_id, camera.CONF_UVC_FPS, fps)
end
-- 打印内存信息
log.info("excamera.video", "lua内存:", rtos.meminfo())
log.info("excamera.video", "sys内存:", rtos.meminfo("sys"))
-- 1. 启动摄像头
if camera.start(camera_id) then
-- 2. 开始MP4录制
if camera.capture(camera_id, file_path, 1) then
-- 3. 等待录制时长
sys.wait(duration)
-- 4. 停止录制
camera.stop(camera_id)
-- 5. 关闭摄像头,释放资源
camera.close(camera_id)
-- 再次打印内存信息
log.info("excamera.video", "lua内存:", rtos.meminfo())
log.info("excamera.video", "sys内存:", rtos.meminfo("sys"))
log.info("excamera.video", "视频录制完成", file_path)
return true, file_path
else
-- 录制启动失败,关闭摄像头
camera.stop(camera_id)
camera.close(camera_id)
log.error("excamera.video", "无法开始录制")
return false
end
else
log.error("excamera.video", "无法启动摄像头")
camera.close(camera_id)
return false
end
end
-- 关闭函数:释放摄像头资源
-- 参数camera_id - 摄像头ID
function excamera.close()
if camera_id then
-- 关闭摄像头,释放摄像头硬件资源
camera.close(camera_id)
end
-- 关闭SPI摄像头时需要关闭I2C接口释放通信总线资源
-- USB和DVP摄像头不需要关闭i2c,所以需要判断摄像头ID返回值USB为32DVP为0SPI为1
if camera_id == 1 then
i2c.close(camera_i2c)
end
-- 保护执行摄像头使能关闭,如果上面没有配置摄像头使能管脚,该函数也不会报错
pcall(cam_pwr, 0)
-- 保护执行摄像头开关关闭,如果上面没有配置摄像头开关管脚,该函数也不会报错
pcall(cam_pwdn, 1)
-- 如果使用了内存缓冲区,释放相关资源
if type(path) == "userdata" then
-- 置空缓冲区引用,便于垃圾回收
camera_buff:free()
camera_buff = nil
path = nil
-- 记录当前系统剩余内存情况
log.info("剩余内存", rtos.meminfo("sys"))
end
return
end
return excamera