[江科大编程技巧] 第1期 定时器实现非阻塞式程序 按键控制LED闪烁模式——笔记

提前声明------我只是写的详细其实非常简单,不要看着多就放弃学习!

阻塞:执行某段程序时,CPU因为需要等待延时或者等待某个信号而被迫处于暂停状态一段时间,程序执行时间较长或者时间不定

非阻塞:执行某段程序时,CPU不会等待,程序很快执行结束

常规delay方法按键控制LED

cs 复制代码
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

cs 复制代码
#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

cs 复制代码
/**
  * @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,则表示按键按下且当前处于刚松手的状态 置键码标志位,

cs 复制代码
/**
  * @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

第三步:写按键返回函数()向主程序报告此事件

cs 复制代码
/**
  * @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),其他都是一样的,我们只是把按键检测这个事情从外部中断检测改为了定时器定时检测

cs 复制代码
#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(),然后在主函数调用即可

控制灯光模式函数,通过主函数输入参数决定

cs 复制代码
/**
  * @brief  控制灯光模式
  * @param  Mode:0->灭,1->亮;
  * @retval 无
  */
void led_SetMOde(uint8_t mode)
{
	led1_Mode = mode;
}	

LED定时器中断函数led_Tick()

cs 复制代码
/**
  * @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();
		}	
	}

}

主函数

cs 复制代码
#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);
	}
}

到这里两个阻塞我们都已经解决了,按键已经可以非常完美的控制灯光了,我们再给他增加一点功能吧

cs 复制代码
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;
	}
}
cs 复制代码
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

cs 复制代码
#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);
	}
}
相关推荐
1101 11014 小时前
STM32-笔记36-ADC(模拟/数字转换器)
笔记·stm32·嵌入式硬件
未完成的歌~6 小时前
Kali 离线安装 ipmitool 笔记
linux·运维·笔记
玩具工匠9 小时前
字玩FontPlayer开发笔记3 性能优化 大量canvas渲染卡顿问题
前端·javascript·vue.js·笔记·elementui·typescript
14_1110 小时前
Cherno C++学习笔记 P49 C++中使用静态库
c++·笔记·学习
StevenGerrad10 小时前
【读书笔记/源码】How Tomcat Works 笔记 - c1~c10
java·笔记·tomcat
安冬的码畜日常11 小时前
【Vim Masterclass 笔记08】第 6 章:Vim 中的文本变换及替换操作 + S06L20:文本的插入、变更、替换,以及合并操作
笔记·vim
StevenGerrad11 小时前
【读书笔记/源码】How Tomcat Works 笔记- c11~c13
java·笔记·tomcat
两笼包子一碗馄饨12 小时前
【软考网工笔记】操作系统管理与配置——Windows
windows·笔记
快乐星球居民13号12 小时前
【XJTUSE算法】考题回忆及复习建议
笔记·算法
LuH112413 小时前
【论文阅读笔记】LTX-Video: Realtime Video Latent Diffusion
论文阅读·笔记·生成对抗网络·aigc