一、题目
二、难点分析
以前没有做过这种逻辑的题目,对我来说,难点有两处:
① 如何判断按键是否3s内有被按过?
我的想法:有些复杂,实现起来bug很多,说出来当反面教材。为每一个按键都设置一个状态变量和一个计时变量。状态变量来表示某个按键是否被按下,计时变量在中断服务程序中负责记录过了多少ms。如果按下某按键就修改状态变量为1,代表可以开始计数,然后在定时中断中,如果状态变量为1就对计时变量+1,如果计时变量到3000,就让状态变量归0。但随后我就发现思路有bug,就是3s内我再次按下按键,那计时变量按理说应该重新计时才对,于是我就修改,当按键按下后,不仅要修改状态变量为1,还要设置计数初值为0,这样确保无论何时按下按键,都能让计时从头开始。
我这个思路非常的冗杂,配合LED实现起来特别难。
大佬想法:参观了4T测评大佬分享出来的代码,给我呈现出了一个全新的思路。
我称之为“倒计时算法”。
即无需使用状态变量,仅需为每个按键分别配一个计时变量(初始值0)。
当按键按下后,启动倒计时,将计时变量设置为3000(3s)。
然后写一个用于放在中断服务程序中的函数LED_DIS(),这个函数负责执行led逻辑。
led 逻辑受按键按下3s的控制,由于有倒计时,所以这个实现起来非常便利。
只需要在这个函数中判断计时变量是否为0,就可以选择执行接下来的led灯亮灭的功能。
//L1
if(time[0]) //如果倒计时不为0,则进入倒计时-1和led亮起程序
{
time[0]--; // 此函数会放在中断服务程序,所以此倒计时 1ms 减 1
led &= ~1; // ~是按位取反,1是0000 0001,~1就是1111 1110;&=就是led和~1按位与运算,也就是将第0位置0,点亮 L1
}
else
led |= 1; // |是按位或,也就是将最低位置1,熄灭L1
代码解释:
如果计时变量不为0就将计时变量-1
点亮L1
如果计时变量是0
熄灭L1
太妙了!!!!!!
② 如何让指示灯 0.1s 间隔闪烁?
a. 如何控制 LED的某一位亮灭?
一句话:位运算在指示灯这块非常实用。
我的思路:还是先说一下我的反面教材,我先开始直接对P0口赋值,这就非常容易出错呀,因为假如两个按键同时在3s内按下,你说人家该听按键1对应的P0逻辑,还是按键2对应的P0逻辑呢???所以肯定不能直接对P0赋值。
大佬思路:看了大佬led这块用的全是按位运算符。
总的来说,就是要先定义一个名为 led 的变量,用于存储最终要传给P0口的值,然后对led变量根据不同的情况进行对应的按位运算,实现对不同位的准确处理,最后再把led变量给P0。
这种做法非常稳健,形象点说,就是先考虑好所有的情况再出手。led变量相当于一个盘子,先把所有人要点的菜都放上去,再端到餐厅分发给大家,有条不紊。
而不是像我的做法那样,我只有一个服务员,顾客A说要牛奶,服务员送过去了,顾客B说要酸奶,服务员送过去了。假如顾客AB前后相差大于3s来点餐,服务员是处理得过来的。但假如顾客AB在三秒内同时点餐,就来不及给他们同时处理了。
符号 | 意义 | 效果 | 代码 | 备注 |
& | 按位与 | 按位置 0 | led &= ~1; | 0 对应的位置0有效 |
| | 按位或 | 按位置 1 | led |= 1; | 1 对应的位置1有效 |
^ | 按位 异或 | 按位 取反 | led ^= 8; | 1 对应的位取反有效 |
总结:
想要亮,就用与&;
想要灭,就用或|;
想要闪烁(反转),就用异或^;
b.如何控制LED等间隔闪烁?
前面讲倒计时算法的时候,最重要的部分就是倒计时变量 ,所以此处也充分利用了这个变量来让LED每隔0.1秒闪烁一次。
if(time[3])
{
time[3]--;
if(time[3]%100==0) //小巧思:如果倒计时是100的整数倍,说明应该闪烁了
led ^= 16; // ^是按位异或,16就是0001 0000,第4位是1
} //效果:把第4位取反
else
led |= 16; // 熄灭L5
“小巧思”代码解释:
如果倒计时除以100的余数是0(倒计时是100的整数倍),说明距离按键按下已经100ms(0.1s),那么就让led变量的第4位(0001 0000=16),进行反转,从而实现定时闪烁。
三、main.c完整代码
#include <STC15F2K60S2.H>
#define Y0C P2=P2&0X1F|0X00
#define Y4C P2=P2&0X1F|0X80
#define Y5C P2=P2&0X1F|0XA0
#define Y6C P2=P2&0X1F|0XC0
#define Y7C P2=P2&0X1F|0XE0
typedef unsigned char u8;
typedef unsigned int u16;
//define variate
u16 time[4]={0}; //用于存放四个按键的倒计时
u8 led=0xff; //用于记录led的数据口,记得传递给P0
void init()
{
Y4C;P0=0XFF;Y0C;
Y5C;P0=0X00;Y0C;
Y6C;P0=0XFF;Y0C;
Y7C;P0=0XFF;Y0C;
}
void Timer1Init() //1毫秒@12.000MHz
{
AUXR |= 0x40; //定时器时钟1T模式
TMOD &= 0x0F; //设置定时器模式
TL1 = 0x20; //设置定时初值
TH1 = 0xD1; //设置定时初值
TF1 = 0; //清除TF1标志
TR1 = 1; //定时器1开始计时
ET1 = 1;
EA = 1;
}
void Delay10ms()
{
unsigned char i, j;
i = 117;
j = 184;
do
{
while (--j);
} while (--i);
}
void key()
{
//S4 L1
P44=0; P42=1; P35=1; P34=1;
if(P33==0)
{
Delay10ms();
if(P33==0)
time[0]=3000; //按键按下,次按键对应的倒计时置为3000(3s)
while(!P33);
}
//S9 L2
P44=1; P42=0; P35=1; P34=1;
if(P32==0)
{
Delay10ms();
if(P32==0)
time[1]=3000;
while(!P32);
}
//S14 L4 0.1
P44=1; P42=1; P35=0; P34=1;
if(P31==0)
{
Delay10ms();
if(P31==0)
time[2]=3000;
while(!P31);
}
//S19 L5 0.1
P44=1; P42=1; P35=1; P34=0;
if(P30==0)
{
Delay10ms();
if(P30==0)
time[3]=3000;
while(!P30);
}
}
void led_dis()
{
//L1
if(time[0]) //如果倒计时不为0,则进入倒计时-1和led亮起程序
{
time[0]--; // 此函数会放在中断服务程序,所以此倒计时 1ms 减 1
led &= ~1; // ~是按位取反,1是0000 0001,~1就是1111 1110;&=就是led和~1按位与运算,也就是将第0位置0,点亮 L1
}
else
led |= 1; // |是按位或,也就是将最低位置1,熄灭L1
//L2
if(time[1])
{
time[1]--;
led &= ~2; // ~是按位取反,2是0000 0010,~2就是1111 1101;&=就是led和~2按位与运算,也就是将第1位置0,点亮 L2
}
else
led |= 2; // 熄灭L2
//L4 0.1s
if(time[2])
{
time[2]--;
if(time[2]%100==0) //小巧思:如果倒计时是100的整数倍,说明应该闪烁了
led ^= 8; // ^是按位异或,8就是0000 1000,led假如是1111 1101,那结果就是1111 0101;
} //led假如是1111 0101,那结果就是1111 1101
//按位异或,0无效,原来是什么就还是什么;1有效,把原来是0的改为1,原1改为0。
//效果:把第3位取反
else
led |= 8; // 熄灭L4
//L5 0.1s
if(time[3])
{
time[3]--;
if(time[3]%100==0) //小巧思:如果倒计时是100的整数倍,说明应该闪烁了
led ^= 16; // ^是按位异或,16就是0001 0000,第4位是1
} //效果:把第4位取反
else
led |= 16; // 熄灭L5
//LED显示
Y4C;P0=led;Y0C;
}
void tm1_isr() interrupt 3
{
led_dis(); // 倒计时控制的led控制逻辑函数
}
main()
{
init();
Timer1Init();
while(1)
{
key();
}
}