1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00

Merge pull request #16787 from maribu/periph/gpio_ng

periph/gpio_ll: New Peripheral GPIO API
This commit is contained in:
benpicco 2022-04-22 12:27:54 +02:00 committed by GitHub
commit a427d630f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 2456 additions and 0 deletions

View File

@ -24,3 +24,6 @@ FEATURES_PROVIDED += cpu_$(CPU)
# Features that are conflicting for all architectures
FEATURES_CONFLICT += picolibc:newlib
FEATURES_CONFLICT_MSG += "Only one standard C library can be used"
FEATURES_CONFLICT += periph_gpio_irq:periph_gpio_ll_irq
FEATURES_CONFLICT_MSG += "Only one GPIO IRQ implementation can be used"

View File

@ -0,0 +1,739 @@
/*
* Copyright (C) 2020 Gunar Schorcht
* 2021 Otto-von-Guericke-Universität Magdeburg
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @defgroup drivers_periph_gpio_ll GPIO Low-Level API
* @ingroup drivers_periph
* @brief Peripheral GPIO Low-Level API
*
* @warning This API is not stable yet and intended for internal use only
* as of now.
*
* # Design Goals
*
* This API aims to provide low-level access to GPIOs with as little
* abstraction and overhead in place as possible for the hot code paths, while
* providing a relatively high-level and feature complete API for the
* configuration of GPIO pins. The former is to enable sophisticated use cases
* such at bit-banging parallel protocols, bit-banging at high data rates,
* bit-banging with strict timing requirements, or any combination of these.
* The latter is to expose as much of the features the (arguably) most
* important peripheral of the MCU as possible.
*
* It is possible to implement the high level pin-based GPIO API of RIOT, @ref
* drivers_periph_gpio, on top of this API. It is expected that for many use
* cases the high level API will still remain the API of choice, since it is
* more concise an easier to use.
*
* Note that this API is likely to be faster moving than the high level GPIO
* API, as it intents to match the hardware features more closely. Hence, new
* support for new MCUs are more likely to result in API changes here. This is
* another reason why only users interested in the low level access to GPIOs
* should be using the this low level API, while the high level API will cater
* all the LED blinking and button sensing use cases with a more convenient
* and more very stable interface.
*
* # Thread Safety
*
* All functions provided by this API have to be implemented in a thread-safe
* manner, except for @ref gpio_ll_prepare_write and
* @ref gpio_ll_prepare_write_all_outputs. If the read-modify-write operations
* @ref gpio_ll_set, @ref gpio_ll_clear, and @ref gpio_ll_toggle can be done
* atomically in hardware with predictable timing, this must be used in the
* implementation. Otherwise IRQs have to be disabled during read-modify-write
* sequences. Calls to @ref gpio_ll_write are inherently thread-safe and
* lock-less, but sharing pins on the same port between threads is still
* requires a lock between the calls to @ref gpio_ll_prepare_write and
* @ref gpio_ll_write in the general case.
*
* Under no circumstances two threads may call @ref gpio_ll_init on the same
* port / pin combination concurrently.
*
* @{
* @file
* @brief Peripheral GPIO Low-Level API
*
* @author Gunar Schorcht <gunar@schorcht.net>
* @author Marian Buschsieweke <marian.buschsieweke@ovgu.de>
*
* @warning This API is not stable yet and intended for internal use only as
* of now.
*/
#ifndef PERIPH_GPIO_LL_H
#define PERIPH_GPIO_LL_H
#include <inttypes.h>
#include <stdbool.h>
#include <stdint.h>
#include "architecture.h"
#include "periph_cpu.h"
#include "periph/gpio.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief GPIO port type
*/
typedef uintptr_t gpio_port_t;
#if !defined(GPIO_PORT_UNDEF) || defined(DOXYGEN)
/**
* @brief Magic "undefined GPIO port" value
*/
#define GPIO_PORT_UNDEF UINTPTR_MAX
#endif
#ifdef DOXYGEN
/**
* @brief Get the @ref gpio_port_t value of the port identified by @p num
*
* @note If @p num is a compile time constant, this is guaranteed to be
* suitable for a constant initializer.
*
* Typically this will be something like `(GPIO_BASE_ADDR + num * sizeof(struct
* vendor_gpio_reg))`
*/
#define GPIO_PORT(num) implementation_specific
#endif
#ifdef DOXYGEN
/**
* @brief Get the number of the GPIO port belonging to the given @ref
* gpio_port_t value
*
* @note If @p port is a compile time constant, this is guaranteed to be
* suitable for a constant initializer.
*
* @pre @p port is the return value of @ref GPIO_PORT
*
* For every supported port number *n* the following `assert()` must not blow
* up:
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c}
* assert(n == GPIO_PORT_NUM(GPIO_PORT(n)));
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
#define GPIO_PORT_NUM(port) implementation_specific
#endif
#if !defined(HAVE_GPIO_STATE_T) || defined(DOXYGEN)
/**
* @brief Enumeration of GPIO states (direction)
*/
typedef enum {
/**
* @brief Use pin as output in push-pull configuration
*
* | Logical Value | Electrical Behavior |
* |:-------------- |:--------------------------------- |
* | `0` | Low |
* | `1` | High |
*/
GPIO_OUTPUT_PUSH_PULL,
/**
* @brief Use pin as output in open collector configuration
*
* | Logical Value | Electrical Behavior |
* |:-------------- |:--------------------------------- |
* | `0` | Low |
* | `1` | High Impedance (Disconnected) |
*/
GPIO_OUTPUT_OPEN_DRAIN,
/**
* @brief Use pin as output in open emitter configuration
*
* | Logical Value | Electrical Behavior |
* |:-------------- |:--------------------------------- |
* | `0` | High Impedance (Disconnected) |
* | `1` | High |
*/
GPIO_OUTPUT_OPEN_SOURCE,
GPIO_INPUT, /**< Use pin as input */
/**
* @brief The GPIO pin is used by a peripheral
*
* Note that calling @ref gpio_ll_init with this state is implementation
* defined behavior, as implementation specific additional details
* (such as which peripheral to connect the pin to) are needed. An
* implementation may choose to not support this at all.
*
* However, it is *strongly* encouraged that @ref gpio_ll_query_conf uses
* this state to indicate a GPIO pin is currently used by a peripheral (e.g.
* as ADC input, I2C data/clock pin, etc.).
*/
GPIO_USED_BY_PERIPHERAL,
/**
* @brief Disconnect pin from all peripherals
*
* The implementation should aim to reduce power consumption of the pin
* when this state is entered, if this is feasible.
*
* @note Pull resistors can still be requested in this mode. This can be
* useful e.g. for keeping an UART TXD pin from emitting noise
* while the UART peripheral is powered off. But not every
* implementation will support this.
*
* @details Once all GPIOs of a GPIO port are disconnected, the
* implementation is allowed to power off the whole GPIO port again
* to conserve power.
*/
GPIO_DISCONNECT,
} gpio_state_t;
#endif
#if !defined(HAVE_GPIO_PULL_T) || defined(DOXYGEN)
/**
* @brief Enumeration of pull resistor configurations
*/
typedef enum {
GPIO_FLOATING, /**< No pull ups nor pull downs enabled */
GPIO_PULL_UP, /**< Pull up resistor enabled */
GPIO_PULL_DOWN, /**< Pull down resistor enabled */
GPIO_PULL_KEEP, /**< Keep the signal at current logic level with pull
up/down resistors */
} gpio_pull_t;
#endif
#if !defined(HAVE_GPIO_PULL_STRENGTH_T) || defined(DOXYGEN)
/**
* @brief Enumeration of pull resistor values
*
* @note Depending on the implementation, some (or even all!) constants can
* have the same numeric value if less than four pull resistors per
* direction are provided. For obvious reasons, only neighboring values
* are allowed to have the same numeric value.
*/
typedef enum {
GPIO_PULL_WEAKEST, /**< Use the weakest (highest Ohm value) resistor */
GPIO_PULL_WEAK, /**< Use a weak pull resistor */
GPIO_PULL_STRONG, /**< Use a strong pull resistor */
GPIO_PULL_STRONGEST, /**< Use the strongest pull resistor */
} gpio_pull_strength_t;
#endif
/**
* @brief The number of distinct supported pull resistor strengths
*
* This equals the number of pull resistor strengths actually supported and can
* be less than four, if one or more enumeration values in @ref
* gpio_pull_strength_t have the same numeric value. Note that: a) some pins
* might have more options than others and b) it could be possible that there
* are e.g. two pull up resistors to pick from, but only one pull down
* resistor.
*/
#define GPIO_PULL_NUMOF (1U + (GPIO_PULL_WEAKEST != GPIO_PULL_WEAK) \
+ (GPIO_PULL_WEAK != GPIO_PULL_STRONG) \
+ (GPIO_PULL_STRONG != GPIO_PULL_STRONGEST))
#if !defined(HAVE_GPIO_DRIVE_STRENGTH_T) || defined(DOXYGEN)
/**
* @brief Enumeration of drive strength options
*
* @note Depending on the implementation, some (or even all!) constants can
* have the same numeric value if less than four drive strength options
* to pick from. For obvious reasons, only neighboring values are
* allowed to have the same numeric value.
*/
typedef enum {
GPIO_DRIVE_WEAKEST, /**< Use the weakest drive strength */
GPIO_DRIVE_WEAK, /**< Use a weak drive strength */
GPIO_DRIVE_STRONG, /**< Use a strong drive strength */
GPIO_DRIVE_STRONGEST, /**< Use the strongest drive strength */
} gpio_drive_strength_t;
#endif
/**
* @brief The number of distinct supported drive strengths
*
* This equals the number of drive strengths actually supported and can be less
* than four, if one or more enumeration values in @ref gpio_drive_strength_t
* have the same numeric value. Note that some pins might have more options
* than others.
*/
#define GPIO_DRIVE_NUMOF (1U + (GPIO_DRIVE_WEAKEST != GPIO_DRIVE_WEAK) \
+ (GPIO_DRIVE_WEAK != GPIO_DRIVE_STRONG) \
+ (GPIO_DRIVE_STRONG != GPIO_DRIVE_STRONGEST))
#if !defined(HAVE_GPIO_SLEW_T) || defined(DOXYGEN)
/**
* @brief Enumeration of slew rate settings
*
* Reducing the slew rate can be useful to limit the high frequency noise
* emitted by a GPIO pin. On the other hand, a high frequency signal cannot be
* generated if the slew rate is too slow.
*
* @warning The numeric values are implementation defined and multiple
* constants can have the same numeric value, if an implementation
* supports fewer slew rates. An implementation only supporting a
* single slew rate can have all constants set to a value of zero.
*/
typedef enum {
GPIO_SLEW_SLOWEST, /**< let the output voltage level rise/fall as slow as
possible */
GPIO_SLEW_SLOW, /**< let the output voltage level rise/fall slowly */
GPIO_SLEW_FAST, /**< let the output voltage level rise/fall fast */
GPIO_SLEW_FASTEST, /**< let the output voltage level rise/fall as fast as
possible */
} gpio_slew_t;
#endif
/**
* @brief The number of distinct supported slew rates
*
* This equals the number of slew rates actually supported and can be less than
* four, if one or more enumeration values in @ref gpio_drive_strength_t have
* the same numeric value. Note that some pins might have more options than
* others.
*/
#define GPIO_SLEW_NUMOF (1U + (GPIO_SLEW_SLOWEST != GPIO_SLEW_SLOW) \
+ (GPIO_SLEW_SLOW != GPIO_SLEW_FAST) \
+ (GPIO_SLEW_FAST != GPIO_SLEW_FASTEST))
#if !defined(HAVE_GPIO_CONF_T) || defined(DOXYGEN)
/**
* @brief GPIO pin configuration
*
* @warning The layout of this structure is implementation dependent and
* additional implementation specific fields might be present. For this
* reason, this structure must be initialized using designated
* initializers or zeroing out the whole contents using `memset()
* before initializing the individual fields.
*
* It is fully valid that an implementation extends this structure with
* additional implementation specific fields. For example, it could be useful
* to also include fields to configure routing of a GPIO pin to other
* peripherals (e.g. for us as an TXD pin of an UART). These implementation
* specific fields **MUST** however have reasonable defaults when initialized
* with zero (e.g. pin is not routed to another peripheral but to be used as
* regular GPIO). For obvious reasons, portable code cannot rely on the
* presence and semantic of any implementation specific fields. Additionally,
* out-of-tree users should not use these fields, as the implementation
* specific fields cannot be considered a stable API.
*/
typedef struct {
gpio_state_t state; /**< State of the pin */
gpio_pull_t pull; /**< Pull resistor configuration */
/**
* @brief Configure the slew rate of outputs
*
* @warning If the requested slew rate is not available, the closest fit
* supported will be configured instead.
*
* This value is ignored *unless* @ref gpio_conf_t::state is configured
* to @ref GPIO_OUTPUT_PUSH_PULL or @ref GPIO_OUTPUT_OPEN_DRAIN.
*/
gpio_slew_t slew_rate;
/**
* @brief Whether to enable the input Schmitt trigger
*
* @warning If the requested Schmitt trigger setting is not available, it
* will be ignored.
*
* This value is ignored *unless* @ref gpio_conf_t::state is configured
* to @ref GPIO_INPUT.
*/
bool schmitt_trigger;
/**
* @brief Initial value of the output
*
* Ignored if @ref gpio_conf_t::state is set to @ref GPIO_INPUT or
* @ref GPIO_DISCONNECT. If the pin was previously in a high impedance
* state, it is guaranteed to directly transition to the given initial
* value.
*
* @ref gpio_ll_query_conf will write the current value of the specified
* pin here, which is read from the input register when the state is
* @ref GPIO_INPUT, otherwise the state from the output register is
* consulted.
*/
bool initial_value;
/**
* @brief Strength of the pull up/down resistor
*
* @warning If the requested pull strength is not available, the closest fit
* supported will be configured instead.
*
* This value is ignored when @ref gpio_conf_t::pull is configured to
* @ref GPIO_FLOATING.
*/
gpio_pull_strength_t pull_strength;
/**
* @brief Drive strength of the GPIO
*
* @warning If the requested drive strength is not available, the closest
* fit supported will be configured instead.
*
* This value is ignored when @ref gpio_conf_t::state is configured to
* @ref GPIO_INPUT or @ref GPIO_DISCONNECT.
*/
gpio_drive_strength_t drive_strength;
} gpio_conf_t;
#endif
/**
* @brief A standard configuration for a generic floating input pin
*/
extern const gpio_conf_t gpio_ll_in;
/**
* @brief A standard configuration for a generic input pin with pull down
* resistor
*/
extern const gpio_conf_t gpio_ll_in_pd;
/**
* @brief A standard configuration for a generic input pin with pull up
* resistor
*/
extern const gpio_conf_t gpio_ll_in_pu;
/**
* @brief A standard configuration for a generic input pin with pull
* resistor to keep signal at bus level
*
* This means, when the input reaches a 0, a pull down resistor is applied. If
* input reaches 1, a pull up is applied instead.
*/
extern const gpio_conf_t gpio_ll_in_pk;
/**
* @brief A standard configuration for a generic push-pull output pin
*
* @note The pin will have an initial value of 0.
*/
extern const gpio_conf_t gpio_ll_out;
/**
* @brief A standard configuration for a generic floating open drain output
*
* @note The pin will have an initial value of 1 (which in absence of an
* external pull up resistor will be high impedance).
*/
extern const gpio_conf_t gpio_ll_od;
/**
* @brief A standard configuration for a generic open drain output with pull
* up
*
* @note The pin will have an initial value of 1 (so that the pull up will
* pill the line high).
*/
extern const gpio_conf_t gpio_ll_od_pu;
/**
* @brief Check if the given number is a valid argument for @ref GPIO_PORT
*
* @param[in] num port number to check
* @retval true the MCU used has a GPIO port with that number
* @retval false the MCU used has ***NO*** GPIO port with that number
*/
static inline bool is_gpio_port_num_valid(uint_fast8_t num);
/**
* @brief Initialize the given GPIO pin as specified
*
* @param[in] port port the pin to initialize belongs to
* @param[in] pin number of the pin to initialize
* @param[in] conf configuration to apply
*
* @retval 0 success
* @retval -ENOTSUP GPIO state or pull resistor configuration not supported
*
* @warning If the configuration of the Schmitt trigger, the drive strength, or
* the pull resistor strength are not supported, the closest supported
* value will be chosen instead and `0` is returned.
* @warning Note that hardware GPIO peripherals may have shared building
* blocks. Those *SHOULD* be handed out by the implementation in
* first-come-fist-served fashion. (E.g. if there is only one pull up
* resistor per port that can be connected to any pin of that port,
* typically the first pin on the port configured as pull up will
* succeed and subsequent configuration as pull ups for other pins on
* that port will get an `-ENOTSUP`.) For that reason, an application
* might need to optimize the order in which it configures GPIO pins
* to get the most suitable overall configuration supported by the
* hardware.
* @note An application having strict requirements can use
* @ref gpio_ll_query_conf afterwards to verify that the used
* configuration is indeed within spec. It is often sensible to omit
* these checks if `DEVELHELP` is disabled. An application can rely on
* that the configuration of pin @p pin at port @p port will not be
* changed as side-effect of operations performed on other pins. That
* is, once @ref gpio_ll_init returns the configuration details are
* settled and may only change due to subsequent calls to
* @ref gpio_ll_init with the same values for @p port and @p pin.
* @pre No concurrent context is calling this function for the same
* combination of @p port and @p pin - concurrent initialization of
* different pins on the same port is supported. The underlying
* implementation might perform locking where needed.
*/
int gpio_ll_init(gpio_port_t port, uint8_t pin, const gpio_conf_t *conf);
/**
* @brief Retrieve the current configuration of a GPIO pin
*
* @param[out] dest Write the current config of the given GPIO here
* @param[in] port GPIO port the pin to query is located at
* @param[in] pin Number of the pin to query within @p port
*
* @pre @p port and @p pin refer to an existing GPIO pin and @p dest can
* be written to. Expect blowing assertions otherwise.
*
* @note @ref gpio_conf_t::initial_value should be set to the current value
* of the pin, so that no shadow log of the initial value is needed to
* consult.
*/
void gpio_ll_query_conf(gpio_conf_t *dest, gpio_port_t port, uint8_t pin);
/**
* @brief INTERNAL, use @ref gpio_ll_print_conf instead
*
* This function prints the public API part of @ref gpio_conf_t to stdio. The
* intention is that implementations that extend @ref gpio_conf_t to contain
* more members overwrite @ref gpio_ll_print_conf and call this function to
* print the common members
*/
void gpio_ll_print_conf_common(const gpio_conf_t *conf);
/**
* @brief Utility function to print a given GPIO configuration to stdio
* @param[in] conf Configuration to print
*/
void gpio_ll_print_conf(const gpio_conf_t *conf);
/**
* @brief Get the current input value of all GPIO pins of the given port as
* bitmask
*
* @param[in] port port to read
*
* @return The current value of the input register of the given
* GPIO port
*
* @note The value of unconfigured pins and pins configured as @ref
* GPIO_DISCONNECT or @ref GPIO_OUTPUT_PUSH_PULL is implementation
* defined.
* @details Unless technically impossible, this must be implemented as a single
* read instruction.
*/
static inline uword_t gpio_ll_read(gpio_port_t port);
/**
* @brief Get the current output value of all GPIO pins of the given port as
* bitmask
*
* @param[in] port port to read
*
* @return The current value of the output register of the given
* GPIO port
*
* @note The value of unconfigured pins and pins configured as @ref
* GPIO_INPUT or @ref GPIO_OUTPUT_PUSH_PULL is implementation
* defined.
* @details Unless technically impossible, this must be implemented as a single
* read instruction.
*/
static inline uword_t gpio_ll_read_output(gpio_port_t port);
/**
* @brief Perform an `reg |= mask` operation on the I/O register of the port
*
* @note The behavior regarding pins not configured as
* @ref GPIO_OUTPUT_PUSH_PULL or as
* @ref GPIO_OUTPUT_OPEN_DRAIN is implementation defined.
* @warning Portable code must set the bits in @p mask that do not correspond to
* pins configured as output to zero.
* @details On hardware that supports implementing this as a single write
* instruction, this must be implemented as such. Otherwise this
* read-modify-write will disable IRQs to still behave atomically.
*
* @param[in] port port to modify
* @param[in] mask bitmask containing the pins to set
*/
static inline void gpio_ll_set(gpio_port_t port, uword_t mask);
/**
* @brief Perform an `reg &= ~mask` operation on the I/O register of the port
*
* @note The behavior regarding pins not configured as
* @ref GPIO_OUTPUT_PUSH_PULL or as
* @ref GPIO_OUTPUT_OPEN_DRAIN is implementation defined.
* @warning Portable code must set the bits in @p mask that do not correspond to
* pins configured as output to zero.
* @details On hardware that supports implementing this as a single write
* instruction, this must be implemented as such. Otherwise this
* read-modify-write will disable IRQs to still behave atomically.
*
* @param[in] port port to modify
* @param[in] mask bitmask containing the pins to clear
*/
static inline void gpio_ll_clear(gpio_port_t port, uword_t mask);
/**
* @brief Perform an `reg ^= mask` operation on the I/O register of the port
*
* @note The behavior regarding pins not configured as
* @ref GPIO_OUTPUT_PUSH_PULL or as
* @ref GPIO_OUTPUT_OPEN_DRAIN is implementation defined.
* @warning Portable code must set the bits in @p mask that do not correspond to
* pins configured as output to zero.
* @details On hardware that supports implementing this as a single write
* instruction, this must be implemented as such. Otherwise this
* read-modify-write will disable IRQs to still behave atomically.
*
* @param[in] port port to modify
* @param[in] mask bitmask containing the pins to toggle
*/
static inline void gpio_ll_toggle(gpio_port_t port, uword_t mask);
#if defined(DOXYGEN) || !defined(HAVE_GPIO_LL_PREPARE_WRITE_ALL_PINS)
/**
* @brief Same as `gpio_ll_prepare_write(port, UWORD_MAX, value)`, but
* faster
* @param[in] port port that should be written to
* @param[in] value value to write to port
* @return Value to call @ref gpio_ll_write with
*
* @details On most platforms this function will just pass @p value through
* unmodified, becoming a no-op (and costing neither CPU cycles,
* nor RAM, nor ROM). Hence, this function will only cost you
* if your platform really requires preparation of any sorts.
*
* @note If all pins on @p port are known to be configured as output,
* calls to this functions can be omitted.
* @details The caller needs to make sure that no concurrent changes
* (this includes configuration changes, writing, clearing, setting
* or toggling GPIO pins) are performed.
*
* This function can be used to prevent side-effects on non-output pins of a
* port when writing to it, e.g. when the output buffer is multiplexed with
* the pull configuration for input pins (such as on ATmega MCUs).
*/
static inline uword_t gpio_ll_prepare_write_all_outputs(gpio_port_t port,
uword_t value)
{
(void)port;
return value;
}
#endif
#if defined(DOXYGEN) || !defined(HAVE_GPIO_LL_PREPARE_WRITE)
/**
* @brief Helper to use @ref gpio_ll_write side-effect free
* @param[in] port port that should be written to
* @param[in] mask bitmask of the pins to write to
* @param[in] value value to write to port
* @return Value to call @ref gpio_ll_write with
*
* @details The caller needs to make sure that no concurrent changes
* (this includes configuration changes, writing, clearing, setting
* or toggling GPIO pins) are performed.
*
* See @ref gpio_ll_write on how to use this function
*/
static inline uword_t gpio_ll_prepare_write(gpio_port_t port, uword_t mask,
uword_t value)
{
return value | (gpio_ll_read_output(port) & (~mask));
}
#endif
/**
* @brief Perform a masked write operation on the I/O register of the port
*
* Some platforms multiplex the "write" I/O register with additional
* functions e.g. for input pin configuration. To prevent unintentional
* side effects prepare a value using @ref gpio_ll_prepare_write that
* will set the bits of non-output pins as needed to not have side
* effects on the state the GPIO port had when calling
* @ref gpio_ll_prepare_write .
*
* @param[in] port port to modify
* @param[in] state Opaque value produced by @ref gpio_ll_prepare_write
*
* Usage:
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c}
* // emit a square wave from two pins with a phase shift of pi between the
* // waves, e.g.:
* // pin1: _⎍_⎍_⎍_⎍_⎍_⎍_⎍_⎍_⎍
* // pin2: ⎍_⎍_⎍_⎍_⎍_⎍_⎍_⎍_⎍_
* void square_wave(gpio_port_t port, uint8_t pin1, uint8_t pin2)
* {
* uword_t mask = (1U << pin1) | (1U << pin2);
* uword_t state1 = gpio_ll_prepare_write(port, mask, 1U << pin1);
* uword_t state2 = gpio_ll_prepare_write(port, mask, 1U << pin2);
* while (1) {
* gpio_ll_write(port, state1);
* gpio_ll_write(port, state2);
* }
* }
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* @note If the configuration of the used port changes between the calls
* of @ref gpio_ll_prepare_write and @ref gpio_ll_write the write will
* still have undesired side effects on the configuration of input pins
* if the platform multiplexes the write register with configuration
* for input pins. It is expected that the user of the API either
* exclusively uses a GPIO port or synchronizes with other users to
* update the prepared value on configuration changes.
* @details Unless technically impossible, this must be implemented as a single
* write instruction.
*/
static inline void gpio_ll_write(gpio_port_t port, uword_t state);
/**
* @brief Extract the `gpio_port_t` from a `gpio_t`
*/
static inline gpio_port_t gpio_get_port(gpio_t pin);
/**
* @brief Extract the pin number from a `gpio_t`
*/
static inline uint8_t gpio_get_pin_num(gpio_t pin);
/**
* @brief Pack a pointer into a @ref gpio_port_t
*
* @pre The address in @p addr is not `NULL` ***and*** points to a regular
* memory region (e.g. anywhere in `.data`, `.rodata`, `.bss` or an
* address returned by `malloc()` and friends)
*
* @return A value that is distinguishable from any valid port reference and
* from which @p addr can be reconstructed by calling
* @ref gpio_port_unpack_addr
*/
static inline gpio_port_t gpio_port_pack_addr(void *addr);
/**
* @brief Extract a data pointer that was packed by @ref gpio_port_pack_addr
*
* @return The exact address previously packed by @ref gpio_port_pack_addr
* @retval NULL @p port is a valid GPIO port
*
* The motivation is that a high level API can multiplex peripheral GPIOs and
* GPIOs provided by port extenders. That high level API could pack pointers to
* the device descriptors of port extenders into a @ref gpio_port_t and use this
* function to check if the port is a peripheral port (the return value is
* `NULL`), or retrieve the device descriptor.
*/
static inline void * gpio_port_unpack_addr(gpio_port_t port);
#ifdef __cplusplus
}
#endif
/* the hardware specific implementation relies on the types such as gpio_port_t
* to be provided */
#include "gpio_ll_arch.h"
#endif /* PERIPH_GPIO_LL_H */
/** @} */

View File

@ -0,0 +1,170 @@
/*
* Copyright (C) 2020 Gunar Schorcht
* 2021 Otto-von-Guericke-Universität Magdeburg
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @defgroup drivers_periph_gpio_ll_irq IRQ Support in Peripheral GPIO Low-Level API
* @ingroup drivers_periph_gpio_ll
* @brief IRQ Support in Peripheral GPIO Low-Level API
*
* @warning This API is not stable yet and intended for internal use only
* as of now.
* @note This API can only be used, if the features `periph_gpio_ll_irq`
* is used. Not every MCU will implement the features required for
* this API or will have a suitable driver
* available.
*
* # Motivation
*
* The IRQ related GPIO Low-Level API is very similar to the one of @ref
* drivers_periph_gpio - however two aspects have been changed (in addition to
* using separate port and pin parameters): First, level triggered IRQs that
* many MCUs support are now exposed. (Note: They can also be trivially be
* emulated, see the STM32 implementation as example.) Second, the
* configuration of the GPIO pin and the associated IRQ have been split into
* two functions. This allows reconfiguring the trigger of an IRQ with less
* overhead. This can result in reconfiguring the IRQ trigger being less effort
* than filtering out unneeded events and hence consume power due to longer
* power saving phases.
*
* @{
* @file
* @brief IRQ Support in Peripheral GPIO Low-Level API
*
* @author Marian Buschsieweke <marian.buschsieweke@ovgu.de>
*
* @warning This API is not stable yet and intended for internal use only
* as of now.
*/
#ifndef PERIPH_GPIO_LL_IRQ_H
#define PERIPH_GPIO_LL_IRQ_H
#include <inttypes.h>
#include <stdbool.h>
#include <stdint.h>
#include "periph/gpio_ll.h"
#ifdef __cplusplus
extern "C" {
#endif
#if !defined(HAVE_GPIO_IRQ_TRIG_T) || defined(DOXYGEN)
/**
* @brief Definition of possible IRQ triggers
*/
typedef enum {
GPIO_TRIGGER_EDGE_FALLING, /**< edge triggered IRQ on falling flanks only
*/
GPIO_TRIGGER_EDGE_RISING, /**< edge triggered IRQ on rising flanks only */
GPIO_TRIGGER_EDGE_BOTH, /**< edge triggered IRQ on falling *AND* rising
flanks */
GPIO_TRIGGER_LEVEL_HIGH, /**< level triggered IRQ on high input */
GPIO_TRIGGER_LEVEL_LOW /**< level triggered IRQ on low input */
} gpio_irq_trig_t;
#endif
/**
* @brief Signature of event callback function that is called on IRQs
* @param[in] arg optional context for the callback
* @warning This function is called in IRQ context
*/
typedef void (*gpio_ll_cb_t)(void *arg);
/**
* @brief Set up an IRQ for the given GPIO pin and activate it
*
* @param[in] port port the pin belongs to
* @param[in] pin number of the pin to setup IRQs on
* @param[in] trig define what triggers the IRQ
* @param[in] cb callback that is called from interrupt context
* @param[in] arg argument to be passed to the callback
*
* @pre The given GPIO pin has been initialized using
* @ref gpio_ll_init prior to this call
* @pre The trigger in @p trig is supported by the MCU
*
* @retval 0 Success
* @retval -ENOTSUP No external IRQs supported for @p port and @p pin
* @retval -EBUSY All hardware entities that monitor GPIOs and issue IRQs
* are busy monitoring other GPIOs. Call
* @ref gpio_ll_irq_off on one of those and retry.
*/
int gpio_ll_irq(gpio_port_t port, uint8_t pin, gpio_irq_trig_t trig,
gpio_ll_cb_t cb, void *arg);
/**
* @brief Mask IRQs on the given GPIO pin
*
* @param[in] port port the pin belongs to
* @param[in] pin number of the pin to disable IRQs on
*
* @note IRQs can be re-enabled without reconfiguration using
* @ref gpio_ll_irq_unmask
*
* If IRQs are not needed for a longer term, consider using @ref
* gpio_ll_irq_off instead.
*/
void gpio_ll_irq_mask(gpio_port_t port, uint8_t pin);
#if MODULE_PERIPH_GPIO_LL_IRQ_UNMASK || defined(DOXYGEN)
/**
* @brief Unmask IRQs on the given GPIO pin
*
* Same as @ref gpio_ll_irq_unmask_and_clear except that IRQs that came in during
*
* @warning On same MCUs (most notably STM32) this is impossible to implement.
* The feature `periph_gpio_ll_irq_unmask` is provided, if this
* function is available.
*
* @ref gpio_ll_irq_unmask_and_clear is a portable alternative that might suit your
* use case equally well.
*
* @param[in] port port the pin belongs to
* @param[in] pin number of the pin to unmask IRQs on
*/
void gpio_ll_irq_unmask(gpio_port_t port, uint8_t pin);
#endif
/**
* @brief Unmask IRQs on the given GPIO pin and clear pending IRQs
*
* @pre IRQs on the given pin have been previously configured via
* @ref gpio_ll_irq
* @post IRQs will be acted upon again from now on. If one or more IRQs came
* in while masked, this IRQs are silently lost.
*
* @param[in] port port the pin belongs to
* @param[in] pin number of the pin to unmask IRQs on
*/
void gpio_ll_irq_unmask_and_clear(gpio_port_t port, uint8_t pin);
/**
* @brief Disable IRQs on the given GPIO pin
*
* @post Signals on the specified pin will no longer triggers IRQs until an
* IRQ is reconfigured using @ref gpio_ll_irq
*
* @param[in] port port the pin belongs to
* @param[in] pin number of the pin disable IRQs on
*
* @note The implementation should consume power by disabling trigger
* detection on the given pin.
*
* Unlike @ref gpio_ll_irq_mask IRQs cannot be re-enabled with a call to
* @ref gpio_ll_irq_unmask.
*/
void gpio_ll_irq_off(gpio_port_t port, uint8_t pin);
#ifdef __cplusplus
}
#endif
#endif /* PERIPH_GPIO_LL_IRQ_H */
/** @} */

View File

@ -0,0 +1,2 @@
# Always use periph_gpio_irq_unmask, if available
FEATURES_OPTIONAL += periph_gpio_ll_irq_unmask

View File

@ -0,0 +1,187 @@
/*
* Copyright (C) 2022 Otto-von-Guericke-Universität Magdeburg
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#include "periph/gpio_ll.h"
/* Optimizing for low stack usage by not using printf(), which on newlib is
* prohibitively costly. This will allow developers to use this for debugging
* even in ISR - hopefully without increasing the ISR stack size.
*
* If module fmt is used, there is a stack friendly print_str() provided.
* Otherwise, fall back to fputs(), which is still way more stack friendly than
* printf().
*/
#ifdef MODULE_FMT
#include "fmt.h"
#else
static inline void print_str(const char *str)
{
fputs(str, stdout);
}
#endif
const gpio_conf_t gpio_ll_in = {
.state = GPIO_INPUT,
.pull = GPIO_FLOATING,
.schmitt_trigger = true,
};
const gpio_conf_t gpio_ll_in_pd = {
.state = GPIO_INPUT,
.pull = GPIO_PULL_DOWN,
.schmitt_trigger = true,
};
const gpio_conf_t gpio_ll_in_pu = {
.state = GPIO_INPUT,
.pull = GPIO_PULL_UP,
.schmitt_trigger = true,
};
const gpio_conf_t gpio_ll_in_pk = {
.state = GPIO_INPUT,
.pull = GPIO_PULL_KEEP,
.schmitt_trigger = true,
};
const gpio_conf_t gpio_ll_out = {
.state = GPIO_OUTPUT_PUSH_PULL,
.drive_strength = GPIO_DRIVE_STRONGEST,
.slew_rate = GPIO_SLEW_FASTEST,
.initial_value = false,
};
const gpio_conf_t gpio_ll_pd = {
.state = GPIO_OUTPUT_OPEN_DRAIN,
.pull = GPIO_FLOATING,
.drive_strength = GPIO_DRIVE_STRONGEST,
.slew_rate = GPIO_SLEW_FASTEST,
.initial_value = true,
};
const gpio_conf_t gpio_ll_pd_pu = {
.state = GPIO_OUTPUT_OPEN_DRAIN,
.pull = GPIO_PULL_UP,
.drive_strength = GPIO_DRIVE_STRONGEST,
.slew_rate = GPIO_SLEW_FASTEST,
.initial_value = true,
};
void gpio_ll_print_conf_common(const gpio_conf_t *conf)
{
assert(conf);
const char *off_on[] = { "off", "on" };
print_str("state: ");
switch (conf->state) {
case GPIO_OUTPUT_PUSH_PULL:
print_str("out-pp");
break;
case GPIO_OUTPUT_OPEN_DRAIN:
print_str("out-od");
break;
case GPIO_OUTPUT_OPEN_SOURCE:
print_str("out-os");
break;
case GPIO_INPUT:
print_str("in");
break;
case GPIO_USED_BY_PERIPHERAL:
print_str("periph");
break;
case GPIO_DISCONNECT:
print_str("off");
break;
}
if (conf->state != GPIO_INPUT) {
if (GPIO_DRIVE_NUMOF > 1) {
print_str(", drive: ");
if (conf->drive_strength == GPIO_DRIVE_WEAK) {
print_str("weak");
}
else if (conf->drive_strength == GPIO_DRIVE_WEAKEST) {
print_str("weakest");
}
else if (conf->drive_strength == GPIO_DRIVE_STRONG) {
print_str("strong");
}
else {
print_str("strongest");
}
}
if (GPIO_SLEW_NUMOF > 1) {
print_str(", slew: ");
if (conf->slew_rate == GPIO_SLEW_SLOW) {
print_str("slow");
}
else if (conf->slew_rate == GPIO_SLEW_SLOWEST) {
print_str("slowest");
}
else if (conf->slew_rate == GPIO_SLEW_FAST) {
print_str("fast");
}
else {
print_str("fastest");
}
}
}
if (conf->state != GPIO_OUTPUT_PUSH_PULL) {
print_str(", pull: ");
switch (conf->pull) {
default:
case GPIO_FLOATING:
print_str("none");
break;
case GPIO_PULL_UP:
print_str("up");
break;
case GPIO_PULL_DOWN:
print_str("down");
break;
case GPIO_PULL_KEEP:
print_str("keep");
break;
}
if ((conf->pull != GPIO_FLOATING) && (GPIO_PULL_NUMOF > 1)) {
print_str(" (");
if (conf->pull_strength == GPIO_PULL_WEAK) {
print_str("weak");
}
else if (conf->pull_strength == GPIO_PULL_WEAKEST) {
print_str("weakest");
}
else if (conf->pull_strength == GPIO_PULL_STRONG) {
print_str("strong");
}
else {
print_str("strongest");
}
print_str(")");
}
print_str(", schmitt trigger: ");
print_str(off_on[conf->schmitt_trigger]);
}
print_str(", value: ");
print_str(off_on[conf->initial_value]);
}
/* implement gpio_ll_print_conf as weak alias symbol for
* gpio_ll_print_conf_common - so that platform specific implementations can
* override gpio_ll_print_conf while reusing gpio_ll_print_conf_common()
*/
__attribute__((weak, alias("gpio_ll_print_conf_common")))
void gpio_ll_print_conf(const gpio_conf_t *conf);

