蓝桥杯单片机第九届初赛主观题-----彩灯控制系统

蓝桥杯单片机第九届初赛主观题-----彩灯控制系统

前言

俺又来了,这次连续好几天都没写博客,真不是我偷懒,这次是真的写了好久!各种bug可给我整惨了,让后最近害仔细看了看大佬写的,于是就把格式给改的略微整齐了一点。。。
废话不多说,我们开始


赛题分析

1、硬件框图分析

仍然是赛题分析先开始,先看基本硬件框图
蓝桥杯单片机第九届初赛主观题-----彩灯控制系统
根据这张图总结一下大概需要的器件以及所需要的特殊功能寄存器

输入功能
1、模拟输入,IIC通讯下的PCF8591进行AD转换
2、按键,定时器0进行间隔1ms的定时器中断进行按键扫描消抖

存储功能
3、E2PROM,IIC通讯下的24C02存储器的数据读写,存储

输出功能
4、LED指示灯,138译码器和573锁存器选择LED输出
5、数码管显示,138译码器和573锁存器选择数码管,并运用定时器0进行间隔1ms的定时器中断输出显示内容

2、功能分析

蓝桥杯单片机第九届初赛主观题-----彩灯控制系统
根据基本功能描述,我们可以知道,我们还需要用到PWM输出、PCF8591的AIN3通道。
蓝桥杯单片机第九届初赛主观题-----彩灯控制系统
蓝桥杯单片机第九届初赛主观题-----彩灯控制系统

1)关闭蜂鸣器这个很简单,直接跳过。

2)上电后关闭LED,数码管,我们可以设置状态位,当状态位改变时再启动LED或者数码管

3)设置一个无符号整型变量用来存储流转间隔

蓝桥杯单片机第九届初赛主观题-----彩灯控制系统

4)同样可以设置状态数负责LED的流转,时间可在定时器中断中计算。
蓝桥杯单片机第九届初赛主观题-----彩灯控制系统
这里可以根据PCF8591读取到的数值来改变PWM的数值
蓝桥杯单片机第九届初赛主观题-----彩灯控制系统
大的来了 ,按键功能一般是赛题中的核心,需要特别重视!

1)S7改变LED的状态数来启动LED
2)我们设置S6按键的状态数,通过S6改变状态数来改变数码管的显示,并且可以通过状态数选择按键S5与S4的功能
3)S5在对应状态数下,改变之前创建的存储流转间隔数据或者运行模式的变量与状态数。
4)S4在对应状态数下,改变之前创建的存储流转间隔数据或者运行模式的变量与状态数。设置温度显示状态数,在另一对应状态下,按下松开改变温度显示状态数。

说了这么多废话,其实总结一下就是,给我设置状态数啊kora!!!

代码讲解

主函数页面代码

#ifndef _SYS_H
#define _SYS_H

typedef  unsigned char uint8;
typedef  unsigned int  uint16;
typedef  unsigned long uint32;

#include "reg52.h"
#include "intrins.h"
#include "iic.h"
#include "Keyboard.h"
#include "LED.h"
#include "SMG.h"
#include "Init.h"

