基于ATMEGA16单片机,MQ-3酒精传感器,LCD1602液晶显示的酒精浓度检测阈值报警仪

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

本文章为个人单片机课程大作业,代码都是自己写的,只是记录自己的学习过程,不做任何商用。

目录

单片机大作业:

一、使用仪器、材料

二、步骤

1.接线

2.写代码

main.c:

ADC.h:

key.h:

1602.H:

3.烧录

三、实验结果

四、总结


单片机大作业:

基于ATMEGA16单片机,MQ-3酒精传感器,LCD1602液晶显示的酒精浓度检测阈值报警仪
功能:
1.将MQ-3传感器模块靠近酒精气体,LCD1602显示屏上显示酒精数值Alcohol  mg/L;
2.按键s1设置阈值Set加一,按键s2调节阈值Set减一,按键s3调节阈值Set加十,按键s4调节阈值Set减十;
3.当MQ-3测得酒精浓度数值大于等于阈值(Set)时,蜂鸣器持续响;当MQ-3测得酒精浓度数值小于阈值(Set)时,蜂鸣器停止响;
4.按键每按一下,阈值(Set)改变的同时蜂鸣器响一声。

一、使用仪器、材料

硬件:ATMega16单片机,LCD1602液晶显示,MQ-3酒精传感器,蜂鸣器,杜邦线若干

软件:ICCV7 for AVR(用于编译),Visual Studio Code(用于写代码),progisp.exe (用于烧录)

二、步骤

1.接线

将ATMega16单片机,MQ-3传感器,lcd1602,蜂鸣器用杜邦线连接好;

接线方式:

ATMEGA16 PA0 ---- MQ-3 A0

ATMEGA16 VCC ---- MQ-3 VCC

ATMEGA16 GND ---- MQ-3 GND

备注:MQ-3 D0 未接线(可以不接)

ATMEGA16 ---- LCD1602

ATMEGA16 PD4 ---- 蜂鸣器 I/O

ATMEGA16 VCC ---- 蜂鸣器 VCC

ATMEGA16 GND ---- 蜂鸣器 GND

2.写代码

代码包含.c文件main.c,头文件ADC.h,key.h, 1602.H,把他们放到一个project里面,选择单片机为ATMega16,编译,生成hex文件。

main.c:

#include <iom16v.h>
#include <macros.h>
#include "ADC.h"
#include "math.h"
#include "key.h"
void io_init(void)//IO口初始化
{
    DDRC = 0xFF; //LCD端口输出
    PORTC = 0xff;

    DDRD = 0xf0;  //输出(1111 0000),PD0,PD1,PD2三位控制LCD,PD4控制蜂鸣器
    PORTD = 0xFF; //输出高电平

    DDRB = 0x00; //按钮输入口(1110 0000)
    PORTB = 0xFF;

    PORTA = 0XFF; //设置AD采集端口ADC为输入,本项目用PA0作为ADC输入
    DDRA = 0X00;
}

void DisplayAlcohol(uchar Line)//显示MQ3酒精浓度
{
  float Vrl=0;
  Vrl=(adc_data*5.0)/1024.0;//将PA0模拟输出转换为电压值
  adc_data=pow((11.5428*35.904*Vrl)/(25.5-5.1*Vrl),0.6549);//ppm即计算后的adc_data
  ten_4 = (adc_data) / 1000;
  ten_3 = (adc_data) / 100%10;
  ten_2 = (adc_data) / 10%10;
  ten_1 = adc_data%10;
  ten_1 += 0x30;//转换成这个数字对应的ASCII码,作用跟  ten_1 += '0'  相同
  ten_2 += 0x30;
  ten_3 += 0x30;
  ten_4 += 0x30;
  WriteNum(Line, 8, (ten_4));//在第Line行,第八列lcd开始显示adc_data的千位数
  WriteNum(Line, 9, (ten_3));
  WriteNum(Line, 10, (ten_2));
  WriteNum(Line, 11, (ten_1));
}

void DisplaySetAlcohol(uchar Line)//显示设定酒精(阈值)浓度
{
  ten_4 = (SetAlcohol) / 1000;
  ten_3 = (SetAlcohol) / 100%10;
  ten_2 = (SetAlcohol) / 10%10;
  ten_1 = SetAlcohol%10;
  ten_1 += 0x30;
  ten_2 += 0x30;
  ten_3 += 0x30;
  ten_4 += 0x30;
  WriteNum(Line, 8, (ten_4));
  WriteNum(Line, 9, (ten_3));
  WriteNum(Line, 10, (ten_2));
  WriteNum(Line, 11, (ten_1));
}

void main(void)//主函数
{
  uint ad1,ad2;
  io_init();//io初始化
  LcdInit();//lcd初始化
  WriteChar(1, 0, 16, Alcohol);//显示数组ALcohol,即显示lcd第一行的  Alcohol:    mg/L
  WriteChar(2, 0, 16, Set);
  while (1)
  {
    key_scan();//按键扫描函数
    get_AD();//ADC初始化函数
	  adc_data=ad_cat();
    DisplayAlcohol(1);//将测得的酒精浓度数值显示在lcd第一行
    delay(100);
    DisplaySetAlcohol(2);//将酒精浓度阈值数值显示在lcd第二行
    delay(100);
    if(adc_data>=SetAlcohol)//测得酒精浓度大于阈值
    {
    //delay(100);
    PORTD &= ~(1 << PD4); //(1110 1111),低电平蜂鸣器响
    }
    if(adc_data<SetAlcohol)
    {
    PORTD |= BIT(4); //(1111 1111),高电平蜂鸣器关
    }
  }
}

ADC.h:

#include "1602.H"
#define uchar unsigned char
#define uint unsigned int 
uchar Alcohol[] = {"Alcohol:    mg/L"};//lcd第一行要显示的数组
uchar Set[] = {"Set    :    mg/L"};//lcd第二行要显示的数组
uchar ten_1, ten_2, ten_3,ten_4;//个十百千位要显示的数字
uint adc_data, adc_l, adc_h;
void get_AD(void)//ADC初始化
{
  ADMUX = 0x40;//ADC0,
  ADCSRA = 0x00;//关掉ADC
  ADCSRA = (1 << ADEN) | (1 << ADSC) | (1 << ADFR) | 0x07;//128分频,连续转换
  s_ms(500);//延时
  adc_l = ADCL;
  adc_h = ADCH;
  adc_data = adc_h << 8 | adc_l;
  adc_data = adc_data >> 1;
  adc_data -= 35;
}

unsigned int ad_cat(void)//电压采集函数
{
     unsigned int t1,t2;
	  ADCSRA = 0X00;//disable ADC
	  ADMUX=0x00;//ref  左对齐  ADC0
	  ACSR = 0x80;//使能ADC可用,不用修改
  	ADCSRA|=BIT(ADEN); //ADC使能          
	  ADCSRA|=BIT(ADSC); //开始转换          
	  while(!(ADCSRA&(BIT(ADIF))));//ADIF置一,adc转换结束时,跳出循环
     ADCSRA&=~BIT(ADIF);//清零
     t1 = (unsigned int)ADCL;
     t2 = (unsigned int)ADCH;
     t2 = (t2<<8)+t1;//高位左移8位加上低位
     return t2;
}   

key.h:

unsigned int i=0,SetAlcohol=400;
void delay(uint time)//延时函数
{
 	 uint i,j;
	 for(i=0;i<254;i++)
	 {
	  	for(j=0;j<time;j++)
		{
		 ;
		}
	}
}

void key_scan(void)//按键函数,实现按键每按一下阈值变化的同时蜂鸣器响一声
{

	if((PINB&(1<<PB0))==0)     //按键PB0,s1
	{
        if(SetAlcohol<1666)
        {
        SetAlcohol++;
        } 
        delay(500);
        PORTD &= ~(1 << PD4);
    }
    if((PINB&(1<<PB1))==0)     //按键PB1,s2
	{
        if(SetAlcohol>0)
        {
        SetAlcohol--;
        }
        delay(500);
        PORTD&=~(1<<PD4);
    }
    if((PINB&(1<<PB2))==0)     //按键PB2,s3
	{
        if(SetAlcohol<1666)
        {
        SetAlcohol+=10;
        } 
        delay(500);
        PORTD&=~(1<<PD4);
    }    
    if((PINB&(1<<PB3))==0)     //按键PB3,s4
	{
        
        if(SetAlcohol>=10)
        {
        SetAlcohol-=10;
        }
        delay(500);
        PORTD&=~(1<<PD4);
    }
}

