51单片机 DHT11温湿度传感器 MQ2传感器

目录

前言

程序思路

DHT11

一、DHT11基础储备

 二、接口说明

三、协议及数据格式

​编辑

四、DHT11代码

MQ-2烟雾传感器

一、MQ-2烟雾传感器介绍

二、工作原理

三、时序配置

四、指令配置

​编辑

五、代码

LCD1602

一、LCD1602介绍

二、LCD1602指令介绍 ​编辑

三、LCD1602代码

 HC-05

一、HC-05介绍

三、uart代码

主程序

总结


文章来源地址https://uudwc.com/A/rZA1D

前言

基于51单片机开发板的DHT11温湿度传感器和MQ2烟雾报警器,DHT11温湿度传感器和MQ2烟雾报警器的数据传入单片机后进行数据处理,通过串口发送到手机端和LCD1602显示屏显示,可以通过手机端进行阈值控制。

e19e7854a39549988953c480489696c1.png

程序思路

a0aa551125e14645921feb9f092e0f78.png

DHT11

一、DHT11基础储备

8ce7ee1d5b6d406d887ed2b5e4ca80b5.jpeg

        DHT11 数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器,内部由一个 8 位单片机控制一个电阻式感湿元件和一个 NTC 测温元件。DHT11 虽然也是采用单总线协议,但是该协议与 DS18B20 的单总线协议稍微有些不同之处。

        相比于 DS18B20 只能测量温度,DHT11 既能检测温度又能检测湿度,不过 DHT11 的精度和测量范围都要低于 DS18B20,其温度测量范围为 0~50℃,误差在±2℃;湿度的测量范围为 20%~90%RH(Relative Humidity 相对湿度—指空气中水汽压与饱和水汽压的百分比),误差在±5%RH。DHT11 电路很简单,只需要将 DATA 引脚连接单片机的一个 I/O 即可,不过该引脚需要上拉一个 5K 的电阻,DHT11 的供电电压为 3~5.5V。

 二、接口说明

接口有两种一种为3个引脚的

Pin 名称 注释
1 +(VCC) 供电3.3-5V
2 out(DATA) 输出串行数据
3 -(GND)

接地

Pin 名称 注释
1 VDD 供电3.3~5.5V DC
2 DATA 串行数据,单总线
3 NC 空脚
4 GND 接地,电源负极

2. 电源引脚
        DHT11的供电电压为3~5.5 V。传感器上电后,要等待 1s 以越过不稳定状态,在此期间无需发送任何指令。电源引脚(VDD,GND)之间可增加一个100nF 的电容,用以去耦滤波。

3. 串行接口(单线双向)
        DATA 用于微处理器与 DHT11之间的通讯和同步,采用单总线数据格式,一次通讯时间4ms左右,数据分小数部分和整数部分

三、协议及数据格式

        DHT11 采用单总线协议与单片机通信,单片机发送一次复位信号后,DHT11 从低功耗模式转换到高速模式,等待主机复位结束后,DHT11 发送响应信号,并拉高总线准备传输数据。一次完整的数据为 40bit,按照高位在前,低位在后的顺序传输。

        数据格式为:8bit 湿度整数数据+8bit 湿度小数数据+8bit 温度整数数据+8bit 温度小数数据+8bit 校验和,一共 5 字节(40bit)数据。由于 DHT11 分辨率只能精确到个位,所以小数部分是数据全为 0。校验和为前 4 个字节数据相加,校验的目的是为了保证数据传输的准确性。

        DHT11 只有在接收到开始信号后才触发一次温湿度采集,如果没有接收到主机发送复位信号,DHT11 不主动进行温湿度采集。当数据采集完毕且无开始信号后,DHT11 自动切换到低速模式。

注意:由于 DHT11 时序要求非常严格,所以在操作时序的时候,为了防止中断干扰总线时序,先关闭总中断,操作完毕后再打开总中断。

ef57b4d1233c4604a976295aab491ce6.png

0c47e98a3ddd4c18a344326a44479b87.png

3dcc2a4895e64013abf8f01d38616f30.png