extern uint8  TurnMod;  //运行模式状态数
extern bit LED_ON;		//LED开关状态数
extern uint8 Con_Mod;	//模式控制状态数
extern uint16 TurnHZ;	//流转间隔时间存储变量(单位毫秒)
extern uint8 PWM_NUM;	//PWM值存储变量
extern bit Tmp_Show;	//亮度显示状态数
extern uint8 TurnHZ_L;	//流转间隔时间存储变量缩小100倍版,方便eeprom存储
#endif
#include "Sys.h"
uint16 TurnHZ = 400;	//流转间隔时间存储变量(单位毫秒)
uint8 TurnHZ_L;			//流转间隔时间存储变量缩小100倍版,方便eeprom存储
uint8  TurnMod = 0x10;	//运行模式状态数
bit Fms = 1;			//运行模式时间抵达状态数
uint8 PWM_NUM = 255;	//PWM存储变量
bit F200ms = 1;			//200ms时间间隔状态数
bit LED_ON = 0;			//LED开关状态数
uint8 Con_Mod = 0x00;	//模式控制状态数
bit Tmp_Show = 0;		//亮度显示状态数
bit FADC = 0;			//PCF8591读取数据状态数
/*
iic读取数据时需要关闭中断,因此应设置读取间隔。
若不设置读取间隔,让iic连续读取,则会造成led闪烁
若读取时不关闭中断,则会造成读取数据不稳定
*/
void main()
{
	Close_BUZZ();		//关闭蜂鸣器与继电器
	Init_T0();			//初始化T0定时器
	Init_PWM();			//初始化T1定时器(PWM)
	TurnHZ_L = EEPROM_read(0x12);//读取EEPROM的数值,存入八位数据中
	TurnHZ = TurnHZ_L*100;//数据增大100倍,使流转时间单位变为1ms
	if(TurnHZ < 400 || TurnHZ % 100 != 0 || TurnHZ>1200)
		TurnHZ = 400;
/*
此处if的作用是防止初次上电EEPROM中无存储数据
*/
	while(1)			//进入主循环
	{
		if(LED_ON)		//LED状态数为开启时
		{
			if(Fms)     //到达流转时间间隔
			{
				Fms = 0;//清零状态数
				LED_Cal();//计算LED状态
			}
			LED_Con(TurnMod);//根据状态选择点亮的LED
	  }
		if(F200ms)		//达到200ms间隔
		{
			SMG_Cal();	//计算一次数码管对应的数值
			F200ms = 0; //置零状态数
		}
		Key_main();		//扫描按键
		if(FADC)		//到达读取PCF8691数据的时间间隔
		{
			PWM_NUM = PCF8591NUM();//PWM数值读取
			FADC = 0;	//置零状态数
		}
	}
}

/*定时器0中断主要负责
数码管的刷新,按键的扫描,
以及各种时间状态数的计算*/
void it0() interrupt 1	
{
	static uint16 flag_TurnHZ =0;//流转间隔时间计数
	static uint8 flag_200ms = 0; //200ms间隔时间计数
	static uint16 pdata flag_SMGOFF = 0;//数码管闪烁时间计数
	static uint8 flag_ADC = 0;	 //PCF8591时间计数
	TH0 = (65536 - 1000)/256;	 //1ms时间间隔
	TL0 = (65536 - 1000)%256;
	
	if(LED_ON)				//当LED为开启状态
	{
		flag_TurnHZ ++;		//流转时间间隔计数加1ms
		if(flag_TurnHZ == TurnHZ)//到达流转间隔时间
		{
			Fms = 1;		//运行模式时间抵达状态数置一
			flag_TurnHZ = 0;//归零
			TurnMod++;		//流转模式状态数加一
			//这里流转模式状态数运用了自定义规则存储数据,到LED页细说
		}
	}
	flag_SMGOFF++;			//数码管闪烁时间计数
	flag_200ms++;			//200ms间隔时间计数	
	flag_ADC++;				//PCF8591读取间隔时间计数
	
	if(flag_ADC >= 100)		//PCF8591读取间隔到100ms
	{
		flag_ADC = 0;
		FADC = 1;
	}
	if(flag_200ms >= 200)	//200ms间隔时间
	{
		flag_200ms = 0;
		F200ms = 1;
	}
	if(flag_SMGOFF >= 800)	//数码管闪烁时间到800ms
	{
		flag_SMGOFF = 0;
		SMG_OFF = ~SMG_OFF;
	}
	
	SMG_Scan();			//数码管扫描
	Key_Scan();			//按键扫描
}

/*定时器1中断负责PWM输出*/
void PWM_it1() interrupt 3
{
	static uint8 HZZ = 0;
	TH1 = (65536 - 100)/256; //100hz每秒
	TL1 = (65536 - 100)%256;
	ET0 = 0;
	P0 = 0xff;
	if(HZZ<PWM_NUM && LED_ON)//进入次数小于PWM设定值且led为打开状态时
	{
		Sel_HC138(4);
		P0 = LED_Sta;	//打开LED_Cal()计算的对应LED灯
		Sel_HC138(0);
	}
	else
	{
		Sel_HC138(4);
		P0 = 0xff;		//关闭LED灯,进行PWM输出
		Sel_HC138(0);
	}
	ET0 = 1;
	HZZ++;
	if(HZZ>=100)		//第100次进入时归零
		HZZ = 0;
}

关于定时优先级问题,数码管扫描的优先级应大与PWM输出,防止数码管扫描过程种数据被干扰。

不过还好,默认定时器0中断大与定时器1中断。

让后是LED页
首先先看一下我们自定义的LED转变模式的状态功能
蓝桥杯单片机第九届初赛主观题-----彩灯控制系统
根据此表格编写

#ifndef _LED_H
#define _LED_H
extern uint8 LED_Sta;

void LED_Con(uint8 T_Mod);
void LED_Cal();

#endif
#include "Sys.h"

uint8 LED_Sta = 0xff;


void MOD3(uint8 n)	//模式3
{
	switch(n)
	{
		case 0x01:
			LED_Sta = ~0x81;
		break;
		case 0x02:
			LED_Sta = ~0x42;
		break;
		case 0x03:
			LED_Sta = ~0x24;
		break;
		case 0x04:
			LED_Sta = ~0x18;
		break;
		default:break;
	}
}
void MOD4(uint8 n)	//模式4
{
	switch(n)
	{
		case 0x04:
			LED_Sta = ~0x81;
		break;
		case 0x03:
			LED_Sta = ~0x42;
		break;
		case 0x02:
			LED_Sta = ~0x24;
		break;
		case 0x01:
			LED_Sta = ~0x18;
		break;
		default:break;
	}
}

void LED_Cal() //LED状态数值计算
{
		if((TurnMod>>4)<=0x02)
		{
			if((TurnMod&0x0f) >= 0x09)
			{
				TurnMod = ((TurnMod&0xf0)|0x01)+16;
			}
		}
		else if((TurnMod>>4)>0x02)
		{
			if((TurnMod&0x0f) >= 0x05)
			{
				TurnMod = ((TurnMod&0xf0)|0x01)+16;
				if((TurnMod>>4)>=0x05)
					TurnMod = 0x11;
			}
		}
}

void LED_Con(uint8 T_Mod)
{
	uint8 n;
	n = (T_Mod&0x0f);
	switch(T_Mod>>4)
	{
		case 0x01:LED_Sta = ~(0x80>>n);break;
		case 0x02:LED_Sta = ~(0x01<<n);break;
		case 0x03:MOD3(n);break;
		case 0x04:MOD4(n);break;
		default:break;
	}
}

iic是官方给的页面,但是具体部件的读取写入还得看自己,所以这里就只放具体部件的读取与写入
iic的读取与写入对时许要求严格,此期间必须关闭中断!!!

uint8 PCF8591NUM()
{
	uint8 Val;
	EA = 0;
	IIC_Start();				//IIC启动
	IIC_SendByte(0x90); 		//IIC选择地址,并发送写指令
	IIC_WaitAck();				//等待IIC应答
	IIC_SendByte(0x03);			//选择模拟量输入通道
	IIC_WaitAck();
	IIC_Stop();    				//IIC停止,接下来开始读取数据
	
	IIC_Start();				//IIC启动
	IIC_SendByte(0x91); 		//IIC选择地址,并发送读指令
	IIC_WaitAck();				//IIC等待应答
	Val = IIC_RecByte();		//接收PCF8591数据
	IIC_SendAck(0);				//发送应答位
	IIC_Stop();
	EA = 1;
	if(Val>=0&&Val<50)
		Val = 10;
	else if(Val>=50&&Val<100)
		Val = 40;
	else if(Val>=100&&Val<140)
		Val = 70;
	else if(Val>=140&&Val<256)
		Val = 100;
	/*根据所得ad值返回四种不同的数据给PWM*/
	return Val;
}

/*addr指eeprom的存储地址,Val指要输入的数据*/
void EEPROM_write(uint8 addr,uint8 Val)//EEPROM写入
{
	EA = 0;
	IIC_Start();
	IIC_SendByte(0xa0);	
	IIC_WaitAck();
	IIC_SendByte(addr);
	IIC_WaitAck();
	IIC_SendByte(Val);
	IIC_WaitAck();
	IIC_Stop();
	
	EA = 1;
}
/*addr指eeprom的存储地址*/
uint8 EEPROM_read(uint8 addr)//EEPROM读取
{
	uint8 Val;
	EA = 0;
	IIC_Start();				//IIC启动
	IIC_SendByte(0xa0); 		//IIC选择地址,并发送写指令
	IIC_WaitAck();				//等待IIC应答
	IIC_SendByte(addr);			//选择地址
	IIC_WaitAck();
	IIC_Stop();    				//IIC停止,接下来开始读取数据
	
	IIC_Start();				//IIC启动
	IIC_SendByte(0xa1); 		//IIC选择地址,并发送读指令
	IIC_WaitAck();				//IIC等待应答
	Val = IIC_RecByte();		//接收EEPROM数据
	IIC_SendAck(0);				//发送应答位
	IIC_Stop();
	EA = 1;
	return Val;
}
#ifndef _IIC_H
#define _IIC_H

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); 
uint8 PCF8591NUM();
void EEPROM_write(uint8 addr,uint8 Val);
uint8 EEPROM_read(uint8 addr);

