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

综述

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

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

模板搭建

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

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

代码(板子):

#include <stc15f2k60s2.h>
#include <intrins.h>
//#include <absacc.h>

#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 <stc15f2k60s2.h>
#include <intrins.h>
#include <iic.h>
//#include <absacc.h>

#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();
	}
}
  • 6
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
### 回答1: 蓝桥杯单片机历年真题pdf是蓝桥杯单片机竞赛的历年真题集合,对于准备参加蓝桥杯单片机竞赛的人来说非常有用。通过前人的经验和历年真题的练习,可以更好地掌握单片机的知识和技能,为竞赛做好充分准备。 蓝桥杯单片机竞赛是国内较有影响力的单片机技能竞赛之一,参赛者可以通过不同层次的初赛、复赛、终赛,展示自己在单片机开发、程序设计、电路搭建等方面的综合素质。在竞赛中,历年真题集合可以帮助参赛者了解竞赛的难度和题型,逐步掌握单片机的电路搭建、编程调试、传感器应用等技巧,从而提升自己在竞赛中的表现。 另外,历年真题集合也可以作为学习单片机的资料,通过逐题分析、学习,深入理解单片机的基本原理和实际应用,掌握单片机程序的设计和编写,提高自己的单片机开发能力。 综上所述,蓝桥杯单片机历年真题pdf的出现,极大地方便了广大单片机爱好者和竞赛选手的学习和备战,更好地推动了单片机技术的发展。 ### 回答2: 蓝桥杯单片机历年真题pdf是指蓝桥杯单片机竞赛历年来的真实考题集合,这些考题主要用于单片机相关竞赛的备考和练习。这个题库包含了各种级别的考题,从初级到高级,涵盖了单片机开发的各个领域的考试内容。 通过学习这些真题,可以更全面地了解单片机竞赛的题型、难度和出题方向,加深对单片机的认识和使用,提高单片机开发的技能水平。同时,这个题库还能帮助竞赛选手熟悉比赛时间、考试规则和注意事项,提高竞技能力,达到更好的比赛成绩。 总之,蓝桥杯单片机历年真题pdf是单片机竞赛学习的重要资源,对于参加单片机竞赛的学生和从事单片机开发的工程师都具有很高的参考价值。 ### 回答3: 蓝桥杯单片机历年真题pdf是一份非常重要的资源,对于参加蓝桥杯单片机比赛的选手来说是必备的资料之一。这份pdf包含了从2007年至今蓝桥杯单片机比赛的所有真题,其中不仅包括竞赛试题和答案,还有对试题的详细分析和解释,对于复习备战考试非常有帮助。 在这份pdf中,可以看到历年来比赛难度逐年提高,试题越来越贴近实际工作中的需求。通过研究历年的试题,可以对比赛的命题方向和难度进行有效的预测和判断,为备战比赛提供有力的参考。 此外,这份pdf还提供了很多考场上非常有用的技巧和策略,如何快速、准确的读题、选择合适的编程方法和数据结构等等,这些都是比赛中取得好成绩的关键因素。 总之,蓝桥杯单片机历年真题pdf是一份非常重要的资料,不仅对于准备参加蓝桥杯单片机比赛的选手来说必不可少,对于学习单片机编程的人来说也是值得一看的资源。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值