View File

@ -0,0 +1,16 @@
/*
* Copyright (C) 2021 Otto-von-Guericke-Universität Magdeburg
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
#include "periph/gpio_ll_irq.h"
#include <stdint.h>
__attribute__((weak))
void gpio_ll_irq_off(gpio_port_t port, uint8_t pin)
{
gpio_ll_irq_mask(port, pin);
}

View File

@ -180,6 +180,34 @@ config HAS_PERIPH_GPIO_TAMPER_WAKE
Indicates that Tamper Detection can be used to wake the CPU from
Deep Sleep.
config HAS_PERIPH_GPIO_LL
bool
help
Indicates that the gpio_ll driver is implemented for the MCU's GPIO
peripheral.
config HAS_PERIPH_GPIO_LL_IRQ
bool
help
Indicates that IRQ support for the gpio_ll driver is implemented for the
MCU's GPIO peripheral.
config HAS_PERIPH_GPIO_LL_IRQ_LEVEL_TRIGGERED_HIGH
bool
help
Indicates that IRQs can be triggered level based for signal high.
config HAS_PERIPH_GPIO_LL_IRQ_LEVEL_TRIGGERED_LOW
bool
help
Indicates that IRQs can be triggered level based for signal low.
config HAS_PERIPH_GPIO_LL_IRQ_UNMASK
bool
help
Indicates that the GPIO peripheral supports unmasking interrupts without
clearing pending IRQs that came in while masked.
config HAS_PERIPH_HWRNG
bool
help

5
tests/bench_periph_gpio_ll/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
# Allow custom pin mapping in Makefile.$(BOARD) files, but those don't need to
# go upstream
/Makefile.*
# but un-ignore Makefile.ci
!/Makefile.ci

View File

@ -0,0 +1,42 @@
BOARD ?= nucleo-f767zi
# Custom per-board pin configuration (e.g. for setting PORT_IN, PIN_IN_0, ...)
# can be provided in a Makefile.$(BOARD) file:
-include Makefile.$(BOARD)
# Choose two output pins that do not conflict with stdio and are not connected
# to external devices such as sensors, network devices, etc.
#
# Beware: If other pins on the output port are configured as output GPIOs, they
# might be written to during this test.
PORT_OUT ?= 0
PIN_OUT_0 ?= 0
PIN_OUT_1 ?= 1
include ../Makefile.tests_common
FEATURES_REQUIRED += periph_gpio_ll
FEATURES_REQUIRED += periph_gpio
FEATURES_OPTIONAL += periph_gpio_ll_irq
FEATURES_OPTIONAL += periph_gpio_ll_irq_level_triggered_high
FEATURES_OPTIONAL += periph_gpio_ll_irq_level_triggered_low
USEMODULE += ztimer_usec
include $(RIOTBASE)/Makefile.include
# Configure if compensation of loop overhead in the estimation of the
# toggling speed should be performed. Default: Do so, except for Cortex-M7.
# For the Cortex-M7 the loop instructions are emitted together with the GPIO
# writes due to the dual issue feature. Hence, there is no loop overhead for
# Cortex-M7 to compensate for.
ifeq (cortex-m7,$(CPU_CORE))
COMPENSATE_OVERHEAD ?= 0
endif
COMPENSATE_OVERHEAD ?= 1
CFLAGS += -DPORT_OUT=$(PORT_OUT)
CFLAGS += -DPIN_OUT_0=$(PIN_OUT_0)
CFLAGS += -DPIN_OUT_1=$(PIN_OUT_1)
CFLAGS += -DCOMPENSATE_OVERHEAD=$(COMPENSATE_OVERHEAD)

View File

@ -0,0 +1,8 @@
BOARD_INSUFFICIENT_MEMORY := \
arduino-duemilanove \
arduino-nano \
arduino-uno \
atmega328p \
atmega328p-xplained-mini \
nucleo-l011k4 \
#

View File

@ -0,0 +1,40 @@
# Benchmark for `periph/gpio_ll`
This application will generate a square wave on two output pins with a phase
difference of zero between them using both the `periph/gpio` API (as reference)
and the `periph/gpio_ll` API. You can use a logic analyzer or scope to verify
that the square waves are indeed simultaneous (no phase difference) and their
frequency. Note that with the pin based `periph/gpio` API a phase difference is
expected, but not for the port based `periph/gpio_ll` API.
In addition, a timer is used to measure the average frequency over 50,000
square wave periods. The overhead of the loop is estimated and a compensated
frequency (that would be achievable only by unrolling the loop) is calculated.
Both frequencies are printed, in addition to the number of CPU cycles per wave
period. The optimal value is 2 CPU cycles (signal is 1 cycle high and 1 cycle
low).
## Configuration
Configure in the `Makefile` or set via environment variables the number of
the GPIO port to use via the `PORT_OUT` variable. The `PIN_OUT_0` and
`PIN_OUT_1` variables select the pins to use within that GPIO port. If possible,
choose a GPIO port that is fully broken out to pins of your board but left
unconnected. That way you can connect a scope or a logic analyzer to verify
the output.
Note that the test using `gpio_ll_write()` might cause changes to unrelated pins
on the `PORT_OUT` GPIO port, by restoring their value to what it was at the
beginning of the benchmark.
## FAQ
Why are 4 functions calls used for `periph/gpio`, but only 2 for
`periph/gpio_ll`? This isn't fair!
Since in a port based APIs multiple pins can be accessed at once, only two
accesses are needed (one for the high and one for the low part of each square
wave period). In the pin based `periph/gpio` API, two accesses are needed per
pin. This unfair advantage in speed is one of the reasons we want a low level
port based API in RIOT - in addition to a more convenient to use and high level
pin based API.

View File

@ -0,0 +1,294 @@
/*
* Copyright (C) 2021 Otto-von-Guericke-Universität Magdeburg
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @ingroup tests
* @{
*
* @file
* @brief Test application for the Peripheral GPIO Low-Level API
*
* @author Marian Buschsieweke <marian.buschsieweke@ovgu.de>
*
* @}
*/
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "mutex.h"
#include "periph/gpio.h"
#include "periph/gpio_ll.h"
#include "test_utils/expect.h"
#include "ztimer.h"
#include "timex.h"
#ifndef COMPENSATE_OVERHEAD
#define COMPENSATE_OVERHEAD 1
#endif
static gpio_port_t port_out = GPIO_PORT(PORT_OUT);
static void print_summary_compensated(uint_fast16_t loops, uint32_t duration,
uint32_t duration_uncompensated)
{
printf("%" PRIuFAST16 " iterations took %" PRIu32 " us "
"(%" PRIu32 " us uncompensated)\n",
loops, duration, duration_uncompensated);
printf("Two square waves pins at %12" PRIu32 " Hz "
"(%12" PRIu32 " Hz uncompensated)\n",
(uint32_t)((uint64_t)US_PER_SEC * loops / duration),
(uint32_t)((uint64_t)US_PER_SEC * loops / duration_uncompensated));
#ifdef CLOCK_CORECLOCK
uint64_t divisor = (uint64_t)US_PER_SEC * loops / CLOCK_CORECLOCK;
uint32_t cycles = (duration + divisor / 2) / divisor;
uint32_t cycles_uncompensated = (duration_uncompensated + divisor / 2)
/ divisor;
printf("~%" PRIu32 " CPU cycles per square wave period "
"(~%" PRIu32 " cycles uncompensated)\n",
cycles, cycles_uncompensated);
if (cycles <= 2) {
puts(":-D");
}
else if (cycles <= 4) {
puts(":-)");
}
else if (cycles <= 8) {
puts(":-|");
}
else if (cycles <= 16) {
puts(":-(");
}
else {
puts(":'-(");
}
#endif
}
static void print_summary_uncompensated(uint_fast16_t loops, uint32_t duration)
{
printf("%" PRIuFAST16 " iterations took %" PRIu32 " us\n",
loops, duration);
printf("Two square waves pins at %12" PRIu32 " Hz\n",
(uint32_t)((uint64_t)US_PER_SEC * loops / duration));
#ifdef CLOCK_CORECLOCK
uint64_t divisor = (uint64_t)US_PER_SEC * loops / CLOCK_CORECLOCK;
uint32_t cycles = (duration + divisor / 2) / divisor;
printf("~%" PRIu32 " CPU cycles per square wave period\n", cycles);
if (cycles <= 2) {
puts(":-D");
}
else if (cycles <= 4) {
puts(":-)");
}
else if (cycles <= 8) {
puts(":-|");
}
else if (cycles <= 16) {
puts(":-(");
}
else {
puts(":'-(");
}
#endif
}
int main(void)
{
static const uint_fast16_t loops = 50000;
uint32_t loop_overhead = 0;
uword_t mask_both = (1U << PIN_OUT_0) | (1U << PIN_OUT_1);
puts("\n"
"Benchmarking GPIO APIs\n"
"======================");
if (COMPENSATE_OVERHEAD) {
puts("\n"
"estimating loop overhead for compensation\n"
"-----------------------------------------");
uint32_t start = ztimer_now(ZTIMER_USEC);
for (uint_fast16_t i = loops; i > 0; i--) {
__asm__ volatile ("" : : : );
}
loop_overhead = ztimer_now(ZTIMER_USEC) - start;
printf("%" PRIu32 " us for %" PRIuFAST16 " iterations\n",
loop_overhead, loops);
}
{
puts("\n"
"periph/gpio: Using 2x gpio_set() and 2x gpio_clear()\n"
"---------------------------------------------------");
gpio_t p0 = GPIO_PIN(PORT_OUT, PIN_OUT_0);
gpio_t p1 = GPIO_PIN(PORT_OUT, PIN_OUT_1);
gpio_init(p0, GPIO_OUT);
gpio_init(p1, GPIO_OUT);
uint32_t start = ztimer_now(ZTIMER_USEC);
for (uint_fast16_t i = loops; i > 0; i--) {
gpio_set(p0);
gpio_set(p1);
gpio_clear(p0);
gpio_clear(p1);
}
uint32_t duration = ztimer_now(ZTIMER_USEC) - start;
if (COMPENSATE_OVERHEAD) {
print_summary_compensated(loops, duration - loop_overhead,
duration);
}
else {
print_summary_uncompensated(loops, duration);
}
}
{
puts("\n"
"periph/gpio_ll: Using gpio_ll_set() and gpio_ll_clear()\n"
"-------------------------------------------------------");
gpio_conf_t conf = {
.state = GPIO_OUTPUT_PUSH_PULL,
.slew_rate = GPIO_SLEW_FASTEST
};
expect(0 == gpio_ll_init(port_out, PIN_OUT_0, &conf));
expect(0 == gpio_ll_init(port_out, PIN_OUT_1, &conf));
uint32_t start = ztimer_now(ZTIMER_USEC);
for (uint_fast16_t i = loops; i > 0; i--) {
gpio_ll_set(port_out, (1UL << PIN_OUT_0) | (1UL << PIN_OUT_1));
gpio_ll_clear(port_out, (1UL << PIN_OUT_0) | (1UL << PIN_OUT_1));
}
uint32_t duration = ztimer_now(ZTIMER_USEC) - start;
if (COMPENSATE_OVERHEAD) {
print_summary_compensated(loops, duration - loop_overhead,
duration);
}
else {
print_summary_uncompensated(loops, duration);
}
}
{
puts("\n"
"periph/gpio: Using 4x gpio_toggle()\n"
"-----------------------------------");
gpio_t p0 = GPIO_PIN(PORT_OUT, PIN_OUT_0);
gpio_t p1 = GPIO_PIN(PORT_OUT, PIN_OUT_1);
gpio_init(p0, GPIO_OUT);
gpio_init(p1, GPIO_OUT);
uint32_t start = ztimer_now(ZTIMER_USEC);
for (uint_fast16_t i = loops; i > 0; i--) {
gpio_toggle(p0);
gpio_toggle(p1);
gpio_toggle(p0);
gpio_toggle(p1);
}
uint32_t duration = ztimer_now(ZTIMER_USEC) - start;
if (COMPENSATE_OVERHEAD) {
print_summary_compensated(loops, duration - loop_overhead,
duration);
}
else {
print_summary_uncompensated(loops, duration);
}
}
{
puts("\n"
"periph/gpio_ll: Using 2x gpio_ll_toggle()\n"
"-----------------------------------------");
gpio_conf_t conf = {
.state = GPIO_OUTPUT_PUSH_PULL,
.slew_rate = GPIO_SLEW_FASTEST
};
expect(0 == gpio_ll_init(port_out, PIN_OUT_0, &conf));
expect(0 == gpio_ll_init(port_out, PIN_OUT_1, &conf));
uint32_t start = ztimer_now(ZTIMER_USEC);
for (uint_fast16_t i = loops; i > 0; i--) {
gpio_ll_toggle(port_out, mask_both);
gpio_ll_toggle(port_out, mask_both);
}
uint32_t duration = ztimer_now(ZTIMER_USEC) - start;
if (COMPENSATE_OVERHEAD) {
print_summary_compensated(loops, duration - loop_overhead,
duration);
}
else {
print_summary_uncompensated(loops, duration);
}
}
{
puts("\n"
"periph/gpio: Using 4x gpio_write()\n"
"----------------------------------");
gpio_t p0 = GPIO_PIN(PORT_OUT, PIN_OUT_0);
gpio_t p1 = GPIO_PIN(PORT_OUT, PIN_OUT_1);
gpio_init(p0, GPIO_OUT);
gpio_init(p1, GPIO_OUT);
uint32_t start = ztimer_now(ZTIMER_USEC);
for (uint_fast16_t i = loops; i > 0; i--) {
gpio_write(p0, 1);
gpio_write(p1, 1);
gpio_write(p0, 0);
gpio_write(p1, 0);
}
uint32_t duration = ztimer_now(ZTIMER_USEC) - start;
if (COMPENSATE_OVERHEAD) {
print_summary_compensated(loops, duration - loop_overhead,
duration);
}
else {
print_summary_uncompensated(loops, duration);
}
}
{
puts("\n"
"periph/gpio_ll: Using 2x gpio_ll_write()\n"
"----------------------------------------");
gpio_conf_t conf = {
.state = GPIO_OUTPUT_PUSH_PULL,
.slew_rate = GPIO_SLEW_FASTEST
};
expect(0 == gpio_ll_init(port_out, PIN_OUT_0, &conf));
expect(0 == gpio_ll_init(port_out, PIN_OUT_1, &conf));
uword_t both_high = gpio_ll_prepare_write(port_out, mask_both,
mask_both);
uword_t both_low = gpio_ll_prepare_write(port_out, mask_both, 0);
uint32_t start = ztimer_now(ZTIMER_USEC);
for (uint_fast16_t i = loops; i > 0; i--) {
gpio_ll_write(port_out, both_high);
gpio_ll_write(port_out, both_low);
}
uint32_t duration = ztimer_now(ZTIMER_USEC) - start;
if (COMPENSATE_OVERHEAD) {
print_summary_compensated(loops, duration - loop_overhead,
duration);
}
else {
print_summary_uncompensated(loops, duration);
}
}
puts("\n\nTEST SUCCEEDED");
return 0;
}

