本帖最后由 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,不通输出范围精度不同;
硬件接口
SGP30使用的是I2C接口:
供电、接口电平为1.8V,因此硬件设计上需要注意电平匹配:
硬件设计SCL、SDA上应增加上拉电阻(或者使用控制器管脚内部上拉电阻):
支持快速模式400KHz:
需要注意SDA valid time不超过0.9us:
设备地址
SGP30 I2C使用7bit从机地址0X58,最低位为读写控制位,0是读,1是写:
写地址为(0X58 << 1) = 0XB0
读为((0X58 << 1)) | 0X01 = 0XB1
控制命令
SGP30的命令都是双字节16bit的:
通信采用一问一答形式,主机发出命令,等待从机回复:
双字节命令先发高位MSB,后发低位LSB,以初始化SGP30命令(0X2003)为例,发送顺序为:0X20、0X03
测量命令
读取测量结果命令采用标准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且恒定不变。
软件复位
可以通过地址0x0006控制SGP30进行软复位:
读取序列号
SGP30内置48bit唯一序列号,通过0x3682地址读出:
CRC校验
数据校验采用8bit CRC和校验方式:
sgp30.h文件
#ifndef _SGP30_H_
#define _SGP30_H_
#include <stdio.h>
#include <stdint.h>
#define SGP30_ADDR 0x58
#define SGP30_ADDR_WRITE (SGP30_ADDR << 1) //0xb0
#define SGP30_ADDR_READ ((SGP30_ADDR << 1) + 1) //0xb1
/* 初始化空气质量测量 */
#define SGP30_CMD_INIT_AIR_QUALITY 0x2003
/* 开始空气质量测量 */
#define SGP30_CMD_MEASURE_AIR_QUALITY 0x2008
/* 获取串号 */
#define SGP30_CMD_GET_SERIAL_ID 0X3682
int sgp30_init(void);
int sgp30_read(uint16_t* CO2, uint16_t* TVOC);
int sgp30_get_serial_id(uint8_t id[6]);
int sgp30_soft_reset(void);
#endif /* _SGP30_H_ */
sgp30.c文件
#include "sgp30.h"
#include "debug.h"
#include "myiic.h"
#define RCC_CLK_PORT RCC_APB2Periph_GPIOA
#define SCL_PORT GPIOA
#define SCL_PIN GPIO_Pin_4
#define SDA_PORT GPIOA
#define SDA_PIN GPIO_Pin_5
#define IIC_SCL_H() GPIO_WriteBit(SCL_PORT,SCL_PIN,1)
#define IIC_SCL_L() GPIO_WriteBit(SCL_PORT,SCL_PIN,0)
#define IIC_SDA_H() GPIO_WriteBit(SDA_PORT,SDA_PIN,1)
#define IIC_SDA_L() GPIO_WriteBit(SDA_PORT,SDA_PIN,0)
#define READ_SDA() GPIO_ReadInputDataBit(SDA_PORT,SDA_PIN)
#define SGP30_SDA_Pin SDA_PIN
#define SGP30_SDA_GPIO_Port SDA_PORT
#define SGP30_SCK_Pin SCL_PIN
#define SGP30_SCK_GPIO_Port SCL_PORT
#define SGP30_SCK_L() IIC_SCL_L()
#define SGP30_SCK_H() IIC_SCL_H()
#define SGP30_SDA_L() IIC_SDA_L()
#define SGP30_SDA_H() IIC_SDA_H()
#define SGP30_READ_SDA() (READ_SDA() == Bit_SET ? 1 : 0)
/// 设置SDA引脚为输出模式
static void SDA_SET_OUT()
{
GPIO_InitTypeDef GPIO_InitStructure = {0};
RCC_APB2PeriphClockCmd(RCC_CLK_PORT, ENABLE);
GPIO_InitStructure.GPIO_Pin = SDA_PIN;//sda
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(SDA_PORT, &GPIO_InitStructure);
IIC_SDA_L();//必要的
}
/// 设置SDA引脚为输入模式
static void SDA_SET_IN()
{
GPIO_InitTypeDef GPIO_InitStructure = {0};
RCC_APB2PeriphClockCmd(RCC_CLK_PORT, ENABLE);
GPIO_InitStructure.GPIO_Pin = SDA_PIN;//sda
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(SDA_PORT, &GPIO_InitStructure);
}
/// 微秒延时函数
static void sgp30_delay_us(uint32_t us)
{
Delay_Us(us);
}
/// 毫秒延时函数
static void sgp30_delay_ms(uint32_t nms)
{
Delay_Ms(nms);
}
/// 初始化SGP30的IIC引脚
static int sgp30_iic_init(void)
{
GPIO_InitTypeDef GPIO_InitStructure = {0};
RCC_APB2PeriphClockCmd(RCC_CLK_PORT, ENABLE);
GPIO_InitStructure.GPIO_Pin = SCL_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//scl
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(SCL_PORT, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = SDA_PIN;//sda
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(SDA_PORT, &GPIO_InitStructure);
//必要的
SGP30_SCK_H(); // 拉高时钟引脚
SGP30_SDA_H(); // 拉高数据引脚
return 0;
}
/////////////////////////////////////////////////////////////////////////////////////
///
/// 模拟IIC协议实现
///
/////////////////////////////////////////////////////////////////////////////////////
static void IIC_Start(void)
{
SDA_SET_OUT();
SGP30_SDA_H();
SGP30_SCK_H();
sgp30_delay_us(5);
SGP30_SDA_L(); //START:when CLK is high,DATA change form high to low
sgp30_delay_us(6);
SGP30_SCK_L();
}
static void IIC_Stop(void)
{
SDA_SET_OUT();
SGP30_SCK_L();
SGP30_SDA_L(); //STOP:when CLK is high DATA change form low to high
SGP30_SCK_H();
sgp30_delay_us(6);
SGP30_SDA_H();
sgp30_delay_us(6);
}
static uint8_t IIC_Wait_Ack(void)
{
uint8_t tempTime = 0;
SGP30_SDA_H();
sgp30_delay_us(1);
SDA_SET_IN();
SGP30_SCK_H();
sgp30_delay_us(1);
while (SGP30_READ_SDA())
{
tempTime++;
if (tempTime > 250)
{
IIC_Stop();
return 1;
}
}
SGP30_SCK_L();
return 0;
}
static void IIC_Ack(void)
{
SGP30_SCK_L();
SDA_SET_OUT();
SGP30_SDA_L();
sgp30_delay_us(2);
SGP30_SCK_H();
sgp30_delay_us(5);
SGP30_SCK_L();
}
static void IIC_NAck(void)
{
SGP30_SCK_L();
SDA_SET_OUT();
SGP30_SDA_H();
sgp30_delay_us(2);
SGP30_SCK_H();
sgp30_delay_us(5);
SGP30_SCK_L();
}
static void IIC_Send_Byte(uint8_t txd)
{
uint8_t t;
SDA_SET_OUT();
SGP30_SCK_L();
for (t = 0; t < 8; t++)
{
if ((txd & 0x80) > 0) //0x80 1000 0000
SGP30_SDA_H();
else
SGP30_SDA_L();
txd <<= 1;
sgp30_delay_us(2);
SGP30_SCK_H();
sgp30_delay_us(2);
SGP30_SCK_L();
sgp30_delay_us(2);
}
}
static uint8_t IIC_Read_Byte(uint8_t ack)
{
uint8_t i, receive = 0;
SDA_SET_IN();
for (i = 0; i < 8; i++)
{
SGP30_SCK_L();
sgp30_delay_us(2);
SGP30_SCK_H();
receive <<= 1;
if (SGP30_READ_SDA())
receive++;
sgp30_delay_us(1);
}
if (!ack)
IIC_NAck();
else
IIC_Ack();
return receive;
}
static int sgp30_iic_write(uint8_t addr, const uint8_t* buf, uint32_t len)
{
int i;
IIC_Start();
IIC_Send_Byte(addr);
IIC_Wait_Ack();
for(i = 0; i < len; i++)
{
IIC_Send_Byte(buf[i]);
IIC_Wait_Ack();
}
IIC_Stop();
return 0;
}
static int sgp30_iic_read(uint8_t addr, uint8_t* buf, uint32_t len)
{
int i;
IIC_Start();
IIC_Send_Byte(addr);
IIC_Wait_Ack();
for(i = 0; i < len - 1; i++)
{
buf[i] = IIC_Read_Byte(1);
}
buf[i] = IIC_Read_Byte(0); // SGP30接收数据时候的最后一个字节不需要等待ACK
IIC_Stop();
return 0;
}
//////////////////////////////////////////////////////////////////////////////////////////
///
/// SGP30驱动代码
///
//////////////////////////////////////////////////////////////////////////////////////////
static uint8_t sgp30_checksum(const uint8_t* buf, uint32_t len);
int sgp30_get_serial_id(uint8_t id[6])
{
uint8_t buf[32];
uint8_t crc[3];
buf[0] = (SGP30_CMD_GET_SERIAL_ID & 0XFF00) >> 8;
buf[1] = (SGP30_CMD_GET_SERIAL_ID & 0X00FF);
if (sgp30_iic_write(SGP30_ADDR_WRITE, buf, 2) < 0)
return -1;
if (sgp30_iic_read(SGP30_ADDR_READ, buf, 9) < 0)
return -2;
crc[0] = buf[2];
crc[1] = buf[5];
crc[2] = buf[8];
id[0] = buf[0];
id[1] = buf[1];
id[2] = buf[3];
id[3] = buf[4];
id[4] = buf[6];
id[5] = buf[7];
if(
sgp30_checksum(&id[0], 2) != crc[0] ||
sgp30_checksum(&id[2], 2) != crc[1] ||
sgp30_checksum(&id[4], 2) != crc[2]
)
return -3;
return 0;
}
static uint8_t sgp30_checksum(const uint8_t* buf, uint32_t len)
{
const uint8_t Polynomial = 0x31;
uint8_t Initialization = 0XFF;
uint8_t i = 0, k = 0;
while(i < len)
{
Initialization ^= buf[i++];
for(k = 0; k < 8; k++)
{
if(Initialization & 0X80)
Initialization = (Initialization << 1) ^ Polynomial;
else
Initialization = (Initialization << 1);
}
}
return Initialization;
}
int sgp30_soft_reset(void)
{
uint8_t cmd = 0X06;
return sgp30_iic_write(0X00, &cmd, 1);
}
int sgp30_init(void)
{
uint8_t buf[2];
// 初始化IIC
if(sgp30_iic_init() < 0)
return -1;
// 软件复位
if (sgp30_soft_reset() < 0)
return -2;
// 等待复位完成
sgp30_delay_ms(50);
buf[0] = (SGP30_CMD_INIT_AIR_QUALITY & 0XFF00) >> 8;
buf[1] = (SGP30_CMD_INIT_AIR_QUALITY & 0X00FF);
// 初始化控制测量参数
if (sgp30_iic_write(SGP30_ADDR_WRITE, buf, 2) < 0)
return -3;
return 0;
}
int sgp30_read(uint16_t* CO2, uint16_t* TVOC)
{
uint8_t buf[8] = {0};
buf[0] = (SGP30_CMD_MEASURE_AIR_QUALITY & 0XFF00) >> 8;
buf[1] = (SGP30_CMD_MEASURE_AIR_QUALITY & 0X00FF);
// 启动空气质量测量
if (sgp30_iic_write(SGP30_ADDR_WRITE, buf, 2) < 0)
return -1;
// 等待测量完成
sgp30_delay_ms(10);
// 读取收到的数据
if (sgp30_iic_read(SGP30_ADDR_READ, buf, 6) < 0)
return -2;
// 校验CRC
if (sgp30_checksum(&buf[3], 2) != buf[5])
return -3;
if (CO2 != NULL) *CO2 = (buf[0] << 8) | buf[1];
if (TVOC != NULL) *TVOC = (buf[3] << 8) | buf[4];
return 0;
}
main函数测试代码:
初始化未完成依据是TVOC == 0 && CO2 == 400
int main(void)
{
Delay_Init();
USART_Printf_Init(115200);
while(sgp30_init() < 0)
{
printf(" sgp30 init fail\r\n");
Delay_Ms(1000);
}
printf(" sgp30 init ok\r\n");
if(sgp30_get_serial_id(ID) < 0)
{
printf(" sgp30 read serial id failed\r\n");
}else
{
printf(" sgp30 read serial id is:");
for(int i = 0; i < 6; i++) printf("%02X", ID[i]);
printf("\r\n");
}
printf(" sgp30 wait air for init");
do
{
if(sgp30_read(&CO2, &TVOC) < 0)
{
printf("\r\n spg30 read failed");
}else
{
printf(".");
}
Delay_Ms(500);
}while(TVOC == 0 && CO2 == 400);
printf("\r\n");
while (1)
{
if(sgp30_read(&CO2, &TVOC) < 0)
{
printf(" sgp30 read fail\r\n");
}
else
{
printf("CO2:%5dppm TVOC:%5dppb\r\n", CO2, TVOC);
}
Delay_Ms(1000);
}
}
实物接线:
串口调试助手打印结果:
|