From 5e26434be7123f52bb4e3c1a0a7c2f429ec6f977 Mon Sep 17 00:00:00 2001 From: Winford Date: Fri, 18 Aug 2023 15:23:14 -0700 Subject: [PATCH 1/4] Improved stm32 GPIO driver Complete rewrite of the stm32 gpio driver using updated APIs. Moves all GPIO related atoms from `platform_defaultatoms.{c,h}` into `gpio_driver.c` Improved input validation. Adds read support port driver. Adds nif functions to support read and write. Adds cmake configuration options to disable nifs and/or port driver when building the VM. Closes #571 Addresses #894 on STM32 platform. Signed-off-by: Winford --- CHANGELOG.md | 1 + src/platforms/stm32/CMakeLists.txt | 10 + src/platforms/stm32/src/lib/CMakeLists.txt | 6 +- src/platforms/stm32/src/lib/gpio_driver.c | 626 ++++++++++++++++++ .../src/lib/{gpiodriver.h => gpio_driver.h} | 20 +- src/platforms/stm32/src/lib/gpiodriver.c | 185 ------ .../stm32/src/lib/platform_defaultatoms.c | 58 -- .../stm32/src/lib/platform_defaultatoms.h | 54 -- src/platforms/stm32/src/lib/platform_nifs.c | 1 - src/platforms/stm32/src/lib/stm_sys.h | 2 + src/platforms/stm32/src/lib/sys.c | 14 +- 11 files changed, 668 insertions(+), 309 deletions(-) create mode 100644 src/platforms/stm32/src/lib/gpio_driver.c rename src/platforms/stm32/src/lib/{gpiodriver.h => gpio_driver.h} (57%) delete mode 100644 src/platforms/stm32/src/lib/gpiodriver.c delete mode 100644 src/platforms/stm32/src/lib/platform_defaultatoms.c delete mode 100644 src/platforms/stm32/src/lib/platform_defaultatoms.h diff --git a/CHANGELOG.md b/CHANGELOG.md index d36a96fe9..af07e289a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added support for the OTP `socket` interface. - Enhancd performance of STM32 by enabling flash cache and i-cache with branch prediction. - Added cmake configuration option `AVM_CONFIG_REBOOT_ON_NOT_OK` for STM32 +- New gpio driver for STM32 with nif and port support for read and write functions. ## [0.6.0-alpha.1] - 2023-10-09 diff --git a/src/platforms/stm32/CMakeLists.txt b/src/platforms/stm32/CMakeLists.txt index b7c233629..22674bc06 100644 --- a/src/platforms/stm32/CMakeLists.txt +++ b/src/platforms/stm32/CMakeLists.txt @@ -33,6 +33,8 @@ option(AVM_LOG_DISABLE "Disable log output" OFF) option(AVM_ENABLE_LOG_COLOR "Use color log output" OFF) option(AVM_ENABLE_LOG_LINES "Include source and line info for all enbled levels" OFF) option(AVM_CONFIG_REBOOT_ON_NOT_OK "Reboot when application exits with non 'ok' return" OFF) +option(AVM_DISABLE_GPIO_NIFS "Disable GPIO nifs (input and output)" OFF) +option(AVM_DISABLE_GPIO_PORT_DRIVER "Disable GPIO 'port' driver (input, output, and interrupts)" OFF) set(AVM_DISABLE_SMP ON FORCE) @@ -64,6 +66,14 @@ if (AVM_ENABLE_LOG_LINES) add_compile_definitions(ENABLE_LOG_LINE_INFO) endif() +# Configure Drivers +if (AVM_DISABLE_GPIO_NIFS) + add_compile_definitions(AVM_DISABLE_GPIO_NIFS) +endif() +if (AVM_DISABLE_GPIO_PORT_DRIVER) + add_compile_definitions(AVM_DISABLE_GPIO_PORT_DRIVER) +endif() + # Include an error in case the user forgets to specify ARM as a toolchain if (NOT CMAKE_TOOLCHAIN_FILE) message(FATAL_ERROR "Cross compiling only. Please use -DCMAKE_TOOLCHAIN_FILE=cmake/arm-toolchain.cmake or use\ diff --git a/src/platforms/stm32/src/lib/CMakeLists.txt b/src/platforms/stm32/src/lib/CMakeLists.txt index 976e53ba5..b0155b577 100644 --- a/src/platforms/stm32/src/lib/CMakeLists.txt +++ b/src/platforms/stm32/src/lib/CMakeLists.txt @@ -23,16 +23,14 @@ project (libAtomVMPlatformSTM32) set(HEADER_FILES avm_log.h - gpiodriver.h - platform_defaultatoms.h + gpio_driver.h stm_sys.h ../../../../libAtomVM/platform_nifs.h ../../../../libAtomVM/sys.h ) set(SOURCE_FILES - gpiodriver.c - platform_defaultatoms.c + gpio_driver.c platform_nifs.c sys.c ) diff --git a/src/platforms/stm32/src/lib/gpio_driver.c b/src/platforms/stm32/src/lib/gpio_driver.c new file mode 100644 index 000000000..6b5861ae7 --- /dev/null +++ b/src/platforms/stm32/src/lib/gpio_driver.c @@ -0,0 +1,626 @@ +/* + * This file is part of AtomVM. + * + * Copyright 2023 Winford (Uncle Grumpy) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later + */ + +#if !(defined(AVM_DISABLE_GPIO_PORT_DRIVER) && defined(AVM_DISABLE_GPIO_NIFS)) + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +// #define ENABLE_TRACE +#include + +#include "avm_log.h" +#include "gpio_driver.h" +#include "stm_sys.h" + +#define TAG "gpio_driver" + +#define GPIO_MODE_OUTPUT_OD 0x4 +// Error that cannot be used for these registers +#define GPIOInvalidBank 0x0000U +#define GPIO_INVALID_MODE 0xE +#define INVALID_GPIO_OSPEED 0xE + +#define HIGH_ATOM globalcontext_make_atom(ctx->global, ATOM_STR("\x4", "high")) +#define LOW_ATOM globalcontext_make_atom(ctx->global, ATOM_STR("\x3", "low")) + +#define INVALID_BANK_ATOM globalcontext_make_atom(ctx->global, ATOM_STR("\xC", "invalid_bank")) +#define INVALID_PIN_ATOM globalcontext_make_atom(ctx->global, ATOM_STR("\xB", "invalid_pin")) +#define INVALID_MODE_ATOM globalcontext_make_atom(ctx->global, ATOM_STR("\xC", "invalid_mode")) +#define INVALID_PULL_ATOM globalcontext_make_atom(ctx->global, ATOM_STR("\xC", "invalid_pull")) +#define INVALID_RATE_ATOM globalcontext_make_atom(ctx->global, ATOM_STR("\xC", "invalid_rate")) +#define INVALID_LEVEL_ATOM globalcontext_make_atom(ctx->global, ATOM_STR("\xD", "invalid_level")) + +// Port driver specific data structures and definitions +#ifndef AVM_DISABLE_GPIO_PORT_DRIVER +static NativeHandlerResult consume_gpio_mailbox(Context *ctx); + +static const char *const gpio_atom = ATOM_STR("\x4", "gpio"); +static const char *const gpio_driver_atom = ATOM_STR("\xB", "gpio_driver"); + +static term gpio_driver; + +enum gpio_cmd +{ + GPIOInvalidCmd = 0, + GPIOSetLevelCmd, + GPIOReadCmd, + GPIOSetDirectionCmd, + GPIOSetIntCmd, + GPIORemoveIntCmd, + GPIOCloseCmd +}; + +static const AtomStringIntPair gpio_cmd_table[] = { + { ATOM_STR("\x9", "set_level"), GPIOSetLevelCmd }, + { ATOM_STR("\x4", "read"), GPIOReadCmd }, + { ATOM_STR("\xD", "set_direction"), GPIOSetDirectionCmd }, + { ATOM_STR("\x7", "set_int"), GPIOSetIntCmd }, + { ATOM_STR("\xA", "remove_int"), GPIORemoveIntCmd }, + { ATOM_STR("\x5", "close"), GPIOCloseCmd }, + SELECT_INT_DEFAULT(GPIOInvalidCmd) +}; +#endif /* NOT defined AVM_DISABLE_GPIO_PORT_DRIVER */ + +enum gpio_pin_state +{ + GPIOPinInvalid = -1, + GPIOPinLow = 0, + GPIOPinHigh = 1 +}; + +static const AtomStringIntPair gpio_bank_table[] = { + { ATOM_STR("\x1", "a"), GPIOA }, + { ATOM_STR("\x1", "b"), GPIOB }, + { ATOM_STR("\x1", "c"), GPIOC }, + { ATOM_STR("\x1", "d"), GPIOD }, + { ATOM_STR("\x1", "e"), GPIOE }, + { ATOM_STR("\x1", "f"), GPIOF }, + { ATOM_STR("\x1", "g"), GPIOG }, + { ATOM_STR("\x1", "h"), GPIOH }, +#ifdef LIBOPENCM3_GPIO_COMMON_F24_H + { ATOM_STR("\x1", "i"), GPIOI }, + { ATOM_STR("\x1", "j"), GPIOJ }, + { ATOM_STR("\x1", "k"), GPIOK }, +#endif /* defined LIBOPENCM3_GPIO_COMMON_F24_H */ + SELECT_INT_DEFAULT(GPIOInvalidBank) +}; + +static const AtomStringIntPair output_mhz_table[] = { + { ATOM_STR("\x5", "mhz_2"), GPIO_OSPEED_2MHZ }, + { ATOM_STR("\x6", "mhz_25"), GPIO_OSPEED_25MHZ }, + { ATOM_STR("\x6", "mhz_50"), GPIO_OSPEED_50MHZ }, + { ATOM_STR("\x7", "mhz_100"), GPIO_OSPEED_100MHZ }, + SELECT_INT_DEFAULT(INVALID_GPIO_OSPEED) +}; + +static const AtomStringIntPair pin_level_table[] = { + { ATOM_STR("\x3", "low"), GPIOPinLow }, + { ATOM_STR("\x4", "high"), GPIOPinHigh }, + SELECT_INT_DEFAULT(GPIOPinInvalid) +}; + +static const AtomStringIntPair pin_mode_table[] = { + { ATOM_STR("\x5", "input"), GPIO_MODE_INPUT }, + { ATOM_STR("\x6", "output"), GPIO_MODE_OUTPUT }, + { ATOM_STR("\x9", "output_od"), GPIO_MODE_OUTPUT_OD }, + { ATOM_STR("\x2", "af"), GPIO_MODE_AF }, + { ATOM_STR("\x6", "analog"), GPIO_MODE_ANALOG }, + SELECT_INT_DEFAULT(GPIO_INVALID_MODE) +}; + +static const AtomStringIntPair pull_mode_table[] = { + { ATOM_STR("\x2", "up"), GPIO_PUPD_PULLUP }, + { ATOM_STR("\x4", "down"), GPIO_PUPD_PULLDOWN }, + { ATOM_STR("\x8", "floating"), GPIO_PUPD_NONE }, + SELECT_INT_DEFAULT(GPIO_PUPD_NONE) +}; + +static term create_pair(Context *ctx, term term1, term term2) +{ + term ret = term_alloc_tuple(2, &ctx->heap); + term_put_tuple_element(ret, 0, term1); + term_put_tuple_element(ret, 1, term2); + + return ret; +} + +// Common setup function used by nif and port driver +static term setup_gpio_pin(Context *ctx, term gpio_pin_tuple, term mode_term) +{ + bool setup_output = false; + uint16_t gpio_pin_mask = 0x0000U; + uint8_t gpio_mode; + uint8_t pull_up_down; + uint8_t out_type; + uint8_t output_speed; + term mhz_atom = term_invalid_term(); + term pull_atom = term_invalid_term(); + + if (UNLIKELY(!term_is_tuple(gpio_pin_tuple))) { + AVM_LOGE(TAG, "Invalid GPIO Pin tuple, expect {Bank, Pin}."); + return create_pair(ctx, ERROR_ATOM, BADARG_ATOM); + } + term gpio_bank_atom = term_get_tuple_element(gpio_pin_tuple, 0); + if (UNLIKELY(!term_is_atom(gpio_bank_atom))) { + AVM_LOGE(TAG, "Bank parameter of pin tuple must be an atom! (a...h|k depending on board)"); + return create_pair(ctx, ERROR_ATOM, INVALID_BANK_ATOM); + } + + uint32_t gpio_bank = ((uint32_t) interop_atom_term_select_int(gpio_bank_table, gpio_bank_atom, ctx->global)); + if (UNLIKELY(gpio_bank == GPIOInvalidBank)) { + char *bank_string = interop_atom_to_string(ctx, gpio_bank_atom); + AVM_LOGE(TAG, "Invalid GPIO Bank '%s' in pin tuple", bank_string); + free(bank_string); + return create_pair(ctx, ERROR_ATOM, INVALID_BANK_ATOM); + } + + term pin_term = term_get_tuple_element(gpio_pin_tuple, 1); + if (term_is_list(pin_term)) { + if (UNLIKELY(!term_is_nonempty_list(pin_term))) { + AVM_LOGE(TAG, "Pin list parameter contains no pin numbers!"); + return create_pair(ctx, ERROR_ATOM, INVALID_PIN_ATOM); + } + while (term_is_nonempty_list(pin_term)) { + term gpio_pin_term = term_get_list_head(pin_term); + uint16_t gpio_pin_num = ((uint16_t) term_to_int32(gpio_pin_term)); + gpio_pin_mask = 1U << gpio_pin_num | gpio_pin_mask; + pin_term = term_get_list_tail(pin_term); + } + } else if (term_is_integer(pin_term)) { + uint16_t gpio_pin_num = ((uint16_t) term_to_int32(pin_term)); + if (UNLIKELY(gpio_pin_num > 15)) { + AVM_LOGE(TAG, "Pin number must be between 0 and 15!"); + return create_pair(ctx, ERROR_ATOM, INVALID_PIN_ATOM); + } else { + gpio_pin_mask = 1U << gpio_pin_num | gpio_pin_mask; + } + } else if (term_is_atom(pin_term)) { + if (pin_term == ALL_ATOM) { + gpio_pin_mask = GPIO_ALL; + } + } else { + return create_pair(ctx, ERROR_ATOM, INVALID_PIN_ATOM); + } + + term mode_atom; + if (term_is_tuple(mode_term)) { + mode_atom = term_get_tuple_element(mode_term, 0); + if (UNLIKELY(!term_is_atom(mode_atom))) { + AVM_LOGE(TAG, "GPIO Mode must be an atom ('input', 'output', 'output_od')."); + return create_pair(ctx, ERROR_ATOM, INVALID_MODE_ATOM); + } + gpio_mode = ((uint8_t) interop_atom_term_select_int(pin_mode_table, mode_atom, ctx->global)); + if (UNLIKELY(gpio_mode == GPIO_INVALID_MODE)) { + char *mode_string = interop_atom_to_string(ctx, mode_atom); + AVM_LOGE(TAG, "Invalid gpio mode: %s", mode_string); + free(mode_string); + return create_pair(ctx, ERROR_ATOM, INVALID_MODE_ATOM); + } + if ((gpio_mode == GPIO_MODE_OUTPUT) || (gpio_mode == GPIO_MODE_OUTPUT_OD)) { + if (gpio_mode == GPIO_MODE_OUTPUT_OD) { + gpio_mode = GPIO_MODE_OUTPUT; + out_type = GPIO_OTYPE_OD; + setup_output = true; + } else { + out_type = GPIO_OTYPE_PP; + setup_output = true; + } + } + + pull_atom = term_get_tuple_element(mode_term, 1); + if (UNLIKELY(!term_is_atom(pull_atom))) { + AVM_LOGE(TAG, "GPIO pull direction must be one of the following atoms: up | down | floating"); + return create_pair(ctx, ERROR_ATOM, INVALID_PULL_ATOM); + } + + pull_up_down = ((uint8_t) interop_atom_term_select_int(pull_mode_table, pull_atom, ctx->global)); + if ((setup_output) && (term_get_tuple_arity(mode_term) == 3)) { + mhz_atom = term_get_tuple_element(mode_term, 2); + if (UNLIKELY(!term_is_atom(mhz_atom))) { + AVM_LOGE(TAG, "GPIO output speed must be one of the following atoms: mhz_2 | mhz_25 | mhz_50 | mhz_100"); + return create_pair(ctx, ERROR_ATOM, INVALID_RATE_ATOM); + } + + output_speed = (uint8_t) interop_atom_term_select_int(output_mhz_table, mhz_atom, ctx->global); + if (output_speed == INVALID_GPIO_OSPEED) { + output_speed = GPIO_OSPEED_2MHZ; + char *mhz_string = interop_atom_to_string(ctx, mhz_atom); + AVM_LOGW(TAG, "Invalid output speed '%s' given, falling back to 2 Mhz default.", mhz_string); + free(mhz_string); + } + } else if (setup_output) { + output_speed = GPIO_OSPEED_2MHZ; + AVM_LOGW(TAG, "No output speed given, falling back to 2 Mhz default."); + } + } else { + mode_atom = mode_term; + if (UNLIKELY(!term_is_atom(mode_atom))) { + AVM_LOGE(TAG, "GPIO Mode must be an atom ('input', 'output', 'output_od')."); + return create_pair(ctx, ERROR_ATOM, INVALID_MODE_ATOM); + } + gpio_mode = ((uint8_t) interop_atom_term_select_int(pin_mode_table, mode_atom, ctx->global)); + if (UNLIKELY(gpio_mode == GPIO_INVALID_MODE)) { + char *mode_string = interop_atom_to_string(ctx, mode_atom); + AVM_LOGE(TAG, "Invalid gpio mode: %s", mode_string); + free(mode_string); + return create_pair(ctx, ERROR_ATOM, INVALID_MODE_ATOM); + } + pull_up_down = GPIO_PUPD_NONE; + if (setup_output) { + output_speed = GPIO_OSPEED_2MHZ; + } + } + + gpio_mode_setup(gpio_bank, gpio_mode, pull_up_down, gpio_pin_mask); + if (setup_output) { + gpio_set_output_options(gpio_bank, out_type, output_speed, gpio_pin_mask); + AVM_LOGD(TAG, "Setup: Pin bank 0x%08lX pin bitmask 0x%04X output mode 0x%02X, output speed 0x%04X, pull mode 0x%02X", gpio_bank, gpio_pin_mask, gpio_mode, output_speed, pull_up_down); + } else { + AVM_LOGD(TAG, "Setup: Pin bank 0x%08lX pin bitmask 0x%04X input mode 0x%02X, pull mode 0x%02X", gpio_bank, gpio_pin_mask, gpio_mode, pull_up_down); + } + return OK_ATOM; +} + +// Common write function used by nif and port driver +static term gpio_digital_write(Context *ctx, term gpio_pin_tuple, term level_term) +{ + uint16_t gpio_pin_mask = 0x0000U; + int level; + uint32_t gpio_bank; + + if (UNLIKELY(!term_is_tuple(gpio_pin_tuple))) { + AVM_LOGE(TAG, "Invalid GPIO Pin tuple, expect {Bank, Pin}."); + return create_pair(ctx, ERROR_ATOM, BADARG_ATOM); + } + term gpio_bank_atom = term_get_tuple_element(gpio_pin_tuple, 0); + if (UNLIKELY(!term_is_atom(gpio_bank_atom))) { + AVM_LOGE(TAG, "Bank parameter of pin tuple must be an atom!"); + return create_pair(ctx, ERROR_ATOM, INVALID_BANK_ATOM); + } + gpio_bank = ((uint32_t) interop_atom_term_select_int(gpio_bank_table, gpio_bank_atom, ctx->global)); + + if (UNLIKELY(gpio_bank == GPIOInvalidBank)) { + char *bank_string = interop_atom_to_string(ctx, gpio_bank_atom); + AVM_LOGE(TAG, "Invalid GPIO Bank '%s' in pin tuple", bank_string); + free(bank_string); + return create_pair(ctx, ERROR_ATOM, INVALID_BANK_ATOM); + } + + term pin_term = term_get_tuple_element(gpio_pin_tuple, 1); + if (term_is_list(pin_term)) { + if (UNLIKELY(!term_is_nonempty_list(pin_term))) { + AVM_LOGE(TAG, "Pin list parameter contains no pin numbers!"); + return create_pair(ctx, ERROR_ATOM, INVALID_PIN_ATOM); + } + while (term_is_nonempty_list(pin_term)) { + term gpio_pin_term = term_get_list_head(pin_term); + uint16_t gpio_pin_num = ((uint16_t) term_to_int32(gpio_pin_term)); + gpio_pin_mask = 1U << gpio_pin_num | gpio_pin_mask; + pin_term = term_get_list_tail(pin_term); + } + } else if (term_is_integer(pin_term)) { + uint16_t gpio_pin_num = ((uint16_t) term_to_int32(pin_term)); + if (UNLIKELY(gpio_pin_num > 15)) { + AVM_LOGE(TAG, "Pin number must be between 0 and 15!"); + return create_pair(ctx, ERROR_ATOM, INVALID_PIN_ATOM); + } else { + gpio_pin_mask = 1U << gpio_pin_num | gpio_pin_mask; + } + } else if (term_is_atom(pin_term)) { + if (pin_term == ALL_ATOM) { + gpio_pin_mask = GPIO_ALL; + } + } else { + return create_pair(ctx, ERROR_ATOM, INVALID_PIN_ATOM); + } + + if (term_is_integer(level_term)) { + level = term_to_int(level_term); + if (UNLIKELY((level != 0) && (level != 1))) { + return create_pair(ctx, ERROR_ATOM, INVALID_LEVEL_ATOM); + } + } else { + if (UNLIKELY(!term_is_atom(level_term))) { + AVM_LOGE(TAG, "GPIO level must be 0 or 1, or an atom ('high' or 'low')."); + return create_pair(ctx, ERROR_ATOM, INVALID_LEVEL_ATOM); + } + level = interop_atom_term_select_int(pin_level_table, level_term, ctx->global); + if (UNLIKELY(level < 0)) { + AVM_LOGE(TAG, "GPIO level atom must be 'high' or 'low'."); + return create_pair(ctx, ERROR_ATOM, INVALID_LEVEL_ATOM); + } + } + + if (level != 0) { + gpio_set(gpio_bank, gpio_pin_mask); + } else { + gpio_clear(gpio_bank, gpio_pin_mask); + } + TRACE("Write: bank: 0x%08lX, pin mask: 0x%04X, level: %i\n", gpio_bank, gpio_pin_mask, level); + return OK_ATOM; +} + +// Common read function used by nif and port driver +static term gpio_digital_read(Context *ctx, term gpio_pin_tuple) +{ + if (UNLIKELY(!term_is_tuple(gpio_pin_tuple))) { + AVM_LOGE(TAG, "Invalid GPIO Pin tuple, expect {Bank, Pin}."); + return create_pair(ctx, ERROR_ATOM, BADARG_ATOM); + } + term gpio_bank_atom = term_get_tuple_element(gpio_pin_tuple, 0); + if (UNLIKELY(!term_is_atom(gpio_bank_atom))) { + AVM_LOGE(TAG, "Bank parameter of pin tuple must be an atom!"); + return create_pair(ctx, ERROR_ATOM, INVALID_BANK_ATOM); + } + uint32_t gpio_bank = ((uint32_t) interop_atom_term_select_int(gpio_bank_table, gpio_bank_atom, ctx->global)); + + if (UNLIKELY(gpio_bank == GPIOInvalidBank)) { + char *bank_string = interop_atom_to_string(ctx, gpio_bank_atom); + AVM_LOGE(TAG, "Invalid GPIO Bank '%s' in pin tuple", bank_string); + free(bank_string); + return create_pair(ctx, ERROR_ATOM, INVALID_BANK_ATOM); + } + // TODO: Add support for reading list, or all input pins on port? + uint16_t gpio_pin_num = ((uint16_t) term_to_int32(term_get_tuple_element(gpio_pin_tuple, 1))); + if (UNLIKELY(gpio_pin_num > 15)) { + AVM_LOGE(TAG, "Pin number must be between 0 and 15!"); + return create_pair(ctx, ERROR_ATOM, INVALID_PIN_ATOM); + } + + uint16_t pin_levels = gpio_get(gpio_bank, (1U << gpio_pin_num)); + uint16_t level = (pin_levels >> gpio_pin_num); + TRACE("Read: Bank 0x%08lX Pin %u. RESULT: %u\n", gpio_bank, gpio_pin_num, level); + + return level ? HIGH_ATOM : LOW_ATOM; +} + +#ifndef AVM_DISABLE_GPIO_PORT_DRIVER + +void gpiodriver_init(GlobalContext *glb) +{ + int index = globalcontext_insert_atom(glb, gpio_driver_atom); + gpio_driver = term_from_atom_index(index); +} + +static Context *gpio_driver_create_port(GlobalContext *global, term opts) +{ + Context *ctx = context_new(global); + + ctx->native_handler = consume_gpio_mailbox; + ctx->platform_data = NULL; + + term reg_name_term = globalcontext_make_atom(global, gpio_atom); + int atom_index = term_to_atom_index(reg_name_term); + + if (UNLIKELY(!globalcontext_register_process(ctx->global, atom_index, ctx->process_id))) { + scheduler_terminate(ctx); + AVM_LOGE(TAG, "Only a single GPIO driver can be opened."); + return create_pair(ctx, ERROR_ATOM, USED_ATOM); + } + + return ctx; +} + +static term gpiodriver_close(Context *ctx) +{ + GlobalContext *glb = ctx->global; + term gpio_atom_term = globalcontext_make_atom(glb, gpio_atom); + int gpio_atom_index = term_to_atom_index(gpio_atom_term); + if (UNLIKELY(!globalcontext_get_registered_process(glb, gpio_atom_index))) { + AVM_LOGE(TAG, "No active GPIO driver can be found."); + return create_pair(ctx, ERROR_ATOM, NOPROC_ATOM); + } + + ctx->platform_data = NULL; + + globalcontext_unregister_process(glb, gpio_atom_index); + + return OK_ATOM; +} + +static term gpiodriver_set_level(Context *ctx, term cmd) +{ + term gpio_pin_tuple = term_get_tuple_element(cmd, 1); + term level = term_get_tuple_element(cmd, 2); + + return gpio_digital_write(ctx, gpio_pin_tuple, level); +} + +static term gpiodriver_set_direction(Context *ctx, term cmd) +{ + term gpio_tuple = term_get_tuple_element(cmd, 1); + term direction = term_get_tuple_element(cmd, 2); + + return setup_gpio_pin(ctx, gpio_tuple, direction); +} + +static term gpiodriver_read(Context *ctx, term cmd) +{ + term gpio_pin_tuple = term_get_tuple_element(cmd, 1); + return gpio_digital_read(ctx, gpio_pin_tuple); +} + +static NativeHandlerResult consume_gpio_mailbox(Context *ctx) +{ + Message *message = mailbox_first(&ctx->mailbox); + term msg = message->message; + term pid = term_get_tuple_element(msg, 0); + term req = term_get_tuple_element(msg, 2); + term cmd_term = term_get_tuple_element(req, 0); + + int local_process_id = term_to_local_process_id(pid); + + term ret; + + enum gpio_cmd cmd = interop_atom_term_select_int(gpio_cmd_table, cmd_term, ctx->global); + switch (cmd) { + case GPIOSetLevelCmd: + ret = gpiodriver_set_level(ctx, req); + break; + + case GPIOSetDirectionCmd: + ret = gpiodriver_set_direction(ctx, req); + break; + + case GPIOReadCmd: + ret = gpiodriver_read(ctx, req); + break; + + case GPIOSetIntCmd: + AVM_LOGE(TAG, "set_int not yet supported on stm32"); + ret = create_pair(ctx, ERROR_ATOM, UNSUPPORTED_ATOM); + break; + + case GPIORemoveIntCmd: + AVM_LOGE(TAG, "remove_int not yet supported on stm32"); + ret = create_pair(ctx, ERROR_ATOM, UNSUPPORTED_ATOM); + break; + + case GPIOCloseCmd: + ret = gpiodriver_close(ctx); + break; + + case GPIOInvalidCmd: + char *invalid_name = interop_atom_to_string(ctx, cmd_term); + AVM_LOGE(TAG, "Invalid command: %s", invalid_name); + free(invalid_name); + ret = create_pair(ctx, ERROR_ATOM, UNDEFINED_ATOM); + break; + + default: + char *cmd_name = interop_atom_to_string(ctx, cmd_term); + AVM_LOGE(TAG, "Unhandled error processing command: %s", cmd_name); + free(cmd_name); + ret = create_pair(ctx, ERROR_ATOM, BADMATCH_ATOM); + } + + term ret_msg; + if (UNLIKELY(memory_ensure_free_with_roots(ctx, 3, 1, &ret, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) { + ret_msg = create_pair(ctx, ERROR_ATOM, OUT_OF_MEMORY_ATOM); + } else { + term ref = term_get_tuple_element(msg, 1); + ret_msg = create_pair(ctx, ref, ret); + } + + globalcontext_send_message(ctx->global, local_process_id, ret_msg); + mailbox_remove_message(&ctx->mailbox, &ctx->heap); + + return cmd == GPIOCloseCmd ? NativeTerminate : NativeContinue; +} + +REGISTER_PORT_DRIVER(gpio, gpiodriver_init, NULL, gpio_driver_create_port) + +#endif /* NOT defined AVM_DISABLE_GPIO_PORT_DRIVER */ + +// +// Nif implementation +// + +#ifndef AVM_DISABLE_GPIO_NIFS + +static term nif_gpio_set_pin_mode(Context *ctx, int argc, term argv[]) +{ + UNUSED(argc); + return setup_gpio_pin(ctx, argv[0], argv[1]); +} + +static term nif_gpio_set_pin_pull(Context *ctx, int argc, term argv[]) +{ + UNUSED(argc); + UNUSED(argv); + AVM_LOGW(TAG, "Pull mode must be set using `gpio:set_pin_mode/2` arg #2 i.e. {Mode,PullMode}"); + return ERROR_ATOM; +} + +static term nif_gpio_digital_write(Context *ctx, int argc, term argv[]) +{ + UNUSED(argc); + return gpio_digital_write(ctx, argv[0], argv[1]); +} + +static term nif_gpio_digital_read(Context *ctx, int argc, term argv[]) +{ + UNUSED(argc); + return gpio_digital_read(ctx, argv[0]); +} + +static const struct Nif gpio_set_pin_mode_nif = { + .base.type = NIFFunctionType, + .nif_ptr = nif_gpio_set_pin_mode +}; + +static const struct Nif gpio_set_pin_pull_nif = { + .base.type = NIFFunctionType, + .nif_ptr = nif_gpio_set_pin_pull +}; + +static const struct Nif gpio_digital_write_nif = { + .base.type = NIFFunctionType, + .nif_ptr = nif_gpio_digital_write +}; + +static const struct Nif gpio_digital_read_nif = { + .base.type = NIFFunctionType, + .nif_ptr = nif_gpio_digital_read +}; + +const struct Nif *gpio_nif_get_nif(const char *nifname) +{ + if (strcmp("gpio:set_pin_mode/2", nifname) == 0 || strcmp("Elixir.GPIO:set_pin_mode/2", nifname) == 0) { + AVM_LOGD(TAG, "Resolved platform nif %s ...", nifname); + return &gpio_set_pin_mode_nif; + } + + if (strcmp("gpio:set_pin_pull/2", nifname) == 0 || strcmp("Elixir.GPIO:set_pin_pull/2", nifname) == 0) { + AVM_LOGD(TAG, "Resolved platform nif %s ...", nifname); + return &gpio_set_pin_pull_nif; + } + + if (strcmp("gpio:digital_write/2", nifname) == 0 || strcmp("Elixir.GPIO:digital_write/2", nifname) == 0) { + AVM_LOGD(TAG, "Resolved platform nif %s ...", nifname); + return &gpio_digital_write_nif; + } + + if (strcmp("gpio:digital_read/1", nifname) == 0 || strcmp("Elixir.GPIO:digital_read/1", nifname) == 0) { + AVM_LOGD(TAG, "Resolved platform nif %s ...", nifname); + return &gpio_digital_read_nif; + } + + return NULL; +} + +REGISTER_NIF_COLLECTION(gpio, NULL, NULL, gpio_nif_get_nif) +#endif /* NOT defined AVM_DISABLE_GPIO_NIFS */ + +#endif /* NOT defined AVM_DISABLE_GPIO_PORT_DRIVER AND AVM_DISABLE_GPIO_NIFS */ diff --git a/src/platforms/stm32/src/lib/gpiodriver.h b/src/platforms/stm32/src/lib/gpio_driver.h similarity index 57% rename from src/platforms/stm32/src/lib/gpiodriver.h rename to src/platforms/stm32/src/lib/gpio_driver.h index d0e7deb97..d6a8d2cd7 100644 --- a/src/platforms/stm32/src/lib/gpiodriver.h +++ b/src/platforms/stm32/src/lib/gpio_driver.h @@ -1,7 +1,7 @@ /* * This file is part of AtomVM. * - * Copyright 2018 Riccardo Binetti + * Copyright 2023 Winford (Uncle Grumpy) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,11 +18,21 @@ * SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later */ -#ifndef _GPIODRIVER_H_ -#define _GPIODRIVER_H_ +#ifndef _GPIO_DRIVER_H_ +#define _GPIO_DRIVER_H_ -#include +#include "stm_sys.h" +#include +#include +#include -void gpiodriver_init(Context *ctx); +#ifndef AVM_DISABLE_GPIO_NIFS +static const struct Nif *gpio_nif_get_nif(const char *nifname); +#endif +#ifndef AVM_DISABLE_GPIO_PORT_DRIVER +static Context *gpio_driver_create_port(GlobalContext *global, term opts); +void gpiodriver_init(GlobalContext *glb); #endif + +#endif /* _GPIO_DRIVER_H_ */ diff --git a/src/platforms/stm32/src/lib/gpiodriver.c b/src/platforms/stm32/src/lib/gpiodriver.c deleted file mode 100644 index 01e8dd8fa..000000000 --- a/src/platforms/stm32/src/lib/gpiodriver.c +++ /dev/null @@ -1,185 +0,0 @@ -/* - * This file is part of AtomVM. - * - * Copyright 2018 Riccardo Binetti - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later - */ - -#include "gpiodriver.h" - -#include -#include - -#include -#include -#include - -// Uncomment to enable trace -//#define ENABLE_TRACE 1 -#include - -#include "platform_defaultatoms.h" - -static NativeHandlerResult consume_gpio_mailbox(Context *ctx); -static uint32_t port_atom_to_gpio_port(Context *ctx, term port_atom); -static uint16_t gpio_port_to_rcc_port(uint32_t gpio_port); -static char gpio_port_to_name(uint32_t gpio_port); - -static term create_pair(Context *ctx, term term1, term term2) -{ - term ret = term_alloc_tuple(2, &ctx->heap); - term_put_tuple_element(ret, 0, term1); - term_put_tuple_element(ret, 1, term2); - - return ret; -} - -void gpiodriver_init(Context *ctx) -{ - ctx->native_handler = consume_gpio_mailbox; - ctx->platform_data = NULL; -} - -static NativeHandlerResult consume_gpio_mailbox(Context *ctx) -{ - term ret; - - Message *message = mailbox_first(&ctx->mailbox); - term msg = message->message; - term pid = term_get_tuple_element(msg, 0); - term req = term_get_tuple_element(msg, 2); - term cmd = term_get_tuple_element(req, 0); - - int local_process_id = term_to_local_process_id(pid); - - if (cmd == SET_LEVEL_ATOM) { - term gpio_tuple = term_get_tuple_element(req, 1); - term gpio_port_atom = term_get_tuple_element(gpio_tuple, 0); - uint32_t gpio_port = port_atom_to_gpio_port(ctx, gpio_port_atom); - int32_t gpio_pin_num = term_to_int32(term_get_tuple_element(gpio_tuple, 1)); - int32_t level = term_to_int32(term_get_tuple_element(req, 2)); - - if (level != 0) { - gpio_set(gpio_port, 1 << gpio_pin_num); - } else { - gpio_clear(gpio_port, 1 << gpio_pin_num); - } - TRACE("gpio: set_level: %c%i %i\n", gpio_port_to_name(gpio_port), gpio_pin_num, level != 0); - ret = OK_ATOM; - - } else if (cmd == SET_DIRECTION_ATOM) { - term gpio_tuple = term_get_tuple_element(req, 1); - term gpio_port_atom = term_get_tuple_element(gpio_tuple, 0); - uint32_t gpio_port = port_atom_to_gpio_port(ctx, gpio_port_atom); - int32_t gpio_pin_num = term_to_int32(term_get_tuple_element(gpio_tuple, 1)); - term direction = term_get_tuple_element(req, 2); - - uint16_t rcc_port = gpio_port_to_rcc_port(gpio_port); - // Set direction implicitly enables the port of the GPIO - rcc_periph_clock_enable(rcc_port); - - if (direction == INPUT_ATOM) { - gpio_mode_setup(gpio_port, GPIO_MODE_INPUT, GPIO_PUPD_NONE, 1 << gpio_pin_num); - TRACE("gpio: set_direction: %c%i INPUT\n", gpio_port_to_name(gpio_port), gpio_pin_num); - ret = OK_ATOM; - - } else if (direction == OUTPUT_ATOM) { - gpio_mode_setup(gpio_port, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, 1 << gpio_pin_num); - TRACE("gpio: set_direction: %c%i OUTPUT\n", gpio_port_to_name(gpio_port), gpio_pin_num); - ret = OK_ATOM; - - } else { - TRACE("gpio: unrecognized direction\n"); - ret = ERROR_ATOM; - } - - } else { - TRACE("gpio: unrecognized command\n"); - ret = ERROR_ATOM; - } - - term ret_msg; - if (UNLIKELY(memory_ensure_free(ctx, 3) != MEMORY_GC_OK)) { - ret_msg = OUT_OF_MEMORY_ATOM; - } else { - term ref = term_get_tuple_element(msg, 1); - ret_msg = create_pair(ctx, ref, ret); - } - globalcontext_send_message(ctx->global, local_process_id, ret_msg); - - mailbox_remove_message(&ctx->mailbox, &ctx->heap); - - return NativeContinue; -} - -static uint32_t port_atom_to_gpio_port(Context *ctx, term port_atom) -{ - if (port_atom == A_ATOM) { - return GPIOA; - } else if (port_atom == B_ATOM) { - return GPIOB; - } else if (port_atom == C_ATOM) { - return GPIOC; - } else if (port_atom == D_ATOM) { - return GPIOD; - } else if (port_atom == E_ATOM) { - return GPIOE; - } else if (port_atom == F_ATOM) { - return GPIOF; - } else { - return 0; - } -} - -static uint16_t gpio_port_to_rcc_port(uint32_t gpio_port) -{ - switch (gpio_port) { - case GPIOA: - return RCC_GPIOA; - case GPIOB: - return RCC_GPIOB; - case GPIOC: - return RCC_GPIOC; - case GPIOD: - return RCC_GPIOD; - case GPIOE: - return RCC_GPIOE; - case GPIOF: - return RCC_GPIOF; - default: - return 0; - } -} - -static char gpio_port_to_name(uint32_t gpio_port) -{ - switch (gpio_port) { - case GPIOA: - return 'A'; - case GPIOB: - return 'B'; - case GPIOC: - return 'C'; - case GPIOD: - return 'D'; - case GPIOE: - return 'E'; - case GPIOF: - return 'F'; - default: - return 0; - } -} diff --git a/src/platforms/stm32/src/lib/platform_defaultatoms.c b/src/platforms/stm32/src/lib/platform_defaultatoms.c deleted file mode 100644 index 428f27095..000000000 --- a/src/platforms/stm32/src/lib/platform_defaultatoms.c +++ /dev/null @@ -1,58 +0,0 @@ -/* - * This file is part of AtomVM. - * - * Copyright 2019 Davide Bettio - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later - */ - -#include "platform_defaultatoms.h" - -static const char *const set_level_atom = "\x9" "set_level"; -static const char *const input_atom = "\x5" "input"; -static const char *const output_atom = "\x6" "output"; -static const char *const set_direction_atom ="\xD" "set_direction"; -static const char *const set_int_atom = "\x7" "set_int"; -static const char *const gpio_interrupt_atom = "\xE" "gpio_interrupt"; -static const char *const a_atom = "\x01" "a"; -static const char *const b_atom = "\x01" "b"; -static const char *const c_atom = "\x01" "c"; -static const char *const d_atom = "\x01" "d"; -static const char *const e_atom = "\x01" "e"; -static const char *const f_atom = "\x01" "f"; -static const char *const stm32_atom = "\x5" "stm32"; - -void platform_defaultatoms_init(GlobalContext *glb) -{ - int ok = 1; - - ok &= globalcontext_insert_atom(glb, set_level_atom) == SET_LEVEL_ATOM_INDEX; - ok &= globalcontext_insert_atom(glb, input_atom) == INPUT_ATOM_INDEX; - ok &= globalcontext_insert_atom(glb, output_atom) == OUTPUT_ATOM_INDEX; - ok &= globalcontext_insert_atom(glb, set_direction_atom) == SET_DIRECTION_ATOM_INDEX; - ok &= globalcontext_insert_atom(glb, set_int_atom) == SET_INT_ATOM_INDEX; - ok &= globalcontext_insert_atom(glb, gpio_interrupt_atom) == GPIO_INTERRUPT_ATOM_INDEX; - ok &= globalcontext_insert_atom(glb, a_atom) == A_ATOM_INDEX; - ok &= globalcontext_insert_atom(glb, b_atom) == B_ATOM_INDEX; - ok &= globalcontext_insert_atom(glb, c_atom) == C_ATOM_INDEX; - ok &= globalcontext_insert_atom(glb, d_atom) == D_ATOM_INDEX; - ok &= globalcontext_insert_atom(glb, e_atom) == E_ATOM_INDEX; - ok &= globalcontext_insert_atom(glb, f_atom) == F_ATOM_INDEX; - ok &= globalcontext_insert_atom(glb, stm32_atom) == STM32_ATOM_INDEX; - - if (!ok) { - AVM_ABORT(); - } -} diff --git a/src/platforms/stm32/src/lib/platform_defaultatoms.h b/src/platforms/stm32/src/lib/platform_defaultatoms.h deleted file mode 100644 index d88cddad5..000000000 --- a/src/platforms/stm32/src/lib/platform_defaultatoms.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - * This file is part of AtomVM. - * - * Copyright 2019 Davide Bettio - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later - */ - -#ifndef _PLATFORM_DEFAULTATOMS_H_ -#define _PLATFORM_DEFAULTATOMS_H_ - -#include "defaultatoms.h" - -#define SET_LEVEL_ATOM_INDEX (PLATFORM_ATOMS_BASE_INDEX + 0) -#define INPUT_ATOM_INDEX (PLATFORM_ATOMS_BASE_INDEX + 1) -#define OUTPUT_ATOM_INDEX (PLATFORM_ATOMS_BASE_INDEX + 2) -#define SET_DIRECTION_ATOM_INDEX (PLATFORM_ATOMS_BASE_INDEX + 3) -#define SET_INT_ATOM_INDEX (PLATFORM_ATOMS_BASE_INDEX + 4) -#define GPIO_INTERRUPT_ATOM_INDEX (PLATFORM_ATOMS_BASE_INDEX + 5) -#define A_ATOM_INDEX (PLATFORM_ATOMS_BASE_INDEX + 6) -#define B_ATOM_INDEX (PLATFORM_ATOMS_BASE_INDEX + 7) -#define C_ATOM_INDEX (PLATFORM_ATOMS_BASE_INDEX + 8) -#define D_ATOM_INDEX (PLATFORM_ATOMS_BASE_INDEX + 9) -#define E_ATOM_INDEX (PLATFORM_ATOMS_BASE_INDEX + 10) -#define F_ATOM_INDEX (PLATFORM_ATOMS_BASE_INDEX + 11) -#define STM32_ATOM_INDEX (PLATFORM_ATOMS_BASE_INDEX + 12) - -#define SET_LEVEL_ATOM term_from_atom_index(SET_LEVEL_ATOM_INDEX) -#define INPUT_ATOM term_from_atom_index(INPUT_ATOM_INDEX) -#define OUTPUT_ATOM term_from_atom_index(OUTPUT_ATOM_INDEX) -#define SET_DIRECTION_ATOM term_from_atom_index(SET_DIRECTION_ATOM_INDEX) -#define SET_INT_ATOM term_from_atom_index(SET_INT_ATOM_INDEX) -#define GPIO_INTERRUPT_ATOM term_from_atom_index(GPIO_INTERRUPT_ATOM_INDEX) -#define A_ATOM term_from_atom_index(A_ATOM_INDEX) -#define B_ATOM term_from_atom_index(B_ATOM_INDEX) -#define C_ATOM term_from_atom_index(C_ATOM_INDEX) -#define D_ATOM term_from_atom_index(D_ATOM_INDEX) -#define E_ATOM term_from_atom_index(E_ATOM_INDEX) -#define F_ATOM term_from_atom_index(F_ATOM_INDEX) -#define STM32_ATOM term_from_atom_index(STM32_ATOM_INDEX) - -#endif diff --git a/src/platforms/stm32/src/lib/platform_nifs.c b/src/platforms/stm32/src/lib/platform_nifs.c index a525aeed6..2a5e84383 100644 --- a/src/platforms/stm32/src/lib/platform_nifs.c +++ b/src/platforms/stm32/src/lib/platform_nifs.c @@ -26,7 +26,6 @@ //#define ENABLE_TRACE #include -#include "platform_defaultatoms.h" #include "stm_sys.h" static term nif_atomvm_platform(Context *ctx, int argc, term argv[]) diff --git a/src/platforms/stm32/src/lib/stm_sys.h b/src/platforms/stm32/src/lib/stm_sys.h index 85acf5716..fc3403d4f 100644 --- a/src/platforms/stm32/src/lib/stm_sys.h +++ b/src/platforms/stm32/src/lib/stm_sys.h @@ -23,6 +23,8 @@ #include #include +#define STM32_ATOM globalcontext_make_atom(ctx->global, ATOM_STR("\x5", "stm32")) + /* Only on ARMv7EM and above * TODO: These definitions are back-ported from libopencm3 `master`, if they ever release a new version this section should * be removed, along with the included headers and replaced with only the following inclusion: diff --git a/src/platforms/stm32/src/lib/sys.c b/src/platforms/stm32/src/lib/sys.c index b364c10ad..c6a7542bd 100644 --- a/src/platforms/stm32/src/lib/sys.c +++ b/src/platforms/stm32/src/lib/sys.c @@ -32,11 +32,12 @@ #include #include "avm_log.h" -#include "gpiodriver.h" #include "stm_sys.h" #define TAG "sys" +static const char *const stm32_atom = ATOM_STR("\x5", "stm32"); + struct PortDriverDefListItem *port_driver_list; struct NifCollectionDefListItem *nif_collection_list; @@ -61,11 +62,20 @@ static int32_t timespec_diff_to_ms(struct timespec *timespec1, struct timespec * return (int32_t) ((timespec1->tv_sec - timespec2->tv_sec) * 1000 + (timespec1->tv_nsec - timespec2->tv_nsec) / 1000000); } -void sys_init_platform(GlobalContext *glb) +/* TODO: Needed because `defaultatoms_init` in libAtomVM/defaultatoms.c calls this function. + * We should be able to remove this after `platform_defaulatoms.{c,h}` are removed on all platforms + * and `defaultatoms_init` is no longer called. + */ +void platform_defaultatoms_init(GlobalContext *glb) { UNUSED(glb); } +void sys_init_platform(GlobalContext *glb) +{ + globalcontext_make_atom(glb, stm32_atom); +} + void sys_free_platform(GlobalContext *glb) { UNUSED(glb); From 7f34c4f62a20fb25b77af4961d13a3c3ab6837fc Mon Sep 17 00:00:00 2001 From: Winford Date: Fri, 6 Oct 2023 18:55:19 -0700 Subject: [PATCH 2/4] Allow locking pins used by stm32 system peripherals To prevent accidental reset, or configuration of pins currently in use by system peripherals on the stm32 platform, reserved pins (such as the CONSOLE) may be locked using `sys_lock_pin`, and unlocked with `sys_unlock_pin` so they are not unintentionally reset later when other peripherals such as gpio are configured. This is also intended to support other drivers that consume gpio pins, such as SPI and I2C, to avoid inadvertent resets or re-configuration by other drivers. The libopencm3 libraries on the stm32 platform provide no protection from re-confuguring a gpio pin that is in active use. Signed-off-by: Winford --- src/platforms/stm32/src/lib/stm_sys.h | 28 ++++++++++++++ src/platforms/stm32/src/lib/sys.c | 55 +++++++++++++++++++++++++++ src/platforms/stm32/src/main.c | 18 +++++---- 3 files changed, 93 insertions(+), 8 deletions(-) diff --git a/src/platforms/stm32/src/lib/stm_sys.h b/src/platforms/stm32/src/lib/stm_sys.h index fc3403d4f..d44bdee32 100644 --- a/src/platforms/stm32/src/lib/stm_sys.h +++ b/src/platforms/stm32/src/lib/stm_sys.h @@ -34,6 +34,7 @@ #include #include +#include /** ICIALLU: I-cache invalidate all to Point of Unification */ #define SCB_ICIALLU MMIO32(SCB_BASE + 0x250) @@ -86,6 +87,30 @@ nif_collection_list = &NAME##_nif_collection_def_list_item; \ } +#ifdef LIBOPENCM3_GPIO_COMMON_F24_H +#define GPIO_CLOCK_LIST \ + { \ + RCC_GPIOA, RCC_GPIOB, RCC_GPIOC, RCC_GPIOD, RCC_GPIOE, RCC_GPIOF, RCC_GPIOG, RCC_GPIOH, RCC_GPIOI, RCC_GPIOJ, RCC_GPIOK \ + } +#else +#define GPIO_CLOCK_LIST \ + { \ + RCC_GPIOA, RCC_GPIOB, RCC_GPIOC, RCC_GPIOD, RCC_GPIOE, RCC_GPIOF, RCC_GPIOG, RCC_GPIOH \ + } +#endif + +struct LockedPin +{ + struct ListHead locked_pins_list_head; + uint32_t gpio_bank; + uint16_t pin_num; +}; + +struct STM32PlatformData +{ + struct ListHead locked_pins; +}; + typedef void (*port_driver_init_t)(GlobalContext *global); typedef void (*port_driver_destroy_t)(GlobalContext *global); typedef Context *(*port_driver_create_port_t)(GlobalContext *global, term opts); @@ -124,6 +149,9 @@ struct NifCollectionDefListItem extern struct PortDriverDefListItem *port_driver_list; extern struct NifCollectionDefListItem *nif_collection_list; +void sys_enable_core_periph_clocks(void); +bool sys_lock_pin(GlobalContext *glb, uint32_t gpio_bank, uint16_t pin_num); + static Context *port_driver_create_port(const char *port_name, GlobalContext *global, term opts); void port_driver_init_all(GlobalContext *global); void port_driver_destroy_all(GlobalContext *global); diff --git a/src/platforms/stm32/src/lib/sys.c b/src/platforms/stm32/src/lib/sys.c index c6a7542bd..22119b28e 100644 --- a/src/platforms/stm32/src/lib/sys.c +++ b/src/platforms/stm32/src/lib/sys.c @@ -30,6 +30,7 @@ #include #include #include +#include #include "avm_log.h" #include "stm_sys.h" @@ -71,6 +72,60 @@ void platform_defaultatoms_init(GlobalContext *glb) UNUSED(glb); } +void sys_enable_core_periph_clocks() +{ + uint32_t list[] = GPIO_CLOCK_LIST; + for (int i = 0; i < sizeof(list) / sizeof(list[0]); i++) { + rcc_periph_clock_enable((enum rcc_periph_clken) list[i]); + } +} + +bool sys_lock_pin(GlobalContext *glb, uint32_t gpio_bank, uint16_t pin_num) +{ + struct STM32PlatformData *platform_data = glb->platform_data; + struct ListHead *item; + struct ListHead *tmp; + if (!list_is_empty(&platform_data->locked_pins)) { + MUTABLE_LIST_FOR_EACH (item, tmp, &platform_data->locked_pins) { + struct LockedPin *gpio_pin = GET_LIST_ENTRY(item, struct LockedPin, locked_pins_list_head); + if ((gpio_pin->gpio_bank == gpio_bank) && (gpio_pin->pin_num == pin_num)) { + AVM_LOGW(TAG, "Pin is already reserved by the system!"); + return false; + } + } + } + + struct LockedPin *data = malloc(sizeof(struct LockedPin)); + if (UNLIKELY(IS_NULL_PTR(data))) { + AVM_LOGE(TAG, "Out of memory!"); + AVM_ABORT(); + } + list_append(&platform_data->locked_pins, &data->locked_pins_list_head); + data->gpio_bank = gpio_bank; + data->pin_num = pin_num; + + return true; +} + +bool sys_unlock_pin(GlobalContext *glb, uint32_t gpio_bank, uint16_t pin_num) +{ + struct STM32PlatformData *platform_data = glb->platform_data; + struct ListHead *item; + struct ListHead *tmp; + if (!list_is_empty(&platform_data->locked_pins)) { + MUTABLE_LIST_FOR_EACH (item, tmp, &platform_data->locked_pins) { + struct LockedPin *gpio_pin = GET_LIST_ENTRY(item, struct LockedPin, locked_pins_list_head); + if ((gpio_pin->gpio_bank == gpio_bank) && (gpio_pin->pin_num == pin_num)) { + list_remove(item); + free(gpio_pin); + return true; + } + } + } + + return false; +} + void sys_init_platform(GlobalContext *glb) { globalcontext_make_atom(glb, stm32_atom); diff --git a/src/platforms/stm32/src/main.c b/src/platforms/stm32/src/main.c index 729ca463f..afc54a887 100644 --- a/src/platforms/stm32/src/main.c +++ b/src/platforms/stm32/src/main.c @@ -72,18 +72,16 @@ static void clock_setup() { // Use external clock, set divider for 168 MHz clock frequency rcc_clock_setup_pll(&rcc_hse_8mhz_3v3[RCC_CLOCK_3V3_168MHZ]); +} - // Enable clock for USART2 GPIO - rcc_periph_clock_enable(RCC_GPIOA); - +static void usart_setup(GlobalContext *glb) +{ // Enable clock for USART2 rcc_periph_clock_enable(RCC_USART2); -} -static void usart_setup() -{ // Setup GPIO pins for USART2 transmit gpio_mode_setup(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO2); + sys_lock_pin(glb, GPIOA, GPIO2); // Setup USART2 TX pin as alternate function gpio_set_af(GPIOA, GPIO_AF7, GPIO2); @@ -162,7 +160,12 @@ int main() sys_init_icache(); clock_setup(); systick_setup(); - usart_setup(); + // Start core peripheral clocks now so there are no accidental resets of peripherals that share a clock later. + sys_enable_core_periph_clocks(); + + GlobalContext *glb = globalcontext_new(); + + usart_setup(glb); fprintf(stdout, "%s", ATOMVM_BANNER); AVM_LOGI(TAG, "Starting AtomVM revision " ATOMVM_VERSION); @@ -177,7 +180,6 @@ int main() AVM_LOGD(TAG, "Maximum application size: %lu", size); - GlobalContext *glb = globalcontext_new(); port_driver_init_all(glb); nif_collection_init_all(glb); From 7f068d07eb43502e96f6367695cf59dee56fa78f Mon Sep 17 00:00:00 2001 From: Winford Date: Sun, 17 Sep 2023 21:53:45 -0700 Subject: [PATCH 3/4] Add STM32 gpio interrupt support Add support for interrupts to stm32 gpio port driver. Signed-off-by: Winford --- CHANGELOG.md | 1 + src/platforms/stm32/src/lib/gpio_driver.c | 405 +++++++++++++++++++++- src/platforms/stm32/src/lib/sys.c | 4 + 3 files changed, 404 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index af07e289a..750101e7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Enhancd performance of STM32 by enabling flash cache and i-cache with branch prediction. - Added cmake configuration option `AVM_CONFIG_REBOOT_ON_NOT_OK` for STM32 - New gpio driver for STM32 with nif and port support for read and write functions. +- Added support for interrupts to STM32 GPIO port driver. ## [0.6.0-alpha.1] - 2023-10-09 diff --git a/src/platforms/stm32/src/lib/gpio_driver.c b/src/platforms/stm32/src/lib/gpio_driver.c index 6b5861ae7..0eef6006c 100644 --- a/src/platforms/stm32/src/lib/gpio_driver.c +++ b/src/platforms/stm32/src/lib/gpio_driver.c @@ -23,6 +23,8 @@ #include #include +#include +#include #include #include @@ -60,16 +62,37 @@ #define INVALID_PULL_ATOM globalcontext_make_atom(ctx->global, ATOM_STR("\xC", "invalid_pull")) #define INVALID_RATE_ATOM globalcontext_make_atom(ctx->global, ATOM_STR("\xC", "invalid_rate")) #define INVALID_LEVEL_ATOM globalcontext_make_atom(ctx->global, ATOM_STR("\xD", "invalid_level")) +#define INVALID_IRQ_ATOM globalcontext_make_atom(ctx->global, ATOM_STR("\xB", "invalid_irq")) +#define INVALID_TRIGGER_ATOM globalcontext_make_atom(ctx->global, ATOM_STR("\xF", "invalid_trigger")) // Port driver specific data structures and definitions #ifndef AVM_DISABLE_GPIO_PORT_DRIVER + static NativeHandlerResult consume_gpio_mailbox(Context *ctx); static const char *const gpio_atom = ATOM_STR("\x4", "gpio"); static const char *const gpio_driver_atom = ATOM_STR("\xB", "gpio_driver"); +#define INVALID_EXTI_TRIGGER 0xEE +#define GPIO_INTERRUPT_ATOM globalcontext_make_atom(ctx->global, ATOM_STR("\xE", "gpio_interrupt")) + static term gpio_driver; +struct GPIOListenerData +{ + struct ListHead gpio_listener_list_head; + int32_t target_local_pid; + term bank_atom; + uint16_t gpio_pin; + uint32_t exti; + uint8_t exti_irq; +}; + +struct GPIOData +{ + struct ListHead gpio_listeners; +}; + enum gpio_cmd { GPIOInvalidCmd = 0, @@ -81,6 +104,13 @@ enum gpio_cmd GPIOCloseCmd }; +static const AtomStringIntPair exti_trigger_table[] = { + { ATOM_STR("\x6", "rising"), EXTI_TRIGGER_RISING }, + { ATOM_STR("\x7", "falling"), EXTI_TRIGGER_FALLING }, + { ATOM_STR("\x4", "both"), EXTI_TRIGGER_BOTH }, + SELECT_INT_DEFAULT(INVALID_EXTI_TRIGGER) +}; + static const AtomStringIntPair gpio_cmd_table[] = { { ATOM_STR("\x9", "set_level"), GPIOSetLevelCmd }, { ATOM_STR("\x4", "read"), GPIOReadCmd }, @@ -90,6 +120,7 @@ static const AtomStringIntPair gpio_cmd_table[] = { { ATOM_STR("\x5", "close"), GPIOCloseCmd }, SELECT_INT_DEFAULT(GPIOInvalidCmd) }; + #endif /* NOT defined AVM_DISABLE_GPIO_PORT_DRIVER */ enum gpio_pin_state @@ -146,6 +177,49 @@ static const AtomStringIntPair pull_mode_table[] = { SELECT_INT_DEFAULT(GPIO_PUPD_NONE) }; +#ifndef AVM_DISABLE_GPIO_PORT_DRIVER +// Obtain the IRQ interrupt associated with a pin number +static uint8_t pin_num_to_exti_irq(uint16_t pin_num) +{ + switch (pin_num) { + case 0: + return NVIC_EXTI0_IRQ; + case 1: + return NVIC_EXTI1_IRQ; + case 2: + return NVIC_EXTI2_IRQ; + case 3: + return NVIC_EXTI3_IRQ; + case 4: + return NVIC_EXTI4_IRQ; + case 5: + return NVIC_EXTI9_5_IRQ; + case 6: + return NVIC_EXTI9_5_IRQ; + case 7: + return NVIC_EXTI9_5_IRQ; + case 8: + return NVIC_EXTI9_5_IRQ; + case 9: + return NVIC_EXTI9_5_IRQ; + case 10: + return NVIC_EXTI15_10_IRQ; + case 11: + return NVIC_EXTI15_10_IRQ; + case 12: + return NVIC_EXTI15_10_IRQ; + case 13: + return NVIC_EXTI15_10_IRQ; + case 14: + return NVIC_EXTI15_10_IRQ; + case 15: + return NVIC_EXTI15_10_IRQ; + default: + return 0; + } +} +#endif /* NOT defined AVM_DISABLE_GPIO_PORT_DRIVER */ + static term create_pair(Context *ctx, term term1, term term2) { term ret = term_alloc_tuple(2, &ctx->heap); @@ -417,8 +491,11 @@ static Context *gpio_driver_create_port(GlobalContext *global, term opts) { Context *ctx = context_new(global); + struct GPIOData *gpio_data = malloc(sizeof(struct GPIOData)); + list_init(&gpio_data->gpio_listeners); + ctx->native_handler = consume_gpio_mailbox; - ctx->platform_data = NULL; + ctx->platform_data = gpio_data; term reg_name_term = globalcontext_make_atom(global, gpio_atom); int atom_index = term_to_atom_index(reg_name_term); @@ -442,13 +519,170 @@ static term gpiodriver_close(Context *ctx) return create_pair(ctx, ERROR_ATOM, NOPROC_ATOM); } - ctx->platform_data = NULL; + struct GPIOData *gpio_data = ctx->platform_data; + + struct ListHead *item; + struct ListHead *tmp; + if (!list_is_empty(&gpio_data->gpio_listeners)) { + MUTABLE_LIST_FOR_EACH (item, tmp, &gpio_data->gpio_listeners) { + struct GPIOListenerData *gpio_listener = GET_LIST_ENTRY(item, struct GPIOListenerData, gpio_listener_list_head); + uint32_t exti = gpio_listener->exti; + uint8_t irqn = pin_num_to_exti_irq(gpio_listener->gpio_pin); + nvic_disable_irq(irqn); + exti_disable_request(exti); + list_remove(&gpio_listener->gpio_listener_list_head); + free(gpio_listener); + } + } globalcontext_unregister_process(glb, gpio_atom_index); + free(gpio_data); return OK_ATOM; } +void gpio_interrupt_callback(Context *ctx, uint32_t exti) +{ + int32_t listening_pid; + term gpio_bank; + uint16_t gpio_pin; + + struct GPIOData *gpio_data = ctx->platform_data; + struct ListHead *item; + struct ListHead *tmp; + MUTABLE_LIST_FOR_EACH (item, tmp, &gpio_data->gpio_listeners) { + struct GPIOListenerData *gpio_listener = GET_LIST_ENTRY(item, struct GPIOListenerData, gpio_listener_list_head); + if (gpio_listener->exti == exti) { + listening_pid = gpio_listener->target_local_pid; + gpio_bank = gpio_listener->bank_atom; + gpio_pin = gpio_listener->gpio_pin; + + // 1 header + 2 elements, second element is tuple with 2 elements + BEGIN_WITH_STACK_HEAP(1 + 2 + 2, heap); + + term int_msg = term_alloc_tuple(2, &heap); + term gpio_tuple = term_alloc_tuple(2, &heap); + term_put_tuple_element(int_msg, 0, GPIO_INTERRUPT_ATOM); + term_put_tuple_element(gpio_tuple, 0, gpio_bank); + term_put_tuple_element(gpio_tuple, 1, term_from_int32((int32_t) gpio_pin)); + term_put_tuple_element(int_msg, 1, gpio_tuple); + + globalcontext_send_message(ctx->global, listening_pid, int_msg); + + END_WITH_STACK_HEAP(heap, ctx->global); + } + } +} + +// This function is used to store the local Context pointer during gpiodriver_set_int and to relay messages to the interrupt_callback +// from the exti#_isr interrupt handlers (defined in libopencm3/stm32/CHIP-SERIES/nvic.h) that have no access to the context. +void isr_handler(Context *ctx, uint32_t exti) +{ + static Context *local_ctx; + if (ctx != NULL) { + UNUSED(exti); + local_ctx = ctx; + TRACE("gpio_driver: GlobalContext stored in isr_handler\n"); + } else { + UNUSED(ctx); + TRACE("gpio_driver: isr_handler relaying exti to gpio_interrupt_callback\n"); + gpio_interrupt_callback(local_ctx, exti); + } +} + +void exti0_isr() +{ + exti_reset_request(EXTI0); + TRACE("gpio_driver: exti0 interrupt triggered.\n"); + isr_handler(NULL, EXTI0); +} + +void exti1_isr() +{ + exti_reset_request(EXTI1); + TRACE("gpio_driver: exti1 interrupt triggered\n"); + isr_handler(NULL, EXTI1); +} + +void exti2_isr() +{ + exti_reset_request(EXTI2); + TRACE("gpio_driver: exti2 interrupt triggered\n"); + isr_handler(NULL, EXTI2); +} + +void exti3_isr() +{ + exti_reset_request(EXTI3); + TRACE("gpio_driver: exti3 interrupt triggered\n"); + isr_handler(NULL, EXTI3); +} + +void exti4_isr() +{ + exti_reset_request(EXTI4); + TRACE("gpio_driver: exti4 interrupt triggered\n"); + isr_handler(NULL, EXTI4); +} + +void exti9_5_isr() +{ + if (exti_get_flag_status(EXTI5) == EXTI5) { + exti_reset_request(EXTI5); + isr_handler(NULL, EXTI5); + TRACE("gpio_driver: exti5 interrupt triggered\n"); + } else if (exti_get_flag_status(EXTI6) == EXTI6) { + exti_reset_request(EXTI6); + isr_handler(NULL, EXTI6); + TRACE("gpio_driver: exti6 interrupt triggered\n"); + } else if (exti_get_flag_status(EXTI7) == EXTI7) { + exti_reset_request(EXTI7); + isr_handler(NULL, EXTI7); + TRACE("gpio_driver: exti7 interrupt triggered\n"); + } else if (exti_get_flag_status(EXTI8) == EXTI8) { + exti_reset_request(EXTI8); + isr_handler(NULL, EXTI8); + TRACE("gpio_driver: exti8 interrupt triggered\n"); + } else if (exti_get_flag_status(EXTI9) == EXTI9) { + exti_reset_request(EXTI9); + isr_handler(NULL, EXTI9); + TRACE("gpio_driver: exti9 interrupt triggered\n"); + } else { + AVM_LOGE(TAG, "exti9_5_isr triggered, but no match found!"); + } +} + +void exti15_10_isr() +{ + if (exti_get_flag_status(EXTI10)) { + exti_reset_request(EXTI10); + isr_handler(NULL, EXTI10); + TRACE("gpio_driver: exti10 interrupt triggered\n"); + } else if (exti_get_flag_status(EXTI11)) { + exti_reset_request(EXTI11); + isr_handler(NULL, EXTI11); + TRACE("gpio_driver: exti11 interrupt triggered\n"); + } else if (exti_get_flag_status(EXTI12)) { + exti_reset_request(EXTI12); + isr_handler(NULL, EXTI12); + TRACE("gpio_driver: exti12 interrupt triggered\n"); + } else if (exti_get_flag_status(EXTI13)) { + exti_reset_request(EXTI13); + isr_handler(NULL, EXTI13); + TRACE("gpio_driver: exti13 interrupt triggered\n"); + } else if (exti_get_flag_status(EXTI14)) { + exti_reset_request(EXTI14); + isr_handler(NULL, EXTI14); + TRACE("gpio_driver: exti14 interrupt triggered\n"); + } else if (exti_get_flag_status(EXTI15)) { + exti_reset_request(EXTI15); + isr_handler(NULL, EXTI15); + TRACE("gpio_driver: exti15 interrupt triggered\n"); + } else { + AVM_LOGE(TAG, "exti15_10_isr triggered, but no match found!"); + } +} + static term gpiodriver_set_level(Context *ctx, term cmd) { term gpio_pin_tuple = term_get_tuple_element(cmd, 1); @@ -471,6 +705,167 @@ static term gpiodriver_read(Context *ctx, term cmd) return gpio_digital_read(ctx, gpio_pin_tuple); } +static bool gpiodriver_is_gpio_attached(struct GPIOData *gpio_data, term gpio_bank_atom, uint16_t pin) +{ + struct ListHead *item; + LIST_FOR_EACH (item, &gpio_data->gpio_listeners) { + struct GPIOListenerData *gpio_listener = GET_LIST_ENTRY(item, struct GPIOListenerData, gpio_listener_list_head); + if ((gpio_listener->gpio_pin == pin) && (gpio_listener->bank_atom == gpio_bank_atom)) { + return true; + } + } + return false; +} + +static term gpiodriver_set_int(Context *ctx, int32_t target_pid, term cmd) +{ + 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}."); + return create_pair(ctx, ERROR_ATOM, BADARG_ATOM); + } + term gpio_bank_atom = term_get_tuple_element(gpio_tuple, 0); + if (UNLIKELY(!term_is_atom(gpio_bank_atom))) { + AVM_LOGE(TAG, "Bank parameter of pin tuple must be an atom!"); + return create_pair(ctx, ERROR_ATOM, INVALID_BANK_ATOM); + } + uint32_t gpio_bank = (uint32_t) interop_atom_term_select_int(gpio_bank_table, gpio_bank_atom, ctx->global); + if (UNLIKELY(gpio_bank == GPIOInvalidBank)) { + char *bank_string = interop_atom_to_string(ctx, gpio_bank_atom); + AVM_LOGE(TAG, "Invalid GPIO bank '%s' in pin tuple", bank_string); + free(bank_string); + return create_pair(ctx, ERROR_ATOM, INVALID_BANK_ATOM); + } + + uint16_t gpio_pin = (uint16_t) term_to_int32(term_get_tuple_element(gpio_tuple, 1)); + + struct GPIOData *gpio_data = ctx->platform_data; + if (gpiodriver_is_gpio_attached(gpio_data, gpio_bank_atom, gpio_pin)) { + char *bank_string = interop_atom_to_string(ctx, gpio_bank_atom); + AVM_LOGW(TAG, "GPIO pin %c%u interrupt already attached.", bank_string, gpio_pin); + free(bank_string); + return OK_ATOM; + } + + term trigger = term_get_tuple_element(cmd, 2); + if (UNLIKELY(!term_is_atom(trigger))) { + AVM_LOGE(TAG, "GPIO interrupt trigger must be an atom ('rising', 'falling', or 'both')."); + return create_pair(ctx, ERROR_ATOM, INVALID_TRIGGER_ATOM); + } + enum exti_trigger_type interrupt_type = interop_atom_term_select_int(exti_trigger_table, trigger, ctx->global); + if (UNLIKELY(interrupt_type == INVALID_EXTI_TRIGGER)) { + char *trigger_string = interop_atom_to_string(ctx, trigger); + AVM_LOGE(TAG, "Interrupt type %s not supported on stm32 platform.", trigger_string); + free(trigger_string); + return create_pair(ctx, ERROR_ATOM, INVALID_TRIGGER_ATOM); + } + + uint32_t exti = 1U << gpio_pin; + + if (!list_is_empty(&gpio_data->gpio_listeners)) { + struct ListHead *item; + struct ListHead *tmp; + MUTABLE_LIST_FOR_EACH (item, tmp, &gpio_data->gpio_listeners) { + struct GPIOListenerData *gpio_listener = GET_LIST_ENTRY(item, struct GPIOListenerData, gpio_listener_list_head); + if (gpio_listener->exti == exti) { + char *bank_string = interop_atom_to_string(ctx, gpio_bank_atom); + AVM_LOGE(TAG, "Cannot set interrupt for pin %c%u, exti%u device already in use!", bank_string, gpio_pin, gpio_pin); + free(bank_string); + return create_pair(ctx, ERROR_ATOM, INVALID_IRQ_ATOM); + } + } + } + + uint8_t exti_irq = pin_num_to_exti_irq(gpio_pin); + if (UNLIKELY(exti_irq == 0)) { + AVM_LOGE(TAG, "BUG: No valid exti irq found!"); + return create_pair(ctx, ERROR_ATOM, INVALID_IRQ_ATOM); + } + + struct GPIOListenerData *data = malloc(sizeof(struct GPIOListenerData)); + if (UNLIKELY(IS_NULL_PTR(data))) { + AVM_LOGE(TAG, "Out of memory!"); + AVM_ABORT(); + } + list_append(&gpio_data->gpio_listeners, &data->gpio_listener_list_head); + data->target_local_pid = target_pid; + data->bank_atom = gpio_bank_atom; + data->gpio_pin = gpio_pin; + data->exti = exti; + data->exti_irq = exti_irq; + + AVM_LOGD(TAG, "Installing interrupt type 0x%02X with exti%u for bank 0x%08lX pin %u.", interrupt_type, gpio_pin, gpio_bank, gpio_pin); + + exti_disable_request(exti); + exti_select_source(exti, gpio_bank); + exti_set_trigger(exti, interrupt_type); + // Store the Context pointer in the isr_handler + isr_handler(ctx, 0x0000U); + exti_enable_request(exti); + if (!nvic_get_irq_enabled(exti_irq)) { + nvic_enable_irq(exti_irq); + } + nvic_set_priority(exti_irq, 1); + + return OK_ATOM; +} + +static term gpiodriver_remove_int(Context *ctx, term cmd) +{ + struct GPIOData *gpio_data = ctx->platform_data; + bool int_removed = false; + bool stop_irq = true; + + 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}."); + return create_pair(ctx, ERROR_ATOM, BADARG_ATOM); + } + term target_bank_atom = term_get_tuple_element(gpio_tuple, 0); + if (UNLIKELY(!term_is_atom(target_bank_atom))) { + AVM_LOGE(TAG, "Bank parameter of pin tuple must be an atom!"); + return create_pair(ctx, ERROR_ATOM, INVALID_BANK_ATOM); + } + uint32_t target_bank = (uint32_t) interop_atom_term_select_int(gpio_bank_table, target_bank_atom, ctx->global); + if (UNLIKELY(target_bank == GPIOInvalidBank)) { + char *bank_string = interop_atom_to_string(ctx, target_bank_atom); + AVM_LOGE(TAG, "Invalid GPIO bank %s in pin tuple", bank_string); + free(bank_string); + return create_pair(ctx, ERROR_ATOM, INVALID_BANK_ATOM); + } + uint16_t target_num = (uint16_t) term_to_int32(term_get_tuple_element(gpio_tuple, 1)); + uint8_t target_irq = pin_num_to_exti_irq(target_num); + + struct ListHead *item; + struct ListHead *tmp; + if (!list_is_empty(&gpio_data->gpio_listeners)) { + MUTABLE_LIST_FOR_EACH (item, tmp, &gpio_data->gpio_listeners) { + struct GPIOListenerData *gpio_listener = GET_LIST_ENTRY(item, struct GPIOListenerData, gpio_listener_list_head); + if ((gpio_listener->gpio_pin == target_num) && (gpio_listener->bank_atom == target_bank_atom)) { + uint16_t exti = gpio_listener->exti; + uint8_t irqn = pin_num_to_exti_irq(gpio_listener->gpio_pin); + list_remove(&gpio_listener->gpio_listener_list_head); + exti_disable_request(exti); + free(gpio_listener); + int_removed = true; + // some pins share irqs - don't stop the irq if another pin is still using it. + } else if (gpio_listener->exti_irq == target_irq) { + stop_irq = false; + } + } + if (stop_irq) { + nvic_disable_irq(target_irq); + } + if (int_removed == false) { + AVM_LOGW(TAG, "No interrupt removed, match not found for bank 0x%08lX pin %u.", target_bank, target_num); + } + } else { + AVM_LOGW(TAG, "No interrupts have been previously set."); + } + + return OK_ATOM; +} + static NativeHandlerResult consume_gpio_mailbox(Context *ctx) { Message *message = mailbox_first(&ctx->mailbox); @@ -498,13 +893,11 @@ static NativeHandlerResult consume_gpio_mailbox(Context *ctx) break; case GPIOSetIntCmd: - AVM_LOGE(TAG, "set_int not yet supported on stm32"); - ret = create_pair(ctx, ERROR_ATOM, UNSUPPORTED_ATOM); + ret = gpiodriver_set_int(ctx, local_process_id, req); break; case GPIORemoveIntCmd: - AVM_LOGE(TAG, "remove_int not yet supported on stm32"); - ret = create_pair(ctx, ERROR_ATOM, UNSUPPORTED_ATOM); + ret = gpiodriver_remove_int(ctx, req); break; case GPIOCloseCmd: diff --git a/src/platforms/stm32/src/lib/sys.c b/src/platforms/stm32/src/lib/sys.c index 22119b28e..f46b239c1 100644 --- a/src/platforms/stm32/src/lib/sys.c +++ b/src/platforms/stm32/src/lib/sys.c @@ -78,6 +78,10 @@ void sys_enable_core_periph_clocks() for (int i = 0; i < sizeof(list) / sizeof(list[0]); i++) { rcc_periph_clock_enable((enum rcc_periph_clken) list[i]); } +#ifndef AVM_DISABLE_GPIO_PORT_DRIVER + // This clock enables the syscfg manger for external gpio interupts & ethernet PHY interface. + rcc_periph_clock_enable(RCC_SYSCFG); +#endif } bool sys_lock_pin(GlobalContext *glb, uint32_t gpio_bank, uint16_t pin_num) From cee9209206807a1263d1315903e2c34dff889789 Mon Sep 17 00:00:00 2001 From: Winford Date: Fri, 8 Sep 2023 21:49:42 -0700 Subject: [PATCH 4/4] Update GPIO documentation for STM32 port Updates the types and edoc entries for gpio.erl, and the "Programmers Guide" with details about the updated STM32 gpio driver. Signed-off-by: Winford --- doc/src/programmers-guide.md | 58 +++++++++- libs/eavmlib/src/gpio.erl | 198 ++++++++++++++++++++++++++++------- 2 files changed, 214 insertions(+), 42 deletions(-) diff --git a/doc/src/programmers-guide.md b/doc/src/programmers-guide.md index 643be9829..4f075bd05 100644 --- a/doc/src/programmers-guide.md +++ b/doc/src/programmers-guide.md @@ -1043,16 +1043,47 @@ The `esp:get_mac/1` function can be used to retrieve the network Media Access Co ## Peripherals -The AtomVM virtual machine and libraries support APIs for interfacing with peripheral devices connected to the ESP32. This section provides information about these APIs. +The AtomVM virtual machine and libraries support APIs for interfacing with peripheral devices connected to the ESP32 and other supported microcontrollers. This section provides information about these APIs. Unless otherwise stated the documentation for these peripherals is specific to the ESP32, most peripherals are not yet supported on rp2040 or stm32 devices - but work is on-going to expand support on these platforms. ### GPIO +The GPIO peripheral has nif support on all platforms. One notable difference on the STM32 platform is that `Pin`s are defined as a tuple consisting of the bank (a.k.a. port) and pin number. For example a pin labeled PB7 on your board would be `{b,7}`. + You can read and write digital values on GPIO pins using the `gpio` module, using the `digital_read/1` and `digital_write/2` functions. You must first set the direction of the pin using the `gpio:set_direction/2` function, using `input` or `output` as the direction parameter. -To read the value of a GPIO pin (`high` or `low`), use `gpio:digital_read/1`: +#### Digital Read + +To read the value of a GPIO pin (`high` or `low`), use `gpio:digital_read/1`. + +For ESP32 family: + + %% erlang + Pin = 2, + gpio:set_direction(Pin, input), + case gpio:digital_read(Pin) of + high -> + io:format("Pin ~p is high ~n", [Pin]); + low -> + io:format("Pin ~p is low ~n", [Pin]) + end. + +For STM32 only the line with the Pin definition needs to be a tuple: + + %% erlang + Pin = {c, 13}, + gpio:set_direction(Pin, input), + case gpio:digital_read(Pin) of + high -> + io:format("Pin ~p is high ~n", [Pin]); + low -> + io:format("Pin ~p is low ~n", [Pin]) + end. + +The Pico has an additional initialization step `gpio:init/1` before using a pin for gpio: %% erlang Pin = 2, + gpio:init(Pin), gpio:set_direction(Pin, input), case gpio:digital_read(Pin) of high -> @@ -1061,15 +1092,36 @@ To read the value of a GPIO pin (`high` or `low`), use `gpio:digital_read/1`: io:format("Pin ~p is low ~n", [Pin]) end. -To set the value of a GPIO pin (`high` or `low`), use `gpio:digital_write/2`: +#### Digital Write + +To set the value of a GPIO pin (`high` or `low`), use `gpio:digital_write/2`. + +For ESP32 family: %% erlang Pin = 2, gpio:set_direction(Pin, output), gpio:digital_write(Pin, low). +For the STM32 use a pin tuple: + + %% erlang + Pin = {b, 7}, + gpio:set_direction(Pin, output), + gpio:digital_write(Pin, low). + +Pico needs the extra `gpio:init/1` before `gpio:read/1` too: + + %% erlang + Pin = 2, + gpio:init(Pin), + gpio:set_direction(Pin, output), + gpio:digital_write(Pin, low). + #### Interrupt Handling +Interrupts are supported on both the ESP32 and STM32 platforms. + You can get notified of changes in the state of a GPIO pin by using the `gpio:set_int/2` function. This function takes a reference to a GPIO Pin and a trigger. Allowable triggers are `rising`, `falling`, `both`, `low`, `high`, and `none` (to disable an interrupt). When a trigger event occurs, such as a pin rising in voltage, a tuple will be delivered to the process containing the atom `gpio_interrupt` and the pin. diff --git a/libs/eavmlib/src/gpio.erl b/libs/eavmlib/src/gpio.erl index 5c545bd08..f91ac90d1 100644 --- a/libs/eavmlib/src/gpio.erl +++ b/libs/eavmlib/src/gpio.erl @@ -25,13 +25,21 @@ %% (General Purpose Input and Output) pins. %% %% Note: `-type pin()' used in this driver refers to a pin number on Espressif -%% chips, or a tuple {GPIO_GROUP, PIN} for stm32 chips. +%% chips, or a tuple {GPIO_BANK, PIN} for stm32 chips. %% @end %%----------------------------------------------------------------------------- -module(gpio). -export([ - start/0, open/0, read/2, set_direction/3, set_level/3, set_int/3, remove_int/2, stop/0, close/1 + start/0, + open/0, + read/2, + set_direction/3, + set_level/3, + set_int/3, + remove_int/2, + stop/0, + close/1 ]). -export([ init/1, @@ -49,25 +57,42 @@ ]). -type gpio() :: pid(). --type pin() :: non_neg_integer() | {atom(), non_neg_integer()}. --type direction() :: input | output | output_od. +%% This is the pid returned by `gpio:start/0'. +-type pin() :: non_neg_integer() | pin_tuple(). +%% The pin definition for ESP32 and PR2040 is a non-negative integer, on the STM32 platform it is a tuple. +-type pin_tuple() :: {gpio_bank(), 0..15}. +%% A pin parameter on STM32 is a tuple consisting of a GPIO bank and pin number. +-type gpio_bank() :: a | b | c | d | e | f | g | h | i | j | k. +%% STM32 gpio banks vary by board, some only break out `a' thru `h'. +-type direction() :: input | output | output_od | mode_config(). +%% The direction is used to set the mode of operation for a GPIO pin, either as an input, an output, or output with open drain. +%% On the STM32 platform pull mode and output_speed must be set at the same time as direction. See @type mode_config() +-type mode_config() :: {direction(), pull()} | {output, pull(), output_speed()}. +%% Extended mode configuration options on STM32. Default pull() is `floating', default output_speed() is `mhz_2' if options are omitted. -type pull() :: up | down | up_down | floating. +%% Internal resistor pull mode. STM32 does not support `up_down'. +-type output_speed() :: mhz_2 | mhz_25 | mhz_50 | mhz_100. +%% Output clock speed. Only available on STM32, default is `mhz_2'. -type low_level() :: low | 0. -type high_level() :: high | 1. -type level() :: low_level() | high_level(). +%% Valid pin levels can be atom or binary representation. -type trigger() :: none | rising | falling | both | low | high. +%% Event type that will trigger a `gpio_interrupt'. STM32 only supports `rising', `falling', or `both'. %%----------------------------------------------------------------------------- -%% @returns Pid +%% @returns Pid | error | {error, Reason} %% @doc Start the GPIO driver port %% %% Returns the pid of the active GPIO port driver, otherwise the GPIO %% port driver will be stared and registered as `gpio'. The use of %% `gpio:open/0' or `gpio:start/0' is required before using any functions %% that require a GPIO pid as a parameter. +%% +%% Not currently available on rp2040 (Pico) port, use nif functions. %% @end %%----------------------------------------------------------------------------- --spec start() -> gpio(). +-spec start() -> gpio() | {error, Reason :: atom()} | error. start() -> case whereis(gpio) of undefined -> @@ -77,7 +102,7 @@ start() -> end. %%----------------------------------------------------------------------------- -%% @returns Pid +%% @returns Pid | error | {error, Reason} %% @doc Start the GPIO driver port %% %% The GPIO port driver will be stared and registered as `gpio'. If the @@ -85,49 +110,57 @@ start() -> %% `gpio:start/0' the command will fail. The use of `gpio:open/0' or %% `gpio:start/0' is required before using any functions that require a %% GPIO pid as a parameter. +%% +%% Not currently available on rp2040 (Pico) port, use nif functions. %% @end %%----------------------------------------------------------------------------- --spec open() -> gpio(). +-spec open() -> gpio() | {error, Reason :: atom()} | error. open() -> open_port({spawn, "gpio"}, []). %%----------------------------------------------------------------------------- %% @param GPIO pid that was returned from gpio:start/0 -%% @returns ok atom +%% @returns ok | error | {error, Reason} %% @doc Stop the GPIO interrupt port %% %% This function disables any interrupts that are set, stops %% the listening port, and frees all of its resources. +%% +%% Not currently available on rp2040 (Pico) port, use nif functions. %% @end %%----------------------------------------------------------------------------- --spec close(GPIO :: gpio()) -> ok | error. +-spec close(GPIO :: gpio()) -> ok | {error, Reason :: atom()} | error. close(GPIO) -> port:call(GPIO, {close}). %%----------------------------------------------------------------------------- -%% @returns ok atom +%% @returns ok | error | {error, Reason} %% @doc Stop the GPIO interrupt port %% %% This function disables any interrupts that are set, stops %% the listening port, and frees all of its resources. +%% +%% Not currently available on rp2040 (Pico) port, use nif functions. %% @end %%----------------------------------------------------------------------------- --spec stop() -> ok | error. +-spec stop() -> ok | {error, Reason :: atom()} | error. stop() -> close(whereis(gpio)). %%----------------------------------------------------------------------------- %% @param GPIO pid that was returned from gpio:start/0 %% @param Pin number of the pin to read -%% @returns high | low +%% @returns high | low | error | {error, Reason} %% @doc Read the digital state of a GPIO pin %% %% Read if an input pin state is `high' or `low'. -%% Warning: if a is not previously configured as an input using +%% Warning: if the pin was not previously configured as an input using %% `gpio:set_direction/3' it will always read as low. +%% +%% Not supported on rp2040 (Pico), use `gpio:digital_read/1' instead. %% @end %%----------------------------------------------------------------------------- --spec read(GPIO :: gpio(), Pin :: pin()) -> high | low. +-spec read(GPIO :: gpio(), Pin :: pin()) -> high | low | {error, Reason :: atom()} | error. read(GPIO, Pin) -> port:call(GPIO, {read, Pin}). @@ -135,13 +168,29 @@ read(GPIO, Pin) -> %% @param GPIO pid that was returned from `gpio:start/0' %% @param Pin number of the pin to configure %% @param Direction is `input', `output', or `output_od' -%% @returns ok | error +%% @returns ok | error | {error, Reason} %% @doc Set the operational mode of a pin %% %% Pins can be used for input, output, or output with open drain. +%% +%% The STM32 platform has extended direction mode configuration options. +%% See @type mode_config() for details. All configuration must be set using +%% `set_direction/3', including pull() mode, unlike the ESP32 which has a +%% separate function (`set_pin_pull/2'). If you are configuring multiple pins +%% on the same GPIO `bank' with the same options the pins may be configured all +%% at the same time by giving a list of pin numbers in the pin tuple. +%% +%% Example to configure all of the leds on a Nucleo board: +%% +%%
+%%    gpio:set_direction({b, [0,7,14], output)
+%% 
+%% +%% Not supported on rp2040 (Pico), use `gpio:set_pin_mode/2' instead. %% @end %%----------------------------------------------------------------------------- --spec set_direction(GPIO :: gpio(), Pin :: pin(), Direction :: direction()) -> ok | error. +-spec set_direction(GPIO :: gpio(), Pin :: pin(), Direction :: direction()) -> + ok | {error, Reason :: atom()} | error. set_direction(GPIO, Pin, Direction) -> port:call(GPIO, {set_direction, Pin, Direction}). @@ -149,13 +198,33 @@ set_direction(GPIO, Pin, Direction) -> %% @param GPIO pid that was returned from `gpio:start/0' %% @param Pin number of the pin to write %% @param Desired output level to set -%% @returns ok | error +%% @returns ok | error | {error, Reason} %% @doc Set GPIO digital output level %% %% Set a pin to `high' (1) or `low' (0). +%% +%% The STM32 is capable of setting the state for any, or all of the output pins +%% on a single bank at the same time, this is done by passing a list of pins numbers +%% in the pin tuple. +%% +%% For example, setting all of the even numbered pins to a `high' state, +%% and all of the odd numbered pins to a `low' state can be accomplished in two lines: +%% +%%
+%%    gpio:digital_write({c, [0,2,4,6,8,10,12,14]}, high}),
+%%    gpio:digital_write({c, [1,3,5,7,9,11,13,15]}, low}).
+%% 
+%% +%% To set the same state for all of the pins that have been previously configured as outputs +%% on a specific bank the atom `all' may be used, this will have no effect on any pins on the +%% same bank that have been configured as inputs, so it is safe to use with mixed direction +%% modes on a bank. +%% +%% Not supported on rp2040 (Pico), use `gpio:digital_write/2' instead. %% @end %%----------------------------------------------------------------------------- --spec set_level(GPIO :: gpio(), Pin :: pin(), Level :: level()) -> ok | error. +-spec set_level(GPIO :: gpio(), Pin :: pin(), Level :: level()) -> + ok | {error, Reason :: atom()} | error. set_level(GPIO, Pin, Level) -> port:call(GPIO, {set_level, Pin, Level}). @@ -163,31 +232,37 @@ set_level(GPIO, Pin, Level) -> %% @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 -%% @returns ok | error +%% @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. +%% 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()) -> ok | error. +-spec set_int(GPIO :: gpio(), Pin :: pin(), Trigger :: trigger()) -> + ok | {error, Reason :: atom()} | error. 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 remove the interrupt -%% @returns ok | error +%% @returns ok | error | {error, Reason} %% @doc Remove a GPIO interrupt %% %% Removes an interrupt from the specified pin. +%% +%% The rp2040 (Pico) port does not support gpio interrupts at this time. %% @end %%----------------------------------------------------------------------------- --spec remove_int(GPIO :: gpio(), Pin :: pin()) -> ok | error. +-spec remove_int(GPIO :: gpio(), Pin :: pin()) -> ok | {error, Reason :: atom()} | error. remove_int(GPIO, Pin) -> port:call(GPIO, {remove_int, Pin}). @@ -216,13 +291,26 @@ deinit(_Pin) -> %%----------------------------------------------------------------------------- %% @param Pin number to set operational mode %% @param Direction is `input', `output', or `output_od' -%% @returns ok | error +%% @returns ok | error | {error, Reason} %% @doc Set the operational mode of a pin %% %% Pins can be used for input, output, or output with open drain. +%% +%% The STM32 platform has extended direction mode configuration options. +%% See @type mode_config() for details. All configuration must be set using +%% `set_direction/3', including pull() mode, unlike the ESP32 which has a +%% separate function (`set_pin_pull/2'). If you are configuring multiple pins +%% on the same GPIO `bank' with the same options the pins may be configured all +%% at the same time by giving a list of pin numbers in the pin tuple. +%% Example to configure all of the leds on a Nucleo board: +%% +%%
+%%    gpio:set_direction({b, [0,7,14], output)
+%% 
%% @end %%----------------------------------------------------------------------------- --spec set_pin_mode(Pin :: pin(), Direction :: direction()) -> ok | error. +-spec set_pin_mode(Pin :: pin(), Direction :: direction()) -> + ok | {error, Reason :: atom()} | error. set_pin_mode(_Pin, _Mode) -> erlang:nif_error(undefined). @@ -234,6 +322,10 @@ set_pin_mode(_Pin, _Mode) -> %% %% Pins can be internally pulled `up', `down', `up_down' (pulled in %% both directions), or left `floating'. +%% +%% This function is not supported on STM32, the internal resistor must +%% be configured when setting the direction mode, see `set_direction/3' +%% or `set_pin_mode/2'. %% @end %%----------------------------------------------------------------------------- -spec set_pin_pull(Pin :: pin(), Pull :: pull()) -> ok | error. @@ -258,6 +350,8 @@ set_pin_pull(_Pin, _Pull) -> %% will resume the hold function when the chip wakes up from %% Deep-sleep. If the digital gpio also needs to be held during %% Deep-sleep `gpio:deep_sleep_hold_en' should also be called. +%% +%% This function is only supported on ESP32. %% @end %%----------------------------------------------------------------------------- -spec hold_en(Pin :: pin()) -> ok | error. @@ -278,6 +372,8 @@ hold_en(_Pin) -> %% low level(because gpio18 is input mode by default). If you don’t %% want this behavior, you should configure gpio18 as output mode and %% set it to hight level before calling `gpio:hold_dis'. +%% +%% This function is only supported on ESP32. %% @end %%----------------------------------------------------------------------------- -spec hold_dis(Pin :: pin()) -> ok | error. @@ -301,6 +397,8 @@ hold_dis(_Pin) -> %% Power down or call `gpio_hold_dis' will disable this function, %% otherwise, the digital gpio hold feature works as long as the chip %% enters Deep-sleep. +%% +%% This function is only supported on ESP32. %% @end %%----------------------------------------------------------------------------- -spec deep_sleep_hold_en() -> ok. @@ -310,6 +408,8 @@ deep_sleep_hold_en() -> %%----------------------------------------------------------------------------- %% @returns ok %% @doc Disable all gpio pad functions during Deep-sleep. +%% +%% This function is only supported on ESP32. %% @end %%----------------------------------------------------------------------------- -spec deep_sleep_hold_dis() -> ok. @@ -319,34 +419,49 @@ deep_sleep_hold_dis() -> %%----------------------------------------------------------------------------- %% @param Pin number of the pin to write %% @param Desired output level to set -%% @returns ok | error +%% @returns ok | error | {error, Reason} %% @doc Set GPIO digital output level %% %% Set a pin to `high' (1) or `low' (0). +%% +%% The STM32 is capable of setting the state for any, or all of the output pins +%% on a single bank at the same time, this is done by passing a list of pins numbers +%% in the pin tuple. For example, setting all of the even numbered pins to a `high' state, +%% and all of the odd numbered pins to a `low' state can be accomplished in two lines: +%% +%%
+%%    gpio:digital_write({c, [0,2,4,6,8,10,12,14]}, high}),
+%%    gpio:digital_write({c, [1,3,5,7,9,11,13,15]}, low}).
+%% 
+%% +%% To set the same state for all of the pins that have been previously configured as outputs +%% on a specific bank the atom `all' may be used, this will have no effect on any pins on the +%% same bank that have been configured as inputs, so it is safe to use with mixed direction +%% modes on a bank. %% @end %%----------------------------------------------------------------------------- --spec digital_write(Pin :: pin(), Level :: level()) -> ok | error. +-spec digital_write(Pin :: pin(), Level :: level()) -> ok | {error, Reason :: atom()} | error. digital_write(_Pin, _Level) -> erlang:nif_error(undefined). %%----------------------------------------------------------------------------- %% @param Pin number of the pin to read -%% @returns high | low +%% @returns high | low | error | {error, Reason} %% @doc Read the digital state of a GPIO pin %% %% Read if an input pin state is high or low. -%% Warning: if a is not previously configured as an input using +%% Warning: if the pin was not previously configured as an input using %% `gpio:set_pin_mode/2' it will always read as low. %% @end %%----------------------------------------------------------------------------- --spec digital_read(Pin :: pin()) -> high | low. +-spec digital_read(Pin :: pin()) -> high | low | {error, Reason :: atom()} | error. digital_read(_Pin) -> erlang:nif_error(undefined). %%----------------------------------------------------------------------------- %% @param Pin number of the pin to set the interrupt on %% @param Trigger is the state that will trigger an interrupt -%% @returns ok | error +%% @returns ok | error | {error, Reason} %% @doc Convenience function for `gpio:set_int/3' %% %% This is a convenience function for `gpio:set_int/3' that allows an @@ -357,15 +472,18 @@ digital_read(_Pin) -> %% used in an application. If multiple pins are being configured with %% interrupt triggers gpio:set_int/3 should be used otherwise there is %% a race condition when start() is called internally by this function. +%% +%% The rp2040 (Pico) port does not support gpio interrupts at this time. %% @end %%----------------------------------------------------------------------------- --spec attach_interrupt(Pin :: pin(), Trigger :: trigger()) -> ok | error. +-spec attach_interrupt(Pin :: pin(), Trigger :: trigger()) -> + ok | {error, Reason :: atom()} | error. attach_interrupt(Pin, Trigger) -> set_int(start(), Pin, Trigger). %----------------------------------------------------------------------------- %% @param Pin number of the pin to remove the interrupt -%% @returns ok | error +%% @returns ok | error | {error, Reason} %% @doc Convenience function for `gpio:remove_int/2' %% %% This is a convenience function for `gpio:remove_int/2' that allows an @@ -373,8 +491,10 @@ attach_interrupt(Pin, Trigger) -> %% %% Unlike `gpio:attach_interrupt/2' this function can be safely used %% regardless of the number of interrupt pins used in the application. +%% +%% The rp2040 (Pico) port does not support gpio interrupts at this time. %% @end %%----------------------------------------------------------------------------- --spec detach_interrupt(Pin :: pin()) -> ok | error. +-spec detach_interrupt(Pin :: pin()) -> ok | {error, Reason :: atom()} | error. detach_interrupt(Pin) -> remove_int(whereis(gpio), Pin).