mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
Merge pull request #19891 from chrysn-pull-requests/ws2181x_timer
drivers/ws281x: Add gpio_ll and timer based driver
This commit is contained in:
commit
f860d96a25
@ -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
|
||||
|
@ -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)
|
||||
/** @} */
|
||||
|
||||
|
@ -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)
|
||||
/** @} */
|
||||
|
||||
|
@ -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)
|
||||
/** @} */
|
||||
|
||||
|
@ -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
|
||||
|
@ -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 */
|
||||
/** @} */
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
42
cpu/nrf5x_common/include/timer_arch.h
Normal file
42
cpu/nrf5x_common/include/timer_arch.h
Normal file
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jan Wagner <mail@jwagner.eu>
|
||||
* 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 <chrysn@fsfe.org>
|
||||
*/
|
||||
|
||||
#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 */
|
||||
/** @} */
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
@ -35,6 +35,7 @@
|
||||
|
||||
#include <limits.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#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
|
||||
|
@ -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"
|
||||
|
||||
|
22
drivers/periph_common/Kconfig.gpio_ll
Normal file
22
drivers/periph_common/Kconfig.gpio_ll
Normal file
@ -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
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
169
drivers/ws281x/timer_gpio_ll.c
Normal file
169
drivers/ws281x/timer_gpio_ll.c
Normal file
@ -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 <chrysn@fsfe.org>
|
||||
*
|
||||
* @}
|
||||
*/
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#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;
|
||||
}
|
@ -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
|
||||
|
@ -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 \
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user