1、前言
设计了一个基于51单片机的室内空气净化系统,所选用单片机型号为STC89C52,硬件部分包括字符型液晶显示屏lcd1602,空气检测传感器MQ135,小功率步进电机,排气扇,蜂鸣警报器,独立按键等。将程序烧入单片机进行验证,达到了预期的效果。
2、设计思路
本系统主要由4个模块所组成: (1)数据采集模块 (2)数据/阈值显示模块 (3)阈值设置模块 (4)蜂鸣报警模块
系统设计简图如下图所示: 系统工作的主要流程如下:传感器MQ135采集数据接入单片机上的外部模拟量数据采集引脚(AIN3),经由xpt2046芯片进行AD模数转换,STC89C52对采集的数据作进一步处理转化成气体浓度(ppm)。当前环境下的气体浓度会与程序设计的阈值一并显示在LCD1602字符型液晶显示屏上。当所监测气体浓度超出阈值时,蜂鸣报警模块开始工作,发出蜂鸣警报声并开启排气扇进行排气。此外也可以人为地对阈值进行设置,以满足不同环境下的需求。
3、 系统硬件设计
3.1、MQ135传感器
1、简述:NQ135是一款对氨气、硫化物、苯系蒸汽等均有着高灵敏度的半导体气敏元件。其凭借着成本低、电路结构简单、功耗低等优点被广泛应用于日常生活生产中。 MQ135传感器实物图如下(背面): 2、重要技术指标:MQ135传感器的重要技术指标如下图所示: 3、引脚功能:从上文关于MQ135的实物图我们可以看出,MQ135一共有4个外接引脚,这4个引脚的功能分别是: VCC:接5V直流电源输入 GND:接地 DO:模拟信号输出 AO:数字信号(TTL)输出 在本项目中,需要用杜邦线将VCC和GND引脚与单片机电源的正负极相接,DO引脚与开发板AIN3引脚相接(只采集模拟量),AO引脚不需要与任何引脚相接。 4、使用注意事项:在实际使用中应该考虑以下两点:(1)VCC与GND引脚不用与电源正负极反接,否则容易烧坏芯片。(2)从上文标准测试条件中可以看出,在使用MQ135之前应该先将MQ135传感器进行预热(即插电工作一段时间)后方可正常工作。
3.2、LCD1602液晶
1、简述:作为各类单片机实物设计的常用显示屏,LCD1602是一种专门用于显示字母、数字、符号等点阵式LCD,其1602是指LCD显示的内容为16X2,即可以显示两行,每行16个字符(1个汉字占用2个字符)。 2、LCD1602关键引脚说明: RS :0=输入指令,1=输入数据 RW :0=向LCD写入指令或数据;1=从LCD读取信息 E :使能信号,1时读取信息,1->0(下降沿)执行指令 3、LCD1602基本操作时序: 1.1读状态:输入:RS=L,RW=H,E=H ----输出:D0~D7=状态字 1.2写指令:输入:RS=L,RW=L,D0~D7=指令码,E=高脉冲—>低脉冲 ----输出:无 1.3读数据:输入:RS=H,RW=H,E=H ----输出:D0~D7=数据 1.4写数据:输入:RS=H,RW=L,D0~D7=数据码,E=高脉冲—>低脉冲 ----输出:无
控制时序图: (1)读操作时序图: (2)写操作时序图: 4、LCD1602常用指令 LCD在使用的过程中,可以在RS=0、RW=0的情况下,向LCM写入一个字节的控制指令。使用的控制指令一共六个类别
(1)01H:清除DDRAM(显示数据存储器)的所有单元,光标被移动到屏幕左上角。 (2)02H:DDRAM所有单元的内容不变,光标移至左上角。 (3)输入方式设置(EnterModeSet),这些指令规定了两个方面:一是写入一个DDRAM单元后,地址指针如何改变(加一还是减一);二是屏幕上的内容是否滚动。 04H:写入DDRAM后,地址指针减一,比如第一个字符写入8FH,则下一个字符会写入8EH;屏幕上的内容不滚动。 05H:写入DDRAM后,地址指针减一,同上一种情况;每一个字符写入以后,屏幕上的内容向右滚动一个字符位。 06H:写入DDRAM后,地址指针加一,比如第一个字符写入80H,则下一个字符会写入81H;屏幕上的内容也是不滚动。这应该是最常用的一种显示方式。 07H:写入DDRAM后,地址指针加一,同上一种情况;每一个字符写入以后,屏幕上的内容向左滚动一个字符位。 (4)屏幕开关、光标开关、闪烁开关。 08H、09H、0AH、0BH:关闭显示屏,实质上是不把DDRAM中的内容对应显示在屏幕上,对DDRAM的操作还是在进行的,执行这条指令,接着对 DDRAM进行写入,屏幕上没有任何内容,但是接着执行下面的某条指令,就能看到刚才屏幕关闭期间,对DDRAM操作的效果了。 0cH:打开显示屏,不显示光标,光标所在位置的字符不闪烁。 0dH:打开显示屏,不显示光标,光标所在位置的字符闪烁。 0eH:打开显示屏,显示光标,光标所在位置的字符不闪烁。 0fH:打开显示屏,显示光标,光标所在位置的字符闪烁。 关于光标的位置:光标所在的位置指示了下一个被写入的字符所处的位置,加入在写入下一个字符前没有通过指令设置DDRAM的地址,那么这个字符就应该显示在光标指定的地方。 (5)设置光标移动(本质就是AC的增加还是减少)、整体画面是否滚动。 10H:每输入一次该指令,AC就减一,对应了光标向左移动一格。整体的画面不滚动。 14H:每输入一次该指令,AC就加一,对应了光标向右移动一格。整体的画面不滚动。 18H:每输入一次该指令,整体的画面就向左滚动一个字符位。 1CH:每输入一次该指令,整体的画面就向右滚动一个字符位。画面在滚动的时候,每行的首尾是连在一起的, 也就是每行的第一个字符,若左移25次,就会显示在该行的最后一格。在画面滚动的过程中,AC的值也是变化的。 (6)显示模式设定指令,设定了显示几行,显示什么样的点阵字符,数据总线占用几位。 20H:4位总线,单行显示,显示5×7的点阵字符。 24H:4位总线,单行显示,显示5×10的点阵字符。 28H:4位总线,双行显示,显示5×7的点阵字符。 2CH:4位总线,双行显示,显示5×10的点阵字符。 30H:8位总线,单行显示,显示5×7的点阵字符。 34H:8位总线,单行显示,显示5×10的点阵字符。 38H:8位总线,双行显示,显示5×7的点阵字符。这是最常用的一种模式。 3CH:8位总线,双行显示,显示5×10的点阵字符。
3.3、XPT2046芯片
1、简述:XPT2046芯片是一款典型的逐次逼近型模数转换器(SAR ADC),包含了采样/保持、模数转换、串口数据输出等功能。 2、典型应用电路: 3、重要引脚功能描述: CS:片选信号,控制转换时序和使能串行输入输出寄存器,当CS为低电平时芯片正常工作。 DIN:串行数据输入端,当CS为低电平时,数据在DCLK上升沿时串行输入。 DCLK:外部时钟信号输入 DOUT:串行数据输出端,当CS为低电平时,数据在DCLK下降沿时串行输出。 4、典型工作方式: XPT2046芯片的典型工作方式如下图所示: 工作方式解析:前 8 个时钟用来通过DIN引脚输入控制字节,接着的12 个时钟周期将完成真正的模数转换,剩下的3个多时钟周期将用来完成被转换器忽略的最后字节(DOUT置低)。 5、外接典型应用电路: 有4个外部接口: AIN0:检测转换电位器模拟信号,信号采集地址为0x94或者0xB4 AIN1:检测转换热敏电阻模拟信号,信号采集地址为0xD4 AIN2:检测转换光敏电阻模拟信号,信号采集地址为0xA4 AIN3:检测转换AIN3通道上模拟信号,信号采集地址为0xE4
4、 系统软件设计
4.1、算法状态机(ASM)图
针对上文关于系统模块的描述以及项目具体的功能需要,为了明晰编程思路,画出算法状态机(ASM)图如下所示:
4.2、传感器数据处理
从MQ135空气质量传感器所检测的数值需要经由模拟量—>数字量—>气体浓度的变换,其中数字量—>气体浓度遵循这如下规律:没有被测气体的环境,设定传感器输出电压值为参考电压,这时,aout端的电压在1v左右,当传感器检测到被测气体时,电压每升高0.1v,实际被测气体的浓度增加20ppm(简单的说:1ppm=1mg/kg=1mg/l=1×10-6 常用来表示气体浓度,或者溶液浓度),根据这个参数就可以在单片机里面将测得的模拟量电压值转换为浓度值。然后再经由AIN3所测得的数值与真实的数值进行线性拟合得出相应表达式: value(ppm) = (int)(0.236 * (value(ad) - 12.8623)); 由于所测数据点样本较少,得出的拟合结果公式可能会与真实值存在小范围的偏差,但经过实际测试发现并不影响实际使用,在一定0~5V的电压范围内所测的气体浓度与真实值接近。
4.3、工程具体代码
此项工程的具体代码如下所示,注意单片机型号!不同型号的单片机所对应的具体引脚值会有所不同,具体请查看单片机开发原理图。此外,对于 XPT2046.H
#ifndef __xpt2046_H_
#define __xpt2046_H_
#include "reg52.h"
#include "intrins.h"
#ifndef u8
#define u8 unsigned char
#endif
#ifndef u16
#define u16 unsigned int
#endif
sbit DOUT = P3^7;
sbit CLK = P3^6;
sbit DIN = P3^4;
sbit CS = P3^5;
u16 read_ad_data(u8 cmd);
u16 SPI_read();
void SPI_write(u8 dat);
#endif
LCD1602.H
#ifndef __lcd1602_H_
#define __lcd1602_H_
#include"reg52.h"
#ifndef uchar8
#define uchar8 unsigned char
#endif
#ifndef uint16
#define uint16 unsigned int
#endif
#define lcd_data P0
sbit lcd_RW = P2^5;
sbit lcd_RS = P2^6;
sbit lcd_E = P2^7;
void delay(uint16 i);
void lcd_writecom(uchar8 com);
void lcd_writedat(uchar8 dat);
void lcdinit();
void lcd_stringwrite(uchar8 com, uchar8 *s);
#endif
xpt2046.c
#include "xpt2046.h"
void SPI_write(u8 dat)
{
u8 i = 0;
CLK = 0;
for(i = 0; i <= 7; i++)
{
DIN = dat>>7;
dat = dat<<1;
CLK = 0;
_nop_();
CLK = 1;
}
}
u16 SPI_read()
{
u8 i = 0;
u16 dat = 0;
for(i = 0; i <= 11; i++)
{
dat = dat<<1;
CLK = 1;
_nop_();
CLK = 0;
dat |= DOUT;
}
return dat;
}
u16 read_ad_data(u8 cmd)
{
u8 i;
u16 value;
CLK = 0;
CS = 0;
SPI_write(cmd);
for(i = 6; i >0; i--);
CLK = 1;
_nop_();
_nop_();
CLK = 0;
_nop_();
_nop_();
value = SPI_read();
CS = 1;
return value;
}
lcd1602.c
#include "lcd1602.h"
void delay(uint16 i)
{
while(i--);
}
#ifndef LCD1602_4PINS
void lcd_writecom(uchar8 com)
{
lcd_RS = 0;
lcd_RW = 0;
lcd_E = 0;
lcd_data = com;
delay(200);
lcd_E = 1;
delay(1000);
lcd_E = 0;
}
#else
void lcd_writecom(uchar8 com)
{
lcd_RS = 0;
lcd_RW = 0;
lcd_E = 0;
lcd_data = com;
delay(200);
lcd_E = 1;
delay(1000);
lcd_E = 0;
lcd_data = com<<4;
delay(200);
lcd_E = 1;
delay(1000);
lcd_E = 0;
}
#endif
#ifndef LCD1602_4PINS
void lcd_writedat(uchar8 dat)
{
lcd_RS = 1;
lcd_RW = 0;
lcd_E = 0;
lcd_data = dat;
delay(200);
lcd_E = 1;
delay(400);
lcd_E = 0;
}
#else
void lcd_writedat(uchar8 dat)
{
lcd_RS = 1;
lcd_RW = 0;
lcd_E = 0;
lcd_data = dat;
delay(200);
lcd_E = 1;
delay(400);
lcd_E = 0;
lcd_data = dat<<4;
delay(200);
lcd_E = 1;
delay(400);
lcd_E = 0;
}
#endif
#ifndef LCD1602_4PINS
void lcdinit()
{
lcd_writecom(0x38);
lcd_writecom(0x0c);
lcd_writecom(0x06);
lcd_writecom(0x01);
}
#else
void lcdinit()
{
lcd_writecom(0x32);
lcd_writecom(0x28);
lcd_writecom(0x0c);
lcd_writecom(0x06);
lcd_writecom(0x01);
}
#endif
void lcd_stringwrite(uchar8 com, uchar8 *s)
{
lcd_writecom(com);
while(*s > 0)
{
lcd_writedat(*s++);
}
}
main.c
#include "reg52.h"
#include "lcd1602.h"
#include "xpt2046.h"
sbit beep = P1^5;
sbit power = P1^0;
sbit key1 = P3^1;
sbit key2 = P3^0;
sbit key3 = P3^2;
uint16 value;
uchar8 token,num;
uchar8 threshold;
uchar8 code str1[] = "GAS:";
uchar8 code str2[] = "SET:";
uchar8 code math[] = "0123456789";
void timecontrol()
{
EA = 1;
ET0 = 1;
TR0 = 0;
TMOD |= 0x01;
TH0 = 0xFC;
TL0 = 0x18;
}
void timecon()
{
EA = 1;
ET1 = 1;
TR1 = 0;
TMOD |= 0x10;
TH1 = 0xFC;
TL1 = 0x18;
}
void timecontrol1()interrupt 1
{
}
void timecon1()interrupt 3
{
uchar8 i;
for(i = 10 ; i > 0; i--)
{
beep =~ beep;
delay(100);
}
}
void dataset()
{
static num = 0;
if(key1 == 0)
{
TR0 = 1;
if(key1 == 0)
{
token = 0;
num = num + 1;
}
while(!key1);
}
if(num == 2)
{
num = 0;
token = 1;
}
if(key2 == 0&&num == 1)
{
TR0 = 1;
if(key2 == 0&&num == 1)
{
threshold = threshold+1;
if(threshold > 200)
{
threshold = 200;
}
}
while(!key2);
}
if(key3 == 0&&num == 1)
{
TR0 = 1;
if(key3 == 0&&num == 1)
{
threshold = threshold - 1;
if(threshold < 0)
{
threshold = 0;
}
}
while(!key3);
}
}
void datadispose()
{
value = read_ad_data(0xE4);
value = (int)(0.236 * (value - 12.8623));
if(value >= threshold&&token == 1)
{
TR1 = 1;
power = 1;
}
else
{
TR1 = 0;
power = 0;
}
}
void datadisplay()
{
uchar8 a, b, c, d, i, j, k, l;
a = value/1000;
b = value%1000/100;
c = value%1000%100/10;
d = value%1000%100%10;
i = threshold/1000;
j = threshold%1000/100;
k = threshold%1000%100/10;
l = threshold%1000%100%10;
lcd_stringwrite(0x80, &str1);
lcd_stringwrite(0xc0, &str2);
lcd_writecom(0x84);
lcd_writedat(math[a]);
lcd_writedat(math[b]);
lcd_writedat(math[c]);
lcd_writedat(math[d]);
lcd_writecom(0xc4);
lcd_writedat(math[i]);
lcd_writedat(math[j]);
lcd_writedat(math[k]);
lcd_writedat(math[l]);
}
void main()
{
beep = 1;
token = 1;
power = 0;
threshold = 80;
timecontrol();
timecon();
lcdinit();
while(1)
{
dataset();
datadisplay();
datadispose();
}
}
5、项目部分成果展示
写在最后:若对本文内容存在质疑或疑惑,欢迎提出指正,一起学习进步!
|