Implement button state machine based on STM32F103

Article directory

  • Development board
  • development environment
  • Preface
  • Anti-shake button
    • Button hardware schematic diagram
    • Software delay implementation ideas
  • Purpose
  • code
    • Button status
    • Key information
    • Definitions related to buttons
    • Button underlying configuration and status acquisition
  • Summarize

Development board

Punctual Atomic STM32F103ZET6 battleship

Development environment

stm32cubeMX Clion

Foreword

When the microcontroller uses buttons, in order to eliminate the jitter of the buttons, the delay method is often used. However, this method puts the CPU in a waiting state and cannot perform other tasks until the delay function is completed. This blocking method is not conducive to Multi-tasking situation. Therefore, using a state machine can achieve non-blocking, allowing the CUP to perform tasks more efficiently.

Button vibration elimination

Key hardware schematic diagram

KEY0 KEY1 KEY2 is used this time

From the schematic diagram, we can see that when we press the button, it shows a low level, and the ideal waveform is as follows:

Actual waveform:

So we want to eliminate the jitter when pressing down and lifting up. For this jitter, our software delays 5-10ms to confirm the level status again. If it is still low level, it is considered that the button is pressed. Lift it up and the same as above.

Software delay implementation ideas

First of all, we abandon the direct delay_ms(10) method. We want to achieve a delay of about 10ms without blocking the CPU. We can adopt the following methods:
You can start a timer with a period of 10, and make a judgment in the callback function.
Here I use to obtain the tick count of the tick timer interrupt. When the difference between the two obtained ticks is 10, the above effect is achieved. This experiment will also implement long press detection. When the difference reaches the set value, it can be judged. Currently in long press state. Therefore, it is more convenient to use the system tick timer interrupt counting method. The specific implementation can be viewed in the code.

Experimental purpose

Implement the state machine method to detect the status of each button and implement the corresponding function (supports long press)
When KEY0 is pressed, the red light turns on. When you release it, the red light turns off. If you press and hold, the red light stays on until you release it.
Press KEY1 and the green light will be on. Release it and the green light will go off. Press and hold and the green light will stay on until you release it.
Press KEY0, the buzzer will sound, and the buzzer will turn off. If you press and hold, the buzzer will sound until you release it.

Code

Key status

//Button state machine
typedef enum _KeyState
{<!-- -->
    KEY_NULL = 0, //Indicates no key pressed
    KEY_DOWN_JUDDER, //Jitter when pressed
    KEY_DOWN, //Press
    KEY_LONG, //Long press
    KEY_UP_JUDDER, //raise jitter
    KEY_UP, //raise
}KeyState;

Key information

This experiment uses three buttons

//Button ID
typedef enum _KeyId
{<!-- -->
    KEY_ID_0 = 0,
    KEY_ID_1 ,
    KEY_ID_2 ,
    KEY_ID_MAX
}KeyId;
//Button structure
typedef struct _keyInfo{<!-- -->

    KeyState keyState; //Key state machine
    uint8_t preKeyState; //The state of the last key press
    int32_t preTime; //Last time
    int32_t curTime; //Current time
}KeyInfo;

Key related definitions

Can be modified by yourself

#define KEYPRESS 1 //Press level
#define KEYRELEASE 0 //Raise the level

#define KEYPRESSTIME 10 //Eliminate jitter for 10ms
#define KEYLONGPRESSTIME 1000 //Short press for more than 1 second counts as long press

Button underlying configuration and status acquisition

bsp_key.c

/********************************************** ************************************
* @author: majj
* @email: [email protected]
* @date: 2023/9/8 21:13
* @version: 2.0
* @description:
*************************************************** ******************************/




#include "bsp_key.h"

KeyInfo keyInfo[KEY_ID_MAX];
KeyValueBuffer keyValueBuffer[KEY_ID_MAX];

/**
 * Get the key level value
 * @param keyID
 * @return
 */
GPIO_PinState GetKeyValue(uint8_t keyID)
{<!-- -->
    GPIO_PinState value = GPIO_PIN_RESET;
    switch (keyID) {<!-- -->
        case KEY_ID_0:
            value = HAL_GPIO_ReadPin(KEY0_GPIO_Port,KEY0_Pin);
            break;

        case KEY_ID_1:
            value = HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin);
            break;

        case KEY_ID_2:
            value = HAL_GPIO_ReadPin(KEY2_GPIO_Port,KEY2_Pin);
            break;

        default:

            break;
    }
    return value;
}


/**
 * Write once status
 * @param keypad_status
 */
void KeyValueWrite(uint8_t id,KeyState keypad_status)
{<!-- -->
    keyValueBuffer[id].valBuffer[keyValueBuffer[id].write] = keypad_status;
    if ( + + keyValueBuffer[id].write >= KEY_ID_MAX)
    {<!-- -->
        keyValueBuffer[id].write = 0;
    }
}

/**
 * Read the status once
 * @return
 */
KeyState KeyValueRead(uint8_t id)
{<!-- -->
    KeyState key_event;
    if (keyValueBuffer[id].read == keyValueBuffer[id].write)
    {<!-- -->
        return KEY_NULL;
    }
    else
    {<!-- -->
        key_event = keyValueBuffer[id].valBuffer[keyValueBuffer[id].read];
        if ( + + keyValueBuffer[id].read >= KEY_ID_MAX)
        {<!-- -->
            keyValueBuffer[id].read = 0;
        }
        return key_event;
    }
}

/**
 * Determine whether the case is pressed
 * @param keyId
 * @return
 */
uint8_t GetKeyIOState(KeyId keyId)
{<!-- -->
    GPIO_PinState key_value = GetKeyValue(keyId);

    if (key_value == GPIO_PIN_RESET){<!-- -->

        return KEYPRESS; // The key is pressed
    }else{<!-- -->

        return KEYRELEASE; // Release the key
    }
}

/**
 *
 */
void KeyInfoInit()
{<!-- -->
    for (int i = 0; i < KEY_ID_MAX; i + + ) {<!-- -->
        keyInfo[i].keyState = KEY_NULL;
        keyInfo[i].preKeyState = KEY_NULL;
        keyInfo[i].preTime = 0;
        keyInfo[i].curTime = 0;
    }
}

bsp_key.h

/********************************************** ************************************
* @author: majj
* @email: [email protected]
* @date: 2023/9/8 21:13
* @version: 2.0
* @description:
*************************************************** ******************************/




#ifndef KEY_STATE_BSP_KEY_H
#define KEY_STATE_BSP_KEY_H

#include "gpio.h"
#include "Retarget.h"

#define KEYPRESS 1 //Press level
#define KEYRELEASE 0 //Raise the level

#define KEYPRESSTIME 10 //Eliminate jitter for 10ms
#define KEYLONGPRESSTIME 1000 //Short press for more than 1 second counts as long press

//Button ID
typedef enum _KeyId
{<!-- -->
    KEY_ID_0 = 0,
    KEY_ID_1 ,
    KEY_ID_2 ,
    KEY_ID_MAX
}KeyId;

//Button state machine
typedef enum _KeyState
{<!-- -->
    KEY_NULL = 0, //Indicates no key pressed
    KEY_DOWN_JUDDER, //Jitter when pressed
    KEY_DOWN, //Press
    KEY_LONG, //Long press
    KEY_UP_JUDDER, //raise jitter
    KEY_UP, //raise
}KeyState;

//buffer structure
typedef struct _KeyValueBuffer
{
    GPIO_PinState valBuffer[KEY_ID_MAX]; //Key value buffer
    uint8_t read; //Buffer read pointer 1
    uint8_t write; //buffer write pointer 1
} KeyValueBuffer;

//Button structure
typedef struct _keyInfo{<!-- -->

    KeyState keyState; //Key state machine
    uint8_t preKeyState; //The state of the last key press
    int32_t preTime; //Last time
    int32_t curTime; //Current time
}KeyInfo;

extern KeyInfo keyInfo[KEY_ID_MAX];

void KeyInfoInit(void);
uint8_t GetKeyIOState(KeyId keyId);
void KeyValueWrite(uint8_t id,KeyState keypad_status);
KeyState KeyValueRead(uint8_t id);

#endif //KEY_STATE_BSP_KEY_H

bsp_led.c

/********************************************** ************************************
* @author: majj
* @email: [email protected]
* @date: 2023/9/8 21:13
* @version: 2.0
* @description:
*************************************************** ******************************/




#include "bsp_led.h"

/**
 * Set LED0 status
 * @param flag
 */
void SetLed0State(uint8_t flag)
{<!-- -->
    if(flag){<!-- -->
        HAL_GPIO_WritePin(LED0_GPIO_Port,LED0_Pin,GPIO_PIN_RESET);
    }else{<!-- -->
        HAL_GPIO_WritePin(LED0_GPIO_Port,LED0_Pin,GPIO_PIN_SET);
    }
}

/**
 * Set LED1 status
 * @param flag
 */
void SetLed1State(uint8_t flag)
{<!-- -->
    if(flag){<!-- -->
        HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,GPIO_PIN_RESET);
    }else{<!-- -->
        HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,GPIO_PIN_SET);
    }
}

bsp_beep.c

/********************************************** ************************************
* @author: majj
* @email: [email protected]
* @date: 2023/9/8 21:14
* @version: 2.0
* @description:
*************************************************** ******************************/




#include "bsp_beep.h"

void SetBeepState(uint8_t flag)
{<!-- -->
    if(flag){<!-- -->
        HAL_GPIO_WritePin(BEEP_GPIO_Port,BEEP_Pin,GPIO_PIN_SET);
    } else{<!-- -->
        HAL_GPIO_WritePin(BEEP_GPIO_Port,BEEP_Pin,GPIO_PIN_RESET);
    }
}

Button state machine polling task

/********************************************** ************************************
* @author: majj
* @email: 1289[email protected]
* @date: 2023/9/8 22:36
* @version: 2.0
* @description:
*************************************************** ******************************/

