一、非阻塞式按键控制LED闪烁
- 按键按下时由于弹片的抖动,会有一瞬间是开合开合,处于不稳定的状态,这种叫做按键的抖动,按键消抖分为硬件消抖和软件消抖,硬件消抖就是增加一个电容消除抖动是产生的杂波,而软件消抖就是在检测到按键被按下之后的一小段时间之后,再检测信号,如果信号跟上次信号不一致,则认为是抖动,不给予处理,如果前后一致,则说明按键被按下。
- 按键的消抖要求不能阻塞主程序,按键灵敏。所以我们使用延迟的方式则不合适,会阻塞主程序的运行,所以下列使用定时器来做为按键的消抖。
- LED的控制也是不能阻塞主程序的运行,点亮LED之后,程序继续往下运行,等到时间之后再来关闭LED,如此反复控制,不会造成阻塞。
1、流程
- LED初始状态熄灭→按键按下切换到常亮→再按下切换到慢闪(亮500ms灭500ms)→再按下切换到快闪(亮50ms灭50ms)→再按下切换为点闪(亮100ms灭900ms)→再按下变为初始状态熄灭。
2、阻塞和非阻塞
1. 阻塞:执行某段程序时,CPU因为需要等待延时或者等待某个信号而被迫处于暂停状态一段时间,程序执行时间较长或者时间不定。
- 阻塞式写法,当按下按键时,绿色LED灯开始慢闪,再按一下时,LED灯关不掉,需要长按才可以关闭LED,原因是因为当LED在闪烁时,按下按键时刚好程序处于阻塞函数(Delay_ms(500))中,检测不到按键被按下,所以表现为不灵敏。接上OLED屏幕查看while循环的次数时,当按住按键时,循环次数不增加,因为此时处于按键的检测程序中,此程序为阻塞程序。
按键部分的阻塞程序:
uint8_t KEY_getKeyNumber(void) {
uint8_t state = 0;
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_12) == 0) {
Delay_ms(3);
while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_12) == 0);
Delay_ms(3);
state = 1;
}
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_13) == 0) {
Delay_ms(3);
while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_13) == 0);
Delay_ms(3);
state = 2;
}
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_14) == 0) {
Delay_ms(3);
while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_14) == 0);
Delay_ms(3);
state = 3;
}
return state;
}
主函数中的LED部分的阻塞函数:
uint16_t i;
uint8_t keyNum;
uint8_t onFlag;
int main(void) {
OLED_init();
LED_init();
KEY_init();
OLED_showString(1, 1, "number:");
while (1) {
i ++;
OLED_showNum(1, 8, i, 5);
keyNumber = KEY_getKeyNumber();
if (keyNumber == 1) {
onFlag = !onFlag;
}if (onFlag == 1) {
LED_greenOn();
Delay_ms(500);
LED_greenOff();
Delay_ms(500);
} else {
LED_greenOff();
}
}
}
2. 非阻塞:执行某段程序时,CPU不会等待,程序很快执行结束。
- 按键非阻塞式写法,利用定时器定时1ms触发一次中断,在中断里面调用按键的检测函数,而按键的检测函数每20ms读取按键的状态,来判断按键是否被按下。这样就不会阻塞程序。
- 读取按键的状态函数:
uint8_t Key_Getstate(void) {
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_12) == 0) {
return 1;
}
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_13) == 0) {
return 2;
}
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_14) == 0) {
return 3;
}
else {
return 0;
}
}
- 1ms的中断函数,每1ms调用1次按键检测函数Key_Tick();:
void TIM2_IRQHandler(void) {
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET) {
Key_Tick();
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
- 按键检测函数:
void Key_Tick(void) {
static uint8_t count;
static uint8_t currentState; //当前状态
static uint8_t beforeState; //上次状态
count ++;
if (count >= 20) {
count = 0;
beforeState = currentState; //将当前状态传递给上次状态
currentState = Key_Getstate(); //读取当前状态,然后两个状态进行对比
if (beforeState != 0 && currentState == 0) { //如果上一次状态不等于0切当前状态等于0,说明按键被按下
keyNumber = beforeState; //确人按键被按下之后,读取是哪个按键的键码
}
}
}
- 获取键码:
/* 检测哪个按键被按下 */
uint8_t Key_getKeyNumber(void) {
uint8_t temprorary = 0;
temprorary = keyNumber;
keyNumber = 0; //将键码赋值给临时变量,然后键码清零,不然会影响到下次的按键检测
return temprorary; //返回键码
}
- 主函数不需要改动,因为我们是改动底层逻辑的函数,运用层的函数我们没有改动,下载程序看到的现象是:按键灵敏,不存在按下每反应,就算按住按键不松手,OLED的i变量的读数也会一直网上加,说明按键程序没有阻塞到主函数的循环,点亮LED之后,读书变化缓慢,说明LED还是阻塞主程序,继续对LED程序进行改造。
- LED非阻塞闪烁程序,思路跟按键的程序差不多,点亮LED之后,程序继续做其他事情,等到设置的时间到了之后,再回来关闭LED即可。
- LED我们分为5个模式,模式0熄灭,模式1常亮,模式2亮500ms灭500ms,模式3亮50ms灭50ms,模式4亮100ms灭500ms,我们先编写设置模式的函数:
uint8_t led1_Mode;
void LED1_setMode(uint8_t mode) {
led1_Mode = mode;
}
- 跟按键的检测函数相似,我们也可以写一个LED灯的控制函数,利用定时器定时1ms触发一次中断,中断函数中调用这个控制函数,调用1次就计数值+1,计数值加到我们所设置的时间节点就可以去控制LED的切换模式,这样就不会阻塞主程序:
uint16_t led1Count;
void LED_Tick(void) {
switch (led1_Mode) {
case 0 :
LED1_Off();
break;
case 1 :
LED1_On();
break;
case 2 :
led1Count ++;
if (led1Count >= 1000) {led1Count = 0;}
if (led1Count < 500) {
LED1_On();
} else {
LED1_Off();
}
break;
case 3 :
led1Count ++;
if (led1Count >= 100) {led1Count = 0;}
if (led1Count < 50) {
LED1_On();
} else {
LED1_Off();
}
break;
case 4 :
led1Count ++;
if (led1Count >= 1000) {led1Count = 0;}
if (led1Count < 100) {
LED1_On();
} else {
LED1_Off();
}
break;
}
}
- 将上面的函数放在定时器中断函数中,每1ms调用1次此函数。
void TIM2_IRQHandler(void) {
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET) {
Key_Tick();
LED_Tick();
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
- 我们将主函数关于LED灯函数修改一下,使之不阻塞程序,并加上OLED显示while循环次数,方便观察程序是否发生阻塞现象:
uint16_t i;
uint8_t led1Mode;
uint8_t keyNum;
int main(void) {
OLED_init();
LED_init();
Key_init();
Timer_Init();
OLED_showString(1, 1, "number:");
OLED_showString(2, 1, "led1mode:");
while (1) {
OLED_showNum(1, 8, i++, 5);
OLED_showNum(2, 10, led1Mode, 1);
keyNum = Key_getKeyNumber();
if (keyNum == 1) {
led1Mode ++;
if (led1Mode > 4) {led1Mode = 0;}
LED1_setMode(led1Mode);
}
}
}
3、下载程序并观察现象
- 我们将程序下载到开发板,按下按键,按键灵敏,长按按键时,OLED显示的i值也会不断自增,没有发生阻塞现象,并且LED点亮时,i 的值也正常自增,没有被LED灯的亮灭所影响,说明此次程序的按键检测程序跟LED的控制程序都是非阻塞式的程序,并不会阻塞程序的运行。
- 此案例是学习b站江协科技教程所写。