基于51单片机的酒精浓度测试仪设计及实现

酒精浓度测试仪

目录

一、绪论… 2

1.选题背景及现实意义… 2

2、任务内容… 2

二、酒精浓度测试仪系统总体设计… 2

1.需求分析及方案设计… 2

2.系统功能… 3

3.系统基本构成及原理… 3

4.功能说明… 4

三、系统单元设计… 4

1.单片机最小系统… 4

复位电路:… 5

晶振电路:… 5

P0口的上拉电阻:… 6

31脚EA/Vpp接电源:… 7

2.各单元电路简介… 7

1 LCD1602液晶显示电路… 7

2报警电路… 15

3 EEPROM阀值存储模块… 16

4传感器的选择… 20

5键盘电路… 21

四、原理图… 23

五、系统软件设计… 24

1软件的介绍… 24

KEIL软件的介绍:… 24

PROTEUS的介绍:… 24

六、调试… 25

七、参考文献… 26

八、附录… 27

附录1 PROTEUS原理图… 27

附录2. 28

效果图… 28

附录3 程序代码… 28

一、绪论

1.选题背景及现实意义

随着如今的经济快速发展,人们的交通出行越加便利,汽车两也在源源不断的增加,灯红酒绿的现代生活,人们的应酬也是越来越多,公司聚餐,家庭聚会,都离不开喝点小酒,即酒后开车行为现在严重影响到了社会的安全问题。酒后驾车造成的交通事故也变得越来越频繁,可以说酒家已经成为了社会上的马路杀手。从而造成了严重的生命财产损失。驾车前的酒精检测九城为了重要的谈论对象,这样一来酒精测试仪的研究也慢慢越来越被引起人们的重视,并且在当前社会的背景下有着重要意义,于是设计了有酒精传感器和单片机组成的简易酒精测试仪,还包括硬件电路设计,单片机软件设计等..

2、任务内容

原理:当气体传感器探测不到酒精时,加在A的第5管脚电平为低电平,当气体传感器探测到酒精时,其内阻变低,从而使A的5管脚电平变高。A为显示推推器,它有10个输出端,每个输出端可以驱动的发光二极管,显示推动器A根据第5脚电平高低来确定依次点亮发光二极管的级数,酒精含量越高则点亮二级管的级数越大。上面5个发光二极管为红色,表示超过安全水平。下面5个发光二极管为绿色,表示安全水平,酒精含量不超过0.05%。

二、酒精浓度测试仪系统总体设计

1.需求分析及方案设计

在这次设计当中,设计要求都有通过酒精传感器要对几种不同浓度的酒精进行测试,在通过前期资料查询和各种科学的分析最终确定下来测试的浓度实在0-30度,误差率在5%-15%之间,由于酒精传感器特性关系测试的浓度越高,误差率也就越大了,由于模拟量转换过程中使用到单片机自带A/D功能,所以我们需要在液晶先视频上对酒精浓度的显示并且我们规定的范围就会发出报警声。

2.系统功能

主要功能有:

  1. 实时测量空气中的乙醇气体浓度,在液晶屏上显示。
  2. 具有乙醇浓度超高报警功能,当测量得到的乙醇浓度超过预先设定的阈值,则红灯报警。
  3. 电路设计了两个红灯,另一个红灯是传感器直接输出的模拟报警信号,其阈值通过调节传感器模块上的兰色电位器改变,一般可以不用。
  4. 醉酒阈值可存储至EEPROM存储器,并在液晶屏显示。系统掉电后醉酒阈值不失,可以保持。
  5. 醉酒阈值可以通过按键修改并保存。

3.系统基本构成及原理

酒精浓度测试仪的基本构成:

1.由MQ-3乙醇气体传感器

2.STC12C5A16AD(或STC12C5A32AD)单片机

3.EEPROM存储电路

4.液晶屏和键盘组成

5.报警电路

检测原理:

当具有N型导电性的氧化物暴露在大气中时,会由于氧气的吸附而减少其内部的电子数量而使其电阻增大。其后如果大气中存在某种特定的还原性气体,它将与吸附的氧气反应,从而使氧化物内的电子数增加,导致氧化物电阻减小。半导体-氧化物传感器就是通过该阻值的变化来分析气体浓度

4.功能说明

1.本设计基于STC12C5A16AD单片机、LCD1602液晶显示、MQ-3酒精传感器、LM393电压比较器和24c02芯片可以掉电存储数据

2.实时显示当前的酒精值,设计有两路浓度报警,一路为硬件检测报警,通过电位器可以调节报警阀值;另一路为软件检测报警,就是通过单片机采集传感器的信号,通过液晶显示出来,按键可以加减阀值,当检测的酒精浓度值超过设定的阀值时,发出声光报警。软硬件检测安全无误!

三、系统单元设计

1.单片机最小系统

单片机最小系统,或者称为最小应用系统,是指用最少的元件组成的单片机可以工作的系统。对51系列单片机来说,最小系统一般应该包括:单片机、晶振电路、复位电路。

复位电路:

一、复位电路的用途:单片机复位电路就好比电脑的重启部分,当电脑在使用中出现死机,按下重启按钮电脑内部的程序从头开始执行。单片机也一样,当单片机系统在运行中,受到环境干扰出现程序跑飞的时候,按下复位按钮内部的程序自动从头开始执行。单片机复位电路如下图:

二、复位电路的工作原理在书本上有介绍,51单片机要复位只需要在第9引脚接个高电平持续2US就可以实现,那这个过程是如何实现的呢?在单片机系统中,系统上电启动的时候复位一次,当按键按下的时候系统再次复位,如果释放后再按下,系统还会复位。所以可以通过按键的断开和闭合在运行的系统中控制其复位。

开机的时候为什么会复位:在电路图中,电容的的大小是10uF,电阻的大小是10k。所以根据公式,可以算出电容充电到电源电压的0.7倍(单片机的电源是5V,所以充电到0.7倍即为3.5V),需要的时间是10K*10UF=0.1S。也就是说在单片机启动的0.1S内,电容两端的电压时在0~3.5V增加。这个时候10K电阻两端的电压为从5~1.5V减少(串联电路各处电压之和为总电压)。所以在0.1S内,RST引脚所接收到的电压是5V~1.5V。在5V正常工作的51单片机中小于1.5V的电压信号为低电平信号,而大于1.5V的电压信号为高电平信号。所以在开机0.1S内,单片机系统自动复位(RST引脚接收到的高电平信号时间为0.1S左右)。

按键按下的时候为什么会复位:在单片机启动0.1S后,电容C两端的电压持续充电为5V,这是时候10K电阻两端的电压接近于0V,RST处于低电平所以系统正常工作。当按键按下的时候,开关导通,这个时候电容两端形成了一个回路,电容被短路,所以在按键按下的这个过程中,电容开始释放之前充的电量。随着时间的推移,电容的电压在0.1S内,从5V释放到变为了1.5V,甚至更小。根据串联电路电压为各处之和,这个时候10K电阻两端的电压为3.5V,甚至更大,所以RST引脚又接收到高电平。单片机系统自动复位。

晶振电路:

晶振电路:晶振是晶体振荡器的简称 在电气上它可以等效成一个电容和一个电阻并联再串联一个电容的二端网络 电工学上这个网络有两个谐振点 以频率的高低分其中较低的频率是串联谐振 较高的频率是并联谐振 由于晶体自身的特性致使这两个频率的距离相当的接近 在这个极窄的频率范围内 晶振等效为一个电感 所以只要晶振的两端并联上合适的电容它就会组成并联谐振电路 这个并联谐振电路加到一个负反馈电路中就可以构成正弦波振荡电路 由于晶振等效为电感的频率范围很窄 所以即使其他元件的参数变化很大 这个振荡器的频率也不会有很大的变化

晶振有一个重要的参数 那就是负载电容值 选择与负载电容值相等的并联电容 就可以得到晶振标称的谐振频率

一般的晶振振荡电路都是在一个反相放大器(注意是放大器不是反相器)的两端接入晶振 再有两个电容分别接到晶振的两端 每个电容的另一端再接到地 这两个电容串联的容量值就应该等于负载电容 请注意一般IC的引脚都有等效输入电容 这个不能忽略

一般的晶振的负载电容为15pF或12.5pF 如果再考虑元件引脚的等效输入电容 则两个22pF的电容构成晶振的振荡电路就是比较好的选择 

如上图:晶振是给单片机提供工作信号脉冲的 这个脉冲就是单片机的工作速度 比如 12M晶振 单片机工作速度就是每秒12M 当然 单片机的工作频率是有范围的 不能太大 一般24M就不上去了 不然不稳定

晶振与单片机的脚XTAL0和脚XTAL1构成的振荡电路中会产生偕波(也就是不希望存在的其他频率的波) 这个波对电路的影响不大 但会降低电路的时钟振荡器的稳定性 为了电路的稳定性起见 ATMEL公司只是建议在晶振的两引脚处接入两个10pf-50pf的瓷片电容接地来削减偕波对电路的稳定性的影响 所以晶振所配的电容在10pf-50pf之间都可以的 没有什么计算公式

P0口的上拉电阻:

P0口作为I/O口输出的时候时 输出低电平为0 输出高电平为高组态(并非5V,相当于

悬空状态)。也就是说P0 口不能真正的输出高电平,给所接的负载提供电流,因此必须接上拉电阻(一电阻连接到VCC),由电源通过这个上拉电阻给负载提供电流。 由于P0口内部没有上拉电阻,是开漏的,不管它的驱动能力多大,相当于它是没有电源的,需要外部的电路提供,绝大多数情况下P0口是必需加上拉电阻的。 

1.一般51单片机的P0口在作为地址/数据复用时不接上拉电阻。  

2.作为一般的I/O口时用时,由于内部没有上拉电阻,故要接上上拉电阻!! 

3.当p0口用来驱动PNP管子的时候,就不需要上拉电阻,因为此时的低电平有效; 4.当P0口用来驱动NPN管子的时候,就需要上拉电阻的,因为此时只有当P0为1时候,才能够使后级端导通。

31脚EA/Vpp接电源:

STC89C51/52或其他51系列兼容单片机特别注意:对于31脚(EA/Vpp),当接高电平时,单片机在复位后从内部ROM的0000H开始执行,当接低电平时,复位后直接从外部ROM的0000H开始执行,这一点是初学者容易忽略的。

2.各单元电路简介

1 LCD1602液晶显示电路

LCD1602A 是一种工业字符型液晶,能够同时显示16×02 即32个字符。(16列2行)。在日常生活中,我们对液晶显示器并不陌生。液晶显示模块已作为很多电子产品的通过器件,如在计算器、万用表、电子表及很多家用电子产品中都可以看到,显示的主要是数字、专用符号和图形。LCD1602液晶显示器是广泛使用的一种字符型液晶显示模块。它是由字符型液晶显示屏(LCD)、控制驱动主电路HD44780及其扩展驱动电路HD44100,以及少量电阻、电容元件和结构件等装配在PCB板上而组成。不同厂家生产的LCD1602芯片可能有所不同,但使用方法都是一样的。为了降低成本,绝大多数制造商都直接将裸片做到板子上。

原理:点阵图形式液晶由M×N个显示单元组成,假设LCD显示屏有64行,每行有128列,每8列对应1字节的8位,即每行由16字节,共16×8=128个点组成。显示屏上64×16个显示单元与显示RAM区的1024字节相对应,每一字节的内容与显示屏上相应位置的亮暗对应。例如显示屏第一行的亮暗由RAM区的000H~00FH的16字节的内容决定,当(000H)=FFH时,屏幕左上角显示一条短亮线,长度为8个点;当(3FFH)=FFH时,屏幕右下角显示一条短亮线;当(000H)=FFH,(001H)=00H,(002H)=00H…,(00EH)=00H,(00FH)=00H时,在屏幕的顶部显示一条由8条亮线和8条暗线组成的虚线。这就是LCD显示的基本原理。

LCD1602的基本操作

1. 读状态:输入RS=0,RW=1,E=高脉冲。输出:D0—D7为状态字

2. 读数据:输入RS=1,RW=1,E=高脉冲。输出:D0—D7为数据。 

3. 写命令:输入RS=0,RW=0,E=低脉冲。输出:无。(写完置E=高脉冲)

4. 写数据:输入RS=1,RW=0,E=低脉冲。输出:无。

LCD的PROTEUS图

引脚的使用情况

1602LCD是指显示的内容为16X2,每行16个字符液晶模块,共有16个引脚:

第1引脚:GND为电源地;

第2引脚:VCC接5V电源正极;

第3引脚:V0为液晶显示器对比度调整端,接正电源时对比度最弱,接地电源时对比度最高;

第4引脚:RS为寄存器选择,高电平1时选择数据寄存器、低电平0时选择指令寄存器;

第5引脚:RW为读写信号线,高电平(1)时进行读操作,低电平(0)时进行写操作;

第6引脚:E(或EN)端为使能(enable)端,高电平(1)时读取信息,负跳变时执行指令;

第7~14引脚:D0~D7为8位双向数据端;

第15~16脚:空脚或背灯电源。

程序如下:

/********************************************************************

* 文件名  : 液晶1602显示.c

* 描述    :  该程序实现了对液晶1602的控制。

***********************************************************************/

#include "1602.h"

#include "math.h"







/********************************************************************

* 名称 : delay()

* 功能 : 延时,延时时间大概为140US。

* 输入 : 无

* 输出 : 无

***********************************************************************/



void delay()

{

    int i,j;

    for(i=0; i<=10; i++)

    for(j=0; j<=2; j++)

;

}



/********************************************************************

* 名称 : Convert(uchar In_Date)

* 功能 : 因为电路设计时,P0.0--P0.7接法刚好了资料中的相反,所以设计该函数。

* 输入 : 1602资料上的值

* 输出 : 送到1602的值

***********************************************************************/

uchar Convert(uchar In_Date)

{

    /*

    uchar i, Out_Date = 0, temp = 0;

    for(i=0; i<8; i++)

    {

        temp = (In_Date >> i) & 0x01;

        Out_Date |= (temp << (7 - i));

    }

   

    return Out_Date;

    */

    return In_Date;



}





/********************************************************************

* 名称 : enable(uchar del)

* 功能 : 1602命令函数

* 输入 : 输入的命令值

* 输出 : 无

***********************************************************************/



void enable(uchar del)

{

    P0 = Convert(del);

    RS = 0;

    RW = 0;

    E = 0;

    delay();

    E = 1;

    delay();

}



/********************************************************************

* 名称 : write(uchar del)

* 功能 : 1602写数据函数

* 输入 : 需要写入1602的数据

* 输出 : 无

***********************************************************************/



void write(uchar del)

{

    P0 = Convert(del);

    RS = 1;

    RW = 0;

    E = 0;

    delay();

    E = 1;

    delay();

}



/********************************************************************

* 名称 : L1602_init()

* 功能 : 1602初始化,请参考1602的资料

* 输入 : 无

* 输出 : 无

***********************************************************************/

void L1602_init(void)

{

    enable(0x01);

    enable(0x38);

    enable(0x0c);

    enable(0x06);

    enable(0xd0);

}



/********************************************************************

* 名称 : L1602_char(uchar hang,uchar lie,char sign)

* 功能 : 改变液晶中某位的值,如果要让第一行,第五个字符显示"b" ,调用该函数如下

        L1602_char(1,5,'b')

* 输入 : 行,列,需要输入1602的数据

* 输出 : 无

***********************************************************************/

void L1602_char(uchar hang,uchar lie,char sign)

{

    uchar a;

    if(hang == 1) a = 0x80;

    if(hang == 2) a = 0xc0;

    a = a + lie - 1;

    enable(a);

    write(sign);

}



/********************************************************************

* 名称 : L1602_string(uchar hang,uchar lie,uchar *p)

* 功能 : 改变液晶中某位的值,如果要让第一行,第五个字符开始显示"ab cd ef" ,调用该函数如下

         L1602_string(1,5,"ab cd ef;")

* 输入 : 行,列,需要输入1602的数据

* 输出 : 无

***********************************************************************/

void L1602_string(uchar hang,uchar lie,uchar *p)

{

    uchar a;

    if(hang == 1) a = 0x80;

    if(hang == 2) a = 0xc0;

    a = a + lie - 1;

    enable(a);

    while(1)

    {

       if(*p == '\0') break;

       write(*p);

       p++;

    }

}



//显示整型的温湿度数据用,共占用4位,其中一位符号位

void L1602_int(uchar hang, uchar lie, int num)

{

   uint temp;

   uint gewei,shiwei,baiwei,sign;

   //首先将4位清空

   L1602_char(hang, lie+0, ' '); 

   L1602_char(hang, lie+1, ' '); 

   L1602_char(hang, lie+2, ' '); 

   L1602_char(hang, lie+3, ' '); 



   if (num >= 0)

   {

      sign = 0;

   }

   else

   {

      sign = 1;

   }

   temp = abs(num);

   baiwei = temp / 100;

   temp = temp - baiwei*100;

   shiwei = temp / 10;

   gewei = temp - shiwei*10;

   num = abs(num);

   if (num>=100)

   {

      if (sign == 1) //负数

      {

         L1602_char(hang, lie, '-');

      }

      L1602_char(hang, lie+1, baiwei+48); 

      L1602_char(hang, lie+2, shiwei+48); 

      L1602_char(hang, lie+3, gewei+48); 

   }

   else if (num>=10)

   {

      if (sign == 1)   

      {

         L1602_char(hang, lie+1, '-');

      }

      L1602_char(hang, lie+2, shiwei+48); 

      L1602_char(hang, lie+3, gewei+48);

   }

   else

   {

      if (sign == 1)   

      {

         L1602_char(hang, lie+2, '-');

      }

      L1602_char(hang, lie+3, gewei+48); 

   }

}

2报警电路

本论文采用led作为报警的标志,LED(LightingEmittingDiode)照明即是发光二极管照明,是一种半导体固体发光器件。它是利用固体半导体芯片作为发光材料,在半导体中通过载流子发生复合放出过剩的能量而引起光子发射,直接发出红、黄、蓝、绿色的光,在此基础上,利用三基色原理,添加荧光粉,可以发出任意颜色的光。利用LED作为光源制造出来的照明器具就是LED灯具。LED照明灯具里,反射用途的LED照明灯具可以完全胜任于任何场合,大面积室内照明还不成熟。

3 EEPROM阀值存储模块

AT24C02是一个2K位串行CMOS E2PROM, 内部含有256个8位字节,CATALYST公司的先进CMOS技术实质上减少了器件的功耗。AT24C02有一个8字节页写缓冲器。该器件通过IIC总线接口进行操作,有一个专门的写保护功能。

极限参数:

工作温度工业级-55℃ +125℃

