【学习记录】STM32学习记录(三)按键控制LED灯,在这篇应用中,按键的去抖动采用了软件延时的方法,利用接口函数HAL_Delay()进行延时。这种延时去抖动的方法会占用处理器的执行时间,在延时过程中,处理器无法处理其他事务,从而降低了处理器的利用率。
在工程实际开发应用中,一般采用状态机和定时中断相结合的方式来完成按键的检测。
状态机是一个抽象的概念,表示把一个过程抽象为若干个状态之间的转换,这些状态之间存在一定的联系。状态机主要包括四个要素:
1、现态:是指当前所处的状态。
2、条件:当一个条件满足后,将会触发一个动作,或者执行一次状态的迁移。
3、动作:条件满足后执行的具体操作。动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。动作不是必需的,当条件满足后,也可以不执行任何动作,直接迁移到新状态。
4、次态:条件满足后要迁往的新状态。“次态”是相对于“现态”而言的,“次态”且被激活,就转变为新的“现态”了。
四要素理解:状态机在当前状态(现态)下重复执行某一动作,并且不断检测跳转状态的条件。当突然满足某一条件后,开始进入下一个状态(次态),并且重复另外的动作。此时的次态变为现态。
状态机编程步骤:1、分析产品的功能需求,提取状态;2、画出状态图,理清状态的切换关系,标注状态转换的条件;3、明确状态机里要实现的功能;4、编写代码。
一般的状态机通过switch分支语句实现。
对于按键监测,我们将单个按键作为一个简单的系统,根据按键的整个工作过程对按键的动作和按键的确认过程进行分析,并用状态转换图表示,然后根据状态图编写应用程序。
根据状态机的原理,我们可以将按键过程抽象为三个状态:
按键抬起状态:按键没有按下。
按键消抖状态:消除按键抖动。
按键等待释放状态:等待按键释放。
具体的状态转换过程如下:
处于按键抬起状态时,如果按键信号有效(按键控制引脚读取到有效电平),则转换到按键消抖状态,开始准备消除按键的抖动。如果按键信号无效,则保持当前状态不变。
处于按键消抖状态时,如果按键信号有效,则表明按键抖动已经消除,按键处于稳定闭合状态,可以获取当前按键所对应白编号(按键译码),并转换到按键等待释放状态如果按键信号无效,则表示之前的按键信号可能是干扰信号,应该返回按键抬起状态。
处于按键等待释放状态时,如果按键信号有效,则表示还处在按键按下状态,不进行状态改变。如果按键信号无效,则表明按键已经释放,应该转换到按键抬起状态。至此已经完成了一次按键检测的全过程。
按键的状态转换过程及转换条件如下图所示。
实验内容:利用状态机设计思想编写按键处理程序,按键按下后执行操作:翻转LED1的状态。
实验设计思路:
- 设计一个按键状态数据类型 KEY_STATE,利用枚举类型实现,包含三个枚举成员KEY_UP表示按键抬起状态,KEY_DEBOUNCE 表示按键消抖状态KEY_WAIT_RELEASE表示按键等待释放状态。
- 设计一个按键扫描函数 KeyScan(),利用Switch-Case 多分支语,通过检测按键脚的电平来实现按键状态的转换。
- 利用定时器产生 10 ms 的定时中断,在定时中断服务程序中调用按键扫描函数。按键扫描函数每隔 10 ms 调用一次,这个时间间隔可以用来消除按键的抖动,提高 CPU 的利用率。
- 采用前后台编程模式。设置一个标志变量 KeyFlag,由定时中断对 KeyFlag 置位。后台程序在 while(1)循环中不断检测 KeyFlag,为真则执行按键的处理任务。
实验外设配置
使用正点原子探索者。
- 打开CubeMX进行配置选择stm32F407ZGT6进行相应引脚配置。PE4引脚设置为GPIO_Input,PF10引脚设置为GPIO_Output。
- 进行时钟树配置
3.进行定时器的配置,激活tim1(可以选用其它的)
4.生成代码
实验代码
1.数据类型设计、变量定义及函数声明
/* USER CODE BEGIN PTD */
typedef enum{
KEY_UP, //按键抬起
KEY_DEBOUNCE, //按键消抖
KEY_WAIT_RELEASE //按键等待释放
}KEY_STATE;
/* USER CODE END PTD */
/* USER CODE BEGIN PV */
KEY_STATE KeyState=KEY_UP;//按键状态,设置按键抬起为初试状态
uint8_t KeyFlag=0; //按键有效检测标识符,0无效,1有效
/* USER CODE END PV */
/* USER CODE BEGIN PFP */
void KeyScan();//按键扫描函数声明
/* USER CODE END PFP */
2.按键处理程序
/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start_IT(&htim1);//启动定时器1,使能定时器1中断
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
if(KeyFlag)
{
KeyFlag=0;
HAL_GPIO_TogglePin(GPIOF,GPIO_PIN_10);
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
3.按键扫描程序
/* USER CODE BEGIN 4 */
void KeyScan()
{
switch(KeyState)
{
case KEY_UP:
{
//读到低电平,转换到按键消除状态
if(HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_4)==GPIO_PIN_RESET)
{
KeyState=KEY_DEBOUNCE;
}
break;
}
case KEY_DEBOUNCE:
{
//读到低电平,转换到按键等待释放状态,并设置按键有效标识
if(HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_4)==GPIO_PIN_RESET)
{
KeyState=KEY_WAIT_RELEASE;
KeyFlag=1;
}
//读到高电平,说明是干扰信号,转换到按键抬起状态
else
{
KeyState=KEY_UP;
}
break;
}
case KEY_WAIT_RELEASE:
{
//读到高电平,说明按键释放,转换到按键抬起状态
if(HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_4)==GPIO_PIN_SET)
{
KeyState=KEY_UP;
}
break;
}
default:break;
}
}
/* USER CODE END 4 */
4.定时器中断回调函数
/* USER CODE BEGIN 0 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance==htim1.Instance)
{
KeyScan();
}
}
/* USER CODE END 0 */