Skip to content

Commit

Permalink
Merge pull request RIOT-OS#20805 from maribu/cpu/stm32/periph_gpio_ll…
Browse files Browse the repository at this point in the history
…_periph_gpio_ll_switch_dir

cpu/stm32: implement `periph_gpio_ll_switch_dir`
  • Loading branch information
maribu authored Aug 8, 2024
2 parents a1efa07 + 8839ccb commit 4c55f92
Show file tree
Hide file tree
Showing 7 changed files with 108 additions and 22 deletions.
1 change: 1 addition & 0 deletions cpu/stm32/Makefile.features
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ FEATURES_PROVIDED += periph_uart_nonblocking

ifneq (f1,$(CPU_FAM))
FEATURES_PROVIDED += periph_gpio_ll_open_drain_pull_up
FEATURES_PROVIDED += periph_gpio_ll_switch_dir
endif

ifneq (,$(filter $(CPU_FAM),c0 f0 f1 f3 g0 g4 l0 l1 l4 l5 u5 wb wl))
Expand Down
25 changes: 25 additions & 0 deletions cpu/stm32/include/gpio_ll_arch.h
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,31 @@ static inline void gpio_ll_write(gpio_port_t port, uword_t value)
p->ODR = value;
}

#ifdef MODULE_PERIPH_GPIO_LL_SWITCH_DIR
static inline uword_t gpio_ll_prepare_switch_dir(uword_t mask)
{
/* implementation too large to always inline */
extern uword_t gpio_ll_prepare_switch_dir_impl(uword_t mask);
return gpio_ll_prepare_switch_dir_impl(mask);
}

static inline void gpio_ll_switch_dir_output(gpio_port_t port, uword_t pins)
{
GPIO_TypeDef *p = (GPIO_TypeDef *)port;
unsigned irq_state = irq_disable();
p->MODER |= pins;
irq_restore(irq_state);
}

static inline void gpio_ll_switch_dir_input(gpio_port_t port, uword_t pins)
{
GPIO_TypeDef *p = (GPIO_TypeDef *)port;
unsigned irq_state = irq_disable();
p->MODER &= ~pins;
irq_restore(irq_state);
}
#endif

