蓝桥杯单片机第三届初赛程序设计——“自动售水机”设计任务书

程序设计流程

  • 综述
    • 模板搭建
    • 根据此题要求对模板做出的改变
    • 程序设计
      • IIC A/D光敏电阻板块程序设计
    • 系统设计
    • 主函数代码

综述

第三届初赛赛题除了基础的独立按键、数码管显示、继电器和蜂鸣器的控制、LED灯的控制以外,难度增加的部分体现在AD转换部分。需要涉及到I2C总线驱动程序的调用。

本解析不代表标准答案或官方答案,仅做分享。若有不足或是更好的写法,望在评论区进行指正!

模板搭建

基础的功能:
1、独立按键
2、数码管显示

可以独立于赛题,提前写好,适用于各类赛题,仅做修改即可。

代码(板子):

#include 
#include 
//#include 

#define uchar unsigned char

uchar keyPressFlag[4] = {0, 0, 0, 0};
uchar keyPress[4] = {0, 0, 0, 0};

uchar code shapeOfNum[11] = {0xc0, 0xf9, 0xa4, 0xb0, 0x99, 0x92, 0x82, 0xf8, 0x80, 0x90, 0xbf};
uchar numGrp[8] = {11, 11, 11, 11, 11, 11, 11, 11};


void Delay5ms(){
	unsigned char i, j;

	i = 54;
	j = 199;
	do
	{
		while (--j);
	} while (--i);
}

void Delay1ms(){
	unsigned char i, j;

	_nop_();
	_nop_();
	_nop_();
	i = 11;
	j = 190;
	do
	{
		while (--j);
	} while (--i);
}

void keyScan(){
	uchar i;
	for(i = 0; i < 4; i ++) keyPress[i] = 0;
	if((P3 & 0x0f) != 0x0f){
		Delay5ms();
		if((P3 & 0x0f) != 0x0f){
			switch(P3 & 0x0f){
				case 0x0e:keyPressFlag[0] = 1;break;
				case 0x0d:keyPressFlag[1] = 1;break;
				case 0x0b:keyPressFlag[2] = 1;break;
				case 0x07:keyPressFlag[3] = 1;break;
			}
		}
	}
	for(i = 0; i < 4; i ++){
		if(keyPressFlag[i] != 0){
			if((P3 & 0x0f) == 0x0f){
				keyPress[i] = 1;
				keyPressFlag[i] = 0;
			}
		}
	}
}

void showNum(){
	uchar i;
	for(i = 0; i < 8; i ++){
		if(numGrp[i] != 11){
			P2 = (6 << 5);P0 = (0x01 << i);
			P2 = (7 << 5);P0 = shapeOfNum[numGrp[i]];
			Delay1ms();
		}
	}
	P2 = (6 << 5);P0 = 0x00;
}


void main(){
	P2 = 0xa0;P0 = 0x00;
	P2 = 0x80;P0 = 0xff;
	
	while(1){
	}
}

这里的模板代码有几个固定地方需要注意:
1、按键开了两个数组,keyPressFlag数组来判断是否按下,但只有按键松开的时候才会在keyPress数组中置1,是一个上升沿开关(按键按下的时候为0)。这样设计的原因在于,如果使用单数组,很容易造成——一旦按住按键不放手,程序就会进入按键函数无法跳出,影响其他程序同步运行。当数码管没有用定时器中断写的时候,也会收到按键的影响。所以这里用两个数组来进行检测,可以达到较好的效果。(某年初赛赛题恰好要求的是按键松开的时候达到某效果)
2、数码管没有用定时器来写。当然,用定时器中断来写数码管对于数字显示而言,效果会更好。但是定时器的强制中断也会给单片机的一些其他外设带来一些影响。尤其是题目需要用到定时器的时候,同时启用多个定时器可能会带来一些问题。所以这里模板采用的是传统的函数调用来写数码管显示。
3、通常赛题需要用到-符号,也就是数码管通常需要显示0 1 2 3 4 5 6 7 8 9 -,因此,模板里面保存的shapeOfNum[11]通常保存11个数,即11个字符。

根据此题要求对模板做出的改变

1、我们发现该题需要调用I2C通信。而在I2C通信期间,会调用P2端口。也就是说,当我们使能一些芯片的时候,需要改变P2.5-P2.7的值,如果此时直接采用

P2 = 0xXX;

这样的赋值语句,会对I2C产生干扰,也就是说我们只能改变P2端口后三位,而不能影响前面的数值。所以我们需要对模板进行修改。

void openP2(uchar num){
	P2 &= 0x1f;
	P2 |= (num << 5);
}

编辑这样一个函数

该函数不同于普通的P2赋值语句的区别在于,它不会影响前P2前五位的值。

比如我们要控制LED灯的时候,参照CT107D单片机原理图可以发现——
CT107DLED灯控制板块
LE口对应的是Y4C,也就是要控制LED灯时,openP2函数的输入应为4,即openP2(4);
需要将showNum函数进行修改:

void showNum(){
	uchar i;
	for(i = 0; i < 8; i ++){
		if(numGrp[i] != 11){
			openP2(6);P0 = (0x01 << i);
			openP2(7);P0 = shapeOfNum[numGrp[i]];
			Delay1ms();
		}
	}
	openP2(6);P0 = 0x00;
}

程序设计

IIC A/D光敏电阻板块程序设计

官方为我们提供了iic的一个c文件一个h头文件。需要在项目中导入iic.c,同时声明iic.h。
同时要注意,由于官方提供的参考驱动代码是基于52单片机的,我们需要在.c文件中将所有的延时函数扩展为原来的12倍,才能成功调用驱动。

IIC读取
官方提供的AT24C02芯片说明书中描述了如何用IIC读取。(此题没有涉及到E2PROM的写入,所以用读就可以了)。结合该图对函数进行设计:

uchar IICRead(uchar add){
	uchar dat;
	IIC_Start();
	IIC_SendByte(0x90);
	IIC_WaitAck();
	IIC_SendByte(add);
	IIC_WaitAck();
	IIC_Stop();
	
	IIC_Start();
	IIC_SendByte(0x91);
	IIC_WaitAck();
	dat = IIC_RecByte();
	IIC_WaitAck();
	IIC_Stop();
	
	return dat;
}

这里A/D转换的 写、读硬件地址为0x90/0x91
而word address可以由CT107D单片机原理图上找到,AIN1对应的是光敏电阻的A/D转换,所以此处函数输入应为0x01。即add=0x01;
A/D

系统设计

在系统设计的时候,尽量避免对P2的重复操作
比如第一次循环的时候LED1为亮,第二次循环的时候LED1仍然为亮。此时我们应该尽量避免对LED1的重复操作。换言之,就是我们的代码仅仅在某个状态发生变化的时候才进行操作。

以光敏电阻部分为例。
当电压>1.25V时灯熄灭,否则灯亮

		light = IICRead(0x01);
		if(light > (255 / 4)){
			if(L1 == 1){
				openP2(4);P0 = 0xff;
				L1 = 0;
			}
		}
		else{
			if(L1 == 0){
				openP2(4);P0 = 0xfe;
				L1 = 1;
			}
		}

而总系统部分,其实就是两个模式之间的切换
1、出水显示模式
2、计价显示模式
按下s7时,实质上就是从模式2变成模式1,而按下s6时,实质上就是从模式1变成模式2。(初始化的时候其实是计价为0元的模式2)
换言之,在每次循环的时候,仅当模式发生了变换的时候,才会进行一部分操作。

if(mod == 0){
			if(preMod == 1){
				openP2(5);P0 = 0x10;openP2(0);
				water = 0;count = 0;
				Timer0Init();
				ET0 = 1;
				preMod = 0;
			}
			numGrp[4] = water / 1000;
			numGrp[5] = (water / 100) % 10;
		  numGrp[6] = (water / 10) % 10;
		  numGrp[7] = water % 10;
			if((keyPress[1] == 1) || (water == 9999)){
				mod = 1;
			}
		}
		else if(mod == 1){
			if(preMod == 0){
				openP2(5);P0 = 0x00;openP2(0);
				ET0 = 0;
				money = water / 2;
				preMod = 1;
			}
			numGrp[4] = money / 1000;
			numGrp[5] = (money / 100) % 10;
		  numGrp[6] = (money / 10) % 10;
		  numGrp[7] = money % 10;
			if(keyPress[0] == 1){
				mod = 0;
			}
		}	

主函数代码

#include 
#include 
#include 
//#include 

#define uchar unsigned char

uchar keyPressFlag[4] = {0, 0, 0, 0};
uchar keyPress[4] = {0, 0, 0, 0};