#endif

让后是按键页,基本方法基于金沙滩按键教学

#ifndef _KEYBOARD_H
#define _KEYBOARD_H

sbit KEY4 = P3^3;
sbit KEY5 = P3^2;
sbit KEY6 = P3^1;
sbit KEY7 = P3^0;

void Key_Action(uint8 KeyCode);
void Key_main();
void Key_Scan();

extern uint8 code Key_Num[4];
extern uint8 Key_Sta[4];


#endif
#include "Sys.h"

uint8 code Key_Num[4] = {
	4,5,6,7
};

uint8 Key_Sta[4] = {
	1,1,1,1
};

//改变各种状态数与存储数据实现功能
void Key_Action(uint8 KeyCode)
{
	switch(KeyCode)
	{
		case 4:
			
		switch(Con_Mod>>4)
			{
			case 0x00:
				Tmp_Show = 1;
			break;
			//查看温度
				case 0x0f:
					TurnMod = (TurnMod-16)&0xf0;
					if(TurnMod < 0x10)
						TurnMod = 0x40;
				break;
				//运行模式+1
				case 0x0e:
					TurnHZ -= 100;
				if(TurnHZ < 400)
					TurnHZ = 1200;
				break;
				//流转间隔模式加100ms
				default:break;
			}
		
		break;
		
		case 5:
			switch(Con_Mod>>4)
			{
				case 0x0f:
					TurnMod = (TurnMod+16)&0xf0;
					if(TurnMod > 0x40)
						TurnMod = 0x10;
				break;
				//运行模式+1
				case 0x0e:
					TurnHZ += 100;
				if(TurnHZ > 1200)
					TurnHZ = 400;
				TurnHZ_L = TurnHZ/100;
				EEPROM_write(0x12,TurnHZ_L);
				break;
				//流转间隔模式加100ms
				default:break;
			}
		break;
		
		case 6:
			switch(Con_Mod>>4)
			{
				case 0x00:
					Con_Mod = 0xf0;
				break;
				//转入模式编号模式
				case 0x0f:
					Con_Mod = ((Con_Mod&0x00)|0xe0)|(TurnHZ/100);
				break;
				//转入流转间隔模式
				case 0x0e:
					Con_Mod = 0x00;
				break;
				//转入数码管熄灭
				default:break;
			}
		break;
		
		case 7:LED_ON = ~LED_ON;break;//流转模式开关
		default:break;
	}
}

void Key_main()		//按键识别
{
	static uint8 backup[4] = {
		1,1,1,1
	};
	uint8 i;
	for(i=0;i<4;i++)
	{
		if(backup[i] != Key_Sta[i])
		{
			if(backup[i] == 1)//按键按下状态
			{
				Key_Action(Key_Num[i]);
			}
			if(backup[i] == 0)//按键松开状态
			{
				if(i == 0)
					Tmp_Show = 0;
			}
			backup[i] = Key_Sta[i];
		}
	}
}

void Key_Scan()		//中断种的按键扫描消抖
{
	static uint8 Key_Buf[4] = {
		0xff,0xff,0xff,0xff
	};
	uint8 i;
	
	Key_Buf[0] = (Key_Buf[0]<<1)|KEY4;
	Key_Buf[1] = (Key_Buf[1]<<1)|KEY5;
	Key_Buf[2] = (Key_Buf[2]<<1)|KEY6;
	Key_Buf[3] = (Key_Buf[3]<<1)|KEY7;
	
	for(i=0;i<4;i++)
	{
		if(Key_Buf[i] == 0x00)
			Key_Sta[i] = 0;
		else if(Key_Buf[i] == 0xff)
			Key_Sta[i] = 1;
	}
}

最后就是数码管页,这里需要根据按键改变的状态数改变显示的数据
如表
蓝桥杯单片机第九届初赛主观题-----彩灯控制系统

#ifndef _KEYBOARD_H
#define _KEYBOARD_H

sbit KEY4 = P3^3;
sbit KEY5 = P3^2;
sbit KEY6 = P3^1;
sbit KEY7 = P3^0;

void Key_Action(uint8 KeyCode);
void Key_main();
void Key_Scan();

