buttons.c 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. /*
  2. * a crude button press/long-press/shift management based on GPIO
  3. *
  4. * (c) Philippe G. 2019, philippe_44@outlook.com
  5. *
  6. * This program is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. *
  19. */
  20. #include <stdio.h>
  21. #include <stdlib.h>
  22. #include <unistd.h>
  23. #include <string.h>
  24. #include "freertos/FreeRTOS.h"
  25. #include "freertos/task.h"
  26. #include "freertos/timers.h"
  27. #include "freertos/queue.h"
  28. #include "esp_system.h"
  29. #include "esp_log.h"
  30. #include "esp_task.h"
  31. #include "driver/gpio.h"
  32. #include "buttons.h"
  33. #include "globdefs.h"
  34. bool gpio36_39_used;
  35. static const char * TAG = "buttons";
  36. static int n_buttons = 0;
  37. #define BUTTON_STACK_SIZE 4096
  38. #define MAX_BUTTONS 16
  39. #define DEBOUNCE 50
  40. static EXT_RAM_ATTR struct button_s {
  41. void *client;
  42. int gpio;
  43. int debounce;
  44. button_handler handler;
  45. struct button_s *self, *shifter;
  46. int shifter_gpio; // this one is just for post-creation
  47. int long_press;
  48. bool long_timer, shifted, shifting;
  49. int type, level;
  50. TimerHandle_t timer;
  51. } buttons[MAX_BUTTONS];
  52. static xQueueHandle button_evt_queue = NULL;
  53. static void buttons_task(void* arg);
  54. /****************************************************************************************
  55. * GPIO low-level handler
  56. */
  57. static void IRAM_ATTR gpio_isr_handler(void* arg)
  58. {
  59. struct button_s *button = (struct button_s*) arg;
  60. BaseType_t woken = pdFALSE;
  61. if (xTimerGetPeriod(button->timer) > button->debounce / portTICK_RATE_MS) xTimerChangePeriodFromISR(button->timer, button->debounce / portTICK_RATE_MS, &woken); // does that restart the timer?
  62. else xTimerResetFromISR(button->timer, &woken);
  63. // ESP_EARLY_LOGI(TAG, "INT gpio %u level %u", button->gpio, button->level);
  64. }
  65. /****************************************************************************************
  66. * Buttons debounce/longpress timer
  67. */
  68. static void buttons_timer( TimerHandle_t xTimer ) {
  69. struct button_s *button = (struct button_s*) pvTimerGetTimerID (xTimer);
  70. button->level = gpio_get_level(button->gpio);
  71. if (button->shifter && button->shifter->type == button->shifter->level) button->shifter->shifting = true;
  72. if (button->long_press && !button->long_timer && button->level == button->type) {
  73. // detect a long press, so hold event generation
  74. ESP_LOGD(TAG, "setting long timer gpio:%u level:%u", button->gpio, button->level);
  75. xTimerChangePeriod(xTimer, button->long_press / portTICK_RATE_MS, 0);
  76. button->long_timer = true;
  77. } else {
  78. // send a button pressed/released event (content is copied in queue)
  79. ESP_LOGD(TAG, "sending event for gpio:%u level:%u", button->gpio, button->level);
  80. // queue will have a copy of button's context
  81. xQueueSend(button_evt_queue, button, 0);
  82. button->long_timer = false;
  83. }
  84. }
  85. /****************************************************************************************
  86. * Tasks that calls the appropriate functions when buttons are pressed
  87. */
  88. static void buttons_task(void* arg) {
  89. ESP_LOGI(TAG, "starting button tasks");
  90. while (1) {
  91. struct button_s button;
  92. button_event_e event;
  93. button_press_e press;
  94. if (!xQueueReceive(button_evt_queue, &button, portMAX_DELAY)) continue;
  95. event = (button.level == button.type) ? BUTTON_PRESSED : BUTTON_RELEASED;
  96. ESP_LOGD(TAG, "received event:%u from gpio:%u level:%u (timer %u shifting %u)", event, button.gpio, button.level, button.long_timer, button.shifting);
  97. // find if shifting is activated
  98. if (button.shifter && button.shifter->type == button.shifter->level) press = BUTTON_SHIFTED;
  99. else press = BUTTON_NORMAL;
  100. /*
  101. long_timer will be set either because we truly have a long press
  102. or we have a release before the long press timer elapsed, so two
  103. events shall be sent
  104. */
  105. if (button.long_timer) {
  106. if (event == BUTTON_RELEASED) {
  107. // early release of a long-press button, send press/release
  108. if (!button.shifting) {
  109. (*button.handler)(button.client, BUTTON_PRESSED, press, false);
  110. (*button.handler)(button.client, BUTTON_RELEASED, press, false);
  111. }
  112. // button is a copy, so need to go to real context
  113. button.self->shifting = false;
  114. } else if (!button.shifting) {
  115. // normal long press and not shifting so don't discard
  116. (*button.handler)(button.client, BUTTON_PRESSED, press, true);
  117. }
  118. } else {
  119. // normal press/release of a button or release of a long-press button
  120. if (!button.shifting) (*button.handler)(button.client, event, press, button.long_press);
  121. // button is a copy, so need to go to real context
  122. button.self->shifting = false;
  123. }
  124. }
  125. }
  126. /****************************************************************************************
  127. * dummy button handler
  128. */
  129. void dummy_handler(void *id, button_event_e event, button_press_e press) {
  130. ESP_LOGW(TAG, "should not be here");
  131. }
  132. /****************************************************************************************
  133. * Create buttons
  134. */
  135. void button_create(void *client, int gpio, int type, bool pull, int debounce, button_handler handler, int long_press, int shifter_gpio) {
  136. static DRAM_ATTR StaticTask_t xTaskBuffer __attribute__ ((aligned (4)));
  137. static EXT_RAM_ATTR StackType_t xStack[BUTTON_STACK_SIZE] __attribute__ ((aligned (4)));
  138. if (n_buttons >= MAX_BUTTONS) return;
  139. ESP_LOGI(TAG, "Creating button using GPIO %u, type %u, pull-up/down %u, long press %u shifter %u", gpio, type, pull, long_press, shifter_gpio);
  140. if (!n_buttons) {
  141. button_evt_queue = xQueueCreate(10, sizeof(struct button_s));
  142. xTaskCreateStatic( (TaskFunction_t) buttons_task, "buttons_thread", BUTTON_STACK_SIZE, NULL, ESP_TASK_PRIO_MIN + 1, xStack, &xTaskBuffer);
  143. }
  144. // just in case this structure is allocated in a future release
  145. memset(buttons + n_buttons, 0, sizeof(struct button_s));
  146. // set mandatory parameters
  147. buttons[n_buttons].client = client;
  148. buttons[n_buttons].gpio = gpio;
  149. buttons[n_buttons].debounce = debounce ? debounce: DEBOUNCE;
  150. buttons[n_buttons].handler = handler;
  151. buttons[n_buttons].long_press = long_press;
  152. buttons[n_buttons].level = -1;
  153. buttons[n_buttons].shifter_gpio = shifter_gpio;
  154. buttons[n_buttons].type = type;
  155. buttons[n_buttons].timer = xTimerCreate("buttonTimer", buttons[n_buttons].debounce / portTICK_RATE_MS, pdFALSE, (void *) &buttons[n_buttons], buttons_timer);
  156. buttons[n_buttons].self = buttons + n_buttons;
  157. for (int i = 0; i < n_buttons; i++) {
  158. // first try to find our shifter
  159. if (buttons[i].gpio == shifter_gpio) {
  160. buttons[n_buttons].shifter = buttons + i;
  161. // a shifter must have a long-press handler
  162. if (!buttons[i].long_press) buttons[i].long_press = -1;
  163. }
  164. // then try to see if we are a non-assigned shifter
  165. if (buttons[i].shifter_gpio == gpio) {
  166. buttons[i].shifter = buttons + n_buttons;
  167. ESP_LOGI(TAG, "post-assigned shifter gpio %u", buttons[i].gpio);
  168. }
  169. }
  170. gpio_pad_select_gpio(gpio);
  171. gpio_set_direction(gpio, GPIO_MODE_INPUT);
  172. // we need any edge detection
  173. gpio_set_intr_type(gpio, GPIO_INTR_ANYEDGE);
  174. // do we need pullup or pulldown
  175. if (pull) {
  176. if (GPIO_IS_VALID_OUTPUT_GPIO(gpio)) {
  177. if (type == BUTTON_LOW) gpio_set_pull_mode(gpio, GPIO_PULLUP_ONLY);
  178. else gpio_set_pull_mode(gpio, GPIO_PULLDOWN_ONLY);
  179. } else {
  180. ESP_LOGW(TAG, "cannot set pull up/down for gpio %u", gpio);
  181. }
  182. }
  183. // nasty ESP32 bug: fire-up constantly INT on GPIO 36/39 if ADC1, AMP, Hall used which WiFi does when PS is activated
  184. if (gpio == 36 || gpio == 39) gpio36_39_used = true;
  185. gpio_isr_handler_add(gpio, gpio_isr_handler, (void*) &buttons[n_buttons]);
  186. gpio_intr_enable(gpio);
  187. n_buttons++;
  188. }
  189. /****************************************************************************************
  190. * Get stored id
  191. */
  192. void *button_get_client(int gpio) {
  193. for (int i = 0; i < n_buttons; i++) {
  194. if (buttons[i].gpio == gpio) return buttons[i].client;
  195. }
  196. return NULL;
  197. }
  198. /****************************************************************************************
  199. * Update buttons
  200. */
  201. void *button_remap(void *client, int gpio, button_handler handler, int long_press, int shifter_gpio) {
  202. int i;
  203. struct button_s *button = NULL;
  204. void *prev_client;
  205. ESP_LOGI(TAG, "remapping GPIO %u, long press %u shifter %u", gpio, long_press, shifter_gpio);
  206. // find button
  207. for (i = 0; i < n_buttons; i++) {
  208. if (buttons[i].gpio == gpio) {
  209. button = buttons + i;
  210. break;
  211. }
  212. }
  213. // huh
  214. if (!button) return NULL;
  215. prev_client = button->client;
  216. button->client = client;
  217. button->handler = handler;
  218. button->long_press = long_press;
  219. button->shifter_gpio = shifter_gpio;
  220. // find our shifter (if any)
  221. for (i = 0; shifter_gpio != -1 && i < n_buttons; i++) {
  222. if (buttons[i].gpio == shifter_gpio) {
  223. button->shifter = buttons + i;
  224. // a shifter must have a long-press handler
  225. if (!buttons[i].long_press) buttons[i].long_press = -1;
  226. break;
  227. }
  228. }
  229. return prev_client;
  230. }