外设读取步骤
主机和从机之间的通信可通过如下几个步骤完成(外设(如微处理器)读取DHT11的数据
步骤一:
DHT11上电后(DHT11上电后要等待1S以越过不稳定状态在此期间不能发送任何指令),测
试环境温湿度数据,并记录数据,同时DHT11的DATA数据线由上拉电阻拉高一直保持高电平;
此时DHT11的DATA引脚处于输入状态,时刻检测外部信号。
步骤二:
微处理器的I/O设置为输出同时输出低电平,且低电平保持时间不能小于18ms(最大不得
超过30ms),然后微处理器的I/O设置为输入状态,由于上拉电阻,微处理器的I/O即DHT11的
DATA数据线也随之变高,等待DHT11作出回答信号。
5640b9cc44ff48e7869ff7565894f16c.png

我们可以通过延迟的方式达到高低电平保持的效果,通过STC-ISP的软件延时计算器生成在11.0592MHz系统频率下的延迟函数

64555e3ef46e4f16b80da0e234836e71.png

使用_nop_();时必须包含头文件 intrins.h,一个_nop_();指令延迟2us

11.0592Mhz:
时钟周期:1/11.0592Mhz,1单位是秒所以11.0592Mhz要转为秒为11059200hz
1/11059200≈0.00000009s
机器周期:12×时钟周期=0.00000009s×12=0.00000109s
转为us就是1.09us

在调用此函数时,需要一个调用指令,此指令消耗 2个机器周期(即 2.18us);函数执行完 毕时要返回主调函数,需要一个返回指令,此指令消耗 2 个机器周期(即 2.18us)。调用和返回消耗了

2.18us + 2.18us = 4.36us。即5us,所以我们调用us延迟函数时,延迟时间等于2*n+5us

//延时us   --2*n+5us
void DHT11_delay_us(unsigned char n)
{
    while(--n);
}
程序经过在keil中运行,计算出一次自减时间约为8.7us。所以在1ms自减运算的次数为: 1000/8.7=114,循环几次就是延迟几毫秒。
//延时ms
void DHT11_delay_ms(unsigned int z)
{
   unsigned int i,j;
   for(i=z;i>0;i--)
      for(j=114;j>0;j--);
}

//DHT11起始信号
void DHT11_start()	
{
	Temp_data=1;
	
	DHT11_delay_us(2);//高电平保持时间
	
	Temp_data=0;
	
	DHT11_delay_ms(20);//低电平保持20ms
	
	Temp_data=1;
	
	DHT11_delay_us(13);//拉高
}
步骤三:
DHT11的DATA引脚检测到外部信号有低电平时,等待外部信号低电平结束,延迟后DHT11的
DATA引脚处于输出状态,输出83微秒的低电平作为应答信号,紧接着输出87微秒的高电平通知
外设准备接收数据,微处理器的I/O此时处于输入状态,检测到I/O有低电平(DHT11回应信号)
后,等待87微秒的高电平后的数据接收。
08a8d35fc51b4c9da703879125d10d7d.png

由于51单片机在11.0592MHz系统频率下延迟无法精确到1us,所以我们统一为延迟85us作为应答信号,实时证明85us应答信号也可以接收到数据。

步骤四:
由DHT11的DATA引脚输出40位数据,微处理器根据I/O电平的变化接收40位数据,位数据
“0”的格式为:54微秒的低电平和23-27微秒的高电平,位数据“1”的格式为:54微秒的低
电平加68-74微秒的高电平。位数据“0”、“1”格式信号。
86d79da269504ca797cd968dca494e82.png

 如图所示我们可以通过从机信号的高电平保持时间来判断位数据为“0”或“1”我们通过调用 DHT11_delay_us(9);2*9+5=23us;然后进行判断是否为高电平(因为执行一条语句需要1.09us)如果为高电平接收到的数据为1,否则为0。

//接收一个字节
unsigned char DHT11_rec_byte()
{
	unsigned char i,dat;
	for(i=0;i<8;i++)
	{
			while(!Temp_data);
		  DHT11_delay_us(9);	//延迟23us
			dat<<=1;			//数据移位,为新一位数据赋值做准备
			if(Temp_data==1)	//如果为高电平,dat为1
			{
				dat+=1;
			}
			while(Temp_data);	//为低电平退出循环,接收下一位数据
	}
	return dat;
}

然后将得到的数据进行储存

结束信号:
DHT11的DATA引脚输出40位数据后,继续输出低电平54微秒后转为输入状态,由于上拉电
阻随之变为高电平。但DHT11内部重测环境温湿度数据,并记录数据,等待外部信号的到来。
//接收温湿度数据
void DHT11_receive()
{
	DHT11_start();
	Temp_data=1;
	if(Temp_data==0)
	{
		while(Temp_data==0);   //等待拉高     
        DHT11_delay_us(40);  //拉高后延时85us
		
        R_H=DHT11_rec_byte();    //接收湿度高八位  
        R_L=DHT11_rec_byte();    //接收湿度低八位  
        T_H=DHT11_rec_byte();    //接收温度高八位  
        T_L=DHT11_rec_byte();    //接收温度低八位
        revise=DHT11_rec_byte(); //接收校正位

        DHT11_delay_us(25);    //结束
        if((R_H+R_L+T_H+T_L)==revise)      //校正
        {
            RH=R_H;
            RL=R_L;
            TH=T_H;
            TL=T_L;		
        } 
        /*数据处理,方便显示*/
        rec_dat[0]=RH;
        rec_dat[1]=RL;
        rec_dat[2]=TH;
        rec_dat[3]=TL;
	}	
}

fdefc5d5b4614545a924adc7fcd6b0c9.png

四、DHT11代码

dht11.c  代码

#include "dht11.h"
unsigned int rec_dat[9];
unsigned int R_H,R_L,T_H,T_L;
unsigned char RH,RL,TH,TL,revise;
//延时us   --2*n+5us
void DHT11_delay_us(unsigned char n)
{
    while(--n);
}

//延时ms
void DHT11_delay_ms(unsigned int z)
{
   unsigned int i,j;
   for(i=z;i>0;i--)
      for(j=114;j>0;j--);
}
//DHT11起始信号
void DHT11_start()	
{
	Temp_data=1;
	
	DHT11_delay_us(2);
	
	Temp_data=0;
	
	DHT11_delay_ms(20);
	
	Temp_data=1;
	
	DHT11_delay_us(13);
}
//接收一个字节
unsigned char DHT11_rec_byte()
{
	unsigned char i,dat;
	for(i=0;i<8;i++)
	{
			while(!Temp_data);
		  DHT11_delay_us(9);	//延迟23us
			dat<<=1;			//数据移位,为新一位数据赋值做准备
			if(Temp_data==1)	//如果为高电平,dat为1
			{
				dat+=1;
			}
			while(Temp_data);	//为低电平退出循环,接收下一位数据
	}
	return dat;
}
//接收温湿度数据
void DHT11_receive()
{
	DHT11_start();
	Temp_data=1;
	if(Temp_data==0)
	{
		while(Temp_data==0);   //等待拉高     
        DHT11_delay_us(40);  //拉高后延时85us
		
        R_H=DHT11_rec_byte();    //接收湿度高八位  
        R_L=DHT11_rec_byte();    //接收湿度低八位  
        T_H=DHT11_rec_byte();    //接收温度高八位  
        T_L=DHT11_rec_byte();    //接收温度低八位
        revise=DHT11_rec_byte(); //接收校正位

        DHT11_delay_us(25);    //结束
        if((R_H+R_L+T_H+T_L)==revise)      //校正
        {
            RH=R_H;
            RL=R_L;
            TH=T_H;
            TL=T_L;		
        } 
        /*数据处理,方便显示*/
        rec_dat[0]=RH;
        rec_dat[1]=RL;
        rec_dat[2]=TH;
        rec_dat[3]=TL;
	}	
}

dht11.h

#ifndef __UART_H__
#define __UART_H__

void InitUART(void);
unsigned char UART_Receive_Data();

#endif

MQ-2烟雾传感器

一、MQ-2烟雾传感器介绍

a0f482cba4bc4603804ccd26cfa22d63.jpegMQ-2传感器所使用的气敏材料是在清洁空气中电导率较低的二氧化锡(SnO2)。当传感器所处环境中存在可燃气体时,传感器的电导率随空气中可燃气体浓度的增加而增大。使用简单的电路即可将电导率的变化转换为与该气体浓度相对应的输出信号。MQ-2气体传感器可用于家庭和工厂的气体泄漏检测,适宜对液化气、丁烷、丙烷、甲烷、酒精、氢气、烟雾等的探测,对天然气和其它可燃蒸汽的检测也很理想。这种传感器可检测多种可燃性气体,是一款适合多种应用的低成本传感器

特点:

1、具有信号输出指示。

2、双路信号输出(模拟量输出及TTL电平输出)

3、TTL输出有效信号为低电平。(当输出低电平时信号灯亮,可直接接单片机)                 

4、模拟量输出0~5V电压,浓度越高电压越高。

5、对液化气,天然气,城市煤气有较好的灵敏度。

6、具有长期的使用寿命和可靠的稳定性

7、快速的响应恢复特性

二、工作原理

MQ-2型烟雾传感器属于二氧化锡半导体气敏材料,属于表面离子式N型半导体。处于200~3000摄氏度时,二氧化锡表面吸附空气中的氧,形成氧的负离子吸附,使半导体中的电子密度减少,从面使其电阻值增加。当与烟雾接触时,如果晶粒间界处的势垒收到烟雾的调至面变化,就会引起表面导电率的变化。利用这一点就可以获得这种烟雾存在的信息烟雾浓度越大导电率越大,输出电阻越低,则输出的模拟信号就越大。
 

Pin 名称 注释
1 VCC 电源正极接口,可外接3.3~5v供电电源
2 GND 电源负极接口,可外接电源负极或地线(GND)
3 DO 数字信号输出接口(0和1),可外接单片机的GPIO
4 AO 模拟信号输出接口,可外接单片的ADC采样通道

3. 烟雾检测
当可燃气体浓度小于指定的阈值时,DO输出高电平,大于指定的阈值时则输出低电平。

4. 阈值调节
模块中蓝色的电位器是用于调节阀值,顺时针旋转,阈值会越大,逆时针越小。

5. 使用AO接口
与DO不同,AO会输出模拟信号,因此需要与单片机的ADC采样通道连接。单片机可以通过此模拟信号来获取可燃气体浓度大小。
我们可以通过接收到的AD值转换为电压后在转换为ppm值。

AD转换过程我们使用开发板上的XPT2046,XPT2046 是一款 4 线制电阻式触摸屏控制器,内含 12 位分辨率 125KHz 转换速率逐步逼近型 A/D 转换器。

446bcfd53ed542ce8cd71c208969e710.png

因为AIN0,AIN1,AIN2给开发板上的可调电阻,NTC和GR占用了,开发板上留出AIN3供我们使用,通过接线把AO口接到图中36400b23728b47a491855c684a9e93e8.png

三、时序配置

XPT2046 数据接口是串行接口,其典型工作时序如图所示,图中展示的信号来自带有基本串行接口的单片机或数据信号处理器。处理器和转换器之间的的通信需要 8 个时钟周期,可采用 SPI、SSI 和Microwire 等同步串行接口。一次完整的转换需要 24 个串行同步时钟(DCLK)来完成。
前 8 个时钟用来通过DIN引脚输入控制字节。当转换器获取有关下一次转换的足够信息后,接着根据获得的信息设置输入多路选择器和参考源输入,并进入采样模式,如果需要,将启动触摸面板驱动器。3个多时钟周期后,控制字节设置完成,转换器进入转换状态。这时,输入采样-保持器进入保持状态,触摸面板驱动器停止工作(单端工作模式)。接着的 12 个时钟周期将完成真正的模数转换。如果是度量比率转换方式(SER/! DFR=0),驱动器在转换过程中将一直工作,第 13 个时钟将输出转换结果的最后一 位。剩下的 3 个多时钟周期将用来完成被转换器忽略的最后字节(DOUT置低)我们读取16位数据,仅高12位有效低4位位0。
控制字节由 DIN 输入的控制字,它用来启动转换,寻址,设置 ADC 分辨率,配置和对 XPT2046 进行掉电控制。

 1477463dd8c14d3fa71f956ee56c4a4d.png

20c3e9e2610344b4bfc62f74f87a8ba6.png

首先我们对控制引脚进行定义
//引脚定义
sbit XPY2046_DIN=P3^4;
sbit XPY2046_CS=P3^5;
sbit XPY2046_DCLK=P3^6;
sbit XPY2046_DOUT=P3^7;

13a5bca1b22a485aa714100513607257.png然后我们根据时序图对其进行读取AD值,!CS为0时,才可以进行有效操作,所以开始时吧CS脚置0,对DCLK赋初值0,然后开始进行写操作,由时序图可以看出,DIN进行赋值时给DCLK一个上升沿,把信号发送出去,发送完给DCLK一个下降沿,由时序规范表可以看到tACQ最小获取时间为1.5us所以我们可以直接给DCLK置1后置0,使DCLK形成一个下降沿。然后通过循环移位的方式把8位指令从高到低全部写入xpt2046,写入后有BUSY忙碌信号,我们可以通过延迟函数来形成,也可以不加,因为我们tACQ已经满足了时序条件,最后读出DOUT由时序图可以看出DOUT读出在DCLK的下降沿,我们可以先给DCLK置1后置0,形成一个下降沿后读取DOUT,数据也是从高到低读取,读取16位数据

/**
  * @brief  ZPT2046读取AD值
  * @param  Command 命令字,范围:头文件内定义的宏,结尾的数字表示转换的位数
  * @retval AD转换后的数字量,范围:8位为0~255,12位为0~4095
  */
unsigned int XPT2046_ReadAD(unsigned char Command)
{
	unsigned char i;
	unsigned int Data=0;
	XPY2046_DCLK=0;	 //DCLK赋初值
	XPY2046_CS=0;	//时序开始,读取一组有效数据
	for(i=0;i<8;i++)
	{
		XPY2046_DIN=Command&(0x80>>i);//写入1位数据
		XPY2046_DCLK=1;				  //写入完置1发送
		XPY2046_DCLK=0;				  //发送完置0,准备接收下一数据
	}
	for(i=0;i<16;i++)
	{
		XPY2046_DCLK=1;				  //置1,形成上升沿
		XPY2046_DCLK=0;			      //置0,形成下降沿,在下降沿读取数据
		if(XPY2046_DOUT){Data|=(0x8000>>i);}
		//如果数据为1,则赋值为1,为0赋值为0,
		//这里为0不赋值,直接移位进行赋值
	}
	XPY2046_CS=1;//完成一组数据读取后拉高,结束时序
	return Data>>8;
	//我们读取的是8位数据,把16位数据右移8位,如果为12位,右移4位
}

时序部分已经完成,接下来是写入的指令。

四、指令配置

由之前的时序图可知,指令为8位数据
起始位:第一位,即 S 位。控制字的首位必须是 1,即 S=1。在 XPT2046 的 DIN 引脚检测到起始位前,所有的输入将被忽略。 地址——接下来的 3 位(A2、A1 和 A0)选择多路选择器的现行通道,触摸 屏驱动和参考源输入。
MODE:模式选择位,用于设置 ADC 的分辨率。MODE=0,下一次的转换将是 12 位模式;
MODE=1,下一次的转换将是 8 位模式。(我们采用8位模式 MODE = 1;)
SER/!DFR:SER/!DFR位控制参考源模式,选择单端模式(SER/!DFR=1),或者差分模式 (SER/!DFR=0)。在X坐标、Y坐标和触摸压力测量中,为达到最佳性能,首选差分工作模式。参考电 压来自开关驱动器的电压。在单端模式下,转换器的参考电压固定为VREF相对于GND引脚的电压。(我们选择单端模式(SER/!DFR=1);)
PD0 PD1——表 5 展示了掉电和内部参考电压配置的关系。ADC 的内部参考电压可以单独关闭或者打开,但是,在转换前,需要额外的时间让内部参考电压稳定到最终稳定值;如果内部参考源处于掉电状态,还要确保有足够的唤醒时间。ADC 要求是即时使用,无唤醒时间的。另外还得注意,当 BUSY 是 高电平的时候,内部参考源禁止进入掉电模式。XPT2046 的通道改变后,如果要关闭参考源,则要重新 对 XPT2046 写入命令。(我们使用低功率模式PD0=0;PD1=0;)
2a142422ae0a4f77b633a7096eb797eb.png

7a2719fa67344290b1631f6151cb4f89.png

 我们只看单端模式下的输入配置A2-A0

7132c286a5e04a0cbbe9e357b0ff95cc.png

//如果读取12位 把C改为4 例如#define XPT2046_VBAT	0xA4
#define XPT2046_VBAT	0xAC
#define XPT2046_AUX		0xEC
#define XPT2046_XP		0x9C	
#define XPT2046_YP		0xDC

13a5bca1b22a485aa714100513607257.png这里配置了AIN0(X+),AIN1(Y+),AIN2(VBAT),AIN3(AUX)的输入配置,我们MQ-2接入的是AIN3(AUX),所以我们调用XPT2046_ReadAD(XPT2046_AUX);就可以得到MQ-2的AO  口输入的AD值,通过AD值转换为电压后,再转换为ppm

30bdeba5f8934398b5f082bdf3120b36.jpeg

有图可以看出我们转换后的电压值和万用表测量值在一定误差范围内,可以忽略误差。

五、代码

XPT2046.c

#include <REG51.H>
#include <INTRINS.H>

//引脚定义
sbit XPY2046_DIN=P3^4;
sbit XPY2046_CS=P3^5;
sbit XPY2046_DCLK=P3^6;
sbit XPY2046_DOUT=P3^7;

/**
  * @brief  XPT2046读取AD值
  * @param  Command 命令字,范围:头文件内定义的宏,结尾的数字表示转换的位数
  * @retval AD转换后的数字量,范围:8位为0~255,12位为0~4095
  */
unsigned int XPT2046_ReadAD(unsigned char Command)
{
	unsigned char i;
	unsigned int Data=0;
	XPY2046_DCLK=0;	 //DCLK赋初值
	XPY2046_CS=0;	//时序开始,读取一组有效数据
	for(i=0;i<8;i++)
	{
		XPY2046_DIN=Command&(0x80>>i);//写入1位数据
		XPY2046_DCLK=1;				  //写入完置1发送
		XPY2046_DCLK=0;				  //发送完置0,准备接收下一数据
	}
	for(i=0;i<16;i++)
	{
		XPY2046_DCLK=1;				  //置1,形成上升沿
		XPY2046_DCLK=0;			      //置0,形成下降沿,在下降沿读取数据
		if(XPY2046_DOUT){Data|=(0x8000>>i);}
		//如果数据为1,则赋值为1,为0赋值为0,
		//这里为0不赋值,直接移位进行赋值
	}
	XPY2046_CS=1;//完成一组数据读取后拉高,结束时序
	return Data>>8;
	//我们读取的是8位数据,把16位数据右移8位,如果为12位,右移4位
}

XPT2046.h

#ifndef __XPT2046_H__
#define __XPT2046_H__
//如果读取12位 把C改为4 例如#define XPT2046_VBAT	0xA4
#define XPT2046_VBAT	0xAC
#define XPT2046_AUX		0xEC
#define XPT2046_XP		0x9C	
#define XPT2046_YP		0xDC

unsigned int XPT2046_ReadAD(unsigned char Command);

#endif

LCD1602

一、LCD1602介绍

LCD1602液晶显示屏
LCD ( Liquid Crystal Display 的简称)液晶显示器。能够同时显示16x2,32个字符,是一种专门用来显示字母、数字、符号等的点阵型液晶模块。
LCD1602液晶显示器是广泛使用的一种字符型液晶显示模块。它是由字符型液晶显示屏(LCD)、控制驱动主电路HD44780及其扩展驱动电路HD44100,以及少量电阻、电容元件和结构件等装配在PCB板上而组成。该显示屏的优点是耗电量低、体积小、辐射低。
LCD1602主要用来显示数字、字母、图形以及少量自定义字符。可以显示2行16个字符,拥有16个引脚,其中8位数据总线D0-D7,和RS、R/W、EN三个控制端口,工作电压为5V,并且带有字符对比度调节V0和背光源AK。

6db7f510a0ba44f785a70c3a34851b2e.png

42111d82dd894210bb111ec8c69d51ea.png

01b63925c11e41b9addaf03ce31a58cb.png

b5006eabc51d4a94a3713bb0ba372e4e.png

二、LCD1602指令介绍 0a97aaad11374519af5a0622d50c2597.png

a8d070e881fb43c1a484bd854ccc9aa1.png

4552016a2a384d45985762f0a17d02f6.png

三、LCD1602代码

 lcd1602.c

#include"lcd1602.h"
void Lcd1602_Write_Cmd(unsigned char cmd)     //写命令
{
    LCD1602_RS = 0;
    LCD1602_RW = 0;
    LCD1602_DB = cmd;
		LCD_Delay10ms(1);
    LCD1602_EN = 1;
		LCD_Delay10ms(1);
    LCD1602_EN = 0;    
}

void Lcd1602_Write_Data(unsigned char dat)   //写数据
{
      //Read_Busy();
      LCD1602_RS = 1;
      LCD1602_RW = 0;
      LCD1602_DB = dat;
	  LCD_Delay10ms(1);
      LCD1602_EN = 1;
	  LCD_Delay10ms(1);
      LCD1602_EN = 0;
}
//指定位置开始显示数据!
void LcdSetCursor(unsigned char x,unsigned char y)  //坐标显示
{
    unsigned char addr;
    if(y == 0)
        addr = 0x00 + x;//第一行开始,x表示一行的第x个
    else
        addr = 0x40 + x;//第二行开始,x表示一行的第x个
    
    Lcd1602_Write_Cmd(addr|0x80);
}

void LcdShowStr(unsigned char x,unsigned char y,unsigned char *str)     //显示字符串
{
    LcdSetCursor(x,y);      //当前字符的坐标
    while(*str != '\0')
    {
        Lcd1602_Write_Data(*str++);
    }
}
int LCD_Pow(int X,int Y)
{
	unsigned char i;
	int Result=1;
	for(i=0;i<Y;i++)
	{
		Result*=X;
	}
	return Result;
}
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i;
	LcdSetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		Lcd1602_Write_Data(Number/LCD_Pow(10,i-1)%10+'0');
	}
}
void InitLcd1602()              //1602初始化
{
    Lcd1602_Write_Cmd(0x38);    //打开,5*8,8位数据
    Lcd1602_Write_Cmd(0x0c);
    Lcd1602_Write_Cmd(0x06);
    Lcd1602_Write_Cmd(0x01);    //清屏   
}

