├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── button.c ├── button.h ├── component.mk ├── example ├── Makefile └── button_example.c ├── port.c ├── port.h ├── resources ├── active-high-wiring.png └── active-low-wiring.png ├── toggle.c └── toggle.h /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | firmware/ 3 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_component_register( 2 | SRCS toggle.c button.c port.c 3 | INCLUDE_DIRS . 4 | ) 5 | target_compile_options(${COMPONENT_LIB} PRIVATE -DESP_IDF) 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Maxim Kulkin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | esp-button 2 | ========== 3 | Library for [ESP-OPEN-RTOS](https://github.com/SuperHouse/esp-open-rtos) to handle 4 | button and toggle input. 5 | 6 | Before you start using library, you need to figure out how button is/will be wired. 7 | There are two ways to wire button: 8 | * active high - signal goes from low to high when button is pressed 9 | 10 | ![Active high wiring](resources/active-high-wiring.png) 11 | * active low - signal connects to ground when button is pressed 12 | 13 | ![Active low wiring](resources/active-low-wiring.png) 14 | 15 | ```c 16 | #include 17 | 18 | #define BUTTON_PIN 5 19 | 20 | void button_callback(button_event_t event, void* context) { 21 | printf("button press\n"); 22 | } 23 | 24 | button_config_t config = BUTTON_CONFIG(button_active_high); 25 | 26 | int r = button_create(BUTTON_PIN, config, button_callback, NULL); 27 | if (r) { 28 | printf("Failed to initialize a button\n"); 29 | } 30 | ``` 31 | 32 | Button config settings: 33 | * **active_level** - `button_active_high` or `button_active_low` - which signal level corresponds to button press. In case of `button_active_low`, it automatically enables pullup resistor on button pin. In case of `button_active_high`, you need to have an additional pulldown (pin-to-ground) resistor on button pin. 34 | * **long\_press_time** - if set, defines time in milliseconds, after which button press is considered a long press. If set to 0, long press tracking is disabled. 35 | * **max\_repeat_presses** - maximum number of repeated presses. Valid values are 1, 2 or 3 (single, double or tripple presses). 36 | * **repeat\_press_time** - defines maximum time in milliseconds to wait for subsequent press to consider it a double/tripple press (defaults to 300ms). 37 | 38 | Implementation effectively handles debounce, no additional configuration is required. 39 | 40 | Example of using button with support of single, double and tripple presses: 41 | 42 | ```c 43 | #include 44 | 45 | #define BUTTON_PIN 5 46 | 47 | void button_callback(button_event_t event, void* context) { 48 | switch (event) { 49 | case button_event_single_press: 50 | printf("single press\n"); 51 | break; 52 | case button_event_double_press: 53 | printf("double press\n"); 54 | break; 55 | case button_event_tripple_press: 56 | printf("tripple press\n"); 57 | break; 58 | case button_event_long_press: 59 | printf("long press\n"); 60 | break; 61 | } 62 | } 63 | 64 | button_config_t config = BUTTON_CONFIG( 65 | button_active_high, 66 | .long_press_time = 1000, 67 | .max_repeat_presses = 3, 68 | ); 69 | 70 | int r = button_create(BUTTON_PIN, config, button_callback, NULL); 71 | if (r) { 72 | printf("Failed to initialize a button\n"); 73 | } 74 | ``` 75 | 76 | Example of using a toggle: 77 | 78 | ```c 79 | #include 80 | 81 | #define TOGGLE_PIN 4 82 | 83 | void toggle_callback(bool high, void *context) { 84 | printf("toggle is %s\n", high ? "high" : "low"); 85 | } 86 | 87 | int r = toggle_create(TOGGLE_PIN, toggle_callback, NULL); 88 | if (r) { 89 | printf("Failed to initialize a toggle\n"); 90 | } 91 | ``` 92 | 93 | Note: when using toggle, you need to make sure that signal is propperly pulled 94 | up/down (either add resistor in case of pull up or enable builtin pullup resistor on 95 | corresponding pin). 96 | 97 | License 98 | ======= 99 | MIT licensed. See the bundled [LICENSE](https://github.com/maximkulkin/esp-button/blob/master/LICENSE) file for more details. 100 | -------------------------------------------------------------------------------- /button.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "toggle.h" 6 | #include "button.h" 7 | #include "port.h" 8 | 9 | 10 | typedef struct _button { 11 | uint8_t gpio_num; 12 | button_config_t config; 13 | button_callback_fn callback; 14 | void* context; 15 | 16 | uint8_t press_count; 17 | TimerHandle_t long_press_timer; 18 | TimerHandle_t repeat_press_timeout_timer; 19 | 20 | struct _button *next; 21 | } button_t; 22 | 23 | static SemaphoreHandle_t buttons_lock = NULL; 24 | static button_t *buttons = NULL; 25 | 26 | static void button_fire_event(button_t *button) { 27 | button_event_t event = button_event_single_press; 28 | 29 | switch (button->press_count) { 30 | case 1: event = button_event_single_press; break; 31 | case 2: event = button_event_double_press; break; 32 | case 3: event = button_event_tripple_press; break; 33 | } 34 | 35 | button->callback(event, button->context); 36 | button->press_count = 0; 37 | } 38 | 39 | static void button_toggle_callback(bool high, void *context) { 40 | if (!context) 41 | return; 42 | 43 | button_t *button = (button_t*) context; 44 | if (high == (button->config.active_level == button_active_high)) { 45 | // pressed 46 | button->press_count++; 47 | if (button->config.long_press_time && button->press_count == 1) { 48 | xTimerStart(button->long_press_timer, 1); 49 | } 50 | } else { 51 | // released 52 | if (!button->press_count) 53 | return; 54 | 55 | if (button->long_press_timer 56 | && xTimerIsTimerActive(button->long_press_timer)) { 57 | xTimerStop(button->long_press_timer, 1); 58 | } 59 | 60 | if (button->press_count >= button->config.max_repeat_presses 61 | || !button->config.repeat_press_timeout) { 62 | if (button->repeat_press_timeout_timer 63 | && xTimerIsTimerActive(button->repeat_press_timeout_timer)) { 64 | xTimerStop(button->repeat_press_timeout_timer, 1); 65 | } 66 | 67 | button_fire_event(button); 68 | } else { 69 | xTimerStart(button->repeat_press_timeout_timer, 1); 70 | } 71 | } 72 | } 73 | 74 | static void button_long_press_timer_callback(TimerHandle_t timer) { 75 | button_t *button = (button_t*) pvTimerGetTimerID(timer); 76 | 77 | button->callback(button_event_long_press, button->context); 78 | button->press_count = 0; 79 | } 80 | 81 | static void button_repeat_press_timeout_timer_callback(TimerHandle_t timer) { 82 | button_t *button = (button_t*) pvTimerGetTimerID(timer); 83 | 84 | button_fire_event(button); 85 | } 86 | 87 | static void button_free(button_t *button) { 88 | if (button->long_press_timer) { 89 | xTimerStop(button->long_press_timer, 1); 90 | xTimerDelete(button->long_press_timer, 1); 91 | } 92 | 93 | if (button->repeat_press_timeout_timer) { 94 | xTimerStop(button->repeat_press_timeout_timer, 1); 95 | xTimerDelete(button->repeat_press_timeout_timer, 1); 96 | } 97 | 98 | free(button); 99 | } 100 | 101 | static int buttons_init() { 102 | if (!buttons_lock) { 103 | buttons_lock = xSemaphoreCreateBinary(); 104 | xSemaphoreGive(buttons_lock); 105 | } 106 | 107 | return 0; 108 | } 109 | 110 | int button_create(const uint8_t gpio_num, 111 | button_config_t config, 112 | button_callback_fn callback, 113 | void* context) 114 | { 115 | if (!buttons_lock) { 116 | buttons_init(); 117 | } 118 | 119 | xSemaphoreTake(buttons_lock, portMAX_DELAY); 120 | button_t *button = buttons; 121 | while (button && button->gpio_num != gpio_num) 122 | button = button->next; 123 | 124 | bool exists = button != NULL; 125 | xSemaphoreGive(buttons_lock); 126 | 127 | if (exists) 128 | return -1; 129 | 130 | button = malloc(sizeof(button_t)); 131 | memset(button, 0, sizeof(*button)); 132 | button->gpio_num = gpio_num; 133 | button->config = config; 134 | button->callback = callback; 135 | button->context = context; 136 | if (config.long_press_time) { 137 | button->long_press_timer = xTimerCreate( 138 | "Button Long Press Timer", pdMS_TO_TICKS(config.long_press_time), 139 | pdFALSE, button, button_long_press_timer_callback 140 | ); 141 | if (!button->long_press_timer) { 142 | button_free(button); 143 | return -2; 144 | } 145 | } 146 | if (config.max_repeat_presses > 1) { 147 | button->repeat_press_timeout_timer = xTimerCreate( 148 | "Button Repeat Press Timeout Timer", pdMS_TO_TICKS(config.repeat_press_timeout), 149 | pdFALSE, button, button_repeat_press_timeout_timer_callback 150 | ); 151 | if (!button->repeat_press_timeout_timer) { 152 | button_free(button); 153 | return -3; 154 | } 155 | } 156 | 157 | my_gpio_enable(button->gpio_num); 158 | if (config.active_level == button_active_low) { 159 | my_gpio_pullup(button->gpio_num); 160 | } else { 161 | my_gpio_pulldown(button->gpio_num); 162 | } 163 | 164 | int r = toggle_create(gpio_num, button_toggle_callback, button); 165 | if (r) { 166 | button_free(button); 167 | return -4; 168 | } 169 | 170 | xSemaphoreTake(buttons_lock, portMAX_DELAY); 171 | 172 | button->next = buttons; 173 | buttons = button; 174 | 175 | xSemaphoreGive(buttons_lock); 176 | 177 | return 0; 178 | } 179 | 180 | void button_destroy(const uint8_t gpio_num) { 181 | if (!buttons_lock) { 182 | buttons_init(); 183 | } 184 | 185 | xSemaphoreTake(buttons_lock, portMAX_DELAY); 186 | 187 | if (!buttons) { 188 | xSemaphoreGive(buttons_lock); 189 | return; 190 | } 191 | 192 | button_t *button = NULL; 193 | if (buttons->gpio_num == gpio_num) { 194 | button = buttons; 195 | buttons = buttons->next; 196 | } else { 197 | button_t *b = buttons; 198 | while (b->next) { 199 | if (b->next->gpio_num == gpio_num) { 200 | button = b->next; 201 | b->next = b->next->next; 202 | break; 203 | } 204 | } 205 | } 206 | 207 | if (button) { 208 | toggle_delete(button->gpio_num); 209 | button_free(button); 210 | } 211 | 212 | xSemaphoreGive(buttons_lock); 213 | } 214 | -------------------------------------------------------------------------------- /button.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | typedef enum { 4 | button_active_low = 0, 5 | button_active_high = 1, 6 | } button_active_level_t; 7 | 8 | typedef struct { 9 | button_active_level_t active_level; 10 | 11 | // times in milliseconds 12 | uint16_t long_press_time; 13 | uint16_t repeat_press_timeout; 14 | uint16_t max_repeat_presses; 15 | } button_config_t; 16 | 17 | typedef enum { 18 | button_event_single_press, 19 | button_event_double_press, 20 | button_event_tripple_press, 21 | button_event_long_press, 22 | } button_event_t; 23 | 24 | typedef void (*button_callback_fn)(button_event_t event, void* context); 25 | 26 | #define BUTTON_CONFIG(level, ...) \ 27 | (button_config_t) { \ 28 | .active_level = level, \ 29 | .repeat_press_timeout = 300, \ 30 | .max_repeat_presses = 1, \ 31 | __VA_ARGS__ \ 32 | } 33 | 34 | int button_create(uint8_t gpio_num, 35 | button_config_t config, 36 | button_callback_fn callback, 37 | void* context); 38 | 39 | void button_destroy(uint8_t gpio_num); 40 | -------------------------------------------------------------------------------- /component.mk: -------------------------------------------------------------------------------- 1 | ifdef component_compile_rules 2 | 3 | INC_DIRS += $(button_ROOT) 4 | 5 | button_INC_DIR = $(button_ROOT) 6 | button_SRC_DIR = $(button_ROOT) 7 | 8 | $(eval $(call component_compile_rules,button)) 9 | else 10 | COMPONENT_ADD_INCLUDEDIRS = . 11 | endif 12 | -------------------------------------------------------------------------------- /example/Makefile: -------------------------------------------------------------------------------- 1 | PROGRAM = button_example 2 | 3 | BUTTON1_GPIO ?= 4 4 | BUTTON2_GPIO ?= 5 5 | 6 | EXTRA_CFLAGS += -DBUTTON1_GPIO=$(BUTTON1_GPIO) -DBUTTON2_GPIO=$(BUTTON2_GPIO) 7 | 8 | EXTRA_COMPONENTS = $(abspath ..) 9 | 10 | include $(SDK_PATH)/common.mk 11 | 12 | monitor: 13 | $(FILTEROUTPUT) --port $(ESPPORT) --baud 115200 --elf $(PROGRAM_OUT) 14 | -------------------------------------------------------------------------------- /example/button_example.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #ifndef BUTTON1_GPIO 10 | #error BUTTON1_GPIO just be defined 11 | #endif 12 | #ifndef BUTTON2_GPIO 13 | #error BUTTON2_GPIO just be defined 14 | #endif 15 | 16 | void idle_task(void* arg) { 17 | while (true) { 18 | vTaskDelay(1000 / portTICK_PERIOD_MS); 19 | } 20 | 21 | vTaskDelete(NULL); 22 | } 23 | 24 | 25 | void button_callback(button_event_t event, void* context) { 26 | int button_idx = *((uint8_t*) context); 27 | 28 | switch (event) { 29 | case button_event_single_press: 30 | printf("button %d single press\n", button_idx); 31 | break; 32 | case button_event_double_press: 33 | printf("button %d double press\n", button_idx); 34 | break; 35 | case button_event_tripple_press: 36 | printf("button %d tripple press\n", button_idx); 37 | break; 38 | case button_event_long_press: 39 | printf("button %d long press\n", button_idx); 40 | break; 41 | default: 42 | printf("unexpected button %d event: %d\n", button_idx, event); 43 | } 44 | } 45 | 46 | 47 | uint8_t button_idx1 = 1; 48 | uint8_t button_idx2 = 2; 49 | 50 | 51 | void user_init(void) { 52 | uart_set_baud(0, 115200); 53 | 54 | printf("Button example\n"); 55 | 56 | button_config_t button_config = BUTTON_CONFIG( 57 | button_active_low, 58 | .long_press_time = 1000, 59 | .max_repeat_presses = 3, 60 | ); 61 | 62 | int r; 63 | r = button_create(BUTTON1_GPIO, button_config, button_callback, &button_idx1); 64 | if (r) { 65 | printf("Failed to initialize button %d (code %d)\n", button_idx1, r); 66 | } 67 | 68 | r = button_create(BUTTON2_GPIO, button_config, button_callback, &button_idx2); 69 | if (r) { 70 | printf("Failed to initialize button %d (code %d)\n", button_idx1, r); 71 | } 72 | 73 | xTaskCreate(idle_task, "Idle task", 256, NULL, tskIDLE_PRIORITY, NULL); 74 | } 75 | -------------------------------------------------------------------------------- /port.c: -------------------------------------------------------------------------------- 1 | #include "port.h" 2 | 3 | #ifdef ESP_IDF 4 | 5 | // ESP-IDF part 6 | #include 7 | 8 | void my_gpio_enable(uint8_t gpio) { 9 | gpio_set_direction(gpio, GPIO_MODE_INPUT); 10 | } 11 | 12 | void my_gpio_pullup(uint8_t gpio) { 13 | gpio_set_pull_mode(gpio, GPIO_PULLUP_ONLY); 14 | } 15 | 16 | void my_gpio_pulldown(uint8_t gpio) { 17 | gpio_set_pull_mode(gpio, GPIO_PULLDOWN_ONLY); 18 | } 19 | 20 | uint8_t my_gpio_read(uint8_t gpio) { 21 | return gpio_get_level(gpio); 22 | } 23 | 24 | #else 25 | 26 | // ESP-OPEN-RTOS part 27 | #include 28 | 29 | void my_gpio_enable(uint8_t gpio) { 30 | gpio_enable(gpio, GPIO_INPUT); 31 | } 32 | 33 | void my_gpio_pullup(uint8_t gpio) { 34 | gpio_set_pullup(gpio, true, true); 35 | } 36 | 37 | void my_gpio_pulldown(uint8_t gpio) { 38 | gpio_set_pullup(gpio, false, false); 39 | } 40 | 41 | uint8_t my_gpio_read(uint8_t gpio) { 42 | return gpio_read(gpio); 43 | } 44 | 45 | 46 | #endif 47 | -------------------------------------------------------------------------------- /port.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #ifdef ESP_IDF 6 | #include 7 | #include 8 | #include 9 | #else 10 | #include 11 | #include 12 | #include 13 | #endif 14 | 15 | void my_gpio_enable(uint8_t gpio); 16 | void my_gpio_pullup(uint8_t gpio); 17 | void my_gpio_pulldown(uint8_t gpio); 18 | uint8_t my_gpio_read(uint8_t gpio); 19 | -------------------------------------------------------------------------------- /resources/active-high-wiring.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maximkulkin/esp-button/b1839a79bb5864951ef8beb0d8f95c78dcb63ba6/resources/active-high-wiring.png -------------------------------------------------------------------------------- /resources/active-low-wiring.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maximkulkin/esp-button/b1839a79bb5864951ef8beb0d8f95c78dcb63ba6/resources/active-low-wiring.png -------------------------------------------------------------------------------- /toggle.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | #include "toggle.h" 7 | #include "port.h" 8 | 9 | 10 | #define MAX_TOGGLE_VALUE 4 11 | #define MIN(a, b) (((b) < (a)) ? (b) : (a)) 12 | #define MAX(a, b) (((a) < (b)) ? (b) : (a)) 13 | 14 | 15 | typedef struct _toggle { 16 | uint8_t gpio_num; 17 | toggle_callback_fn callback; 18 | void* context; 19 | 20 | // Implementation inspired by 21 | // https://github.com/RavenSystem/esp-homekit-devices/blob/master/libs/adv_button/adv_button.c 22 | // and 23 | // https://github.com/pcsaito/esp-homekit-demo/blob/LPFToggle/examples/sonoff_basic_toggle/toggle.c 24 | int8_t value; 25 | bool last_high; 26 | 27 | struct _toggle *next; 28 | } toggle_t; 29 | 30 | 31 | static SemaphoreHandle_t toggles_lock = NULL; 32 | static toggle_t *toggles = NULL; 33 | static TimerHandle_t toggle_timer = NULL; 34 | static bool toggles_initialized = false; 35 | 36 | 37 | static toggle_t *toggle_find_by_gpio(const uint8_t gpio_num) { 38 | toggle_t *toggle = toggles; 39 | while (toggle && toggle->gpio_num != gpio_num) 40 | toggle = toggle->next; 41 | 42 | return toggle; 43 | } 44 | 45 | 46 | static void toggle_timer_callback(TimerHandle_t timer) { 47 | if (xSemaphoreTake(toggles_lock, 0) != pdTRUE) 48 | return; 49 | 50 | toggle_t *toggle = toggles; 51 | 52 | while (toggle) { 53 | if (my_gpio_read(toggle->gpio_num) == 1) { 54 | toggle->value = MIN(toggle->value + 1, MAX_TOGGLE_VALUE); 55 | if (toggle->value == MAX_TOGGLE_VALUE && !toggle->last_high) { 56 | toggle->last_high = true; 57 | toggle->callback(true, toggle->context); 58 | } 59 | } else { 60 | toggle->value = MAX(toggle->value - 1, 0); 61 | if (toggle->value == 0 && toggle->last_high) { 62 | toggle->last_high = false; 63 | toggle->callback(false, toggle->context); 64 | } 65 | } 66 | 67 | toggle = toggle->next; 68 | } 69 | 70 | xSemaphoreGive(toggles_lock); 71 | } 72 | 73 | 74 | static int toggles_init() { 75 | if (!toggles_initialized) { 76 | toggles_lock = xSemaphoreCreateBinary(); 77 | xSemaphoreGive(toggles_lock); 78 | 79 | toggle_timer = xTimerCreate( 80 | "Toggle timer", pdMS_TO_TICKS(10), pdTRUE, NULL, toggle_timer_callback 81 | ); 82 | 83 | toggles_initialized = true; 84 | } 85 | 86 | return 0; 87 | } 88 | 89 | 90 | int toggle_create(const uint8_t gpio_num, toggle_callback_fn callback, void* context) { 91 | if (!toggles_initialized) 92 | toggles_init(); 93 | 94 | toggle_t *toggle = toggle_find_by_gpio(gpio_num); 95 | if (toggle) 96 | return -1; 97 | 98 | toggle = malloc(sizeof(toggle_t)); 99 | memset(toggle, 0, sizeof(*toggle)); 100 | toggle->gpio_num = gpio_num; 101 | toggle->callback = callback; 102 | toggle->context = context; 103 | toggle->last_high = my_gpio_read(toggle->gpio_num) == 1; 104 | 105 | my_gpio_enable(toggle->gpio_num); 106 | 107 | xSemaphoreTake(toggles_lock, portMAX_DELAY); 108 | 109 | toggle->next = toggles; 110 | toggles = toggle; 111 | 112 | xSemaphoreGive(toggles_lock); 113 | 114 | if (!xTimerIsTimerActive(toggle_timer)) { 115 | xTimerStart(toggle_timer, 1); 116 | } 117 | 118 | return 0; 119 | } 120 | 121 | 122 | void toggle_delete(const uint8_t gpio_num) { 123 | if (!toggles_initialized) 124 | toggles_init(); 125 | 126 | xSemaphoreTake(toggles_lock, portMAX_DELAY); 127 | 128 | if (!toggles) { 129 | xSemaphoreGive(toggles_lock); 130 | return; 131 | } 132 | 133 | toggle_t *toggle = NULL; 134 | if (toggles->gpio_num == gpio_num) { 135 | toggle = toggles; 136 | toggles = toggles->next; 137 | } else { 138 | toggle_t *b = toggles; 139 | while (b->next) { 140 | if (b->next->gpio_num == gpio_num) { 141 | toggle = b->next; 142 | b->next = b->next->next; 143 | break; 144 | } 145 | } 146 | } 147 | 148 | if (!toggles) { 149 | xTimerStop(toggle_timer, 1); 150 | } 151 | 152 | xSemaphoreGive(toggles_lock); 153 | 154 | if (!toggle) 155 | return; 156 | 157 | free(toggle); 158 | } 159 | -------------------------------------------------------------------------------- /toggle.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | typedef void (*toggle_callback_fn)(bool high, void* context); 7 | 8 | int toggle_create(uint8_t gpio_num, toggle_callback_fn callback, void* context); 9 | void toggle_delete(uint8_t gpio_num); 10 | --------------------------------------------------------------------------------