提前声明——我只是写的详细其实非常简单,不要看着多就放弃学习!
阻塞:执行某段程序时,CPU因为需要等待延时或者等待某个信号而被迫处于暂停状态一段时间,程序执行时间较长或者时间不定
非阻塞:执行某段程序时,CPU不会等待,程序很快执行结束
常规delay方法按键控制LED
uint8_t Key_GetNum(void)
{
uint8_t KeyNum = 0;
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0)
{
Delay_ms(20);
while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0);
Delay_ms(20);
KeyNum = 1;
}
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0)
{
Delay_ms(20);
while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0);
Delay_ms(20);
KeyNum = 2;
}
return KeyNum;
}
这种方法通过外部中断来实现。会响应阻塞
mian()里面本想通过按键按下控制LED的慢闪和熄灭。但是因为Delay_ms(500);所以熄灭时会很不灵敏,得长按才能熄灭。并且在LED亮时i这个变量要1ms才增加1
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "kEY.h"
#include "LED.h"
uint8_t key1_flag = 0 ;
uint8_t keyNum ;
uint8_t i;
int main(void)
{
OLED_Init();
Key_Init();
LED_Init();
while (1)
{
keyNum = Key_GetNum();
if(keyNum == 1)
{
key1_flag = !key1_flag;
}
if(key1_flag)
{
LED1_ON();
Delay_ms(500);
LED1_OFF();
Delay_ms(500);
}
else
{
LED1_OFF();
}
OLED_ShowNum(2,2,i++,5);
}
}
这种程序不仅仅效果非常差,而且很占CPU
为了让主程序不被阻塞,也就是主程序可以快速刷新,但是按键消抖和LED闪烁是很常见的,就必须要让我们的程序有类似于多线程的操作了,单片机最长用的多线程是定时器定时中断
我们有两个Delay 1:按键消抖Delay_ms(20);LED闪烁Delay_ms(500);
一.定时器中断解决按键消抖Delay的问题,
解决办法就是定时器扫描按键,不要用外部中断检测
先归纳:
1.在Key.c写获取按键状态函数:PB1按下返回1,PB11按下返回2,没有按键按返回0(目的获取状态)
2.key.C建立一个key_Tick(),然后在主函数void TIM2_IRQHandler(void)调用:每隔20ms读取一次本次键码值和上次键码值,判断,如果本次是0,上次非0,则表示按键按下且当前处于刚松手的状态 置键码标志位,
3写按键返回函数()向主程序报告此事件
4.主函数根据什么按键按下对应执行操作
根据上述思路
我们第一步:在Key.c写获取按键状态函数
PB1按下返回1,PB11按下返回2,没有按键按返回0
/**
* @brief 获取按键状态
* @param 无
* @retval 状态返回值
*/
uint8_t Key_Getstate(void)
{
if (GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1) == 0)
{
return 1;
}
if (GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1) == 0)
{
return 2;
}
return 0;
}
第二步:写定时中断函数()
定时中断函数如果写在主函数里,不利于外设模块化编程,如果写在key.C里其他模块不好用
key.C建立一个key_Tick(),然后在主函数调用即可
1.每隔20ms读取一次本次键码值和上次键码值
2.判断,如果本次是0,上次非0,则表示按键按下且当前处于刚松手的状态 置键码标志位,
/**
* @brief 定时器按键检测
* @param key_count:记20ms;;
* @param prevstate:上次状态;
* @param currstate:本次状态;
* @retval 状态返回值
*/
void Key_Tick(void)
{
static uint8_t key_count =0;
static uint8_t prevstate ,currstate;
key_count++;
/*20ms检测*/
if (key_count >= 20)
{
key_count = 0;
/*上一次的本次就是上一次*/
prevstate = currstate; //上次
currstate = Key_Getstate(); //本次
/*得到本次和上一次的状态后判断*/
if (currstate == 0 && prevstate !=0) //满足就代表按下了 prevstate按键
{
key_num = prevstate; //那么返回按键
}
}
}
为了返回Key_num
第三步:写按键返回函数()向主程序报告此事件
/**
* @brief 按键返回
* @param 无
* @retval KeyNum:按键值
*/
uint8_t Key_GetNum(void)
{
uint8_t temp;
if(key_num)
{
temp= key_num;
key_num = 0;
return temp;
}
return 0;
}
于是我们在主函数里调用
其实就添加了void TIM2_IRQHandler(void),其他都是一样的,我们只是把按键检测这个事情从外部中断检测改为了定时器定时检测
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "kEY.h"
#include "LED.h"
#include "timer.h"
uint8_t key1_flag = 0 ;
uint8_t keyNum ;
uint8_t i;
int main(void)
{
OLED_Init();
Key_Init();
LED_Init();
Timer_Init();
while (1)
{
keyNum = Key_GetNum();
if(keyNum == 1)
{
key1_flag = !key1_flag;
}
if(key1_flag)
{
LED1_ON();
Delay_ms(500);
LED1_OFF();
Delay_ms(500);
}
else
{
LED1_OFF();
}
OLED_ShowNum(2,2,i++,5);
}
}
void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
{
key_Tick();
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
到这里我们已经解决了,按键阻塞问题,实现定时器扫描按键的任务就完成了。
二.定时器中断解决LED闪烁Delay_ms(500)的问题
第一步:写定时中断函数()
这里也是一样的定时中断函数如果写在主函数里,不利于外设模块化编程,如果写在led.c里其他模块不好用
故我们在led.C建立一个led_Tick(),然后在主函数调用即可
控制灯光模式函数,通过主函数输入参数决定
/**
* @brief 控制灯光模式
* @param Mode:0->灭,1->亮;
* @retval 无
*/
void led_SetMOde(uint8_t mode)
{
led1_Mode = mode;
}
LED定时器中断函数led_Tick()
/**
* @brief 定时器按键检测
* @param led1_Mode:0->灭,1->亮;
* @retval 状态返回值
*/
void led_Tick(void)
{
if(led1_Mode == 0) //控制模式0,熄灭
{
LED1_OFF();
}
else if (led1_Mode == 1) //控制模式1,慢闪
{
led1_count++;
led1_count %= 1000; //1000ms周期
if(led1_count<500) //亮500ms
{
LED1_ON();
}
else //灭500ms
{
LED1_OFF();
}
}
}
主函数
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "kEY.h"
#include "LED.h"
#include "timer.h"
uint8_t key1_flag = 0 ;
uint8_t keyNum ;
uint8_t i;
int main(void)
{
OLED_Init();
Key_Init();
LED_Init();
Timer_Init();
while (1)
{
keyNum = Key_GetNum();
if(keyNum == 1)
{
key1_flag = !key1_flag;
}
if(key1_flag)
{
led_SetMOde(1);
}
else
{
led_SetMOde(0);
}
OLED_ShowNum(2,2,i++,5);
}
}
void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
{
key_Tick();
led_Tick();
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
到这里两个阻塞我们都已经解决了,按键已经可以非常完美的控制灯光了,我们再给他增加一点功能吧
void LED1_SetMode(uint8_t Mode)
{
if (Mode != LED1_Mode)
{
LED1_Mode = Mode;
LED1_Count = 0;
}
}
void LED2_SetMode(uint8_t Mode)
{
if (Mode != LED2_Mode)
{
LED2_Mode = Mode;
LED2_Count = 0;
}
}
void LED_Tick(void)
{
if (LED1_Mode == 0)
{
LED1_OFF();
}
else if (LED1_Mode == 1)
{
LED1_ON();
}
else if (LED1_Mode == 2)
{
LED1_Count ++;
LED1_Count %= 1000;
if (LED1_Count < 500)
{
LED1_ON();
}
else
{
LED1_OFF();
}
}
else if (LED1_Mode == 3)
{
LED1_Count ++;
LED1_Count %= 100;
if (LED1_Count < 50)
{
LED1_ON();
}
else
{
LED1_OFF();
}
}
else if (LED1_Mode == 4)
{
LED1_Count ++;
LED1_Count %= 1000;
if (LED1_Count < 100)
{
LED1_ON();
}
else
{
LED1_OFF();
}
}
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();
}
}
}
因为按键有五种状态了用flag不太好,我们修改为mode
#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;
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);
}
}