【硬件模块】SGP30气体传感器

10 篇文章 2 订阅
6 篇文章 1 订阅

SGP30

这是SGP30官方文档里开头的介绍,简单来说就是SGP30是一个数字多像素气体传感器,然后具有长期稳定性和低漂移。

这些我们都不用管,我们只需要知道SGP30是通过I2C来通信的,并且可以采集的数据有CO2和TVOC的含量。TVOC是“Total Volatile Organic Compounds”,意思是总挥发性有机化合物。

可以来看一下它的参数。

TVOC的输出范围是0~60000ppb,而CO2的范围是400~60000ppm。一开始没注意范围,我看CO2一直都在400以上还以为出了啥问题。

对了,在SGP30上电初始化之后会有一段时间输出的CO2固定是400,而TVOC固定是0,是正常现象,等一会就可以正常采集数据了,后面会再说

接下来是电气规格,这边要注意电压不能超过1.98V!!!跟我们平时常用的3.3V和5V不一样。直接使用SGP30需要进行电平转换。

不过我用的是模块,已经把电平转换芯片加上去了,供电3.3V~5V都是可以的。

除了电气规则,还有一个就是物理环境,但是大家一般都不会处于这么极端的环境吧。

工作温度在-40℃~85℃之间,湿度在10%95%之间。

然后是通信时间,可以看到SGP30支持的I2C最大速率是400K。

了解完上面的内容之后就可以开始研究如何和SGP30用I2C通信了。

SGP30的七位从机地址是0x58,因此从机地址+读的地址就是0xB1,从机地址+写的地址就是0xB0,我们也可以写成 ( 0x58<<1 | 0x01) 和 ( 0x58<<1 | 0x00)

通信的时序就是I2C的标准,不一样的是SGP30的指令分两个字节发送,也就是说我们发送一个命令需要发送两次。

并且SGP30发来的数据,每俩字节就跟一个CRC校验位。

CRC校验多项式是0x31,我们可以直接搜索CRC在线校验计算器帮我们计算。当然,我们也可以忽略掉,但是还是要接收。

接下来看看指令。

虽然不多,但是我们用到的更少,我们基本上只用俩命令,第一个是0x2003初始化,第二个是0x2008获取采集数据。

我们在一开始的发送0x2003初始化一下,等待12ms(文档里说的,但我们最好多延时一会儿)

然后我们就可以发送0x2008采集数据了,等待10ms(我们最好多等一会),会返回给我们6个byte的数据,其中前俩字节是CO2的数据,第三个是CO2数据的CRC校验码,第四五个字节是TVOC的数据,最后一个字节是TVOC的CRC校验码。

根据文档里说的,在初始化(0x2003)的15s内,我们获取采集数据(0x2008)收到的结果会是400和0。并且我们需要每秒发送一次0x2008就可以保证SGP30内部的补偿算法生效,采集的数据会更精确。

至此我们就知道应该如何使用SGP了,接下来我会贴出示例代码,结合代码和注释以及上文,相信大家就都可以将SGP30这个模块移植到各自的板子上了。

软件I2C使用SGP30(以GD32为例)

虽然我这边演示的是GD32(因为最近在用的板子是GD32),但是其他板子也是一样的流程,不一样的只是操作GPIO的方式不一样,自己修改一下即可。

另外有个小坑需要注意一下,我们STM32模拟I2C的时候GPIO配置的是推挽输出模式(没试过开漏),然后在弄GD32的时候一开始我也配置的是推挽,结果连ACK都没收到,排查了好久才发现需要将GD32的GPIO配置为开漏才可以进行软件模拟I2C。

#include "board.h"
#include <stdio.h>
#include "Z_UART.h"

#define SCL_Pin GPIO_PIN_8
#define SDA_Pin GPIO_PIN_9
#define IIC_PORT GPIOB
 
//下面Z_I2C开头的是软件I2C,不是使用SGP30的重点
void Z_I2C_Init(void){
    rcu_periph_clock_enable(RCU_GPIOB);
    
    gpio_mode_set(IIC_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLUP,SCL_Pin|SDA_Pin);
    gpio_output_options_set(IIC_PORT, GPIO_OTYPE_OD, GPIO_OSPEED_50MHZ,SCL_Pin|SDA_Pin);
    
    gpio_bit_write(IIC_PORT,SCL_Pin,1);
    gpio_bit_write(IIC_PORT,SDA_Pin,1);
}
    
void Z_I2C_SetSCL(uint8_t signal){
    if(signal==1) gpio_bit_write(IIC_PORT,SCL_Pin,1);
    else gpio_bit_write(IIC_PORT,SCL_Pin,0);
    delay_us(5);
}
 
void Z_I2C_SetSDA(uint8_t signal){
    if(signal==1) gpio_bit_write(IIC_PORT,SDA_Pin,1);
    else gpio_bit_write(IIC_PORT,SDA_Pin,0);
    delay_us(5);
}
 
uint8_t Z_I2C_GetSDA(void){
    return gpio_input_bit_get(IIC_PORT,SDA_Pin);
}
 
void Z_I2C_Start(void){
    Z_I2C_SetSDA(1);
    Z_I2C_SetSCL(1);
    Z_I2C_SetSDA(0);
    Z_I2C_SetSCL(0);
}
 
void Z_I2C_End(){
    Z_I2C_SetSDA(0);
    Z_I2C_SetSCL(1);
    Z_I2C_SetSDA(1);
}
 
void Z_I2C_SendByte(uint8_t byte){
    Z_I2C_SetSCL(0);
    for(int i=0;i<8;++i){
        if((byte&0x80)==0) Z_I2C_SetSDA(0);
        else Z_I2C_SetSDA(1);
        byte<<=1;
        Z_I2C_SetSCL(1);
        Z_I2C_SetSCL(0);
    }
}
 
uint8_t Z_I2C_ReveiceByte(){
    uint8_t data=0x00;
    Z_I2C_SetSDA(1);
    for(int i=0;i<8;++i){
        Z_I2C_SetSCL(1);
        if(Z_I2C_GetSDA()==1) data|=(0x80>>i);
        Z_I2C_SetSCL(0);
    }
    return data;
}
 
void Z_I2C_SendACK(uint8_t ack){
    if(ack==0) Z_I2C_SetSDA(0);
    else Z_I2C_SetSDA(1);
    Z_I2C_SetSCL(1);
    Z_I2C_SetSCL(0);
}
 
uint8_t Z_I2C_ReveiceACK(){
    Z_I2C_SetSDA(1);
    Z_I2C_SetSCL(1);
    uint8_t ack=Z_I2C_GetSDA();
    Z_I2C_SetSCL(0);
    return ack;
}

//获取SGP30的数据并打印
void printfSGP30(void){
    Z_I2C_Start();                                          //I2C起始时序
    Z_I2C_SendByte(0x58<<1|0x00);                           //发送从机地址+写(0xB0)
    if(0 != Z_I2C_ReveiceACK()) printf("ACK error\r\n");    //接收ACK
    
    Z_I2C_SendByte(0x20);                                   //发送采集命令0x2008,需要分两次发送
    if(0 != Z_I2C_ReveiceACK()) printf("ACK error\r\n");
    Z_I2C_SendByte(0x08);
    if(0 != Z_I2C_ReveiceACK()) printf("ACK error\r\n");

    Z_I2C_End();                                            //结束I2C
    
    delay_ms(100);                                          //需要等待10ms,保险起见延时久一点
    
    uint16_t data[4] = {0};
    
    Z_I2C_Start();
    Z_I2C_SendByte(0x58<<1|0x01);                           //发送从机地址+读(0xB0)
    if(0 != Z_I2C_ReveiceACK()) printf("ACK error\r\n");    //接收ACK
        
    data[0] = Z_I2C_ReveiceByte();                          //接收CO2的高8位
    Z_I2C_SendACK(0);                                       //发送ACK
    data[1] = Z_I2C_ReveiceByte();                          //接收CO2的低八位
    Z_I2C_SendACK(0);
    
    printf("CRC is 0x%X\r\n",Z_I2C_ReveiceByte());          //获取并打印CO2的CRC,可以不处理,但是一定要读取
    Z_I2C_SendACK(0);
    
    data[2] = Z_I2C_ReveiceByte();                          //接收TVOC(16bit)
    Z_I2C_SendACK(0);
    data[3] = Z_I2C_ReveiceByte();
    Z_I2C_End();                                            //TVOC的CRC不接收了,直接结束I2C通信
    
    printf("%X\t%X\t%X\t%X\r\n",data[0],data[1],data[2],data[3]);               //打印一下原始数据
    printf("CO2 is %d,TVOC is %d\r\n",data[0]<<8|data[1],data[2]<<8|data[3]);   //打印一下CO2和TVOC
    
}

int main(void){
    board_init();
    //初始化串口,为了将结果打印到串口助手上,不懂怎么操作的小伙伴可以看看之前关于串口的文章
    Z_UART_Init();
    Z_I2C_Init();                                       //初始化软件I2C相关引脚
    
    Z_I2C_Start();                                      //I2C起始时序
    Z_I2C_SendByte(0xB0);                               //发送从机地址+写
    if(0 != Z_I2C_ReveiceACK()) printf("ACK error\r\n");
    
    Z_I2C_SendByte(0x20);                               //发送初始化命令0x2003
    if(0 != Z_I2C_ReveiceACK()) printf("ACK error\r\n");
    Z_I2C_SendByte(0x03);
    if(0 != Z_I2C_ReveiceACK()) printf("ACK error\r\n");

    Z_I2C_End();                                        //结束I2C
    delay_ms(100);                                      //初始化需要10ms,但我们还是延时久一点
    
    while (1){
        printfSGP30();
        delay_ms(1000);
    }
}


可以收到数据,并且前十几秒的数据固定是400和0是正常现象。

GD32硬件I2C

之前的文章讲了GD32的引脚IIC,那么我们也加上GD32引脚IIC的例子吧。没看过且感兴趣的小伙伴可以再回顾一下。

【GD32】08 - IIC(以SHT20为例)-CSDN博客文章浏览阅读550次,点赞26次,收藏14次。接下来是设置IIC通信的模式与地址,模式我们自然是选择I2C模式的,而地址可以选择7位或者是10位的(10位的参数截图没截上,因为卡在手册的下一页了),这个根据我们通信的模块的从机地址而定。今天来了解一下GD32中的硬件IIC,其实我个人是觉得软件IIC比较方便的,不过之前文章里用的都是软件IIC,今天就算是走出自己的舒适圈,我们来了解了解GD32中的硬件IIC。关于IIC以及本文中演示的SHT20,在之前的文章里都有,并且也不是本文的重点,因此这里就不介绍了,不了解且感兴趣的小伙伴可以去看看之前的文章。https://blog.csdn.net/m0_63235356/article/details/140020224?spm=1001.2014.3001.5501

#include "board.h"
#include <stdio.h>
#include "Z_UART.h"


//获取SGP30的数据并打印
void printfSGP30(void){

    i2c_start_on_bus(I2C0);                                     //I2C起始时序
    while(!i2c_flag_get(I2C0,I2C_FLAG_SBSEND) );
    
    i2c_master_addressing(I2C0,0xB0,I2C_TRANSMITTER);           //发送从机地址+写
    while(!i2c_flag_get(I2C0,I2C_FLAG_ADDSEND) );               //等待从机发送完毕之后得到回应(即从机地址正确)
    i2c_flag_clear(I2C0,I2C_FLAG_ADDSEND);
    
    i2c_data_transmit(I2C0,0x20);
    while(!i2c_flag_get(I2C0,I2C_FLAG_BTC) ); 
    i2c_data_transmit(I2C0,0x08);
    while(!i2c_flag_get(I2C0,I2C_FLAG_BTC) ); 

    i2c_stop_on_bus(I2C0);                                       //结束I2C
    
    delay_ms(100);                                              //需要等待10ms,保险起见延时久一点

    i2c_start_on_bus(I2C0);                                     //I2C起始时序
    while(!i2c_flag_get(I2C0,I2C_FLAG_SBSEND) );

    i2c_master_addressing(I2C0,0xB0,I2C_RECEIVER);              //发送从机地址+读
    while(!i2c_flag_get(I2C0,I2C_FLAG_ADDSEND) );               //等待从机发送完毕之后得到回应(即从机地址正确)
    i2c_flag_clear(I2C0,I2C_FLAG_ADDSEND);
        
    i2c_ack_config(I2C0, I2C_ACK_ENABLE);   
       
    uint16_t data[4] = {0};

    while(!i2c_flag_get(I2C0,I2C_FLAG_RBNE) );          //等待接收缓冲区不为空
    data[0] = i2c_data_receive (I2C0); 
    
    while(!i2c_flag_get(I2C0,I2C_FLAG_RBNE) );          //等待接收缓冲区不为空
    data[1] = i2c_data_receive (I2C0); 
    
    while(!i2c_flag_get(I2C0,I2C_FLAG_RBNE) );          //等待接收缓冲区不为空
    uint8_t crc = i2c_data_receive (I2C0); 
    printf("CRC is %X\r\n",crc);
    
    while(!i2c_flag_get(I2C0,I2C_FLAG_RBNE) );          //等待接收缓冲区不为空
    data[2] = i2c_data_receive (I2C0); 
    
    i2c_ack_config(I2C0, I2C_ACK_DISABLE);
    
    while(!i2c_flag_get(I2C0,I2C_FLAG_RBNE) );          //等待接收缓冲区不为空
    data[3] = i2c_data_receive (I2C0); 
    
    i2c_stop_on_bus(I2C0); 
    
    printf("%X\t%X\t%X\t%X\r\n",data[0],data[1],data[2],data[3]);               //打印一下原始数据
    printf("CO2 is %d,TVOC is %d\r\n",data[0]<<8|data[1],data[2]<<8|data[3]);   //打印一下CO2和TVOC
    
}

int main(void){
    board_init();
    //初始化串口,为了将结果打印到串口助手上,不懂怎么操作的小伙伴可以看看之前关于串口的文章
    Z_UART_Init();
    //开启时钟
    rcu_periph_clock_enable(RCU_I2C0);
    rcu_periph_clock_enable(RCU_GPIOB);
    //初始化硬件IIC的引脚
    gpio_af_set(GPIOB, GPIO_AF_4,GPIO_PIN_8|GPIO_PIN_9);
    gpio_mode_set(GPIOB, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_8|GPIO_PIN_9);
    gpio_output_options_set(GPIOB, GPIO_OTYPE_OD, GPIO_OSPEED_50MHZ,GPIO_PIN_8|GPIO_PIN_9);
       
    i2c_deinit(I2C0);                                           //复位IIC0
    i2c_clock_config(I2C0, 100000, I2C_DTCY_2);                 //设置IIC速率为100k
    i2c_ack_config(I2C0, I2C_ACK_ENABLE);                       //使能应答
    i2c_enable(I2C0);                                           //使能IIC
    

    i2c_start_on_bus(I2C0);                                     //I2C起始时序
    while(!i2c_flag_get(I2C0,I2C_FLAG_SBSEND) );

    i2c_master_addressing(I2C0,0xB0,I2C_TRANSMITTER);           //发送从机地址+写
    while(!i2c_flag_get(I2C0,I2C_FLAG_ADDSEND));                //等待从机发送完毕之后得到回应(即从机地址正确)
    i2c_flag_clear(I2C0,I2C_FLAG_ADDSEND);
   
    i2c_data_transmit(I2C0,0x20);
    while(!i2c_flag_get(I2C0,I2C_FLAG_BTC) ); 
    i2c_data_transmit(I2C0,0x03);
    while(!i2c_flag_get(I2C0,I2C_FLAG_BTC) ); 

    i2c_stop_on_bus(I2C0);                                      //结束I2C
    delay_ms(100);                                              //初始化需要10ms,但我们还是延时久一点
    
    while (1){
        printfSGP30();
        delay_ms(1000);
    }
}


ESP32硬件I2C

这边也提供一下ESP32硬件I2C的代码吧,ESP32的硬件I2C方便好多,一下子就调通了。

