提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
本文章为个人单片机课程大作业,代码都是自己写的,只是记录自己的学习过程,不做任何商用。
目录
单片机大作业:
基于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远离酒精,只暴露在普通的空气中](https://i-blog.csdnimg.cn/blog_migrate/d8d278207a89b09197880398f6fa24db.jpeg)
当MQ-3传感器原理酒精时,也就是只暴露在正常的空气中时,LCD1602显示 Alcohol:1666mg/L
备注:ATMega的ADC模块最高只有十位,此时输入的电压值有限,因此此时显示的有最大值,至于为什么是1666,我也没花时间去弄明白。有兴趣的小伙伴可以自己慢慢钻研哈。其实将传感器慢慢离酒精远一点显示屏上面的数值会变化,这里不方便放图片了。
![MQ-3靠近酒精](https://i-blog.csdnimg.cn/blog_migrate/ab0b838ad528226cb07b00deff913e41.jpeg)
![MQ-3传感器靠近酒精](https://i-blog.csdnimg.cn/blog_migrate/d4a51456bfbc0cd15245514d805ed21e.jpeg)
下面那一行Set的数值(也就是自己设置的酒精浓度阈值)可以通过按按键改变:
![用按键修改Set值](https://i-blog.csdnimg.cn/blog_migrate/808604e7b47385fcf934cd21f7900063.jpeg)
四、总结
没啥总结的,就是有几点需要注意:
线路一定不要接错;
MQ-3模块一开始工作的时候可以预热一会,这样测出来更精准(这个我也是看到别人说的);
MQ-3模块工作的时候会发热,但是温度如果烫手了的话应该就是出问题了。
最后,如果是要自己学习单片机的话,建议不要用这个板子,真不好用。
祝各位不负青春!