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:
commit
4c55f92b1b
@ -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))
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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 };
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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]);
|
||||
|
Loading…
Reference in New Issue
Block a user