void LCD_Delay10ms(unsigned int c)   //误差 0us
{
    unsigned char a,b;
    for(;c>0;c--)
        for(b=38;b>0;b--)
            for(a=130;a>0;a--);
}

lcd1602.h

#ifndef __LCD1602_H_
#define __LCD1602_H_
#include<reg51.h>

#define LCD1602_DB  P0      //data bus 数据总线
sbit LCD1602_RS = P2^6;
sbit LCD1602_RW = P2^5;
sbit LCD1602_EN = P2^7;	 

void LCD_Delay10ms(unsigned int c);
void Lcd1602_Write_Cmd(unsigned char cmd);     //写命令
void Lcd1602_Write_Data(unsigned char dat);   //写数据
void LcdSetCursor(unsigned char x,unsigned char y);  //坐标显示
void LcdShowStr(unsigned char x,unsigned char y,unsigned char *str);     //显示字符串  
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);//显示数字
void InitLcd1602();              //1602初始化	
#endif

 HC-05

一、HC-05介绍

HC-05蓝牙模块是一种基于蓝牙协议的简单无线通信设备。该模块基于BC417单芯片蓝牙IC,符合蓝牙v2.0标准,支持UART和USB接口。

具有两种工作模式:命令响应工作模式和自动连接工作模式。

