diff --git a/boards/common/e104-bt50xxa-tb/include/board.h b/boards/common/e104-bt50xxa-tb/include/board.h index 058aadd64399..97ab45a0af2d 100644 --- a/boards/common/e104-bt50xxa-tb/include/board.h +++ b/boards/common/e104-bt50xxa-tb/include/board.h @@ -56,6 +56,14 @@ extern "C" { #define BTN1_MODE GPIO_IN_PU /** @} */ +/** + * @name WS281x RGB LED configuration + * @{ + */ +#define WS281X_TIMER_DEV TIMER_DEV(1) /**< Timer device */ +#define WS281X_TIMER_MAX_VALUE TIMER_1_MAX_VALUE /**< Timer max value */ +/** @} */ + #ifdef __cplusplus } #endif diff --git a/boards/common/nrf51/include/cfg_timer_01.h b/boards/common/nrf51/include/cfg_timer_01.h index eabd5af07d0e..5c04d9e4ac88 100644 --- a/boards/common/nrf51/include/cfg_timer_01.h +++ b/boards/common/nrf51/include/cfg_timer_01.h @@ -49,6 +49,11 @@ static const timer_conf_t timer_config[] = { #define TIMER_0_ISR isr_timer0 #define TIMER_1_ISR isr_timer1 +/** See @ref timer_init */ +#define TIMER_0_MAX_VALUE 0xffffffff +/** See @ref timer_init */ +#define TIMER_1_MAX_VALUE 0xffffffff + #define TIMER_NUMOF ARRAY_SIZE(timer_config) /** @} */ diff --git a/boards/common/nrf51/include/cfg_timer_012.h b/boards/common/nrf51/include/cfg_timer_012.h index 985e0a9e510d..bf1770c238d8 100644 --- a/boards/common/nrf51/include/cfg_timer_012.h +++ b/boards/common/nrf51/include/cfg_timer_012.h @@ -56,6 +56,13 @@ static const timer_conf_t timer_config[] = { #define TIMER_1_ISR isr_timer1 #define TIMER_2_ISR isr_timer2 +/** See @ref timer_init */ +#define TIMER_0_MAX_VALUE 0xffffffff +/** See @ref timer_init */ +#define TIMER_1_MAX_VALUE 0xffffffff +/** See @ref timer_init */ +#define TIMER_2_MAX_VALUE 0xffffffff + #define TIMER_NUMOF ARRAY_SIZE(timer_config) /** @} */ diff --git a/boards/common/nrf52/include/cfg_timer_default.h b/boards/common/nrf52/include/cfg_timer_default.h index e82b17fdb80e..2931a014c338 100644 --- a/boards/common/nrf52/include/cfg_timer_default.h +++ b/boards/common/nrf52/include/cfg_timer_default.h @@ -80,6 +80,21 @@ static const timer_conf_t timer_config[] = { #define TIMER_2_ISR isr_timer3 #define TIMER_3_ISR isr_timer4 +/** See @ref timer_init */ +#define TIMER_0_MAX_VALUE 0xffffffff +/** See @ref timer_init */ +#define TIMER_1_MAX_VALUE 0xffffffff +#ifdef NRF_TIMER3 +/** See @ref timer_init */ +#define TIMER_2_MAX_VALUE 0xffffffff +#endif +/* If there is no NRF_TIMER3 this should be TIMER_2 because the index shifts + * up, but there is only a TIMER4 if there is a TIMER3 too. */ +#ifdef NRF_TIMER4 +/** See @ref timer_init */ +#define TIMER_3_MAX_VALUE 0xffffffff +#endif + #define TIMER_NUMOF ARRAY_SIZE(timer_config) /** @} */ diff --git a/boards/nrf9160dk/include/board.h b/boards/nrf9160dk/include/board.h index 8010a4deeb45..b94fd399842a 100644 --- a/boards/nrf9160dk/include/board.h +++ b/boards/nrf9160dk/include/board.h @@ -87,6 +87,14 @@ extern "C" { #define BTN3_MODE GPIO_IN /**< BTN3 default mode */ /** @} */ +/** + * @name WS281x RGB LED configuration + * @{ + */ +#define WS281X_TIMER_DEV TIMER_DEV(1) /**< Timer device */ +#define WS281X_TIMER_MAX_VALUE TIMER_1_MAX_VALUE /**< Timer max value */ +/** @} */ + #ifdef __cplusplus } #endif diff --git a/boards/nrf9160dk/include/periph_conf.h b/boards/nrf9160dk/include/periph_conf.h index cdca44782f2c..b157bf368b90 100644 --- a/boards/nrf9160dk/include/periph_conf.h +++ b/boards/nrf9160dk/include/periph_conf.h @@ -83,6 +83,11 @@ static const timer_conf_t timer_config[] = { #define TIMER_0_ISR isr_timer0 /**< Timer0 IRQ*/ #define TIMER_1_ISR isr_timer1 /**< Timer1 IRQ */ +/** See @ref timer_init */ +#define TIMER_0_MAX_VALUE 0xffffffff +/** See @ref timer_init */ +#define TIMER_1_MAX_VALUE 0xffffffff + #define TIMER_NUMOF ARRAY_SIZE(timer_config) /**< Timer configuration NUMOF */ /** @} */ diff --git a/cpu/nrf53/Kconfig b/cpu/nrf53/Kconfig index e86107a0529b..9d62961a3dc4 100644 --- a/cpu/nrf53/Kconfig +++ b/cpu/nrf53/Kconfig @@ -16,6 +16,7 @@ config CPU_FAM_NRF53 select HAS_PERIPH_GPIO select HAS_PERIPH_GPIO_IRQ select HAS_PERIPH_TIMER_PERIODIC + select HAS_PERIPH_TIMER_POLL select HAS_PERIPH_TIMER_QUERY_FREQS select HAS_PERIPH_UART_MODECFG select HAS_PERIPH_WDT diff --git a/cpu/nrf5x_common/Kconfig b/cpu/nrf5x_common/Kconfig index 9a9a8d7736a4..8e715bb39ef2 100644 --- a/cpu/nrf5x_common/Kconfig +++ b/cpu/nrf5x_common/Kconfig @@ -20,6 +20,7 @@ depends on !CPU_FAM_NRF53 select HAS_PERIPH_HWRNG select HAS_PERIPH_TEMPERATURE select HAS_PERIPH_TIMER_PERIODIC + select HAS_PERIPH_TIMER_POLL select HAS_PERIPH_TIMER_QUERY_FREQS select HAS_PERIPH_RTT_OVERFLOW select HAS_PERIPH_UART_MODECFG diff --git a/cpu/nrf5x_common/Makefile.features b/cpu/nrf5x_common/Makefile.features index 0bfe5661f356..7f6fdfdd07ab 100644 --- a/cpu/nrf5x_common/Makefile.features +++ b/cpu/nrf5x_common/Makefile.features @@ -5,6 +5,7 @@ FEATURES_PROVIDED += periph_flashpage_in_address_space FEATURES_PROVIDED += periph_flashpage_pagewise FEATURES_PROVIDED += periph_gpio periph_gpio_irq FEATURES_PROVIDED += periph_timer_periodic +FEATURES_PROVIDED += periph_timer_poll FEATURES_PROVIDED += periph_timer_query_freqs FEATURES_PROVIDED += periph_uart_modecfg FEATURES_PROVIDED += periph_wdt periph_wdt_cb diff --git a/cpu/nrf5x_common/include/timer_arch.h b/cpu/nrf5x_common/include/timer_arch.h new file mode 100644 index 000000000000..325145215888 --- /dev/null +++ b/cpu/nrf5x_common/include/timer_arch.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2015 Jan Wagner + * 2015-2016 Freie Universität Berlin + * 2019 Inria + * + * 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 cpu_nrf5x_common + * @ingroup drivers_periph_timer + * @{ + * + * @file + * @brief CPU specific part of the timer API + * + * @author Christian Amsüss + */ + +#ifndef TIMER_ARCH_H +#define TIMER_ARCH_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef DOXYGEN /* hide implementation specific details from Doxygen */ + +static inline bool timer_poll_channel(tim_t tim, int channel) +{ + return timer_config[tim].dev->EVENTS_COMPARE[channel]; +} + +#endif /* DOXYGEN */ +#ifdef __cplusplus +} +#endif + +#endif /* TIMER_ARCH_H */ +/** @} */ diff --git a/cpu/nrf5x_common/periph/timer.c b/cpu/nrf5x_common/periph/timer.c index 556ead9b63be..ae944867d57f 100644 --- a/cpu/nrf5x_common/periph/timer.c +++ b/cpu/nrf5x_common/periph/timer.c @@ -111,7 +111,9 @@ int timer_init(tim_t tim, uint32_t freq, timer_cb_t cb, void *arg) } /* enable interrupts */ - NVIC_EnableIRQ(timer_config[tim].irqn); + if (cb != NULL) { + NVIC_EnableIRQ(timer_config[tim].irqn); + } /* start the timer */ dev(tim)->TASKS_START = 1; diff --git a/cpu/nrf9160/Kconfig b/cpu/nrf9160/Kconfig index e90ad52ea331..990e74dd210b 100644 --- a/cpu/nrf9160/Kconfig +++ b/cpu/nrf9160/Kconfig @@ -17,6 +17,7 @@ config CPU_FAM_NRF9160 select HAS_PERIPH_GPIO_LL_IRQ select HAS_PERIPH_GPIO_LL_IRQ_UNMASK select HAS_PERIPH_TIMER_PERIODIC + select HAS_PERIPH_TIMER_POLL select HAS_PERIPH_TIMER_QUERY_FREQS select HAS_PERIPH_UART_MODECFG select HAS_PERIPH_SPI_GPIO_MODE diff --git a/drivers/include/periph/timer.h b/drivers/include/periph/timer.h index c4c021aba1fa..e9321c196373 100644 --- a/drivers/include/periph/timer.h +++ b/drivers/include/periph/timer.h @@ -35,6 +35,7 @@ #include #include +#include #include "architecture.h" #include "periph_cpu.h" @@ -295,6 +296,39 @@ uword_t timer_query_channel_numof(tim_t dev); */ uint32_t timer_query_freqs(tim_t dev, uword_t index); +#if defined(DOXYGEN) +/** + * @brief Check whether a compare channel has matched + * + * @return true once after the channel has matched. + * + * It is currently not defined whether this keeps returning true after a + * channel has been polled until that channel is set, or whether later calls + * return false. + * + * This is typically used in spin loops that wait for a timer's completion: + * + * ~~~ + * while (!timer_poll_channel(tim, chan)) {}; + * ~~~ + * + * This function is only available on platforms that implement the + * `periph_timer_poll` peripheral in addition to `periph_timer`. + * + */ +/* As this function is polled, it needs to be inlined, so it is typically + * provided through timer_arch.h. If a platform ever does not need to go + * through static inline here, this declaration's condition can be extended to + * be `(defined(MODULE_PERIPH_TIMER_POLL) && + * !defined(PERIPH_TIMER_PROVIDES_INLINE_POLL_CHANNEL) || defined(DOXYGEN)` or + * similar. */ +bool timer_poll_channel(tim_t dev, int channel); +#endif + +#if defined(MODULE_PERIPH_TIMER_POLL) +#include "timer_arch.h" +#endif + #ifdef __cplusplus } #endif diff --git a/drivers/periph_common/Kconfig b/drivers/periph_common/Kconfig index 564a6283add5..c9eae6d7e9a3 100644 --- a/drivers/periph_common/Kconfig +++ b/drivers/periph_common/Kconfig @@ -78,6 +78,8 @@ rsource "Kconfig.flashpage" rsource "Kconfig.gpio" +rsource "Kconfig.gpio_ll" + config MODULE_PERIPH_HWRNG bool "HWRNG peripheral driver" depends on HAS_PERIPH_HWRNG @@ -157,6 +159,12 @@ config MODULE_PERIPH_INIT_RTT default y if MODULE_PERIPH_INIT depends on MODULE_PERIPH_RTT +config MODULE_PERIPH_TIMER_POLL + bool "Timer poll" + depends on HAS_PERIPH_TIMER_POLL + help + Enables the timer_poll_channel function. + rsource "Kconfig.sdmmc" rsource "Kconfig.spi" diff --git a/drivers/periph_common/Kconfig.gpio_ll b/drivers/periph_common/Kconfig.gpio_ll new file mode 100644 index 000000000000..55e0b2263147 --- /dev/null +++ b/drivers/periph_common/Kconfig.gpio_ll @@ -0,0 +1,22 @@ +# Copyright (c) 2023 HAW Hamburg +# +# 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. +# + +menuconfig MODULE_PERIPH_GPIO_LL + bool "Low-level GPIO peripheral driver" + depends on HAS_PERIPH_GPIO_LL + +if MODULE_PERIPH_GPIO_LL + +config MODULE_PERIPH_GPIO_LL_IRQ_UNMASK + bool "Unmask GPIO peripheral interrupts" + default y + depends on HAS_PERIPH_GPIO_LL_IRQ_UNMASK + help + Enables GPIO peripheral unmasking interrupts without + clearing pending IRQs that came in while masked. + +endif # MODULE_PERIPH_GPIO_LL diff --git a/drivers/ws281x/Kconfig b/drivers/ws281x/Kconfig index ef44ba5f5e16..d8bda4366a15 100644 --- a/drivers/ws281x/Kconfig +++ b/drivers/ws281x/Kconfig @@ -7,12 +7,13 @@ config MODULE_WS281X bool "WS2812/SK6812 RGB LED (NeoPixel)" - depends on HAS_CPU_CORE_ATMEGA || HAS_ARCH_ESP32 || HAS_ARCH_NATIVE + depends on HAS_CPU_CORE_ATMEGA || HAS_ARCH_ESP32 || HAS_ARCH_NATIVE || HAS_PERIPH_TIMER_POLL depends on TEST_KCONFIG select MODULE_XTIMER select MODULE_WS281X_ATMEGA if HAS_CPU_CORE_ATMEGA select MODULE_WS281X_VT100 if HAS_ARCH_NATIVE select MODULE_WS281X_ESP32 if HAS_ARCH_ESP32 + select MODULE_WS281X_TIMER_GPIO_LL if HAS_PERIPH_TIMER_POLL config MODULE_WS281X_ATMEGA bool @@ -45,6 +46,16 @@ config MODULE_WS281X_ESP32_SW Use the bit-banging software implementation to generate the RGB LED signal. +config MODULE_WS281X_TIMER_GPIO_LL + bool + depends on HAS_PERIPH_TIMER_POLL + select MODULE_PERIPH_TIMER_POLL + depends on HAS_PERIPH_GPIO_LL + select MODULE_PERIPH_GPIO_LL + help + Use a platform independent bit-banging software implementation to + generate the RGB LED signal. + config HAVE_WS281X bool help diff --git a/drivers/ws281x/Makefile.dep b/drivers/ws281x/Makefile.dep index 76ef1f0b323f..f539c4de83d7 100644 --- a/drivers/ws281x/Makefile.dep +++ b/drivers/ws281x/Makefile.dep @@ -1,4 +1,5 @@ -FEATURES_REQUIRED_ANY += cpu_core_atmega|arch_esp32|arch_native +# Actually |(periph_timer_poll and periph_gpio_ll), but that's too complex for FEATURES_REQUIRED_ANY to express +FEATURES_REQUIRED_ANY += cpu_core_atmega|arch_esp32|arch_native|periph_timer_poll ifeq (,$(filter ws281x_%,$(USEMODULE))) ifneq (,$(filter cpu_core_atmega,$(FEATURES_USED))) @@ -10,6 +11,10 @@ ifeq (,$(filter ws281x_%,$(USEMODULE))) ifneq (,$(filter arch_esp32,$(FEATURES_USED))) USEMODULE += ws281x_esp32 endif + # Not only looking for the used feature but also for the absence of any more specific driver + ifeq (-periph_timer_poll,$(filter ws281x_%,$(USEMODULE))-$(filter periph_timer_poll,$(FEATURES_USED))) + USEMODULE += ws281x_timer_gpio_ll + endif endif ifneq (,$(filter ws281x_atmega,$(USEMODULE))) @@ -28,3 +33,7 @@ ifneq (,$(filter ws281x_esp32%,$(USEMODULE))) USEMODULE += ws281x_esp32_hw endif endif + +ifneq (,$(filter ws281x_timer_gpio_ll,$(USEMODULE))) + FEATURES_REQUIRED += periph_gpio_ll periph_timer periph_timer_poll +endif diff --git a/drivers/ws281x/include/ws281x_backend.h b/drivers/ws281x/include/ws281x_backend.h index 888c279a9045..01f74fb87789 100644 --- a/drivers/ws281x/include/ws281x_backend.h +++ b/drivers/ws281x/include/ws281x_backend.h @@ -51,6 +51,15 @@ extern "C" { #endif /** @} */ +/** + * @name Properties of the timer_gpio_ll backend. + * @{ + */ +#ifdef MODULE_WS281X_TIMER_GPIO_LL +#define WS281X_HAVE_INIT (1) +#endif +/** @} */ + #ifdef __cplusplus } #endif diff --git a/drivers/ws281x/include/ws281x_params.h b/drivers/ws281x/include/ws281x_params.h index 1950e3f4ebc1..e68b8f91839b 100644 --- a/drivers/ws281x/include/ws281x_params.h +++ b/drivers/ws281x/include/ws281x_params.h @@ -64,6 +64,47 @@ static const ws281x_params_t ws281x_params[] = WS281X_PARAMS }; +/** @brief Timer used for WS281x (by the timer_gpio_ll implementation) + * + * A single timer is configured for any number of WS281x strands, so this does + * not need to be part of params. + * + * It is required that the timer has at least 2 channels. (Future versions may + * require a 3rd channel). + * + * It is required that the timer's MAX_VALUE is 2^n-1, which is a trivial but + * not explicitly stated case. + * + * This timer is configured at WS281x initialization time, and kept stopped + * outside of transmissions. + * + * The default value of 2 is chosen because the only platform on which the + * module is usable is nRF5x, where TIMER_DEV(1) is in use by the radio module. + * It is strongly advised to explicitly set this timer to a known free timer, + * as the default may change without notice. + * */ +#if !defined(WS281X_TIMER_DEV) || defined(DOXYGEN) +#define WS281X_TIMER_DEV TIMER_DEV(2) +#endif + +/** @brief Maximum value of the timer used for WS281x (by the timer_gpio_ll implementation) + * + * This macro needs to be defined to the `TIMER_x_MAX_VALUE` corresponding to + * the `TIMER_DEV(x)` in @ref WS281X_TIMER_DEV. + * */ +#ifndef WS281X_TIMER_MAX_VALUE +#define WS281X_TIMER_MAX_VALUE TIMER_2_MAX_VALUE +#endif + +/** @brief Frequency for the timer used for WS281x (by the timer_gpio_ll implementation) + * + * This should be set to a frequency that is a close multiple of 3MHz, + * depending on the precise low and high times. A value of 16MHz works well. + * */ +#ifndef WS281X_TIMER_FREQ +#define WS281X_TIMER_FREQ 16000000 +#endif + #ifdef __cplusplus } #endif diff --git a/drivers/ws281x/timer_gpio_ll.c b/drivers/ws281x/timer_gpio_ll.c new file mode 100644 index 000000000000..652f5af0ae4f --- /dev/null +++ b/drivers/ws281x/timer_gpio_ll.c @@ -0,0 +1,169 @@ +/* + * Copyright 2023 Christian Amsüss + * + * 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 drivers_ws281x + * + * @{ + * + * @file + * @brief Implementation of the WS281x abstraction based on GPIO_LL and timers + * + * When this is used, the whole @ref ws281x_write_buffer operation is conducted + * in a single interrupts-disabled run. As it takes about 1us per bit (eg. + * 0.3ms on a 100-LED strip), the use of this module needs to be carefully + * evaluated against other real-time requirements. + * + * (Letting the trailing pause run with interrupts enabled, or even enabling + * interrupts between bits, is an option that is being considered for future + * expansion). + * + * @author Christian Amsüss + * + * @} + */ +#include +#include +#include +#include + +#include "irq.h" +#include "time_units.h" +#include "periph/timer.h" +#include "periph/gpio_ll.h" + +#include "ws281x.h" +#include "ws281x_params.h" +#include "ws281x_constants.h" + +#define ENABLE_DEBUG 0 +#include "debug.h" + +/* (+ NS_PER_SEC - 1): Rounding up, as T1H is the time that needs to distinctly + * longer than T0H. + * + * Then adding +1 extra, because the spin loop adds another layer of jitter. A + * more correct version would be to add the spin loop time before rounding (and + * then rounding up), but as that time is not available, spending one more + * cycle is the next best thing to do. */ +const int ticks_one = ((uint64_t)WS281X_T_DATA_ONE_NS * WS281X_TIMER_FREQ + NS_PER_SEC - 1) + / NS_PER_SEC + 1; +/* Rounding down, zeros are better shorter */ +const int ticks_zero = (uint64_t)WS281X_T_DATA_ZERO_NS * (uint64_t)WS281X_TIMER_FREQ / NS_PER_SEC; +/* No particular known requirements, but we're taking longer than that anyway + * because we don't clock the times between bits. */ +const int ticks_data = (uint64_t)WS281X_T_DATA_NS * (uint64_t)WS281X_TIMER_FREQ / NS_PER_SEC; + +void ws281x_write_buffer(ws281x_t *dev, const void *buf, size_t size) +{ + assert(dev); + + const uint8_t *pos = buf; + const uint8_t *end = pos + size; + + /* The logical sequence for a cycle would be to set high, sleep, set low, + * sleep. But the critical time is the high time, so we do something + * different: We go low, set up the timer, and then go high and back low + * both as controlled by the timer. That means that our logical cycles are + * the previous bit's low time followed by the current bit's high time -- so we store the previous bit here. + * + * It is initialized to 1 because the 1 has the longer high time, so the + * lower low time -- we just spend 325 instead of 600ns before actually + * sending the message. (This wait-before-we-start time is also part of how + * this implementation can get away without compensation for the time it + * takes to set up timers: it sets them up and then waits for some time + * that is known to be long enough, because that time also works + * mid-transmission). */ + bool last_bit = 1; + + gpio_port_t port = gpio_get_port(dev->params.pin); + uword_t mask = 1 << gpio_get_pin_num(dev->params.pin); + + /* If a too-slow channel is used, then the critical section could be done + * away with: If an interrupt fires (or a higher priority thread runs; + * really: runs long enough to matter, but that'd need very fast switching + * and hundreds of MHz to be the case), the function will return with an + * error (or ignore the error), and the worst thing that'd happen is that + * if it hits the last bit of a LED, that might be flipped to a value 1. + * */ + unsigned int irq_state = irq_disable(); + + while (pos < end) { + uint8_t data = *pos; + for (uint8_t cnt = 8; cnt > 0; cnt--) { + bool bit = data >> 7; + data <<= 1; + int time_to_set = last_bit ? (ticks_data - ticks_one) : (ticks_data - ticks_zero); + int time_to_clear = time_to_set + (bit ? ticks_one : ticks_zero); + + /* I'd prefer to just zero the timer, but we don't have API for that */ + int now = timer_read(WS281X_TIMER_DEV); + time_to_set = (time_to_set + now) & WS281X_TIMER_MAX_VALUE; + time_to_clear = (time_to_clear + now) & WS281X_TIMER_MAX_VALUE; + + timer_set_absolute(WS281X_TIMER_DEV, 0, time_to_set); + timer_set_absolute(WS281X_TIMER_DEV, 1, time_to_clear); + timer_start(WS281X_TIMER_DEV); + /* A + * ~~~ + * while ((int)timer_read(WS281X_TIMER_DEV) < time_to_set) {}; + * ~~~ + * would be way too slow, plus it'd impose additional requirements + * on the timer to avoid wrapping */ + while (!timer_poll_channel(WS281X_TIMER_DEV, 0)) {} + gpio_ll_set(port, mask); + while (!timer_poll_channel(WS281X_TIMER_DEV, 1)) {} + gpio_ll_clear(port, mask); + timer_stop(WS281X_TIMER_DEV); + + last_bit = bit; + } + pos++; + } + + irq_restore(irq_state); +} + +int ws281x_init(ws281x_t *dev, const ws281x_params_t *params) +{ + int err; + + if (!dev || !params || !params->buf) { + return -EINVAL; + } + + memset(dev, 0, sizeof(ws281x_t)); + dev->params = *params; + + gpio_port_t port = gpio_get_port(dev->params.pin); + uint8_t pin = gpio_get_pin_num(dev->params.pin); + + err = gpio_ll_init(port, pin, &gpio_ll_out); + DEBUG("Initializing port %x pin %d (originally %x): %d\n", + port, pin, params->pin, err); + if (err != 0) { + return -EIO; + } + + err = timer_init(WS281X_TIMER_DEV, WS281X_TIMER_FREQ, NULL, NULL); + DEBUG("Initialized timer to %d Hz: %d\n", WS281X_TIMER_FREQ, err); + if (err != 0) { + return -EIO; + } + timer_stop(WS281X_TIMER_DEV); + + /* We're not trying to make an assessment on whether that means we can + * manage or not, for that also depends on the number of instructions in + * the loop, which adds to the granularity. (Really the relevant time is + * the duration converted back from ticks to time, rounded up or down + * randomly to the duration of the loop). */ + DEBUG("At this speed, ones are %d ticks long and zeros %d ticks of a %d " + "tick transmission.\n", ticks_one, ticks_zero, ticks_data); + + return 0; +} diff --git a/kconfigs/Kconfig.features b/kconfigs/Kconfig.features index b867d180a444..7614d72a8530 100644 --- a/kconfigs/Kconfig.features +++ b/kconfigs/Kconfig.features @@ -572,6 +572,12 @@ config HAS_PERIPH_TIMER_PERIODIC Indicates that the Timer peripheral provides the periodic timeout functionality. +config HAS_PERIPH_TIMER_POLL + bool + help + Indicates that the Timer peripheral supports the timer_poll_channel + function. + config HAS_PERIPH_TIMER_QUERY_FREQS bool help diff --git a/makefiles/features_modules.inc.mk b/makefiles/features_modules.inc.mk index 77e344a8087f..c9b958c3af18 100644 --- a/makefiles/features_modules.inc.mk +++ b/makefiles/features_modules.inc.mk @@ -47,6 +47,7 @@ PERIPH_IGNORE_MODULES := \ periph_rtt_hw_rtc \ periph_rtt_hw_sys \ periph_spi_on_qspi \ + periph_timer_poll \ periph_timer_query_freqs \ periph_uart_collision \ periph_uart_rxstart_irq \ diff --git a/tests/drivers/ws281x/Makefile b/tests/drivers/ws281x/Makefile index 008d0033a18b..a8def7989520 100644 --- a/tests/drivers/ws281x/Makefile +++ b/tests/drivers/ws281x/Makefile @@ -4,6 +4,10 @@ include ../Makefile.drivers_common # Update this to your needs # PIN ?= GPIO_PIN(0, 0) # N ?= 8 +# When using the ws281x_timer_gpio_ll module, you'll also need to define those: +# (Example values are suitable for nRF52 devices) +# TIMER ?= 2 +# FREQ ?= 16000000 USEMODULE += ws281x USEMODULE += xtimer @@ -22,3 +26,9 @@ endif ifneq (, $(N)) CFLAGS += '-DWS281X_PARAM_NUMOF=$(N)' endif +ifneq (, $(TIMER)) + CFLAGS += '-DWS281X_TIMER_DEV=TIMER_DEV($(TIMER))' '-DWS281X_TIMER_MAX_VALUE=TIMER_$(TIMER)_MAX_VALUE' +endif +ifneq (, $(FREQ)) + CFLAGS += '-DWS281X_TIMER_FREQ=$(FREQ)' +endif