A lightweight C library for microcontroller button input debouncing and advanced click pattern detection.
- Hardware debouncing - Filters out electrical noise from button presses
- Multiple click patterns - Detects single, double, triple clicks
- Long press support - Recognizes long presses and long multi-clicks
- Configurable timing - Customizable debounce, press, and cooldown intervals
- Callback-based - Non-blocking event-driven architecture
- Memory efficient - Low memory footprint
- Timer agnostic - Works with any timer implementation
- Single click
- Double click
- Triple click
- Long press
- Long double click
- Long triple click
Add this to your CMakeLists.txt to automatically download and build the library:
include(FetchContent)
FetchContent_Declare(
misclick
GIT_REPOSITORY https://github.com/cyborgize/misclick.git
GIT_TAG main # or a specific version tag like v1.0.0
)
FetchContent_MakeAvailable(misclick)
# Link to your targettarget_link_libraries(your_targetPRIVATEmisclick)This example shows integration with Zephyr RTOS, but the same patterns apply to other platforms.
#include<zephyr/kernel.h>#include<zephyr/drivers/gpio.h>#include"misclick/misclick.h"// Zephyr timers for the misclick librarystaticstructk_timermisclick_state_timer;
staticstructk_timermisclick_click_timer;
// Timer callback handlersstaticvoidmisclick_state_timer_handler(structk_timer*timer) {
misclick_handle_state_timeout(k_uptime_get() *1000); // Convert ms to us
}
staticvoidmisclick_click_timer_handler(structk_timer*timer) {
misclick_handle_click_timeout(k_uptime_get() *1000); // Convert ms to us
}
// Timer interface functions for misclick librarystaticvoidmisclick_stop_timer(void*handle) {
structk_timer*timer= (structk_timer*)handle;
k_timer_stop(timer);
}
staticvoidmisclick_start_timer(void*handle, int64_ttimeout_us) {
structk_timer*timer= (structk_timer*)handle;
k_timer_start(timer, K_USEC(timeout_us), K_NO_WAIT);
}// Handle different click patternsstaticvoidbutton_click_callback(void*callback_arg, intbutton_id,
enummisclick_click_tclick, int64_ttimestamp) {
switch (click) {
caseMISCLICK_CLICK_SINGLE:
printk("Single click detected\\n");
// Trigger action for single clickbreak;
caseMISCLICK_CLICK_DOUBLE:
printk("Double click detected\\n");
// Trigger action for double clickbreak;
caseMISCLICK_CLICK_TRIPLE:
printk("Triple click detected\\n");
// Trigger action for triple clickbreak;
caseMISCLICK_CLICK_LONG:
printk("Long press detected\\n");
// Trigger action for long pressbreak;
caseMISCLICK_CLICK_LONG_DOUBLE:
printk("Long double click detected\\n");
break;
caseMISCLICK_CLICK_LONG_TRIPLE:
printk("Long triple click detected\\n");
break;
caseMISCLICK_CLICK_CANCELLED:
printk("Click sequence cancelled\\n");
break;
}
printk("Button %d click: %d at %lld\\n", button_id, click, timestamp);
}
// Optional: Handle individual press/release eventsstaticvoidbutton_press_callback(void*callback_arg, intbutton_id,
enummisclick_press_tpress, int64_ttimestamp) {
printk("Button %d press: %d at %lld\\n", button_id, press, timestamp);
}staticstructmisclick_t*main_button=NULL;
staticvoidinit_buttons(void) {
// Initialize timersk_timer_init(&misclick_state_timer, misclick_state_timer_handler, NULL);
k_timer_init(&misclick_click_timer, misclick_click_timer_handler, NULL);
// Configure the misclick librarystructmisclick_config_tconfig= {
.state_timer_handle=&misclick_state_timer,
.click_timer_handle=&misclick_click_timer,
.stop_timer=misclick_stop_timer,
.start_timer=misclick_start_timer,
.debounce_time_us=DEFAULT_MISCLICK_DEBOUNCE_TIME_US,
.long_press_time_us=DEFAULT_MISCLICK_LONG_PRESS_TIME_US,
.next_press_time_us=DEFAULT_MISCLICK_NEXT_PRESS_TIME_US,
.final_press_time_us=DEFAULT_MISCLICK_FINAL_PRESS_TIME_US,
.cooldown_time_us=DEFAULT_MISCLICK_COOLDOWN_TIME_US,
};
misclick_init(&config);
// Add main buttonstructmisclick_params_tbutton_params= {
.button_id=0,
.callback_arg=NULL,
.click_callback=button_click_callback,
.press_callback=button_press_callback,
};
main_button=misclick_add(&button_params);
}// GPIO interrupt callback (called from ISR context)staticvoidbutton_gpio_callback(conststructdevice*dev,
structgpio_callback*cb, uint32_tpins) {
if (!main_button) {
return;
}
// Read current button state (inverted since button is active low)intbutton_state= !gpio_pin_get_dt(&button_gpio);
// Send button event to misclick library// Convert milliseconds to microseconds for timestampmisclick_handle_input_event(main_button, button_state, k_uptime_get() *1000);
}intmain(void) {
// Initialize GPIO for button inputif (!device_is_ready(button_gpio.port)) {
printk("Error: button device not ready\\n");
return-1;
}
// Configure button pin as input with interruptgpio_pin_configure_dt(&button_gpio, GPIO_INPUT);
gpio_pin_interrupt_configure_dt(&button_gpio, GPIO_INT_EDGE_BOTH);
// Set up GPIO callbackgpio_init_callback(&button_cb_data, button_gpio_callback, BIT(button_gpio.pin));
gpio_add_callback(button_gpio.port, &button_cb_data);
// Initialize misclick libraryinit_buttons();
// Application main loopwhile (1) {
// Your application logic herek_sleep(K_MSEC(100));
}
return0;
}#defineDEFAULT_MISCLICK_DEBOUNCE_TIME_US 40000 // 40ms debounce
#defineDEFAULT_MISCLICK_LONG_PRESS_TIME_US 600000 // 600ms for long press
#defineDEFAULT_MISCLICK_NEXT_PRESS_TIME_US 500000 // 500ms between clicks
#defineDEFAULT_MISCLICK_FINAL_PRESS_TIME_US 500000 // 500ms final click timeout
#defineDEFAULT_MISCLICK_COOLDOWN_TIME_US 1000000 // 1s cooldown after complex patternsThe library requires two timers with microsecond precision:
- State timer: Used for input debouncing
- Click timer: Used for click pattern detection timeouts
Implement the timer interface functions according to your platform:
voidmy_start_timer(void*handle, int64_ttimeout_us) {
// Cast handle to your timer instance and configure for one-shot operation// Set the timeout period and start the timer// Timer should fire once after timeout_us microseconds
}
voidmy_stop_timer(void*handle) {
// Stop and reset the specified timer instance
}Requirements:
- Create two separate timer instances (for state and click timers)
- Configure timers for one-shot mode (fire once, then stop)
- Implement microsecond-precision timing
- Call the library timeout handlers from your timer interrupt callbacks
- Per button: ~40 bytes
- Global state: ~100 bytes
- No dynamic allocation after initialization
This library is not thread-safe. If using in a multi-threaded environment, provide your own synchronization.
Licensed under the Apache License, Version 2.0. See LICENSE for the full license text.