当模块处于命令响应工作模式(或者AT模式)时能才能执行 AT 命令,用户可向模块发送各种 AT指令,为模块设定控制参数或发布控制命令。(AT指令就是我们PC与一些终端设备(例如蓝牙,WiFi模块)之间进行通信的,配置这些终端设备参数的一套指令。)在自动连接工作模式下模块又可分为主(Master)、从(Slave)和回环(Loopback)三种工作角色。当模块处于自动连接工作模式时,将自动根据事先设定的方式连接的数据传输。主模式:该模块可以主动搜索并连接其它蓝牙模块并接收发送数据。从模式:只能被搜索被其它蓝牙模块连接进行接收发送数据。回环:蓝牙模块就是将接收的数据原样返回给远程的主设备。
 

a658a0bb54a44528b8503ac76dfe432f.jpeg

Pin 名称 注释
1 STATE 状态指示。未连接时输出低电平,连接时输出高电平。
2 RXD UART接收引脚
3 TXD UART发射引脚
4 GND
5 VCC 接电源,可以用+5V。
6 EN 使能。接地禁用模块,悬空或接3.3V使能。

 如何进入AT模式:
HC-05与HC-06不同,HC-06没有连接上时,就是AT模式,而HC-05需要有个开关,注意到模块上的小黑点没有,不按的时候是可配对状态,1秒闪2次。按它再上电就使HC-05进入AT模式了,严格的讲,它只是一个开的作用,无法退出AT模式。(退出的话可以在串口发送AT+RESET复位,或者重新上电)此时指示灯慢闪(2秒亮一次),模块进入AT状态。
进入AT状态后,通过串口转换器连接和电脑进行AT指令操作

