蓝桥杯单片机第九届初赛主观题-----彩灯控制系统
前言
俺又来了,这次连续好几天都没写博客,真不是我偷懒,这次是真的写了好久!各种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