商业级0 ℃+75℃

贮存温度-65℃ +150℃

各管脚承受电压-2.0 Vcc+2.0V

Vcc管脚承受电压-2.0 +7.0V

封装功率损耗(Ta=25℃) 1.0W

焊接温度(10 秒) 300℃

输出短路电流100mA

管脚配置

管脚封装如右图1所示。

图1 管脚封装

(DIP:双列直插式封装,是最简单的一种封装技术。)

管脚名称

功能

A0、A1、A2

器件地址选择

 SDA

串行数据、地址

SCL

串行时钟

WP

写保护

VCC

+1.8V~6.0V工作电压

VSS

程序如下

#include "2402.h"



/*********************************

      BASE  DRIVE

**********************************/

void DELAY(unsigned int t)

{

   while(t!=0)

   t--;

}



/******************************

      IIC  DRIVE

******************************/

void IICStart(void)

{

     SCL=0; 

    DELAY(1);

     SDA=1; SCL=1; DELAY(1);

     SDA=0; DELAY(1);

     SCL=0;

}



void IICStop(void)

{

    SDA=0;SCL=1; DELAY(1);

     SDA=1; DELAY(1);

     SCL=0;  

}

void SEND0(void)

{

   SDA=0;

   SCL=1;

   DELAY(1);

   SCL=0;

}



void SEND1(void)

{

   SDA=1;

   DELAY(1);

   SCL=1;

   DELAY(1);

   SCL=0;

}



bit Check_Ack(void)

{

   unsigned char errtime=250;

   DELAY(1);

   SCL=1;

   DELAY(1);

   CY=SDA;

   while(CY)

   {

    errtime--;

    CY=SDA;

    if (!errtime)

     {

     IICStop();

   

     return 1;

     }

}

 DELAY(1);

 SCL=0;

 return 0;

}





void Write_byte(unsigned char dat)

{

    unsigned char i;

   for(i=0;i<8;i++)

   {  

       if((dat<<i)&0x80)

       SEND1();

       else

       SEND0();

   }

}





unsigned char Read_byte(void)

{

   unsigned char i,temp=0;    

   for(i=0;i<8;i++)

   {

        SDA=1;

       SCL=1;

       DELAY(1); 

       if(SDA==1)

       {

          temp=temp<<1;

          temp=temp|0x01;

       }

       else

          temp=temp<<1;

        SCL=0;   

     }

     return temp;

}



/************************************

      EEPROM  DRIVE

     Addr:from 0x00-->

*************************************/

unsigned char rdeeprom(unsigned char addr)

{

        unsigned char temp=0;

       bit flag=0;

       IICStart();

       Write_byte(0xa0);

        Check_Ack();

       Write_byte(addr);

       Check_Ack();

       IICStart();

       Write_byte(0xa1);

       Check_Ack();

       temp=Read_byte();

       SEND1();

       IICStop();

       return temp;  

} 

   

  

void wrteeprom(unsigned char addr,unsigned char dat)

{

          IICStart();

       Write_byte(0xa0);

       Check_Ack();

       Write_byte(addr);

       Check_Ack();

       Write_byte(dat);

       Check_Ack();

       IICStop();

} 

4传感器的选择

传感器选择MQ3,MQ-3气体传感器所使用的气敏材料是在清洁空气中电导率较低的二氧化锡(SnO2)。当传感器所处环境中存在酒精蒸汽时,传感器的电导率随空气中酒精气体浓度的增加而增大。使用简单的电路即可将电导率的变化转换为与该气体浓度相对应的输出信号。MQ-3气体传感器对酒精的灵敏度高,可以抵抗汽油、烟雾、水蒸气的干扰。这种传感器可检测多种浓度酒精气氛,是一款适合多种应用的低成本传感器。

MQ-3传感器模块具有的特点:

  1. 具有信号输出指示
  2. 双路信号输出(模拟量输出及TTL电平输出)
  3. 模拟量输出0-5V电压,浓度越高电压越高
  4. 对乙醇蒸汽具有良好的选择性和很高的灵敏度
  5. 具有长期的使用寿命和可靠的稳定性
  6. 快速的响应恢复特性
  7. MQ-3标准工作条件表

表3-1 MQ-3标准工作条件表

符号

参数名称

技术条件

备注

  Vc

回路电压

≤15V

AC or DC

  VH

加热电压

5.0V±0.2V

AC or DC

  RL

负载电阻

可调

  RH

加热电阻

31Ω±3Ω

室温

  PH

加热功耗

≤900mW

MQ3的PROTEUS图:

  

5键盘电路

本设计采用按键接低的方式来读取按键,单片机初始时,因为为高电平,当按键按下的时候,会给单片机一个低电平,单片机对信号进行处理

