目录
一、介绍
二、传感器原理
1.原理图
2.引脚描述
3.工作原理介绍
三、程序设计
main.c文件
ccs811.h文件
ccs811.c文件
四、实验效果
五、资料获取
项目分享
一、介绍
CCS811模块是一种气体传感器,可以测量环境中TVOC(总挥发性有机物质)浓度和eCO2(二氧化碳)浓度,作为衡量空气质量(IAQ)的指标。它内部还集成了MCU,使得这款传感器具有了板载处理能力,在无需主机干预的情况下,即可提供等效二氧化碳等级或总挥发性有机化合物(TVOc)指标。
以下是二氧化碳传感器的参数:
型号 | CCS811 |
工作电压 | 1.8~3.6V(推荐3.3V) |
工作电流 | 20mA |
工作温度 | -5~50℃ |
存储温度 | -40~125℃ |
检测范围 | 400~5000ppm |
通信接口 | IIC |
哔哩哔哩视频:
CCS811二氧化碳传感器详解(STM32)
(资料分享见文末)
二、传感器原理
1.原理图
这里需要说明一下,SDA和SCL大家都比较熟悉,但是INT和WAK就少见了,在这款模块中,INT相当于复位引脚,CCS811芯片内部集成的单片机程序跑飞之后可以将该引脚接地复位,同时需要WAK为低电平的时候SDA、SCL才能正常通信
2.引脚描述
引脚名称 | 描述 |
VCC | 供给电压DC 3.3V |
GND | 地线 |
SCL | IIC时钟线 |
SDA | IIC数据线 |
WAK | 低电平使能 |
INT | 中断 |
RST | 复位 |
ADD | 地址选择位 |
3.工作原理介绍
CCS811-811是一种低功耗的数字气体传感器,集成了CCS801传感器和8位MCU(带模数转换器(ADC)),用来检测室内的空气质量,包括二氧化碳(Co2)和广泛的挥发性有机化合物气体(VOCs),产品的低功耗特性可用在环境监测设备上,灵敏度高,智能算法计算TVOC/eCO2数值并输出IIC信号可直接与单片机通信。模块的主要特点:检测室内空气质量的金属氧化物(MOX)传感器,集成了8位MCU用于运算第一级算法,集成了12位ADC用于传感器读数和数字化转换,IIC从属接口可直接接入主控系统复位/中断控制。
三、程序设计
1.使用STM32F103C8T6读取CCS811二氧化碳传感器采集的数据,通过串口发送至电脑
2.将读取得到信息数据同时在OLED上显示
CCS811_SCL | PB6 |
CCS811_SDA | PB7 |
CCS811_WAK | PB5 |
OLED_SCL | PB11 |
OLED_SDA | PB10 |
串口 | 串口1 |
main.c文件
#include "stm32f10x.h"
#include "led.h"
#include "usart.h"
#include "delay.h"
#include "oled.h"
#include "ccs811.h"
/*****************辰哥单片机设计******************
STM32
* 项目 : CCS811二氧化碳传感器实验
* 版本 : V1.0
* 日期 : 2024.8.28
* MCU : STM32F103C8T6
* 接口 : 参看ccs811.h
* BILIBILI : 辰哥单片机设计
* CSDN : 辰哥单片机设计
* 作者 : 辰哥
**********************BEGIN***********************/
float co2;
u8 buff[30];//参数显示缓存数组
int main(void)
{
SystemInit();//配置系统时钟为72M
delay_init(72);
LED_Init();
LED_On();
CCS811_Init();
USART1_Config();//串口初始化
OLED_Init();
printf("Start \n");
delay_ms(1000);
OLED_Clear();
//显示“二氧化碳:”
OLED_ShowChinese(0,0,0,16,1);
OLED_ShowChinese(16,0,1,16,1);
OLED_ShowChinese(32,0,2,16,1);
OLED_ShowChinese(48,0,3,16,1);
OLED_ShowChar(64,0,':',16,1);
while (1)
{
LED_Toggle();
co2 = CCS811_GetData();
OLED_ShowNum(40,20,co2,4,16,1);
OLED_ShowString(80,20,"ppm",16,1);
delay_ms(50); //延时50ms
}
}
ccs811.h文件
#ifndef __CCS811_H
#define __CCS811_H
#include "stm32f10x.h"
#include "delay.h"
#include "sys.h"
/*****************辰哥单片机设计******************
STM32
* 文件 : CCS811二氧化碳传感器h文件
* 版本 : V1.0
* 日期 : 2024.8.28
* MCU : STM32F103C8T6
* 接口 : 见代码
* BILIBILI : 辰哥单片机设计
* CSDN : 辰哥单片机设计
* 作者 : 辰哥
**********************BEGIN***********************/
/***************根据自己需求更改****************/
// CCS811 GPIO宏定义
#define CCS811_IIC_CLK RCC_APB2Periph_GPIOB
#define CCS811_IIC_PORT GPIOB
#define CCS811_IIC_SDA_PIN GPIO_Pin_7
#define CCS811_IIC_SCL_PIN GPIO_Pin_6
#define CCS811_WAK_PORT GPIOB
#define CCS811_WAK_PIN GPIO_Pin_5
//IO操作函数
#define CCS811_IIC_SCL PBout(6) //SCL
#define CCS811_IIC_SDA PBout(7) //SDA
#define CCS811_READ_SDA PBin(7) //输入SDA
/*********************END**********************/
//CCS811
#define CCS811_Add 0x5A<<1
#define STATUS_REG 0x00 //状态寄存器
#define MEAS_MODE_REG 0x01 //测量模式和条件寄存器
#define ALG_RESULT_DATA 0x02 //算法结果。最高有效 2 个字节包含等效 CO2 (eCO2) 水平的 ppm 估计值,最低有效 2 个字节包含总 VOC 水平的 ppb 估计值
#define ENV_DATA 0x05
#define NTC_REG 0x06
#define THRESHOLDS 0x10
#define BASELINE 0x11
#define HW_ID_REG 0x20 //硬件 ID 值为 0x81
#define ERROR_ID_REG 0xE0 //错误 ID。当状态寄存器报告错误时,它的源位于此寄存器中
#define APP_START_REG 0xF4
#define SW_RESET 0xFF
#define CCS_811_ADDRESS 0x5A
#define GPIO_WAKE 0x5
#define DRIVE_MODE_IDLE 0x0
#define DRIVE_MODE_1SEC 0x10
#define DRIVE_MODE_10SEC 0x20
#define DRIVE_MODE_60SEC 0x30
#define INTERRUPT_DRIVEN 0x8
#define THRESHOLDS_ENABLED 0x4
void CCS811_Init(void);
void ON_CCS811(void);
void CCS811_EN(void);
void OFF_CCS811(void);
u16 CCS811_GetData(void);
u8 CCS811_Single_WriteI2C_byte(u8 Slave_Address,u8 REG_Address,u8 data);
u8 CCS811_Single_MWriteI2C_byte(u8 Slave_Address,u8 REG_Address,u8 const *data,u8 length);
u8 CCS811_Single_ReadI2C(u8 Slave_Address,u8 REG_Address,u8 *REG_data,u8 length);
#endif
ccs811.c文件
#include "ccs811.h"
/*****************辰哥单片机设计******************
STM32
* 文件 : CCS811二氧化碳传感器c文件
* 版本 : V1.0
* 日期 : 2024.8.28
* MCU : STM32F103C8T6
* 接口 : 见代码
* BILIBILI : 辰哥单片机设计
* CSDN : 辰哥单片机设计
* 作者 : 辰哥
**********************BEGIN***********************/
u8 MeasureMode,Status,Error_ID;
u8 Information[10];
u8 BUF[12];
typedef struct {
u16 eco2;
u16 tvoc;
u8 status;
u8 error_id;
u16 raw_data;
} ccs811_measurement_t;
ccs811_measurement_t CCS;
u16 car_num;
void CCS811_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(CCS811_IIC_CLK,ENABLE);//先使能外设IO PORTB时钟
GPIO_InitStructure.GPIO_Pin = CCS811_IIC_SDA_PIN|CCS811_IIC_SCL_PIN; // 端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度为50MHz
GPIO_Init(CCS811_IIC_PORT, &GPIO_InitStructure); //根据设定参数初始化GPIO
CCS811_IIC_SCL = 1;
CCS811_IIC_SDA = 1;
CCS811_EN();
CCS811_Single_ReadI2C(CCS811_Add,0x00,&Status,1);
CCS811_Single_ReadI2C(CCS811_Add,0xE0,&Error_ID,1);
CCS811_Single_ReadI2C(CCS811_Add,0x02,BUF,8);
CCS811_Single_ReadI2C(CCS811_Add,0x20,Information,1); //Read CCS's information ,ID
}
//CCS811引脚输出模式控制
void CCS811_IIC_SDA_OUT(void)//SDA输出方向配置
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin=CCS811_IIC_SDA_PIN;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;//SDA推挽输出
GPIO_Init(CCS811_IIC_PORT,&GPIO_InitStructure);
}
void CCS811_IIC_SDA_IN(void)//SDA输入方向配置
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin=CCS811_IIC_SDA_PIN;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;//SCL上拉输入
GPIO_Init(CCS811_IIC_PORT,&GPIO_InitStructure);
}
u16 CCS811_GetData(void)
{
int car;
//CCS811 CO2数据采集
ON_CCS811(); //nWAKE pin is asserted at least 50μs before the transaction and kept asserted throughout,nWAKE pin is active low
CCS811_Single_ReadI2C(CCS811_Add,0x00,&Status,1);
CCS811_Single_ReadI2C(CCS811_Add,0xE0,&Error_ID,1);
CCS811_Single_ReadI2C(CCS811_Add,0x02,BUF,8);
CCS811_Single_ReadI2C(CCS811_Add,0x20,Information,1); //Read CCS's information ,ID
OFF_CCS811();
CCS.eco2= (u16)BUF[0]*256+BUF[1];
CCS.tvoc= (u16)BUF[2]*256+BUF[3];
Information[0]=0;
car=(float)CCS.eco2; //二氧化碳
return car;
}
IIC起始函数//
/*
IIC起始:当SCL处于高电平期间,SDA由高电平变成低电平出现一个下降沿,然后SCL拉低
*/
u8 CCS811_IIC_Start(void)
{
CCS811_IIC_SDA_OUT();
CCS811_IIC_SDA = 1;
delay_us(5); //延时保证时钟频率低于40K,以便从机识别
CCS811_IIC_SCL = 1;
delay_us(5);//延时保证时钟频率低于40K,以便从机识别
//if(!CCS811_READ_SDA) return 0;//SDA线为低电平则总线忙,退出
CCS811_IIC_SDA = 0; //SCL处于高电平的时候,SDA拉低
delay_us(5);
//if(CCS811_READ_SDA) return 0;//SDA线为高电平则总线出错,退出
CCS811_IIC_SCL = 0;
delay_us(5);
return 1;
}
//**************************************
//IIC停止信号
/*
IIC停止:当SCL处于高电平期间,SDA由低电平变成高电平出现一个上升沿
*/
//**************************************
void CCS811_IIC_Stop(void)
{
CCS811_IIC_SDA_OUT();
CCS811_IIC_SDA = 0;
CCS811_IIC_SCL = 0;
delay_us(5);
CCS811_IIC_SCL = 1;
delay_us(5);
CCS811_IIC_SDA = 1;//当SCL处于高电平期间,SDA由低电平变成高电平 //延时
}
//**************************************
//IIC发送应答信号
//入口参数:ack (0:ACK 1:NAK)
/*
应答:当从机接收到数据后,向主机发送一个低电平信号
先准备好SDA电平状态,在SCL高电平时,主机采样SDA
*/
//**************************************
void CCS811_IIC_SendACK(u8 i)
{
CCS811_IIC_SDA_OUT();
if(1==i)
CCS811_IIC_SDA = 1; //准备好SDA电平状态,不应答
else
CCS811_IIC_SDA = 0; //准备好SDA电平状态,应答
CCS811_IIC_SCL = 1; //拉高时钟线
delay_us(5); //延时
CCS811_IIC_SCL = 0 ; //拉低时钟线
delay_us(5);
}
///等待从机应答
/*
当本机(主机)发送了一个数据后,等待从机应答
先释放SDA,让从机使用,然后采集SDA状态
*/
/
u8 CCS811_IIC_WaitAck(void) //返回为:=1有ACK,=0无ACK
{
uint16_t i=0;
CCS811_IIC_SDA_IN();
CCS811_IIC_SDA = 1;delay_us(1); //释放SDA
CCS811_IIC_SCL = 1;delay_us(1); //SCL拉高进行采样
while(CCS811_READ_SDA)//等待SDA拉低
{
i++; //等待计数
if(i==500)//超时跳出循环
break;
}
if(CCS811_READ_SDA)//再次判断SDA是否拉低
{
CCS811_IIC_SCL = 0;
return RESET;//从机应答失败,返回0
}
delay_us(5);//延时保证时钟频率低于40K,
CCS811_IIC_SCL = 0;
delay_us(5); //延时保证时钟频率低于40K,
CCS811_IIC_SDA_OUT();
return SET;//从机应答成功,返回1
}
//**************************************
//向IIC总线发送一个字节数据
/*
一个字节8bit,当SCL低电平时,准备好SDA,SCL高电平时,从机采样SDA
*/
//**************************************
void CCS811_IIC_SendByte(u8 dat)
{ u8 i;
CCS811_IIC_SDA_OUT();
CCS811_IIC_SCL = 0;//SCL拉低,给SDA准备
for (i=0; i<8; i++) //8位计数器
{
if(dat&0x80)//SDA准备
CCS811_IIC_SDA = 1;
else
CCS811_IIC_SDA = 0;
CCS811_IIC_SCL = 1; //拉高时钟,给从机采样
delay_us(5); //延时保持IIC时钟频率,也是给从机采样有充足时间
CCS811_IIC_SCL = 0; //拉低时钟,给SDA准备
delay_us(5); //延时保持IIC时钟频率
dat <<= 1; //移出数据的最高位
}
delay_us(10);
}
//**************************************
//从IIC总线接收一个字节数据
//**************************************
u8 CCS811_IIC_RecvByte()
{
u8 i;
u8 dat = 0;
CCS811_IIC_SDA_IN();
CCS811_IIC_SDA = 1;//释放SDA,给从机使用
delay_us(5); //延时给从机准备SDA时间
for (i=0; i<8; i++) //8位计数器
{
dat <<= 1;
CCS811_IIC_SCL = 1; //拉高时钟线,采样从机SDA
if(CCS811_READ_SDA) //读数据
dat |=0x01;
delay_us(5); //延时保持IIC时钟频率
CCS811_IIC_SCL = 0; //拉低时钟线,处理接收到的数据
delay_us(5); //延时给从机准备SDA时间
}
return dat;
}
//**************************************
//向IIC设备写入一个字节数据
//**************************************
u8 CCS811_Single_WriteI2C_byte(u8 Slave_Address,u8 REG_Address,u8 data)
{
if(CCS811_IIC_Start()==0) //起始信号
{CCS811_IIC_Stop(); return RESET;}
CCS811_IIC_SendByte(Slave_Address); //发送设备地址+写信号
if(!CCS811_IIC_WaitAck()){CCS811_IIC_Stop(); return RESET;}
CCS811_IIC_SendByte(REG_Address); //内部寄存器地址,
if(!CCS811_IIC_WaitAck()){CCS811_IIC_Stop(); return RESET;}
CCS811_IIC_SendByte(data); //内部寄存器数据,
if(!CCS811_IIC_WaitAck()){CCS811_IIC_Stop(); return RESET;}
CCS811_IIC_Stop(); //发送停止信号
return SET;
}
u8 CCS811_Single_MWriteI2C_byte(u8 Slave_Address,u8 REG_Address,u8 const *data,u8 length)
{
if(CCS811_IIC_Start()==0) //起始信号
{CCS811_IIC_Stop();return RESET;}
CCS811_IIC_SendByte(Slave_Address); //发送设备地址+写信号
if(!CCS811_IIC_WaitAck()){CCS811_IIC_Stop();return RESET;}
CCS811_IIC_SendByte(REG_Address); //内部寄存器地址,
if(!CCS811_IIC_WaitAck()){CCS811_IIC_Stop();return RESET;}
while(length)
{
CCS811_IIC_SendByte(*data++); //内部寄存器数据,
if(!CCS811_IIC_WaitAck()){CCS811_IIC_Stop(); return RESET;} //应答
length--;
}
// CCS811_IIC_SendByte(*data); //内部寄存器数据,
// if(!CCS811_IIC_WaitAck()){CCS811_IIC_Stop(); return RESET;}
CCS811_IIC_Stop(); //发送停止信号
return SET;
}
//**************************************
//从IIC设备读取一个字节数据
//**************************************
u8 CCS811_Single_ReadI2C(u8 Slave_Address,u8 REG_Address,u8 *REG_data,u8 length)
{
if(CCS811_IIC_Start()==0) //起始信号
{CCS811_IIC_Stop();return RESET;}
CCS811_IIC_SendByte(Slave_Address); //发送设备地址+写信号
if(!CCS811_IIC_WaitAck()){CCS811_IIC_Stop() ;return RESET;}
CCS811_IIC_SendByte(REG_Address); //发送存储单元地址
if(!CCS811_IIC_WaitAck()){CCS811_IIC_Stop();return RESET;}
if(CCS811_IIC_Start()==0) //起始信号
{CCS811_IIC_Stop(); return RESET;}
CCS811_IIC_SendByte(Slave_Address+1); //发送设备地址+读信号
if(!CCS811_IIC_WaitAck()){CCS811_IIC_Stop(); return RESET;}
while(length-1)
{
*REG_data++=CCS811_IIC_RecvByte(); //读出寄存器数据
CCS811_IIC_SendACK(0); //应答
length--;
}
*REG_data=CCS811_IIC_RecvByte();
CCS811_IIC_SendACK(1); //发送停止传输信号
CCS811_IIC_Stop(); //停止信号
return SET;
}
void CCS811_EN()
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//先使能外设IO PORTB时钟
GPIO_InitStructure.GPIO_Pin = CCS811_WAK_PIN; // 端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度为50MHz
GPIO_Init(CCS811_WAK_PORT, &GPIO_InitStructure); //根据设定参数初始化GPIO
GPIO_ResetBits(CCS811_WAK_PORT,CCS811_WAK_PIN);
}
void ON_CCS811()
{
GPIO_ResetBits(CCS811_WAK_PORT,CCS811_WAK_PIN);
}
void OFF_CCS811()
{
GPIO_SetBits(CCS811_WAK_PORT,CCS811_WAK_PIN);
}