SGP30是一款单一芯片上具有多个传感元件的金属氧化物气体传感器,内集成4个气体传感元件,具有完全校准的空气质量输出信号。另外,SGP易于集成,能够将金属氧化物气体传感器集成到移动设备中,为智能家居、家电和物联网应用中的环境监测开辟了新的可能性。主要用于甲醛的检测!
模块来源
规格参数
工作电压:3.3V
工作电流:40mA
输出方式: IIC
管脚数量:4 Pin
以上信息见厂家资料文件
移植过程
我们的目标是将例程移植至开发板上【测量甲醛】。首先要获取资料,查看数据手册应如何实现读取数据,再移植至我们的工程。
查看资料
SGP30是一款单一芯片上具有多个传感元件的金属氧化物室内气体传感器,内部集成4个气体传感元件,具有完全校准的空气质量输出信号,主要是对空气质量进行检测。可以输出:
TVOC(Total Volatile Organic Compounds,总挥发性有机物),量程为0~60000ppb;CO2浓度,量程400~60000ppm。
SGP30的传感(MEMS)部分基于金属氧化物(MOx)纳米颗粒的加热膜。气敏材料——金属氧化物颗粒上吸附的氧气与目标气体发生反应,从而释放出电子。这导致由传感器测量的金属氧化物层的电阻发生改变。简而言之,还原性气体的出现造成气敏材料表面氧浓度降低,改变了半导体的电阻(或电导率)。后续通过电路(ASIC)部分对电阻进行检测、信号处理与转换等,最终获取到气体值。
I2C从机地址是0X58,由于地址只用到了7bit,最高位未使用,最低位为判断是读还是写,为0是读,为1是写,所以:
- 对于写SGP30,地址为(0X58 << 1) = 0XB0
- 对于读SGP30,地址为((0X58 << 1)) | 0X01 = 0XB1
SGP30的命令都是双字节的,先发高位。有如下命令:
常用的有两个,一个是0x2003为初始化SGP30命令,另一个0x2008为获取空气质量值命令。
SGP30获取的数据格式为:2位CO2数据+1位CO2的CRC校验+2位TVOC数据+1位TVOC的CRC校验。模块上电需要15s左右初始化,在初始化阶段读取的CO2浓度为400ppm,TVOC为0ppd且恒定不变。因此上电后一直读,直到TVOC不为0并且CO2不为400,SGP30模块才初始化完成。
初始化完成后刚开始读出数据会波动比较大,属于正常现象,一段时间后会逐渐趋于稳定。气体类传感器比较容易受环境影响,测量数据出现波动是正常的,可以添加滤波函数进行滤波。
引脚选择
这里选择的引脚见引脚接线表
代码移植
下载为大家准备的驱动代码文件夹,复制到自己工程中\luban-lite\application\rt-thread\helloworld\user-bsp
文件夹下
提示
如果未找到 user-bsp
这个文件夹,说明你未进行模块移植的前置操作。请转移到手册使用必要操作(点击跳转)中进行必要的配置操作!!!
接下来打开自己的工程,开始修改Kconfig文件。
1、在 VSCode 中打开 application\rt-thread\helloworld\Kconfig 文件
2、在该文件的 #endif
前面添加该模块的 Kconfig路径语句
# SGP30气体传感器
source "application/rt-thread/helloworld/user-bsp/sgp30-gas-sensor/Kconfig"
2
menuconfig操作
1、我们 双击 luban-lite
文件夹下的 win_env.bat
脚本打开env工具:
2、输入以下命令列出所有可用的默认配置:
scons --list-def
3、选择 d13x_JLC_rt-thread_helloworld
这个配置!这个是我们衡山派开发板的默认配置!输入以下命令即可:
scons --apply-def=7
或者
scons --apply-def=d13x_JLC_rt-thread_helloworld_defconfig
这两个命令作用是一样的,一个是 文件名 ,一个是 编号 !!!
4、输入以下命令进入menuconfig菜单
scons --menuconfig
进入以下界面:
5、选中 Porting code using the LCKFB module
按
Y
选中按
N
取消选中方向键
左右
调整 最下面菜单的选项方向键
上下
调整 列表的选项
回车
执行最下面菜单的选项
6、回车进入 Porting code using the LCKFB module
菜单
7、按方向键 上下
选中 Using SGP30 gas sensor
后按 Y
键,看到前面括号中出现一个 *
号,就可以下一步了。
8、按方向键 左右
选中 <Save>
然后一路回车
,然后 退出
即可
编译
我们 保存并退出menuconfig菜单 之后,输入以下命令进行编译:
scons
或
scons -j16
-j 用来选择参与编译的核心数: 我这里是选择16
大家可以根据自己的电脑来选择
核心越多编译越快
如果写的数量高于电脑本身,那么就自动按照最高可用的来运行!
镜像烧录
编译完成之后会在 \luban-lite\output\d13x_JLC_rt-thread_helloworld\images
文件夹下生成一个 d13x_JLC_v1.0.0.img
镜像文件!
然后我们烧录镜像,具体的教程请查看:镜像烧录(点击跳转🚀)
到这里完成了,请移步到 最后一节 进行移植验证。
工程代码解析
bsp_sgp30.c
/*
* 立创开发板软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 文档网站:wiki.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 嘉立创社区问答:https://www.jlc-bbs.com/lckfb
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
*/
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <getopt.h>
#include <sys/time.h>
#include <rtthread.h>
#include <inttypes.h>
#include <finsh.h>
#include "hal_adcim.h"
#include "rtdevice.h"
#include "aic_core.h"
#include "aic_log.h"
#include "hal_gpai.h"
#include "aic_hal_gpio.h"
#include "hal_i2c.h"
#include "bsp_sgp30.h"
#define I2C_BUS_NAME "i2c0" /* I2C总线设备名称 */
#define SLAVE_ADDR 0x58 /* 器件地址 */
static struct rt_i2c_bus_device *i2c_bus = RT_NULL; /* I2C总线设备句柄 */
/* 发送数据 */
static rt_err_t write_data(struct rt_i2c_bus_device *bus, rt_uint8_t len, rt_uint8_t *data)
{
struct rt_i2c_msg msgs;
msgs.addr = SLAVE_ADDR;
msgs.flags = RT_I2C_WR;
msgs.buf = data;
msgs.len = len;
/* 调用I2C设备接口传输数据 */
if (rt_i2c_transfer(bus, &msgs, 1) == 1)
{
return RT_EOK;
}
else
{
return -RT_ERROR;
}
}
/* 读取数据 */
static rt_err_t read_data(struct rt_i2c_bus_device *bus, rt_uint8_t len, rt_uint8_t *buf)
{
struct rt_i2c_msg msgs;
msgs.addr = SLAVE_ADDR; // 器件地址
msgs.flags = RT_I2C_RD; // 读写标志的设定
msgs.buf = buf; // 发送缓存区地址
msgs.len = len; // 发送的字节长度
/* 调用I2C设备接口传输数据 */
if (rt_i2c_transfer(bus, &msgs, 1) == 1)
{
return RT_EOK;
}
else
{
return -RT_ERROR;
}
}
/******************************************************************
* 函 数 名 称:SGP30_Write_CMD
* 函 数 说 明:SGP30写命令
* 函 数 形 参:regaddr_H命令高8位 regaddr_L命令低8位
* 函 数 返 回:RT_EOK成功 -RT_ERROR失败
* 作 者:LC
* 备 注:无
******************************************************************/
static int SGP30_Write_CMD(uint8_t regaddr_H, uint8_t regaddr_L)
{
uint8_t send_buff[2] = {regaddr_H, regaddr_L};
if(RT_EOK != write_data(i2c_bus, 2, send_buff))
{
LOG_E("[%d] | SGP30_Write_CMD failed !!", __LINE__);
return -RT_ERROR;
}
return RT_EOK;
}
/**********************************************************
* 函 数 名 称:SGP30_Init
* 函 数 功 能:初始化SGP30
* 传 入 参 数:无
* 函 数 返 回:RT_OK:完成 -RT_ERROR:错误
* 作 者:LCKFB
* 备 注:
**********************************************************/
int SGP30_Init(void)
{
/* 查找I2C总线设备,获取I2C总线设备句柄 */
i2c_bus = (struct rt_i2c_bus_device *)rt_device_find(I2C_BUS_NAME);
if(i2c_bus == RT_NULL)
{
LOG_E("no device: %s",I2C_BUS_NAME);
return -RT_ERROR;
}
else
{
rt_kprintf("\nfind device: %s\n",I2C_BUS_NAME);
}
/* 写入初始化命令 */
if(RT_EOK != SGP30_Write_CMD(0x20, 0x03))
{
LOG_E("[%d] | SGP30_Init failed !!", __LINE__);
return -RT_ERROR;
}
aicos_mdelay(100);
return RT_EOK;
}
/******************************************************************
* 函 数 名 称:SGP30_Read
* 函 数 说 明:测量温湿度
* 函 数 形 参:CO2数据地址 TVOC数据地址
* 函 数 返 回:RT_EOK:成功 -RT_ERROR:错误
* 作 者:LC
* 备 注:无
******************************************************************/
int SGP30_Read(uint32_t *CO2_Value, uint32_t *TVOC_Value)
{
int ret = 0;
uint8_t recv_buff[5] = {0};
/* 设置命令 */
ret = SGP30_Write_CMD(0x20,0x08);
if(ret != RT_EOK)
{
LOG_E("[%d] | SGP30_Read failed !!", __LINE__);
return -RT_ERROR;
}
aicos_mdelay(100);
/* 读取数据 */
ret = read_data(i2c_bus, 5, recv_buff);
if(ret != RT_EOK)
{
LOG_E("[%d] | SGP30_Read failed !!", __LINE__);
return -RT_ERROR;
}
/* 如果CO2数据地址不为空 */
if(RT_NULL != CO2_Value)
{
/* 高八位和第八位合并 */
*CO2_Value = (recv_buff[0] << 8) | recv_buff[1];
}
/* 如果TVOC数据地址不为空 */
if(RT_NULL != TVOC_Value)
{
/* 高八位和第八位合并 */
*TVOC_Value = (recv_buff[3] << 8) | recv_buff[4];
}
return RT_EOK;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
bsp_sgp30.h
/*
* 立创开发板软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 文档网站:wiki.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 嘉立创社区问答:https://www.jlc-bbs.com/lckfb
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
*/
#ifndef __BSP_SGP30_H__
#define __BSP_SGP30_H__
#include "stdio.h"
int SGP30_Init(void);
int SGP30_Read(uint32_t *CO2_Value, uint32_t *TVOC_Value);
#endif
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Kconfig
这个是一个menuconfig中的选项,如果在菜单中选中该选项,就会在rtconfig.h
中定义一个语句,用来if判断条件编译之类的。
config LCKFB_SGP30_GAS_SENSOR
bool "Using SGP30 gas sensor"
select AIC_USING_I2C0
default n
help
More information is available at: https://wiki.lckfb.com/
2
3
4
5
6
7
SConscript
自动化构建文件,如果定义了 LCKFB_SGP30_GAS_SENSOR
和 USING_LCKFB_TRANSPLANT_CODE
就自动编译当前目录下的文件!!
Import('RTT_ROOT')
Import('rtconfig')
import rtconfig
from building import *
cwd = GetCurrentDir()
CPPPATH = [cwd]
src = []
if GetDepend('LCKFB_SGP30_GAS_SENSOR') and GetDepend('USING_LCKFB_TRANSPLANT_CODE'):
src = Glob(os.path.join(cwd, '*.c'))
group = DefineGroup('lckfb-sgp30-gas-sensor', src, depend = [''], CPPPATH = CPPPATH)
Return('group')
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
test_sgp30_gas_sensor.c
这个文件定义了一个SGP30空气质量传感器处理的线程,初始化了传感器的硬件抽象层,并设置了线程的优先级、栈大小和时间片。
线程的主要任务是读取SGP30传感器的CO2和TVOC数据,并打印到控制台。在读取操作前有15秒的校准时间,期间数据可能不准确。读取操作会持续进行,直到用户通过命令行接口输入特定命令来退出读取循环。
线程入口函数逻辑
- 初始化时调用SGP30_Init函数来初始化传感器。
- 定义一个循环计数器while_count,并初始化为1。
- 在一个无限循环中,首先尝试读取SGP30传感器的CO2和TVOC值。
- 如果读取成功,将读取到的数据打印到控制台。
- 如果读取失败,打印错误信息。
- 当循环次数达到100次时,提示用户可以通过输入命令来退出传感器读取循环。
- 在每次循环结束时,线程会挂起一段时间,这里是1秒。
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <getopt.h>
#include <sys/time.h>
#include <rtthread.h>
#include "rtdevice.h"
#include "aic_core.h"
#include "aic_hal_gpio.h"
#include "bsp_sgp30.h"
#define THREAD_PRIORITY 25 // 线程优先级
#define THREAD_STACK_SIZE 1024 // 线程大小
#define THREAD_TIMESLICE 20 // 时间片
static rt_thread_t sgp30_thread = RT_NULL; // 线程控制块
// 线程入口函数
static void sgp30_thread_entry(void *param)
{
int ret = 0;
int while_count = 1; // 循环次数
/* SGP30初始化 */
ret = SGP30_Init();
if(ret != RT_EOK)
{
LOG_E("failed to SGP30_Init !!");
return;
}
rt_kprintf("Start Loop !!\n");
while(while_count++)
{
uint32_t CO2_Value = 0;
uint32_t TVOC_Value = 0;
/*
* 注意:开始读取的前15秒左右是校准的时间,数据不准!
*/
int ret = SGP30_Read(&CO2_Value, &TVOC_Value); //读取
if(ret != RT_EOK)
{
LOG_E("failed to SGP30_Read !");
}
else
{
rt_kprintf("\nRead [SGP30] CO2 = %d\n", CO2_Value);
rt_kprintf("Read [SGP30] TVOC = %d\n", TVOC_Value);
}
if(while_count >= 100)
{
while_count = 1;
rt_kprintf("\nType [test_exit_sgp30_sensor] command to exit SGP30 to read data\n");
rt_kprintf("Note: Pressing [TAB] as you type will autocomplete the command\n");
rt_thread_mdelay(2000);
}
rt_thread_mdelay(1000);
}
rt_kprintf("\nEND!!\n");
}
/* SGP30启动函数 */
static void test_sgp30_sensor(int argc, char **argv)
{
/* 创建线程,名称是 sgp30_thread,入口是 sgp30_thread_entry */
sgp30_thread = rt_thread_create("sgp30_thread1",
sgp30_thread_entry, RT_NULL,
THREAD_STACK_SIZE,
THREAD_PRIORITY, THREAD_TIMESLICE);
/* 如果获得线程控制块,启动这个线程 */
if (sgp30_thread != RT_NULL)
rt_thread_startup(sgp30_thread);
}
// 导出函数为命令
MSH_CMD_EXPORT(test_sgp30_sensor, run SGP30 sensor);
/* SGP30退出函数 */
static void test_exit_sgp30_sensor(void)
{
int ret = rt_thread_delete(sgp30_thread);
if(ret != RT_EOK)
{
LOG_E("failed to test_exit_sgp30_sensor !!");
}
else
{
rt_kprintf("\n========SGP30 exit successful !!========\n");
}
}
// 导出函数为命令
MSH_CMD_EXPORT(test_exit_sgp30_sensor, quit SGP30);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
移植验证
我们使用串口调试,将 USB转TTL模块 连接到衡山派开发板上面!!
具体的教程查看:串口调试(点击跳转🚀)
串口波特率默认为
115200
我们在输入下面的命令运行该模块的线程:
输入的时候按下
TAB键
会进行命令补全!!
test_sgp30_sensor
模块上电效果: