毕业设计 - 基于STM32的空气质量检测仪 - 环境检测盒子


1 简介

Hi,大家好,这里是丹成学长,今天向大家介绍一个 单片机项目

基于STM32的空气质量检测仪

大家可用于 课程设计 或 毕业设计

技术解答、毕设帮助、开题指导
print("Q 746876041") 

2 系统设计概述

如今人们大约 80%的时间是在室内度过的, 室内空气质量与我们每个人的工作和生活都息息相关, 因此对生活环境的空气质量提出了更高的要求。 针对雾霾、 室内装修等污染问题, 人们还没有有效的办法控制空气中的有害物质, 这一问题也成为人们的健康隐患, 因此对室内的空气质量进行检测至关重要。

当室内有害气体的浓度超标时, 会对人们的身心健康带来不可忽视的影响。 为了让人们及时准确地获得室内有害气体甲醛、 PM2.5 的浓度, 采取有效措施改善生活环境的空气质量, 提高工作效率。 根据我国室内空气监管标准 GB50325-2010, 学长设计了一款基于STM32F103C8T6 单片机的便携式、 低功耗室内空气质量检测仪。 该检测仪可以实时测量室内的温湿度、 甲醛浓度、 PM2.5 浓度, 并在液晶显示屏 LCD12864 上实时显示出来, 当甲醛浓度或 PM2.5 浓度超过报警值时, 测试仪通过亮红灯和蜂鸣器鸣叫的方式提醒用户室内有害气体超标。

用户可以根据需要通过 GSM 短信方式将室内空气质量情况发送到移动终端, 最后系统在 Visual Studio 2012 平台运用 C#语言开发上位机, 可以实时显示空气质量指标。

在这里插入图片描述

3 系统总体方案

学长设计为了能够实时检测室内的空气质量情况, 提高系统的稳定性和自动化程度, 将系统分为硬件设计和软件设计。 本文研制的室内 空气质 量检测仪是基于 32 位单片机STM32C8T6 进行设计的, 将整个系统模块化, 主要包括 STM32 最小系统、 温湿度检测模块、 甲醛气体检测模块、 PM2.5 检测模块、 按键、 液晶显示器、 蜂鸣器、 GSM 模块、蓝牙模块、 移动终端和上位机以及电源管理电路。 其系统结构框图如图所示。

在这里插入图片描述

在整个室内空气质量检测系统中, STM32 芯片是整个系统的主控制器, 整个检测系统都是在它的控制下完成的, 并且 STM32 为测试仪提供外部接口, 如液晶显示屏、
按键、 数据采集电路。 首先通过温湿度、 甲醛浓度以及 PM2.5 等传感器进行温湿度、 甲醛气体及 PM2.5 浓度信号采集, 再将采集到的信号送至中央处理单元 STM32 单片机进行信号转换、 放大和处理。 通过库函数编程控制检测系统的工作流程, 可以通过按键输入控制输出显示不同的测量数据以及设置甲醛和 PM2.5 浓度报警值, 当气体浓度值超过报警值时, 通过蜂鸣器鸣叫的方式提醒用户室内有害气体超标, 并在液晶屏上实时显示。利用 GSM 通信模块可以通过短信的形式将用户室内的空气质量情况发到移动终端, 实现了仪器的智能化。 STM32 最小系统通过蓝牙模块与上位机通信, 上位机可以实时显示测量数据。

4 硬件设计方案

学长设计的系统所研究的室内空气质量检测仪, 系统硬件由 STM32 单片机、 电源、 温湿度传感器、 甲醛检测模块、 PM2. 5 检测模块、 按键输入模块、 液晶显示模块、 报警电路、 GSM短信接收模块和蓝牙模块等部分组成。 该系统采用 USB 供电, 通过 USB 数据线与电脑USB 接口相连, 输出+5V 的直流电压, 输出电压在 500mA 左右, 在目前普遍采用 USB接口的情况, 这样也提高了电源的来源以及可靠性, 用户也可以通过手机充电器、 充电宝对测试仪供电, 给用户使用该测试仪带来了很大的便利 模块化的设计可以使系统设计更加简单, 系统功耗降低, 操作变得简单, 可以进行现场检测, 通过液晶显示屏用户直观地读出测量数据。 空气质量检测仪检的整体硬件设计结构如下图所示。

在这里插入图片描述

4.1 stm32 主控

STM32C8T6(最小核心板),当然,用其他型号的32,如STM32ZET6也是可以的。

在这里插入图片描述

4.2 温度采集模块

系统采用的传感器为含有已校准数字信号输出的温湿度复合传感器 DHT11。该传感器是一款已经得到业界广泛认可的安全性高、 稳定性较强、 性价比高的集中数字化传感器的温湿度。

传感器可以传输信号高达 20 米以上, 可以满足大部分场合的测量要求;具有四针的单排引脚封装系统, 这样外部连接就变得简单; 同时因其超小的体积和极低的功耗在暖通空调、 汽车、 气象站、 除湿器等各行业广泛应用。

在这里插入图片描述

4.3 甲醛浓度检测模块

综合考虑甲醛检测精度和测量成本后, 系统选用 ZE08-CH 2 O 电化学甲醛传感器检测室内空气中的甲醛浓度含量, 因该模块精度高、 功耗低,体积小的特点, 成为了制作空气质量检测仪、 空气净化器的优良器件。 相比其他甲醛

传感器主要有以下特点 :

  • (1) 分辨率和灵敏度高;
  • (2) 功耗低、 使用寿命长;
  • (2) UART、 模拟电压信号、 PWM 等多方式输出;
  • (3) 具有优秀的抗干扰性、 稳定性高;
  • (4) 内置温度补偿单元, 线性输出。

在这里插入图片描述

4.4 PM2. 5 浓度检测模块

系统选用夏普公司授权弘成基科技有限公司生产的型号为 YW-51GJ 的 PM2.5 气敏粉尘传感器, 该产品主要针对香烟颗粒、 细微颗粒等特别细微的颗粒等检测对象, 通过脉冲高度来判断细颗粒物浓度, 常用于空气净化器、 车载空气净化系统以及民用空气质量检测系统中, 符合本系统的设计要求。

YW-51GJ PM2.5 粉尘传感器实物图如图所示。

在这里插入图片描述

可以看到该 PM2.5 传感器中间有一个圆孔, 其内部安装红外线发光二极管和光电晶体管, 当微小颗粒物进入传感器的圆孔内时, 根据微小颗粒物对光的散射原理, 当细颗粒物(PM2.5) 经过传感器内部的检测孔时会对光线形成散射作用。 部分光线通过光轴,经过透镜后聚焦到感光元件上, 光信号经过感光元件转换为电信号输出。

4.5 液晶显示模块设计

在这里插入图片描述

4.6 GSM 模块

经综合考虑学长选用了 SIM800C GSM 无线通信模块, 这是 SIMCOM 公司生产的基于 MT6261 芯片平台的新一代 GSM 模块, 性价比高, 支持蓝牙功能, 支持 51、MSP430、STM32 等主流单片机开发 。

在该模块中, 通信接口为 TTL 电平串口, 引出四根排针与控制器通信。 在 IPX 天线接口连接通信天线, Micro-SIM 卡卡座可以插入移动、 联通和物联网卡, 插入 SIM 卡即可发信息给用户移动终端, SIM 卡插进去听到“咔哒” 响声表明插卡正确, SIM800C GSM 模块实物图如图所示。
在这里插入图片描述

4.7 蓝牙模块

为了能够实现测试仪与上位机的通信, 本文采用的是 HC-05 嵌入式蓝牙串口通信模块[65] , 分为两个小模块 a、 b, 如下图所示依次左右排列, 模块 a 与通过 USB 接口与电脑相连, 模块 b 通过四根排母线与测试仪对应的排针相连接。

在这里插入图片描述

为了实现主控制器 STM32 与蓝牙模块的连接, 通过导线连接对应接口, 蓝牙接口电路原理图如图所示。

在这里插入图片描述

5 软件部分设计

系统主要实现温湿度、 甲醛浓度以及 PM2.5 浓度的检测与显示功能, 通过传感器不断采集数据, 同时对这些数据进行处理、 修正和补偿, 将数据转换为温湿度以及气体浓度进行显示, 让用户可以知道室内的空气质量。 与硬件系统一样分模块设计, 软件设计分成人机交互模块和数据处理模块两部分

在这里插入图片描述

5.1 初始化

系统上电后, 先对系统的各模块传感器、 12864LCD 显示屏、 按键等外设进行初始化, 设置标志位和定时器。 之后读取温湿度、 甲醛浓度、 PM2.5 浓度对应的模拟电流电压等电信号参数值, 将数据进行 AD 转换, 对数字量处理后转换为对应的温湿度、 甲醛浓度、 PM2.5 浓度显示出来。 软件设计的主程序流程图下图所示。

在这里插入图片描述

5.2 温湿度检测程序设计

DHT11 温湿度传感器硬件设计可以知道温湿度检测电路连接十分简单,DHT11 通过引脚 2(DATA) 给 STM32 发送单总线数字信号。 在电路开始工作的时候,为了跳过不稳定状态, DHT11 需要用一秒的时间不发任何指令。
在这里插入图片描述

5.3 甲醛浓度检测程序设计

由甲醛硬件模块设计可以知道本系统采用 ZE08-CH2O 电化学甲醛传感器采集甲醛浓度信号, 利用系统 STM32 单片机自带的 2 个 AD 转换器对甲醛浓度进行采集, 这些AD 转换器具有 10 个通道, 可以单独使用, 也可以实现复用。

在这里插入图片描述

5.4 PM2. 5 浓度检测程序设计

章 PM2.5 硬件设计可以知道系统选用了 YM-51 PM2.5 传感器, 通过该模块可以对室内 PM2.5 浓度测量, 传感器通过 RXD 向 STM32 发送数据, 接线图如下所示

在这里插入图片描述

在这里插入图片描述

PM2.5 浓度检测软件程序流程与甲醛浓度检测流程类似, 首先关闭中断, 再对与PM2.5 检测模块相关的时钟、 AD 转换器进行初始化设置, 确定 AD 转换器的工作模式、触发方式等。 之后打开中断在中断服务程序中实现数据采集, 1 秒内采集 10 次, 对这些数据采用限幅平均滤波法进行软件滤波, 去除因电路干扰产生干扰有效数据的毛刺。

5.5 短信发送程序设计

章 GSM 模块硬件设计可知本文采用 SIM800C GSM 模块实现短信收发的功能。 手机短消息主要采用 PDU(协议数据单元) 模式和 TEXT 模式, PDU 模式同时支持中文和英文短信, 而 TEXT 模式只支持英文短信

在这里插入图片描述

效果展示

设计好各个模块原理图, 然后制作出 PCB 板, 通过导线将各个模块与系统主板连接, 室内空气质量检测检测仪实物图如下所示。
在这里插入图片描述

室内空气质量实时显示
在这里插入图片描述

短信收发测试
在这里插入图片描述

6 项目源码

6.1 ADC部分

 #include "adc.h"
 #include "delay.h"

//初始化ADC
//这里我们仅以规则通道为例
//我们默认将开启通道0~3																	   
void  Adc_Init(void)
{ 	
	ADC_InitTypeDef ADC_InitStructure; 
	GPIO_InitTypeDef GPIO_InitStructure;

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA |RCC_APB2Periph_ADC1	, ENABLE );	  //使能ADC1通道时钟
 

	RCC_ADCCLKConfig(RCC_PCLK2_Div6);   //设置ADC分频因子6 72M/6=12,ADC最大时间不能超过14M

	//PA1 作为模拟通道输入引脚                         
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;		//模拟输入引脚
	GPIO_Init(GPIOA, &GPIO_InitStructure);	

	ADC_DeInit(ADC1);  //复位ADC1,将外设 ADC1 的全部寄存器重设为缺省值

	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;	//ADC工作模式:ADC1和ADC2工作在独立模式
	ADC_InitStructure.ADC_ScanConvMode = DISABLE;	//模数转换工作在单通道模式
	ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;	//模数转换工作在单次转换模式
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;	//转换由软件而不是外部触发启动
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;	//ADC数据右对齐
	ADC_InitStructure.ADC_NbrOfChannel = 1;	//顺序进行规则转换的ADC通道的数目
	ADC_Init(ADC1, &ADC_InitStructure);	//根据ADC_InitStruct中指定的参数初始化外设ADCx的寄存器   

  
	ADC_Cmd(ADC1, ENABLE);	//使能指定的ADC1
	
	ADC_ResetCalibration(ADC1);	//使能复位校准  
	 
	while(ADC_GetResetCalibrationStatus(ADC1));	//等待复位校准结束
	
	ADC_StartCalibration(ADC1);	 //开启AD校准
 
	while(ADC_GetCalibrationStatus(ADC1));	 //等待校准结束
 
//	ADC_SoftwareStartConvCmd(ADC1, ENABLE);		//使能指定的ADC1的软件转换启动功能

}				  
//获得ADC值
//ch:通道值 0~3
u16 Get_Adc(u8 ch)   
{
  	//设置指定ADC的规则组通道,一个序列,采样时间
	ADC_RegularChannelConfig(ADC1, ch, 1, ADC_SampleTime_239Cycles5 );	//ADC1,ADC通道,采样时间为239.5周期	  			    
  
	ADC_SoftwareStartConvCmd(ADC1, ENABLE);		//使能指定的ADC1的软件转换启动功能	
	 
	while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC ));//等待转换结束

	return ADC_GetConversionValue(ADC1);	//返回最近一次ADC1规则组的转换结果
}

u16 Get_Adc_Average(u8 ch,u8 times)
{
	u32 temp_val=0;
	u8 t;
	for(t=0;t<times;t++)
	{
		temp_val+=Get_Adc(ch);
		delay_ms(5);
	}
	return temp_val/times;
} 	 

6.2 DS18B20

#include "ds18b20.h"
#include "delay.h"	

//复位DS18B20
void DS18B20_Rst(void)	   
{                 
	DS18B20_IO_OUT(); //SET PA0 OUTPUT
    DS18B20_DQ_OUT=0; //拉低DQ
    delay_us(750);    //拉低750us
    DS18B20_DQ_OUT=1; //DQ=1 
	delay_us(15);     //15US
}
//等待DS18B20的回应
//返回1:未检测到DS18B20的存在
//返回0:存在
u8 DS18B20_Check(void) 	   
{   
	u8 retry=0;
	DS18B20_IO_IN();//SET PA0 INPUT	 
    while (DS18B20_DQ_IN&&retry<200)
	{
		retry++;
		delay_us(1);
	};	 
	if(retry>=200)return 1;
	else retry=0;
    while (!DS18B20_DQ_IN&&retry<240)
	{
		retry++;
		delay_us(1);
	};
	if(retry>=240)return 1;	    
	return 0;
}
//从DS18B20读取一个位
//返回值:1/0
u8 DS18B20_Read_Bit(void) 			 // read one bit
{
    u8 data;
	DS18B20_IO_OUT();//SET PA0 OUTPUT
    DS18B20_DQ_OUT=0; 
	delay_us(2);
    DS18B20_DQ_OUT=1; 
	DS18B20_IO_IN();//SET PA0 INPUT
	delay_us(12);
	if(DS18B20_DQ_IN)data=1;
    else data=0;	 
    delay_us(50);           
    return data;
}
//从DS18B20读取一个字节
//返回值:读到的数据
u8 DS18B20_Read_Byte(void)    // read one byte
{        
    u8 i,j,dat;
    dat=0;
	for (i=1;i<=8;i++) 
	{
        j=DS18B20_Read_Bit();
        dat=(j<<7)|(dat>>1);
    }						    
    return dat;
}
//写一个字节到DS18B20
//dat:要写入的字节
void DS18B20_Write_Byte(u8 dat)     
 {             
    u8 j;
    u8 testb;
	DS18B20_IO_OUT();//SET PA0 OUTPUT;
    for (j=1;j<=8;j++) 
	{
        testb=dat&0x01;
        dat=dat>>1;
        if (testb) 
        {
            DS18B20_DQ_OUT=0;// Write 1
            delay_us(2);                            
            DS18B20_DQ_OUT=1;
            delay_us(60);             
        }
        else 
        {
            DS18B20_DQ_OUT=0;// Write 0
            delay_us(60);             
            DS18B20_DQ_OUT=1;
            delay_us(2);                          
        }
    }
}
//开始温度转换
void DS18B20_Start(void)// ds1820 start convert
{   						               
    DS18B20_Rst();	   
	DS18B20_Check();	 
    DS18B20_Write_Byte(0xcc);// skip rom
    DS18B20_Write_Byte(0x44);// convert
} 
//初始化DS18B20的IO口 DQ 同时检测DS的存在
//返回1:不存在
//返回0:存在    	 
u8 DS18B20_Init(void)
{
 	GPIO_InitTypeDef  GPIO_InitStructure;
 	
 	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	 //使能PORTA口时钟 
	
 	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;				//PORTA0 推挽输出
 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 		  
 	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
 	GPIO_Init(GPIOA, &GPIO_InitStructure);

 	GPIO_SetBits(GPIOA,GPIO_Pin_0);    //输出1

	DS18B20_Rst();

	return DS18B20_Check();
}  
//从ds18b20得到温度值
//精度:0.1C
//返回值:温度值 (-550~1250) 
short DS18B20_Get_Temp(void)
{
    u8 temp;
    u8 TL,TH;
	short tem;
    DS18B20_Start ();                    // ds1820 start convert
    DS18B20_Rst();
    DS18B20_Check();	 
    DS18B20_Write_Byte(0xcc);// skip rom
    DS18B20_Write_Byte(0xbe);// convert	    
    TL=DS18B20_Read_Byte(); // LSB   
    TH=DS18B20_Read_Byte(); // MSB  
	    	  
    if(TH>7)
    {
        TH=~TH;
        TL=~TL; 
        temp=0;//温度为负  
    }else temp=1;//温度为正	  	  
    tem=TH; //获得高八位
    tem<<=8;    
    tem+=TL;//获得底八位
    tem=(float)tem*0.625;//转换     
	if(temp)return tem; //返回温度值
	else return -tem;    
} 

6.3 RTC部分

#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "rtc.h" 		    
//Mini STM32开发板
//RTC实时时钟 驱动代码			 
//正点原子@ALIENTEK
//2010/6/6
	   
_calendar_obj calendar;//时钟结构体 
 
static void RTC_NVIC_Config(void)
{	
    NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn;		//RTC全局中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;	//先占优先级1位,从优先级3位
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;	//先占优先级0位,从优先级4位
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;		//使能该通道中断
	NVIC_Init(&NVIC_InitStructure);		//根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
}

//实时时钟配置
//初始化RTC时钟,同时检测时钟是否工作正常
//BKP->DR1用于保存是否第一次配置的设置
//返回0:正常
//其他:错误代码

u8 RTC_Init(void)
{
	//检查是不是第一次配置时钟
	u8 temp=0;
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);	//使能PWR和BKP外设时钟   
	PWR_BackupAccessCmd(ENABLE);	//使能后备寄存器访问  
	if (BKP_ReadBackupRegister(BKP_DR1) != 0x5050)		//从指定的后备寄存器中读出数据:读出了与写入的指定数据不相乎
		{	 			

		BKP_DeInit();	//复位备份区域 	
		RCC_LSEConfig(RCC_LSE_ON);	//设置外部低速晶振(LSE),使用外设低速晶振
		while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET)	//检查指定的RCC标志位设置与否,等待低速晶振就绪
			{
			temp++;
			delay_ms(10);
			}
		if(temp>=250)return 1;//初始化时钟失败,晶振有问题	    
		RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);		//设置RTC时钟(RTCCLK),选择LSE作为RTC时钟    
		RCC_RTCCLKCmd(ENABLE);	//使能RTC时钟  
		RTC_WaitForLastTask();	//等待最近一次对RTC寄存器的写操作完成
		RTC_WaitForSynchro();		//等待RTC寄存器同步  
		RTC_ITConfig(RTC_IT_SEC, ENABLE);		//使能RTC秒中断
		RTC_WaitForLastTask();	//等待最近一次对RTC寄存器的写操作完成
		RTC_EnterConfigMode();/// 允许配置	
		RTC_SetPrescaler(32767); //设置RTC预分频的值
		RTC_WaitForLastTask();	//等待最近一次对RTC寄存器的写操作完成
		RTC_Set(2020,5,5,12,59,00);  //设置时间	
		RTC_ExitConfigMode(); //退出配置模式  
		BKP_WriteBackupRegister(BKP_DR1, 0X5050);	//向指定的后备寄存器中写入用户程序数据
		}
	else//系统继续计时
		{

		RTC_WaitForSynchro();	//等待最近一次对RTC寄存器的写操作完成
		RTC_ITConfig(RTC_IT_SEC, ENABLE);	//使能RTC秒中断
		RTC_WaitForLastTask();	//等待最近一次对RTC寄存器的写操作完成
		}
	RTC_NVIC_Config();//RCT中断分组设置		    				     
	RTC_Get();//更新时间	
	return 0; //ok

}		 				    
//RTC时钟中断
//每秒触发一次  
//extern u16 tcnt; 
void RTC_IRQHandler(void)
{		 
	if (RTC_GetITStatus(RTC_IT_SEC) != RESET)//秒钟中断
	{							
		RTC_Get();//更新时间   
 	}
	if(RTC_GetITStatus(RTC_IT_ALR)!= RESET)//闹钟中断
	{
		RTC_ClearITPendingBit(RTC_IT_ALR);		//清闹钟中断	  	   
  	} 				  								 
	RTC_ClearITPendingBit(RTC_IT_SEC|RTC_IT_OW);		//清闹钟中断
	RTC_WaitForLastTask();	  	    						 	   	 
}
//判断是否是闰年函数
//月份   1  2  3  4  5  6  7  8  9  10 11 12
//闰年   31 29 31 30 31 30 31 31 30 31 30 31
//非闰年 31 28 31 30 31 30 31 31 30 31 30 31
//输入:年份
//输出:该年份是不是闰年.1,是.0,不是
u8 Is_Leap_Year(u16 year)
{			  
	if(year%4==0) //必须能被4整除
	{ 
		if(year%100==0) 
		{ 
			if(year%400==0)return 1;//如果以00结尾,还要能被400整除 	   
			else return 0;   
		}else return 1;   
	}else return 0;	
}	 			   
//设置时钟
//把输入的时钟转换为秒钟
//以1970年1月1日为基准
//1970~2099年为合法年份
//返回值:0,成功;其他:错误代码.
//月份数据表											 
u8 const table_week[12]={0,3,3,6,1,4,6,2,5,0,3,5}; //月修正数据表	  
//平年的月份日期表
const u8 mon_table[12]={31,28,31,30,31,30,31,31,30,31,30,31};
u8 RTC_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec)
{
	u16 t;
	u32 seccount=0;
	if(syear<1970||syear>2099)return 1;	   
	for(t=1970;t<syear;t++)	//把所有年份的秒钟相加
	{
		if(Is_Leap_Year(t))seccount+=31622400;//闰年的秒钟数
		else seccount+=31536000;			  //平年的秒钟数
	}
	smon-=1;
	for(t=0;t<smon;t++)	   //把前面月份的秒钟数相加
	{
		seccount+=(u32)mon_table[t]*86400;//月份秒钟数相加
		if(Is_Leap_Year(syear)&&t==1)seccount+=86400;//闰年2月份增加一天的秒钟数	   
	}
	seccount+=(u32)(sday-1)*86400;//把前面日期的秒钟数相加 
	seccount+=(u32)hour*3600;//小时秒钟数
    seccount+=(u32)min*60;	 //分钟秒钟数
	seccount+=sec;//最后的秒钟加上去

	RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);	//使能PWR和BKP外设时钟  
	PWR_BackupAccessCmd(ENABLE);	//使能RTC和后备寄存器访问 
	RTC_SetCounter(seccount);	//设置RTC计数器的值

	RTC_WaitForLastTask();	//等待最近一次对RTC寄存器的写操作完成  	
	RTC_Get();
	return 0;	    
}
//得到当前的时间
//返回值:0,成功;其他:错误代码.
u8 RTC_Get(void)
{
	static u16 daycnt=0;
	u32 timecount=0; 
	u32 temp=0;
	u16 temp1=0;	  
    timecount=RTC_GetCounter();	 
 	temp=timecount/86400;   //得到天数(秒钟数对应的)
	if(daycnt!=temp)//超过一天了
	{	  
		daycnt=temp;
		temp1=1970;	//从1970年开始
		while(temp>=365)
		{				 
			if(Is_Leap_Year(temp1))//是闰年
			{
				if(temp>=366)temp-=366;//闰年的秒钟数
				else {temp1++;break;}  
			}
			else temp-=365;	  //平年 
			temp1++;  
		}   
		calendar.w_year=temp1;//得到年份
		temp1=0;
		while(temp>=28)//超过了一个月
		{
			if(Is_Leap_Year(calendar.w_year)&&temp1==1)//当年是不是闰年/2月份
			{
				if(temp>=29)temp-=29;//闰年的秒钟数
				else break; 
			}
			else 
			{
				if(temp>=mon_table[temp1])temp-=mon_table[temp1];//平年
				else break;
			}
			temp1++;  
		}
		calendar.w_month=temp1+1;	//得到月份
		calendar.w_date=temp+1;  	//得到日期 
	}
	temp=timecount%86400;     		//得到秒钟数   	   
	calendar.hour=temp/3600;     	//小时
	calendar.min=(temp%3600)/60; 	//分钟	
	calendar.sec=(temp%3600)%60; 	//秒钟
	calendar.week=RTC_Get_Week(calendar.w_year,calendar.w_month,calendar.w_date);//获取星期   
	return 0;
}	 
//获得现在是星期几
//功能描述:输入公历日期得到星期(只允许1901-2099年)
//输入参数:公历年月日 
//返回值:星期号																						 
u8 RTC_Get_Week(u16 year,u8 month,u8 day)
{	
	u16 temp2;
	u8 yearH,yearL;
	
	yearH=year/100;	yearL=year%100; 
	// 如果为21世纪,年份数加100  
	if (yearH>19)yearL+=100;
	// 所过闰年数只算1900年之后的  
	temp2=yearL+yearL/4;
	temp2=temp2%7; 
	temp2=temp2+day+table_week[month-1];
	if (yearL%4==0&&month<3)temp2--;
	return(temp2%7);
}			  

6.4 main部分

#include "led.h"
#include "delay.h"
#include "sys.h"
#include "usart.h"
#include "lcd.h"
#include "adc.h"
#include "math.h"
#include "ds18b20.h"  
#include "rtc.h"

   	
 int main(void)
 { 
	u16 adcx;
	float temp,quality;
	short temperature; 
	u8 t;	
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);// 设置中断优先级分组2 
	 
	delay_init();	    	 //延时函数初始化	  
	uart_init(9600);	 	 //串口初始化为9600
	LED_Init();		  		 //初始化与LED连接的硬件接口
 	LCD_Init();
 	Adc_Init();		  		//ADC初始化	

	POINT_COLOR=BLACK; 
	LCD_DrawLine(0,80,240,80);
	LCD_DrawLine(120,80,120,240);
	LCD_DrawLine(0,240,240,240);
	LCD_DrawLine(0,280,240,280);
	LCD_DrawLine(120,120,120,240);
	LCD_DrawLine(120,160,240,160); 
	LCD_ShowString(40,20,230,16,16,"AIR MONITIRING SYSTEM");
	LCD_ShowString(76,40,230,16,16,"BASED ON RTOS");
	LCD_ShowString(10,100,230,16,16,"AIR QUALITY:");
	LCD_ShowString(130,90,230,16,16,"HARMFUL GAS");
	LCD_ShowString(125,110,230,16,16,"CONCENTRATION:");
	LCD_ShowString(150,130,230,16,16,"     PPM");  
	LCD_ShowString(130,170,230,16,16,"TEMPERATURE:");  
	LCD_ShowString(150,190,230,16,16,"   . C"); 	
	//LCD_ShowString(10,250,200,16,16,"2020-4-30 16:00");	
	LCD_ShowString(10,290,200,16,16,"STM32F103,MQ135,DS18B20");	
	
	while(DS18B20_Init())	//DS18B20初始化	
	{
		LCD_ShowString(150,210,200,16,16,"DS18B20 Error");
		delay_ms(200);
		LCD_Fill(60,130,239,130+16,WHITE);
 		delay_ms(200);
	}
		
	while(RTC_Init())		//RTC初始化	,一定要初始化成功
	{ 
		LCD_ShowString(10,250,200,16,16,"RTC ERROR!   ");	
		delay_ms(800);
		LCD_ShowString(10,250,200,16,16,"RTC Trying...");	
	}		    						
	//显示时间				 
	LCD_ShowString(10,250,200,16,16,"    -  -  ");	   
	LCD_ShowString(100,250,200,16,16,"  :  :  ");	 	
	//显示提示信息
	//LCD_ShowString(60,130,200,16,16,"ADC_CH1_VAL:");	      
	//LCD_ShowString(60,150,200,16,16,"ADC_CH1_VOL:0.000V");	
    //LCD_ShowString(60,170,200,16,16,"KQ_ZHILIANG:    ppm");	
   	 
	while(1)
	{
		
		adcx=Get_Adc_Average(ADC_Channel_1,10);
		//LCD_ShowxNum(156,130,adcx,4,16,0);//显示ADC的值
		temp=(float)adcx*(3.3/4096);
		quality=pow((11.5428 * 35.904 * temp )/(25.5 - 5.1 * temp),1.0/0.6549);
		adcx=temp;
		//LCD_ShowxNum(156,150,adcx,1,16,0);//显示电压值
		temp-=adcx;
		//temp*=1000;
		//LCD_ShowxNum(172,150,temp,3,16,0X80);
		LCD_ShowxNum(150,130,quality,4,16,0);//显示转化后的PPM值
		//根据有害气体浓度判断空气质量
		if(0<= quality && quality <=75)
			LCD_ShowString(20,150,200,16,24,"NORMAL");	
		else if(quality<=150)
			LCD_ShowString(20,150,200,16,24,"MILD  ");
		else if(quality<=500)
			LCD_ShowString(20,150,200,16,24,"MIDDLE");
		else 
			LCD_ShowString(20,150,200,16,24,"SEVERE");
	
		
		temperature=DS18B20_Get_Temp();	
		if(temperature<0)
		{
			LCD_ShowChar(60+40,150,'-',16,0);			//显示负号
			temperature=-temperature;					//转为正数
		}else LCD_ShowChar(60+40,150,' ',16,0);			//去掉负号
		LCD_ShowNum(150+8,190,temperature/10,2,16);	//显示正数部分	    
   		LCD_ShowNum(150+32,190,temperature%10,1,16);	//显示小数部分
		
		//LCD_ShowString(170,190,290,16,16,"26");//显示温度
		
		if(quality>=150) //当到达中度污染时红灯闪烁报警
		{
			LED1=1;
			LED0=!LED0;
			POINT_COLOR=RED;//设置字体为红色 
		}
		else
		{
			LED0=1;
			LED1=!LED1;  //正常时绿灯闪烁
			POINT_COLOR=BLUE;//设置字体为蓝色 
		}
		
		//显示时间
		if(t!=calendar.sec)
		{
			POINT_COLOR=BLACK;//设置字体为黑色
			t=calendar.sec;
			LCD_ShowNum(10,250,calendar.w_year,4,16);									  
			LCD_ShowNum(50,250,calendar.w_month,2,16);									  
			LCD_ShowNum(74,250,calendar.w_date,2,16);	 
			
			LCD_ShowNum(100,250,calendar.hour,2,16);									  
			LCD_ShowNum(124,250,calendar.min,2,16);									  
			LCD_ShowNum(148,250,calendar.sec,2,16);
			switch(calendar.week)
			{
				case 0:
					LCD_ShowString(170,250,200,16,16,"Sunday");
					break;
				case 1:
					LCD_ShowString(170,250,200,16,16,"Monday");
					break;
				case 2:
					LCD_ShowString(170,250,200,16,16,"Tuesday");
					break;
				case 3:
					LCD_ShowString(170,250,200,16,16,"Wednesday");
					break;
				case 4:
					LCD_ShowString(170,250,200,16,16,"Thursday");
					break;
				case 5:
					LCD_ShowString(170,250,200,16,16,"Friday");
					break;
				case 6:
					LCD_ShowString(60,148,200,16,16,"Saturday ");
					break;  
			}
		}
		delay_ms(200);	
	}											    
}	


7 最后

技术解答、毕设帮助、开题指导
print("Q 746876041") 

请添加图片描述

版权声明:本文为CSDN博主「DC-STDIO」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/huawei123444/article/details/121561361


1 简介

Hi,大家好,这里是丹成学长,今天向大家介绍一个 单片机项目

基于STM32的空气质量检测仪

大家可用于 课程设计 或 毕业设计

技术解答、毕设帮助、开题指导
print("Q 746876041") 

2 系统设计概述

如今人们大约 80%的时间是在室内度过的, 室内空气质量与我们每个人的工作和生活都息息相关, 因此对生活环境的空气质量提出了更高的要求。 针对雾霾、 室内装修等污染问题, 人们还没有有效的办法控制空气中的有害物质, 这一问题也成为人们的健康隐患, 因此对室内的空气质量进行检测至关重要。

当室内有害气体的浓度超标时, 会对人们的身心健康带来不可忽视的影响。 为了让人们及时准确地获得室内有害气体甲醛、 PM2.5 的浓度, 采取有效措施改善生活环境的空气质量, 提高工作效率。 根据我国室内空气监管标准 GB50325-2010, 学长设计了一款基于STM32F103C8T6 单片机的便携式、 低功耗室内空气质量检测仪。 该检测仪可以实时测量室内的温湿度、 甲醛浓度、 PM2.5 浓度, 并在液晶显示屏 LCD12864 上实时显示出来, 当甲醛浓度或 PM2.5 浓度超过报警值时, 测试仪通过亮红灯和蜂鸣器鸣叫的方式提醒用户室内有害气体超标。

用户可以根据需要通过 GSM 短信方式将室内空气质量情况发送到移动终端, 最后系统在 Visual Studio 2012 平台运用 C#语言开发上位机, 可以实时显示空气质量指标。

在这里插入图片描述

3 系统总体方案

学长设计为了能够实时检测室内的空气质量情况, 提高系统的稳定性和自动化程度, 将系统分为硬件设计和软件设计。 本文研制的室内 空气质 量检测仪是基于 32 位单片机STM32C8T6 进行设计的, 将整个系统模块化, 主要包括 STM32 最小系统、 温湿度检测模块、 甲醛气体检测模块、 PM2.5 检测模块、 按键、 液晶显示器、 蜂鸣器、 GSM 模块、蓝牙模块、 移动终端和上位机以及电源管理电路。 其系统结构框图如图所示。

在这里插入图片描述

在整个室内空气质量检测系统中, STM32 芯片是整个系统的主控制器, 整个检测系统都是在它的控制下完成的, 并且 STM32 为测试仪提供外部接口, 如液晶显示屏、
按键、 数据采集电路。 首先通过温湿度、 甲醛浓度以及 PM2.5 等传感器进行温湿度、 甲醛气体及 PM2.5 浓度信号采集, 再将采集到的信号送至中央处理单元 STM32 单片机进行信号转换、 放大和处理。 通过库函数编程控制检测系统的工作流程, 可以通过按键输入控制输出显示不同的测量数据以及设置甲醛和 PM2.5 浓度报警值, 当气体浓度值超过报警值时, 通过蜂鸣器鸣叫的方式提醒用户室内有害气体超标, 并在液晶屏上实时显示。利用 GSM 通信模块可以通过短信的形式将用户室内的空气质量情况发到移动终端, 实现了仪器的智能化。 STM32 最小系统通过蓝牙模块与上位机通信, 上位机可以实时显示测量数据。

4 硬件设计方案

学长设计的系统所研究的室内空气质量检测仪, 系统硬件由 STM32 单片机、 电源、 温湿度传感器、 甲醛检测模块、 PM2. 5 检测模块、 按键输入模块、 液晶显示模块、 报警电路、 GSM短信接收模块和蓝牙模块等部分组成。 该系统采用 USB 供电, 通过 USB 数据线与电脑USB 接口相连, 输出+5V 的直流电压, 输出电压在 500mA 左右, 在目前普遍采用 USB接口的情况, 这样也提高了电源的来源以及可靠性, 用户也可以通过手机充电器、 充电宝对测试仪供电, 给用户使用该测试仪带来了很大的便利 模块化的设计可以使系统设计更加简单, 系统功耗降低, 操作变得简单, 可以进行现场检测, 通过液晶显示屏用户直观地读出测量数据。 空气质量检测仪检的整体硬件设计结构如下图所示。

在这里插入图片描述

4.1 stm32 主控

STM32C8T6(最小核心板),当然,用其他型号的32,如STM32ZET6也是可以的。

在这里插入图片描述

4.2 温度采集模块

系统采用的传感器为含有已校准数字信号输出的温湿度复合传感器 DHT11。该传感器是一款已经得到业界广泛认可的安全性高、 稳定性较强、 性价比高的集中数字化传感器的温湿度。

传感器可以传输信号高达 20 米以上, 可以满足大部分场合的测量要求;具有四针的单排引脚封装系统, 这样外部连接就变得简单; 同时因其超小的体积和极低的功耗在暖通空调、 汽车、 气象站、 除湿器等各行业广泛应用。

在这里插入图片描述

4.3 甲醛浓度检测模块

综合考虑甲醛检测精度和测量成本后, 系统选用 ZE08-CH 2 O 电化学甲醛传感器检测室内空气中的甲醛浓度含量, 因该模块精度高、 功耗低,体积小的特点, 成为了制作空气质量检测仪、 空气净化器的优良器件。 相比其他甲醛

传感器主要有以下特点 :

  • (1) 分辨率和灵敏度高;
  • (2) 功耗低、 使用寿命长;
  • (2) UART、 模拟电压信号、 PWM 等多方式输出;
  • (3) 具有优秀的抗干扰性、 稳定性高;
  • (4) 内置温度补偿单元, 线性输出。

在这里插入图片描述

4.4 PM2. 5 浓度检测模块

系统选用夏普公司授权弘成基科技有限公司生产的型号为 YW-51GJ 的 PM2.5 气敏粉尘传感器, 该产品主要针对香烟颗粒、 细微颗粒等特别细微的颗粒等检测对象, 通过脉冲高度来判断细颗粒物浓度, 常用于空气净化器、 车载空气净化系统以及民用空气质量检测系统中, 符合本系统的设计要求。

YW-51GJ PM2.5 粉尘传感器实物图如图所示。

在这里插入图片描述

可以看到该 PM2.5 传感器中间有一个圆孔, 其内部安装红外线发光二极管和光电晶体管, 当微小颗粒物进入传感器的圆孔内时, 根据微小颗粒物对光的散射原理, 当细颗粒物(PM2.5) 经过传感器内部的检测孔时会对光线形成散射作用。 部分光线通过光轴,经过透镜后聚焦到感光元件上, 光信号经过感光元件转换为电信号输出。

4.5 液晶显示模块设计

在这里插入图片描述

4.6 GSM 模块

经综合考虑学长选用了 SIM800C GSM 无线通信模块, 这是 SIMCOM 公司生产的基于 MT6261 芯片平台的新一代 GSM 模块, 性价比高, 支持蓝牙功能, 支持 51、MSP430、STM32 等主流单片机开发 。

在该模块中, 通信接口为 TTL 电平串口, 引出四根排针与控制器通信。 在 IPX 天线接口连接通信天线, Micro-SIM 卡卡座可以插入移动、 联通和物联网卡, 插入 SIM 卡即可发信息给用户移动终端, SIM 卡插进去听到“咔哒” 响声表明插卡正确, SIM800C GSM 模块实物图如图所示。
在这里插入图片描述

4.7 蓝牙模块

为了能够实现测试仪与上位机的通信, 本文采用的是 HC-05 嵌入式蓝牙串口通信模块[65] , 分为两个小模块 a、 b, 如下图所示依次左右排列, 模块 a 与通过 USB 接口与电脑相连, 模块 b 通过四根排母线与测试仪对应的排针相连接。

在这里插入图片描述

为了实现主控制器 STM32 与蓝牙模块的连接, 通过导线连接对应接口, 蓝牙接口电路原理图如图所示。

在这里插入图片描述

5 软件部分设计

系统主要实现温湿度、 甲醛浓度以及 PM2.5 浓度的检测与显示功能, 通过传感器不断采集数据, 同时对这些数据进行处理、 修正和补偿, 将数据转换为温湿度以及气体浓度进行显示, 让用户可以知道室内的空气质量。 与硬件系统一样分模块设计, 软件设计分成人机交互模块和数据处理模块两部分

在这里插入图片描述

5.1 初始化

系统上电后, 先对系统的各模块传感器、 12864LCD 显示屏、 按键等外设进行初始化, 设置标志位和定时器。 之后读取温湿度、 甲醛浓度、 PM2.5 浓度对应的模拟电流电压等电信号参数值, 将数据进行 AD 转换, 对数字量处理后转换为对应的温湿度、 甲醛浓度、 PM2.5 浓度显示出来。 软件设计的主程序流程图下图所示。

在这里插入图片描述

5.2 温湿度检测程序设计

DHT11 温湿度传感器硬件设计可以知道温湿度检测电路连接十分简单,DHT11 通过引脚 2(DATA) 给 STM32 发送单总线数字信号。 在电路开始工作的时候,为了跳过不稳定状态, DHT11 需要用一秒的时间不发任何指令。
在这里插入图片描述

5.3 甲醛浓度检测程序设计

由甲醛硬件模块设计可以知道本系统采用 ZE08-CH2O 电化学甲醛传感器采集甲醛浓度信号, 利用系统 STM32 单片机自带的 2 个 AD 转换器对甲醛浓度进行采集, 这些AD 转换器具有 10 个通道, 可以单独使用, 也可以实现复用。

在这里插入图片描述

5.4 PM2. 5 浓度检测程序设计

章 PM2.5 硬件设计可以知道系统选用了 YM-51 PM2.5 传感器, 通过该模块可以对室内 PM2.5 浓度测量, 传感器通过 RXD 向 STM32 发送数据, 接线图如下所示

在这里插入图片描述

在这里插入图片描述

PM2.5 浓度检测软件程序流程与甲醛浓度检测流程类似, 首先关闭中断, 再对与PM2.5 检测模块相关的时钟、 AD 转换器进行初始化设置, 确定 AD 转换器的工作模式、触发方式等。 之后打开中断在中断服务程序中实现数据采集, 1 秒内采集 10 次, 对这些数据采用限幅平均滤波法进行软件滤波, 去除因电路干扰产生干扰有效数据的毛刺。

5.5 短信发送程序设计

章 GSM 模块硬件设计可知本文采用 SIM800C GSM 模块实现短信收发的功能。 手机短消息主要采用 PDU(协议数据单元) 模式和 TEXT 模式, PDU 模式同时支持中文和英文短信, 而 TEXT 模式只支持英文短信

在这里插入图片描述

效果展示

设计好各个模块原理图, 然后制作出 PCB 板, 通过导线将各个模块与系统主板连接, 室内空气质量检测检测仪实物图如下所示。
在这里插入图片描述

室内空气质量实时显示
在这里插入图片描述

短信收发测试
在这里插入图片描述

6 项目源码

6.1 ADC部分

 #include "adc.h"
 #include "delay.h"

//初始化ADC
//这里我们仅以规则通道为例
//我们默认将开启通道0~3																	   
void  Adc_Init(void)
{ 	
	ADC_InitTypeDef ADC_InitStructure; 
	GPIO_InitTypeDef GPIO_InitStructure;

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA |RCC_APB2Periph_ADC1	, ENABLE );	  //使能ADC1通道时钟
 

	RCC_ADCCLKConfig(RCC_PCLK2_Div6);   //设置ADC分频因子6 72M/6=12,ADC最大时间不能超过14M

	//PA1 作为模拟通道输入引脚                         
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;		//模拟输入引脚
	GPIO_Init(GPIOA, &GPIO_InitStructure);	

	ADC_DeInit(ADC1);  //复位ADC1,将外设 ADC1 的全部寄存器重设为缺省值

	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;	//ADC工作模式:ADC1和ADC2工作在独立模式
	ADC_InitStructure.ADC_ScanConvMode = DISABLE;	//模数转换工作在单通道模式
	ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;	//模数转换工作在单次转换模式
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;	//转换由软件而不是外部触发启动
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;	//ADC数据右对齐
	ADC_InitStructure.ADC_NbrOfChannel = 1;	//顺序进行规则转换的ADC通道的数目
	ADC_Init(ADC1, &ADC_InitStructure);	//根据ADC_InitStruct中指定的参数初始化外设ADCx的寄存器   

  
	ADC_Cmd(ADC1, ENABLE);	//使能指定的ADC1
	
	ADC_ResetCalibration(ADC1);	//使能复位校准  
	 
	while(ADC_GetResetCalibrationStatus(ADC1));	//等待复位校准结束
	
	ADC_StartCalibration(ADC1);	 //开启AD校准
 
	while(ADC_GetCalibrationStatus(ADC1));	 //等待校准结束
 
//	ADC_SoftwareStartConvCmd(ADC1, ENABLE);		//使能指定的ADC1的软件转换启动功能

}				  
//获得ADC值
//ch:通道值 0~3
u16 Get_Adc(u8 ch)   
{
  	//设置指定ADC的规则组通道,一个序列,采样时间
	ADC_RegularChannelConfig(ADC1, ch, 1, ADC_SampleTime_239Cycles5 );	//ADC1,ADC通道,采样时间为239.5周期	  			    
  
	ADC_SoftwareStartConvCmd(ADC1, ENABLE);		//使能指定的ADC1的软件转换启动功能	
	 
	while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC ));//等待转换结束

	return ADC_GetConversionValue(ADC1);	//返回最近一次ADC1规则组的转换结果
}

u16 Get_Adc_Average(u8 ch,u8 times)
{
	u32 temp_val=0;
	u8 t;
	for(t=0;t<times;t++)
	{
		temp_val+=Get_Adc(ch);
		delay_ms(5);
	}
	return temp_val/times;
} 	 

6.2 DS18B20

#include "ds18b20.h"
#include "delay.h"	

//复位DS18B20
void DS18B20_Rst(void)	   
{                 
	DS18B20_IO_OUT(); //SET PA0 OUTPUT
    DS18B20_DQ_OUT=0; //拉低DQ
    delay_us(750);    //拉低750us
    DS18B20_DQ_OUT=1; //DQ=1 
	delay_us(15);     //15US
}
//等待DS18B20的回应
//返回1:未检测到DS18B20的存在
//返回0:存在
u8 DS18B20_Check(void) 	   
{   
	u8 retry=0;
	DS18B20_IO_IN();//SET PA0 INPUT	 
    while (DS18B20_DQ_IN&&retry<200)
	{
		retry++;
		delay_us(1);
	};	 
	if(retry>=200)return 1;
	else retry=0;
    while (!DS18B20_DQ_IN&&retry<240)
	{
		retry++;
		delay_us(1);
	};
	if(retry>=240)return 1;	    
	return 0;
}
//从DS18B20读取一个位
//返回值:1/0
u8 DS18B20_Read_Bit(void) 			 // read one bit
{
    u8 data;
	DS18B20_IO_OUT();//SET PA0 OUTPUT
    DS18B20_DQ_OUT=0; 
	delay_us(2);
    DS18B20_DQ_OUT=1; 
	DS18B20_IO_IN();//SET PA0 INPUT
	delay_us(12);
	if(DS18B20_DQ_IN)data=1;
    else data=0;	 
    delay_us(50);           
    return data;
}
//从DS18B20读取一个字节
//返回值:读到的数据
u8 DS18B20_Read_Byte(void)    // read one byte
{        
    u8 i,j,dat;
    dat=0;
	for (i=1;i<=8;i++) 
	{
        j=DS18B20_Read_Bit();
        dat=(j<<7)|(dat>>1);
    }						    
    return dat;
}
//写一个字节到DS18B20
//dat:要写入的字节
void DS18B20_Write_Byte(u8 dat)     
 {             
    u8 j;
    u8 testb;
	DS18B20_IO_OUT();//SET PA0 OUTPUT;
    for (j=1;j<=8;j++) 
	{
        testb=dat&0x01;
        dat=dat>>1;
        if (testb) 
        {
            DS18B20_DQ_OUT=0;// Write 1
            delay_us(2);                            
            DS18B20_DQ_OUT=1;
            delay_us(60);             
        }
        else 
        {
            DS18B20_DQ_OUT=0;// Write 0
            delay_us(60);             
            DS18B20_DQ_OUT=1;
            delay_us(2);                          
        }
    }
}
//开始温度转换
void DS18B20_Start(void)// ds1820 start convert
{   						               
    DS18B20_Rst();	   
	DS18B20_Check();	 
    DS18B20_Write_Byte(0xcc);// skip rom
    DS18B20_Write_Byte(0x44);// convert
} 
//初始化DS18B20的IO口 DQ 同时检测DS的存在
//返回1:不存在
//返回0:存在    	 
u8 DS18B20_Init(void)
{
 	GPIO_InitTypeDef  GPIO_InitStructure;
 	
 	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	 //使能PORTA口时钟 
	
 	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;				//PORTA0 推挽输出
 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 		  
 	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
 	GPIO_Init(GPIOA, &GPIO_InitStructure);

 	GPIO_SetBits(GPIOA,GPIO_Pin_0);    //输出1

	DS18B20_Rst();

	return DS18B20_Check();
}  
//从ds18b20得到温度值
//精度:0.1C
//返回值:温度值 (-550~1250) 
short DS18B20_Get_Temp(void)
{
    u8 temp;
    u8 TL,TH;
	short tem;
    DS18B20_Start ();                    // ds1820 start convert
    DS18B20_Rst();
    DS18B20_Check();	 
    DS18B20_Write_Byte(0xcc);// skip rom
    DS18B20_Write_Byte(0xbe);// convert	    
    TL=DS18B20_Read_Byte(); // LSB   
    TH=DS18B20_Read_Byte(); // MSB  
	    	  
    if(TH>7)
    {
        TH=~TH;
        TL=~TL; 
        temp=0;//温度为负  
    }else temp=1;//温度为正	  	  
    tem=TH; //获得高八位
    tem<<=8;    
    tem+=TL;//获得底八位
    tem=(float)tem*0.625;//转换     
	if(temp)return tem; //返回温度值
	else return -tem;    
} 

6.3 RTC部分

#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "rtc.h" 		    
//Mini STM32开发板
//RTC实时时钟 驱动代码			 
//正点原子@ALIENTEK
//2010/6/6
	   
_calendar_obj calendar;//时钟结构体 
 
static void RTC_NVIC_Config(void)
{	
    NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn;		//RTC全局中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;	//先占优先级1位,从优先级3位
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;	//先占优先级0位,从优先级4位
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;		//使能该通道中断
	NVIC_Init(&NVIC_InitStructure);		//根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
}

//实时时钟配置
//初始化RTC时钟,同时检测时钟是否工作正常
//BKP->DR1用于保存是否第一次配置的设置
//返回0:正常
//其他:错误代码

u8 RTC_Init(void)
{
	//检查是不是第一次配置时钟
	u8 temp=0;
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);	//使能PWR和BKP外设时钟   
	PWR_BackupAccessCmd(ENABLE);	//使能后备寄存器访问  
	if (BKP_ReadBackupRegister(BKP_DR1) != 0x5050)		//从指定的后备寄存器中读出数据:读出了与写入的指定数据不相乎
		{	 			

		BKP_DeInit();	//复位备份区域 	
		RCC_LSEConfig(RCC_LSE_ON);	//设置外部低速晶振(LSE),使用外设低速晶振
		while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET)	//检查指定的RCC标志位设置与否,等待低速晶振就绪
			{
			temp++;
			delay_ms(10);
			}
		if(temp>=250)return 1;//初始化时钟失败,晶振有问题	    
		RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);		//设置RTC时钟(RTCCLK),选择LSE作为RTC时钟    
		RCC_RTCCLKCmd(ENABLE);	//使能RTC时钟  
		RTC_WaitForLastTask();	//等待最近一次对RTC寄存器的写操作完成
		RTC_WaitForSynchro();		//等待RTC寄存器同步  
		RTC_ITConfig(RTC_IT_SEC, ENABLE);		//使能RTC秒中断
		RTC_WaitForLastTask();	//等待最近一次对RTC寄存器的写操作完成
		RTC_EnterConfigMode();/// 允许配置	
		RTC_SetPrescaler(32767); //设置RTC预分频的值
		RTC_WaitForLastTask();	//等待最近一次对RTC寄存器的写操作完成
		RTC_Set(2020,5,5,12,59,00);  //设置时间	
		RTC_ExitConfigMode(); //退出配置模式  
		BKP_WriteBackupRegister(BKP_DR1, 0X5050);	//向指定的后备寄存器中写入用户程序数据
		}
	else//系统继续计时
		{

		RTC_WaitForSynchro();	//等待最近一次对RTC寄存器的写操作完成
		RTC_ITConfig(RTC_IT_SEC, ENABLE);	//使能RTC秒中断
		RTC_WaitForLastTask();	//等待最近一次对RTC寄存器的写操作完成
		}
	RTC_NVIC_Config();//RCT中断分组设置		    				     
	RTC_Get();//更新时间	
	return 0; //ok

}		 				    
//RTC时钟中断
//每秒触发一次  
//extern u16 tcnt; 
void RTC_IRQHandler(void)
{		 
	if (RTC_GetITStatus(RTC_IT_SEC) != RESET)//秒钟中断
	{							
		RTC_Get();//更新时间   
 	}
	if(RTC_GetITStatus(RTC_IT_ALR)!= RESET)//闹钟中断
	{
		RTC_ClearITPendingBit(RTC_IT_ALR);		//清闹钟中断	  	   
  	} 				  								 
	RTC_ClearITPendingBit(RTC_IT_SEC|RTC_IT_OW);		//清闹钟中断
	RTC_WaitForLastTask();	  	    						 	   	 
}
//判断是否是闰年函数
//月份   1  2  3  4  5  6  7  8  9  10 11 12
//闰年   31 29 31 30 31 30 31 31 30 31 30 31
//非闰年 31 28 31 30 31 30 31 31 30 31 30 31
//输入:年份
//输出:该年份是不是闰年.1,是.0,不是
u8 Is_Leap_Year(u16 year)
{			  
	if(year%4==0) //必须能被4整除
	{ 
		if(year%100==0) 
		{ 
			if(year%400==0)return 1;//如果以00结尾,还要能被400整除 	   
			else return 0;   
		}else return 1;   
	}else return 0;	
}	 			   
//设置时钟
//把输入的时钟转换为秒钟
//以1970年1月1日为基准
//1970~2099年为合法年份
//返回值:0,成功;其他:错误代码.
//月份数据表											 
u8 const table_week[12]={0,3,3,6,1,4,6,2,5,0,3,5}; //月修正数据表	  
//平年的月份日期表
const u8 mon_table[12]={31,28,31,30,31,30,31,31,30,31,30,31};
u8 RTC_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec)
{
	u16 t;
	u32 seccount=0;
	if(syear<1970||syear>2099)return 1;	   
	for(t=1970;t<syear;t++)	//把所有年份的秒钟相加
	{
		if(Is_Leap_Year(t))seccount+=31622400;//闰年的秒钟数
		else seccount+=31536000;			  //平年的秒钟数
	}
	smon-=1;
	for(t=0;t<smon;t++)	   //把前面月份的秒钟数相加
	{
		seccount+=(u32)mon_table[t]*86400;//月份秒钟数相加
		if(Is_Leap_Year(syear)&&t==1)seccount+=86400;//闰年2月份增加一天的秒钟数	   
	}
	seccount+=(u32)(sday-1)*86400;//把前面日期的秒钟数相加 
	seccount+=(u32)hour*3600;//小时秒钟数
    seccount+=(u32)min*60;	 //分钟秒钟数
	seccount+=sec;//最后的秒钟加上去

	RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);	//使能PWR和BKP外设时钟  
	PWR_BackupAccessCmd(ENABLE);	//使能RTC和后备寄存器访问 
	RTC_SetCounter(seccount);	//设置RTC计数器的值

	RTC_WaitForLastTask();	//等待最近一次对RTC寄存器的写操作完成  	
	RTC_Get();
	return 0;	    
}
//得到当前的时间
//返回值:0,成功;其他:错误代码.
u8 RTC_Get(void)
{
	static u16 daycnt=0;
	u32 timecount=0; 
	u32 temp=0;
	u16 temp1=0;	  
    timecount=RTC_GetCounter();	 
 	temp=timecount/86400;   //得到天数(秒钟数对应的)
	if(daycnt!=temp)//超过一天了
	{	  
		daycnt=temp;
		temp1=1970;	//从1970年开始
		while(temp>=365)
		{				 
			if(Is_Leap_Year(temp1))//是闰年
			{
				if(temp>=366)temp-=366;//闰年的秒钟数
				else {temp1++;break;}  
			}
			else temp-=365;	  //平年 
			temp1++;  
		}   
		calendar.w_year=temp1;//得到年份
		temp1=0;
		while(temp>=28)//超过了一个月
		{
			if(Is_Leap_Year(calendar.w_year)&&temp1==1)//当年是不是闰年/2月份
			{
				if(temp>=29)temp-=29;//闰年的秒钟数
				else break; 
			}
			else 
			{
				if(temp>=mon_table[temp1])temp-=mon_table[temp1];//平年
				else break;
			}
			temp1++;  
		}
		calendar.w_month=temp1+1;	//得到月份
		calendar.w_date=temp+1;  	//得到日期 
	}
	temp=timecount%86400;     		//得到秒钟数   	   
	calendar.hour=temp/3600;     	//小时
	calendar.min=(temp%3600)/60; 	//分钟	
	calendar.sec=(temp%3600)%60; 	//秒钟
	calendar.week=RTC_Get_Week(calendar.w_year,calendar.w_month,calendar.w_date);//获取星期   
	return 0;
}	 
//获得现在是星期几
//功能描述:输入公历日期得到星期(只允许1901-2099年)
//输入参数:公历年月日 
//返回值:星期号																						 
u8 RTC_Get_Week(u16 year,u8 month,u8 day)
{	
	u16 temp2;
	u8 yearH,yearL;
	
	yearH=year/100;	yearL=year%100; 
	// 如果为21世纪,年份数加100  
	if (yearH>19)yearL+=100;
	// 所过闰年数只算1900年之后的  
	temp2=yearL+yearL/4;
	temp2=temp2%7; 
	temp2=temp2+day+table_week[month-1];
	if (yearL%4==0&&month<3)temp2--;
	return(temp2%7);
}			  