View File

@ -0,0 +1,18 @@
#!/usr/bin/env python3
# Copyright (C) 2022 Otto-von-Guericke-Universität Magdeburg
#
# This file is subject to the terms and conditions of the GNU Lesser
# General Public License v2.1. See the file LICENSE in the top level
# directory for more details.
import sys
from testrunner import run
def testfunc(child):
child.expect('TEST SUCCEEDED')
if __name__ == "__main__":
sys.exit(run(testfunc))

5
tests/periph_gpio_ll/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
# Allow custom pin mapping in Makefile.$(BOARD) files, but those don't need to
# go upstream
/Makefile.*
# but un-ignore Makefile.ci
!/Makefile.ci

View File

@ -0,0 +1,50 @@
BOARD ?= nucleo-f767zi
# Custom per-board pin configuration (e.g. for setting PORT_IN, PIN_IN_0, ...)
# can be provided in a Makefile.$(BOARD) file:
-include Makefile.$(BOARD)
# Choose two input pins and two pins that do not conflict with stdio. All four
# *can* be on the same GPIO port, but the two output pins and the two inputs
# pins *must* be on the same port, respectively. Connect the first input to the
# first output pin and the second input pin to the second output pin, e.g. using
# jumper wires.
PORT_IN ?= 1
PORT_OUT ?= 0
PIN_IN_0 ?= 0
PIN_IN_1 ?= 1
PIN_OUT_0 ?= 0
PIN_OUT_1 ?= 1
# Boards that require tweaking for low ROM
LOW_ROM_BOARDS := \
nucleo-l011k4 \
stm32f030f4-demo \
#
ifneq (,$(filter $(BOARD),$(LOW_ROM_BOARDS)))
LOW_ROM ?= 1
endif
LOW_ROM ?= 0
include ../Makefile.tests_common
FEATURES_REQUIRED += periph_gpio_ll
FEATURES_OPTIONAL += periph_gpio_ll_irq
FEATURES_OPTIONAL += periph_gpio_ll_irq_level_triggered_high
FEATURES_OPTIONAL += periph_gpio_ll_irq_level_triggered_low
USEMODULE += ztimer_usec
include $(RIOTBASE)/Makefile.include
CFLAGS += -DPORT_OUT=$(PORT_OUT)
CFLAGS += -DPORT_IN=$(PORT_IN)
CFLAGS += -DPIN_IN_0=$(PIN_IN_0)
CFLAGS += -DPIN_IN_1=$(PIN_IN_1)
CFLAGS += -DPIN_OUT_0=$(PIN_OUT_0)
CFLAGS += -DPIN_OUT_1=$(PIN_OUT_1)
# We only need 1 thread (+ the Idle thread on some platforms) and we really want
# this app working on all boards.
CFLAGS += -DMAXTHREADS=2
CFLAGS += -DLOW_ROM=$(LOW_ROM)

View File

@ -0,0 +1,21 @@
# Test for `periph/gpio_ll`
This application will use two output and two input GPIOs, which have to be
connected via a jumper wire. The test will change the value of the two pins
output pins and use the input pins to read the value back in. If the value read
doesn't match the expected result, the test aborts and fails.
If IRQ support is provided, the test will additionally walk through every IRQ
configuration for the first GPIO pin given and iterate over any trigger
condition possible. It will check that edge trigger and (if supported) level
trigger IRQs trigger exactly on the configured triggers.
## Configuration
Configure in the `Makefile` or set via environment variables the number of
the output GPIO port to use via the `PORT_OUT` variable. The `PIN_OUT_0` and
`PIN_OUT_1` variables select the pins to use within that GPIO port. The input
GPIO port is set via `PORT_IN`. Both `PORT_IN == PORT_OUT` and
`PORT_IN != PORT_OUT` is valid. The input pin number within `PORT_IN` are set
via `PIN_IN_0` and `PIN_IN_1`. `PIN_IN_0` has to be wired to `PIN_OUT_0`, and
`PIN_IN_1` to `PIN_OUT_1`.