1602.H:

#define uchar unsigned char 
#define uint unsigned int 

#define RS 7
#define RW 6
#define EN 5

void s_ms(uint ms)
{
	for(;ms>1;ms--);
}	

//查忙
void busy(void)
{
    uchar temp;
	s_ms(500);
	PORTD&=~(1<<RS);    //RS=7
	s_ms(500);
	PORTD|=(1<<RW);     //RW=6
	s_ms(500);
	while(temp)
	{
		PORTD|=(1<<EN); //EN=5
		s_ms(500);
		DDRC=0x00;      //A口变输入
		PORTC=0xff;     //上拉使能
		s_ms(500);
		temp = PINC&0x80;    //读取C口
		s_ms(500);      
		DDRC=0xff;      
		PORTC=0xff;        //C口变输出
		s_ms(500);
		PORTD&=~(1<<EN);   //EN=0
		s_ms(500);
	}
}

//写指令
void writecom(uchar	com)
{
	busy();
	s_ms(500);
	PORTD&=~(1<<RS);   //RS=7
	s_ms(500);
	PORTD&=~(1<<RW);   //RW=6
	s_ms(500);
	PORTD|=(1<<EN);    //EN=5
	s_ms(500);
	PORTC = com;       //输出指令
	s_ms(500);
	PORTD&=~(1<<EN);   //EN=0
	s_ms(500);
}

//1602初始化
void	LcdInit(void)
{
	writecom(0x38);
	s_ms(1000);
	writecom(0x01);
	s_ms(10000);
	s_ms(1000);
	s_ms(1000);
	s_ms(1000);
	s_ms(1000);
	s_ms(1000);
	s_ms(1000);
	writecom(0x02);
	s_ms(1000);
	writecom(0x06);
	s_ms(1000);
	writecom(0x0c);
	s_ms(1000);
	writecom(0x38);	
	s_ms(1000);
}	

//写数据
void	writedata(uchar data)
{
	busy();
	s_ms(500);
	PORTD|=(1<<RS);   //RS=7
	s_ms(500);
	PORTD&=~(1<<RW);   //RW=6
	s_ms(500);
	PORTD|=(1<<EN);    //EN=5
	s_ms(500);
	PORTC = data;      //输出数据
	s_ms(500);
	PORTD&=~(1<<EN);   //EN=0
	s_ms(500);
}


//读数据
uchar	readdata(void)
{
	uchar temp;
	busy();
	s_ms(500);
	PORTD|=(1<<RS);  //RS=7
	s_ms(500);
	PORTD|=(1<<RW);  //RW=6
	s_ms(500);
	PORTD|=(1<<EN);  //EN=5
	s_ms(500);
	DDRC=0x00;       //A端口变输入
	s_ms(500);
	temp = PINC;     //读A端口
	s_ms(500);
	DDRC=0xff;       //A端口变输出
	s_ms(500);
	PORTD&=~(1<<EN); //EN=0
	s_ms(500);
	return temp;	
}

//=================================================
// 描述: 写LCD内部CGRAM函数
// 入口: ‘num’要写的数据个数
//        ‘pbuffer’要写的数据的首地址
// 出口: 无
//================================================
void	WriteCGRAM(uint	num, const uint	*pBuffer)
{
	uint	i,t;
	writecom(0x40);
	PORTD|=(1<<RS);
	PORTD&=~(1<<RW);
	for(i=num;i!=0;i--)
	{
		t = *pBuffer;
		PORTD|=(1<<EN);
		PORTC = t;
		PORTD&=~(1<<EN);				
		pBuffer++;
	}
	
}

