编程技巧——非阻塞式编程

一、非阻塞式按键控制LED闪烁

  • 按键按下时由于弹片的抖动,会有一瞬间是开合开合,处于不稳定的状态,这种叫做按键的抖动,按键消抖分为硬件消抖和软件消抖,硬件消抖就是增加一个电容消除抖动是产生的杂波,而软件消抖就是在检测到按键被按下之后的一小段时间之后,再检测信号,如果信号跟上次信号不一致,则认为是抖动,不给予处理,如果前后一致,则说明按键被按下。
  • 按键的消抖要求不能阻塞主程序,按键灵敏。所以我们使用延迟的方式则不合适,会阻塞主程序的运行,所以下列使用定时器来做为按键的消抖。
  • LED的控制也是不能阻塞主程序的运行,点亮LED之后,程序继续往下运行,等到时间之后再来关闭LED,如此反复控制,不会造成阻塞。

1、流程

  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不会等待,程序很快执行结束。

  1. 按键非阻塞式写法,利用定时器定时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程序进行改造。
  1. 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、下载程序并观察现象

  1. 我们将程序下载到开发板,按下按键,按键灵敏,长按按键时,OLED显示的i值也会不断自增,没有发生阻塞现象,并且LED点亮时,i 的值也正常自增,没有被LED灯的亮灭所影响,说明此次程序的按键检测程序跟LED的控制程序都是非阻塞式的程序,并不会阻塞程序的运行。
  • 此案例是学习b站江协科技教程所写。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值