23.蓝桥杯单片机设计与开发第三届省赛

设计内容:通过竞赛硬件平台模拟小区自动售水机的工作流程:通过按键控制售水机水流出和停止;通过数码管显示费率、出水量及总费用;通过光敏电阻检测环境亮度,在亮度过低的情况下,自动开灯。系统硬件电路主要由单片机控制电路、数码管显示电路、 A/D 转换电路及功能按键组成。
在这里插入图片描述
由上面的设计要求可知,我们本次设计使用到了按键、A/D转换、数码管显示、继电器和LED几个部分,本人的习惯是先将省赛必考部分写出一个框架,详情可见:
https://blog.csdn.net/qq_44628230/article/details/104364428
并在此基础上根据题目要求,判断是使用矩阵按键还是独立按键,是否使用定时器以及定时器类型,MCU和模块间使用什么通信协议等等,在框架中增删对应的子程序,但不对其作具体操作。
然后在此基础上,从子函数入手完成设计要求。下面是我们的设计要求:
在这里插入图片描述
在这里插入图片描述
1.延时函数、初始化函数、数码管显示函数我们在之前的博客里已经编写和讲述过,也相信大家可能都有自己的编程思路,此处不再赘述。(数码管注意有两位需要加点操作,可采用多种方式)

2.独立按键部分
题目要求我们使用的是独立按键,故选择跳线帽应该置于右边两针上。
本人编写的程序如下:

void KeyScan(void)
{
	if(P30==0)			//k7
	{
		Delayms(5);
		if(P30==0)
		{
			key=1;
			key_first++;
		}
		while(!P30);
	}
	
	if(P31==0)			//S6
	{
		Delayms(5);
		if(P31==0)
		{
			key=0;
			key_first=0;
		}
		while(!P30);
	}
}

按下不同的按键,我们赋给key变量不同的值,0相当于饮水机关(继电器断开L10熄灭),1相当于开(继电器接通L10点亮),用于在主函数中的判断。key_first在定义时初始化为0,S7按下时使key_first自增1,key_first=1时即为第一次按下S7,于是在后面程序中我们可以通过判断key_first的值来确定是第几次按下,从而实现题目要求只有第一次按下时会起到清零数码管的作用;S6按下时使key_first再次归零便于下一次按下S7时重新计数。
3.定时器部分
题目要求,出水速度100ml/s,于是我使用了定时器来保证这个时间。

void Timer0Init(void)		//1毫秒@11.0592MHz
{
	AUXR |= 0x80;		//定时器时钟1T模式
	TMOD &= 0xF0;		//设置定时器模式
	TL0 = 0xCD;		//设置定时初值
	TH0 = 0xD4;		//设置定时初值
	TF0 = 0;		//清除TF0标志
	TR0 = 1;		//定时器0开始计时
}

void Timer0(void) interrupt 1
{
	t++;
	if(t==100)
	{
		t=0;
		if(key==1)
		{
			num+=10;
			if(num==99990)
			{
				key=0;
			}
			if(key_first==1)
			{
				num=0;
				key_first=2;
			}
			sum=num/2;
		}
	}
}	

首先初始化定时器0(注意定时器0的中断号为1),我使用了ISP下载器进行初始化操作,我取的定时为1ms,那么运行100次为100ms,而100ms可以出水10ml(因为数码管只有后四位显示出水量,故精确度为10ml,那我就直接转化为10ml/100ms了)每100ms后t进行一次清零重新计数操作,并判断如果key=1也即S7按下,我们使出水量加10ml,并且判断S7是否是第一次按下(key_first的值)第一次按下则对出水量进行一次清零操作(也就实现了按下S7时清零了后四位数码管),直到出水量计满99990(为了避免小数不便,我使用了ml为单位)时(当然num要使用long型)我们使key=0与按下S6等效(具体可见主函数部分)。sum用于计算总价并保存其值。
4.IIC通信以及PCF8591 AD转换
IIC的底层驱动程序在考生资源包会提供,我们只需要对其延时进行8-12倍扩大即可(因为这个驱动原是为了8051单片机设计,IAP15系列单片机运行速度约为8051的8-12倍),然后利用底层驱动来编写AD转换程序(在主函数中是从光敏电阻也即通道1也即0x01地址读取模拟信号电压值)

uchar AD_Read(uchar add)
{
	uchar temp;
	
	IIC_Start();
	IIC_SendByte(0X90);
	IIC_WaitAck();
	IIC_SendByte(add);
	IIC_WaitAck();
	IIC_Stop();
	
	IIC_Start();
	IIC_SendByte(0X91);
	IIC_WaitAck();
	temp=IIC_RecByte();
	IIC_Stop();
	
	return temp;
}

关于IIC读数据的时序等内容介绍请见:
https://blog.csdn.net/qq_44628230/article/details/104492239
5.主函数

uchar tab[]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0x40,0x79,0x24,0x30,0x19,0x12,0x02,0x78,0x00,0x10,0xff};
void main(void)
{
	Allinit();
	Timer0Init();
	EA=1;ET0=1;
	yi=20;er=10;san=5;si=0;
	wu=20;liu=20;qi=20;ba=20;
	
	while(1)
	{
		KeyScan();
		if(key==1)
		{
			P2=0XA0;
			P0=0X10;	
			wu=num/10000;liu=(num%10000/1000)+10;qi=num%1000/100;ba=num%100/10;
		}
		else if(key==0)
		{
			P2=0XA0;
			P0=0X00;
			wu=sum/10000;liu=(sum%10000/1000)+10;qi=sum%1000/100;ba=sum%100/10;
		}
		
		vol=AD_Read(0X01);
		if(vol<64)
		{
			P2=0X80;
			P0=0XFE;
		}
		else
		{
			P2=0X80;
			P0=0XFF;
		}
		Display1(yi,er);
		Display2(san,si);
		Display3(wu,liu);
		Display4(qi,ba);
	}
}

首先调用板子的初始化函数和定时器0的初始化函数,对数码管的值进行初始化显示。由于前四位显示单价,故可以直接设为定值0.50并不再进行赋值操作。循环调用按键扫描程序,当key=1时S7按下,使继电器接通L10点亮,此时数码管上显示的是出水量num值,num值在定时器0中断中得到为long型,进行相应的取整取余运算即可显示在后四位数码管上;当key=0时S6按下,继电器断开L10熄灭,此时数码管显示的是总价sum值,sum值在定时器0中断中得到为long型,进行相应的取整取余运算即可显示在后四位数码管上。(回想定时器0中断中num计满99990置key=0,便相当于S6按下)
AD转换根据题目要求是从光敏电阻捕获电压值(PCF8591的模拟输入通道1)然后将转换后的数据存在了vol中(0~255),板子供电电压为5v,当低于1.25时L1点亮,也即转化为0-255范围内低于64,故我们可以判断vol是否小于64,从而确定是否要点亮L1。至此我们完成了第三届省赛赛题的全部要求。

ps:在后来的完善中发现,其实还可以通过S7控制打开定时器0中断,S6控制关闭中断,用这种方法可以大大简化程序的逻辑复杂程度,不失为一种技巧。

猜你喜欢

转载自blog.csdn.net/qq_44628230/article/details/104879003