Browse Source

Merge branch 'master' of https://gitee.com/openLuat/LuatOS

mw 5 tháng trước cách đây
mục cha
commit
bfdac8461a

+ 188 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirVOC_1000/AirVOC_1000.lua

@@ -0,0 +1,188 @@
+--本文件中的主机是指I2C主机,具体指Air780EHV
+--本文件中的从机是指I2C从机,具体指AirVOC_1000配件板上的ags02ma VOC(挥发性有机化合物)气体传感器芯片
+
+local AirVOC_1000 = 
+{
+    -- i2c_id:主机的i2c id;
+}   
+
+-- 从机地址为0x1A
+local slave_address = 0x1A
+
+-- TVOC数据的寄存器地址
+local DATA_REG_ADDR = 0x00
+-- TVOC数据的长度
+local DATA_REG_LEN = 0x05
+
+
+-- 计算数据表data中所有数据元素的crc8校验值
+local function crc8(data)
+    local crc = 0xFF
+    for i = 1, #data do
+        crc = bit.bxor(crc, data[i])
+        for j = 1, 8 do
+            crc = crc * 2
+            if crc >= 0x100 then
+                crc = bit.band(bit.bxor(crc, 0x31), 0xff)
+            end
+        end
+    end
+    return crc
+end
+
+
+-- 读取AirVOC_1000的寄存器中指定长度的数据
+
+--reg:number类型;
+--         表示AirVOC_1000上的寄存器地址;
+--         必须传入,不允许为空;
+
+--返回值:失败返回false;成功返回读取到的指定长度的数据(string类型)
+local function read_register(reg, len)
+    -- 发送寄存器地址
+    i2c.send(AirVOC_1000.i2c_id, slave_address, reg)
+
+    -- sys.wait(20)
+
+    -- 读取应答的数据
+    local data = i2c.recv(AirVOC_1000.i2c_id, slave_address, len)
+
+    -- log.info("read_register", data:toHex())
+
+    -- 读取到的数据为指定的长度,则表示读取成功
+    -- 否则读取失败
+    if type(data)=="string" and data:len()==len then
+        return data
+    else
+        log.error("AirVOC_1000 read_register error", type(data), type(data)=="string" and data:len() or "invalid type", len)
+        return false
+    end
+end
+
+
+--打开AirVOC_1000;
+
+--i2c_id:number类型;
+--        主机使用的I2C ID,用来控制AirVOC_1000;
+--        取值范围:仅支持0和1;
+--        如果没有传入此参数,则默认为0;
+
+--返回值:成功返回true,失败返回false
+function AirVOC_1000.open(i2c_id)
+    --如果i2c_id为nil,则赋值为默认值0
+    if i2c_id==nil then i2c_id=0 end
+
+    --检查参数的合法性
+    if not (i2c_id == 0 or i2c_id == 1) then
+        log.error("AirVOC_1000.open", "invalid i2c_id", i2c_id)
+        return false
+    end
+
+    AirVOC_1000.i2c_id = i2c_id
+    
+    --初始化I2C
+    if i2c.setup(i2c_id, i2c.FAST) ~= 1 then
+        log.error("AirVOC_1000.open", "i2c.setup error", i2c_id)
+        return false
+    end
+
+    return true
+end
+
+--读取TVOC的ppb值;
+--ppb: 代表 parts per billion,即 十亿分之一。 1 ppb TVOC 表示在每 10 亿个体积单位的空气中,含有 1 个体积单位的 TVOC
+
+--返回值:失败返回false;
+--       成功返回ppb值(number类型)
+function AirVOC_1000.get_ppb()
+    --从寄存器DATA_REG_ADDR中读取DATA_REG_LEN长度的数据
+    local raw = read_register(DATA_REG_ADDR, DATA_REG_LEN)
+
+    --读取数据出错
+    if not raw then
+        log.error("AirVOC_1000.get_ppb", "read_register error")
+        return false
+    end
+
+    --检查校验值
+    if crc8({raw:byte(1), raw:byte(2), raw:byte(3), raw:byte(4)}) ~= raw:byte(5) then
+        log.error("AirVOC_1000.get_ppb", "crc error")
+        return false
+    end
+
+    --解析数据: 大端格式
+    local tvoc = (raw:byte(2) << 16) | 
+                 (raw:byte(3) << 8) | 
+                 raw:byte(4)
+    
+    return tvoc
+end
+
+--读取TVOC的ppm值;
+--ppm: 代表 parts per million,即 百万分之一。 1 ppm TVOC 表示在每 100 万个体积单位的空气中,含有 1 个体积单位的 TVOC
+
+--返回值:失败返回false;
+--       成功返回ppb值(number类型)
+function AirVOC_1000.get_ppm()
+    --读取ppb值
+    local ppb = AirVOC_1000.get_ppb()
+
+    --如果ppb读取失败
+    if not ppb then
+        log.error("AirVOC_1000.get_ppm", "get_ppb error")
+        return false
+    end
+
+    --ppb = ppm*1000
+    return ppb/1000
+    
+end
+
+--读取TVOC的空气质量等级;
+-- 根据 TVOC 浓度(通常以 ppb 或 ppm 表示)划分的等级,用于评估室内空气质量的优劣和对人体健康的潜在风险;
+-- 不同国家、地区或机构的标准可能略有差异,但核心划分逻辑相似:浓度越低,等级越好,风险越低。
+
+--返回值:失败返回false;
+--       成功返回空气质量等级值(number类型,数值越小,表示空气质量越好)和空气质量描述(string类型,例如优、良好、轻度污染、中度污染、重度污染)
+--           1:表示优
+--           2:表示良好
+--           3:表示轻度污染
+--           4:表示中度污染
+--           5:表示重度污染
+function AirVOC_1000.get_quality_level()
+    --读取ppb值
+    local ppb = AirVOC_1000.get_ppb()
+
+    --如果ppb读取失败
+    if not ppb then
+        log.error("AirVOC_1000.get_qulity_level", "get_ppb error")
+        return false
+    end
+
+    --根据ppb值计算空气质量等级
+    if ppb < 200 then
+        return 1,"优"
+    elseif ppb < 1000 then
+        return 2,"良好"
+    elseif ppb < 3000 then
+        return 3,"轻度污染"
+    elseif ppb < 5000 then
+        return 4,"中度污染"
+    else
+        return 5,"重度污染"
+    end 
+end
+
+
+--关闭AirVOC_1000
+
+--返回值:成功返回true,失败返回false
+function AirVOC_1000.close()
+    --close接口没有返回值,理论上不会关闭失败
+    i2c.close(AirVOC_1000.i2c_id)
+
+    return true
+end
+
+
+return AirVOC_1000

