STM32驱动SGP30(标准库+软件IIC)

STM32驱动SGP30(标准库+软件IIC)

0.简介

SGP30是由Sensirion公司推出的一款多功能空气质量传感器,采用CMOSens技术,通过I2C接口输出数据。具备高度集成、高灵敏度、低功耗等特点,适合用于家庭室内空气监测系统中。该模块可以同时测量空气中的总挥发性有机物(TVOC)和CO2当量浓度,相比MQ系列传感器,其数据更稳定、响应更迅速,适合精准的空气质量评估。

SGP30的核心原理是基于金属氧化物(MOX)气体感应技术。传感器表面涂覆了一层金属氧化物材料,这些材料在与空气中的气体分子接触时,会发生氧化还原反应,从而导致材料电导率的变化。这种变化会被内部电路转换为数字信号,SGP30采用多像素设计,每个像素对特定气体更敏感,传感器内部的算法会综合各像素的数据,输出稳定而精确的TVOC和CO2浓度值。SGP30计算CO2的值是基于检测到的H2浓度,因为室内环境中人类活动会产生H2,且与CO2水平有一定的相关性,因此CO2提供的是一个估算值,而非直接的CO2测量,测量范围见表。

检测量测量范围
TVOC0~60000ppb
CO2400~60000ppm

1. 部分SGP30数据手册解读

通信地址

在这里插入图片描述
在这里插入图片描述

  • 这部分就是说SGP30遵循IIC协议,从机地址是0X58,每个命令和返回数据都是十六位的数据,并且发送的命令的十六位包含三位CRC校验,接收的数据后面跟了八位的CRC校验。

  • 由于地址只用到了7bit,最高位未使用,最低位为判断是读还是写,为0是读,为1是写

    • 对于写SGP30,地址为(0X58 << 1) = 0XB0
    • 对于读SGP30,地址为((0X58 << 1)) | 0X01 = 0XB1

读取数据

在这里插入图片描述
在这里插入图片描述

  • 接收数据后要及时发送接收应答
  • 我之前因为接收应答发送有问题,导致了收到的数据虽然不是65535,但是接收的数据一个是511,另一个是65535

在这里插入图片描述
在这里插入图片描述

  • SGP30获取的数据格式:2位CO2数据 + 1位CO2的CRC校验 + 2位TVOC数据 + 1位TVOC的CRC校验

  • 模块上电需要15s左右初始化,在初始化阶段读取的CO2浓度为400ppm,TVOC为0ppd且恒定不变。

在这里插入图片描述

  • 初始化SGP30命令:0x2003
  • 获取空气质量值命令:0x2008

2. STM32软件IIC驱动SGP30

  • 软件IIC的代码是在江科大的代码基础上稍微修改了一下
  • 使用的时候直接在sgp30.h中修改引脚就行

sgp30.c

#include "sgp30.h"



/**
  * 函    数:I2C写SCL引脚电平
  * 参    数:BitValue 协议层传入的当前需要写入SCL的电平,范围0~1
  * 返 回 值:无
  * 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置SCL为低电平,当BitValue为1时,需要置SCL为高电平
  */
void IIC_W_SCL(uint8_t BitValue)
{
	GPIO_WriteBit(SGP30_GPIO_PORT, SGP30_SCL_GPIO_PIN, (BitAction)BitValue);		//根据BitValue,设置SCL引脚的电平
	delay_us(10);												//延时10us,防止时序频率超过要求
}

/**
  * 函    数:I2C写SDA引脚电平
  * 参    数:BitValue 协议层传入的当前需要写入SDA的电平,范围0~0xFF
  * 返 回 值:无
  * 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置SDA为低电平,当BitValue非0时,需要置SDA为高电平
  */
void IIC_W_SDA(uint8_t BitValue)
{
	GPIO_WriteBit(SGP30_GPIO_PORT, SGP30_SDA_GPIO_PIN, (BitAction)BitValue);		//根据BitValue,设置SDA引脚的电平,BitValue要实现非0即1的特性
	delay_us(10);												//延时10us,防止时序频率超过要求
}

/**
  * 函    数:I2C读SDA引脚电平
  * 参    数:无
  * 返 回 值:协议层需要得到的当前SDA的电平,范围0~1
  * 注意事项:此函数需要用户实现内容,当前SDA为低电平时,返回0,当前SDA为高电平时,返回1
  */
uint8_t IIC_R_SDA(void)
{
	uint8_t BitValue;
	BitValue = GPIO_ReadInputDataBit(SGP30_GPIO_PORT, SGP30_SDA_GPIO_PIN);		//读取SDA电平
	delay_us(10);												//延时10us,防止时序频率超过要求
	return BitValue;											//返回SDA电平
}

/**
  * 函    数:I2C初始化
  * 参    数:无
  * 返 回 值:无
  * 注意事项:此函数需要用户实现内容,实现SCL和SDA引脚的初始化
  */
void IIC_Init(void)
{
	/*开启时钟*/
	RCC_APB2PeriphClockCmd(SGP30_GPIO_CLK, ENABLE);	//开启GPIOB的时钟
	
	/*GPIO初始化*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
	GPIO_InitStructure.GPIO_Pin = SGP30_SCL_GPIO_PIN | SGP30_SDA_GPIO_PIN;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(SGP30_GPIO_PORT, &GPIO_InitStructure);					//将PB10和PB11引脚初始化为开漏输出
	
	/*设置默认电平*/
	GPIO_SetBits(SGP30_GPIO_PORT, SGP30_SCL_GPIO_PIN | SGP30_SDA_GPIO_PIN);			//设置PB10和PB11引脚初始化后默认为高电平(释放总线状态)
}

/*协议层*/

/**
  * 函    数:I2C起始
  * 参    数:无
  * 返 回 值:无
  */
void IIC_Start(void)
{
	IIC_W_SDA(1);							//释放SDA,确保SDA为高电平
	IIC_W_SCL(1);							//释放SCL,确保SCL为高电平
	IIC_W_SDA(0);							//在SCL高电平期间,拉低SDA,产生起始信号
	IIC_W_SCL(0);							//起始后把SCL也拉低,即为了占用总线,也为了方便总线时序的拼接
}

/**
  * 函    数:I2C终止
  * 参    数:无
  * 返 回 值:无
  */
void IIC_Stop(void)
{
	IIC_W_SDA(0);							//拉低SDA,确保SDA为低电平
	IIC_W_SCL(1);							//释放SCL,使SCL呈现高电平
	IIC_W_SDA(1);							//在SCL高电平期间,释放SDA,产生终止信号
}

/**
  * 函    数:I2C发送一个字节
  * 参    数:Byte 要发送的一个字节数据,范围:0x00~0xFF
  * 返 回 值:无
  */
void IIC_SendByte(uint8_t Byte)
{
	uint8_t i;
	for (i = 0; i < 8; i ++)				//循环8次,主机依次发送数据的每一位
	{
		IIC_W_SDA(Byte & (0x80 >> i));	//使用掩码的方式取出Byte的指定一位数据并写入到SDA线
		IIC_W_SCL(1);						//释放SCL,从机在SCL高电平期间读取SDA
		IIC_W_SCL(0);						//拉低SCL,主机开始发送下一位数据
	}
}

/**
  * 函    数:I2C接收一个字节
  * 参    数:无
  * 返 回 值:接收到的一个字节数据,范围:0x00~0xFF
  */
uint8_t IIC_ReceiveByte(void)
{
	uint8_t i, Byte = 0x00;					//定义接收的数据,并赋初值0x00,此处必须赋初值0x00,后面会用到
	IIC_W_SDA(1);							//接收前,主机先确保释放SDA,避免干扰从机的数据发送
	for (i = 0; i < 8; i ++)				//循环8次,主机依次接收数据的每一位
	{
		IIC_W_SCL(1);						//释放SCL,主机机在SCL高电平期间读取SDA
		if (IIC_R_SDA() == 1){Byte |= (0x80 >> i);}	//读取SDA数据,并存储到Byte变量
														//当SDA为1时,置变量指定位为1,当SDA为0时,不做处理,指定位为默认的初值0
		IIC_W_SCL(0);						//拉低SCL,从机在SCL低电平期间写入SDA
	}
	return Byte;							//返回接收到的一个字节数据
}

/**
  * 函    数:I2C发送应答位
  * 参    数:Byte 要发送的应答位,范围:0~1,0表示应答,1表示非应答
  * 返 回 值:无
  */
void IIC_SendAck(uint8_t AckBit)
{
	IIC_W_SDA(AckBit);					//主机把应答位数据放到SDA线
	IIC_W_SCL(1);							//释放SCL,从机在SCL高电平期间,读取应答位
	IIC_W_SCL(0);							//拉低SCL,开始下一个时序模块
}

/**
  * 函    数:I2C接收应答位
  * 参    数:无
  * 返 回 值:接收到的应答位,范围:0~1,0表示应答,1表示非应答
  */
uint8_t IIC_ReceiveAck(void)
{
	uint8_t AckBit;							//定义应答位变量
	IIC_W_SDA(1);							//接收前,主机先确保释放SDA,避免干扰从机的数据发送
	IIC_W_SCL(1);							//释放SCL,主机机在SCL高电平期间读取SDA
	AckBit = IIC_R_SDA();					//将应答位存储到变量里
	IIC_W_SCL(0);							//拉低SCL,开始下一个时序模块
	return AckBit;							//返回定义应答位变量
}


//初始化IIC接口
void SGP30_Init(void)
{
  IIC_Init();
  SGP30_Write(0x20, 0x03);
//	SGP30_ad_write(0x20,0x61);
//	SGP30_ad_write(0x01,0x00);
}


void SGP30_Write(u8 a, u8 b)
{
  IIC_Start();
  IIC_SendByte(SGP30_write); //发送器件地址+写指令
  IIC_ReceiveAck();

  IIC_SendByte(a);		//发送控制字节
  IIC_ReceiveAck();

  IIC_SendByte(b);
  IIC_ReceiveAck();

  IIC_Stop();
  delay_ms(100);
}

u32 SGP30_Read(void)
{
  u32 dat;
  u8 crc;
	
  IIC_Start();
  IIC_SendByte(SGP30_read); //发送器件地址+读指令
  IIC_ReceiveAck();

	
  dat = IIC_ReceiveByte();   //CO2高位数据
  IIC_SendAck(0);
  dat <<= 8;
  dat += IIC_ReceiveByte();  //CO2低位数据
  IIC_SendAck(0);
	
  crc = IIC_ReceiveByte(); //crc数据,舍去
  IIC_SendAck(0);
  crc = crc;  //为了不让出现编译警告
	
  dat <<= 8;
  dat += IIC_ReceiveByte(); //TVOC高位数据
  IIC_SendAck(0);
  dat <<= 8;
  dat += IIC_ReceiveByte();//TVOC低位数据
  IIC_SendAck(0);
  
  crc = IIC_ReceiveByte(); //crc数据,舍去
  IIC_SendAck(1);
  
  IIC_Stop();

  return(dat);
}

void Get_CO2_TVOC(int n){
	
	uint32_t sgp30_dat = 0,CO2_val = 0,TVOC_val = 0;  // 初始化,避免随机的脏数据
	u8 t;
	for(t=0;t<n;t++)
	{
		SGP30_Write(0x20,0x08);
		sgp30_dat = SGP30_Read();//读取SGP30的值
		CO2_val += (sgp30_dat & 0xffff0000) >> 16;//取出CO2浓度值
		TVOC_val += sgp30_dat & 0x0000ffff;
		delay_ms(10);	
	}
	CO2 = CO2_val / n;
	
	TVOC = TVOC_val / n;
	
}

sgp30.h

#ifndef __SGP30_H
#define __SGP30_H
#include "stm32f10x.h"
#include "Delay.h"


// USART GPIO 引脚宏定义
#define  SGP30_GPIO_CLK        RCC_APB2Periph_GPIOA
#define  SGP30_GPIO_PORT       GPIOA
#define  SGP30_SCL_GPIO_PIN        GPIO_Pin_10
#define  SGP30_SDA_GPIO_PIN        GPIO_Pin_9


#define SGP30_read  0xb1  //SGP30的读地址
#define SGP30_write 0xb0  //SGP30的写地址

extern  uint32_t TVOC,CO2;

void IIC_Init(void);
void IIC_Start(void);
void IIC_Stop(void);
void IIC_SendByte(uint8_t Byte);
uint8_t IIC_ReceiveByte(void);
void IIC_SendAck(uint8_t AckBit);
uint8_t IIC_ReceiveAck(void);

void SGP30_Init(void);				  
void SGP30_Write(u8 a, u8 b);
u32 SGP30_Read(void);
void Get_CO2_TVOC(int n);


#endif

Delay.c

#include "Delay.h"
#include "misc.h"

static u8  fac_us=0;//us延时倍乘数
static u16 fac_ms=0;//ms延时倍乘数

//初始化延迟函数
//SYSTICK的时钟固定为HCLK时钟的1/8
//SYSCLK:系统时钟
void delay_init(u8 SYSCLK)
{
//	SysTick->CTRL&=0xfffffffb;//bit2清空,选择外部时钟  HCLK/8
	SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);	//选择外部时钟  HCLK/8
	fac_us=SYSCLK/8;		    
	fac_ms=(u16)fac_us*1000;
}								    
//延时nms
//注意nms的范围
//SysTick->LOAD为24位寄存器,所以,最大延时为:
//nms<=0xffffff*8*1000/SYSCLK
//SYSCLK单位为Hz,nms单位为ms
//对72M条件下,nms<=1864 
void delay_ms(u16 nms)
{	 		  	  
	u32 temp;		   
	SysTick->LOAD=(u32)nms*fac_ms;//时间加载(SysTick->LOAD为24bit)
	SysTick->VAL =0x00;           //清空计数器
	SysTick->CTRL=0x01 ;          //开始倒数  
	do
	{
		temp=SysTick->CTRL;
	}
	while(temp&0x01&&!(temp&(1<<16)));//等待时间到达   
	SysTick->CTRL=0x00;       //关闭计数器
	SysTick->VAL =0X00;       //清空计数器	  	    
}   
//延时nus
//nus为要延时的us数.		    								   
void delay_us(u32 nus)
{		
	u32 temp;	    	 
	SysTick->LOAD=nus*fac_us; //时间加载	  		 
	SysTick->VAL=0x00;        //清空计数器
	SysTick->CTRL=0x01 ;      //开始倒数 	 
	do
	{
		temp=SysTick->CTRL;
	}
	while(temp&0x01&&!(temp&(1<<16)));//等待时间到达   
	SysTick->CTRL=0x00;       //关闭计数器
	SysTick->VAL =0X00;       //清空计数器	 
}

void delay_s(u16 ns)
{
	for(int i=0;i<ns;i++)
	delay_ms(1000);
}

Delay.h

#ifndef __DELAY_H
#define __DELAY_H

#include "stm32f10x.h"

void delay_init(u8 SYSCLK);
void delay_ms(u16 nms);
void delay_us(u32 nus);
void delay_s(u16 ns);

#endif

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h" 
#include "usart.h"
#include "sgp30.h"

uint32_t TVOC = 0,CO2 = 0;


void sys_init(void);


int main(){
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    delay_init(72);
	SGP30_Init();   //初始化SGP30
	uart1_init(115200);

	while(1){
		Get_CO2_TVOC(5);
		printf("CO2:%dppm------TVOC:%dppd\r\n",CO2,TVOC);
    }  
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

追上

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值