当前位置: 首页 > article >正文

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

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

阻塞:执行某段程序时,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);
	}
}


http://www.kler.cn/a/456153.html

相关文章:

  • 【连续学习之SSL算法】2018年论文Selfless sequential learning
  • AMD | GPU | 深度学习 | 如何使用
  • 119.【C语言】数据结构之快速排序(调用库函数)
  • SpringCloudAlibaba实战入门之路由网关Gateway初体验(十一)
  • Wend看源码-Java-集合学习(List)
  • 《OpenCV计算机视觉》-对图片的各种操作(均值、方框、高斯、中值滤波处理)及形态学处理
  • SQL 实战:复杂数据去重与唯一值提取
  • Android——自定义按钮button
  • Python学生管理系统(MySQL)
  • default、delete 和 explicit
  • Spark生态圈
  • 在FreeBSD或Ubuntu平台仿真RISCV64位版本FreeBSD系统相关技术文档
  • 基于Spring Boot + Vue3实现的在线商品竞拍管理系统源码+文档
  • 记录命令行操作树莓派Wifi的方式
  • FAISS进行高效的向量检索 原理详解
  • MyBatis中XML文件的模板
  • Vite系列课程 | 11. Vite 配置文件中 CSS 配置(Modules 模块化篇)
  • xadmin后台首页增加一个导入数据按钮
  • CA系统的设计(CA证书生成,吊销,数字签名生成)
  • 关于Qt::BlockingQueuedConnection的死锁问题
  • Fastbot-iOS(iOS monkey)schema参数的指定方式
  • 【工具变量】地级市减碳重视程度及减碳词频数据(2003-2024年)
  • Mybatis-Plus updateById 方法更新无效及空值处理
  • 【C++面向对象——类与对象】Computer类(头歌实践教学平台习题)【合集】
  • mprpc框架代码详解
  • 【HarmonyOS之旅】ArkTS语法(二)->动态构建UI元素