返回列表 发新帖我要提问本帖赏金: 25.00元(功能说明)

[单片机芯片] CH32V307使用GPIO模拟I2C驱动TVOC、CO2eq传感器SGP30

[复制链接]
 楼主| dql2015 发表于 2022-12-19 10:40 | 显示全部楼层 |阅读模式
<
本帖最后由 dql2015 于 2022-12-19 11:38 编辑

#申请原创#  
@21小跑堂
近年来,清洁的空气成为诸多重视健康生活人士的新需求。常见重要的空气质量指标有2种:
二氧化碳:
二氧化碳是一种微量气体。我们吸入氧气并呼出二氧化碳,因此在封闭空间中,二氧化碳浓度与人类活动和人口密度相关。高浓度的二氧化碳可能会导致头痛、困倦、精神萎靡和表现不佳。
VOC(挥发性有机化合物):
VOC 是在室温或更高温度下蒸发的含碳物质。短期接触会导致刺激、头晕或哮喘恶化;长期接触则可能会导致肺癌或损害肝脏或神经系统。
手头正好有一款seeed的空气质量传感器模块SGP30,SGP30是TVOC(总挥发性有机化合物)、CO2eq气体传感器,是一款数字多气体传感器,可轻松集成到空气净化器、指令控制通风和物联网应用中。 该传感器的的CMOSens®技术在单个芯片上提供完整的传感器系统,并带有I2C接口、温度控制的微型加热板和两个预处理的室内空气质量信号。
测量结果输出频率1Hz,TVOC输出范围0 ppb to 60000 ppb,CO2eq输出范围400 ppm to 60000 ppm,不通输出范围精度不同;
001.JPG

硬件接口

SGP30使用的是I2C接口:
22.JPG
供电、接口电平为1.8V,因此硬件设计上需要注意电平匹配:
000000.JPG
硬件设计SCL、SDA上应增加上拉电阻(或者使用控制器管脚内部上拉电阻):
33.JPG
支持快速模式400KHz:
11.JPG
需要注意SDA valid time不超过0.9us:
002.JPG
设备地址
SGP30 I2C使用7bit从机地址0X58,最低位为读写控制位,0是读,1是写:
44.JPG
写地址为(0X58 << 1) = 0XB0
读为((0X58 << 1)) | 0X01 = 0XB1

控制命令
SGP30的命令都是双字节16bit的:
55.JPG
通信采用一问一答形式,主机发出命令,等待从机回复:
00.JPG
双字节命令先发高位MSB,后发低位LSB,以初始化SGP30命令(0X2003)为例,发送顺序为:0X20、0X03
88-1.JPG
测量命令
读取测量结果命令采用标准I2C形式,读写相关寄存器:
For the first 15s after the “Init_air_quality” command the sensor is in an initialization phase during which a “Measure_air_quality” command returns fixed values of
400 ppm CO2eq and 0 ppb TVOC.
需要特别注意这段话,SGP30上电后在发送初始化命令后,需要大约15秒完成初始化,在初始化过程读取的CO2浓度为400ppm,TVOC为0ppd且恒定不变。



88-2.JPG

88-3.JPG

88-4.JPG

88-5.JPG

软件复位
可以通过地址0x0006控制SGP30进行软复位:
77-1.JPG
77-2.JPG
读取序列号
SGP30内置48bit唯一序列号,通过0x3682地址读出:
66.JPG
CRC校验
数据校验采用8bit CRC和校验方式:
99.JPG
sgp30.h文件
  1. #ifndef _SGP30_H_
  2. #define _SGP30_H_

  3. #include <stdio.h>
  4. #include <stdint.h>


  5. #define SGP30_ADDR          0x58
  6. #define        SGP30_ADDR_WRITE        (SGP30_ADDR << 1)         //0xb0
  7. #define        SGP30_ADDR_READ                ((SGP30_ADDR << 1) + 1)   //0xb1


  8. /* 初始化空气质量测量 */
  9. #define SGP30_CMD_INIT_AIR_QUALITY 0x2003

  10. /* 开始空气质量测量 */
  11. #define SGP30_CMD_MEASURE_AIR_QUALITY 0x2008

  12. /* 获取串号 */
  13. #define SGP30_CMD_GET_SERIAL_ID  0X3682

  14. int sgp30_init(void);
  15. int sgp30_read(uint16_t* CO2, uint16_t* TVOC);
  16. int sgp30_get_serial_id(uint8_t id[6]);
  17. int sgp30_soft_reset(void);

  18. #endif /* _SGP30_H_ */

sgp30.c文件
  1. #include "sgp30.h"
  2. #include "debug.h"
  3. #include "myiic.h"


  4. #define RCC_CLK_PORT RCC_APB2Periph_GPIOA

  5. #define SCL_PORT GPIOA
  6. #define SCL_PIN GPIO_Pin_4

  7. #define SDA_PORT GPIOA
  8. #define SDA_PIN GPIO_Pin_5

  9. #define IIC_SCL_H() GPIO_WriteBit(SCL_PORT,SCL_PIN,1)
  10. #define IIC_SCL_L() GPIO_WriteBit(SCL_PORT,SCL_PIN,0)

  11. #define IIC_SDA_H() GPIO_WriteBit(SDA_PORT,SDA_PIN,1)
  12. #define IIC_SDA_L() GPIO_WriteBit(SDA_PORT,SDA_PIN,0)

  13. #define READ_SDA() GPIO_ReadInputDataBit(SDA_PORT,SDA_PIN)


  14. #define SGP30_SDA_Pin                      SDA_PIN
  15. #define SGP30_SDA_GPIO_Port                SDA_PORT

  16. #define SGP30_SCK_Pin                      SCL_PIN
  17. #define SGP30_SCK_GPIO_Port                SCL_PORT


  18. #define SGP30_SCK_L()    IIC_SCL_L()
  19. #define SGP30_SCK_H()    IIC_SCL_H()
  20. #define SGP30_SDA_L()    IIC_SDA_L()
  21. #define SGP30_SDA_H()    IIC_SDA_H()
  22. #define SGP30_READ_SDA() (READ_SDA() == Bit_SET ? 1 : 0)

  23. /// 设置SDA引脚为输出模式
  24. static void SDA_SET_OUT()
  25. {
  26.     GPIO_InitTypeDef GPIO_InitStructure = {0};
  27.     RCC_APB2PeriphClockCmd(RCC_CLK_PORT, ENABLE);

  28.     GPIO_InitStructure.GPIO_Pin = SDA_PIN;//sda
  29.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
  30.     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  31.     GPIO_Init(SDA_PORT, &GPIO_InitStructure);
  32.     IIC_SDA_L();//必要的
  33. }

  34. /// 设置SDA引脚为输入模式
  35. static void SDA_SET_IN()
  36. {
  37.     GPIO_InitTypeDef GPIO_InitStructure = {0};
  38.     RCC_APB2PeriphClockCmd(RCC_CLK_PORT, ENABLE);

  39.     GPIO_InitStructure.GPIO_Pin = SDA_PIN;//sda
  40.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
  41.     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  42.     GPIO_Init(SDA_PORT, &GPIO_InitStructure);
  43. }

  44. /// 微秒延时函数
  45. static void sgp30_delay_us(uint32_t us)
  46. {
  47.     Delay_Us(us);
  48. }

  49. /// 毫秒延时函数
  50. static void sgp30_delay_ms(uint32_t nms)
  51. {
  52.         Delay_Ms(nms);
  53. }

  54. /// 初始化SGP30的IIC引脚
  55. static int sgp30_iic_init(void)
  56. {
  57.         GPIO_InitTypeDef GPIO_InitStructure = {0};

  58.     RCC_APB2PeriphClockCmd(RCC_CLK_PORT, ENABLE);

  59.     GPIO_InitStructure.GPIO_Pin = SCL_PIN;
  60.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//scl
  61.     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  62.     GPIO_Init(SCL_PORT, &GPIO_InitStructure);

  63.     GPIO_InitStructure.GPIO_Pin = SDA_PIN;//sda
  64.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
  65.     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  66.     GPIO_Init(SDA_PORT, &GPIO_InitStructure);

  67.     //必要的
  68.         SGP30_SCK_H();  // 拉高时钟引脚
  69.         SGP30_SDA_H();  // 拉高数据引脚
  70.         return 0;
  71. }

  72. /////////////////////////////////////////////////////////////////////////////////////
  73. ///
  74. ///                               模拟IIC协议实现
  75. ///
  76. /////////////////////////////////////////////////////////////////////////////////////

  77. static void IIC_Start(void)
  78. {
  79.     SDA_SET_OUT();
  80.     SGP30_SDA_H();
  81.     SGP30_SCK_H();
  82.     sgp30_delay_us(5);
  83.     SGP30_SDA_L(); //START:when CLK is high,DATA change form high to low
  84.     sgp30_delay_us(6);
  85.     SGP30_SCK_L();
  86. }

  87. static void IIC_Stop(void)
  88. {
  89.     SDA_SET_OUT();
  90.     SGP30_SCK_L();
  91.     SGP30_SDA_L(); //STOP:when CLK is high DATA change form low to high
  92.     SGP30_SCK_H();
  93.     sgp30_delay_us(6);
  94.     SGP30_SDA_H();
  95.     sgp30_delay_us(6);
  96. }

  97. static uint8_t IIC_Wait_Ack(void)
  98. {
  99.     uint8_t tempTime = 0;
  100.     SGP30_SDA_H();
  101.     sgp30_delay_us(1);
  102.     SDA_SET_IN();
  103.     SGP30_SCK_H();
  104.     sgp30_delay_us(1);
  105.     while (SGP30_READ_SDA())
  106.     {
  107.         tempTime++;
  108.         if (tempTime > 250)
  109.         {
  110.             IIC_Stop();
  111.             return 1;
  112.         }
  113.     }
  114.     SGP30_SCK_L();
  115.     return 0;
  116. }

  117. static void IIC_Ack(void)
  118. {
  119.     SGP30_SCK_L();
  120.     SDA_SET_OUT();
  121.     SGP30_SDA_L();
  122.     sgp30_delay_us(2);
  123.     SGP30_SCK_H();
  124.     sgp30_delay_us(5);
  125.     SGP30_SCK_L();
  126. }

  127. static void IIC_NAck(void)
  128. {
  129.     SGP30_SCK_L();
  130.     SDA_SET_OUT();
  131.     SGP30_SDA_H();
  132.     sgp30_delay_us(2);
  133.     SGP30_SCK_H();
  134.     sgp30_delay_us(5);
  135.     SGP30_SCK_L();
  136. }

  137. static void IIC_Send_Byte(uint8_t txd)
  138. {
  139.     uint8_t t;
  140.     SDA_SET_OUT();
  141.     SGP30_SCK_L();
  142.     for (t = 0; t < 8; t++)
  143.     {
  144.         if ((txd & 0x80) > 0) //0x80  1000 0000
  145.             SGP30_SDA_H();
  146.         else
  147.             SGP30_SDA_L();
  148.         txd <<= 1;
  149.         sgp30_delay_us(2);
  150.         SGP30_SCK_H();
  151.         sgp30_delay_us(2);
  152.         SGP30_SCK_L();
  153.         sgp30_delay_us(2);
  154.     }
  155. }

  156. static uint8_t IIC_Read_Byte(uint8_t ack)
  157. {
  158.     uint8_t i, receive = 0;
  159.     SDA_SET_IN();
  160.     for (i = 0; i < 8; i++)
  161.     {
  162.         SGP30_SCK_L();
  163.         sgp30_delay_us(2);
  164.         SGP30_SCK_H();
  165.         receive <<= 1;
  166.         if (SGP30_READ_SDA())
  167.             receive++;
  168.         sgp30_delay_us(1);
  169.     }
  170.     if (!ack)
  171.         IIC_NAck();
  172.     else
  173.         IIC_Ack();
  174.     return receive;
  175. }

  176. static int sgp30_iic_write(uint8_t addr, const uint8_t* buf, uint32_t len)
  177. {
  178.         int i;
  179.     IIC_Start();
  180.     IIC_Send_Byte(addr);
  181.     IIC_Wait_Ack();
  182.         for(i = 0; i < len; i++)
  183.         {
  184.                 IIC_Send_Byte(buf[i]);
  185.                 IIC_Wait_Ack();
  186.         }
  187.     IIC_Stop();
  188.         return 0;
  189. }

  190. static int sgp30_iic_read(uint8_t addr, uint8_t* buf, uint32_t len)
  191. {
  192.         int i;
  193.     IIC_Start();
  194.     IIC_Send_Byte(addr);
  195.         IIC_Wait_Ack();
  196.         for(i = 0; i < len - 1; i++)
  197.         {
  198.                 buf[i] = IIC_Read_Byte(1);
  199.         }
  200.         buf[i] = IIC_Read_Byte(0);  // SGP30接收数据时候的最后一个字节不需要等待ACK
  201.         IIC_Stop();
  202.         return 0;
  203. }

  204. //////////////////////////////////////////////////////////////////////////////////////////
  205. ///
  206. ///       SGP30驱动代码
  207. ///
  208. //////////////////////////////////////////////////////////////////////////////////////////

  209. static uint8_t sgp30_checksum(const uint8_t* buf, uint32_t len);

  210. int sgp30_get_serial_id(uint8_t id[6])
  211. {
  212.     uint8_t buf[32];
  213.     uint8_t crc[3];

  214.     buf[0] = (SGP30_CMD_GET_SERIAL_ID & 0XFF00) >> 8;
  215.     buf[1] = (SGP30_CMD_GET_SERIAL_ID & 0X00FF);

  216.     if (sgp30_iic_write(SGP30_ADDR_WRITE, buf, 2) < 0)
  217.         return -1;

  218.     if (sgp30_iic_read(SGP30_ADDR_READ, buf, 9) < 0)
  219.         return -2;

  220.     crc[0] = buf[2];
  221.     crc[1] = buf[5];
  222.     crc[2] = buf[8];

  223.     id[0]  = buf[0];
  224.     id[1]  = buf[1];
  225.     id[2]  = buf[3];
  226.     id[3]  = buf[4];
  227.     id[4]  = buf[6];
  228.     id[5]  = buf[7];

  229.     if(
  230.         sgp30_checksum(&id[0], 2) != crc[0] ||
  231.         sgp30_checksum(&id[2], 2) != crc[1] ||
  232.         sgp30_checksum(&id[4], 2) != crc[2]
  233.     )
  234.         return -3;

  235.     return 0;
  236. }

  237. static uint8_t sgp30_checksum(const uint8_t* buf, uint32_t len)
  238. {
  239.         const uint8_t Polynomial = 0x31;
  240.         uint8_t Initialization = 0XFF;
  241.     uint8_t i = 0, k = 0;
  242.         while(i < len)
  243.         {
  244.                 Initialization ^= buf[i++];
  245.                 for(k = 0; k < 8; k++)
  246.                 {
  247.                         if(Initialization & 0X80)
  248.                                 Initialization = (Initialization << 1) ^ Polynomial;
  249.                         else
  250.                                 Initialization = (Initialization << 1);
  251.                 }
  252.         }
  253.         return Initialization;
  254. }

  255. int sgp30_soft_reset(void)
  256. {
  257.     uint8_t cmd = 0X06;
  258.     return sgp30_iic_write(0X00, &cmd, 1);
  259. }

  260. int sgp30_init(void)
  261. {
  262.     uint8_t buf[2];

  263.         // 初始化IIC
  264.         if(sgp30_iic_init() < 0)
  265.                 return -1;

  266.     // 软件复位
  267.     if (sgp30_soft_reset() < 0)
  268.         return -2;

  269.     // 等待复位完成
  270.     sgp30_delay_ms(50);

  271.     buf[0] = (SGP30_CMD_INIT_AIR_QUALITY & 0XFF00) >> 8;
  272.     buf[1] = (SGP30_CMD_INIT_AIR_QUALITY & 0X00FF);

  273.         // 初始化控制测量参数
  274.     if (sgp30_iic_write(SGP30_ADDR_WRITE, buf, 2) < 0)
  275.         return -3;

  276.     return 0;
  277. }

  278. int sgp30_read(uint16_t* CO2, uint16_t* TVOC)
  279. {
  280.     uint8_t buf[8] = {0};

  281.     buf[0] = (SGP30_CMD_MEASURE_AIR_QUALITY & 0XFF00) >> 8;
  282.     buf[1] = (SGP30_CMD_MEASURE_AIR_QUALITY & 0X00FF);

  283.     // 启动空气质量测量
  284.     if (sgp30_iic_write(SGP30_ADDR_WRITE, buf, 2) < 0)
  285.         return -1;

  286.     // 等待测量完成
  287.     sgp30_delay_ms(10);

  288.     // 读取收到的数据
  289.     if (sgp30_iic_read(SGP30_ADDR_READ, buf, 6) < 0)
  290.         return -2;

  291.     // 校验CRC
  292.     if (sgp30_checksum(&buf[3], 2) != buf[5])
  293.         return -3;

  294.     if (CO2 != NULL)    *CO2  = (buf[0] << 8) | buf[1];
  295.     if (TVOC != NULL)   *TVOC = (buf[3] << 8) | buf[4];

  296.     return 0;
  297. }
main函数测试代码:
初始化未完成依据是TVOC == 0 && CO2 == 400
  1. int main(void)
  2. {
  3.     Delay_Init();
  4.     USART_Printf_Init(115200);

  5.     while(sgp30_init() < 0)
  6.     {
  7.         printf(" sgp30 init fail\r\n");
  8.         Delay_Ms(1000);
  9.     }

  10.     printf(" sgp30 init ok\r\n");

  11.     if(sgp30_get_serial_id(ID) < 0)
  12.     {
  13.         printf(" sgp30 read serial id failed\r\n");
  14.     }else
  15.     {
  16.         printf(" sgp30 read serial id is:");
  17.         for(int i = 0; i < 6; i++)  printf("%02X", ID[i]);
  18.         printf("\r\n");
  19.     }

  20.     printf(" sgp30 wait air for init");
  21.     do
  22.     {
  23.         if(sgp30_read(&CO2, &TVOC) < 0)
  24.         {
  25.             printf("\r\n spg30 read failed");
  26.         }else
  27.         {
  28.             printf(".");
  29.         }
  30.         Delay_Ms(500);
  31.     }while(TVOC == 0 && CO2 == 400);

  32.     printf("\r\n");

  33.     while (1)
  34.     {
  35.         if(sgp30_read(&CO2, &TVOC) < 0)
  36.         {
  37.             printf(" sgp30 read fail\r\n");
  38.         }
  39.         else
  40.         {
  41.             printf("CO2:%5dppm TVOC:%5dppb\r\n", CO2, TVOC);
  42.         }
  43.         Delay_Ms(1000);
  44.     }
  45. }