uchar code shapeOfNum[11] = {0xc0, 0xf9, 0xa4, 0xb0, 0x99, 0x92, 0x82, 0xf8, 0x80, 0x90, 0xbf};
uchar numGrp[8] = {11, 0, 5, 0, 0, 0, 0, 0};

uchar count = 0;
int water = 0;


void Delay5ms(){
	unsigned char i, j;

	i = 54;
	j = 199;
	do
	{
		while (--j);
	} while (--i);
}

void Delay1ms(){
	unsigned char i, j;

	_nop_();
	_nop_();
	_nop_();
	i = 11;
	j = 190;
	do
	{
		while (--j);
	} while (--i);
}

void openP2(uchar num){
	P2 &= 0x1f;
	P2 |= (num << 5);
}

void keyScan(){
	uchar i;
	for(i = 0; i < 4; i ++) keyPress[i] = 0;
	if((P3 & 0x0f) != 0x0f){
		Delay5ms();
		if((P3 & 0x0f) != 0x0f){
			switch(P3 & 0x0f){
				case 0x0e:keyPressFlag[0] = 1;break;
				case 0x0d:keyPressFlag[1] = 1;break;
				case 0x0b:keyPressFlag[2] = 1;break;
				case 0x07:keyPressFlag[3] = 1;break;
			}
		}
	}
	for(i = 0; i < 4; i ++){
		if(keyPressFlag[i] != 0){
			if((P3 & 0x0f) == 0x0f){
				keyPress[i] = 1;
				keyPressFlag[i] = 0;
			}
		}
	}
}

void showNum(){
	uchar i;
	for(i = 0; i < 8; i ++){
		if(numGrp[i] != 11){
			if((i == 1) || (i == 5)){
				openP2(6);P0 = (0x01 << i);
				openP2(7);P0 = shapeOfNum[numGrp[i]] - 0x80;
				Delay1ms();
			}
			else{
				openP2(6);P0 = (0x01 << i);
				openP2(7);P0 = shapeOfNum[numGrp[i]];
				Delay1ms();
			}
		}
	}
	openP2(6);P0 = 0x00;
}


uchar IICRead(uchar add){
	uchar dat;
	IIC_Start();
	IIC_SendByte(0x90);
	IIC_WaitAck();
	IIC_SendByte(add);
	IIC_WaitAck();
	IIC_Stop();
	
	IIC_Start();
	IIC_SendByte(0x91);
	IIC_WaitAck();
	dat = IIC_RecByte();
	IIC_WaitAck();
	IIC_Stop();
	
	return dat;
}

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

void timer0() interrupt 1{
	//100 ms 进1位
	if(count == 99){
		count = 0;
		water ++;
	}
	else count ++;
}

void main(){
	uchar light, mod = 1, preMod = 1, L1 = 0;
	int money = 0;
	P2 = 0xa0;P0 = 0x00;
	P2 = 0x80;P0 = 0xff;
	EA = 1;ET0 = 0;
	while(1){
		light = IICRead(0x01);
		if(light > (255 / 4)){
			if(L1 == 1){
				openP2(4);P0 = 0xff;
				L1 = 0;
			}
		}
		else{
			if(L1 == 0){
				openP2(4);P0 = 0xfe;
				L1 = 1;
			}
		}
		
		
		keyScan();
		
		if(mod == 0){
			if(preMod == 1){
				openP2(5);P0 = 0x10;openP2(0);
				water = 0;count = 0;
				Timer0Init();
				ET0 = 1;
				preMod = 0;
			}
			numGrp[4] = water / 1000;
			numGrp[5] = (water / 100) % 10;
		  numGrp[6] = (water / 10) % 10;
		  numGrp[7] = water % 10;
			if((keyPress[1] == 1) || (water == 9999)){
				mod = 1;
			}
		}
		else if(mod == 1){
			if(preMod == 0){
				openP2(5);P0 = 0x00;openP2(0);
				ET0 = 0;
				money = water / 2;
				preMod = 1;
			}
			numGrp[4] = money / 1000;
			numGrp[5] = (money / 100) % 10;
		  numGrp[6] = (money / 10) % 10;
		  numGrp[7] = money % 10;
			if(keyPress[0] == 1){
				mod = 0;
			}
		}	
		showNum();
	}
}

你可能感兴趣的:(蓝桥杯单片机)