Skip to content

[STM32 HAL] 为嵌入式开发提供一个全面的、易用的按键框架。Provide a comprehensive and simple button framework for embedded development.

License

Notifications You must be signed in to change notification settings

Kim-J-Smith/STM32-SimpleButton

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

23 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

STM32-SimpleButton

English 中文


This project is based on version-0.7.0 of Simple-Button

The current version of the project is: 0.3.1


Contents


Brief Introduction

Brief instruction of design

  • Buttons are the simplest, most common, and most effective input in embedded design. There are quite a few mature open source projects with buttons on GitHub today, but I still see a lot of problems that these projects don't address: Some projects don't support long-hold buttons, some don't support multi-click buttons, some don't support low power, and some have complex API. (The lack of low power support is a consequence of the project's use of polling instead of interrupts!)

  • There are a lot of projects that use polling because it has a natural advantage: users only need to implement the Read_Pin() functional interface to use polling, which is very portable; But polling comes with a natural drawback: the CPU must be working all the time to keep scanning, which is the natural contradiction of low power !

  • I got the idea to make my own non-polling button project. The project should have the following characteristics:

    1. Comprehensive Feature - "Comprehensive" means: at least supporting short press, long press, timer long press, double push, counter multiple push, combination buttons, and long press hold.
    1. Simple Deployment - "Simple" means: providing only ONE interface for creating buttons, and one line of code can register(create) a button statically.
    1. Use State Machine - The purpose is: To achieve non-blocking debouncing while having high scalability and a clear hierarchical structure. Different from similar lwbtn that incident reporting mechanism, although the traditional state machine will lead to state sort is more, but easy to debug and add a new state.
    1. Use EXTI - The purpose is: Using interrupts instead of polling is beneficial for low power consumption support.
  • So, after comprehensive consideration, I chose to use C language macros to simulate the generation of functions similar to C++ templates. Users only need to use the provided template to create a button object (which is actually a structure and three functions) with just one line of code.

  • Except for the initialization function which needs to be explicitly called, the calling methods of the other two functions are similar to the "methods" in C++. This is my attempt to implementing OOP using the C language.

Brief introduction of the feature

  • Under the guidance of the Design Concept, this project has implemented a pure C language button project based on the C99 standard (or C++11 standard).
  1. Comprehensive Features : This project currently supports short press, long press, timer long press, double push, counter multiple push, combination buttons, and long press hold.

  2. State Machine : This project uses a state machine for code organization, which has strong scalability. However, users can use it easily without having to understand the details of the state machine.

  3. External Interrupt(EXTI) : This project uses an external interrupt(EXTI) trigger button, naturally supporting low power consumption. The project also provides a line of code to determine and enter a low-power interface.

  4. Asynchronous Processing : The callback function is processed asynchronously to reduce the interrupt dwell time.

  5. Secondary Confirmation : This project performs secondary confirmation on the pin trigger signal, which can ensure stability even in scenarios with unstable power supply environments.

  6. Adjustable Time : This project supports setting the minimum time for long press determination, multi-click window time, and cooldown time for each button separately, making it convenient for button customization.

  7. Multi-threading Safety : This project supports enabling (multi-threading mode)[] to ensure multi-threading safety.

  8. Debugging Support : This project supports enabling (debugging mode)[] to locate anomalies, facilitating secondary development of the project.

Back to Contents


Usage

Overview

  • This project is based on Simple-Button, an out-of-the-box Simple-Button derivative for the STM32HAL library.

  • Step 1: You need to customize the transformation for your chip:

    • The STM32HAL library has been customized for this project, you just need to start with step 2.
  • Step 2: Use project to create and use button: ([] represents optional step)

    • 2.1 - Use the SIMPLEBTN__CREATE() macro to create the required buttons.
    • [2.2] - Declare the created button (if used in another file) using the SIMPLEBTN__DECLARE() macro.
    • 2.3 - Calling the button initializer before the while loop in the main function.
    • 2.4 - Calling buttons' asynchronous handler inside a while loop.
    • 2.5 - Calling buttons' interrupt handler function from the EXTI interrupt function.

Detailed Steps

Step 1

  • The STM32HAL library has been customized for this project, you just need to start with step 2.

Step 2

  1. Buttons can be created with a single macro. But in real projects we often want to have a single .c file to manage all the buttons we need and a .h file to use as an interface. The two API macros provided by this project can well complete the create + declare two work. The typical project directory structure is the one used in steps 2.1-2.5 below :
.
|
+-- simple_button_config.h  # The header file provided by this project is responsible for providing configuration information
|
+-- Simple_Button.h  # The main file provided by this project.
|
+-- my_buttons.c  # User's file, in where buttons will be created.
|
+-- my_buttons.h  # User's file, in where buttons will be declared.
|
+-- main.c  # User's file, importing "my_buttons.h", and using the buttons.
  1. Use the SIMPLEBTN__CREATE() macro to create the required buttons. Create three buttons and connect them respectively to GPIOA-Pin0, GPIOB-Pin1, and GPIOD-Pin14, all triggered by the falling edge. Name them respectively as SB1, SB2, and SB3. The STM32-HAL example is as follows (the following code is located at my_buttons.c) :
#include "Simple_Button.h"

/* no ';' after Macro */

SIMPLEBTN__CREATE(GPIOA_BASE, GPIO_PIN_0, EXTI_TRIGGER_FALLING, SB1)

SIMPLEBTN__CREATE(GPIOB_BASE, GPIO_PIN_1, EXTI_TRIGGER_FALLING, SB2)

SIMPLEBTN__CREATE(GPIOD_BASE, GPIO_PIN_14, EXTI_TRIGGER_FALLING, SB3)
  1. The button created using the SIMPLEBTN__DECLARE() macro declaration (if used in another file). Following the three buttons created in 2.1, here demonstrates how to declare the three created buttons in my_buttons.
#include "Simple_Button.h"

/* no ';' after Macro */

SIMPLEBTN__DECLARE(SB1)

SIMPLEBTN__DECLARE(SB2)

SIMPLEBTN__DECLARE(SB3)
  1. The button initializers are called before the while loop in the main function. Following up on the previous step, here's an example:
#include "my_buttons.h"

int main(void) {

    SimpleButton_SB1_Init();
    SimpleButton_SB2_Init();
    SimpleButton_SB3_Init();

    while (1) {

    }
}
  1. Call the button asynchronous handler inside the while loop. Following up from the previous step, prepare short press, long press, and double click callbacks (see Advanced features for more features), and pass in a while loop that handles buttons asynchronically, as shown in the following example (the following code is located in main.c) :
#include "my_buttons.h"

/* Prepare the callback functions for 'short press', 'long press', and 'double-click'. By default, they have no parameters and no return values */
void TurnOn_LED(void) {
    /* The function name is arbitrary, as long as it has no parameters and no return */
    /* The function will be called after the corresponding event is triggered */
}

void DoSomething(void) {
    /* ... */
}

int main(void) {

    SimpleButton_SB1_Init();
    SimpleButton_SB2_Init();
    SimpleButton_SB3_Init();

    while (1) {
        //Pass the 'short press', 'long press', 'double click' callback function in turn, or NULL or 0 if not needed.
        SimpleButton_SB1.Methods.asynchronousHandler(
            TurnOn_LED,
            NULL,
            DoSomething
        );
        SimpleButton_SB2.Methods.asynchronousHandler(
            NULL,
            NULL,
            DoSomething
        );
        SimpleButton_SB3.Methods.asynchronousHandler(
            NULL,
            DoSomething,
            NULL
        );
    }
}
  1. The final step is to call the button interrupt handling function in the EXTI interrupt function. Here, it is divided into two situations: 1. You need to implement the interrupt function yourself. 2. There are already ready-made interrupt callback functions (for example, the HAL library of STM32 uses CubeMX to generate code). Here are some examples to illustrate:

    • You need to implement the interrupt function yourself. Most single-chip microcomputer bare-metal development requires this to be done. Go to the assembly startup file to find the Interrupt Vector Table, then locate the interrupt corresponding to the EXTI pin and implement the interrupt function. Continuing from the previous step, taking the STM32 standard library as an example:
        // SB1 ----- Pin0
        void EXTI0_IRQHandler(void) {
            if (EXTI_GetITStatus(EXTI_Line0) == SET) {
                SimpleButton_SB1.Methods.interruptHandler();
    
                EXTI_ClearITPendingBit(EXTI_Line0);
            }
        }
    
        // SB2 ----- Pin1
        void EXTI1_IRQHandler(void) {
            if (EXTI_GetITStatus(EXTI_Line1) == SET) {
                SimpleButton_SB2.Methods.interruptHandler();
    
                EXTI_ClearITPendingBit(EXTI_Line1);
            }
        }
    
        // SB3 ----- Pin14
        void EXTI15_10_IRQHandler(void) {
            if (EXTI_GetITStatus(EXTI_Line14) == SET) {
                SimpleButton_SB3.Methods.interruptHandler();
    
                EXTI_ClearITPendingBit(EXTI_Line14);
            }
        }
    • There are already interrupt callbacks out there. Just find the generated callback and call interruptHandler() from there. Following up on the previous step, using the HAL library generated by STM32 CubeMX as an example, stm32f1xx_hal_gpio.c provides this weak functional interface:
    __weak void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
    {
        /* Prevent unused argument(s) compilation warning */
        UNUSED(GPIO_Pin);
        /* NOTE: This function Should not be modified, when the callback is needed,
           the HAL_GPIO_EXTI_Callback could be implemented in the user file
        */
    }

    So, let's copy the code above to main.c and call interruptHandler() there, like so:

    // no __weak
    void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
    {
        switch (GPIO_Pin) {
        case GPIO_PIN_0: {
            SimpleButton_SB1.Methods.interruptHandler();
            SimpleButton_SB2.Methods.interruptHandler();
            break;
        }
        case GPIO_PIN_14: {
            SimpleButton_SB3.Methods.interruptHandler();
            break;
        }
        }
    }

Back to Contents


Advanced Features

Timer Long Push

  • Sometimes, a single long press is not enough for our needs, and we want different long presses to have different effects. This is where you need to use an advanced feature called timer long push.
  • Find Mode-Set in CUSTOMIZATION at the top of the file simple_button_config.h, Change #define SIMPLEBTN_MODE_ENABLE_TIMER_LONG_PUSH 0 to #define SIMPLEBTN_MODE_ENABLE_TIMER_LONG_PUSH 1 to enable timer long push.
  • With this feature enabled, the button's long-press callback will no longer have a no-parameter, no-return type, but a uint32_t, no-return type, which will take the duration of the long-press.
  • Here's an example (the initialization function, interrupt handling, and short press/double click callbacks do not make any special changes here and are omitted without demonstration) :
/* Macro definition needs to be changed at Mode-Set at the beginning of the file `simple_button_config.h` to enable timed long presses */
#define SIMPLEBTN_MODE_ENABLE_TIMER_LONG_PUSH           1

/* Prepare the long-press callback with arguments */
void TimerLongPush_CallBack(uint32_t pushTime) {
    if (pushTime < 5000) {
    /* Long press less than 5 seconds function 1 */
    } else {
    /* Long press for 5 seconds or more 2 */
    }
}

int main(void) {
    /* ... */
    while (1) {
        SimpleButton_SB1.Methods.asynchronousHandler(
            NULL,
            TimerLongPush_CallBack,
            NULL
        );
    }
}

/* ... */

Counter Repeat Push

  • Sometimes, a simple double click doesn't fit our needs. We might need three, four... In this case, we need to enable the Counter Repeat Push feature.
  • Find Mode-Set in CUSTOMIZATION at the top of the file simple_button_config.h, Change #define SIMPLEBTN_MODE_ENABLE_COUNTER_REPEAT_PUSH 0 to #define SIMPLEBTN_MODE_ENABLE_COUNTER_REPEAT_PUSH 1 to enable Counter Repeat Push.
  • With this feature enabled, the no-parameter, no-return double-click callback function for a button will become a no-return function with a uint8_t parameter, which takes the actual number of presses. So what was passed into the double-click callback function is actually passed into the counting multi-click callback function.
  • Here's an example (the initialization function, interrupt handling, and short press/long press callbacks do not change here, so they are omitted without demonstration) :
/* Need to change macro definition at Mode-Set at the beginning of the file `simple_button_config.h` to enable counting multi-clicks */
#define SIMPLEBTN_MODE_ENABLE_COUNTER_REPEAT_PUSH       1

/* Get ready to count multi-click callbacks (instead of the original double-click callback) */
void CounterRepeatPush_CallBack(uint8_t pushTime) {
    switch (pushTime) {
    case 2:
        /* ... */
        break;
    case 3:
        /*... */
        break;
    case 4:
        /*... */
        break;

    case 10:
        /*... */
        break;
    default:
        break;
    }
}

int main(void) {
    /*... */
    while (1) {
        SimpleButton_SB1.Methods.asynchronousHandler(
            NULL,
            NULL,
            CounterRepeatPush_CallBack
        );
    }
}

Long Push Hold

  • Sometimes we want a function to fire intermittently and continuously after a button press. This is where Long Push Hold comes in.
  • Find Mode-Set in CUSTOMIZATION at the top of the file simple_button_config.h, Change #define SIMPLEBTN_MODE_ENABLE_LONGPUSH_HOLD 0 to #define SIMPLEBTN_MODE_ENABLE_LONGPUSH_HOLD 1to enable Long Push Hold.
  • With this feature enabled, the incoming long press callback function remains empty (if you also enable timer long push, it will have a uint32_t argument just like normal timer long push), the only difference is that the function will be called periodically for as long as you keep pressing the button.
  • The example is omitted because the user code has not changed, only the callback timing has changed.

Button Combinations

  • Sometimes we want a combination of buttons to do something completely new. This is where button combinations come in.
  • Find Mode-Set in CUSTOMIZATION at the top of the file simple_button_config.h, Change #define SIMPLEBTN_MODE_ENABLE_COMBINATION 0 to #define SIMPLEBTN_MODE_ENABLE_COMBINATION 1 to enable button combinations.
  • The button combination in this project ispredecessor button+successor button. The composite button's callback is bound to the next button and specifies itsprevious buttonat thenext button. When the user presses the next button during the previous button press, the button combination callback function bound to the next button is triggered.
  • button combinations are in order. button A + button B is A different combination from button B + button A.
  • Neither the predecessor nor the successor button will trigger their short press, long press/timed long press/hold, double click/count multi-click callbacks after the button combination fires. (But if the [keep-long-press](# keep-long-press) mode is enabled and the keep-long-press callback is triggered before the buttonstroke is triggered, the buttonstroke will not work!!)
  • While composite button callbacks don't pass asynchronous handlers as arguments, async handlers can't be missing.
  • Here's an example (the initialization function, interrupt handling, short press/long press/multi-click callbacks do not change here, so they are omitted without demonstration) :
/* Macro definition needs to be changed at Mode-Set at the beginning of the file `simple_button_config.h` to enable button combinations */
#define SIMPLEBTN_MODE_ENABLE_COMBINATION               1

// Press SB1 then SB2 callback function
void Cmb_SB1_then_SB2_CallBack(void) {
    /*... */
}

// Press SB2 then SB1's callback function
void Cmb_SB2_then_SB1_CallBack(void) {
    /*... */
}

int main(void) {
    /* Initialize first, otherwise data will be overwritten */

    /* Configure composite buttons after initialization */

    // SB2 prepend SB1 and configure the SB1 --> SB2 combo callback
    SimpleButton_SB2.Public.combinationConfig.previousButton = &SimpleButton_SB1;
    SimpleButton_SB2.Public.combinationConfig.callBack = Cmb_SB1_then_SB2_CallBack;

    // SB1 prepend SB2 and configure the SB2 --> SB1 combo callback
    SimpleButton_SB1.Public.combinationConfig.previousButton = &SimpleButton_SB2;
    SimpleButton_SB1.Public.combinationConfig.callBack = Cmb_SB2_then_SB1_CallBack;

    while (1) {
        SimpleButton_SB1.Methods.asynchronousHandler(
            NULL,
            NULL,
            NULL
        );
        SimpleButton_SB2.Methods.asynchronousHandler(
            NULL,
            NULL,
            NULL
        );
    }
}

Low Power

  • CPU idling makes no sense when buttons are not pressed. At this point, the low-power mode can be entered.
  • This project provides a macro function SIMPLEBTN__START_LOWPOWER (C99 + or C++11 + only) that determines if all buttons are idle and enters lowpower mode. One line of code enters lowpower.
  • Low power support is a core design motivation for this project, which is covered in detail in Low Power Design.
  • A simple example is as follows (no special changes are made to initialization, async handlers, interrupt handlers, etc.) :
/*... */

int main(void) {
    /*... */

    while (1) {
        if ( /* all else allowed, enter low power */ ) {
            // This is a variadic macro, passing in all button objects
            SIMPLEBTN__START_LOWPOWER(SimpleButton_SB1, SimpleButton_SB2, SimpleButton_SB3);
        }
    }
}

/*... */

Adjustable time

  • There are many important "decision times" in this project, such as: minimum long press time, window time for multiple clicks, button cooldown time... Maybe you need to configure different decision times for different buttons, in which case, you need to use the adjustable time feature.
  • Find Mode-Set in CUSTOMIZATION at the top of the file simple_button_config.h, Change #define SIMPLEBTN_MODE_ENABLE_ONLY_DEFAULT_TIME 1 to #define SIMPLEBTN_MODE_ENABLE_ONLY_DEFAULT_TIME 0, to enable adjustable time. (Note! It is turned on when it is 0. Usually enabled by default)
  • The following is an example (no special changes are made to initialization, async handlers, interrupt handlers, etc.) :
/* Need to change macro definition value at Mode-Set at the beginning of file `simple_button_config.h` to enable adjustable time */
#define SIMPLEBTN_MODE_ENABLE_ONLY_DEFAULT_TIME         0

int main(void) {
    /*... */
    /* Initialize first, otherwise data will be overwritten */

    /* Configure adjustable time after initialization */
    SimpleButton_SB1.Public.longPushMinTime = 5000;  // Min press time changed to 5 seconds
    SimpleButton_SB1.Public.coolDownTime = 1000; // cooldown changed to 1 second
    SimpleButton_SB1.Public.repeatWindowTime = 0;  // No double/multiple clicks

    while (1) {
        /*... */
    }
}

/*... */

Name prefixes/namespaces

  • Sometimes we don't want to use the SimpleButton_ prefix and need to define a custom one. This is easily achieved with name prefixes/namespaces.
  • Just find the Namespace in CUSTOMIZATION at the top of the file simple_button_config.h, Just change #define SIMPLEBTN_NAMESPACE SimpleButton_ to the custom prefix you need.
  • Here is an example of #define SIMPLEBTN_NAMESPACE SB_ :
// Find the 'Namespace' in 'CUSTOMIZATION' at the top of the file `simple_button_config.h` and modify the following macro
#define SIMPLEBTN_NAMESPACE                             SB_

// Suppose the name of the case is myButton when creating the button in SIMPLEBTN__CREATE().

int main(void) {
    // Initialize the button with SB_myButton_Init() instead of SimpleButton_myButton_Init()
    SB_myButton_Init();

    while (1) {
        // Call the asynchronous handler with SB_myButton instead of SimpleButton_myButton
        SB_myButton.Methods.asynchronousHandler(...) ;
    }
}

void EXTI0_IRQHandler(void) {
    // Call the interrupt handler with SB_myButton instead of SimpleButton_myButton
    SB_myButton.Methods.interruptHandler();
}

Back to Contents


State Machine Graph

stateDiagram-v2

    classDef Begin_Point_State fill: #8dbfe0ff,stroke:#0369a1,stroke-width:2px,color:black
    class Wait_For_Interrupt Begin_Point_State
    
    %% Core States
    Wait_For_Interrupt --> Push_Delay: EXTI trigger
    Push_Delay --> Wait_For_End: Delay
    Push_Delay --> Wait_For_Interrupt: **Found to be a false trigger**
    Wait_For_End --> Release_Delay: Release button
    Release_Delay --> Wait_For_End: **Found didn't release**
    Release_Delay --> Wait_For_Repeat: **Confirm release**
    Wait_For_Repeat --> Repeat_Push: Push again
    Wait_For_Repeat --> Single_Push: repeat-push-window timeout
    Repeat_Push --> Cool_Down: do repeat-callback
    Single_Push --> Cool_Down: do single-callback
    Cool_Down --> Wait_For_Interrupt: cool down over
    
    %% Comination Button 

    Wait_For_End --> Combination_WaitForEnd: **next button** is pushed down

    state Combination {
        Combination_WaitForEnd
        Combination_Release
        Combination_Push
    }

    Combination_WaitForEnd --> Combination_Release: release
    Combination_Release --> Combination_WaitForEnd: Reconfirm release failed
    Combination_Release --> Cool_Down: Reconfirm release success

    Release_Delay --> Combination_Push: **next button** is pushed down
    Combination_Push --> Cool_Down: do combination_callback
    
    state Hold {
        Hold_Push
        Hold_Release
    }

    Wait_For_End --> Hold_Push: time reached
    Hold_Push --> Hold_Release: release button
    Hold_Release --> Hold_Push: Reconfirm release failed
    Hold_Release --> Cool_Down: Reconfirm release success

    Wait_For_End --> Wait_For_Interrupt: safe-time timeout
    Combination_WaitForEnd --> Wait_For_Interrupt: safe-time timeout
    
    note left of Wait_For_Interrupt
        Wait for interrupt
        Idle
    end note

Loading

Back to Contents


Low Power Design

  • Low power consumption is the main objective of this project design: Using external interrupts instead of polling provides natural interrupt support.

  • When all the buttons are inactive, a function like __WFI() can be used to stop the CPU's clock and enter a low-power mode. When a button is pressed and an external interrupt is triggered, the button will be awakened.

  • This project provides the SIMPLEBTN__START_LOWPOWER(...) function for directly accessing the low-power interface. The usage method has been described in the [Low Power] section (#low-power).

  • __WFI() is undoubtedly the simplest function for entering low-power mode, but doing so may not yield the exact results one expects. Here are some suggestions for achieving low power consumption:

  1. Before entering the low-power mode, it is recommended to configure all I/O as pull-up/pull-down inputs or analog inputs to prevent floating of the chip I/O and the generation of leakage current. [1]

  2. For the small package type of chips, compared to the largest package, the unconnected pins should be configured as pull-up/pull-down inputs or analog inputs; otherwise, it may affect the current indicators. [1]

  3. Release the SWD debugging interface and configure it as a GPIO function. It should be set as an input with pull-up/pull-down or as an analog input (the SWD function will be restored after wake-up). [1]

  4. It is recommended to completely turn off all unnecessary peripherals before entering the low-power mode. If conditions permit, disable the PLL switching to a lower-speed clock to save energy.

[1]: Refer to https://github.com/openwch/ch32_application_notes

  1. Therefore, you may need to customize the SIMPLEBTN_FUNC_START_LOW_POWER() macro interface, which will be called by SIMPLEBTN__START_LOWPOWER(...). The pseudo-code example is as follows:
// Find this macro at Other-Functions at the beginning of file.
#define SIMPLEBTN_FUNC_START_LOW_POWER()    simpleButton_start_low_power()

static inline void simpleButton_start_low_power(void) {
    config_GPIO_IPD_for_low_power();
    Disable_SWD();
    Disable_some_Periph();
    Disable_PLL();

    __WFI();

    Enable_PLL();
    Enable_some_Periph();
    Enable_SWD();
    config_GPIO_normal();
}

Back to Contents


Derivative Project

Back to Contents

About

[STM32 HAL] 为嵌入式开发提供一个全面的、易用的按键框架。Provide a comprehensive and simple button framework for embedded development.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages