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:
commit
a427d630f1
@ -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"
|
||||
|
739
drivers/include/periph/gpio_ll.h
Normal file
739
drivers/include/periph/gpio_ll.h
Normal 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 */
|
||||
/** @} */
|
170
drivers/include/periph/gpio_ll_irq.h
Normal file
170
drivers/include/periph/gpio_ll_irq.h
Normal 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 */
|
||||
/** @} */
|
2
drivers/periph_common/Makefile.dep
Normal file
2
drivers/periph_common/Makefile.dep
Normal file
@ -0,0 +1,2 @@
|
||||
# Always use periph_gpio_irq_unmask, if available
|
||||
FEATURES_OPTIONAL += periph_gpio_ll_irq_unmask
|
187
drivers/periph_common/gpio_ll.c
Normal file
187
drivers/periph_common/gpio_ll.c
Normal 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);
|
16
drivers/periph_common/gpio_ll_irq.c
Normal file
16
drivers/periph_common/gpio_ll_irq.c
Normal 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);
|
||||
}
|
@ -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
5
tests/bench_periph_gpio_ll/.gitignore
vendored
Normal 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
|
42
tests/bench_periph_gpio_ll/Makefile
Normal file
42
tests/bench_periph_gpio_ll/Makefile
Normal 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)
|
8
tests/bench_periph_gpio_ll/Makefile.ci
Normal file
8
tests/bench_periph_gpio_ll/Makefile.ci
Normal file
@ -0,0 +1,8 @@
|
||||
BOARD_INSUFFICIENT_MEMORY := \
|
||||
arduino-duemilanove \
|
||||
arduino-nano \
|
||||
arduino-uno \
|
||||
atmega328p \
|
||||
atmega328p-xplained-mini \
|
||||
nucleo-l011k4 \
|
||||
#
|
40
tests/bench_periph_gpio_ll/README.md
Normal file
40
tests/bench_periph_gpio_ll/README.md
Normal 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.
|
294
tests/bench_periph_gpio_ll/main.c
Normal file
294
tests/bench_periph_gpio_ll/main.c
Normal 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;
|
||||
}
|
18
tests/bench_periph_gpio_ll/tests-with-config/01-run.py
Executable file
18
tests/bench_periph_gpio_ll/tests-with-config/01-run.py
Executable 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
5
tests/periph_gpio_ll/.gitignore
vendored
Normal 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
|
50
tests/periph_gpio_ll/Makefile
Normal file
50
tests/periph_gpio_ll/Makefile
Normal 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)
|
21
tests/periph_gpio_ll/README.md
Normal file
21
tests/periph_gpio_ll/README.md
Normal 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
BIN
tests/periph_gpio_ll/core
Normal file
Binary file not shown.
810
tests/periph_gpio_ll/main.c
Normal file
810
tests/periph_gpio_ll/main.c
Normal 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;
|
||||
}
|
18
tests/periph_gpio_ll/tests-with-config/01-run.py
Executable file
18
tests/periph_gpio_ll/tests-with-config/01-run.py
Executable 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))
|
Loading…
Reference in New Issue
Block a user