单片机键盘有独立键盘和矩阵式键盘两种:独立键盘每一个I/O 口上只接一个按键,按键的另一端接电源或接地(一般接地),这种接法程序比较简单且系统更加稳定;而矩阵式键盘式接法程序比较复杂,但是占用的I/O少。根据本设计的需要这里选用了独立式键盘接法。

独立式键盘的实现方法是利用单片机I/O口读取口的电平高低来判断是否有键按下。将常开按键的一端接地,另一端接一个I/O 口,程序开始时将此I/O口置于高电平,平时无键按下时I/O口保护高电平。当有键按下时,此I/O 口与地短路迫使I/O 口为低电平。按键释放后,单片机内部的上拉电阻使I/O口仍然保持高电平。我们所要做的就是在程序中查寻此I/O口的电平状态就可以了解我们是否有按键动作了。

在用单片机对键盘处理的时候涉及到了一个重要的过程,那就是键盘的去抖动。这里说的抖动是机械的抖动,是当键盘在未按到按下的临界区产生的电平不稳定正常现象,并不是我们在按键时通过注意可以避免的。这种抖动一般10~200毫秒之间,这种不稳定电平的抖动时间对于人来说太快了,而对于时钟是微秒的单片机而言则是慢长的。硬件去抖动就是用部分电路对抖动部分加之处理,软件去抖动不是去掉抖动,而是避抖动部分的时间,等键盘稳定了再对其处理。所以这里选择了软件去抖动,实现法是先查寻按键当有低电平出现时立即延时10~200毫秒以避开抖动(经典值为20毫秒),延时结束后再读一次I/O 口的值,这一次的值如果为1 表示低电平的时间不到10~200 毫秒,视为干扰信号。当读出的值是0时则表示有按键按下,调用相应的处理程序。

按键的proteus图:

四、原理图

五、系统软件设计

1软件的介绍

KEIL软件的介绍:

Keil C51是美国Keil Software公司出品的51系列兼容单片机C语言软件开发系统,提供了包括C编译器、宏汇编、链接器、库管理和一个功能强大的仿真调试器等在内的完整开发方案,通过一个集成开发环境(μVision)将这些部分组合在一起。

即使不使用C语言而仅用汇编语言编程,它方便易用的集成环境、强大的软件仿真调试工具也会令你事半功倍。

