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

Merge pull request #20805 from maribu/cpu/stm32/periph_gpio_ll_periph_gpio_ll_switch_dir

cpu/stm32: implement `periph_gpio_ll_switch_dir`
This commit is contained in:
Marian Buschsieweke 2024-08-08 20:34:52 +00:00 committed by GitHub
commit 4c55f92b1b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 108 additions and 22 deletions

View File

@ -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))

View File

@ -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;

View File

@ -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,

View File

@ -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 };

View File

@ -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.
@ -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

View File

@ -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

View File

@ -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 */
@ -924,7 +925,7 @@ 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",
@ -932,15 +933,17 @@ static void test_switch_dir(void)
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 */
@ -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]);