BIN
tests/periph_gpio_ll/core Normal file

Binary file not shown.

810
tests/periph_gpio_ll/main.c Normal file
View File

@ -0,0 +1,810 @@
/*
* Copyright (C) 2021 Otto-von-Guericke-Universität Magdeburg
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @ingroup tests
* @{
*
* @file
* @brief Test application for the Peripheral GPIO Low-Level API
*
* @author Marian Buschsieweke <marian.buschsieweke@ovgu.de>
*
* @}
*/
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "mutex.h"
#include "periph/gpio_ll.h"
#include "periph/gpio_ll_irq.h"
#include "test_utils/expect.h"
#include "ztimer.h"
#include "timex.h"
#ifndef LOW_ROM
#define LOW_ROM 0
#endif
static const char *noyes[] = { "no", "yes" };
static gpio_port_t port_out = GPIO_PORT(PORT_OUT);
static gpio_port_t port_in = GPIO_PORT(PORT_IN);
static const uint64_t mutex_timeout = US_PER_MS;
static void puts_optional(const char *str)
{
if (!LOW_ROM) {
puts(str);
}
}
#if LOW_ROM
#define printf_optional(...) (void)0
#else
#define printf_optional(...) printf(__VA_ARGS__)
#endif
static void print_cabling(unsigned port1, unsigned pin1,
unsigned port2, unsigned pin2)
{
printf(" P%u.%u (P%c%u) -- P%u.%u (P%c%u)\n",
port1, pin1, 'A' + (char)port1, pin1,
port2, pin2, 'A' + (char)port2, pin2);
}
static void print_details(void)
{
puts_optional("Test / Hardware Deatils:\n"
"========================\n"
"Cabling:");
print_cabling(PORT_IN, PIN_IN_0, PORT_OUT, PIN_OUT_0);
print_cabling(PORT_IN, PIN_IN_1, PORT_OUT, PIN_OUT_1);
printf("Number of pull resistor values supported: %u\n", GPIO_PULL_NUMOF);
printf("Number of drive strengths supported: %u\n", GPIO_DRIVE_NUMOF);
printf("Number of slew rates supported: %u\n", GPIO_SLEW_NUMOF);
puts("Valid GPIO ports:");
/* 64 GPIO ports ought to be enough for anybody */
for (uint_fast8_t i = 0; i < 64; i++) {
if (is_gpio_port_num_valid(i)) {
printf("- PORT %" PRIuFAST8 " (PORT %c)\n", i, 'A' + i);
}
}
}
static void test_gpio_port_pack(void)
{
char stacked;
puts_optional("\n"
"Testing gpio_port_pack_addr()\n"
"=============================\n");
expect(gpio_port_unpack_addr(gpio_port_pack_addr(&stacked)) == &stacked);
expect(gpio_port_unpack_addr(gpio_port_pack_addr(noyes)) == noyes);
void *tmp = malloc(1);
expect(tmp);
expect(gpio_port_unpack_addr(gpio_port_pack_addr(tmp)) == tmp);
free(tmp);
expect(gpio_port_unpack_addr(port_in) == NULL);
expect(gpio_port_unpack_addr(port_out) == NULL);
puts_optional("All OK");
}
static void test_gpio_ll_init(void)
{
bool is_supported;
puts_optional("\n"
"Testing gpip_ng_init()\n"
"======================\n"
"\n"
"Testing is_gpio_port_num_valid() is true for PORT_OUT and "
"PORT_IN:");
expect(is_gpio_port_num_valid(PORT_IN));
expect(is_gpio_port_num_valid(PORT_OUT));
puts_optional("\nTesting input configurations for PIN_IN_0:");
is_supported = (0 == gpio_ll_init(port_in, PIN_IN_0, &gpio_ll_in_pu));
printf_optional("Support for input with pull up: %s\n",
noyes[is_supported]);
if (is_supported) {
gpio_conf_t conf;
gpio_ll_query_conf(&conf, port_in, PIN_IN_0);
gpio_ll_print_conf(&conf);
puts_optional("");
expect((conf.state == GPIO_INPUT) && (conf.pull == GPIO_PULL_UP));
}
is_supported = (0 == gpio_ll_init(port_in, PIN_IN_0, &gpio_ll_in_pd));
printf_optional("Support for input with pull down: %s\n",
noyes[is_supported]);
if (is_supported) {
gpio_conf_t conf;
gpio_ll_query_conf(&conf, port_in, PIN_IN_0);
gpio_ll_print_conf(&conf);
puts_optional("");
expect((conf.state == GPIO_INPUT) && (conf.pull == GPIO_PULL_DOWN));
}
is_supported = (0 == gpio_ll_init(port_in, PIN_IN_0, &gpio_ll_in_pk));
printf_optional("Support for input with pull to bus level: %s\n",
noyes[is_supported]);
if (is_supported) {
gpio_conf_t conf;
gpio_ll_query_conf(&conf, port_in, PIN_IN_0);
gpio_ll_print_conf(&conf);
puts_optional("");
expect((conf.state == GPIO_INPUT) && (conf.pull == GPIO_PULL_KEEP));
}
is_supported = (0 == gpio_ll_init(port_in, PIN_IN_0, &gpio_ll_in));
printf_optional("Support for floating input (no pull resistors): %s\n",
noyes[is_supported]);
{
gpio_conf_t conf;
gpio_ll_query_conf(&conf, port_in, PIN_IN_0);
gpio_ll_print_conf(&conf);
puts_optional("");
expect((conf.state == GPIO_INPUT) && (conf.pull == GPIO_FLOATING));
}
/* Support for floating inputs is mandatory */
expect(is_supported);
puts_optional("\nTesting output configurations for PIN_OUT_0:");
{
gpio_conf_t conf = {
.state = GPIO_OUTPUT_PUSH_PULL,
.initial_value = false,
};
is_supported = (0 == gpio_ll_init(port_out, PIN_OUT_0, &conf));
}
printf_optional("Support for output (push-pull) with initial value of "
"LOW: %s\n",
noyes[is_supported]);
if (is_supported) {
gpio_conf_t conf;
gpio_ll_query_conf(&conf, port_out, PIN_OUT_0);
gpio_ll_print_conf(&conf);
puts_optional("");
expect((conf.state == GPIO_OUTPUT_PUSH_PULL) && !conf.initial_value);
ztimer_sleep(ZTIMER_USEC, US_PER_MS);
is_supported = !(gpio_ll_read(port_in) & (1ULL << PIN_IN_0));
printf_optional("Output is indeed LOW: %s\n", noyes[is_supported]);
expect(is_supported);
gpio_ll_set(port_out, (1ULL<< PIN_OUT_0));
gpio_ll_query_conf(&conf, port_out, PIN_OUT_0);
gpio_ll_print_conf(&conf);
puts_optional("");
ztimer_sleep(ZTIMER_USEC, US_PER_MS);
is_supported = (gpio_ll_read(port_in) & (1ULL << PIN_IN_0));
printf_optional("Output can be pushed HIGH: %s\n", noyes[is_supported]);
expect(is_supported);
gpio_ll_query_conf(&conf, port_out, PIN_OUT_0);
expect((conf.state == GPIO_OUTPUT_PUSH_PULL) && conf.initial_value);
}
{
gpio_conf_t conf = {
.state = GPIO_OUTPUT_PUSH_PULL,
.initial_value = true,
};
is_supported = (0 == gpio_ll_init(port_out, PIN_OUT_0, &conf));
}
printf_optional("Support for output (push-pull) with initial value of "
"HIGH: %s\n",
noyes[is_supported]);
if (is_supported) {
gpio_conf_t conf;
gpio_ll_query_conf(&conf, port_out, PIN_OUT_0);
gpio_ll_print_conf(&conf);
puts_optional("");
expect((conf.state == GPIO_OUTPUT_PUSH_PULL) && conf.initial_value);
ztimer_sleep(ZTIMER_USEC, US_PER_MS);
is_supported = (gpio_ll_read(port_in) & (1ULL << PIN_IN_0));
printf_optional("Output is indeed HIGH: %s\n", noyes[is_supported]);
expect(is_supported);
}
{
gpio_conf_t conf = {
.state = GPIO_OUTPUT_OPEN_DRAIN,
.initial_value = false,
.pull = GPIO_PULL_UP
};
is_supported = (0 == gpio_ll_init(port_out, PIN_OUT_0, &conf));
}
printf_optional("Support for output (open drain with pull up) with initial "
"value of LOW: %s\n",
noyes[is_supported]);
if (is_supported) {
gpio_conf_t conf;
gpio_ll_query_conf(&conf, port_out, PIN_OUT_0);
gpio_ll_print_conf(&conf);
puts_optional("");
expect((conf.state == GPIO_OUTPUT_OPEN_DRAIN) && !conf.initial_value
&& (conf.pull == GPIO_PULL_UP));
ztimer_sleep(ZTIMER_USEC, US_PER_MS);
is_supported = !(gpio_ll_read(port_in) & (1ULL << PIN_IN_0));
printf_optional("Output is indeed LOW: %s\n", noyes[is_supported]);
expect(is_supported);
}
{
gpio_conf_t conf = {
.state = GPIO_OUTPUT_OPEN_DRAIN,
.initial_value = true,
.pull = GPIO_PULL_UP
};
is_supported = (0 == gpio_ll_init(port_out, PIN_OUT_0, &conf));
}
printf_optional("Support for output (open drain with pull up) with initial "
"value of HIGH: %s\n",
noyes[is_supported]);
if (is_supported) {
gpio_conf_t conf;
gpio_ll_query_conf(&conf, port_out, PIN_OUT_0);
gpio_ll_print_conf(&conf);
puts_optional("");
expect((conf.state == GPIO_OUTPUT_OPEN_DRAIN) && conf.initial_value
&& (conf.pull == GPIO_PULL_UP));
ztimer_sleep(ZTIMER_USEC, US_PER_MS);
is_supported = !!(gpio_ll_read(port_in) & (1ULL << PIN_IN_0));
printf_optional("Output is indeed HIGH: %s\n", noyes[is_supported]);
expect(is_supported);
}
{
gpio_conf_t conf = {
.state = GPIO_OUTPUT_OPEN_DRAIN,
.initial_value = false,
.pull = GPIO_FLOATING,
};
is_supported = (0 == gpio_ll_init(port_out, PIN_OUT_0, &conf));
}
printf_optional("Support for output (open drain) with initial value of "
"LOW: %s\n",
noyes[is_supported]);
if (is_supported) {
gpio_conf_t conf;
gpio_ll_query_conf(&conf, port_out, PIN_OUT_0);
gpio_ll_print_conf(&conf);
puts_optional("");
expect((conf.state == GPIO_OUTPUT_OPEN_DRAIN) && !conf.initial_value
&& (conf.pull == GPIO_FLOATING));
ztimer_sleep(ZTIMER_USEC, US_PER_MS);
is_supported = !(gpio_ll_read(port_in) & (1ULL << PIN_IN_0));
printf_optional("Output is indeed LOW: %s\n", noyes[is_supported]);
expect(is_supported);
}
{
gpio_conf_t conf = {
.state = GPIO_OUTPUT_OPEN_DRAIN,
.initial_value = true,
.pull = GPIO_FLOATING,
};
is_supported = (0 == gpio_ll_init(port_out, PIN_OUT_0, &conf));
}
printf_optional("Support for output (open drain) with initial value of "
"HIGH: %s\n",
noyes[is_supported]);
if (is_supported) {
gpio_conf_t conf;
gpio_ll_query_conf(&conf, port_out, PIN_OUT_0);
gpio_ll_print_conf(&conf);
puts_optional("");
expect((conf.state == GPIO_OUTPUT_OPEN_DRAIN) && conf.initial_value
&& (conf.pull == GPIO_FLOATING));
is_supported = (0 == gpio_ll_init(port_in, PIN_IN_0, &gpio_ll_in_pd));
if (is_supported) {
gpio_conf_t conf;
gpio_ll_query_conf(&conf, port_in, PIN_IN_0);
gpio_ll_print_conf(&conf);
puts_optional("");
ztimer_sleep(ZTIMER_USEC, US_PER_MS);
is_supported = !(gpio_ll_read(port_in) & (1ULL << PIN_IN_0));
printf_optional("Output can indeed be pulled LOW: %s\n",
noyes[is_supported]);
expect(is_supported);
}
else {
puts_optional("WARN: Cannot enable pull down of PIN_IN_0 to verify "
"correct Open Drain behavior");
}
is_supported = (0 == gpio_ll_init(port_in, PIN_IN_0, &gpio_ll_in_pu));
if (is_supported) {
gpio_conf_t conf;
gpio_ll_query_conf(&conf, port_in, PIN_IN_0);
gpio_ll_print_conf(&conf);
puts_optional("");
ztimer_sleep(ZTIMER_USEC, US_PER_MS);
is_supported = (gpio_ll_read(port_in) & (1ULL << PIN_IN_0));
printf_optional("Output can indeed be pulled HIGH: %s\n",
noyes[is_supported]);
expect(is_supported);
}
else {
puts_optional("WARN: Cannot enable pull up of PIN_IN_0 to verify "
"correct Open Drain behavior");
}
}
{
gpio_conf_t conf = {
.state = GPIO_OUTPUT_OPEN_SOURCE,
.initial_value = false,
.pull = GPIO_FLOATING,
};
is_supported = (0 == gpio_ll_init(port_out, PIN_OUT_0, &conf));
}
printf_optional("Support for output (open source) with initial value of "
"LOW: %s\n",
noyes[is_supported]);
if (is_supported) {
gpio_conf_t conf;
gpio_ll_query_conf(&conf, port_out, PIN_OUT_0);
gpio_ll_print_conf(&conf);
puts_optional("");
expect((conf.state == GPIO_OUTPUT_OPEN_SOURCE) && !conf.initial_value
&& (conf.pull == GPIO_FLOATING));
is_supported = (0 == gpio_ll_init(port_in, PIN_IN_0, &gpio_ll_in_pd));
if (is_supported) {
gpio_conf_t conf;
gpio_ll_query_conf(&conf, port_in, PIN_IN_0);
gpio_ll_print_conf(&conf);
puts_optional("");
ztimer_sleep(ZTIMER_USEC, US_PER_MS);
is_supported = !(gpio_ll_read(port_in) & (1ULL << PIN_IN_0));
printf_optional("Output can indeed be pulled LOW: %s\n",
noyes[is_supported]);
expect(is_supported);
}
else {
puts_optional("WARN: Cannot enable pull down of PIN_IN_0 to verify "
"correct Open Source behavior");
}
is_supported = (0 == gpio_ll_init(port_in, PIN_IN_0, &gpio_ll_in_pu));
if (is_supported) {
gpio_conf_t conf;
gpio_ll_query_conf(&conf, port_in, PIN_IN_0);
gpio_ll_print_conf(&conf);
puts_optional("");
ztimer_sleep(ZTIMER_USEC, US_PER_MS);
is_supported = (gpio_ll_read(port_in) & (1ULL << PIN_IN_0));
printf_optional("Output can indeed be pulled HIGH: %s\n",
noyes[is_supported]);
expect(is_supported);
}
else {
puts_optional("WARN: Cannot enable pull up of PIN_IN_0 to verify "
"correct Open Source behavior");
}
}
expect(0 == gpio_ll_init(port_in, PIN_IN_0, &gpio_ll_in));
{
gpio_conf_t conf = {
.state = GPIO_OUTPUT_OPEN_SOURCE,
.initial_value = true,
.pull = GPIO_FLOATING,
};
is_supported = (0 == gpio_ll_init(port_out, PIN_OUT_0, &conf));
}
printf_optional("Support for output (open drain) with initial value of "
"HIGH: %s\n",
noyes[is_supported]);
if (is_supported) {
gpio_conf_t conf;
gpio_ll_query_conf(&conf, port_out, PIN_OUT_0);
gpio_ll_print_conf(&conf);
puts_optional("");
expect((conf.state == GPIO_OUTPUT_OPEN_SOURCE) && conf.initial_value
&& (conf.pull == GPIO_FLOATING));
ztimer_sleep(ZTIMER_USEC, US_PER_MS);
is_supported = (gpio_ll_read(port_in) & (1ULL << PIN_IN_0));
printf_optional("Output is indeed HIGH: %s\n", noyes[is_supported]);
expect(is_supported);
}
{
gpio_conf_t conf = {
.state = GPIO_OUTPUT_OPEN_SOURCE,
.initial_value = true,
.pull = GPIO_PULL_DOWN,
};
is_supported = (0 == gpio_ll_init(port_out, PIN_OUT_0, &conf));
}
printf_optional("Support for output (open drain with pull up) with initial "
"value of HIGH: %s\n",
noyes[is_supported]);
if (is_supported) {
ztimer_sleep(ZTIMER_USEC, US_PER_MS);
is_supported = (gpio_ll_read(port_in) & (1ULL << PIN_IN_0));
printf_optional("Output is indeed HIGH: %s\n", noyes[is_supported]);
expect(is_supported);
}
{
gpio_conf_t conf = {
.state = GPIO_OUTPUT_OPEN_SOURCE,
.initial_value = false,
.pull = GPIO_PULL_DOWN,
};
is_supported = (0 == gpio_ll_init(port_out, PIN_OUT_0, &conf));
}
printf_optional("Support for output (open drain with pull up) with initial "
"value of LOW: %s\n",
noyes[is_supported]);
if (is_supported) {
ztimer_sleep(ZTIMER_USEC, US_PER_MS);
is_supported = !(gpio_ll_read(port_in) & (1ULL << PIN_IN_0));
printf_optional("Output is indeed LOW: %s\n", noyes[is_supported]);
expect(is_supported);
}
{
gpio_conf_t conf = {
.state = GPIO_DISCONNECT,
};
is_supported = (0 == gpio_ll_init(port_out, PIN_OUT_0, &conf));
}
printf_optional("Support for disconnecting GPIO: %s\n", noyes[is_supported]);
/* This is mandatory */
expect(is_supported);
is_supported = (0 == gpio_ll_init(port_in, PIN_IN_0, &gpio_ll_in_pd));
if (is_supported) {
ztimer_sleep(ZTIMER_USEC, US_PER_MS);
is_supported = !(gpio_ll_read(port_in) & (1ULL << PIN_IN_0));
printf_optional("Output can indeed be pulled LOW: %s\n",
noyes[is_supported]);
expect(is_supported);
}
else {
puts_optional("WARN: Cannot enable pull down of PIN_IN_0 to verify "
"correct disabled behavior");
}
is_supported = (0 == gpio_ll_init(port_in, PIN_IN_0, &gpio_ll_in_pu));
if (is_supported) {
ztimer_sleep(ZTIMER_USEC, US_PER_MS);
is_supported = (gpio_ll_read(port_in) & (1ULL << PIN_IN_0));
printf_optional("Output can indeed be pulled HIGH: %s\n",
noyes[is_supported]);
expect(is_supported);
}
else {
puts_optional("WARN: Cannot enable pull up of PIN_IN_0 to verify "
"correct disabled behavior");
}
}
static void test_input_output(void)
{
puts_optional("\n"
"Testing Reading/Writing GPIO Ports\n"
"==================================\n");
expect(0 == gpio_ll_init(port_in, PIN_IN_0, &gpio_ll_in));
expect(0 == gpio_ll_init(port_in, PIN_IN_1, &gpio_ll_in));
expect(0 == gpio_ll_init(port_out, PIN_OUT_0, &gpio_ll_out));
expect(0 == gpio_ll_init(port_out, PIN_OUT_1, &gpio_ll_out));
uword_t mask_in_0 = (1UL << PIN_IN_0);
uword_t mask_in_1 = (1UL << PIN_IN_1);
uword_t mask_in_both = mask_in_0 | mask_in_1;
uword_t mask_out_both = (1UL << PIN_OUT_0) | (1UL << PIN_OUT_1);
puts_optional("testing initial value of 0 after init");
ztimer_sleep(ZTIMER_USEC, US_PER_MS);
expect(0x00 == (gpio_ll_read(port_in) & mask_in_both));
puts_optional("...OK");
puts_optional("testing setting both outputs_optional simultaneously");
gpio_ll_set(port_out, (1UL << PIN_OUT_0) | (1UL << PIN_OUT_1));
ztimer_sleep(ZTIMER_USEC, US_PER_MS);
expect(mask_in_both == (gpio_ll_read(port_in) & mask_in_both));
puts_optional("...OK");
puts_optional("testing clearing both outputs_optional simultaneously");
gpio_ll_clear(port_out, (1UL << PIN_OUT_0) | (1UL << PIN_OUT_1));
ztimer_sleep(ZTIMER_USEC, US_PER_MS);
expect(0x00 == (gpio_ll_read(port_in) & mask_in_both));
puts_optional("...OK");
puts_optional("testing toggling first output (0 --> 1)");
gpio_ll_toggle(port_out, 1UL << PIN_OUT_0);
ztimer_sleep(ZTIMER_USEC, US_PER_MS);
expect(mask_in_0 == (gpio_ll_read(port_in) & mask_in_both));
puts_optional("...OK");
puts_optional("testing toggling first output (1 --> 0)");
gpio_ll_toggle(port_out, 1UL << PIN_OUT_0);
ztimer_sleep(ZTIMER_USEC, US_PER_MS);
expect(0x00 == (gpio_ll_read(port_in) & mask_in_both));
puts_optional("...OK");
puts_optional("testing toggling second output (0 --> 1)");
gpio_ll_toggle(port_out, 1UL << PIN_OUT_1);
ztimer_sleep(ZTIMER_USEC, US_PER_MS);
expect(mask_in_1 == (gpio_ll_read(port_in) & mask_in_both));
puts_optional("...OK");
puts_optional("testing toggling second output (1 --> 0)");
gpio_ll_toggle(port_out, 1UL << PIN_OUT_1);
ztimer_sleep(ZTIMER_USEC, US_PER_MS);
expect(0x00 == (gpio_ll_read(port_in) & mask_in_both));
puts_optional("...OK");
puts_optional("testing setting first output and clearing second with "
"write");
/* Preventing side-effects on output GPIO port due to changes on unrelated
* pins between the calls to gpio_ll_prepare_write() and gpio_ll_write()
* by disabling IRQs. Better safe than sorry. */
unsigned irq_state = irq_disable();
gpio_ll_write(port_out, gpio_ll_prepare_write(port_out, mask_out_both,
1UL << PIN_OUT_0));
irq_restore(irq_state);
ztimer_sleep(ZTIMER_USEC, US_PER_MS);
expect(mask_in_0 == (gpio_ll_read(port_in) & mask_in_both));
puts_optional("...OK");
irq_state = irq_disable();
gpio_ll_write(port_out, gpio_ll_prepare_write(port_out, mask_out_both,
1UL << PIN_OUT_1));
irq_restore(irq_state);
puts_optional("testing setting second output and clearing first with "
"write");
expect(mask_in_1 == (gpio_ll_read(port_in) & mask_in_both));
puts_optional("...OK");
puts_optional("All input/output operations worked as expected");
}
static void irq_edge_cb(void *mut)
{
mutex_unlock(mut);
}
static void test_irq_edge(void)
{
mutex_t irq_mut = MUTEX_INIT_LOCKED;
puts_optional("Testing rising edge on PIN_IN_0");
expect(0 == gpio_ll_irq(port_in, PIN_IN_0, GPIO_TRIGGER_EDGE_RISING,
irq_edge_cb, &irq_mut));
/* test for spurious IRQ */
expect(-ECANCELED == ztimer_mutex_lock_timeout(ZTIMER_USEC, &irq_mut,
mutex_timeout));
gpio_ll_set(port_out, 1UL << PIN_OUT_0);
/* test for IRQ on rising edge */
expect(0 == ztimer_mutex_lock_timeout(ZTIMER_USEC, &irq_mut,
mutex_timeout));
/* test for spurious IRQ */
expect(-ECANCELED == ztimer_mutex_lock_timeout(ZTIMER_USEC, &irq_mut,
mutex_timeout));
gpio_ll_clear(port_out, 1UL << PIN_OUT_0);
/* test for no IRQ on falling edge */
expect(-ECANCELED == ztimer_mutex_lock_timeout(ZTIMER_USEC, &irq_mut,
mutex_timeout));
puts_optional("... OK");
puts_optional("Testing falling edge on PIN_IN_0");
expect(0 == gpio_ll_irq(port_in, PIN_IN_0, GPIO_TRIGGER_EDGE_FALLING,
irq_edge_cb, &irq_mut));
/* test for spurious IRQ */
expect(-ECANCELED == ztimer_mutex_lock_timeout(ZTIMER_USEC, &irq_mut,
mutex_timeout));
gpio_ll_set(port_out, 1UL << PIN_OUT_0);
/* test for no IRQ on rising edge */
expect(-ECANCELED == ztimer_mutex_lock_timeout(ZTIMER_USEC, &irq_mut,
mutex_timeout));
gpio_ll_clear(port_out, 1UL << PIN_OUT_0);
/* test for IRQ on falling edge */
expect(0 == ztimer_mutex_lock_timeout(ZTIMER_USEC, &irq_mut,
mutex_timeout));
/* test for spurious IRQ */
expect(-ECANCELED == ztimer_mutex_lock_timeout(ZTIMER_USEC, &irq_mut,
mutex_timeout));
puts_optional("... OK");
puts_optional("Testing both edges on PIN_IN_0");
expect(0 == gpio_ll_irq(port_in, PIN_IN_0, GPIO_TRIGGER_EDGE_BOTH,
irq_edge_cb, &irq_mut));
/* test for spurious IRQ */
expect(-ECANCELED == ztimer_mutex_lock_timeout(ZTIMER_USEC, &irq_mut,
mutex_timeout));
gpio_ll_set(port_out, 1UL << PIN_OUT_0);
/* test for IRQ on rising edge */
expect(0 == ztimer_mutex_lock_timeout(ZTIMER_USEC, &irq_mut,
mutex_timeout));
/* test for spurious IRQ */
expect(-ECANCELED == ztimer_mutex_lock_timeout(ZTIMER_USEC, &irq_mut,
mutex_timeout));
gpio_ll_clear(port_out, 1UL << PIN_OUT_0);
/* test for IRQ on falling edge */
expect(0 == ztimer_mutex_lock_timeout(ZTIMER_USEC, &irq_mut,
mutex_timeout));
/* test for spurious IRQ */
expect(-ECANCELED == ztimer_mutex_lock_timeout(ZTIMER_USEC, &irq_mut,
mutex_timeout));
puts_optional("... OK");
puts_optional("Testing masking of IRQs (still both edges on PIN_IN_0)");
gpio_ll_irq_mask(port_in, PIN_IN_0);
gpio_ll_set(port_out, 1UL << PIN_OUT_0);
/* test for no IRQ while masked */
expect(-ECANCELED == ztimer_mutex_lock_timeout(ZTIMER_USEC, &irq_mut,
mutex_timeout));
gpio_ll_irq_unmask_and_clear(port_in, PIN_IN_0);
/* test for IRQ of past event is cleared */
expect(-ECANCELED == ztimer_mutex_lock_timeout(ZTIMER_USEC, &irq_mut,
mutex_timeout));
/* testing for normal behavior after unmasked */
gpio_ll_clear(port_out, 1UL << PIN_OUT_0);
expect(0 == ztimer_mutex_lock_timeout(ZTIMER_USEC, &irq_mut,
mutex_timeout));
/* test for spurious IRQ */
expect(-ECANCELED == ztimer_mutex_lock_timeout(ZTIMER_USEC, &irq_mut,
mutex_timeout));
#if MODULE_PERIPH_GPIO_LL_IRQ_UNMASK
gpio_ll_irq_mask(port_in, PIN_IN_0);
gpio_ll_set(port_out, 1UL << PIN_OUT_0);
/* test for no IRQ while masked */
expect(-ECANCELED == ztimer_mutex_lock_timeout(ZTIMER_USEC, &irq_mut,
mutex_timeout));
gpio_ll_irq_unmask(port_in, PIN_IN_0);
/* test for IRQ of past event */
expect(0 == ztimer_mutex_lock_timeout(ZTIMER_USEC, &irq_mut,
mutex_timeout));
/* test for spurious IRQ */
expect(-ECANCELED == ztimer_mutex_lock_timeout(ZTIMER_USEC, &irq_mut,
mutex_timeout));
#endif
puts_optional("... OK");
gpio_ll_irq_off(port_in, PIN_IN_0);
}
struct mutex_counter {
mutex_t mutex;
unsigned counter;
};
__attribute__((unused))
static void irq_level_cb(void *_arg)
{
struct mutex_counter *arg = _arg;
if (!arg->counter) {
gpio_ll_toggle(port_out, 1UL << PIN_OUT_0);
mutex_unlock(&arg->mutex);
}
else {
arg->counter--;
}
}
static void test_irq_level(void)
{
struct mutex_counter arg = { .mutex = MUTEX_INIT_LOCKED, .counter = 10 };
if (IS_USED(MODULE_PERIPH_GPIO_LL_IRQ_LEVEL_TRIGGERED_HIGH)) {
puts_optional("Testing level-triggered on HIGH on PIN_IN_0 (when input "
"is LOW when setting up IRQ)");
gpio_ll_clear(port_out, 1UL << PIN_OUT_0);
expect(0 == gpio_ll_irq(port_in, PIN_IN_0, GPIO_TRIGGER_LEVEL_HIGH,
irq_level_cb, &arg));
/* test for spurious IRQ */
expect(-ECANCELED == ztimer_mutex_lock_timeout(ZTIMER_USEC, &arg.mutex,
mutex_timeout));
/* test for 10 level triggered IRQs on high */
gpio_ll_set(port_out, 1UL << PIN_OUT_0);
expect(0 == ztimer_mutex_lock_timeout(ZTIMER_USEC, &arg.mutex,
mutex_timeout));
/* test for spurious IRQ */
expect(-ECANCELED == ztimer_mutex_lock_timeout(ZTIMER_USEC, &arg.mutex,
mutex_timeout));
gpio_ll_irq_off(port_in, PIN_IN_0);
puts_optional("... OK");
puts_optional("Testing level-triggered on HIGH on PIN_IN_0 (when input "
"is HIGH when setting up IRQ)");
gpio_ll_set(port_out, 1UL << PIN_OUT_0);
expect(0 == gpio_ll_irq(port_in, PIN_IN_0, GPIO_TRIGGER_LEVEL_HIGH,
irq_level_cb, &arg));
/* test for 10 level triggered IRQs */
expect(0 == ztimer_mutex_lock_timeout(ZTIMER_USEC, &arg.mutex,
mutex_timeout));
/* test for spurious IRQ */
expect(-ECANCELED == ztimer_mutex_lock_timeout(ZTIMER_USEC, &arg.mutex,
mutex_timeout));
gpio_ll_irq_off(port_in, PIN_IN_0);
puts_optional("... OK");
}
if (IS_USED(MODULE_PERIPH_GPIO_LL_IRQ_LEVEL_TRIGGERED_LOW)) {
puts_optional("Testing level-triggered on LOW on PIN_IN_0 (when input "
"is HIGH when setting up IRQ)");
gpio_ll_set(port_out, 1UL << PIN_OUT_0);
arg.counter = 10;
expect(0 == gpio_ll_irq(port_in, PIN_IN_0, GPIO_TRIGGER_LEVEL_LOW,
irq_level_cb, &arg));
/* test for spurious IRQ */
expect(-ECANCELED == ztimer_mutex_lock_timeout(ZTIMER_USEC, &arg.mutex,
mutex_timeout));
/* test for 10 level triggered IRQs on low */
gpio_ll_clear(port_out, 1UL << PIN_OUT_0);
expect(0 == ztimer_mutex_lock_timeout(ZTIMER_USEC, &arg.mutex,
mutex_timeout));
/* test for spurious IRQ */
expect(-ECANCELED == ztimer_mutex_lock_timeout(ZTIMER_USEC, &arg.mutex,
mutex_timeout));
gpio_ll_irq_off(port_in, PIN_IN_0);
puts_optional("... OK");
puts_optional("Testing level-triggered on LOW on PIN_IN_0 (when input "
"is LOW when setting up IRQ)");
gpio_ll_clear(port_out, 1UL << PIN_OUT_0);
arg.counter = 10;
expect(0 == gpio_ll_irq(port_in, PIN_IN_0, GPIO_TRIGGER_LEVEL_LOW,
irq_level_cb, &arg));
/* test for 10 level triggered IRQs */
expect(0 == ztimer_mutex_lock_timeout(ZTIMER_USEC, &arg.mutex,
mutex_timeout));
/* test for spurious IRQ */
expect(-ECANCELED == ztimer_mutex_lock_timeout(ZTIMER_USEC, &arg.mutex,
mutex_timeout));
gpio_ll_irq_off(port_in, PIN_IN_0);
puts_optional("... OK");
}
}
static void test_irq(void)
{
if (IS_USED(MODULE_PERIPH_GPIO_LL_IRQ)) {
puts_optional("\n"
"Testing External IRQs\n"
"=====================\n");
expect(0 == gpio_ll_init(port_in, PIN_IN_0, &gpio_ll_in));
expect(0 == gpio_ll_init(port_out, PIN_OUT_0, &gpio_ll_out));
test_irq_edge();
test_irq_level();
}
}
int main(void)
{
print_details();
test_gpio_port_pack();
test_gpio_ll_init();
test_input_output();
if (IS_USED(MODULE_PERIPH_GPIO_LL_IRQ)) {
test_irq();
}
/* if no expect() didn't blow up until now, the test is passed */
puts("\n\nTEST SUCCEEDED");
return 0;
}

View File

@ -0,0 +1,18 @@
#!/usr/bin/env python3
# Copyright (C) 2022 Otto-von-Guericke-Universität Magdeburg
#
# This file is subject to the terms and conditions of the GNU Lesser
# General Public License v2.1. See the file LICENSE in the top level
# directory for more details.
import sys
from testrunner import run
def testfunc(child):
child.expect('TEST SUCCEEDED')
if __name__ == "__main__":
sys.exit(run(testfunc))