欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 财经 > 创投人物 > 【STM32点灯大师】定时器实现非阻塞式程序-按键控制LED

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

2025/4/19 14:04:43 来源:https://blog.csdn.net/2301_81011494/article/details/147166867  浏览:    关键词:【STM32点灯大师】定时器实现非阻塞式程序-按键控制LED

阻塞和非阻塞 

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

    版权声明:

    本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

    我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

    热搜词