| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425 |
- --[[
- @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: 缓冲区大小(宽*高*2,2字节/像素)
- -- 参数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为32,DVP为0,SPI为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
|