本节是学习MOOC视频后,对于状态机思想的应用,目的是通过状态机的思想来编写按键检测的代码,同时控制LED灯
    开发环境:CubeMX+MDK5.27
    芯片型号:STM32F103ZET6
    时间:2020/07/06

    首先介绍一下状态机思想,所谓状态机就是将一个事情发生的过程抽象为状态的切换,比如按键检测的过程,我们就可以把它分解为按键检测状态,按键确认状态,按键释放状态这三个状态。按键从没有被按下,到完全按下,再到释放的过程就是这三个状态的切换。那么在状态机里有四个要素——现态、条件、动作、次态。
    现态:顾名思义就是当前的状态
    条件:条件满足时,会触发动作或者改变状态。注意:条件满足时,并不一定会改变状态
    动作:条件满足时,将执行动作。注意:执行动作并不一定会影响状态的改变。即满足条件时,可以不执行任何动作,直接改变状态。
    次态:当满足条件后迁移到的新的状态就是次态
    ///**///
    ///**///
    了解了以上概念,我们来实际设计一下按键检测的状态机实现代码。
    第一步,在CubeMX中配置相关硬件,具体配置见图
    key.pngTIM.pngNVIC.png
    第二步,编写代码
    在main.c中第33行定义枚举类型的数据结构表示按键状态

    1. typedef enum
    2. {
    3. KEY_CHECK = 0,
    4. KEY_CONFIRM,
    5. KEY_RELEASE
    6. }KEY_STATE;

    在第53行添加以下代码,定义初始状态和按键标志

    1. KEY_STATE KeyState = KEY_CHECK;
    2. uint8_t KeyFlag = 0;

    修改main函数内容为:

    1. int main(void)
    2. {
    3. /* USER CODE BEGIN 1 */
    4. /* USER CODE END 1 */
    5. /* MCU Configuration--------------------------------------------------------*/
    6. /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
    7. HAL_Init();
    8. /* USER CODE BEGIN Init */
    9. /* USER CODE END Init */
    10. /* Configure the system clock */
    11. SystemClock_Config();
    12. /* USER CODE BEGIN SysInit */
    13. /* USER CODE END SysInit */
    14. /* Initialize all configured peripherals */
    15. MX_GPIO_Init();
    16. MX_TIM6_Init();
    17. /* USER CODE BEGIN 2 */
    18. HAL_TIM_Base_Start_IT(&htim6);
    19. /* USER CODE END 2 */
    20. /* Infinite loop */
    21. /* USER CODE BEGIN WHILE */
    22. while (1)
    23. {
    24. /* USER CODE END WHILE */
    25. if(KeyFlag==1)
    26. {
    27. KeyFlag = 0;
    28. HAL_GPIO_TogglePin(LED0_GPIO_Port,LED0_Pin);
    29. HAL_GPIO_TogglePin(LED1_GPIO_Port,LED1_Pin);
    30. }
    31. /* USER CODE BEGIN 3 */
    32. }
    33. /* USER CODE END 3 */
    34. }

    由于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_