基本配置(所有AT指令都必须换行)
模块默认波特率为9600、默认配对密码为1234、默认名称为HC-05,
AT模式波特率固定38400,8位数据位、1位停止位、无奇偶校验的通信格式。
(1)发送AT\r\n,回复OK;
(2)发送AT+UART ?\r\n,回复+UART 9600,1,0
(3)发送AT+UART=9600,0,0\r\n,回复OK。即为波特率配置成功。

(4)发送AT+NAME="xzxq",更改蓝牙名字返回+NAME: xzxq OK
通过这几个简单的配置,蓝牙模块基本配置完成

单片机端通过配置好相同的9600波特率进行串口通信

通过软件生成的方式进行配置,获取定时器初值

e3f6fc42871f4779bebb32c850ab80aa.png定时器初值为16进制的FD

b5f630ee9e8142e39ad4c3cd2e9bf4da.png

94bb9cee9e42449490860120ae4dc243.png 0cef8a10467341a4bf1767f108f2257b.png

 我们使用方式二进行串口通信,还要允许串行接收,所以SCON = 0x50;或者对每个位进行单独配置

2a71d61637ff470cb630edc53275fda9.png

 我们使用的是定时器1,8位自动重载定时器,TMOD = 0x20;

三、uart代码

 uart.c

#include "uart.h"
#include "reg51.h"
void InitUART(void)//使用定时器1作为串口波特率发生器
{
	SCON=0x40;					//串口通信工作方式1
	REN=1;						//允许接收
	TMOD |=0x20;				//定时器1的工作方式2
	TH1=0xFD;
	TL1=0xFD;	
	TF1 = 0;		//清除TF1标志
	TR1=1;	
	TI=1;                     
	EA=1;  			//总中断允许
}
unsigned char UART_Receive_Data()	
{
	unsigned char temp;
	if(RI)//等待串口接收到数据(RI被置1)
	{
		RI=0;//清空接收中断标志位,为下次接收做准备
		temp=SBUF;
	}
	return temp;
}

 uart.h

#ifndef __UART_H__
#define __UART_H__

void InitUART(void);
unsigned char UART_Receive_Data();

#endif

主程序

main.c

#include <reg51.h>
#include <intrins.h>
#include <stdio.h>
#include "lcd1602.h"
#include "dht11.h"
#include "Key.h"
#include "Buzzer.h"
#include "XPT2046.h"
#include "uart.h"
#include "Delay.h"
#include "math.h"
/**********************
模块接口
DHT11 	    	P1^1
HC-05 RXD   	P3^1
HC-05 TXD   	P3^0
MQ2传感器AO 		IN3
MQ2传感器DO  	P2^2
风扇            P1^0
**********************/

