阻塞和非阻塞
阻塞:执行某段程序时,CPU因为需要等待延时或者等待某个信号而被迫处于暂停状态一段时间,程序执行时间较长或者时间不定
非阻塞:执行某段程序时,CPU不会等待,程序很快执行结束
阻塞状态
程序阻塞状态,下载主循环开始执行,OLED显示i快速++,按住按键1,i停止++(这是因为程序阻塞在等待按键松手的地方),松开按键,LED1闪烁,然后长按才能熄灭LED1(这是因为延时函数)
uint8_t KeyNum;
uint8_t FlashFlag;
uint16_t i;int main(void)
{OLED_Init();LED_Init();Key_Init();//Timer_Init();while (1){KeyNum = Key_GetNum();if(KeyNum == 1){FlashFlag = !FlashFlag;}if(FlashFlag){LED1_ON();Delay_ms(500);LED1_OFF();Delay_ms(500);}else{LED1_OFF();}OLED_ShowNum(1,1,i ++,5);}
}
引入定时器,改进代码
解决按键阻塞状态
解决按键阻塞问题,使得按键不用长按灭灯,使按键更加灵敏,修改Key模块函数
解决了按键按住主循环阻塞问题
方法思路:用定时器扫描按键(往下看,用多按键扫描)
uint8_t Key_Num;void Key_Init(void)
{RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_11;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStructure);
}/**
**获取键码值
**/
uint8_t Key_GetNum(void)
{uint8_t Temp; //定义一个中间变量if (Key_Num) //如果Key_Num不为0{Temp = Key_Num; //赋值给TempKey_Num = 0; //Key_Num清0return Temp; //返回Temp}return 0;
}/**
**非阻塞式获取键码值的子函数
****功能:获取本次按键状态
****返回值:本次按键状态
**/
uint8_t Key_GetState(void)
{if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0){return 1;}if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0){return 2;}return 0;
}/*
让主程序更简洁,方便封装按键
相当于Key模块多了个中断函数Key_Tick,每隔一毫秒自动执行一次
这样就可以实现多模块共用一个定时器来定时
哪个模块想定时了,就定义一个Key_Tick函数,在main函数的中断函数统一调用
*/
void Key_Tick(void) //这个函数1ms执行一次,我们20ms执行一次功能
{static uint8_t Count; //定义一个静态变量,函数默认值为0,函数退出后不会清0static uint8_t CurrState, PrevState;//定义静态变量,分别表示本次状态和上一次状态Count ++; //计次++if (Count >= 20) //大于20ms就执行功能{Count = 0; //Count清零PrevState = CurrState; //上一次状态就是上一次的本次状态CurrState = Key_GetState(); //本次状态的返回值if (CurrState == 0 && PrevState != 0) //捕捉一次按键按下后松手的瞬间{Key_Num = PrevState; //置键码标志位//如果K1按下,在松手瞬间,Key_Num的值就会变成1//如果K2按下,在松手瞬间,Key_Num的值就会变成2}}
}
主函数
按住按键不放,主程序仍然在快速刷新, 松手灯闪烁,主程序以一秒刷新(因为延时函数)。此外按键变得灵敏,不用长按灭灯。
uint8_t KeyNum;
uint8_t FlashFlag;
uint16_t i;int main(void)
{OLED_Init();LED_Init();Key_Init();Timer_Init();while (1){KeyNum = Key_GetNum();if(KeyNum == 1){FlashFlag = !FlashFlag;}if(FlashFlag){LED1_ON();Delay_ms(500);LED1_OFF();Delay_ms(500);}else{LED1_OFF();}OLED_ShowNum(1,1,i ++,5);}
}void TIM2_IRQHandler(void)
{if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET){Key_Tick(); //每一毫秒进一次中断TIM_ClearITPendingBit(TIM2, TIM_IT_Update);}
}
定时器扫描按键
单按键
- 定时中断,每隔20ms读取一次本次引脚值和上次引脚值
- 判断:如果本次是1,上次是0,则表示按键按下且当前处于刚松手的状态
- 置键码标志位,向主程序报告此事件
多按键
- 先写一个获取键码值的子函数(非阻塞式)
- 定时中断,每隔20ms读取一次本次键码值和上次键码值
- 判断:如果本次是0,上次非0,则表示按键按下且当前处于刚松手的状态
- 置键码标志位,向主程序报告此事件
实现LED闪烁的非阻塞
最终目的是任何时候主循环都是不容阻塞的。 (把Delay函数改掉)
定时器实现LED闪烁
- 定时中断,每隔1ms计次变量自增
- 计次变量计到周期值时,归零
- 判断,如果计次变量小于一个比较值,开灯,否则,关灯
最终实验功能
程序功能:两个按键分别控制两个LED,使其切换不同的点亮模式
程序要求:
- 按键灵敏,每次按键按下都能准确切换模式
- 模块要高度封装,主程序调用要简洁
- 在任何时候模块代码都不能阻塞主程序
改进LED模块代码
#include "stm32f10x.h" // Device headeruint8_t LED1_Mode; //定义全局变量,用于指定LED1模式
uint8_t LED2_Mode; //定义全局变量,用于指定LED2模式uint16_t LED1_Count; //定义全局变量,用于指定LED1计次自增
uint16_t LED2_Count; //定义全局变量,用于指定LED2计次自增void LED_Init(void)
{RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);GPIO_SetBits(GPIOA, GPIO_Pin_1 | GPIO_Pin_2);
}/**
LED1设置模式,并将计次清0
**/
void LED1_SetMode(uint8_t Mode)
{if (Mode != LED1_Mode){LED1_Mode = Mode;LED1_Count = 0;}
}/**
LED2设置模式,并将计次清0
**/
void LED2_SetMode(uint8_t Mode)
{if (Mode != LED2_Mode){LED2_Mode = Mode;LED2_Count = 0;}
}void LED1_ON(void)
{GPIO_ResetBits(GPIOA, GPIO_Pin_1);
}void LED1_OFF(void)
{GPIO_SetBits(GPIOA, GPIO_Pin_1);
}void LED2_ON(void)
{GPIO_ResetBits(GPIOA, GPIO_Pin_2);
}void LED2_OFF(void)
{GPIO_SetBits(GPIOA, GPIO_Pin_2);
}/*
让主程序更简洁,方便封装按键
相当于LED模块多了个中断函数LED_Tick,每隔一毫秒自动执行一次
这样就可以实现多模块共用一个定时器来定时
哪个模块想定时了,就定义一个Key_Tick函数,在main函数的中断函数统一调用
*/
void LED_Tick(void) //每隔1ms执行一次
{if (LED1_Mode == 0) //LED1模式0{LED1_OFF(); //关灯}else if (LED1_Mode == 1) //模式1{LED1_ON(); //开灯}else if (LED1_Mode == 2) //模式2,亮500ms,灭500ms{LED1_Count ++;LED1_Count %= 1000; //LED1_Count小于1000,对1000取余为LED1_Count本身//LED1_Count等于1000,对1000取余为0//相等于if(LED1_Count >999) LED1_Count = 0;两种方法都可以防止自增越界if (LED1_Count < 500){LED1_ON();}else{LED1_OFF();}}else if (LED1_Mode == 3) //模式3,亮5ms,灭50ms{LED1_Count ++;LED1_Count %= 100; //周期值100msif (LED1_Count < 50) //比较值50ms{LED1_ON();}else{LED1_OFF();}}else if (LED1_Mode == 4) //模式4,亮100ms,灭900ms{LED1_Count ++;LED1_Count %= 1000;if (LED1_Count < 100){LED1_ON();}else{LED1_OFF();}}/**控制LED2模式,和上面LED1一样
**/if (LED2_Mode == 0){LED2_OFF();}else if (LED2_Mode == 1){LED2_ON();}else if (LED2_Mode == 2){LED2_Count ++;LED2_Count %= 1000;if (LED2_Count < 500){LED2_ON();}else{LED2_OFF();}}else if (LED2_Mode == 3){LED2_Count ++;LED2_Count %= 100;if (LED2_Count < 50){LED2_ON();}else{LED2_OFF();}}else if (LED2_Mode == 4){LED2_Count ++;LED2_Count %= 1000;if (LED2_Count < 100){LED2_ON();}else{LED2_OFF();}}
}
主函数
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "LED.h"
#include "Key.h"
#include "Timer.h"uint8_t KeyNum;
uint8_t LED1Mode;
uint8_t LED2Mode;uint16_t i;int main(void)
{OLED_Init();LED_Init();Key_Init();Timer_Init();OLED_ShowString(1, 1, "i:");OLED_ShowString(2, 1, "LED1Mode:");OLED_ShowString(3, 1, "LED2Mode:");while (1){KeyNum = Key_GetNum();if (KeyNum == 1){LED1Mode ++;LED1Mode %= 5; //防止自增溢出,加到4之后就归0LED1_SetMode(LED1Mode);}if (KeyNum == 2){LED2Mode ++;LED2Mode %= 5;LED2_SetMode(LED2Mode);}OLED_ShowNum(1, 3, i ++, 5);OLED_ShowNum(2, 10, LED1Mode, 1);OLED_ShowNum(3, 10, LED2Mode, 1);}
}void TIM2_IRQHandler(void)
{if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET){Key_Tick(); //每隔一毫秒调用一次LED_Tick(); //每隔一毫秒调用一次TIM_ClearITPendingBit(TIM2, TIM_IT_Update);}
}