阻塞和非阻塞
阻塞:执行某段程序时,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; //赋值给Temp
Key_Num = 0; //Key_Num清0
return 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,函数退出后不会清0
static 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 header
uint8_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; //周期值100ms
if (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之后就归0
LED1_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);
}
}