#include "key_status_controller.h"

/**
 * Perform keystroke tasks
 * @param keyID
 * @param keyState
 */
void OperateKeyTask(KeyId keyID,KeyState keyState)
{<!-- -->
    switch (keyID) {<!-- -->
        case KEY_ID_0:
            if(keyState == KEY_DOWN){<!-- -->
                SetLed0State(1);
            }else if(keyState == KEY_LONG){<!-- -->
                printf("KEY0 --long press\r\
");
                SetLed0State(1);
            } else if(keyState ==KEY_NULL){<!-- -->
                SetLed0State(0);
            }
            break;
        case KEY_ID_1:
            if(keyState == KEY_DOWN){<!-- -->
                SetLed1State(1);
            }else if(keyState == KEY_LONG){<!-- -->
                SetLed1State(1);
            } else if(keyState ==KEY_NULL){<!-- -->

                SetLed1State(0);
            }
            break;
        case KEY_ID_2:
            if(keyState == KEY_DOWN){<!-- -->
                SetBeepState(1);
            }else if(keyState == KEY_LONG){<!-- -->
                printf("KEY2 --long press\r\
");
                SetBeepState(1);
            } else if(keyState ==KEY_NULL ){<!-- -->
                SetBeepState(0);
            }
            break;
        default:
            break;
    }

}

/**
 * Detect button
 * @param keyID
 */
void DetectKeyState(KeyId keyID)
{<!-- -->
    //Read key status information
    KeyState keyState = keyInfo[keyID].preKeyState;
    //Read key level changes
    uint8_t keyValue = GetKeyIOState(keyID);

    switch (keyState) {<!-- -->
        case KEY_NULL:
            if(keyValue == KEYPRESS){<!-- -->
                //Detect button press
                keyInfo[keyID].keyState = KEY_DOWN_JUDDER;
                keyInfo[keyID].preTime = keyInfo[keyID].curTime;
            } else{<!-- -->

                keyInfo[keyID].curTime = 0;
                keyInfo[keyID].preTime = 0;
            }

            break;
        case KEY_DOWN_JUDDER:
            if(keyValue == KEYPRESS){<!-- -->
                keyInfo[keyID].curTime = HAL_GetTick();

                if((keyInfo[keyID].curTime - keyInfo[keyID].preTime) > KEYPRESSTIME){<!-- -->
                    //Confirm press
                    keyInfo[keyID].keyState = KEY_DOWN;
                    keyInfo[keyID].preTime = keyInfo[keyID].curTime;
                }
            } else{<!-- -->
                keyInfo[keyID].keyState = KEY_NULL;
            }

            break;

        case KEY_DOWN:
            keyInfo[keyID].curTime = HAL_GetTick();
            if((keyInfo[keyID].curTime - keyInfo[keyID].preTime) > KEYLONGPRESSTIME){<!-- -->
                //Confirm press
                keyInfo[keyID].keyState = KEY_LONG;
                keyInfo[keyID].preTime = keyInfo[keyID].curTime;

            }else{<!-- -->
                //dog
                if(keyValue == KEYRELEASE){<!-- -->
                    keyInfo[keyID].keyState = KEY_UP_JUDDER;
                    keyInfo[keyID].preTime = keyInfo[keyID].curTime;
                }
            }

            break;

        case KEY_LONG:
            keyInfo[keyID].curTime = HAL_GetTick();
            if(keyValue == KEYRELEASE){<!-- -->
                //raise jitter
                keyInfo[keyID].keyState = KEY_UP_JUDDER;
                keyInfo[keyID].curTime = HAL_GetTick();
                keyInfo[keyID].preTime = keyInfo[keyID].curTime;
            }

            break;

        case KEY_UP_JUDDER:
            if(keyValue == KEYRELEASE){<!-- -->
                keyInfo[keyID].curTime = HAL_GetTick();
                if((keyInfo[keyID].curTime - keyInfo[keyID].preTime) > KEYPRESSTIME){<!-- -->
                    //Confirm to lift
                    keyInfo[keyID].keyState = KEY_UP;
                    KeyValueWrite(keyID,keyInfo[keyID].keyState);
                }
            }

            break;

        case KEY_UP:

            keyInfo[keyID].keyState = KEY_NULL;
            KeyValueWrite(keyID,keyInfo[keyID].keyState);

            break;

        default:

            break;

    }
    //Execute action tasks
    OperateKeyTask(keyID,keyState);
    //Record this status
    keyInfo[keyID].preKeyState = keyInfo[keyID].keyState;

}

/**
 * Scan button
 */
void KeyScanTask()
{<!-- -->
    for(int i = 0; i < KEY_ID_MAX; i + + ){<!-- -->
        DetectKeyState((KeyId)i);
    }
}

Summary

The above is a simple and rough way to implement the button state machine. There are many shortcomings and it is for reference only. This routine uses stm32cube + clion and cannot be run directly on keil5, so the entire file is not uploaded.