STM32 key finite state machine (super detailed, easy to transplant)

State machine

In short, a state machine is a mechanism that enables changes between states and the corresponding actions that occur when states are in place.

1.1 Four elements of a state machine

  • State: indicates the current state of the state machine.

  • Trigger condition: Condition that changes the current state.

  • Action: Changes in state produce corresponding actions.

  • Secondary state: the next state to which the state machine jumps after activating the trigger condition.

Note: state and action are different. State is continuous while action is intermittent. Changing state produces action, and the state continues after the action is completed.

1.2 Why use a State machine

As a simple example, there are often three ways to implement keystroke scanning

  • Polling: added key_scan() to the main function
  • Interrupt mode: most of the MCU support external interrupt, but each (or several)IO port occupies an interrupt vector, the use is very convenient.
  • State machine approach: In my opinion this approach is better than either of the other two, why? Let’s take a look.

1. Resource occupation:

Polling mode: the execution that always occupies CPU in the main loop.

Interrupt mode: only after the time generated after the jump to perform the callback, relative to polling occupies much less resources, but many keys need multiple interrupt vector.

State machine mode: when using state machine to realize key scanning, we only need a timer to realize the scanning of any key, and the efficiency is not lower than the interrupt mode.

2. Execution efficiency:

Polling mode: very low efficiency, response and insensitive, sometimes need to press many times to have a response.

Interrupt mode: High efficiency, sensitive response, a separate interrupt mode does not support long press and other state operations.

State machine mode: It is efficient and responsive, and supports long-press operation.

3. Key jitter:

Polling mode: it needs to delay the shaking elimination, and other operations cannot be carried out at the same time.

Interrupt mode: need delay shake elimination, shake elimination at the same time can not carry out other operations.

State machine approach: quitter elimination is indirectly generated, why say so? Here, we use a timer with a timing of 20ms to process the status every 20ms. In the last and next 20ms, we can interrupt the timer for other operations, that is, other operations are carried out while shaking is eliminated, which greatly improves the efficiency of operation.

1.3 How to Implement the State Machine

Now that we know the four elements of a state machine and why to use a state machine, this step is easy to implement with a button state machine. Since it is a state machine, there should be a state table, which should describe the relationship between the state and the secondary state. As for what actions are produced, they are often different in different occasions in practical application. Ok, having said so much, go straight to the code:

I define several types:

First of all, the key is divided into two types: common positive and common negative. Different connection methods correspond to different levels when pressing down. The structure below is mainly used to initialize the corresponding effective level when pressing down and the hardware pin port that needs to be known when reading.

Uint32_t GPIO_Pull; // GPIO_TypeDef* GPIOx; // The corresponding port of the key is uint16_t GPIO_Pin_x; // Uint8_t key_nox; }Key_Init;Copy the code

According to the five states of keys that are useful to me, in fact, a series of other operations such as click and double click may also be used in practical applications, but I have not written them because I have not used them for the time being. I will brief you on my ideas later

// Typedef enum _KEY_STATUS_LIST{KEY_NULL = 0x00, // No action KEY_SURE = 0x01, // Confirm status KEY_RAISE = 0x02, // KEY_PRESS = 0x04, // KEY_LONG = 0x08, // long press}KEY_STATUS_LIST;Copy the code

The following types are some status flags, in 32 have defined such types, the following is simply re-declared, if migrated to other microcontroller can implement their own KEY_ENABLE_STATUS enumeration type

/* key masking flag */ typedef FunctionalState KEY_ENABLE_STATUS; // #define LOW_LEVEL GPIO_PIN_RESET #define HIGH_LEVER GPIO_PIN_SET typedef GPIO_PinState IO_STATUS_LIST; // static IO_STATUS_LIST KEY_ReadPin(Key_Init Key) // Key read function {return (IO_STATUS_LIST)HAL_GPIO_ReadPin(Key.GPIOx,Key.GPIO_Pin_x); }Copy the code

This is a structure of the state machine class. It describes a series of operations of the state machine class, which is the most important part

Typedef struct _KEY_COMPONENTS // state machine class {KEY_ENABLE_STATUS KEY_SHIELD; // DISABLE(0): uint8_t KEY_TIMECOUNT; ENABLE(1): uint8_t KEY_TIMECOUNT; IO_STATUS_LIST KEY_FLAG; IO_STATUS_LIST KEY_DOWN_LEVEL; IO_STATUS_LIST KEY_DOWN_LEVEL; // The actual level of IO when pressed KEY_STATUS_LIST KEY_STATUS; // KEY_STATUS_LIST KEY_EVENT; IO_STATUS_LIST (*READ_PIN)(Key_Init Key); // read IO level function}KEY_COMPONENTS;Copy the code

Next comes the button class

Typedef struct {Key_Init Key_Board; // Initialize the parent class KEY_COMPONENTS KeyStatus; }Key_Config;Copy the code

The registry is used to represent the number of existing keys for easy management

Typedef enum // key registry {KEY1, KEY2, KEY3, KEY4, KEY5, KEY6,// user added button name KEY_NUM, // must record the number of buttons, must be at the end}KEY_LIST;Copy the code

Here is the code in C:

Key_Config Key_Buf[KEY_NUM]; #define KEY_LONG_DOWN_DELAY 20 // Set 20 TIM3 timer interrupts 20*50 = 1s long press static void Get_Key_Level(void) // According to the actual level of the button pressed to convert it into a virtual result for(i = 0; i < KEY_NUM; I ++) {if(Key_Buf[I].keystatus.key_shield == DISABLE) if(Key_Buf[i].KeyStatus.READ_PIN(Key_Buf[i].Key_Board) == Key_Buf[i].KeyStatus.KEY_DOWN_LEVEL) Key_Buf[i].KeyStatus.KEY_FLAG = HIGH_LEVER; else Key_Buf[i].KeyStatus.KEY_FLAG = LOW_LEVEL; }} /* Create key_key */ static void Creat_Key(Key_Init* Init) {uint8_t I; for(i = 0; i < KEY_NUM; i++) { Key_Buf[i].Key_Board = Init[i]; Key_Buf[I].key_board.key_nox = I; // Initialize the state machine property of the button object Key_Buf[I].keystatus.key_shield = ENABLE; Key_Buf[i].KeyStatus.KEY_TIMECOUNT = 0; Key_Buf[i].KeyStatus.KEY_FLAG = LOW_LEVEL; If (Key_Buf[I].key_board. GPIO_Pull == GPIO_PULLUP) // Assign value according to mode Key_Buf[I].keystatus. KEY_DOWN_LEVEL = LOW_LEVEL; else Key_Buf[i].KeyStatus.KEY_DOWN_LEVEL = HIGH_LEVER; Key_Buf[i].KeyStatus.KEY_STATUS = KEY_NULL; Key_Buf[i].KeyStatus.KEY_EVENT = KEY_NULL; Key_Buf[i].KeyStatus.READ_PIN = KEY_ReadPin; Void KEY_Init(void) //IO initialization {KEY_Init KeyInit[KEY_NUM]= {{GPIO_PULLUP, GPIOC, GPIO_PIN_12} // Initialize key 0 {GPIO_PULLUP, GPIOC, GPIO_PIN_13}, // initialize key 1 {GPIO_PULLUP, GPIOA, GPIO_PIN_0}, GPIO_PULLUP, GPIOA, GPIO_PIN_1}, // Initialize key 3 {GPIO_PULLUP, GPIOA, GPIO_PIN_2}, // Initialize key 4 {GPIO_PULLUP, GPIOA, GPIO_PIN_3}, // Initialize key 5}; Creat_Key(KeyInit); // void ReadKeyStatus(void) {uint8_t I; Get_Key_Level(); for(i = 0; i < KEY_NUM; I ++) {switch(Key_Buf[I].keystatus.key_status) {// Status 0: no key pressed case KEY_NULL: If (Key_Buf[I].keystatus.KEY_FLAG == HIGH_LEVER)// If (Key_Buf[I].keystatus. Key_Buf[I].keystatus. KEY_EVENT = KEY_NULL; } else {Key_Buf[I].keystatus.key_event = KEY_NULL; } break; // State 1: Press the key to confirm case KEY_SURE: If (Key_Buf[I].keystatus. KEY_FLAG == HIGH_LEVER)// confirm same as before {Key_Buf[I].keystatus. KEY_STATUS = KEY_PRESS; Key_Buf[I].keystatus. KEY_EVENT = KEY_PRESS; Key_Buf[I].keyStatus.key_timecount = 0; // Press the event Key_Buf[I].keyStatus.key_timecount = 0; } else {Key_Buf[I].keystatus. KEY_STATUS = KEY_NULL; Key_Buf[I].keystatus. KEY_EVENT = KEY_NULL; } break; Case KEY_PRESS: if(Key_Buf[I].keystatus.key_flag! [I].keystatus.KEY_STATUS = KEY_NULL; Key_Buf[I].keystatus. KEY_EVENT = KEY_RAISE; Else if((Key_Buf[I].keyStatus.key_flag == HIGH_LEVER) && (++Key_Buf[I].keyStatus.key_timecount >= KEY_LONG_DOWN_DELAY)) {Key_Buf[I].keystatus. KEY_STATUS = KEY_LONG; Key_Buf[I].keystatus.key_event = KEY_LONG; Key_Buf[I].keyStatus.key_timecount = 0; } else {Key_Buf[I].keystatus.key_event = KEY_NULL; } break; Case KEY_LONG: if(Key_Buf[I].keystatus.key_flag! [I].keystatus.KEY_STATUS = KEY_NULL; Key_Buf[I].keystatus. KEY_EVENT = KEY_RAISE; Else if((Key_Buf[I].keyStatus.key_flag == HIGH_LEVER) && (++Key_Buf[I].keyStatus.key_timecount >= KEY_LONG_DOWN_DELAY)) {Key_Buf[I].keystatus.key_event = KEY_LONG; Key_Buf[I].keyStatus.key_timecount = 0; } else {Key_Buf[I].keystatus.key_event = KEY_NULL; } break; default: break; }}}Copy the code

In fact, the transition from NULL to SURE state is a process of key chattering. There are two level detections for each key press

All the code has been uploaded to the public account, you can enter the public account to obtain the above code.

Second, implementation method

  • The switch-case jump implements finite state machines
  • Finite state machines are implemented using data structures

There are two common ways to implement finite state machines

2.1. The switch – case

cur_state = nxt_state; Switch (cur_state) {/ / at the current state of events in case s0: / / s0 state if (e0_event) / / if e0 events occur, then the a0 action, and maintain a state of constant; {// perform a0; //nxt_state = s0; // The status number is self, so we can delete this sentence to improve the speed of the operation. } else if(e1_event) // If e1 event occurs, a1 action is executed and the state is changed to S1; {// perform a1 action; nxt_state = s1; } else if(e2_event) // If an E2 event occurs, perform an A2 action and change the state to S2; {// perform the a2 action; nxt_state = s2; } else { break; } case s1: // if(e2_event) if(e2_event) if(e2_event) if(e2_event) if(e2_event) if(e2_event) {// perform the a2 action; nxt_state = s2; } else { break; } case s2: if(e0_event) if(e0_event) if(e0_event) if(e0_event) if(e0_event) {// perform a0; nxt_state = s0; }}Copy the code

2.2 Data Structure

/ / struct FsmTable_s {int event; Int CurState; Void (*eventActFun)(); void (*eventActFun)(); // function pointer int NextState; }FsmTable_t;}FsmTable_t; /* struct FSM_s{int curState; FsmTable_t * pFsmTable; // status table int size; }FSM_t; */ void FSM_Regist(FSM_t* pFsm, FsmTable_t* pTable) {pFsm->pFsmTable = pTable; } /* stateTransfer (FSM_t* pFsm, int state) {pFsm->curState = state; } /* Event processing */ void FSM_EventHandle(FSM_t* pFsm, int event) {FsmTable_t* pActTable = pFsm->pFsmTable; void (*eventActFun)() = NULL; // The function pointer is initialized to null int NextState; int CurState = pFsm->curState; int g_max_num = pFsm->size; int flag = 0; Int I; /* for (I = 0; i<g_max_num; I ++) {if (Event == pActTable[I].event && CurState == pActTable[I].curState) {flag = 1; eventActFun = pActTable[i].eventActFun; NextState = pActTable[i].NextState; break; }} if (flag) {/* If (eventActFun) {eventActFun(); } // jump to the NextState FSM_StateTransfer(pFsm, NextState); } else { printf("there is no match\n"); }}Copy the code