diff --git a/CHANGELOG.md b/CHANGELOG.md index d3d2f0999..77ce0e37d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added `esp:get_default_mac/0` for retrieving the default MAC address on ESP32. - Added support for `pico` and `poci` as an alternative to `mosi` and `miso` for SPI - ESP32: Added support to SPI peripherals other than hspi and vspi +- Added `gpio:set_int/4`, with the 4th parameter being the pid() or registered name of the process to receive interrupt messages ### Changed diff --git a/libs/eavmlib/src/gpio.erl b/libs/eavmlib/src/gpio.erl index d2fd246ac..6ae3e6367 100644 --- a/libs/eavmlib/src/gpio.erl +++ b/libs/eavmlib/src/gpio.erl @@ -36,7 +36,7 @@ read/2, set_direction/3, set_level/3, - set_int/3, + set_int/3, set_int/4, remove_int/2, stop/0, close/1 @@ -251,6 +251,31 @@ set_level(GPIO, Pin, Level) -> set_int(GPIO, Pin, Trigger) -> port:call(GPIO, {set_int, Pin, Trigger}). +%%----------------------------------------------------------------------------- +%% @param GPIO pid that was returned from `gpio:start/0' +%% @param Pin number of the pin to set the interrupt on +%% @param Trigger is the state that will trigger an interrupt +%% @param Pid is the process that will receive the interrupt message +%% @returns ok | error | {error, Reason} +%% @doc Set a GPIO interrupt +%% +%% Available triggers are `none' (which is the same as disabling an +%% interrupt), `rising', `falling', `both' (rising or falling), `low', and +%% `high'. When the interrupt is triggered it will send a tuple: +%% `{gpio_interrupt, Pin}' +%% to the process that set the interrupt. Pin will be the number +%% of the pin that triggered the interrupt. +%% +%% The STM32 port only supports `rising', `falling', or `both'. +%% +%% The rp2040 (Pico) port does not support gpio interrupts at this time. +%% @end +%%----------------------------------------------------------------------------- +-spec set_int(GPIO :: gpio(), Pin :: pin(), Trigger :: trigger(), Pid :: pid()) -> + ok | {error, Reason :: atom()} | error. +set_int(GPIO, Pin, Trigger, Pid) -> + port:call(GPIO, {set_int, Pin, Trigger, Pid}). + %%----------------------------------------------------------------------------- %% @param GPIO pid that was returned from `gpio:start/0' %% @param Pin number of the pin to remove the interrupt diff --git a/src/platforms/esp32/components/avm_builtins/gpio_driver.c b/src/platforms/esp32/components/avm_builtins/gpio_driver.c index 4391b8e8a..7c9b9a63a 100644 --- a/src/platforms/esp32/components/avm_builtins/gpio_driver.c +++ b/src/platforms/esp32/components/avm_builtins/gpio_driver.c @@ -32,7 +32,6 @@ #include "atom.h" #include "bif.h" #include "context.h" -#include "scheduler.h" #include "debug.h" #include "defaultatoms.h" #include "globalcontext.h" @@ -41,6 +40,7 @@ #include "module.h" #include "nifs.h" #include "platform_defaultatoms.h" +#include "scheduler.h" #include "term.h" #include "utils.h" @@ -133,6 +133,8 @@ struct GPIOData struct ListHead gpio_listeners; }; +/* TODO: Change error returns to {error, Reason} (See: https://github.com/atomvm/AtomVM/issues/894) */ + static inline term gpio_set_pin_mode(Context *ctx, term gpio_num_term, term mode_term) { int gpio_num = term_to_int(gpio_num_term); @@ -343,16 +345,38 @@ static bool gpiodriver_is_gpio_attached(struct GPIOData *gpio_data, int gpio_num static term gpiodriver_set_int(Context *ctx, int32_t target_pid, term cmd) { GlobalContext *glb = ctx->global; + int32_t target_local_pid; struct GPIOData *gpio_data = ctx->platform_data; int32_t gpio_num = term_to_int32(term_get_tuple_element(cmd, 1)); term trigger = term_get_tuple_element(cmd, 2); + if (term_get_tuple_arity(cmd) == 4) { + term pid = term_get_tuple_element(cmd, 3); + if (UNLIKELY(!term_is_pid(pid) && !term_is_atom(pid))) { + ESP_LOGE(TAG, "Invalid listener parameter, must be a pid() or registered process!"); + return ERROR_ATOM; + } + if (term_is_pid(pid)) { + target_local_pid = term_to_local_process_id(pid); + } else { + int pid_atom_index = term_to_atom_index(pid); + int32_t registered_process = (int32_t) globalcontext_get_registered_process(ctx->global, pid_atom_index); + if (UNLIKELY(registered_process == 0)) { + ESP_LOGE(TAG, "Invalid listener parameter, atom() is not a registered process name!"); + return ERROR_ATOM; + } + target_local_pid = registered_process; + } + } else { + target_local_pid = target_pid; + } if (gpiodriver_is_gpio_attached(gpio_data, gpio_num)) { return ERROR_ATOM; } + /* TODO: GPIO specific atoms should be removed from platform_defaultatoms and constructed within this driver */ gpio_int_type_t interrupt_type; switch (trigger) { case NONE_ATOM: @@ -397,7 +421,7 @@ static term gpiodriver_set_int(Context *ctx, int32_t target_pid, term cmd) } list_append(&gpio_data->gpio_listeners, &data->gpio_listener_list_head); data->gpio = gpio_num; - data->target_local_pid = target_pid; + data->target_local_pid = target_local_pid; sys_register_listener(glb, &data->listener); data->listener.sender = data; data->listener.handler = gpio_interrupt_callback; @@ -523,6 +547,8 @@ REGISTER_PORT_DRIVER(gpio, gpio_driver_init, NULL, gpio_driver_create_port) #ifdef CONFIG_AVM_ENABLE_GPIO_NIFS +/* TODO: in the case of {error, Return} we should RAISE_ERROR(Reason) */ + static term nif_gpio_set_pin_mode(Context *ctx, int argc, term argv[]) { return gpio_set_pin_mode(ctx, argv[0], argv[1]); diff --git a/src/platforms/stm32/src/lib/gpio_driver.c b/src/platforms/stm32/src/lib/gpio_driver.c index 908ed829f..077026811 100644 --- a/src/platforms/stm32/src/lib/gpio_driver.c +++ b/src/platforms/stm32/src/lib/gpio_driver.c @@ -71,6 +71,7 @@ static NativeHandlerResult consume_gpio_mailbox(Context *ctx); static const char *const gpio_atom = ATOM_STR("\x4", "gpio"); static const char *const gpio_interrupt_atom = ATOM_STR("\xE", "gpio_interrupt"); static const char *const invalid_trigger_atom = ATOM_STR("\xF", "invalid_trigger"); +static const char *const invalid_listener_atom = ATOM_STR("\x10", "invalid_listener"); #define INVALID_EXTI_TRIGGER 0xEE @@ -748,6 +749,8 @@ static bool gpiodriver_is_gpio_attached(struct GPIOData *gpio_data, term gpio_ba static term gpiodriver_set_int(Context *ctx, int32_t target_pid, term cmd) { + int32_t target_local_pid; + term gpio_tuple = term_get_tuple_element(cmd, 1); if (UNLIKELY(!term_is_tuple(gpio_tuple))) { AVM_LOGE(TAG, "Invalid GPIO Pin tuple, expect {Bank, Pin}."); @@ -789,6 +792,27 @@ static term gpiodriver_set_int(Context *ctx, int32_t target_pid, term cmd) return create_pair(ctx, ERROR_ATOM, globalcontext_make_atom(ctx->global, invalid_trigger_atom)); } + if (term_get_tuple_arity(cmd) == 4) { + term pid = term_get_tuple_element(cmd, 3); + if (UNLIKELY(!term_is_pid(pid) && !term_is_atom(pid))) { + AVM_LOGE(TAG, "Invalid listener parameter, must be a pid() or registered process!"); + return create_pair(ctx, ERROR_ATOM, invalid_listener_atom); + } + if (term_is_pid(pid)) { + target_local_pid = term_to_local_process_id(pid); + } else { + int pid_atom_index = term_to_atom_index(pid); + int32_t registered_process = (int32_t) globalcontext_get_registered_process(ctx->global, pid_atom_index); + if (UNLIKELY(registered_process == 0)) { + AVM_LOGE(TAG, "Invalid listener parameter, atom() is not a registered process name!"); + return create_pair(ctx, ERROR_ATOM, NOPROC_ATOM); + } + target_local_pid = registered_process; + } + } else { + target_local_pid = target_pid; + } + uint32_t exti = 1U << gpio_pin; if (!list_is_empty(&gpio_data->gpio_listeners)) { @@ -816,7 +840,7 @@ static term gpiodriver_set_int(Context *ctx, int32_t target_pid, term cmd) AVM_ABORT(); } list_append(&gpio_data->gpio_listeners, &data->gpio_listener_list_head); - data->target_local_pid = target_pid; + data->target_local_pid = target_local_pid; data->bank_atom = gpio_bank_atom; data->gpio_pin = gpio_pin; data->exti = exti;