+ 65 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirVOC_1000/main.lua

@@ -0,0 +1,65 @@
+--[[
+必须定义PROJECT和VERSION变量,Luatools工具会用到这两个变量,远程升级功能也会用到这两个变量
+PROJECT:项目名,ascii string类型
+        可以随便定义,只要不使用,就行
+VERSION:项目版本号,ascii string类型
+        如果使用合宙iot.openluat.com进行远程升级,必须按照"XXX.YYY.ZZZ"三段格式定义:
+            X、Y、Z各表示1位数字,三个X表示的数字可以相同,也可以不同,同理三个Y和三个Z表示的数字也是可以相同,可以不同
+            因为历史原因,YYY这三位数字必须存在,但是没有任何用处,可以一直写为000
+        如果不使用合宙iot.openluat.com进行远程升级,根据自己项目的需求,自定义格式即可
+
+AirVOC_1000是合宙设计生产的一款I2C接口的VOC(挥发性有机化合物)气体传感器配件板;
+主要用于检测甲醛、一氧化碳、可燃气体、酒精、氨气、硫化物、苯系蒸汽、烟雾、其它有害气体的监测;
+本demo演示的核心功能为:
+Air8101核心板+AirVOC_1000配件板,每隔1秒读取1次TVOC空气质量数据;
+更多说明参考本目录下的readme.md文件
+]]
+PROJECT = "AirVOC_1000"
+VERSION = "001.000.000"
+
+
+-- 在日志中打印项目名和项目版本号
+log.info("main", PROJECT, VERSION)
+
+
+-- 如果内核固件支持wdt看门狗功能,此处对看门狗进行初始化和定时喂狗处理
+-- 如果脚本程序死循环卡死,就会无法及时喂狗,最终会自动重启
+if wdt then
+    --配置喂狗超时时间为9秒钟
+    wdt.init(9000)
+    --启动一个循环定时器,每隔3秒钟喂一次狗
+    sys.timerLoopStart(wdt.feed, 3000)
+end
+
+
+-- 如果内核固件支持errDump功能,此处进行配置,【强烈建议打开此处的注释】
+-- 因为此功能模块可以记录并且上传脚本在运行过程中出现的语法错误或者其他自定义的错误信息,可以初步分析一些设备运行异常的问题
+-- 以下代码是最基本的用法,更复杂的用法可以详细阅读API说明文档
+-- 启动errDump日志存储并且上传功能,600秒上传一次
+-- if errDump then
+--     errDump.config(true, 600)
+-- end
+
+
+-- 使用LuatOS开发的任何一个项目,都强烈建议使用远程升级FOTA功能
+-- 可以使用合宙的iot.openluat.com平台进行远程升级
+-- 也可以使用客户自己搭建的平台进行远程升级
+-- 远程升级的详细用法,可以参考fota的demo进行使用
+
+
+-- 启动一个循环定时器
+-- 每隔3秒钟打印一次总内存,实时的已使用内存,历史最高的已使用内存情况
+-- 方便分析内存使用是否有异常
+-- sys.timerLoopStart(function()
+--     log.info("mem.lua", rtos.meminfo())
+--     log.info("mem.sys", rtos.meminfo("sys"))
+--  end, 3000)
+
+ -- 加载voc应用模块
+ require "voc_app"
+
+
+-- 用户代码已结束---------------------------------------------
+-- 结尾总是这一句
+sys.run()
+-- sys.run()之后不要加任何语句!!!!!因为添加的任何语句都不会被执行

+ 59 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirVOC_1000/readme.md

