本节是学习MOOC视频后,对于状态机思想的应用,目的是通过状态机的思想来编写按键检测的代码,同时控制LED灯
开发环境:CubeMX+MDK5.27
芯片型号:STM32F103ZET6
时间:2020/07/06
首先介绍一下状态机思想,所谓状态机就是将一个事情发生的过程抽象为状态的切换,比如按键检测的过程,我们就可以把它分解为按键检测状态,按键确认状态,按键释放状态这三个状态。按键从没有被按下,到完全按下,再到释放的过程就是这三个状态的切换。那么在状态机里有四个要素——现态、条件、动作、次态。
现态:顾名思义就是当前的状态
条件:条件满足时,会触发动作或者改变状态。注意:条件满足时,并不一定会改变状态
动作:条件满足时,将执行动作。注意:执行动作并不一定会影响状态的改变。即满足条件时,可以不执行任何动作,直接改变状态。
次态:当满足条件后迁移到的新的状态就是次态
///**///
///**///
了解了以上概念,我们来实际设计一下按键检测的状态机实现代码。
第一步,在CubeMX中配置相关硬件,具体配置见图


第二步,编写代码
在main.c中第33行定义枚举类型的数据结构表示按键状态
typedef enum{KEY_CHECK = 0,KEY_CONFIRM,KEY_RELEASE}KEY_STATE;
在第53行添加以下代码,定义初始状态和按键标志
KEY_STATE KeyState = KEY_CHECK;uint8_t KeyFlag = 0;
修改main函数内容为:
int main(void){/* USER CODE BEGIN 1 *//* USER CODE END 1 *//* MCU Configuration--------------------------------------------------------*//* Reset of all peripherals, Initializes the Flash interface and the Systick. */HAL_Init();/* USER CODE BEGIN Init *//* USER CODE END Init *//* Configure the system clock */SystemClock_Config();/* USER CODE BEGIN SysInit *//* USER CODE END SysInit *//* Initialize all configured peripherals */MX_GPIO_Init();MX_TIM6_Init();/* USER CODE BEGIN 2 */HAL_TIM_Base_Start_IT(&htim6);/* USER CODE END 2 *//* Infinite loop *//* USER CODE BEGIN WHILE */while (1){/* USER CODE END WHILE */if(KeyFlag==1){KeyFlag = 0;HAL_GPIO_TogglePin(LED0_GPIO_Port,LED0_Pin);HAL_GPIO_TogglePin(LED1_GPIO_Port,LED1_Pin);}/* USER CODE BEGIN 3 */}/* USER CODE END 3 */}
由于HAL库的设计,定时器的中断会被封装多层,最终我们需要在中断回调函数中编写自己的代码来实现按键检测。默认生成的代码中,在STM32F1xx_HAL_Driver目录下的stm32f1xx_hal_tim.c文件第4831行是已经编写好了中断回调函数的,但是在函数前面加上了__weak,这表示的意思是如果用户没有自己编写同名的函数,那么将调用已存在的这个函数,如果用户编写了同名的函数,则只调用用户编写的函数,不调用这个已经编写好的函数。因此我们在main.c的第155行重新编写一个中断回调函数。代码如下
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM6)
{
switch(KeyState)
{
case KEY_CHECK:
{
if(HAL_GPIO_ReadPin(KEY0_GPIO_Port,KEY0_Pin) == GPIO_PIN_RESET)
{
KeyState = KEY_CONFIRM;
}
break;
}
case KEY_CONFIRM:
{
if(HAL_GPIO_ReadPin(KEY0_GPIO_Port,KEY0_Pin) == GPIO_PIN_RESET)
{
KeyState = KEY_RELEASE;
KeyFlag = 1;
}
else
{
KeyState = KEY_CHECK;
}
break;
}
case KEY_RELEASE:
{
if(HAL_GPIO_ReadPin(KEY0_GPIO_Port,KEY0_Pin) == GPIO_PIN_SET)
{
KeyState = KEY_CHECK;
}
break;
}
default:break;
}
}
}
然后编译并下载就能观察到现象了。
总结:利用状态机可以很好的解决延时消抖带来的CPU利用率低的问题,因为调用延时函数时CPU无法进行任何工作,完全浪费了这段时间。通过这样的状态机消抖可以很好地提高CPU的利用率,并且在程序设计上也很容易理解。
注:在编写switch语句时如果括号里的内容是枚举类型,那么case中要包含所有的类型,否则编译会提示waring_
