【蓝桥杯】第三届单片机省赛:自动售水机详细讲解

蓝桥杯资料包
源码
强烈建议先自己做一遍,这是蓝桥杯单片机最简单的一道题。
首先从试题的系统框图开始看

如图所示,有AD转换,所以IIC肯定是需要用的
有数码管,所以数码管先写上
有按键,但确定是独立按键还是矩阵按键,往下翻

S5,S7所以使用独立按键就行
所以,我们先建立工程,然后把需要的数码管,IIC,独立按键先添加进去并且调试好。
main.c

#include <stc15f2k60s2.h>
#include "intrins.h"
#include "iic.h"

#define uchar unsigned char
#define uint unsigned int
	
void SMG_output(void);
void init(void);
void Delay1ms(void);
void delay5ms(void);
void Dkey_scan(void);

uchar tab[]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,\
0x40,0x79,0x24,0x30,0x19,0x12,0x02,0x78,0x00,0x10,0xff,0xbf};
uchar SMG[8]={20,20,20,20,20,20,20,20};//初始显示10,全息数码管

void main(void)
{
	init();
	while(1)
	{
		SMG_output();
		Dkey_scan();
	}
}

void Dkey_scan(void) //矩阵按键
{
	static uchar keybyte=0;
	static uchar key;
	if(((P3&0X0F)!=0X0F)&&(keybyte==0))
	{
		delay5ms();
		if((P3&0X0F)!=0X0F)
		{
			keybyte=1;key=P3&0x0f;
		}
	}
	if((keybyte==1)&&((P3&0X0F)==0X0F))
	{
		if((P3&0X0F)==0X0F)
		{
			switch(key)
			{
				case 0x0e:SMG[0]=1;
					break;
				case 0x0d:SMG[1]=1;
					break;
				case 0x0b:SMG[2]=1;
					break;
				case 0x07:SMG[3]=1;
					break;
			}
			keybyte=0;
		}
	}
}

void SMG_output(void) //数码管显示函数
{
	uchar i;
	for(i=0;i<8;i++)
	{
	P2=(P2&0X1F)|0Xc0;
	P0=(1<<i);
	P2=(P2&0X1F)|0Xe0;
	P0=tab[SMG[i]];
	Delay1ms();
	}
	P2=(P2&0X1F)|0Xc0;
	P0=0Xff;
	P2=(P2&0X1F)|0Xe0;
	P0=0Xff;	
}

void init(void) //初始化函数,关闭所有外设
{
	P2=(P2&0X1F)|0XA0;
	P0=0X00;
	P2=(P2&0X1F)|0X80;
	P0=0Xff;
	P2=(P2&0X1F)|0Xc0;
	P0=0Xff;
	P2=(P2&0X1F)|0Xe0;
	P0=0Xff;	
}