sbit LED1 = P2^0;	//运行指示灯
sbit LED2 = P2^1;	//运行指示灯
sbit fan  = P1^0;	//风扇控制引脚
bit  flag;
float ppm;
float Vrl;
extern unsigned int rec_dat[9];
unsigned char rec_dat_lcd0[6];
unsigned char rec_dat_lcd1[6];
unsigned char rec_dat_lcd2[6];
unsigned char rec_dat_lcd3[6];
unsigned char humi_t = 60;
unsigned char temp_t = 35;
unsigned int mq2_data_t = 100;

void control();
void mq2_warning();
//主函数
void main()
{
	InitUART();
	P1=0xf0;
	InitLcd1602();
	LcdShowStr(0,0,"Temp:");
	LcdShowStr(12,0,"Humi:");
	LcdShowStr(25,0,"Smoke:");
	LcdShowStr(35,0,"mv");
	LcdShowStr(35,1,"mv");
	while(1)
	{ 
		Lcd1602_Write_Cmd(0x06);
		DHT11_receive();		//获取温湿度数据	   
		sprintf(rec_dat_lcd0,"%d",rec_dat[0]);
		sprintf(rec_dat_lcd1,"%d",rec_dat[1]);
		sprintf(rec_dat_lcd2,"%d",rec_dat[2]);
		sprintf(rec_dat_lcd3,"%d",rec_dat[3]);	
		//温度
		LcdShowStr(5,0,rec_dat_lcd2);
		LcdShowStr(7,0,".");
		LcdShowStr(8,0,rec_dat_lcd3);
		LcdShowStr(9,0," C");
		LCD_ShowNum(5,1,temp_t,2);	
		LcdShowStr(9,1," C");	
		//湿度
		LcdShowStr(17,0,rec_dat_lcd0);
		LcdShowStr(19,0,".");
		LcdShowStr(20,0,rec_dat_lcd1);
		LcdShowStr(21,0," %");
		LCD_ShowNum(17,1,humi_t,2);
		LcdShowStr(21,1," %");		

		Vrl=XPT2046_ReadAD(XPT2046_AUX)*5.0/255;	//读取AIN3的AD值,转换为电压,MQ2传感器		
		ppm = pow(11.5428 * 35.904 * Vrl/(25.5-5.1* Vrl),0.6549);
		LCD_ShowNum(31,0,Vrl*1000,4);
		LCD_ShowNum(31,1,mq2_data_t,4);	
		//下面通过串口助手打印温度
	
		printf("Humi:%d.%d %S\n",rec_dat[0],rec_dat[1],"%");
		printf("Temp:%d.%d °C\n",rec_dat[2],rec_dat[3]); 
		printf("Smoke:%f%S\n",ppm,"ppm");		
		control();
		mq2_warning();
	}
}
void mq2_warning()
{
	 if(Vrl*1000 >= mq2_data_t)//当浓度高于设定值时 ,执行条件函数
	 {
    	Delaynms(3000);//延时抗干扰
		if(Vrl*1000 >= mq2_data_t)//确定 浓度高于设定值时 ,执行条件函数
	    {
			flag = 1;
		}
	 }
	 else
	 {
		 flag = 0;
	 }
	 if(flag)
	 {
		 Buzzer_Time(500);
		 fan = 1;
	 }
	 else
	 {
		 fan = 0;
	 }
	sprintf(rec_dat_lcd0,"%d",rec_dat[0]);
	sprintf(rec_dat_lcd2,"%d",rec_dat[2]);
	if(rec_dat[2]> temp_t)
		LED1 = 0;
	else
		LED1 = 1;	
	if(rec_dat[0] > humi_t)
		LED2 = 0;
	else
		LED2 = 1;	
}
void control()
{
	switch(UART_Receive_Data())
	{
		case 0x01:humi_t--;break;
		case 0x02:humi_t++;break;
		case 0x03:temp_t--;break;
		case 0x04:temp_t++;break;
		case 0x06:mq2_data_t--;break;
		case 0x07:mq2_data_t++;break;		
		case 0x08:Lcd1602_Write_Cmd(0x18);break;//通过指令控制屏幕画面左右移动
		case 0x09:Lcd1602_Write_Cmd(0x1c);break;
		default: humi_t =humi_t;temp_t=temp_t;mq2_data_t=mq2_data_t;Lcd1602_Write_Cmd(0x06); break;
	}
	switch(Key())
	{
		case 3:Lcd1602_Write_Cmd(0x18);break;
		case 4:Lcd1602_Write_Cmd(0x1c);break;
		default:Lcd1602_Write_Cmd(0x06);break;
	}
}

