#include "badge/gpio.h" #include "badge/config.h" #include "badge/log.h" #include #define BUTTON_PIN 0 #if UVOK_EPAP_BOARD == BOARD_ESP32_CROWPANEL // Elecrow #define EXIT_KEY 1 #define HOME_KEY 2 #define OK_KEY 5 #endif #define GPIO_STACK 2048 #define GPIO_QUEUE_LEN 32 typedef struct { uint32_t timestamp; uint32_t pin : 8; uint32_t state : 1; } pin_notification_t; _Static_assert(sizeof(uint32_t) >= sizeof(unsigned long), "return type of millis"); //! Keep track of inputs via interrupt typedef struct { // uint32_t last_change; uint32_t last_level; uint32_t pressed; uint32_t released; // ... already forgot why I wanted this uint32_t last_handled; } InputInfo_t; //! Keep track of inputs via interrupt static volatile InputInfo_t inputs[NUM_DIGITAL_PINS]; //! FreeRTOS handles, storages, etc. static struct { StaticTask_t task; StackType_t task_stack[GPIO_STACK / sizeof(StackType_t)]; StaticQueue_t queue; uint8_t queue_storage[GPIO_QUEUE_LEN * sizeof(pin_notification_t)]; QueueHandle_t queue_handle; } gpio_task_stuff; static ARDUINO_ISR_ATTR void gpio_pin_irq(void *); static void gpio_loop(void *); //! Create task, initialize pins... void de::uvok::badge::gpio_init(void) { xTaskCreateStatic(gpio_loop, "gpio", GPIO_STACK, NULL, configMAX_PRIORITIES - 1, gpio_task_stuff.task_stack, &gpio_task_stuff.task); gpio_task_stuff.queue_handle = xQueueCreateStatic(4, sizeof(pin_notification_t), &(gpio_task_stuff.queue_storage[0]), &gpio_task_stuff.queue); #if UVOK_EPAP_BOARD == BOARD_ESP32_CROWPANEL uint8_t inPins[] = {EXIT_KEY, HOME_KEY, NEXT_KEY, OK_KEY, PRV_KEY}; for (uint8_t p : inPins) { pinMode(p, GPIO_MODE_INPUT); // simply use pin number as arg attachInterruptArg(digitalPinToInterrupt(p), gpio_pin_irq, (void *)(ptrdiff_t)p, CHANGE); inputs[p].last_level = digitalRead(p); } #endif } static volatile uint8_t event; // "Poll task", for button 0. long de::uvok::badge::gpio_poll(void) { static unsigned long pressedTime = 0; static unsigned long releasedTime = 0; int x = 0; static int lastState = HIGH; int buttonState = digitalRead(BUTTON_PIN); long pressDuration = 0; if (lastState == HIGH && buttonState == LOW) { Serial.println("``\\__"); pressedTime = millis(); lastState = LOW; } else if (lastState == LOW && buttonState == HIGH) { lastState = HIGH; Serial.println("__/``"); releasedTime = millis(); pressDuration = releasedTime - pressedTime; }; if (event) { uint8_t r = event; event = 0; return r; } return pressDuration; } //! GPIO task loop, read / handle queue. static void gpio_loop(void *ctx) { Serial.println("Starting GPIO loop"); while (1) { pin_notification_t received; if (xQueueReceive(gpio_task_stuff.queue_handle, &received, portMAX_DELAY) == pdTRUE) { const bool pressed = !received.state; const uint8_t pin = received.pin; LOG_F("(%u) Pin %u was %s\n", received.timestamp, pin, pressed ? "pressed" : "released"); LOG_F(" pressed: %u, released: %u\n", inputs[pin].pressed, inputs[pin].released); if ( // ignore weird stuff inputs[pin].pressed == 0 // Ignore overflows - badge won't be powered always anyway || inputs[pin].pressed >= inputs[pin].released // Ignore "bouncing" or very short presses. || inputs[pin].released - inputs[pin].pressed < 50) continue; LOG_F(" Handling\n"); event = pin; } } } //! GPIO interrupt handler static ARDUINO_ISR_ATTR IRAM_ATTR void gpio_pin_irq(void *arg) { /* ... Ugh. Pins 4, 5, 6, the selector thingy, is very dirty, or the IRQ is too slow. Even with that code, I sometimes get multiples pressed events directly after each other, or multiple released events. Need to use some filtering, maybe missing some dirty events as a result. */ const uint8_t pin_no = (uint8_t)(ptrdiff_t)arg; const int pinLevel = digitalRead(pin_no); const uint32_t now = millis(); // Only remember pressed events, don't post. if (!pinLevel) { inputs[pin_no].pressed = now; return; } // Only post released events inputs[pin_no].released = now; // do any filtering in the main task loop const pin_notification_t send = {.timestamp = now, .pin = pin_no, .state = (pinLevel == HIGH)}; BaseType_t wakeup = false; xQueueSendFromISR(gpio_task_stuff.queue_handle, &send, &wakeup); if (wakeup) { portYIELD_FROM_ISR(); } }