void Delay1ms(void)		//@11.0592MHz
{
	unsigned char i, j;

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

void delay5ms(void)		//@11.0592MHz
{
	unsigned char i, j;

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

iic.c

#include "iic.h"

#define DELAY_TIME 40
//IIC的读函数,参数(硬件地址,寄存器地址,数据)
void IIC_write(uchar hw_address,uchar reg_address,uchar num)
{ 
	IIC_Start();
	IIC_SendByte(hw_address&0xfe);
	IIC_WaitAck();
	IIC_SendByte(reg_address);
	IIC_WaitAck();
	IIC_SendByte(num);
	IIC_WaitAck();	
	IIC_Stop();	
}	
//IIC读函数 参数:硬件地址,寄存器地址
uchar IIC_read(uchar hw_address,uchar reg_address)
{
	uchar num;
	IIC_Start();
	IIC_SendByte(hw_address&0xfe);
	IIC_WaitAck();
	IIC_SendByte(reg_address);	
	IIC_WaitAck();
	IIC_Stop();
	
	IIC_Start();
	IIC_SendByte(hw_address|0x01);
	IIC_WaitAck();
	num=IIC_RecByte();
	IIC_WaitAck();
	IIC_Stop();	
	
	return num;
}

//
void IIC_Delay(unsigned char i)
{
    do{_nop_();}
    while(i--);        
}

//
void IIC_Start(void)
{
    SDA = 1;
    SCL = 1;
    IIC_Delay(DELAY_TIME);
    SDA = 0;
    IIC_Delay(DELAY_TIME);
    SCL = 0;	
}

//
void IIC_Stop(void)
{
    SDA = 0;
    SCL = 1;
    IIC_Delay(DELAY_TIME);
    SDA = 1;
    IIC_Delay(DELAY_TIME);
}

//
void IIC_SendAck(bit ackbit)
{
    SCL = 0;
    SDA = ackbit;  					
    IIC_Delay(DELAY_TIME);
    SCL = 1;
    IIC_Delay(DELAY_TIME);
    SCL = 0; 
    SDA = 1;
    IIC_Delay(DELAY_TIME);
}

//
bit IIC_WaitAck(void)
{
    bit ackbit;
	
    SCL  = 1;
    IIC_Delay(DELAY_TIME);
    ackbit = SDA;
    SCL = 0;
    IIC_Delay(DELAY_TIME);
    return ackbit;
}

//
void IIC_SendByte(unsigned char byt)
{
    unsigned char i;

    for(i=0; i<8; i++)
    {
        SCL  = 0;
        IIC_Delay(DELAY_TIME);
        if(byt & 0x80) SDA  = 1;
        else SDA  = 0;
        IIC_Delay(DELAY_TIME);
        SCL = 1;
        byt <<= 1;
        IIC_Delay(DELAY_TIME);
    }
    SCL  = 0;  
}

//
unsigned char IIC_RecByte(void)
{
    unsigned char i, da;
    for(i=0; i<8; i++)
    {   
    	SCL = 1;
		IIC_Delay(DELAY_TIME);
		da <<= 1;
		if(SDA) da |= 1;
		SCL = 0;
		IIC_Delay(DELAY_TIME);
    }
    return da;    
}

iic.h

#ifndef _IIC_H
#define _IIC_H

#include "stc15f2k60s2.h"
#include "intrins.h"

#define uchar unsigned char
#define uint unsigned int

sbit SDA = P2^1;
sbit SCL = P2^0;

void IIC_Start(void); 
void IIC_Stop(void);  
bit IIC_WaitAck(void);  
void IIC_SendAck(bit ackbit); 
void IIC_SendByte(unsigned char byt); 
unsigned char IIC_RecByte(void); 

uchar IIC_read(uchar hw_address,uchar reg_address);
void IIC_write(uchar hw_address,uchar reg_address,uchar num);

#endif

然后编译,会发现有几个警告,*** WARNING L16: UNCALLED SEGMENT, IGNORED FOR OVERLAY PROCESS,像这中警告一般是有函数没有调用,不用理会,直接下载调试。效果应该是按下按键,数码管对应亮一。
然后接着往下看题目。

数码管显示有两种状态,所以肯定是要定义一,个变量用于切换状态的。定义好后,首先按照如图数码管一模一样显示出来,定义一个价格变量price=50,50方便显示;定义一个总价all_price=50,然后显示出来。改变之后的代码如下:

uchar SMG_mode=0; //数码管切换状态变量
uchar price=50; //价格变量
uint all_price=50,water_num=100; //总价变量 //出水量定义
void main(void)
{
	init();
	while(1)
	{
		if(SMG_mode==0) //数码管模式一 售水机出水状态数码管显示
		{//加上10都是为了显示小数点
			SMG[0]=20;SMG[1]=price/100+10;SMG[2]=price%100/10;
			SMG[3]=price%10;SMG[4]=water_num/1000;
			SMG[5]=water_num%1000/100+10;
			SMG[6]=water_num%100/10;SMG[7]=water_num%10;
		}
		else if(SMG_mode==1) //数码管模式二 售水机出停水态数码管显示
		{//加上10都是为了显示小数点
			SMG[0]=20;SMG[1]=price/100+10;SMG[2]=price%100/10;
			SMG[3]=price%10;SMG[4]=all_price/1000;
			SMG[5]=all_price%1000/100+10;
			SMG[6]=all_price%100/10;SMG[7]=all_price%10;
		}
		SMG_output();
		Dkey_scan();
	}
}

如题

然后数码管需要由按键控制,所以在按键中在增加相应控制程序(S7显示数码管模式一,并且继电器接通,L10亮,S6显示数码管模式二,并且继电器断开,L10熄灭),由于只需要S7和S6,所以把独立按键另外两个按键删除。
对应独立按键修改的程序为:

void Dkey_scan(void)
{
	static uchar keybyte=0;
	static uchar key;
	if(((P3&0X0F)!=0X0F)&&(keybyte==0))
	{
		delay5ms();
		if((P3&0X0F)!=0X0F)
		{
			keybyte=1;key=P3&0x0f;
		}
	}
	if((keybyte==1)&&((P3&0X0F)==0X0F))
	{
		if((P3&0X0F)==0X0F)
		{
			switch(key)
			{
				case 0x0e:
					if(SMG_mode==1)SMG_mode=0; //切换数码管模式
					P2=0XA0;P0=0X10;  //点亮L10,接通继电器
					break;
				case 0x0d:
					if(SMG_mode==0)SMG_mode=1;
					P2=0XA0;P0=0X00;  //熄灭L10,关闭继电器
					break;
			}
			keybyte=0;
		}
	}
}

记得下载调试!!!
然后接着看试题

由于要使用AD转换,所以要使用IIC了,首先看要求是PCF8591读取光敏电阻的电压,所以IIC读的硬件地址0x90,然后光敏电阻地址为0x01,然后输入电压小于1.25V,L1点亮,否则L1熄灭,所以首先要将读取的值转换为电压值,即255转换为5V,然后再进行判断就行。
所以主函数变为:

uchar light=0;
void main(void)
{
	init();
	while(1)
	{
		light=IIC_read(0x90,0x01); //读取光敏电阻阻值
		light=light*1.9607; //500/255 放大100倍
		if(light<125) //判断条件
		{
			P2=0X80;P0=0XFE; //打开L1;
		}
		else if(light>125)
		{
			P2=0X80;P0=0XFF; //关闭L1;
		}

然后下载调试,发现遮住光敏电阻L1点亮。
然后再看最后一个要求:

计算价格,并且给出出水量限额,100毫升/秒就是0.1升/秒,所以首先要使用定时器来计时出水。
更改代码如下:
定时器出水

void time0() interrupt 1
{
	t++;
	if(t==1000) //1ms计时1000次==1s
	{
		t=0;
		if(SMG_mode==0) //数码管模式0才加水 即出水模式才加水
				water_num+=10; //每一秒出水
	}
}

按键计算价格和清零

				case 0x0e:
					if(SMG_mode==1){SMG_mode=0;water_num=0;}//切换数码管模式,出水量清零
					P2=0XA0;P0=0X10;  //点亮L10,接通继电器
					break;
				case 0x0d:
					if(SMG_mode==0)SMG_mode=1;
					P2=0XA0;P0=0X00;  //熄灭L10,关闭继电器
					all_price=((water_num*price)/100); //计算价格
					break;

出水限制

		if(water_num>9999)
		{
			SMG_mode=1;P2=0XA0;P0=0X00;  //熄灭L10,关闭继电器
			all_price=((water_num*price)/100); //计算价格
		}

最后别忘记更改初始出水量和价格,删除没用的函数。
完整代码

#include <stc15f2k60s2.h>
#include "intrins.h"
#include "iic.h"

#define uchar unsigned char
#define uint unsigned int
	
void SMG_output(void);
void init(void);
void Delay1ms(void);
void delay5ms(void);
void Dkey_scan(void);
void Timer0Init(void);
uchar tab[]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,\
0x40,0x79,0x24,0x30,0x19,0x12,0x02,0x78,0x00,0x10,0xff,0xbf};
uchar SMG[8]={20,20,20,20,20,20,20,20};//初始显示10,全息数码管

uchar SMG_mode=0; //数码管切换状态变量
uchar price=50; //价格变量
uint all_price=0,water_num=0; //总价变量 //出水量定义
uint light=0;
uint t=0;  //定时器变量
void main(void)
{
	init(); //初始开发板
	Timer0Init(); //初始化定时器
	while(1)
	{
		light=IIC_read(0x90,0x01); //读取光敏电阻阻值
		light=light*1.9607; //500/255 放大100倍
		if(light<125) //判断条件
		{
			P2=0X80;P0=0XFE; //打开L1;
		}
		else if(light>125)
		{
			P2=0X80;P0=0XFF; //关闭L1;
		}
		if(SMG_mode==0) //数码管模式一 售水机出水状态数码管显示
		{//加上10都是为了显示小数点
			SMG[0]=20;SMG[1]=price/100+10;SMG[2]=price%100/10;
			SMG[3]=price%10;SMG[4]=water_num/1000;
			SMG[5]=water_num%1000/100+10;
			SMG[6]=water_num%100/10;SMG[7]=water_num%10;
		}
		else if(SMG_mode==1) //数码管模式二 售水机出停水态数码管显示
		{//加上10都是为了显示小数点
			SMG[0]=20;SMG[1]=price/100+10;SMG[2]=price%100/10;
			SMG[3]=price%10;SMG[4]=all_price/1000;
			SMG[5]=all_price%1000/100+10;
			SMG[6]=all_price%100/10;SMG[7]=all_price%10;
		}
		if(water_num>9999)
		{
			SMG_mode=1;P2=0XA0;P0=0X00;  //熄灭L10,关闭继电器
			all_price=((water_num*price)/100); //计算价格
		}
		SMG_output();
		Dkey_scan();
	}
}

void Dkey_scan(void)
{
	static uchar keybyte=0;
	static uchar key;
	if(((P3&0X0F)!=0X0F)&&(keybyte==0))
	{
		delay5ms();
		if((P3&0X0F)!=0X0F)
		{
			keybyte=1;key=P3&0x0f;
		}
	}
	if((keybyte==1)&&((P3&0X0F)==0X0F))
	{
		if((P3&0X0F)==0X0F)
		{
			switch(key)
			{
				case 0x0e:
					if(SMG_mode==1){SMG_mode=0;water_num=0;}//切换数码管模式,出水量清零
					P2=0XA0;P0=0X10;  //点亮L10,接通继电器
					break;
				case 0x0d:
					if(SMG_mode==0)SMG_mode=1;
					P2=0XA0;P0=0X00;  //熄灭L10,关闭继电器
					all_price=((water_num*price)/100); //计算价格
					break;
			}
			keybyte=0;
		}
	}
}

void time0() interrupt 1
{
	t++;
	if(t==1000) //1ms计时1000次==1s
	{
		t=0;
		if(SMG_mode==0) //数码管模式0才加水 即出水模式才加水
				water_num+=10; //每一秒出水
	}
}

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

void SMG_output(void)
{
	uchar i;
	for(i=0;i<8;i++)
	{
	P2=(P2&0X1F)|0Xc0;
	P0=(1<<i);
	P2=(P2&0X1F)|0Xe0;
	P0=tab[SMG[i]];
	Delay1ms();
	}
	P2=(P2&0X1F)|0Xc0;
	P0=0Xff;
	P2=(P2&0X1F)|0Xe0;
	P0=0Xff;	
}

void init(void)
{
	P2=(P2&0X1F)|0XA0;
	P0=0X00;
	P2=(P2&0X1F)|0X80;
	P0=0Xff;
	P2=(P2&0X1F)|0Xc0;
	P0=0Xff;
	P2=(P2&0X1F)|0Xe0;
	P0=0Xff;	
}

void Delay1ms(void)		//@11.0592MHz
{
	unsigned char i, j;

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

void delay5ms(void)		//@11.0592MHz
{
	unsigned char i, j;

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

最后演示视频

最后可以有问题可以私信和评论。

物联沃分享整理
物联沃-IOTWORD物联网 » 【蓝桥杯】第三届单片机省赛:自动售水机详细讲解

发表评论