static inline gpio_port_t gpio_get_port(gpio_t pin)
{
return pin & 0xfffffff0LU;
Expand Down
5 changes: 5 additions & 0 deletions cpu/stm32/include/periph/cpu_gpio_ll.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ extern "C" {
* public view on type */
#ifndef DOXYGEN

#if !defined(CPU_FAM_STM32F1)
/* For the STM32F1 GPIO peripheral, the gpio_ll_switch_dir is not supported */
# define HAVE_GPIO_LL_PREPARE_SWITCH_DIR
#endif

#define HAVE_GPIO_PULL_STRENGTH_T
typedef enum {
GPIO_PULL_WEAKEST = 0,
Expand Down
44 changes: 44 additions & 0 deletions cpu/stm32/periph/gpio_ll.c
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,50 @@ int gpio_ll_init(gpio_port_t port, uint8_t pin, gpio_conf_t conf)
return 0;
}

uword_t gpio_ll_prepare_switch_dir_impl(uword_t mask)
{
/* Mask contains a bitmask containing the pins needed to change
* the direction of. E.g. for pins 0 to 3 it looks like:
*
* 3 2 1 0
* +----+----+----+----+
* | P3 | P2 | P1 | P0 |
* +----+----+----+----+
*
* We need to update the GPIOX->MODER register, which for pins 0 to 3
* looks like this:
*
* 7 6 5 4 3 2 1 0
* +---------+---------+---------+---------+
* | MODE3 | MODE2 | MODE1 | MODE0 |
* +---------+---------+---------+---------+
*
* Where each mode field will have the value `0b00` for input or `0b01`
* for output (the others two values are for alternate function mode and
* analog mode, which are both not relevant here). So, we need a way to
* efficiently set and clear every second bit. Specifically, a bitmask
* that looks like this is our goal:
*
* 7 6 5 4 3 2 1 0
* +----+----+----+----+----+----+----+----+
* | 0 | P3 | 0 | P2 | 0 | P1 | 0 | P0 |
* +----+----+----+----+----+----+----+----+
*
* This is what below bit magic magic does (but for 16 pins instead of
* 4).
*/
uword_t output = mask & 0xFFFF;
output |= output << 8;
output &= 0x00FF00FF;
output |= output << 4;
output &= 0x0F0F0F0F;
output |= output << 2;
output &= 0x33333333;
output |= output << 1;
output &= 0x55555555;
return output;
}

gpio_conf_t gpio_ll_query_conf(gpio_port_t port, uint8_t pin)
{
gpio_conf_t result = { 0 };
Expand Down
40 changes: 23 additions & 17 deletions drivers/include/periph/gpio_ll.h
Original file line number Diff line number Diff line change
Expand Up @@ -731,29 +731,39 @@ static inline uword_t gpio_ll_prepare_write(gpio_port_t port, uword_t mask,
}
#endif

#if defined(DOXYGEN) || !defined(HAVE_GPIO_LL_PREPARE_SWITCH_DIR)
/**
* @brief Turn GPIO pins specified by the bitmask @p outputs to outputs
* @brief Prepare bitmask for use with @ref gpio_ll_switch_dir_output
* and @ref gpio_ll_switch_dir_input
* @param[in] mask bitmask specifying the pins to switch the direction of
*
* @return Value to use in @ref gpio_ll_switch_dir_output or
* @ref gpio_ll_switch_dir_input
*/
static inline uword_t gpio_ll_prepare_switch_dir(uword_t mask)
{
return mask;
}
#endif

/**
* @brief Turn GPIO pins specified by @p pins (obtained from
* @ref gpio_ll_prepare_switch_dir) to outputs
*
* @param[in] port GPIO port to modify
* @param[in] outputs Bitmask specifying the GPIO pins to set in output
* mode
* @param[in] pins Output of @ref gpio_ll_prepare_switch_dir
* @pre The feature `gpio_ll_switch_dir` is available
* @pre Each affected GPIO pin is either configured as input or as
* push-pull output.
*
* @note This is a makeshift solution to implement bit-banging of
* bidirectional protocols on less sophisticated GPIO peripherals
* that do not support open drain mode.
* @warning Use open drain mode instead, if supported.
*/
static inline void gpio_ll_switch_dir_output(gpio_port_t port, uword_t outputs);
static inline void gpio_ll_switch_dir_output(gpio_port_t port, uword_t pins);

/**
* @brief Turn GPIO pins specified by the bitmask @p inputs to inputs
* @brief Turn GPIO pins specified by @p pins (obtained from
* @ref gpio_ll_prepare_switch_dir) to inputs
*
* @param[in] port GPIO port to modify
* @param[in] inputs Bitmask specifying the GPIO pins to set in input
* mode
* @param[in] pins Output of @ref gpio_ll_prepare_switch_dir
* @pre The feature `gpio_ll_switch_dir` is available
* @pre Each affected GPIO pin is either configured as input or as
* push-pull output.
Expand All @@ -765,12 +775,8 @@ static inline void gpio_ll_switch_dir_output(gpio_port_t port, uword_t outputs);
* resistor is enabled). Hence, the bits in the output
* register of the pins switched to input should be restored
* just after this call.
* @note This is a makeshift solution to implement bit-banging of
* bidirectional protocols on less sophisticated GPIO peripherals
* that do not support open drain mode.
* @warning Use open drain mode instead, if supported.
*/
static inline void gpio_ll_switch_dir_input(gpio_port_t port, uword_t inputs);
static inline void gpio_ll_switch_dir_input(gpio_port_t port, uword_t pins);

/**
* @brief Perform a masked write operation on the I/O register of the port
Expand Down
6 changes: 3 additions & 3 deletions features.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -567,9 +567,9 @@ groups:
help: The GPIO LL driver allows switching the direction between input
and (push-pull) output in an efficient manner. The main use case
is bit-banging bidirectional protocols when open-drain / open-source
mode is not supported. GPIO LL drivers for peripherals that do
support open drain mode typically do not bother implementing this,
even if the hardware would allow it.
mode is not supported. Another use case is controlling GPIOs
at high speed with three output states (high, low, high impedance),
as e.g. needed for Charlieplexing

- title: Serial Interfaces
help: Features related to serial interfaces
Expand Down
9 changes: 7 additions & 2 deletions tests/periph/gpio_ll/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -905,6 +905,7 @@ static void test_switch_dir(void)
"===========================\n");

uword_t mask_out = 1U << PIN_OUT_0;
uword_t pins_out = gpio_ll_prepare_switch_dir(mask_out);
uword_t mask_in = 1U << PIN_IN_0;

/* floating input must be supported by every MCU */
Expand All @@ -924,23 +925,25 @@ static void test_switch_dir(void)
uword_t out_state = gpio_ll_read_output(port_out);

/* now, switch to output mode and verify the switch */
gpio_ll_switch_dir_output(port_out, mask_out);
gpio_ll_switch_dir_output(port_out, pins_out);
conf = gpio_ll_query_conf(port_out, PIN_OUT_0);
test_passed = (conf.state == GPIO_OUTPUT_PUSH_PULL);
printf_optional("Input pin can be switched to output (push-pull) mode: %s\n",
noyes[test_passed]);
expect(test_passed);

gpio_ll_clear(port_out, mask_out);
ztimer_sleep(ZTIMER_USEC, US_PER_MS);
test_passed = (0 == (gpio_ll_read(port_in) & mask_in));
gpio_ll_set(port_out, mask_out);
ztimer_sleep(ZTIMER_USEC, US_PER_MS);
test_passed = test_passed && (gpio_ll_read(port_in) & mask_in);
printf_optional("Pin behaves as output after switched to output mode: %s\n",
noyes[test_passed]);
expect(test_passed);

/* switch back to input mode */
gpio_ll_switch_dir_input(port_out, mask_out);
gpio_ll_switch_dir_input(port_out, pins_out);
/* restore out state from before the switch */
gpio_ll_write(port_out, out_state);
/* verify we are back at the old config */
Expand All @@ -964,8 +967,10 @@ static void test_switch_dir(void)
expect(0 == gpio_ll_init(port_in, PIN_IN_0, gpio_ll_out));

gpio_ll_clear(port_in, mask_in);
ztimer_sleep(ZTIMER_USEC, US_PER_MS);
test_passed = (0 == (gpio_ll_read(port_out) & mask_out));
gpio_ll_set(port_in, mask_in);
ztimer_sleep(ZTIMER_USEC, US_PER_MS);
test_passed = test_passed && (gpio_ll_read(port_out) & mask_out);
printf_optional("Pin behaves as input after switched back to input mode: %s\n",
noyes[test_passed]);
Expand Down

0 comments on commit 4c55f92

Please sign in to comment.