//=================================================
//描述:写菜单函数,本程序使用的LCD规格为 16 * 2
//入口:菜单数组首地址
//出口:无
//=================================================
void	WriteMenu(const uchar *pBuffer)
{
	uchar	i,t;
	writecom(0x80);   //数据地址
	
	PORTD|=(1<<RS);
	PORTD&=~(1<<RW);
	s_ms(50);
	for(i=0;i<16;i++)
	{
		t = *pBuffer;
		PORTC = t;
		PORTD|=(1<<EN);
		s_ms(50);
		PORTD&=~(1<<EN);				
		pBuffer++;
	}
	writecom(0xC0);

	PORTD|=(1<<RS);
	PORTD&=~(1<<RW);
	s_ms(50);	
	for(i=0;i<16;i++)
	{
		t = *pBuffer;
		PORTC = t;
		PORTD|=(1<<EN);
		s_ms(50);
		PORTD&=~(1<<EN);				
		pBuffer++;
	}
}
//====================================================
// 描述:在任意位置写数字函数
// 入口:’row‘表示要写数字所在的行地址,只能为1或2
//       ’col‘表示要写数字所在的列地址,只能为0--15
//		 ‘num’表示要写的数字,只能为0--9
// 出口:无
//===================================================
void WriteNum(uchar row,uchar col,uchar num)
{
	if (row == 1)	row = 0x80 + col;
	else	row = 0xC0 + col;
	writecom(row);

	PORTD|=(1<<RS);
	s_ms(500);
	PORTD&=~(1<<RW);
	s_ms(500);
	PORTC = num;
	s_ms(500);
	PORTD|=(1<<EN);
	s_ms(500);
	PORTD&=~(1<<EN);	
	s_ms(500);			
}
//================================================================
// 描述:在任意位置写任意多个字符
// 入口:’row‘要写的字符所在的行,只能为1或2;
//       ‘col’要写的字符所在的列,只能为0---15
//       ‘num’要写字符的个数
//       ‘pbuffer’要写字符的首地址
//================================================================== 
void	WriteChar(uchar row,uchar col,uint num,uchar *pBuffer)
{
	uchar i,t;
	if (row == 1)	row = 0x80 + col;
	else	row = 0xC0 + col;
	writecom(row);


	PORTD|=(1<<RS);
	s_ms(500);
	PORTD&=~(1<<RW);
	s_ms(500);
	for(i=num;i!=0;i--)
	{
		t = *pBuffer;
		s_ms(500);
		PORTC = t;
		s_ms(500);
		PORTD|=(1<<EN);
		s_ms(500);
		PORTD&=~(1<<EN);		
		s_ms(500);		
		pBuffer++;
	}
	
}

3.烧录

找到生成的hex文件,将单片机连接上USBISP线后,通过烧录软件烧录到接好线的单片机中,观察实验结果。

三、实验结果

当MQ-3传感器原理酒精时,也就是只暴露在正常的空气中时,LCD1602显示  Alcohol:13mg/L

MQ-3远离酒精,只暴露在普通的空气中
标题MQ-3远离酒精,只暴露在普通的空气中

 当MQ-3传感器原理酒精时,也就是只暴露在正常的空气中时,LCD1602显示  Alcohol:1666mg/L

备注:ATMega的ADC模块最高只有十位,此时输入的电压值有限,因此此时显示的有最大值,至于为什么是1666,我也没花时间去弄明白。有兴趣的小伙伴可以自己慢慢钻研哈。其实将传感器慢慢离酒精远一点显示屏上面的数值会变化,这里不方便放图片了。

MQ-3靠近酒精
MQ-3传感器靠近酒精

MQ-3传感器靠近酒精
MQ-3传感器靠近酒精

下面那一行Set的数值(也就是自己设置的酒精浓度阈值)可以通过按按键改变:

用按键修改Set值
用按键修改Set值

四、总结

没啥总结的,就是有几点需要注意:

线路一定不要接错;

MQ-3模块一开始工作的时候可以预热一会,这样测出来更精准(这个我也是看到别人说的);

MQ-3模块工作的时候会发热,但是温度如果烫手了的话应该就是出问题了。

最后,如果是要自己学习单片机的话,建议不要用这个板子,真不好用。

祝各位不负青春!

  • 30
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值