顺便一提,因为没找到实习,于是决定暑假开始录制ESP32(ESP-IDF)的视频了,可以的话可以把今年服务外包的项目(省赛都没进)的硬件部分当个练手项目分享出来(基于ESP32),还有时间的话可以再录制一下GD32的视频。

ESP32已经在录了,相信没过多久就可以和大家见面了。可以关注一下b站的同名账号,微信视频号也会一起发。

#include <stdio.h>
#include "driver/i2c.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

// 获取SGP30的数据并打印
void printfSGP30(void) {
    i2c_cmd_handle_t cmd_handle = i2c_cmd_link_create();

    i2c_master_start(cmd_handle);
    i2c_master_write_byte(cmd_handle, 0xB0, true);
    i2c_master_write_byte(cmd_handle, 0x20, true);
    i2c_master_write_byte(cmd_handle, 0x08, true);
    i2c_master_stop(cmd_handle);
    i2c_master_cmd_begin(I2C_NUM_0, cmd_handle, 100 / portTICK_PERIOD_MS);
    i2c_cmd_link_delete(cmd_handle);

    vTaskDelay(100 / portTICK_PERIOD_MS);

    uint8_t data[6] = {0};

    cmd_handle = i2c_cmd_link_create();
    i2c_master_start(cmd_handle);
    i2c_master_write_byte(cmd_handle, 0xB1, true);
    i2c_master_read_byte(cmd_handle, &data[0], I2C_MASTER_ACK);
    i2c_master_read_byte(cmd_handle, &data[1], I2C_MASTER_ACK);
    i2c_master_read_byte(cmd_handle, &data[2], I2C_MASTER_ACK);
    i2c_master_read_byte(cmd_handle, &data[3], I2C_MASTER_ACK);
    i2c_master_read_byte(cmd_handle, &data[4], I2C_MASTER_ACK);
    i2c_master_read_byte(cmd_handle, &data[5], I2C_MASTER_ACK);
    i2c_master_stop(cmd_handle);
    i2c_master_cmd_begin(I2C_NUM_0, cmd_handle, 100 / portTICK_PERIOD_MS);
    i2c_cmd_link_delete(cmd_handle);

    printf("CO2 is %d,TVOC is %d\r\n", (uint16_t)data[0] << 8 | data[1],
           (uint16_t)data[3] << 8 | data[4]);  // 打印一下CO2和TVOC
}

void app_main(void) {
    printf("hello world\r\n");
    i2c_config_t i2c_initer = {
        .clk_flags = 0,              // 选择默认时钟源
        .master.clk_speed = 100000,  // 指定速率为100Kbit,最大可以为400Kbit
        .mode = I2C_MODE_MASTER,  // 主机模式
        .scl_io_num = 7,          // 指定SCL的GPIO口
        .scl_pullup_en = true,    // SCL接上拉电阻
        .sda_io_num = 8,          // 指定SDA的GPIO口
        .sda_pullup_en = true,    // SDA接上拉电阻
    };
    if (i2c_param_config(I2C_NUM_0, &i2c_initer) == ESP_OK)
        printf("i2c parm config success\r\n");
    else
        printf("config fail\r\n");

    if (i2c_driver_install(I2C_NUM_0, I2C_MODE_MASTER, 0, 0, 0) == ESP_OK)
        printf("i2c driver install success\r\n");
    else
        printf("driver fail\r\n");

    i2c_cmd_handle_t cmd_handle = i2c_cmd_link_create();

    i2c_master_start(cmd_handle);
    i2c_master_write_byte(cmd_handle, 0xB0, true);
    i2c_master_write_byte(cmd_handle, 0x20, true);
    i2c_master_write_byte(cmd_handle, 0x03, true);
    i2c_master_stop(cmd_handle);
    i2c_master_cmd_begin(I2C_NUM_0, cmd_handle, 100 / portTICK_PERIOD_MS);
    i2c_cmd_link_delete(cmd_handle);

    while (1) {
        printfSGP30();
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}

也可以正常打印出结果。 

文档原文包括译文,以及卖家发的资料我都打包好了,大家可以关注我的同名公众号“折途想要敲代码”,回复关键词“SGP30”即可免费下载啦。

  • 17
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值