extern uint8 code Key_Num[4];
extern uint8 Key_Sta[4];


#endif
#include "Sys.h"

uint8 SMG_NUM[10] = {
	0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90
};
uint8 SMG_SHOW[8] = {
	0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
};

bit SMG_OFF = 0;

//该函数结合上图表看!!!
void SMG_Cal()
{
	SMG_SHOW[4] = 0xff;
	if(Con_Mod != 0 && Tmp_Show != 1)
	{
			switch(Con_Mod>>4)
			{
				case 0x0f:
					if(SMG_OFF)
					{
						SMG_SHOW[7] = 0xff;
						SMG_SHOW[6] = 0xff;
						SMG_SHOW[5] = 0xff;
					}
					else
					{
						SMG_SHOW[7] = 0xbf;
						SMG_SHOW[6] = SMG_NUM[TurnMod>>4];
						SMG_SHOW[5] = 0xbf;
					}
					if((TurnHZ/1000)%10==0)
							SMG_SHOW[3] = 0xff;C
						else
							SMG_SHOW[3] = SMG_NUM[(TurnHZ/1000)%10];
					SMG_SHOW[2] = SMG_NUM[(TurnHZ/100)%10];
					SMG_SHOW[1] = SMG_NUM[(TurnHZ/10)%10];
					SMG_SHOW[0] = SMG_NUM[TurnHZ%10];
				break;
				case 0x0e:
					SMG_SHOW[7] = 0xbf;
					SMG_SHOW[6] = SMG_NUM[TurnMod>>4];
					SMG_SHOW[5] = 0xbf;
					if(SMG_OFF)
					{
						SMG_SHOW[2] = 0xff;
						SMG_SHOW[1] = 0xff;
						SMG_SHOW[0] = 0xff;
						SMG_SHOW[3] = 0xff;
					}
					else
					{
						if((TurnHZ/1000)%10==0)
							SMG_SHOW[3] = 0xff;
						else
							SMG_SHOW[3] = SMG_NUM[(TurnHZ/1000)%10];
						SMG_SHOW[2] = SMG_NUM[(TurnHZ/100)%10];
						SMG_SHOW[1] = SMG_NUM[(TurnHZ/10)%10];
						SMG_SHOW[0] = SMG_NUM[TurnHZ%10];
					}
				break;
				default:break;
			}
	}
	else if(Con_Mod==0 && Tmp_Show!=1)
	{
		SMG_SHOW[7] = 0xff;
		SMG_SHOW[6] = 0xff;
		SMG_SHOW[5] = 0xff;
		SMG_SHOW[3] = 0xff;
		SMG_SHOW[2] = 0xff;
		SMG_SHOW[1] = 0xff;
		SMG_SHOW[0] = 0xff;
	}
	else if(Tmp_Show == 1)
	{
		SMG_SHOW[7] = 0xff;
		SMG_SHOW[6] = 0xff;
		SMG_SHOW[5] = 0xff;
		SMG_SHOW[3] = 0xff;
		SMG_SHOW[2] = 0xff;
		SMG_SHOW[1] = 0xbf;
		switch(PWM_NUM)
		{
			case  10:SMG_SHOW[0] = SMG_NUM[1];break;
			case  40:SMG_SHOW[0] = SMG_NUM[2];break;
			case  70:SMG_SHOW[0] = SMG_NUM[3];break;
			case 100:SMG_SHOW[0] = SMG_NUM[4];break;
			default:break;
		}
	}
}
//数码管动态刷新————基于金沙滩数码管教学
void SMG_Scan()
{
	static uint8 index = 0;
	Sel_HC138(7);
	P0 = 0xff;
	Sel_HC138(0);//数码管消隐

	Sel_HC138(6);
	P0 = (0x80>>index);
	Sel_HC138(0);
	
	Sel_HC138(7);
	P0 = SMG_SHOW[index];
	Sel_HC138(0);
	
	index++;
	index &= 0x07;
}

总结

这次写代码明显不够细心,好几次忘记把状态位置零,并且iic读取数据是关闭中断也忘记,导致开始LED闪烁没办法,这次算是长个教训。。。

源码度娘

链接:https://pan.baidu.com/s/10vvrz0IukBPUC5V2ysLUjQ
提取码:xly2

上一篇:浅谈三种近场通信技术特点以及未来应用场景分析与预测


下一篇:基于单片机智能浇花控制系统设计-毕设课设