excamera.lua 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425
  1. --[[
  2. @module excamera
  3. @summary excamera扩展库
  4. @version 1.0
  5. @date 2025.10.21
  6. @author 陈取德
  7. @usage
  8. 用法实例
  9. 注意:excamera.lua适用的产品范围
  10. Air780系列、Air700系列、Air8000系列:支持SPI摄像头
  11. Air8101系列:支持USB摄像、DVP摄像头
  12. 合宙所有型号的soc产品都仅支持一路摄像头,所以excamera库不需要管理camera id,只需要调用摄像头的开关和拍照功能即可
  13. 使用excamera库时会有两种应用场景
  14. 1、拍照模式:使用拍照模式时
  15. 按照实际使用的摄像头类型填写配置表 - 创建摄像头excamera.open() - 拍照excamera.photo() - 关闭摄像头 excamera.close()的逻辑使用
  16. 2、扫描模式:当前USB和DVP摄像头不支持扫描模式,仅SPI摄像头可使用
  17. 按照实际使用的摄像头类型填写配置表 - 创建摄像头excamera.open() - 扫描excamera.scan() - 关闭摄像头 excamera.close()的逻辑使用
  18. local excamera = require "excamera"
  19. local spi_camera_param = {
  20. id = "gc032a", -- SPI摄像头仅支持"gc032a"、"gc0310"、"bf30a2",请带引号填写
  21. i2c_id = 1, -- 模块上使用的I2C编号
  22. work_mode = 0, -- 工作模式,0为拍照模式,1为扫描模式
  23. save_path = "ZBUFF", -- 拍照结果存储路径,可用"ZBUFF"交由excamera库内部管理
  24. camera_pwr = 2 , -- 摄像头使能管脚,填写GPIO号即可,无则填nil
  25. camera_pwdn = 5 , -- 摄像头pwdn开关脚,填写GPIO号即可,无则填nil
  26. camera_light = 25 -- 摄像头补光灯控制管脚,填写GPIO号即可,无则填nil
  27. }
  28. local usb_camera_param = {
  29. id = camera.USB , -- 摄像头类型,默认camera.USB
  30. sensor_width = 1280, -- 摄像头像素宽度,根据摄像头实际参数填写数值
  31. sensor_height = 720, -- 摄像头像素高度,根据摄像头实际参数填写数值
  32. usb_port = 1 ,
  33. save_path = "/ram/test.jpg"
  34. }
  35. local dvp_camera_param = {
  36. id = camera.DVP, -- 摄像头类型,默认camera.DVP
  37. sensor_width = 1280, -- 摄像头像素宽度,根据摄像头实际参数填写数值
  38. sensor_height = 720, -- 摄像头像素高度,根据摄像头实际参数填写数值
  39. save_path = "/ram/test.jpg"
  40. }
  41. sys.taskInit(function()
  42. local camera_id
  43. while true do
  44. sys.waitUntil("ONCE_CAPTURE")
  45. camera_id = excamera.open(spi_camera_param)
  46. log.info("初始化状态", camera_id)
  47. local result ,data = excamera.photo()
  48. log.info("拍完了",data)
  49. excamera.close()
  50. end
  51. end)
  52. sys.run()
  53. ]] --
  54. local excamera = {}
  55. local h, w
  56. local camera_id, path, camera_buff, camera_i2c, data, result
  57. local cam_pwr, cam_pwdn, cam_light
  58. -- 设备打开函数:初始化指定类型的摄像头设备
  59. -- 参数:camera_param - 摄像头配置参数表,包含id、i2c_id、work_mode等配置
  60. -- 返回值:成功返回camera_id,失败返回false
  61. -- 支持SPI摄像、USB摄像头、DVP摄像头使用
  62. -- 自动处理异步回调函数,将摄像头业务流程改为同步流程
  63. -- 支持ZBUFF处理照片,支持文件路径处理照片
  64. function excamera.open(camera_param)
  65. -- 判断摄像头类型是否为字符串类型(用于支持不同型号的摄像头模块)
  66. if type(camera_param.id) == "string" then
  67. -- 判断是否需要管理供电使能
  68. if type(camera_param.camera_pwr) == "number" then
  69. cam_pwr = gpio.setup(camera_param.camera_pwr, 1)
  70. end
  71. -- 判断是否需要管理摄像头pwdn开关
  72. if type(camera_param.camera_pwdn) == "number" then
  73. cam_pwdn = gpio.setup(camera_param.camera_pwdn, 0)
  74. -- 为8000暂时兼容,后续版本会移除
  75. sys.wait(10)
  76. end
  77. -- 配置I2C接口,用于与摄像头通信
  78. if i2c.setup(camera_param.i2c_id, i2c.FAST) then
  79. -- 保存I2C接口ID到camera_i2c,用于局内调用
  80. camera_i2c = camera_param.i2c_id
  81. -- 保护执行配置文件加载,并赋值给camera_module,便于后续调用配置表信息
  82. local result, camera_module = pcall(require, camera_param.id)
  83. if not result then
  84. log.error("excamera.open", camera_param.id .. ".lua文件加载失败")
  85. return false
  86. end
  87. -- 通过摄像头配置表信息初始化摄像头
  88. camera_id = camera.init(1, 24000000, camera_module.mode, camera_module.is_msb, camera_module.rx_bit,
  89. camera_module.seq_type, camera_module.is_ddr, camera_param.work_mode, camera_param.work_mode,
  90. camera_module.width, camera_module.height)
  91. if not camera_id then
  92. log.error("excamera.open", "camera.init失败")
  93. return false
  94. end
  95. -- 通过I2C向摄像头发送配置信息
  96. for i = 1, #camera_module.init_cmds do
  97. result = i2c.send(camera_param.i2c_id, camera_module.i2c_slave_addr, camera_module.init_cmds[i], 1)
  98. if not result then
  99. log.error("excamera.open", "i2c.send失败")
  100. return false
  101. end
  102. end
  103. else
  104. -- I2C配置失败,记录错误日志
  105. log.info("I2C配置错误,请确认I2C接口配置是否正确")
  106. return false
  107. end
  108. else
  109. -- 如果不是SPI摄像头,则按照DVP/USB摄像头的初始化方式处理
  110. -- 如果既不是SPI摄像头,也不是DVP/USB摄像头,则返回错误
  111. if not camera.init(camera_param) then
  112. log.info(
  113. "配置表中“id”参数未配置正确,DVP/USB摄像头请使用camera.USB or camera.DVP这样的常量,不需要加引号,请检查配置表,选择正确类型的配置表填写")
  114. return false
  115. end
  116. camera_id = camera_param.id
  117. end
  118. -- 注册摄像头事件回调处理
  119. camera.on(camera_id, "scanned", function(id, str)
  120. -- 如果返回字符串,表示扫码成功并获得结果
  121. if type(str) == 'string' then
  122. log.info("扫码结果", str)
  123. sys.publish("SCAN_DONE", str)
  124. -- 如果返回false,表示摄像头没有有效数据
  125. elseif str == false then
  126. log.error("摄像头没有数据")
  127. -- 如果返回true或数字,表示成功捕获到图像文件大小
  128. elseif str == true or type(str) == 'number' then
  129. log.info("摄像头数据", str)
  130. -- 发布CAPTURE_DONE事件,通知其他任务拍照已完成
  131. sys.publish("CAPTURE_DONE", true)
  132. end
  133. end)
  134. -- 停止摄像头当前采集,释放内存空间
  135. camera.stop(camera_id)
  136. -- 处理图像保存路径,支持内存缓冲区(ZBUFF)或文件路径
  137. if camera_param.save_path == "ZBUFF" then
  138. -- 根据摄像头型号设置图像分辨率
  139. if camera_param.id == "bf30a2" then
  140. h, w = 240, 320 -- BF30A2摄像头分辨率
  141. elseif camera_param.id == "gc032a" or "gc0310" then
  142. h, w = 640, 480 -- GC032A/GC0310摄像头分辨率
  143. elseif camera_param.id == camera.USB or camera.DVP then
  144. -- USB或DVP摄像头使用传入的分辨率参数
  145. h, w = camera_param.sensor_height, camera_param.sensor_width
  146. end
  147. -- 创建ZBUFF内存缓冲区,用于存储图像数据
  148. -- 参数1: 缓冲区大小(宽*高*2,2字节/像素)
  149. -- 参数2: 对齐方式
  150. camera_buff = zbuff.create(h * w * 2, 0)
  151. if camera_buff == nil then
  152. -- 缓冲区创建失败
  153. log.info("ZBUFF创建失败")
  154. return false
  155. else
  156. -- 缓冲区创建成功,保存到path变量
  157. path = camera_buff
  158. end
  159. else
  160. -- 如果是文件路径则赋值到path,便于后面调用
  161. path = camera_param.save_path
  162. end
  163. -- 判断是否需要管理摄像头补光灯
  164. if type(camera_param.camera_light) == "number" then
  165. cam_light = gpio.setup(camera_param.camera_light, 0)
  166. end
  167. -- 返回初始化动作结果
  168. return true
  169. end
  170. -- 拍照函数:使用指定摄像头拍摄照片并保存
  171. -- 参数:x, y, w, h - 可选,指定拍摄区域的起始坐标和尺寸(裁剪区域)
  172. -- 返回值:成功返回(true, 保存路径),失败返回false
  173. -- 使用ZBUFF处理照片时,每次调用该接口为了避免内存爆满,会覆盖写入ZBUFF区,保证ZBUFF区始终只有一张照片,处理上传或者存储后再调用该接口,避免照片丢失
  174. function excamera.photo(x, y, w, h)
  175. if not camera_id then
  176. log.info("摄像头初始化失败,请重新确认软硬件配置")
  177. return false
  178. end
  179. -- 开始摄像头图像采集
  180. camera.start(camera_id)
  181. -- 如果使用内存缓冲区保存,重置缓冲区位置指针到开始位置
  182. if type(path) == "userdata" then
  183. camera_buff:seek(0)
  184. end
  185. -- 保护执行打开补光灯,如果上面没有配置补光灯,该函数也不会报错
  186. pcall(cam_light, 1)
  187. log.info("照片存储路径", path)
  188. -- 执行拍照操作,保存到指定路径
  189. if camera.capture(camera_id, path, 1, x, y, w, h) then
  190. -- 等待拍照完成事件,超时时间5000ms
  191. result = sys.waitUntil("CAPTURE_DONE", 5000)
  192. -- 保护执行关闭补光灯,如果上面没有配置补光灯,该函数也不会报错
  193. pcall(cam_light, 0)
  194. -- 停止摄像头采集,释放内存空间
  195. camera.stop(camera_id)
  196. if result then
  197. -- 拍照成功
  198. log.info("拍照完成")
  199. else
  200. -- 拍照超时
  201. log.info("拍照成功,无照片生成")
  202. return false
  203. end
  204. else
  205. -- 保护执行关闭补光灯,如果上面没有配置补光灯,该函数也不会报错
  206. pcall(cam_light, 0)
  207. -- 停止摄像头采集,释放内存空间
  208. camera.stop(camera_id)
  209. -- 拍照操作失败
  210. log.info("拍照失败,请重试")
  211. return false
  212. end
  213. -- 返回成功状态和照片保存路径
  214. return true, path
  215. end
  216. -- 扫描函数:使用摄像头进行扫描(如二维码/条形码扫描)
  217. -- 参数:扫描时长ms,单位毫秒
  218. -- 返回值:成功返回(true, 扫描数据),超时未有扫描结果返回false
  219. function excamera.scan(ms)
  220. if not camera_id then
  221. log.info("摄像头初始化失败,请重新确认软硬件配置")
  222. return false
  223. end
  224. -- 开始摄像头图像采集
  225. camera.start(camera_id)
  226. -- 保护执行打开补光灯,如果上面没有配置补光灯,该函数也不会报错
  227. pcall(cam_light, 1)
  228. -- 等待SCAN_DONE事件,超时时间根据用户配置
  229. result, data = sys.waitUntil("SCAN_DONE", ms)
  230. -- 停止摄像头采集,释放内存空间
  231. camera.stop(camera_id)
  232. -- 保护执行关闭补光灯,如果上面没有配置补光灯,该函数也不会报错
  233. pcall(cam_light, 0)
  234. if result then
  235. log.info("扫描完成,扫描结果为:", data)
  236. else
  237. log.info(ms .. "秒内未扫描成功,请将摄像头对准二维码")
  238. return false
  239. end
  240. -- 返回成功状态和扫描到的数据
  241. return true, data
  242. end
  243. -- 录像函数:使用指定摄像头录制视频并存入tf卡中
  244. -- 参数:
  245. -- file_path - 视频保存路径,如"/sd/video.mp4"
  246. -- duration - 录制时长,单位毫秒
  247. -- fps - 可选,帧率配置
  248. -- 返回值:成功返回(true, 保存路径),失败返回false
  249. -- 注意:在使用此函数前,需要先使用excamera.open配置摄像头
  250. -- spi_id,pin_cs
  251. local function fatfs_spi_pin()
  252. local rtos_bsp = rtos.bsp()
  253. if rtos_bsp == "AIR101" then
  254. return 0, pin.PB04
  255. elseif rtos_bsp == "AIR103" then
  256. return 0, pin.PB04
  257. elseif rtos_bsp == "AIR105" then
  258. return 2, pin.PB03
  259. elseif rtos_bsp == "ESP32C3" then
  260. return 2, 7
  261. elseif rtos_bsp == "ESP32S3" then
  262. return 2, 14
  263. elseif rtos_bsp == "EC618" then
  264. return 0, 8
  265. elseif string.find(rtos_bsp,"EC718") then
  266. return 0, 8
  267. elseif string.find(rtos_bsp,"Air810") then
  268. gpio.setup(13, 1, gpio.PULLUP)
  269. gpio.setup(28, 1, gpio.PULLUP)
  270. return 0, 3, fatfs.SDIO
  271. else
  272. log.info("main", "bsp not support")
  273. return
  274. end
  275. end
  276. -- TF卡挂载函数
  277. local function mount_tf_card()
  278. -- 检查TF卡是否已经挂载
  279. local result = io.open("/sd/test.txt", "w")
  280. if result then
  281. result:close()
  282. os.remove("/sd/test.txt")
  283. log.info("excamera.mount_tf_card", "TF卡已经挂载")
  284. return true
  285. end
  286. -- 尝试挂载TF卡
  287. local spi_id, pin_cs, tp = fatfs_spi_pin()
  288. if not spi_id then
  289. log.error("excamera.mount_tf_card", "不支持的平台")
  290. return false
  291. end
  292. -- SPI模式需要初始化SPI总线
  293. if tp and tp == fatfs.SPI then
  294. spi.setup(spi_id, nil, 0, 0, 8, 400 * 1000)
  295. gpio.setup(pin_cs, 1)
  296. end
  297. -- 挂载TF卡
  298. local ret = fatfs.mount(tp or fatfs.SPI, "/sd", spi_id, pin_cs, 24 * 1000 * 1000)
  299. if ret then
  300. log.info("excamera.mount_tf_card", "TF卡挂载成功")
  301. -- 检查空间
  302. local free_info = fatfs.getfree("/sd")
  303. if free_info then
  304. log.info("excamera.mount_tf_card", "剩余空间:", free_info.free_kb/1024, "MB")
  305. end
  306. return true
  307. else
  308. log.error("excamera.mount_tf_card", "TF卡挂载失败")
  309. return false
  310. end
  311. end
  312. function excamera.video(file_path, duration, fps)
  313. if not file_path or not duration then
  314. log.error("excamera.video", "参数错误")
  315. return false
  316. end
  317. if not camera_id then
  318. log.error("excamera.video", "摄像头未初始化")
  319. return false
  320. end
  321. -- 如果文件路径以/sd开头,确保TF卡已挂载
  322. if string.sub(file_path, 1, 4) == "/sd/" then
  323. if not mount_tf_card() then
  324. log.error("excamera.video", "TF卡挂载失败,无法录制视频")
  325. return false
  326. end
  327. end
  328. log.info("excamera.video", "开始录制视频到", file_path)
  329. -- 如果指定了帧率,则设置摄像头帧率
  330. if fps and fps > 0 then
  331. camera.config(camera_id, camera.CONF_UVC_FPS, fps)
  332. end
  333. -- 打印内存信息
  334. log.info("excamera.video", "lua内存:", rtos.meminfo())
  335. log.info("excamera.video", "sys内存:", rtos.meminfo("sys"))
  336. -- 1. 启动摄像头
  337. if camera.start(camera_id) then
  338. -- 2. 开始MP4录制
  339. if camera.capture(camera_id, file_path, 1) then
  340. -- 3. 等待录制时长
  341. sys.wait(duration)
  342. -- 4. 停止录制
  343. camera.stop(camera_id)
  344. -- 5. 关闭摄像头,释放资源
  345. camera.close(camera_id)
  346. -- 再次打印内存信息
  347. log.info("excamera.video", "lua内存:", rtos.meminfo())
  348. log.info("excamera.video", "sys内存:", rtos.meminfo("sys"))
  349. log.info("excamera.video", "视频录制完成", file_path)
  350. return true, file_path
  351. else
  352. -- 录制启动失败,关闭摄像头
  353. camera.stop(camera_id)
  354. camera.close(camera_id)
  355. log.error("excamera.video", "无法开始录制")
  356. return false
  357. end
  358. else
  359. log.error("excamera.video", "无法启动摄像头")
  360. camera.close(camera_id)
  361. return false
  362. end
  363. end
  364. -- 关闭函数:释放摄像头资源
  365. -- 参数:camera_id - 摄像头ID
  366. function excamera.close()
  367. if camera_id then
  368. -- 关闭摄像头,释放摄像头硬件资源
  369. camera.close(camera_id)
  370. end
  371. -- 关闭SPI摄像头时需要关闭I2C接口,释放通信总线资源
  372. -- USB和DVP摄像头不需要关闭i2c,所以需要判断摄像头ID返回值,USB为32,DVP为0,SPI为1
  373. if camera_id == 1 then
  374. i2c.close(camera_i2c)
  375. end
  376. -- 保护执行摄像头使能关闭,如果上面没有配置摄像头使能管脚,该函数也不会报错
  377. pcall(cam_pwr, 0)
  378. -- 保护执行摄像头开关关闭,如果上面没有配置摄像头开关管脚,该函数也不会报错
  379. pcall(cam_pwdn, 1)
  380. -- 如果使用了内存缓冲区,释放相关资源
  381. if type(path) == "userdata" then
  382. -- 置空缓冲区引用,便于垃圾回收
  383. camera_buff:free()
  384. camera_buff = nil
  385. path = nil
  386. -- 记录当前系统剩余内存情况
  387. log.info("剩余内存", rtos.meminfo("sys"))
  388. end
  389. return
  390. end
  391. return excamera