实物接线:
微信图片_20221219103520.jpg

串口调试助手打印结果:
spg30读取结果.png







打赏榜单

21ic小管家 打赏了 25.00 元 2022-12-20
理由:签约作者

claretttt 发表于 2023-1-5 12:43 | 显示全部楼层
这个TVOC都包括什么的?              
mattlincoln 发表于 2023-1-5 12:52 | 显示全部楼层
二氧化碳的数值测量怎么转换实际数值的?
belindagraham 发表于 2023-1-5 13:39 | 显示全部楼层
这个是不是可以监测甲醛的数据呢              
mattlincoln 发表于 2023-1-6 12:49 | 显示全部楼层
这个是什么源代码              
maqianqu 发表于 2023-1-6 19:00 | 显示全部楼层
SGP30保持400是什么原因              
LOVEEVER 发表于 2023-2-8 09:41 | 显示全部楼层
belindagraham 发表于 2023-1-5 13:39
这个是不是可以监测甲醛的数据呢

说的有理,甲醛能测就更好了
Kear 发表于 2024-3-13 13:11 | 显示全部楼层
能发下源码码?谢谢
您需要登录后才可以回帖 登录 | 注册

本版积分规则

104

主题

384

帖子

8

粉丝
快速回复 在线客服 返回列表 返回顶部