1. 一些其他知识
在头文件#include <REGX52.H>
下,LED灯的亮灭除了通过16进制统一控制一排(给P2
赋值),还可以给单个位置0/1来控制亮灭
在#include <REGX52.H>
下还单个定义了每一位:
通过给每一位赋值0/1可以控制LED灯亮/灭:
P2_X = 0; // 灯亮,高电平和单片机低电平形成电压差产生电流
P2_X = 1; // 灯灭,无电压差
2. 独立按键控制LED灯亮灭
独立按键是一种电子开关,按下开关接通,松开开关断开,其实现原理是通过轻触按键内部金属弹片受力弹动来实现接通和断开。
从电路图上来看,K1-K4分别接到单片机的P30-P33接口(这个在单片机板子上也有体现),另一侧是接地的。则说明:按钮按下时,读取单片机对应接口的电平应该是低电平(0),按钮松开时,读取单片机对应接口的电平应该是高电平(1)。
注意: K1按钮连接的是P31,K2按钮连接的是P30,这两个是反的,我们的需求是按下K1按钮灯亮,松开K1按钮灯灭
知道了这个原理就可以开始写代码了:
#include <REGX52.H>
int main(void)
{
while (1){
if (P3_1 == 0) // 按钮按下时,读取对应接口为低电平
{
P2_0 = 0; // 此时LED灯亮起
}
else // 否则
{
P2_0 = 1; // LED灯灭
}
}
}
结果如下(视频转gif居然超过大小了。。。。放静态图吧):
按下按钮:
松开按钮:
3. 独立按键控制LED状态(按一下变一次状态)——初版
理论上来说,我们只需要在每次按下按钮的时候,让LED灯的状态取反,就能够实现按一下变一次状态,那么我们可以写出以下代码:
#include <REGX52.H>
int main(void)
{
while (1)
{
if (P3_1 == 0)
{
P2_0 = ~P2_0;
}
}
}
但是得到的结果如下,会发现按键的状态不稳定:
按下松开,按钮亮了就灭,退化回了2中的结果
按下:
松开:
按下松开,按钮亮了,再按一下,按钮不会灭
按下第一次:
按下第二次:
按下松开,按钮没反应,一直是灭的
按下:
松开:
这是因为这种按键在按下弹起时候是存在抖动的,具体见4的介绍,修改后的代码在5
4. 按键消除抖动
开关按下和弹起的时候是会存在抖动的,这种抖动的时间很短,所以肉眼上基本观察不到。
这种抖动导致的问题是:按一下开关可能会产生按多下开关的效果,如下图中,按下的时候,会在高低电平中抖动,看起来像按了三下(三次低电平),弹起时候看起来又像是按了两-三下(产生三次低电平),从而我们按3的方式去写代码,会导致结果的不稳定
消除抖动可以从两个层面解决:
硬件层面:加电容消抖
软件层面:加延时消抖
5. 改进独立控制LED灯状态(按一下变一次状态)——终版
本小节用于个人不断校正思路,需要最终版直接看最下面的代码
我的第一个想法是:按下代码的前面写一个延时函数,避免按下时候的抖动
#include <REGX52.H>
#include <INTRINS.H>
void Delay(unsigned int ms) //@11.0592MHz
{
unsigned char i, j;
while (ms)
{
_nop_();
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
ms--;
}
}
int main(void)
{
while (1)
{
if (P3_1 == 0)
{
Delay(20);
P2_0 = ~P2_0;
}
}
}
观察结果发现,还是不稳定,有时候能够实现,但有时候按下亮松开又灭了,长按时候还会发现灯一直在闪
这是因为按下的时候就一直在执行if语句里面的代码,不断重复延时-取反-延时-取反的情况,所以灯就会延时-亮-延时-灭……
这意味着我们没有对弹起时候不稳定的状态做处理,我们应该在松手的时候加延时处理
于是修改main函数如下:
int main(void)
{
while (1)
{
if (P3_1 == 0)
{
Delay(20);
P2_0 = ~P2_0;
}
if (P3_1 == 1)
{
Delay(20);
}
}
}
烧录后发现这样的结果还是不理想,灯还是时暗时亮的,状态不稳定。
分析一下,是因为松手时候还会产生抖动,假设一种按钮状态为弹起-按下-弹起-按下
,并假设灯是亮的情况,这时候会产生:延时20ms-延时20ms并取反(灭)-延时20ms-延时20ms并取反(亮)
,所以会导致灯亮时按按钮不灭,其他情况可以自己举例分析,所以这种做法是不可行的
重新分析我们的目标:
按钮按下,状态不变
松开按钮,立即延时
所以最终的代码应该是这样的:
#include <REGX52.H>
#include <INTRINS.H>
void Delay(unsigned int ms) //@11.0592MHz
{
unsigned char i, j;
while (ms)
{
_nop_();
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
ms--;
}
}
int main(void)
{
while (1)
{
if (P3_1 == 0)
{
Delay(20); // 处理按下时候的抖动
P2_0 = ~P2_0;
while (P3_1 == 0) ; // 按钮始终处于按下情况,状态不变,死循环
// 处理松手时候的抖动
// 判断到按钮弹起,在下面延时20ms避免状态跳跃
Delay(20);
}
}
}
最终结果如下:
按下第一次:
按下第二次:
6. 独立按键控制LED显示二进制
8位二进制一共可以表示2^8=128个数
已知LED显示全灭的时候是P2=0xFF
,也就是二进制的1111 1111
,那么逐步显示到全亮P2=0x00
,也就是逐步减1:
0xFF(1111 1111)
0xFE(1111 1110)
0xFD(1111 1101)
…
0x02(0000 0010)
0x01(0000 0001)
0x00(0000 0000)
为此,我们只需要将LED灯初始化为0xFF,逐步减一就行,代码如下:
#include <REGX52.H>
#include <INTRINS.H>
void Delay(unsigned int ms) //@11.0592MHz
{
unsigned char i, j;
while (ms)
{
_nop_();
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
ms--;
}
}
int main(void)
{
P2 = 0xFF;
while (1)
{
if (P3_1 == 0)
{
Delay(20);
P2 = P2 - 1;
while (P3_1 == 0) ;
Delay(20);
}
}
}
效果如下:
gif图超大小了,放三张图展示连续按三次的情况
7. 独立按键控制LED移位
这个也很简单,只需要用“<<”或“>>”运算符移位一下就可以
先列举出期望的状态:
1111 1110
1111 1101
1111 1011
…
1101 1111
1011 1111
0111 1111
因为左移右移都是补0,我们尽量搞出补0的状态,直接将上面取反,就会很多0,上面状态取反的结果是:
0000 0001(<<0)
0000 0010(<<1)
0000 0100(<<2)
…
0010 0000(<<5)
0100 0000(<<6)
1000 0000(<<7)
代码如下:
#include <REGX52.H>
#include <INTRINS.H>
void Delay(unsigned int ms) //@11.0592MHz
{
unsigned char i, j;
while (ms)
{
_nop_();
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
ms--;
}
}
int main(void)
{
unsigned int LEDNUM = 0; // 统计位移数
while (1)
{
if (P3_1 == 0)
{
Delay(20);
if (LEDNUM == 8) LEDNUM = 0; // 位移为8时重置为0
P2 = ~((0x01) << LEDNUM); // 基于(0000 0001)左移LEDNUM位
LEDNUM++; // 左移位数+1
while (P3_1 == 0) ;
Delay(20);
}
}
}
结果如下:
如果想要从右向左亮,只需要修改给P2赋值的代码为:
P2 = ~((0x80) >> LEDNUM);
只需要照着上面的逻辑分析一遍就可以得出这段修改,效果如下: