设计内容:通过竞赛硬件平台模拟小区自动售水机的工作流程:通过按键控制售水机水流出和停止;通过数码管显示费率、出水量及总费用;通过光敏电阻检测环境亮度,在亮度过低的情况下,自动开灯。系统硬件电路主要由单片机控制电路、数码管显示电路、 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控制关闭中断,用这种方法可以大大简化程序的逻辑复杂程度,不失为一种技巧。