keil优点

  • Keil C51生成的目标代码效率非常之高,多数语句生成的汇编代码很紧凑,容易理解。在开发大型软件时更能体现高级语言的优势。
  • ⒉与汇编相比,C语言在功能上、结构性、可读性、可维护性上有明显的优势,因而易学易用。用过汇编语言后再使用C来开发,体会更加深刻。
  • PROTEUS的介绍:

    Proteus软件是英国Lab Center Electronics公司出版的EDA工具软件(该软件中国总代理为广州风标电子技术有限公司)。它不仅具有其它EDA工具软件的仿真功能,还能仿真单片机及外围器件。它是比较好的仿真单片机及外围器件的工具。虽然国内推广刚起步,但已受到单片机爱好者、从事单片机教学的教师、致力于单片机开发应用的科技工作者的青睐。

    Proteus是英国著名的EDA工具(仿真软件),从原理图布图、代码调试到单片机与外围电路协同仿真,一键切换到PCB设计,真正实现了从概念到产品的完整设计。是世界上唯一将电路仿真软件、PCB设计软件和虚拟模型仿真软件三合一的设计平台,其处理器模型支持8051、HC11、PIC10/12/16/18/24/30/DSPIC33、AVR、ARM8086和MSP430等,2010年又增加了CortexDSP系列处理器,并持续增加其他系列处理器模型。在编译方面,它也支持IAR、Keil和MATLAB等多种编译器

    Proteus的工作画面:

    六、调试

    安装完成后,酒精浓度测试仪上电。传感器第一次上电预热时间比较长,需等待预热完成。

    当预热完成后,液晶屏显示空气中测得的乙醇气体浓度,因为是清洁空气,所以浓度比较低,没有达到事先设定的报警阈值(80mg/L),故报警灯不亮。

    然后做酒精瓶的测试。酒精瓶对酒精浓度测试仪,把MQ3放在酒精瓶口,液晶屏显示的乙醇气体浓度值开始变化,会超过报警阈值,报警灯亮。

    可以通过按键调整报警阈值。按“上升”键增大报警阈值,按“减小”键降低报警阈值,调整后的阈值保存在EEPROM芯片AT24C02中,系统重新上电时能保持原来设定好的阈值。

    调试说明:

    1、线路连接好后,不要急于上电,首先用万用表电阻档量一下电路板的GND和5V电源之间是否短路。短路切不可上电。

    2、如果不短路,再接上USB电源,用万用表电压档测量5V和GND之间的电压是否正常,如果电压不正常,速断电检查。

    3、一切正常,可以下载程序了。打开STC下载软件,选择芯片型号,导入hex文件,设置波特率(两项都低于4800bps),下载程序,调试运行。

    4、新传感器第一次上电有一个老化过程,数据会抖动,此时不去管它,上电一断时间后关电,再重新上电即可正确测量。

    5、直接将传感器放在酒精瓶口就行了。

    七、参考文献

    [1]李庆亮.C语言程序设计实用教程[M].北京:机械工业出版社,2010.3

    [2]杨志忠.数字电子技术[M].北京:高等教育出版社,2008.12.

    [3]及力.Protel 99 SE原理图与PCB设计教程[M].北京:电子工业出版社,2009.8.

    [4]徐江海.单片机实用教程[M].北京:机械工业出版社,2012.12

    八、附录

    附录1 PROTEUS原理图

    附录2

     效果图

    附录3 程序代码

    //#include "reg52.H"
    
    #include "STC12c5A.h"
    
    #include "1602.h"
    
    #include "2402.h"
    
    
    
    #define uchar unsigned char
    
    #define uint  unsigned int
    
    
    
    //声明常量
    
    #define ALCH       80       //醉驾标准80mg/L
    
    //K_MG_MV和K_ZERO为传感器校准系数,要根据每个MQ-3模块校准
    
    #define K_MG_MV    160/66  //传感器灵敏度系数,每毫克/L对应的10毫伏数
    
    #define K_ZERO     15       //传感器零点漂移,约130mV
    
    
    
    //定义按键
    
    sbit Key_Up = P3^6;
    
    sbit Key_Down = P3^7;
    
    
    
    //定义LED报警灯
    
    sbit Led_Warn1 = P3^4;
    
    sbit Led_Warn2 = P3^5;
    
    
    
    //定义乙醇传感器TTL电平输出引脚
    
    sbit DOUT = P1^4;
    
    
    
    //定义标识
    
    volatile bit FlagStartAL = 0;  //开始转换标志
    
    volatile bit FlagKeyPress = 0; //有键弹起标志
    
    
    
    //全局变量定义
    
    uchar Threshold;             //酒精浓度上限报警值
    
    uint  ALCounter;                //酒精转换计时器
    
    long   ALValue;                   //酒精测量值
    
    float ALtemp;                    //计算临时变量
    
    
    
    uint keyvalue, keyUp, keyDown; //键值
    
    char * pSave;                  //EEPROM存盘用指针
    
    
    
    //函数声明
    
    void Data_Init();
    
    void Timer0_Init();
    
    void Port_Init();
    
    void ADC_Init();
    
    uchar GetADVal();
    
    void KeyProcess(uint );
    
    
    
    //数据初始化
    
    void Data_Init()
    
    {
    
       ALCounter = 0;
    
       ALValue = 0;
    
       Led_Warn1 = 1;
    
       Led_Warn2 = 2;
    
       keyvalue = 0;
    
       keyUp = 1;
    
       keyDown = 1;
    
    }
    
    
    
    //定时器0初始化,中断时间约2毫秒
    
    //计算:晶振11.0592MHz,定时器时钟11059200/12=921600,每毫秒922个脉冲
    
    //      16位定时器初值65536-1844=63692=0xf8cc
    
    void Timer0_Init()
    
    {
    
        ET0 = 1;        //允许定时器0中断
    
        TMOD = 1;       //定时器工作方式选择
    
        TL0 = 0xcc;     //
    
        TH0 = 0xf8;     //定时器赋予初值,大约为2毫秒中断1次
    
        TR0 = 1;        //启动定时器
    
    }
    
    
    
    //定时器0中断
    
    void Timer0_ISR (void) interrupt 1 using 0
    
    {
    
        TL0 = 0xcc;
    
        TH0 = 0xf8;     //定时器赋予初值
    
    
    
        //每1秒钟启动一次AD转换
    
        ALCounter ++;
    
        if (ALCounter >= 500)
    
        {
    
           FlagStartAL = 1;
    
           ALCounter = 0;
    
        }
    
    }
    
    
    
    void Port_Init()
    
    {
    
       P1M0 = 0x80;     //10000000,P1.7作为AD输入
    
       P1M1 = 0x80;     //
    
    }
    
    
    
    void ADC_Init()
    
    {
    
       uint i;
    
       P1ASF = 0x80;             //设P1.7为AD输入
    
       ADC_RES = 0;              //清先前的结果
    
       ADC_CONTR|=0x80;           //POWER=1,打开ADC电源
    
       for(i=5000;i>0;i--) ; //延时
    
       ADC_CONTR = ADC_CONTR&0xE0; //1110,0000 清ADC_FLAG,ADC_START位和低3位
    
       ADC_CONTR = ADC_CONTR&0xf8|0x07; //设置当前通道号为P1.7
    
       for(i=2500;i>0;i--) ; //延时
    
    
    
    }
    
    
    
    //进行AD转换,得到当前酒精值
    
    uchar GetADVal()
    
    {
    
       uint i;
    
       ADC_CONTR&=0xf7;
    
       for(i=250;i>0;i--);         //待输入电压稳定后开始转换
    
       //ADC_RES = 0;
    
       ADC_CONTR |= 0x08;          //ADC_Start=1, 启动转换
    
       while((ADC_CONTR&0x10)==0); //等待转换结束ADC_FLAG=1
    
       ADC_CONTR&=0xe7;             //清ADC_FLAG和ADC_START位,停止转换
    
       return ADC_RES;
    
    }
    
    
    
    //存入设定值
    
    void Save_Setting()
    
    {
    
       pSave =  (char *)&Threshold;   //地址低位对应低8位,高位对应高8位
    
       wrteeprom(0, *pSave);       //存醉酒阈值低8位
    
       DELAY(300);
    
       pSave ++;
    
       wrteeprom(1, *pSave);       //存醉酒阈值高8位
    
       DELAY(300);
    
    }
    
    
    
    //载入设定值
    
    void Load_Setting()
    
    {
    
       pSave =  (char *)&Threshold;
    
       *pSave++ = rdeeprom(0);
    
       *pSave = rdeeprom(1);
    
       if ((Threshold>=255)||(Threshold<0)) Threshold = 80;
    
    }
    
    
    
    //按键处理程序,参数为键值,1为Up键,2为Down键
    
    void KeyProcess(uint num)
    
    {
    
       switch (num)
    
       {
    
          case 1:
    
             if (Threshold<255) Threshold++;
    
             break;
    
          case 2:
    
             if (Threshold>1) Threshold--;
    
             break;
    
          default:
    
             break;
    
       }
    
       L1602_int(2,9,Threshold);
    
       Save_Setting();
    
    }
    
    
    
    void main()
    
    {
    
       uint i,j;
    
    
    
       EA = 0;
    
       Data_Init();     //数据初始化
    
       Timer0_Init();   //定时器0初始化
    
       Port_Init();     //端口初始化
    
       ADC_Init();      //ADC初始化
    
       EA = 1;
    
       L1602_init();
    
       L1602_string(1,1,"Welcome to ALCT! ");
    
       L1602_string(2,1,"Designed by AAA  ");
    
       //延时
    
       for (i=0;i<1000;i++)
    
          for (j=0;j<1000;j++)
    
             {;}
    
       //清屏
    
       L1602_string(1,1,"                ");
    
       L1602_string(2,1,"                ");
    
       L1602_string(1,1,"Alcohol:    mg/L");
    
       L1602_string(2,1,"Thresho:    mg/L");
    
       //载入设定值
    
       Load_Setting();
    
       L1602_int(2,9,Threshold);
    
       while(1)
    
       {
    
          //如果FlagStartAL标志置位,则进行AD转换
    
          if (FlagStartAL == 1)
    
          {
    
             //酒精浓度换算,50mg/L=62.5ppm,传感器灵敏度应事先校准
    
             ALValue = 500 * GetADVal() / 256;        //8位ADC,首先得到电压值,单位10毫伏
    
             ALValue = ALValue - K_ZERO;              //首先减去零点漂移,一般是130mV
    
            if (ALValue < 0) ALValue = 0;          
    
            ALValue = ALValue * K_MG_MV;             //将mV转变成mg/L,K_MG_MV系数需要校准          
    
             L1602_int(1,9,ALValue);
    
            if (ALValue > Threshold) Led_Warn1 = 0;  //超过阈值,则 Led_Warn1灯报警,否则报警灯灭。
    
            else Led_Warn1 = 1;
    
            FlagStartAL = 0;
    
           }
    
           //查询乙醇传感器TTL电平,该指示灯为传感器模块报警
    
           if (DOUT == 0)  Led_Warn2 = 0;
    
           else            Led_Warn2 = 1;
    
           //键盘查询,在弹起时响应
    
           if ((Key_Up)&&(keyUp==0)) {FlagKeyPress = 1; keyvalue = 1;}
    
           else if ((Key_Down)&&(keyDown==0)) {FlagKeyPress = 1; keyvalue = 2;}
    
           if (FlagKeyPress == 1)
    
           {
    
              KeyProcess(keyvalue);
    
              FlagKeyPress = 0;          
    
           }
    
           if (!Key_Up) keyUp = 0;
    
           else keyUp = 1;
    
           if (!Key_Down) keyDown = 0;
    
           else keyDown = 1;
    
           DELAY(100);
    
       }
    
    }
    
     

    物联沃分享整理
    物联沃-IOTWORD物联网 » 基于51单片机的酒精浓度测试仪设计及实现

    发表评论