Everything will be ok!!!!
刷了第二套题,马上要比赛了,不知道自己还能不能再刷几套题呀,还要把底层好好写写呐,最近遇到了一些好的事情,也有一些糟心的事情,但是我相信一切都会好起来的!!!加油!!!
代码参考:<我的GitHub>第九届省赛之彩灯模拟器
题目呀:
分析题目:
功能实现:
一. 流水灯的四个工作模式:
注意看清题目要求:设备固定按照模式 1、模式 2、模式 3、模式 4 的次序循环往复运行(emm,我刚开始就理解错了题意),模式1和模式2类似,模式3和模式4类似,那这里就只举模式1和模式3的例子吧:
void LedMode1()
{
static u8 shift1 = 0x01;//L1-L8
P2 = (P2 & 0x1F) | 0x80;
P0 = ~shift1;
PWMSTA = ~shift1;//不要直接操作P0口!!!!
shift1 <<= 1;
if(shift1 == 0x00)
{
runmode = 2;
shift1 = 0x01;
}
P2 &= 0x1F;
}
void LedMode3()
{
static u8 index = 1;
P2 = (P2 & 0x1F) | 0x80;
if(index == 1)
{
P0 = 0x7E;
PWMSTA = 0x7E;
index+=1;
}
else if(index == 2)
{
P0 = 0xBD;
PWMSTA = 0xBD;
index += 1;
}
else if(index == 3)
{
P0 = 0xDB;
PWMSTA = 0xDB;
index += 1;
}
else if(index == 4)
{
P0 = 0xE7;
PWMSTA = 0xE7;
index = 1;
runmode = 4;
}
P2 &= 0x1F;
}
做几点说明:
u8 flagstart = 0;//0-停止LED流转,1-开始LED流转
流转间隔设置,以及数码管闪烁,这个功能也比较好实现,数码管闪烁的思想和第八届的电子钟是一样的思想,需要注意的一点是:看清题目要求,题目中说 “各工作模式的流转间隔时间需在 E2PROM 中保存,并可在硬件重新上电后,自动载入”,说明是4个不同的模式分别对应四个不同的流转间隔,那么我们需要定义一个数组,用来存储4个模式下的流转间隔,并实现分别设置时间,看代码:
u16 runtime[4] = {400, 400, 400, 400};//四个模式下的闪烁间隔时间(注意类型错误)
void KeyAction(u8 keycode)
{
u8 buff[4];//定义一个数组用来存储缩小10倍后的流转间隔(因为E2PROM里最多写入u8类型的数据)
u8 i;
if(keycode == '1')
{
if(flagstart == 0)
{
flagstart = 1;
}
else if(flagstart == 1)
{
flagstart = 0;
}
}
else if(keycode == '2')
{
if(flagset == 0)
{
flagset = 1;
SetMode = 0;
}
else if(flagset == 1)
{
SetMode += 1;
if(SetMode > 1)
{
flagset = 0;
runmode = 1;
}
}
}
else if(keycode == '3')
{
AddTime();
for(i = 0; i<4; i++)
{
buff[i] = runtime[i] / 10;
}
E2Write(buff, 0x00, 4);//将数组中的值写入E2PROM
//(注意这个写法),写入地址从0x00开始,因为是4个字节的数据,所以字节数写4
}
else if(keycode == '4')
{
SubTime();
for(i = 0; i<4; i++)
{
buff[i] = runtime[i] / 10;
}
E2Write(buff, 0x00, 4);
}
}
三. 加减时间功能:(以加为例,减法类似)
u8 runmode = 1;//运行模式编号
u16 runtime[4] = {400, 400, 400, 400};//四个模式下的闪烁间隔时间
u8 SetMode = 0;//0-闪烁LED的模式,1-闪烁流转间隔
void AddTime()
{
u8 i;
if(SetMode == 0)
{
runmode+=1;
if(runmode >4)
{
runmode = 1;
}
}
else
{
for(i = 0; i<4; i++)
{
if(runmode == i+1)
{
runtime[i] += 100;
if(runtime[i] > 1200)
{
runtime[i] = 400;
}
}
}
}
}
模式1对应runtime[0],模式2对应runtime[1],模式3对应runtime[2],模式4对应runtime[3]
四. 设置模式(这里我写的不是很简便,有很多重复部分,读者可以自己优化一下)numBlinkSta这个标志量是在中断里每隔0.8秒改变一次状态的,这样就实现了数码管每隔0.8秒亮灭了,第八届的题里我有详细写;
u8 numBlinkSta = 1;//0-数码管灭,1-数码管亮
void SetTime()
{
LedBuff[4] = 0xFF;//关闭第5个数码管
if(SetMode == 0)//设置LED的模式
{
if(numBlinkSta)
{
LedBuff[7] = 0xBF;//第8个数码管显示"-"
LedBuff[5] = 0xBF;//第6个数码管显示"-"
LedBuff[6] = LedChar[runmode];//第7个数码管显示运行模式
}
else
{
LedBuff[7] = 0xFF;
LedBuff[5] = 0xFF;
LedBuff[6] = 0xFF;
}
LedBuff[0] = LedChar[runtime[runmode-1] % 10];
LedBuff[1] = LedChar[(runtime[runmode-1]/10) % 10];
LedBuff[2] = LedChar[(runtime[runmode-1]/100) % 10];
if(runtime[runmode-1] < 1000)
{
LedBuff[3] = 0xFF;
}
else
{
LedBuff[3] = LedChar[(runtime[runmode-1]/1000) % 10];
}
}
else if(SetMode == 1)//设置流转间隔
{
if(numBlinkSta)
{
LedBuff[0] = LedChar[runtime[runmode-1] % 10];
LedBuff[1] = LedChar[(runtime[runmode-1]/10) % 10];
LedBuff[2] = LedChar[(runtime[runmode-1]/100) % 10];
if(runtime[runmode-1] < 1000)
{
LedBuff[3] = 0xFF;
}
else
{
LedBuff[3] = LedChar[(runtime[runmode-1]/1000) % 10];
}
}
else
{
LedBuff[0] = 0xFF;
LedBuff[1] = 0xFF;
LedBuff[2] = 0xFF;
LedBuff[3] = 0xFF;
}
LedBuff[7] = 0xBF;
LedBuff[5] = 0xBF;
LedBuff[6] = LedChar[runmode];
}
}
五. 重头戏!!!关于调整亮度!!!用到的也是PWM波的思想,先明确一点:我们是如何实现亮度调节的呢?说白了,就是在极短的一段时间里让小灯灭一段时间,然后再亮一段时间,这样在视觉上看起来灯的亮度就是发生变化的,看代码,有详细注释:
u8 brightness = 0;//亮度等级
void ConfigTimer1()//软件生成的10us定时器(每10us进一次中断)
{
AUXR &= 0xBF; //定时器时钟12T模式
TMOD &= 0x0F; //设置定时器模式
TMOD |= 0x10; //设置定时器模式
TL1 = 0xF7; //设置定时初值
TH1 = 0xFF; //设置定时初值
TF1 = 0; //清除TF1标志
TR1 = 1; //定时器1开始计时
ET1 = 1;//注意这里软件生成的函数里是没有这条语句的,要自己加上!!!!
}
void InterruptTimer1()interrupt 3
{
static u16 PWMCnt = 0;//存储进入中断的次数
TL1 = 0xF7; //设置定时初值
TH1 = 0xFF; //设置定时初值
PWMCnt++;
PWMCnt %= 101;//666的操作,实现满100清零,设置总时间为1ms,即PWM波的频率为1k(在1ms的时间里让小灯灭hightime的时间,其余时间是亮的状态)
//这里就体现了之前的PWM波的思想了,只不过之前的那个是更高级的操作,就是频率可调
//这里我们是直接限定了频率
P2 = (P2 & 0x1F) | 0x80;
if(PWMCnt >= hightime)//如果进入中断扫描的时间大于等于高电平持续的时间
{
P0 = PWMSTA;//点亮对应状态下的led
}
else
{
P0 = 0xFF;//否则关闭所有的小灯
}
P2 &= 0x1F;
}
u8 AdjustDC()//判断高电平的时间,实现调节rb2,灯的亮度发生改变(高电平时间需要在主函数里不断获取,每隔200ms获取一次即可)
{
u16 val;
val = GetADCValue(3);
if(val<60)
{
hightime = 90;
brightness = 1;
}
else if(val <130)//这里必须大于等于130(否则不会显示这个等级的亮度)
{
hightime = 70;
brightness = 2;
}
else if(val <195)//这里必须大于190,emm这个数据是后期调的时候发现的,不太懂为啥
{
hightime = 30;
brightness = 3;
}
else
{
hightime = 5;
brightness = 4;
}
return hightime;
}
emmm,读者可以好好看看注释,还有一点我觉得特别重要的地方!!!就是什么时候读取E2PROM的问题:读取E2PROM里面的数据必须要放在总中断打开之前,主函数里读取一次就可以了,为什么呢?因为我们是一次读取四个字节的数据的,而我们的定时器1是一个10us定时器,时间太快了,会影响到E2PROM读取数据,比如说我们正在读第一个字节的数据,这时候中断来了,我们需要进入中断,那么就会扰乱E2PROM读取数据;
另外还要注意!!!刚下载程序的时候,要先向E2PROM里面写入数据,不能先读取,也就是说下第一遍程序的时候要先把E2Read这个函数屏蔽,看一下代码:
u8 runtimebuff[4];
E2Read(runtimebuff, 0x00, 4);//一个字节是1个u8,E2Prom的地址范围是0x00到0xFF(数组的名字就是地址,不需要再取地址符)
for(i = 0;i <4; i++)
{
runtime[i] = runtimebuff[i] * 10;
}
EA = 1;
CloseOther();
ConfigTimer0(2);
ConfigTimer1();
六. 按下S4显示亮度等级:
这个功能和第八届省赛试题里,按下按键显示温度差不多,我们在AdjustDC这个函数里面改变的亮度等级,只要在按下S4的时候显示出来就可以了,需要注意的是一直按着S4才会显示,一旦松开就不显示了,所以我们需要一直扫描S4的状态,那么我们就在KeyScan这个按键扫描函数里处理不就可以了吗,看代码:
void KeyScan()
{
static u8 keybuff[4] = {0xFF, 0xFF, 0xFF, 0xFF};
u8 i;
keybuff[0] = (keybuff[0] << 1) | KEY_IN_1;
keybuff[1] = (keybuff[1] << 1) | KEY_IN_2;
keybuff[2] = (keybuff[2] << 1) | KEY_IN_3;
keybuff[3] = (keybuff[3] << 1) | KEY_IN_4;
for(i = 0; i<4; i++)
{
if(keybuff[i] == 0x00)
{
KeySta[i] = 0;
if(keybuff[3] == 0x00)
{
if(flagset == 0)
{
ShowNumber(brightness);//注意shownumber函数调用时会自动关闭除显示数字之外的数码管,所以要把他放前面
LedBuff[1] = 0xBF;
}
}
}
else if(keybuff[i] == 0xFF)
{
KeySta[i] = 1;
}
}
}
具体功能就是这样实现的,注意一下细节,打好底层,基本上就可以写出来了,我会再写一遍程序,遇到问题的话还会再更新~
第一遍遇到的问题:
1. nice的操作:到8归零,两种方法:index &= 7(是2的整数次方才能用这个方法)或者!!!!!!index %= 8;(通用!!!!!)
2. 每调试一个地方就要下载验证一下,再次强调一下E2PROM的操作:E2Read(runtimebuff, 0x00, 4);//一个字节是1个u8,E2Prom的地址范围是0x00到0xFF(数组的名字就是地址,不需要再取地址符)
3. 要先往E2PROM里面写入你想写入的数据,然后再读,否则你从E2PROM里面读到的是E2PROM里原有的值!!!!!
4. 另外!!!要特别注意数据溢出的问题!!!!!
5. 关于软件生成的10us定时器,有几点需要注意的问题:
6. 进入中断时,不要忘记设置定时初值!!!!!!!
第二遍遇到的问题: