【STM32点灯大师】定时器实现非阻塞式程序-按键控制LED

发布于:2025-04-17 ⋅ 阅读:(23) ⋅ 点赞:(0)

阻塞和非阻塞 

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

定时器扫描按键

单按键

  1. 定时中断,每隔20ms读取一次本次引脚值和上次引脚值
  2. 判断:如果本次是1,上次是0,则表示按键按下且当前处于刚松手的状态
  3. 置键码标志位,向主程序报告此事件  

 多按键

  1. 先写一个获取键码值的子函数(非阻塞式)
  2. 定时中断,每隔20ms读取一次本次键码值和上次键码值
  3. 判断:如果本次是0,上次非0,则表示按键按下且当前处于刚松手的状态
  4. 置键码标志位,向主程序报告此事件

实现LED闪烁的非阻塞

最终目的是任何时候主循环都是不容阻塞的。 (把Delay函数改掉)

定时器实现LED闪烁

  1. 定时中断,每隔1ms计次变量自增
  2. 计次变量计到周期值时,归零
  3. 判断,如果计次变量小于一个比较值,开灯,否则,关灯

    最终实验功能

    程序功能:两个按键分别控制两个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);
    	}
    }
    

    网站公告

    今日签到

    点亮在社区的每一天
    去签到