diff --git a/epicardium/epicardium.h b/epicardium/epicardium.h index 2428cb583fe0924fb33a1046bb1693e2349a5ad3..17adf63a0a4b22033cd5ca5e8900a5a784daf0fe 100644 --- a/epicardium/epicardium.h +++ b/epicardium/epicardium.h @@ -196,9 +196,11 @@ API(API_INTERRUPT_DISABLE, int epic_interrupt_disable(api_int_id_t int_id)); #define EPIC_INT_BHI160_GYROSCOPE 6 /** MAX30001 ECG. See :c:func:`epic_isr_max30001_ecg`. */ #define EPIC_INT_MAX30001_ECG 7 +/** Button interrupt. See :c:func:`epic_isr_button`. */ +#define EPIC_INT_BUTTON 8 /* Number of defined interrupts. */ -#define EPIC_INT_NUM 8 +#define EPIC_INT_NUM 9 /* clang-format on */ /* @@ -445,6 +447,12 @@ enum epic_button { */ API(API_BUTTONS_READ, uint8_t epic_buttons_read(uint8_t mask)); +struct epic_isr_button_param { + unsigned int pb; + bool falling; +}; +API_ISR(EPIC_INT_BUTTON, epic_isr_button); + /** * Wristband GPIO * ============== diff --git a/epicardium/main.c b/epicardium/main.c index a0c5fad5758c141428512b86ffc287b229ef113a..dffbb9b46320ba54d8ae4d6f14665bd11541588b 100644 --- a/epicardium/main.c +++ b/epicardium/main.c @@ -150,6 +150,18 @@ int main(void) abort(); } + /* Buttons */ + if (xTaskCreate( + vButtonTask, + (const char *)"Button", + configMINIMAL_STACK_SIZE, + NULL, + tskIDLE_PRIORITY + 1, + NULL) != pdPASS) { + LOG_CRIT("startup", "Failed to create %s task!", "Button"); + abort(); + } + /* * Initialize serial driver data structures. */ diff --git a/epicardium/modules/buttons.c b/epicardium/modules/buttons.c index 14e9e613719e3e5279f811393afdfaf843025430..eeb1eb8ff6d0fa4d7a4a30edcda46d3e6ea32349 100644 --- a/epicardium/modules/buttons.c +++ b/epicardium/modules/buttons.c @@ -1,11 +1,22 @@ #include "epicardium.h" #include "modules/modules.h" #include "modules/log.h" +#include "api/interrupt-sender.h" +#include "api/dispatcher.h" #include "portexpander.h" +#include "pb.h" #include "MAX77650-Arduino-Library.h" #include <stdint.h> +#include <string.h> + +#define LOCK_WAIT pdMS_TO_TICKS(1000) + +static TaskHandle_t button_task_id = NULL; + +enum { BUTTON_NOTIFY_IRQ = 1, +}; static const uint8_t pin_mask[] = { [BUTTON_LEFT_BOTTOM] = 1 << 5, @@ -13,6 +24,13 @@ static const uint8_t pin_mask[] = { [BUTTON_RIGHT_TOP] = 1 << 6, }; +static const uint8_t pb_epic_mapping[] = { + BUTTON_LEFT_BOTTOM, + BUTTON_LEFT_TOP, + BUTTON_RIGHT_BOTTOM, + BUTTON_RIGHT_TOP, +}; + uint8_t epic_buttons_read(uint8_t mask) { uint8_t ret = 0; @@ -43,3 +61,64 @@ uint8_t epic_buttons_read(uint8_t mask) return ret; } + +void portexpander_interrupt_callback(void *_) +{ + portexpander_ack_interrupt(); + + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + if (button_task_id != NULL) { + xTaskNotifyFromISR( + button_task_id, + BUTTON_NOTIFY_IRQ, + eSetBits, + &xHigherPriorityTaskWoken + ); + portYIELD_FROM_ISR(xHigherPriorityTaskWoken); + } +} + +void portexpander_poll_interrupts() +{ + while (hwlock_acquire(HWLOCK_I2C, LOCK_WAIT) < 0) { + LOG_WARN("portexpander", "Failed to acquire I2C. Retrying ..."); + xTaskNotify(button_task_id, BUTTON_NOTIFY_IRQ, eSetBits); + return; + } + + uint8_t pending = 0; + uint8_t levels = 0; + + portexpander_get_pending_interrupts(&pending, &levels); + hwlock_release(HWLOCK_I2C); + + portexpander_handle_pending_interrupts(&pending, &levels); +} + +void button_callback_handler(unsigned int pb, bool falling) +{ + // TODO: Proper parameter passing + struct epic_isr_button_param p = { pb_epic_mapping[pb - 1], falling }; + memcpy(API_CALL_MEM->buffer + 0x20, &p, sizeof(p)); + api_interrupt_trigger(EPIC_INT_BUTTON); +} + +void vButtonTask(void *pvParameters) +{ + button_task_id = xTaskGetCurrentTaskHandle(); + LOG_INFO("buttons", "Creating button task"); + + portexpander_int_clr(0xFF); + + PB_RegisterCallback(1, button_callback_handler); + PB_RegisterCallback(3, button_callback_handler); + PB_RegisterCallback(4, button_callback_handler); + + while (1) { + uint32_t reason = ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + + if (reason & BUTTON_NOTIFY_IRQ) { + portexpander_poll_interrupts(); + } + } +} diff --git a/epicardium/modules/modules.h b/epicardium/modules/modules.h index 330f4f67e9c056df601f12a560029047c1872b59..fa83d5e3ad120253faedd25808ed069f366b44b1 100644 --- a/epicardium/modules/modules.h +++ b/epicardium/modules/modules.h @@ -111,4 +111,7 @@ void max30001_mutex_init(void); #define MAX30001_MUTEX_WAIT_MS 50 extern gpio_cfg_t gpio_configs[]; +/* ---------- BUTTONS ------------------------------------------------------ */ +void vButtonTask(void *pvParameters); + #endif /* MODULES_H */ diff --git a/lib/card10/portexpander.c b/lib/card10/portexpander.c index 52eb88975a660306f540567b55abe56dc53e6151..d9ab4b0d7b430cbad2d673959f20783290e2cd16 100644 --- a/lib/card10/portexpander.c +++ b/lib/card10/portexpander.c @@ -170,9 +170,6 @@ int portexpander_config(const portexpander_cfg_t *cfg) /* ************************************************************************** */ uint8_t portexpander_in_get(uint8_t mask) { - // Reading the input port clears interrupts, so we need to check them here to avoid losing information - portexpander_poll(); - uint8_t buf = 0xFF; if (detected) { @@ -303,35 +300,52 @@ int portexpander_register_callback( /* ************************************************************************** */ __attribute__((weak)) void portexpander_interrupt_callback(void *_) +{ + portexpander_ack_interrupt(); +} + +void portexpander_ack_interrupt() { GPIO_IntDisable(&pe_int_pin); GPIO_IntClr(&pe_int_pin); interrupt_pending = true; } -/* ************************************************************************** */ -void portexpander_poll() +void portexpander_get_pending_interrupts(uint8_t *pending, uint8_t *levels) { - if (detected && interrupt_pending) { + if (detected) { interrupt_pending = false; - - uint8_t caused_by = portexpander_int_status(); + *pending = portexpander_int_status(); // Port read resets interrupts - uint8_t port_levels = portexpander_in_get(0xFF); + *levels = portexpander_in_get(0xFF); GPIO_IntEnable(&pe_int_pin); + } +} - for (uint8_t pin = 0; pin < 8; ++pin) { - if ((caused_by & (1 << pin)) && callbacks[pin]) { - gpio_int_pol_t edge_type = - (port_levels & (1 << pin) ? - GPIO_INT_RISING : - GPIO_INT_FALLING); - if ((int_edge_config[pin] == GPIO_INT_BOTH) || - (edge_type == int_edge_config[pin])) { - callbacks[pin](edge_type, cbparam[pin]); - } +void portexpander_handle_pending_interrupts(uint8_t *pending, uint8_t *levels) +{ + for (uint8_t pin = 0; pin < 8; ++pin) { + if ((*pending & (1 << pin)) && callbacks[pin]) { + gpio_int_pol_t edge_type = + (*levels & (1 << pin) ? GPIO_INT_RISING : + GPIO_INT_FALLING); + if ((int_edge_config[pin] == GPIO_INT_BOTH) || + (edge_type == int_edge_config[pin])) { + callbacks[pin](edge_type, cbparam[pin]); } } } } + +/* ************************************************************************** */ +void portexpander_poll() +{ + if (detected && interrupt_pending) { + uint8_t pending; + uint8_t levels; + + portexpander_get_pending_interrupts(&pending, &levels); + portexpander_handle_pending_interrupts(&pending, &levels); + } +} diff --git a/lib/card10/portexpander.h b/lib/card10/portexpander.h index b260d935b127bb913d26a003867412bb1ac51140..7216a9be0cb7b829c348e88d516d1b187c221d76 100644 --- a/lib/card10/portexpander.h +++ b/lib/card10/portexpander.h @@ -37,8 +37,11 @@ void portexpander_int_disable(uint8_t mask); uint8_t portexpander_int_status(); void portexpander_int_clr(uint8_t mask); int portexpander_register_callback(uint8_t mask, pe_callback callback, void *cbdata); +void portexpander_get_pending_interrupts(uint8_t *pending, uint8_t *levels); +void portexpander_handle_pending_interrupts(uint8_t *pending, uint8_t *levels); void portexpander_poll(); void portexpander_interrupt_callback(void *_); +void portexpander_ack_interrupt(); #endif diff --git a/pycardium/modules/buttons.c b/pycardium/modules/buttons.c index ab8dabd7d28692f7e586dc64d717c4c3cfb6aa31..174579e2e137493db9f74a203f0e35b30185db23 100644 --- a/pycardium/modules/buttons.c +++ b/pycardium/modules/buttons.c @@ -2,8 +2,50 @@ #include "py/objlist.h" #include "py/runtime.h" #include <stdio.h> +#include <string.h> #include "epicardium.h" +#include "interrupt.h" + +#include "api/caller.h" + +// This should really be defined somewhere in a central location to make it reusable (+ use the better linux kernel version) +#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) + +/* clang-format off */ +static const uint8_t button_callback_mapping[] = { + BUTTON_LEFT_BOTTOM, + BUTTON_RIGHT_BOTTOM, + BUTTON_RIGHT_TOP, +}; +/* clang-format on */ + +static mp_obj_t button_callbacks[] = { + [0 ... ARRAY_SIZE(button_callback_mapping) - 1] = mp_const_none +}; + +void epic_isr_button() +{ + // TODO: Proper parameter passing + struct epic_isr_button_param p; + memcpy(&p, API_CALL_MEM->buffer + 0x20, sizeof(p)); + + mp_obj_t callback = mp_const_none; + + for (uint8_t i = 0; i < ARRAY_SIZE(button_callback_mapping); ++i) { + if (p.pb == button_callback_mapping[i]) { + callback = button_callbacks[i]; + break; + } + } + + if (callback != mp_const_none) { + // This may drop some events if the queue is full + mp_sched_schedule( + callback, (p.falling ? mp_const_true : mp_const_false) + ); + } +} static mp_obj_t mp_buttons_read(mp_obj_t mask_in) { @@ -13,9 +55,51 @@ static mp_obj_t mp_buttons_read(mp_obj_t mask_in) } static MP_DEFINE_CONST_FUN_OBJ_1(buttons_read_obj, mp_buttons_read); +static mp_obj_t mp_buttons_set_callback(mp_obj_t mask_in, mp_obj_t callback) +{ + uint8_t mask = mp_obj_get_int(mask_in); + uint8_t param_check_mask = mask; + bool enable_interrupt = false; + + for (uint8_t i = 0; i < ARRAY_SIZE(button_callback_mapping); ++i) { + param_check_mask &= ~button_callback_mapping[i]; + } + + if (param_check_mask) { + mp_raise_ValueError("Callbacks not supported for given button"); + } + + for (uint8_t i = 0; i < ARRAY_SIZE(button_callback_mapping); ++i) { + if (button_callback_mapping[i] & mask) { + button_callbacks[i] = callback; + } + } + + for (uint8_t i = 0; i < ARRAY_SIZE(button_callbacks); ++i) { + if (button_callbacks[i] != mp_const_none) { + enable_interrupt = true; + } + } + + mp_obj_t irq_id = MP_OBJ_NEW_SMALL_INT(EPIC_INT_BUTTON); + + if (enable_interrupt) { + mp_interrupt_enable_callback(irq_id); + } else { + mp_interrupt_disable_callback(irq_id); + } + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_2( + buttons_set_callback_obj, mp_buttons_set_callback +); + static const mp_rom_map_elem_t buttons_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_buttons) }, { MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&buttons_read_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_callback), + MP_ROM_PTR(&buttons_set_callback_obj) }, { MP_ROM_QSTR(MP_QSTR_BOTTOM_LEFT), MP_OBJ_NEW_SMALL_INT(BUTTON_LEFT_BOTTOM) }, { MP_ROM_QSTR(MP_QSTR_BOTTOM_RIGHT), diff --git a/pycardium/modules/interrupt.c b/pycardium/modules/interrupt.c index 838ef2b3a397887221041804a0477a5d1cfdbce5..f35e6e4a3c07d9c3fb05aa247b137d9268419e50 100644 --- a/pycardium/modules/interrupt.c +++ b/pycardium/modules/interrupt.c @@ -7,17 +7,12 @@ #include "py/obj.h" #include "py/runtime.h" -// TODO: these should be intialized as mp_const_none -mp_obj_t callbacks[EPIC_INT_NUM] = { - 0, -}; +mp_obj_t callbacks[EPIC_INT_NUM] = { [0 ... EPIC_INT_NUM - 1] = mp_const_none }; void epic_isr_default_handler(api_int_id_t id) { - // TODO: check if id is out of rante - // TOOD: check against mp_const_none if (id < EPIC_INT_NUM) { - if (callbacks[id]) { + if (callbacks[id] && (callbacks[id] != mp_const_none)) { mp_sched_schedule( callbacks[id], MP_OBJ_NEW_SMALL_INT(id) ); @@ -93,6 +88,7 @@ static const mp_rom_map_elem_t interrupt_module_globals_table[] = { MP_OBJ_NEW_SMALL_INT(EPIC_INT_BHI160_GYROSCOPE) }, { MP_ROM_QSTR(MP_QSTR_MAX30001_ECG), MP_OBJ_NEW_SMALL_INT(EPIC_INT_MAX30001_ECG) }, + { MP_ROM_QSTR(MP_QSTR_BUTTON), MP_OBJ_NEW_SMALL_INT(EPIC_INT_BUTTON) }, }; static MP_DEFINE_CONST_DICT( diff --git a/pycardium/modules/qstrdefs.h b/pycardium/modules/qstrdefs.h index 0ff0a8caac12fff03bcdb18b781a091a5784e98e..0b192d7fe8a60573ac33aa5471d7feb1721781d3 100644 --- a/pycardium/modules/qstrdefs.h +++ b/pycardium/modules/qstrdefs.h @@ -29,6 +29,7 @@ Q(TOP_RIGHT) /* buttons */ Q(buttons) Q(read) +Q(set_callback) Q(BOTTOM_LEFT) Q(TOP_LEFT) Q(BOTTOM_RIGHT) @@ -57,6 +58,7 @@ Q(set_unix_time) Q(vibra) Q(vibrate) +/* interrupt */ Q(set_callback) Q(enable_callback) Q(disable_callback) @@ -64,6 +66,7 @@ Q(BHI160_ACCELEROMETER) Q(BHI160_ORIENTATION) Q(BHI160_GYROSCOPE) Q(RTC_ALARM) +Q(BUTTON) /* bhi160 */ Q(sys_bhi160)