6.4 main部分

#include "led.h"
#include "delay.h"
#include "sys.h"
#include "usart.h"
#include "lcd.h"
#include "adc.h"
#include "math.h"
#include "ds18b20.h"  
#include "rtc.h"

   	
 int main(void)
 { 
	u16 adcx;
	float temp,quality;
	short temperature; 
	u8 t;	
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);// 设置中断优先级分组2 
	 
	delay_init();	    	 //延时函数初始化	  
	uart_init(9600);	 	 //串口初始化为9600
	LED_Init();		  		 //初始化与LED连接的硬件接口
 	LCD_Init();
 	Adc_Init();		  		//ADC初始化	

	POINT_COLOR=BLACK; 
	LCD_DrawLine(0,80,240,80);
	LCD_DrawLine(120,80,120,240);
	LCD_DrawLine(0,240,240,240);
	LCD_DrawLine(0,280,240,280);
	LCD_DrawLine(120,120,120,240);
	LCD_DrawLine(120,160,240,160); 
	LCD_ShowString(40,20,230,16,16,"AIR MONITIRING SYSTEM");
	LCD_ShowString(76,40,230,16,16,"BASED ON RTOS");
	LCD_ShowString(10,100,230,16,16,"AIR QUALITY:");
	LCD_ShowString(130,90,230,16,16,"HARMFUL GAS");
	LCD_ShowString(125,110,230,16,16,"CONCENTRATION:");
	LCD_ShowString(150,130,230,16,16,"     PPM");  
	LCD_ShowString(130,170,230,16,16,"TEMPERATURE:");  
	LCD_ShowString(150,190,230,16,16,"   . C"); 	
	//LCD_ShowString(10,250,200,16,16,"2020-4-30 16:00");	
	LCD_ShowString(10,290,200,16,16,"STM32F103,MQ135,DS18B20");	
	
	while(DS18B20_Init())	//DS18B20初始化	
	{
		LCD_ShowString(150,210,200,16,16,"DS18B20 Error");
		delay_ms(200);
		LCD_Fill(60,130,239,130+16,WHITE);
 		delay_ms(200);
	}
		
	while(RTC_Init())		//RTC初始化	,一定要初始化成功
	{ 
		LCD_ShowString(10,250,200,16,16,"RTC ERROR!   ");	
		delay_ms(800);
		LCD_ShowString(10,250,200,16,16,"RTC Trying...");	
	}		    						
	//显示时间				 
	LCD_ShowString(10,250,200,16,16,"    -  -  ");	   
	LCD_ShowString(100,250,200,16,16,"  :  :  ");	 	
	//显示提示信息
	//LCD_ShowString(60,130,200,16,16,"ADC_CH1_VAL:");	      
	//LCD_ShowString(60,150,200,16,16,"ADC_CH1_VOL:0.000V");	
    //LCD_ShowString(60,170,200,16,16,"KQ_ZHILIANG:    ppm");	
   	 
	while(1)
	{
		
		adcx=Get_Adc_Average(ADC_Channel_1,10);
		//LCD_ShowxNum(156,130,adcx,4,16,0);//显示ADC的值
		temp=(float)adcx*(3.3/4096);
		quality=pow((11.5428 * 35.904 * temp )/(25.5 - 5.1 * temp),1.0/0.6549);
		adcx=temp;
		//LCD_ShowxNum(156,150,adcx,1,16,0);//显示电压值
		temp-=adcx;
		//temp*=1000;
		//LCD_ShowxNum(172,150,temp,3,16,0X80);
		LCD_ShowxNum(150,130,quality,4,16,0);//显示转化后的PPM值
		//根据有害气体浓度判断空气质量
		if(0<= quality && quality <=75)
			LCD_ShowString(20,150,200,16,24,"NORMAL");	
		else if(quality<=150)
			LCD_ShowString(20,150,200,16,24,"MILD  ");
		else if(quality<=500)
			LCD_ShowString(20,150,200,16,24,"MIDDLE");
		else 
			LCD_ShowString(20,150,200,16,24,"SEVERE");
	
		
		temperature=DS18B20_Get_Temp();	
		if(temperature<0)
		{
			LCD_ShowChar(60+40,150,'-',16,0);			//显示负号
			temperature=-temperature;					//转为正数
		}else LCD_ShowChar(60+40,150,' ',16,0);			//去掉负号
		LCD_ShowNum(150+8,190,temperature/10,2,16);	//显示正数部分	    
   		LCD_ShowNum(150+32,190,temperature%10,1,16);	//显示小数部分
		
		//LCD_ShowString(170,190,290,16,16,"26");//显示温度
		
		if(quality>=150) //当到达中度污染时红灯闪烁报警
		{
			LED1=1;
			LED0=!LED0;
			POINT_COLOR=RED;//设置字体为红色 
		}
		else
		{
			LED0=1;
			LED1=!LED1;  //正常时绿灯闪烁
			POINT_COLOR=BLUE;//设置字体为蓝色 
		}
		
		//显示时间
		if(t!=calendar.sec)
		{
			POINT_COLOR=BLACK;//设置字体为黑色
			t=calendar.sec;
			LCD_ShowNum(10,250,calendar.w_year,4,16);									  
			LCD_ShowNum(50,250,calendar.w_month,2,16);									  
			LCD_ShowNum(74,250,calendar.w_date,2,16);	 
			
			LCD_ShowNum(100,250,calendar.hour,2,16);									  
			LCD_ShowNum(124,250,calendar.min,2,16);									  
			LCD_ShowNum(148,250,calendar.sec,2,16);
			switch(calendar.week)
			{
				case 0:
					LCD_ShowString(170,250,200,16,16,"Sunday");
					break;
				case 1:
					LCD_ShowString(170,250,200,16,16,"Monday");
					break;
				case 2:
					LCD_ShowString(170,250,200,16,16,"Tuesday");
					break;
				case 3:
					LCD_ShowString(170,250,200,16,16,"Wednesday");
					break;
				case 4:
					LCD_ShowString(170,250,200,16,16,"Thursday");
					break;
				case 5:
					LCD_ShowString(170,250,200,16,16,"Friday");
					break;
				case 6:
					LCD_ShowString(60,148,200,16,16,"Saturday ");
					break;  
			}
		}
		delay_ms(200);	
	}											    
}	


7 最后

技术解答、毕设帮助、开题指导
print("Q 746876041") 

请添加图片描述

版权声明:本文为CSDN博主「DC-STDIO」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/huawei123444/article/details/121561361

DC-STDIO

我还没有学会写个人说明!

暂无评论

相关推荐

做一辆超mini平衡自行车,全开源!

大家好,我是张巧龙,今天给大家带来一个平衡自行车,我实验室一个19级的本科生做的,他今年也获得了全国电赛二等奖(F题)的成绩。人嘛,非常帅的一个小