Key.c

#include <REGX52.H>
#include "Delay.h"

/**
  * @brief  获取独立按键键码
  * @param  无
  * @retval 按下按键的键码,范围:0~4,无按键按下时返回值为0
  */
unsigned char Key()
{
	unsigned char KeyNumber=0;
	
	if(P3_1==0){Delaynms(20);KeyNumber=1;}
	if(P3_0==0){Delaynms(20);KeyNumber=2;}
	if(P3_2==0){Delaynms(20);KeyNumber=3;}
	if(P3_3==0){Delaynms(20);KeyNumber=4;}
	
	return KeyNumber;
}

Key.h

#ifndef __KEY_H__
#define __KEY_H__

unsigned char Key();

#endif

Delay.c


void Delaynms(unsigned int xms)
{
	unsigned char i, j;
	while(xms--)
	{
		i = 2;
		j = 239;
		do
		{
			while (--j);
		} while (--i);
	}
}

Delay.h

#ifndef __DELAY_H__
#define __DELAY_H__

void Delaynms(unsigned int xms);
#endif

 Buzzer.c

#include <REGX52.H>
#include <INTRINS.H>

//蜂鸣器端口:
sbit Buzzer=P2^5;

/**
  * @brief  蜂鸣器私有延时函数,延时500us
  * @param  无
  * @retval 无
  */
void Buzzer_Delay500us()		//@12.000MHz
{
	unsigned char i;

	_nop_();
	i = 247;
	while (--i);
}

/**
  * @brief  蜂鸣器发声
  * @param  ms 发声的时长,范围:0~32767
  * @retval 无
  */
void Buzzer_Time(unsigned int ms)
{
	unsigned int i;
	for(i=0;i<ms*2;i++)
	{
		Buzzer=!Buzzer;
		Buzzer_Delay500us();
	}
}

Buzzer.h

#ifndef __BUZZER_H__
#define __BUZZER_H__

void Buzzer_Time(unsigned int ms);

#endif

总结

 程序可能出现的一些问题比如定义的变量太多,导致地址空间溢出

*** ERROR L107: ADDRESS SPACE OVERFLOW
    SPACE:   DATA    
    SEGMENT: _DATA_GROUP_
    LENGTH:  001CH
Program Size: data=105.1 xdata=0 code=5548
Target not created.
Build Time Elapsed:  00:00:01

80895b0a4b5c405c8c14c153b466fa9e.png

把Memory Model 修改为第二个,或者第三个就行。

 全部代码

https://download.csdn.net/download/m0_64355812/87837706


原文地址:https://blog.csdn.net/m0_64355812/article/details/130898749

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请联系站长进行投诉反馈,一经查实,立即删除!

h
上一篇 2023年10月23日 21:03
【网络安全】2.3 安全的网络设计
下一篇 2023年10月23日 22:03