1. 传感器用途

检测原理

采用非分散红外(NDIR)技术,利用 CO₂分子对特定波长(4.26 微米)红外光的选择性吸收特性实现测量。传感器内部红外光源发射光束,经过气室时部分光被 CO₂吸收,探测器通过检测光强变化计算 CO₂浓度。

参数与性能

测量范围:350~2000ppm(适用于室内空气质量监测场景);

输出方式:UART 串口通信,波特率 9600,数据格式为 6 字节(基础版)或 9 字节(三合一版本,可同步检测甲醛、TVOC);

工作电压:5V,预热时间约 60 秒,响应速度达 1 次 / 秒;

精度与稳定性:通过校验和算法(前 5 字节之和等于第 6 字节)确保数据准确性,支持温度补偿功能,可消除环境干扰。

典型应用场景

智能家居与环境监测:实时监测家庭、办公室、教室等室内 CO₂浓度,联动新风系统自动换气(如浓度超过 1000ppm 时触发通风),改善空气质量;

农业与温室控制:在大棚种植中,通过监测 CO₂浓度优化植物光合作用效率(如浓度低于 800ppm 时自动释放 CO₂气肥,提升作物产量);

工业过程监控:用于食品包装(控制气调保鲜环境)、酿酒发酵(监测 CO₂生成量)等场景,保障生产过程稳定性;

物联网与嵌入式系统:作为物联网节点的传感器单元,将数据上传至云端(如阿里云、OneNET),实现远程监控与数据分析(例如在智能宠物托运装置中集成 JW01,实时推送宠物舱内 CO₂浓度)。

2. 传感器介绍

JW01-CO₂-V2.2 是由佰培电子推出的模块化传感器,专为低成本、高精度室内 / 场景化 CO₂监测设计,核心优势在于 “NDIR 技术小型化 + 串口直读”,无需复杂外围电路即可实现 CO₂浓度采集,广泛适配单片机、物联网模块等嵌入式系统。

核心特性

小型化设计:尺寸约 25mm×30mm×10mm,兼容面包板 / PCB 焊接,适合空间受限设备(如智能手环、小型空气监测仪);

即插即用:仅需 5V 电源 + 2 根 UART 通信线即可工作,无需额外校准电路(出厂预校准,支持后期指令校准);

多版本可选:

基础版:仅监测 CO₂(350~2000ppm);

三合一版:额外集成甲醛(0~1.5mg/m³)、TVOC(0~6mg/m³)检测,数据格式扩展为 9 字节,满足多参数空气质量监测需求;

抗干扰能力:内置温度补偿电路,可抵消 - 10℃~60℃环境温度对检测精度的影响(误差≤±5% FS);气室采用防尘滤网,减少灰尘对红外探测的干扰。

3.单片机连接硬件图

实物图

4. 驱动思路

JW01 的驱动核心为 “UART 数据接收 + 校验和解析”,需按 “初始化→数据接收→校验→解析→应用” 流程设计,具体如下:

步骤 1:硬件初始化

UART 初始化:配置 STM32 的 USART1(PA9=TX,PA10=RX),波特率 9600、8 位数据位、1 位停止位、无校验(匹配 JW01 默认参数),使能接收中断(避免轮询占用 CPU 资源);

GPIO 初始化:配置 OLED 的 I2C 引脚(PB6=SCL,PB7=SDA)为推挽输出;

步骤 2:数据接收与缓存

开启 UART 接收中断,将接收到的字节存入环形缓冲区;

步骤 3:数据校验与解析

校验和验证:基础版 6 字节数据格式为[0x2C, D1, D2, D3, D4, SUM],其中SUM = (0x2C + D1 + D2 + D3 + D4) % 256;若计算的 SUM 与接收的第 6 字节不一致,则丢弃该帧数据;

浓度计算:CO₂浓度值由 D1(高 8 位)和 D2(低 8 位)组成,公式为CO₂浓度(ppm) = (D1 << 8) | D2(例如 D1=0x03、D2=0xE8 时,浓度 = 1000ppm)。

步骤 4:数据应用与联动

显示输出:将解析后的 CO₂浓度值通过OLED显示,格式为 “CO2 Value: 1050 ppm”;

串口一打印以下数据:

接收数据(Hex): 2C 01 66 03 FF 95

模块地址: 0x2C

计算校验和: 0x95

接收校验和: 0x95

CO2 数值: 358 ppm

5. 单片机程序代码

main.c

#include "stm32f10x.h"  
#include "string.h"  
#include "stdio.h" 
#include "delay.h"   
#include "bsp_usart.h"
#include "oled.h"

uint8_t parseData(uint8_t data[], uint8_t length)
{
    // 检查数据长度是否符合要求
    if(length < 6) {
        printf("数据长度不足!\n");
        return 0;
    }
		printf("接收数据(Hex): ");
    for(int i = 0; i < length; i++) {
        printf("%02X ", data[i]);
    }
    printf("\n");
    // 模块地址
    uint8_t moduleAddress = data[0]; 
    // CO2浓度高字节
    uint8_t co2High = data[1]; 
    // CO2浓度低字节
    uint8_t co2Low = data[2]; 
    // 满量程高字节
    uint8_t fullScaleHigh = data[3]; 
    // 满量程低字节
    uint8_t fullScaleLow = data[4]; 
    // 校验和
    uint8_t checksum = data[5]; 
    // 计算校验和
    uint8_t calculatedChecksum = 0;
    for(int i = 0; i < 5; i++) {
        calculatedChecksum += data[i];
    }
    // 输出模块地址
    printf("模块地址: 0x%02X\n", moduleAddress); 
    // 输出计算得到的校验和
    printf("计算校验和: 0x%02X\n", calculatedChecksum); 
    // 输出接收到的校验和
    printf("接收校验和: 0x%02X\n", checksum); 
    // 校验和验证
    if (calculatedChecksum == checksum) {
        // 计算并输出CO2浓度值
        uint16_t co2Value = (uint16_t)(co2High * 256 + co2Low); 
        printf("CO2 数值: %d ppm\n", co2Value);
        // 在OLED上显示CO2浓度
        OLED_Clear();
        OLED_ShowString(0, 0, "CO2 Sensor");
        OLED_ShowString(0, 2, "CO2 Value:");
        char co2Str[16];
        sprintf(co2Str, "%d ppm", co2Value);
        OLED_ShowString(0, 4, co2Str);
        return 1; // 返回成功标志
    } else {
        printf("校验和不匹配,数据可能有误!\n");
        OLED_Clear();
        OLED_ShowString(0, 0, "CO2 Sensor");
        OLED_ShowString(0, 2, "Checksum Error!");
        return 0; // 返回失败标志
    }
}

int main(void)
{
	NVIC_PriorityGroupConfig (NVIC_PriorityGroup_2);
	SysTick_Init(72);  //系统时钟初始化 
	usart1_init(115200);//串口1初始化
	printf("USART1 OK!\r\n");
	usart2_init(9600);//串口1初始化
	usart3_init(115200);//串口3初始化	
	OLED_Init();
	while(1)
	{
		  if(buf_uart2.rx_flag==1)
			{			
			  delay_ms(10);
				parseData((uint8_t *)buf_uart2.buf, buf_uart2.index);
				Clear_Buffer_UART2();
			}
	}
}

Uart.c

#include "stm32f10x.h"
#include "bsp_usart.h"	  
#include "stdio.h"
#include "string.h"
#include "stm32f10x_tim.h"


////////////////////////////////////////////////////////////////////////////////// 	

//////////////////////////////////////////////////////////////////
//加入以下代码,支持printf函数,而不需要选择use MicroLIB	  
#if 1
#pragma import(__use_no_semihosting)             
//标准库需要的支持函数                 
struct __FILE 
{ 
	int handle; 
}; 

FILE __stdout;       
//定义_sys_exit()以避免使用半主机模式    
int _sys_exit(int x) 
{ 
	x = x; 
	return 0;
} 
//重定义fputc函数 
int fputc(int ch, FILE *f)
{ 	
	while((USART1->SR&0X40)==0);//循环发送,直到发送完毕   
	USART1->DR = (u8) ch;      
	return ch;
}
#endif
 
#if EN_USART1

UART_BUF buf_uart1;     //CH340
//初始化IO 串口1 
//bound:波特率
void usart1_init(u32 bound){
   //GPIO端口设置
  GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE);	//使能USART1,GPIOA时钟
	USART_DeInit(USART1);  //复位串口1
 //USART1_TX   PA.9
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	//复用推挽输出
	GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化PA9
 
	//USART1_RX	  PA.10
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//浮空输入
	GPIO_Init(GPIOA, &GPIO_InitStructure);  //初始化PA10

  //USART 初始化设置

	USART_InitStructure.USART_BaudRate = bound;//一般设置为115200;
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
	USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
	USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;	//收发模式
	USART_Init(USART1, &USART_InitStructure); //初始化串口
	
	USART_Cmd(USART1, ENABLE);                    //使能串口 

	
#if EN_USART1_RX	

	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启相关中断
	USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);//开启相关中断
	USART_ClearFlag(USART1, USART_FLAG_TC);
	//Usart1 NVIC 配置
  NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;//串口1中断通道
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3;//抢占优先级3
	NVIC_InitStructure.NVIC_IRQChannelSubPriority =3;		//子优先级3
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQ通道使能
	NVIC_Init(&NVIC_InitStructure);	//根据指定的参数初始化VIC寄存器、

#endif

}

/*********************************串口1的服务函数*************************************************/
void USART1_Send_byte(char data)
{
	while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
	USART_SendData(USART1, data);
}
/*-------------------------------------------------*/
/*函数名:串口1 发送数组                             */
/*参  数:bound:波特率                             */
/*返回值:无                                        */
/*-------------------------------------------------*/
void USART1_Send(char *Data,uint16_t Len)
{ 
	uint16_t i;
	for(i=0; i<Len; i++)
	{
		USART1_Send_byte(Data[i]);
	}
}
void USART1_SendStr(char*SendBuf)//串口1打印数据
{
	while(*SendBuf)
	{
        while((USART1->SR&0X40)==0);//等待发送完成 
        USART1->DR = (u8) *SendBuf; 
				SendBuf++;
	}
}

/*****************************************************
清空电脑反馈的缓冲数据 串口1
*****************************************************/
void Clear_Buffer_UART1(void)//清空缓存
{
    buf_uart1.index=0;
	  buf_uart1.rx_flag=0;
    memset(buf_uart1.buf,0,BUFLEN);
}
void UART1_receive_process_event(char ch )     //串口2给4g用
{
    if(buf_uart1.index >= BUFLEN)
    {
        buf_uart1.index = 0 ;
    }
    else
    {
        buf_uart1.buf[buf_uart1.index++] = ch;
    }
}

//串口1的接收中断程序
void USART1_IRQHandler(void)                                //串口1中断服务程序
{
		uint8_t Res;
		Res=Res;
    if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)  //接收中断,可以扩展来控制
    {
        Res=USART_ReceiveData(USART1);//接收模块的数据;

        UART1_receive_process_event(Res);//接收模块的数据
    } 
    if(USART_GetITStatus(USART1, USART_IT_IDLE) != RESET)  //模块空闲
    {
        Res=USART_ReceiveData(USART1);//接收模块的数据;

				buf_uart1.rx_flag=1;
    } 

} 


#endif




#if EN_USART2
UART_BUF buf_uart2;     //EC200T
//初始化IO 串口2
//pclk1:PCLK1时钟频率(Mhz)
//bound:波特率 
void usart2_init(u32 bound)
{  	 

    GPIO_InitTypeDef GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

		RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	//使能,GPIOA时钟
		RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);//USART2
		USART_DeInit(USART2);  //复位串口2
	 //USART2_TX   PA.2
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; //PA.2
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	//复用推挽输出
    GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化PA2
   
    //USART2_RX	  PA.3
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
    GPIO_Init(GPIOA, &GPIO_InitStructure);  //初始化PA3

   
   //USART 初始化设置

		USART_InitStructure.USART_BaudRate = bound;//115200
		USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
		USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
		USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
		USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
		USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;	//收发模式
    USART_Init(USART2, &USART_InitStructure); //初始化串口
    USART_Cmd(USART2, ENABLE);                    //使能串口 




#if EN_USART2_RX	
    USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);//开启相关中断
		USART_ITConfig(USART2, USART_IT_IDLE, ENABLE);//开启相关中断
		
    //Usart1 NVIC 配置
    NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;//串口1中断通道
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;//抢占优先级3
    NVIC_InitStructure.NVIC_IRQChannelSubPriority =1;		//子优先级3
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQ通道使能
    NVIC_Init(&NVIC_InitStructure);	//根据指定的参数初始化VIC寄存器、
#endif
}


void Clear_Buffer_UART2(void)//清空缓存
{
    buf_uart2.index=0;
	  buf_uart2.rx_flag=0;
    memset(buf_uart2.buf,0,BUFLEN);
}

/*********************************串口2的服务函数*************************************************/
void USART2_Send_byte(char data)
{
	while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET);
	USART_SendData(USART2, data);
}




/*-------------------------------------------------*/
/*函数名:串口2 发送数组                             */
/*参  数:bound:波特率                             */
/*返回值:无                                        */
/*-------------------------------------------------*/
void USART2_Send(char *Data,uint16_t Len)
{ 
	uint16_t i;
	for(i=0; i<Len; i++)
	{
		USART2_Send_byte(Data[i]);
	}
}



void USART2_SendStr(char*SendBuf)//串口1打印数据
{
	while(*SendBuf)
	{
        while((USART2->SR&0X40)==0);//等待发送完成 
        USART2->DR = (u8) *SendBuf; 
        SendBuf++;
	}
}


void usart2_receive_process_event(unsigned char ch )     //串口2给4g用
{
    if(buf_uart2.index >= BUFLEN)
    {
        buf_uart2.index = 0 ;
    }
    else
    {
        buf_uart2.buf[buf_uart2.index++] = ch;
			
    }
}
void USART2_IRQHandler(void)                            //串口2接收函数
{
		char Res;
		Res=Res;
    if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET)  //接收中断,可以扩展来控制
    {
        Res=USART_ReceiveData(USART2);//接收模块的数据;
        usart2_receive_process_event(Res);//接收模块的数据
    } 
    if(USART_GetITStatus(USART2, USART_IT_IDLE) != RESET)  //模块空闲
    {
        Res=USART_ReceiveData(USART2);//接收模块的数据;
        buf_uart2.rx_flag=1;
		 } 
}


#endif


#if EN_USART3

UART_BUF buf_uart3;     //TTL
void usart3_init(u32 bound)
{
    //GPIO端口设置
    GPIO_InitTypeDef GPIO_InitStructure;
		USART_InitTypeDef USART_InitStructure;
		NVIC_InitTypeDef NVIC_InitStructure;
		 
		RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);	//使能,GPIOA时钟
		RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3,ENABLE);//USART3
		USART_DeInit(USART3);  //复位串口3
	 //USART3_TX   PB10
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; //PB10
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	//复用推挽输出
    GPIO_Init(GPIOB, &GPIO_InitStructure); //初始化PA2
   
    //USART3_RX	  PB11
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
    GPIO_Init(GPIOB, &GPIO_InitStructure);  //初始化PB11

  
   //USART 初始化设置

		USART_InitStructure.USART_BaudRate = bound;//115200
		USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
		USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
		USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
		USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
		USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;	//收发模式
    USART_Init(USART3, &USART_InitStructure); //初始化串口
	
    USART_Cmd(USART3, ENABLE);                    //使能串口 

#if EN_USART3_RX	

	USART_ITConfig(USART3, USART_IT_RXNE, ENABLE);//开启相关中断
	USART_ITConfig(USART3, USART_IT_IDLE, ENABLE);//开启相关中断
	USART_ClearFlag(USART3, USART_FLAG_TC);
	//Usart3 NVIC 配置
  NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;//串口3中断通道
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;//抢占优先级1
	NVIC_InitStructure.NVIC_IRQChannelSubPriority =3;		//子优先级3
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQ通道使能
	NVIC_Init(&NVIC_InitStructure);	//根据指定的参数初始化VIC寄存器、
#endif
	
}


void USART3_Send_byte(char data)
{
	while(USART_GetFlagStatus(USART3, USART_FLAG_TXE) == RESET);
	USART_SendData(USART3, data);
}


/*-------------------------------------------------*/
/*函数名:串口2 发送数组                             */
/*参  数:bound:波特率                             */
/*返回值:无                                        */
/*-------------------------------------------------*/
void USART3_Send(char *Data,uint16_t Len)
{ 
	uint16_t i;
	for(i=0; i<Len; i++)
	{
		USART3_Send_byte(Data[i]);
	}
}




void USART3_SendStr(char*SendBuf)//串口3打印数据
{
	while(*SendBuf)
	{
        while((USART3->SR&0X40)==0);//等待发送完成 
        USART3->DR = (u8) *SendBuf; 
        SendBuf++;
	}
}


/*****************************************************
清空电脑反馈的缓冲数据 串口1
*****************************************************/
void Clear_Buffer_UART3(void)//清空缓存
{
    buf_uart3.index=0;
	  buf_uart3.rx_flag=0;
    memset(buf_uart3.buf,0,BUFLEN);
}
void USART3_receive_process_event(char ch )     //串口2给4g用
{
    if(buf_uart3.index >= BUFLEN)
    {
        buf_uart3.index = 0 ;
    }
    else
    {
        buf_uart3.buf[buf_uart3.index++] = ch;
    }
} 
void USART3_IRQHandler(void)                                //串口3中断服务程序
{
		char Res;
		Res=Res;
    if(USART_GetITStatus(USART3, USART_IT_RXNE) != RESET)  //接收中断,可以扩展来控制
    {
        Res=USART_ReceiveData(USART3);//接收模块的数据;
        USART3_receive_process_event(Res);
    } 
    if(USART_GetITStatus(USART3, USART_IT_IDLE) != RESET)  //模块空闲
    {
        Res=USART_ReceiveData(USART3);//接收模块的数据;
				buf_uart3.rx_flag=1;
    } 
} 	

#endif




6.实现效果图片

代码下载链接

https://download.csdn.net/download/qq_41954594/92121204?spm=1001.2014.3001.5503https://download.csdn.net/download/qq_41954594/92121204?spm=1001.2014.3001.5503

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