@@ -0,0 +1,59 @@
+
+## 演示功能概述
+
+AirVOC_1000是合宙设计生产的一款I2C接口的VOC(挥发性有机化合物)气体传感器配件板;
+
+主要用于检测甲醛、一氧化碳、可燃气体、酒精、氨气、硫化物、苯系蒸汽、烟雾、其它有害气体的监测;
+
+本demo演示的核心功能为:
+
+Air780EHV核心板+AirVOC_1000配件板,每隔1秒读取1次TVOC空气质量数据;
+
+
+## 核心板+配件板资料
+
+[Air780EHV核心板+配件板相关资料](https://docs.openluat.com/air780ehv/product/shouce/)
+
+
+## 演示硬件环境
+
+![](https://docs.openluat.com/accessory/AirVOC_1000/image/connect_Air780ehv.jpg)
+
+1、Air780EHV核心板
+
+2、AirVOC_1000配件板
+
+3、母对母的杜邦线4根
+
+4、Air780EHV核心板和AirVOC_1000配件板的硬件接线方式为
+
+| Air780EHV核心板 | AirVOC_1000配件板  |
+| ------------ | ------------------ |
+|     3V3     |         3V3        |
+|     GND   |         GND        |
+| 66/I2C1SDA |         SDA        |
+| 67/I2C1SCL |         SCL        |
+
+
+## 演示软件环境
+
+1、Luatools下载调试工具
+
+2、[Air780EHV最新版本的内核固件](https://docs.openluat.com/air780ehv/luatos/firmware/version/)
+
+
+## 演示操作步骤
+
+1、搭建好演示硬件环境
+
+2、不需要修改demo脚本代码
+
+3、Luatools烧录内核固件和demo脚本代码
+
+4、烧录成功后,自动开机运行
+
+5、通过观察Luatools的运行日志,每隔1秒出现一次类似于下面的打印,就表示测试正常
+
+``` lua
+[2025-09-25 17:36:49.445][000007163.359] I/user.空气质量 TVOC: ppb 91, ppm 0.091, 等级 1(优)
+[2025-09-25 17:36:50.452][000007164.363] I/user.空气质量 TVOC: ppb 91, ppm 0.091, 等级 1(优)

+ 36 - 0
module/Air780EHM_Air780EHV_Air780EGH/demo/accessory_board/AirVOC_1000/voc_app.lua

@@ -0,0 +1,36 @@
+--加载AirVOC_1000驱动文件
+local air_voc = require "AirVOC_1000"
+
+
+--每隔1秒读取一次TVOC数据
+local function read_voc_task_func()
+    --打开voc硬件
+    air_voc.open(1)
+
+    while true do
+        --读取TVOC的ppb,ppm,quality_level值
+        local ppb = air_voc.get_ppb()
+        local ppm = air_voc.get_ppm()
+        local level, description = air_voc.get_quality_level()
+        
+        --读取成功
+        if ppb then
+            log.info("空气质量", 
+                string.format("TVOC: ppb %d, ppm %.3f, 等级 %d(%s)", 
+                ppb, ppm, level, description))
+        --读取失败
+        else
+            log.error("空气质量", "读取数据失败")
+        end
+
+        --等待1秒
+        sys.wait(1000)
+    end
+
+    --关闭voc硬件
+    air_voc.close()
+end
+
+--创建一个task,并且运行task的主函数read_voc_task_func
+sys.taskInit(read_voc_task_func)
+

+ 192 - 0
module/Air780EPM/demo/accessory_board/AirVOC_1000/AirVOC_1000.lua

@@ -0,0 +1,192 @@
+--本文件中的主机是指I2C主机,具体指Air780EPM
+--本文件中的从机是指I2C从机,具体指AirVOC_1000配件板上的ags02ma VOC(挥发性有机化合物)气体传感器芯片
+
+local AirVOC_1000 = 
+{
+    -- i2c_id:主机的i2c id;
+}   
+
+-- 从机地址为0x1A
+local slave_address = 0x1A
+
+-- TVOC数据的寄存器地址
+local DATA_REG_ADDR = 0x00
+-- TVOC数据的长度
+local DATA_REG_LEN = 0x05
+
+--电平设为3.3v
+pm.ioVol(pm.IOVOL_ALL_GPIO, 3300)
+--设置gpio2输出,给camera_sda、camera_scl引脚提供上拉
+gpio.setup(2, 1)
+
+-- 计算数据表data中所有数据元素的crc8校验值
+local function crc8(data)
+    local crc = 0xFF
+    for i = 1, #data do
+        crc = bit.bxor(crc, data[i])
+        for j = 1, 8 do
+            crc = crc * 2
+            if crc >= 0x100 then
+                crc = bit.band(bit.bxor(crc, 0x31), 0xff)
+            end
+        end
+    end
+    return crc
+end
+
+
+-- 读取AirVOC_1000的寄存器中指定长度的数据
+
+--reg:number类型;
+--         表示AirVOC_1000上的寄存器地址;
+--         必须传入,不允许为空;
+
+--返回值:失败返回false;成功返回读取到的指定长度的数据(string类型)
+local function read_register(reg, len)
+    -- 发送寄存器地址
+    i2c.send(AirVOC_1000.i2c_id, slave_address, reg)
+
+    -- sys.wait(20)
+
+    -- 读取应答的数据
+    local data = i2c.recv(AirVOC_1000.i2c_id, slave_address, len)
+
+    -- log.info("read_register", data:toHex())
+
+    -- 读取到的数据为指定的长度,则表示读取成功
+    -- 否则读取失败
+    if type(data)=="string" and data:len()==len then
+        return data
+    else
+        log.error("AirVOC_1000 read_register error", type(data), type(data)=="string" and data:len() or "invalid type", len)
+        return false
+    end
+end
+
+
+--打开AirVOC_1000;
+
+--i2c_id:number类型;
+--        主机使用的I2C ID,用来控制AirVOC_1000;
+--        取值范围:仅支持0和1;
+--        如果没有传入此参数,则默认为0;
+
+--返回值:成功返回true,失败返回false
+function AirVOC_1000.open(i2c_id)
+    --如果i2c_id为nil,则赋值为默认值0
+    if i2c_id==nil then i2c_id=0 end
+
+    --检查参数的合法性
+    if not (i2c_id == 0 or i2c_id == 1) then
+        log.error("AirVOC_1000.open", "invalid i2c_id", i2c_id)
+        return false
+    end
+
+    AirVOC_1000.i2c_id = i2c_id
+    
+    --初始化I2C
+    if i2c.setup(i2c_id, i2c.FAST) ~= 1 then
+        log.error("AirVOC_1000.open", "i2c.setup error", i2c_id)
+        return false
+    end
+
+    return true
+end
+
+--读取TVOC的ppb值;
+--ppb: 代表 parts per billion,即 十亿分之一。 1 ppb TVOC 表示在每 10 亿个体积单位的空气中,含有 1 个体积单位的 TVOC
+
+--返回值:失败返回false;
+--       成功返回ppb值(number类型)
+function AirVOC_1000.get_ppb()
+    --从寄存器DATA_REG_ADDR中读取DATA_REG_LEN长度的数据
+    local raw = read_register(DATA_REG_ADDR, DATA_REG_LEN)
+
+    --读取数据出错
+    if not raw then
+        log.error("AirVOC_1000.get_ppb", "read_register error")
+        return false
+    end
+
+    --检查校验值
+    if crc8({raw:byte(1), raw:byte(2), raw:byte(3), raw:byte(4)}) ~= raw:byte(5) then
+        log.error("AirVOC_1000.get_ppb", "crc error")
+        return false
+    end
+
+    --解析数据: 大端格式
+    local tvoc = (raw:byte(2) << 16) | 
+                 (raw:byte(3) << 8) | 
+                 raw:byte(4)
+    
+    return tvoc
+end
+
+--读取TVOC的ppm值;
+--ppm: 代表 parts per million,即 百万分之一。 1 ppm TVOC 表示在每 100 万个体积单位的空气中,含有 1 个体积单位的 TVOC
+
+--返回值:失败返回false;
+--       成功返回ppb值(number类型)
+function AirVOC_1000.get_ppm()
+    --读取ppb值
+    local ppb = AirVOC_1000.get_ppb()
+
+    --如果ppb读取失败
+    if not ppb then
+        log.error("AirVOC_1000.get_ppm", "get_ppb error")
+        return false
+    end
+
+    --ppb = ppm*1000
+    return ppb/1000
+    
+end
+
+--读取TVOC的空气质量等级;
+-- 根据 TVOC 浓度(通常以 ppb 或 ppm 表示)划分的等级,用于评估室内空气质量的优劣和对人体健康的潜在风险;
+-- 不同国家、地区或机构的标准可能略有差异,但核心划分逻辑相似:浓度越低,等级越好,风险越低。
+
+--返回值:失败返回false;
+--       成功返回空气质量等级值(number类型,数值越小,表示空气质量越好)和空气质量描述(string类型,例如优、良好、轻度污染、中度污染、重度污染)
+--           1:表示优
+--           2:表示良好
+--           3:表示轻度污染
+--           4:表示中度污染
+--           5:表示重度污染
+function AirVOC_1000.get_quality_level()
+    --读取ppb值
+    local ppb = AirVOC_1000.get_ppb()
+
+    --如果ppb读取失败
+    if not ppb then
+        log.error("AirVOC_1000.get_qulity_level", "get_ppb error")
+        return false
+    end
+
+    --根据ppb值计算空气质量等级
+    if ppb < 200 then
+        return 1,"优"
+    elseif ppb < 1000 then
+        return 2,"良好"
+    elseif ppb < 3000 then
+        return 3,"轻度污染"
+    elseif ppb < 5000 then
+        return 4,"中度污染"
+    else
+        return 5,"重度污染"
+    end 
+end
+
+
+--关闭AirVOC_1000
+
+--返回值:成功返回true,失败返回false
+function AirVOC_1000.close()
+    --close接口没有返回值,理论上不会关闭失败
+    i2c.close(AirVOC_1000.i2c_id)
+
+    return true
+end
+
+
+return AirVOC_1000

+ 65 - 0
module/Air780EPM/demo/accessory_board/AirVOC_1000/main.lua

@@ -0,0 +1,65 @@
+--[[
+必须定义PROJECT和VERSION变量,Luatools工具会用到这两个变量,远程升级功能也会用到这两个变量
+PROJECT:项目名,ascii string类型
+        可以随便定义,只要不使用,就行
+VERSION:项目版本号,ascii string类型
+        如果使用合宙iot.openluat.com进行远程升级,必须按照"XXX.YYY.ZZZ"三段格式定义:
+            X、Y、Z各表示1位数字,三个X表示的数字可以相同,也可以不同,同理三个Y和三个Z表示的数字也是可以相同,可以不同
+            因为历史原因,YYY这三位数字必须存在,但是没有任何用处,可以一直写为000
+        如果不使用合宙iot.openluat.com进行远程升级,根据自己项目的需求,自定义格式即可
+
+AirVOC_1000是合宙设计生产的一款I2C接口的VOC(挥发性有机化合物)气体传感器配件板;
+主要用于检测甲醛、一氧化碳、可燃气体、酒精、氨气、硫化物、苯系蒸汽、烟雾、其它有害气体的监测;
+本demo演示的核心功能为:
+Air8101核心板+AirVOC_1000配件板,每隔1秒读取1次TVOC空气质量数据;
+更多说明参考本目录下的readme.md文件
+]]
+PROJECT = "AirVOC_1000"
+VERSION = "001.000.000"
+
+
+-- 在日志中打印项目名和项目版本号
+log.info("main", PROJECT, VERSION)
+
+
+-- 如果内核固件支持wdt看门狗功能,此处对看门狗进行初始化和定时喂狗处理
+-- 如果脚本程序死循环卡死,就会无法及时喂狗,最终会自动重启
+if wdt then
+    --配置喂狗超时时间为9秒钟
+    wdt.init(9000)
+    --启动一个循环定时器,每隔3秒钟喂一次狗
+    sys.timerLoopStart(wdt.feed, 3000)
+end
+
+
+-- 如果内核固件支持errDump功能,此处进行配置,【强烈建议打开此处的注释】
+-- 因为此功能模块可以记录并且上传脚本在运行过程中出现的语法错误或者其他自定义的错误信息,可以初步分析一些设备运行异常的问题
+-- 以下代码是最基本的用法,更复杂的用法可以详细阅读API说明文档
+-- 启动errDump日志存储并且上传功能,600秒上传一次
+-- if errDump then
+--     errDump.config(true, 600)
+-- end
+
+
+-- 使用LuatOS开发的任何一个项目,都强烈建议使用远程升级FOTA功能
+-- 可以使用合宙的iot.openluat.com平台进行远程升级
+-- 也可以使用客户自己搭建的平台进行远程升级
+-- 远程升级的详细用法,可以参考fota的demo进行使用
+
+
+-- 启动一个循环定时器
+-- 每隔3秒钟打印一次总内存,实时的已使用内存,历史最高的已使用内存情况
+-- 方便分析内存使用是否有异常
+-- sys.timerLoopStart(function()
+--     log.info("mem.lua", rtos.meminfo())
+--     log.info("mem.sys", rtos.meminfo("sys"))
+--  end, 3000)
+
+ -- 加载voc应用模块
+ require "voc_app"
+
+
+-- 用户代码已结束---------------------------------------------
+-- 结尾总是这一句
+sys.run()
+-- sys.run()之后不要加任何语句!!!!!因为添加的任何语句都不会被执行

+ 59 - 0
module/Air780EPM/demo/accessory_board/AirVOC_1000/readme.md

@@ -0,0 +1,59 @@
+
+## 演示功能概述
+
+AirVOC_1000是合宙设计生产的一款I2C接口的VOC(挥发性有机化合物)气体传感器配件板;
+
+主要用于检测甲醛、一氧化碳、可燃气体、酒精、氨气、硫化物、苯系蒸汽、烟雾、其它有害气体的监测;
+
+本demo演示的核心功能为:
+
+Air780EPM开发板+AirVOC_1000配件板,每隔1秒读取1次TVOC空气质量数据;
+
+
+## 核心板+配件板资料
+
+[Air780EPM开发板+配件板相关资料](https://docs.openluat.com/air780epm/product/shouce/)
+
+
+## 演示硬件环境
+
+![](https://docs.openluat.com/accessory/AirVOC_1000/image/connect_Air780EPM.png)
+
+1、Air780EPM开发板
+
+2、AirVOC_1000配件板
+
+3、母对母的杜邦线4根
+
+4、Air780EPM开发板和AirVOC_1000配件板的硬件接线方式为
+
+| Air780EPM开发板 | AirVOC_1000配件板  |
+| ------------ | ------------------ |
+|     3V3(VDD_EXT)     |         3V3        |
+|     GND   |         GND        |
+| I2C1_SDA(CAMERA_SDA) |         SDA        |
+| I2C1_SCL(CAMERA_SCL) |         SCL        |
+
+
+## 演示软件环境
+
+1、Luatools下载调试工具
+
+2、[Air780EPM最新版本的内核固件](https://docs.openluat.com/air780epm/luatos/firmware/version/)
+
+
+## 演示操作步骤
+
+1、搭建好演示硬件环境
+
+2、不需要修改demo脚本代码
+
+3、Luatools烧录内核固件和demo脚本代码
+
+4、烧录成功后,自动开机运行
+
+5、通过观察Luatools的运行日志,每隔1秒出现一次类似于下面的打印,就表示测试正常
+
+``` lua
+[2025-09-25 15:21:52.270][000002162.898] I/user.空气质量 TVOC: ppb 93, ppm 0.093, 等级 1(优)
+[2025-09-25 15:21:53.273][000002163.902] I/user.空气质量 TVOC: ppb 93, ppm 0.093, 等级 1(优)

+ 36 - 0
module/Air780EPM/demo/accessory_board/AirVOC_1000/voc_app.lua

@@ -0,0 +1,36 @@
+--加载AirVOC_1000驱动文件
+local air_voc = require "AirVOC_1000"
+
+
+--每隔1秒读取一次TVOC数据
+local function read_voc_task_func()
+    --打开voc硬件
+    air_voc.open(1)
+
+    while true do
+        --读取TVOC的ppb,ppm,quality_level值
+        local ppb = air_voc.get_ppb()
+        local ppm = air_voc.get_ppm()
+        local level, description = air_voc.get_quality_level()
+        
+        --读取成功
+        if ppb then
+            log.info("空气质量", 
+                string.format("TVOC: ppb %d, ppm %.3f, 等级 %d(%s)", 
+                ppb, ppm, level, description))
+        --读取失败
+        else
+            log.error("空气质量", "读取数据失败")
+        end
+
+        --等待1秒
+        sys.wait(1000)
+    end
+
+    --关闭voc硬件
+    air_voc.close()
+end
+
+--创建一个task,并且运行task的主函数read_voc_task_func
+sys.taskInit(read_voc_task_func)
+

+ 3 - 0
module/Air8000/demo/accessory_board/AirGPIO_1000/readme.md

@@ -67,6 +67,7 @@ Air8000核心板+AirGPIO_1000配件板,演示I2C扩展16路GPIO功能;
 
    (3) 通过观察Luatools的运行日志,首先打印 P04_int_cbfunc 4 0      P14_int_cbfunc 20 0, 再隔一秒打印  P04_int_cbfunc 4 1      P14_int_cbfunc 20 1,再隔一秒打印 P04_int_cbfunc 4 0      P14_int_cbfunc 20 0,如此循环输出,表示GPIO中断测试正常;
 
+```
 [2025-09-24 16:15:09.221][000000054.571] I/user.air_gpio.get(0x11) 1
 [2025-09-24 16:15:09.223][000000054.572] I/user.AirGPIO_1000.set enter 16 0
 [2025-09-24 16:15:09.223][000000054.573] I/user.AirGPIO_1000.set output 3 255 254
@@ -93,3 +94,5 @@ Air8000核心板+AirGPIO_1000配件板,演示I2C扩展16路GPIO功能;
 [2025-09-24 16:15:10.308][000000055.643] I/user.P14_int_cbfunc 20 1
 [2025-09-24 16:15:11.190][000000056.534] I/user.AirGPIO_1000.set enter 0 0
 [2025-09-24 16:15:11.200][000000056.535] I/user.AirGPIO_1000.set output 2 255 254
+```
+

+ 188 - 0
module/Air8000/demo/accessory_board/AirVOC_1000/AirVOC_1000.lua

@@ -0,0 +1,188 @@
+--本文件中的主机是指I2C主机,具体指Air780EHV
+--本文件中的从机是指I2C从机,具体指AirVOC_1000配件板上的ags02ma VOC(挥发性有机化合物)气体传感器芯片
+
+local AirVOC_1000 = 
+{
+    -- i2c_id:主机的i2c id;
+}   
+
+-- 从机地址为0x1A
+local slave_address = 0x1A
+
+-- TVOC数据的寄存器地址
+local DATA_REG_ADDR = 0x00
+-- TVOC数据的长度
+local DATA_REG_LEN = 0x05
+
+
+-- 计算数据表data中所有数据元素的crc8校验值
+local function crc8(data)
+    local crc = 0xFF
+    for i = 1, #data do
+        crc = bit.bxor(crc, data[i])
+        for j = 1, 8 do
+            crc = crc * 2
+            if crc >= 0x100 then
+                crc = bit.band(bit.bxor(crc, 0x31), 0xff)
+            end
+        end
+    end
+    return crc
+end
+
+
+-- 读取AirVOC_1000的寄存器中指定长度的数据
+
+--reg:number类型;
+--         表示AirVOC_1000上的寄存器地址;
+--         必须传入,不允许为空;
+
+--返回值:失败返回false;成功返回读取到的指定长度的数据(string类型)
+local function read_register(reg, len)
+    -- 发送寄存器地址
+    i2c.send(AirVOC_1000.i2c_id, slave_address, reg)
+
+    -- sys.wait(20)
+
+    -- 读取应答的数据
+    local data = i2c.recv(AirVOC_1000.i2c_id, slave_address, len)
+
+    -- log.info("read_register", data:toHex())
+
+    -- 读取到的数据为指定的长度,则表示读取成功
+    -- 否则读取失败
+    if type(data)=="string" and data:len()==len then
+        return data
+    else
+        log.error("AirVOC_1000 read_register error", type(data), type(data)=="string" and data:len() or "invalid type", len)
+        return false
+    end
+end
+
+
+--打开AirVOC_1000;
+
+--i2c_id:number类型;
+--        主机使用的I2C ID,用来控制AirVOC_1000;
+--        取值范围:仅支持0和1;
+--        如果没有传入此参数,则默认为0;
+
+--返回值:成功返回true,失败返回false
+function AirVOC_1000.open(i2c_id)
+    --如果i2c_id为nil,则赋值为默认值0
+    if i2c_id==nil then i2c_id=0 end
+
+    --检查参数的合法性
+    if not (i2c_id == 0 or i2c_id == 1) then
+        log.error("AirVOC_1000.open", "invalid i2c_id", i2c_id)
+        return false
+    end
+
+    AirVOC_1000.i2c_id = i2c_id
+    
+    --初始化I2C
+    if i2c.setup(i2c_id, i2c.FAST) ~= 1 then
+        log.error("AirVOC_1000.open", "i2c.setup error", i2c_id)
+        return false
+    end
+
+    return true
+end
+
+--读取TVOC的ppb值;
+--ppb: 代表 parts per billion,即 十亿分之一。 1 ppb TVOC 表示在每 10 亿个体积单位的空气中,含有 1 个体积单位的 TVOC
+
+--返回值:失败返回false;
+--       成功返回ppb值(number类型)
+function AirVOC_1000.get_ppb()
+    --从寄存器DATA_REG_ADDR中读取DATA_REG_LEN长度的数据
+    local raw = read_register(DATA_REG_ADDR, DATA_REG_LEN)
+
+    --读取数据出错
+    if not raw then
+        log.error("AirVOC_1000.get_ppb", "read_register error")
+        return false
+    end
+
+    --检查校验值
+    if crc8({raw:byte(1), raw:byte(2), raw:byte(3), raw:byte(4)}) ~= raw:byte(5) then
+        log.error("AirVOC_1000.get_ppb", "crc error")
+        return false
+    end
+
+    --解析数据: 大端格式
+    local tvoc = (raw:byte(2) << 16) | 
+                 (raw:byte(3) << 8) | 
+                 raw:byte(4)
+    
+    return tvoc
+end
+
+--读取TVOC的ppm值;
+--ppm: 代表 parts per million,即 百万分之一。 1 ppm TVOC 表示在每 100 万个体积单位的空气中,含有 1 个体积单位的 TVOC
+
+--返回值:失败返回false;
+--       成功返回ppb值(number类型)
+function AirVOC_1000.get_ppm()
+    --读取ppb值
+    local ppb = AirVOC_1000.get_ppb()
+
+    --如果ppb读取失败
+    if not ppb then
+        log.error("AirVOC_1000.get_ppm", "get_ppb error")
+        return false
+    end
+
+    --ppb = ppm*1000
+    return ppb/1000
+    
+end
+
+--读取TVOC的空气质量等级;
+-- 根据 TVOC 浓度(通常以 ppb 或 ppm 表示)划分的等级,用于评估室内空气质量的优劣和对人体健康的潜在风险;
+-- 不同国家、地区或机构的标准可能略有差异,但核心划分逻辑相似:浓度越低,等级越好,风险越低。
+
+--返回值:失败返回false;
+--       成功返回空气质量等级值(number类型,数值越小,表示空气质量越好)和空气质量描述(string类型,例如优、良好、轻度污染、中度污染、重度污染)
+--           1:表示优
+--           2:表示良好
+--           3:表示轻度污染
+--           4:表示中度污染
+--           5:表示重度污染
+function AirVOC_1000.get_quality_level()
+    --读取ppb值
+    local ppb = AirVOC_1000.get_ppb()
+
+    --如果ppb读取失败
+    if not ppb then
+        log.error("AirVOC_1000.get_qulity_level", "get_ppb error")
+        return false
+    end
+
+    --根据ppb值计算空气质量等级
+    if ppb < 200 then
+        return 1,"优"
+    elseif ppb < 1000 then
+        return 2,"良好"
+    elseif ppb < 3000 then
+        return 3,"轻度污染"
+    elseif ppb < 5000 then
+        return 4,"中度污染"
+    else
+        return 5,"重度污染"
+    end 
+end
+
+
+--关闭AirVOC_1000
+
+--返回值:成功返回true,失败返回false
+function AirVOC_1000.close()
+    --close接口没有返回值,理论上不会关闭失败
+    i2c.close(AirVOC_1000.i2c_id)
+
+    return true
+end
+
+
+return AirVOC_1000

+ 65 - 0
module/Air8000/demo/accessory_board/AirVOC_1000/main.lua

@@ -0,0 +1,65 @@
+--[[
+必须定义PROJECT和VERSION变量,Luatools工具会用到这两个变量,远程升级功能也会用到这两个变量
+PROJECT:项目名,ascii string类型
+        可以随便定义,只要不使用,就行
+VERSION:项目版本号,ascii string类型
+        如果使用合宙iot.openluat.com进行远程升级,必须按照"XXX.YYY.ZZZ"三段格式定义:
+            X、Y、Z各表示1位数字,三个X表示的数字可以相同,也可以不同,同理三个Y和三个Z表示的数字也是可以相同,可以不同
+            因为历史原因,YYY这三位数字必须存在,但是没有任何用处,可以一直写为000
+        如果不使用合宙iot.openluat.com进行远程升级,根据自己项目的需求,自定义格式即可
+
+AirVOC_1000是合宙设计生产的一款I2C接口的VOC(挥发性有机化合物)气体传感器配件板;
+主要用于检测甲醛、一氧化碳、可燃气体、酒精、氨气、硫化物、苯系蒸汽、烟雾、其它有害气体的监测;
+本demo演示的核心功能为:
+Air8101核心板+AirVOC_1000配件板,每隔1秒读取1次TVOC空气质量数据;
+更多说明参考本目录下的readme.md文件
+]]
+PROJECT = "AirVOC_1000"
+VERSION = "001.000.000"
+
+
+-- 在日志中打印项目名和项目版本号
+log.info("main", PROJECT, VERSION)
+
+
+-- 如果内核固件支持wdt看门狗功能,此处对看门狗进行初始化和定时喂狗处理
+-- 如果脚本程序死循环卡死,就会无法及时喂狗,最终会自动重启
+if wdt then
+    --配置喂狗超时时间为9秒钟
+    wdt.init(9000)
+    --启动一个循环定时器,每隔3秒钟喂一次狗
+    sys.timerLoopStart(wdt.feed, 3000)
+end
+
+
+-- 如果内核固件支持errDump功能,此处进行配置,【强烈建议打开此处的注释】
+-- 因为此功能模块可以记录并且上传脚本在运行过程中出现的语法错误或者其他自定义的错误信息,可以初步分析一些设备运行异常的问题
+-- 以下代码是最基本的用法,更复杂的用法可以详细阅读API说明文档
+-- 启动errDump日志存储并且上传功能,600秒上传一次
+-- if errDump then
+--     errDump.config(true, 600)
+-- end
+
+
+-- 使用LuatOS开发的任何一个项目,都强烈建议使用远程升级FOTA功能
+-- 可以使用合宙的iot.openluat.com平台进行远程升级
+-- 也可以使用客户自己搭建的平台进行远程升级
+-- 远程升级的详细用法,可以参考fota的demo进行使用
+
+
+-- 启动一个循环定时器
+-- 每隔3秒钟打印一次总内存,实时的已使用内存,历史最高的已使用内存情况
+-- 方便分析内存使用是否有异常
+-- sys.timerLoopStart(function()
+--     log.info("mem.lua", rtos.meminfo())
+--     log.info("mem.sys", rtos.meminfo("sys"))
+--  end, 3000)
+
+ -- 加载voc应用模块
+ require "voc_app"
+
+
+-- 用户代码已结束---------------------------------------------
+-- 结尾总是这一句
+sys.run()
+-- sys.run()之后不要加任何语句!!!!!因为添加的任何语句都不会被执行

+ 63 - 0
module/Air8000/demo/accessory_board/AirVOC_1000/readme.md

@@ -0,0 +1,63 @@
+
+## 演示功能概述
+
+AirVOC_1000是合宙设计生产的一款I2C接口的VOC(挥发性有机化合物)气体传感器配件板;
+
+主要用于检测甲醛、一氧化碳、可燃气体、酒精、氨气、硫化物、苯系蒸汽、烟雾、其它有害气体的监测;
+
+本demo演示的核心功能为:
+
+Air8000核心板+AirVOC_1000配件板,每隔1秒读取1次TVOC空气质量数据;
+
+
+## 核心板+配件板资料
+
+[Air8000核心板+配件板相关资料]([硬件手册和证书 - product@air8000 - 合宙模组资料中心](https://docs.openluat.com/air8000/product/shouce/#air8000_1))
+
+
+## 演示硬件环境
+
+![](https://docs.openluat.com/accessory/AirVOC_1000/image/connect_Air8000.jpg)
+
+![](https://docs.openluat.com/accessory/AirSHT30_1000/image/8000.png)
+
+1、Air8000核心板
+
+2、AirVOC_1000配件板
+
+3、母对母的杜邦线4根
+
+4、Air8000核心板和AirVOC_1000配件板的硬件接线方式为
+
+| Air8000核心板 | AirVOC_1000配件板  |
+| ------------ | ------------------ |
+|     VDD_EXT     |         3V3        |
+|     GND   |         GND        |
+| I2C1_SDA |         SDA        |
+| I2C1_SCL |         SCL        |
+
+
+## 演示软件环境
+
+1、Luatools下载调试工具
+
+2、[Air8000最新版本的内核固件](https://docs.openluat.com/air8000/luatos/firmware/)
+
+
+## 演示操作步骤
+
+1、搭建好演示硬件环境
+
+2、不需要修改demo脚本代码
+
+3、Luatools烧录内核固件和demo脚本代码
+
+4、烧录成功后,自动开机运行
+
+5、通过观察Luatools的运行日志,每隔1秒出现一次类似于下面的打印,就表示测试正常
+
+``` lua
+[2025-09-25 11:28:01.193][000002293.756] I/user.空气质量 TVOC: ppb 93, ppm 0.093, 等级 1(优)
+[2025-09-25 11:28:02.194][000002294.761] I/user.空气质量 TVOC: ppb 91, ppm 0.091, 等级 1(优)
+
+```

+ 36 - 0
module/Air8000/demo/accessory_board/AirVOC_1000/voc_app.lua

@@ -0,0 +1,36 @@
+--加载AirVOC_1000驱动文件
+local air_voc = require "AirVOC_1000"
+
+
+--每隔1秒读取一次TVOC数据
+local function read_voc_task_func()
+    --打开voc硬件
+    air_voc.open(1)
+
+    while true do
+        --读取TVOC的ppb,ppm,quality_level值
+        local ppb = air_voc.get_ppb()
+        local ppm = air_voc.get_ppm()
+        local level, description = air_voc.get_quality_level()
+        
+        --读取成功
+        if ppb then
+            log.info("空气质量", 
+                string.format("TVOC: ppb %d, ppm %.3f, 等级 %d(%s)", 
+                ppb, ppm, level, description))
+        --读取失败
+        else
+            log.error("空气质量", "读取数据失败")
+        end
+
+        --等待1秒
+        sys.wait(1000)
+    end
+
+    --关闭voc硬件
+    air_voc.close()
+end
+
+--创建一个task,并且运行task的主函数read_voc_task_func
+sys.taskInit(read_voc_task_func)
+