Merge pull request #12693 from maribu/neopixel-atmega
drivers: Added WS281x RGB LED driver for ATmega platform
This commit is contained in:
commit
09f647eee2
102
cpu/atmega_common/include/atmega_gpio.h
Normal file
102
cpu/atmega_common/include/atmega_gpio.h
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2015 HAW Hamburg
|
||||||
|
* 2016 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_atmega_common
|
||||||
|
* @ingroup drivers_periph_gpio
|
||||||
|
* @{
|
||||||
|
*
|
||||||
|
* @file
|
||||||
|
* @brief Macros and inline functions for accessing GPIOs of the ATmega
|
||||||
|
* family
|
||||||
|
*
|
||||||
|
* @author René Herthel <rene-herthel@outlook.de>
|
||||||
|
* @author Francisco Acosta <francisco.acosta@inria.fr>
|
||||||
|
* @author Laurent Navet <laurent.navet@gmail.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef ATMEGA_GPIO_H
|
||||||
|
#define ATMEGA_GPIO_H
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include <avr/interrupt.h>
|
||||||
|
|
||||||
|
#include "cpu.h"
|
||||||
|
#include "board.h"
|
||||||
|
#include "periph/gpio.h"
|
||||||
|
#include "periph_conf.h"
|
||||||
|
#include "periph_cpu.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define ATMEGA_GPIO_BASE_PORT_A (0x20)
|
||||||
|
#define ATMEGA_GPIO_OFFSET_PORT_H (0xCB)
|
||||||
|
#define ATMEGA_GPIO_OFFSET_PIN_PORT (0x02)
|
||||||
|
#define ATMEGA_GPIO_OFFSET_PIN_PIN (0x03)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Extract the pin number of the given pin
|
||||||
|
*/
|
||||||
|
static inline uint8_t atmega_pin_num(gpio_t pin)
|
||||||
|
{
|
||||||
|
return (pin & 0x0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Extract the port number of the given pin
|
||||||
|
*/
|
||||||
|
static inline uint8_t atmega_port_num(gpio_t pin)
|
||||||
|
{
|
||||||
|
return (pin >> 4) & 0x0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Generate the PORTx address of the give pin.
|
||||||
|
*/
|
||||||
|
static inline uint16_t atmega_port_addr(gpio_t pin)
|
||||||
|
{
|
||||||
|
uint8_t port_num = atmega_port_num(pin);
|
||||||
|
uint16_t port_addr = port_num * ATMEGA_GPIO_OFFSET_PIN_PIN;
|
||||||
|
|
||||||
|
port_addr += ATMEGA_GPIO_BASE_PORT_A;
|
||||||
|
port_addr += ATMEGA_GPIO_OFFSET_PIN_PORT;
|
||||||
|
|
||||||
|
#if defined (PORTG)
|
||||||
|
if (port_num > PORT_G) {
|
||||||
|
port_addr += ATMEGA_GPIO_OFFSET_PORT_H;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return port_addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Generate the DDRx address of the given pin
|
||||||
|
*/
|
||||||
|
static inline uint16_t atmega_ddr_addr(gpio_t pin)
|
||||||
|
{
|
||||||
|
return (atmega_port_addr(pin) - 0x01);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Generate the PINx address of the given pin.
|
||||||
|
*/
|
||||||
|
static inline uint16_t atmega_pin_addr(gpio_t pin)
|
||||||
|
{
|
||||||
|
return (atmega_port_addr(pin) - 0x02);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* ATMEGA_GPIO_H */
|
||||||
|
/** @} */
|
||||||
@ -35,15 +35,11 @@
|
|||||||
#include "periph/gpio.h"
|
#include "periph/gpio.h"
|
||||||
#include "periph_conf.h"
|
#include "periph_conf.h"
|
||||||
#include "periph_cpu.h"
|
#include "periph_cpu.h"
|
||||||
|
#include "atmega_gpio.h"
|
||||||
|
|
||||||
#define ENABLE_DEBUG (0)
|
#define ENABLE_DEBUG (0)
|
||||||
#include "debug.h"
|
#include "debug.h"
|
||||||
|
|
||||||
#define GPIO_BASE_PORT_A (0x20)
|
|
||||||
#define GPIO_OFFSET_PORT_H (0xCB)
|
|
||||||
#define GPIO_OFFSET_PIN_PORT (0x02)
|
|
||||||
#define GPIO_OFFSET_PIN_PIN (0x03)
|
|
||||||
|
|
||||||
#ifdef MODULE_PERIPH_GPIO_IRQ
|
#ifdef MODULE_PERIPH_GPIO_IRQ
|
||||||
/*
|
/*
|
||||||
* @brief Define GPIO interruptions for an specific atmega CPU, by default
|
* @brief Define GPIO interruptions for an specific atmega CPU, by default
|
||||||
@ -69,7 +65,7 @@
|
|||||||
static gpio_isr_ctx_t config[GPIO_EXT_INT_NUMOF];
|
static gpio_isr_ctx_t config[GPIO_EXT_INT_NUMOF];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief detects ammount of possible PCINTs
|
* @brief detects amount of possible PCINTs
|
||||||
*/
|
*/
|
||||||
#if defined(MODULE_ATMEGA_PCINT0) || defined(MODULE_ATMEGA_PCINT1) || \
|
#if defined(MODULE_ATMEGA_PCINT0) || defined(MODULE_ATMEGA_PCINT1) || \
|
||||||
defined(MODULE_ATMEGA_PCINT2) || defined(MODULE_ATMEGA_PCINT3)
|
defined(MODULE_ATMEGA_PCINT2) || defined(MODULE_ATMEGA_PCINT3)
|
||||||
@ -167,72 +163,21 @@ static gpio_isr_ctx_pcint_t pcint_config[8 * PCINT_NUM_BANKS];
|
|||||||
|
|
||||||
#endif /* MODULE_PERIPH_GPIO_IRQ */
|
#endif /* MODULE_PERIPH_GPIO_IRQ */
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Extract the pin number of the given pin
|
|
||||||
*/
|
|
||||||
static inline uint8_t _pin_num(gpio_t pin)
|
|
||||||
{
|
|
||||||
return (pin & 0x0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Extract the port number of the given pin
|
|
||||||
*/
|
|
||||||
static inline uint8_t _port_num(gpio_t pin)
|
|
||||||
{
|
|
||||||
return (pin >> 4) & 0x0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Generate the PORTx address of the give pin.
|
|
||||||
*/
|
|
||||||
static inline uint16_t _port_addr(gpio_t pin)
|
|
||||||
{
|
|
||||||
uint8_t port_num = _port_num(pin);
|
|
||||||
uint16_t port_addr = port_num * GPIO_OFFSET_PIN_PIN;
|
|
||||||
|
|
||||||
port_addr += GPIO_BASE_PORT_A;
|
|
||||||
port_addr += GPIO_OFFSET_PIN_PORT;
|
|
||||||
|
|
||||||
#if defined (PORTG)
|
|
||||||
if (port_num > PORT_G) {
|
|
||||||
port_addr += GPIO_OFFSET_PORT_H;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
return port_addr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Generate the DDRx address of the given pin
|
|
||||||
*/
|
|
||||||
static inline uint16_t _ddr_addr(gpio_t pin)
|
|
||||||
{
|
|
||||||
return (_port_addr(pin) - 0x01);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Generate the PINx address of the given pin.
|
|
||||||
*/
|
|
||||||
static inline uint16_t _pin_addr(gpio_t pin)
|
|
||||||
{
|
|
||||||
return (_port_addr(pin) - 0x02);
|
|
||||||
}
|
|
||||||
|
|
||||||
int gpio_init(gpio_t pin, gpio_mode_t mode)
|
int gpio_init(gpio_t pin, gpio_mode_t mode)
|
||||||
{
|
{
|
||||||
uint8_t pin_mask = (1 << _pin_num(pin));
|
uint8_t pin_mask = (1 << atmega_pin_num(pin));
|
||||||
|
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case GPIO_OUT:
|
case GPIO_OUT:
|
||||||
_SFR_MEM8(_ddr_addr(pin)) |= pin_mask;
|
_SFR_MEM8(atmega_ddr_addr(pin)) |= pin_mask;
|
||||||
break;
|
break;
|
||||||
case GPIO_IN:
|
case GPIO_IN:
|
||||||
_SFR_MEM8(_ddr_addr(pin)) &= ~pin_mask;
|
_SFR_MEM8(atmega_ddr_addr(pin)) &= ~pin_mask;
|
||||||
_SFR_MEM8(_port_addr(pin)) &= ~pin_mask;
|
_SFR_MEM8(atmega_port_addr(pin)) &= ~pin_mask;
|
||||||
break;
|
break;
|
||||||
case GPIO_IN_PU:
|
case GPIO_IN_PU:
|
||||||
_SFR_MEM8(_ddr_addr(pin)) &= ~pin_mask;
|
_SFR_MEM8(atmega_ddr_addr(pin)) &= ~pin_mask;
|
||||||
_SFR_MEM8(_port_addr(pin)) |= pin_mask;
|
_SFR_MEM8(atmega_port_addr(pin)) |= pin_mask;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return -1;
|
return -1;
|
||||||
@ -243,17 +188,17 @@ int gpio_init(gpio_t pin, gpio_mode_t mode)
|
|||||||
|
|
||||||
int gpio_read(gpio_t pin)
|
int gpio_read(gpio_t pin)
|
||||||
{
|
{
|
||||||
return (_SFR_MEM8(_pin_addr(pin)) & (1 << _pin_num(pin)));
|
return (_SFR_MEM8(atmega_pin_addr(pin)) & (1 << atmega_pin_num(pin)));
|
||||||
}
|
}
|
||||||
|
|
||||||
void gpio_set(gpio_t pin)
|
void gpio_set(gpio_t pin)
|
||||||
{
|
{
|
||||||
_SFR_MEM8(_port_addr(pin)) |= (1 << _pin_num(pin));
|
_SFR_MEM8(atmega_port_addr(pin)) |= (1 << atmega_pin_num(pin));
|
||||||
}
|
}
|
||||||
|
|
||||||
void gpio_clear(gpio_t pin)
|
void gpio_clear(gpio_t pin)
|
||||||
{
|
{
|
||||||
_SFR_MEM8(_port_addr(pin)) &= ~(1 << _pin_num(pin));
|
_SFR_MEM8(atmega_port_addr(pin)) &= ~(1 << atmega_pin_num(pin));
|
||||||
}
|
}
|
||||||
|
|
||||||
void gpio_toggle(gpio_t pin)
|
void gpio_toggle(gpio_t pin)
|
||||||
@ -307,7 +252,7 @@ int gpio_init_int(gpio_t pin, gpio_mode_t mode, gpio_flank_t flank,
|
|||||||
/* If pin change interrupts are enabled, enable mask and interrupt */
|
/* If pin change interrupts are enabled, enable mask and interrupt */
|
||||||
#ifdef PCINT_NUM_BANKS
|
#ifdef PCINT_NUM_BANKS
|
||||||
int8_t offset = -1;
|
int8_t offset = -1;
|
||||||
uint8_t pin_num = _pin_num(pin);
|
uint8_t pin_num = atmega_pin_num(pin);
|
||||||
|
|
||||||
for (unsigned i = 0; i < ARRAY_SIZE(pcint_mapping); i++) {
|
for (unsigned i = 0; i < ARRAY_SIZE(pcint_mapping); i++) {
|
||||||
if (pin != GPIO_UNDEF && pin == pcint_mapping[i]) {
|
if (pin != GPIO_UNDEF && pin == pcint_mapping[i]) {
|
||||||
@ -365,7 +310,7 @@ int gpio_init_int(gpio_t pin, gpio_mode_t mode, gpio_flank_t flank,
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
/* As ports are mixed in a bank (e.g. PCINT0), we can only save a single bit here! */
|
/* As ports are mixed in a bank (e.g. PCINT0), we can only save a single bit here! */
|
||||||
uint8_t port_value = (_SFR_MEM8(_pin_addr( pin )));
|
uint8_t port_value = (_SFR_MEM8(atmega_pin_addr( pin )));
|
||||||
uint8_t pin_mask = (1 << pin_num);
|
uint8_t pin_mask = (1 << pin_num);
|
||||||
uint8_t pin_value = ((port_value & pin_mask) != 0);
|
uint8_t pin_value = ((port_value & pin_mask) != 0);
|
||||||
if (pin_value) {
|
if (pin_value) {
|
||||||
@ -448,9 +393,9 @@ static inline void pcint_handler(uint8_t bank, uint8_t enabled_pcints)
|
|||||||
/* get pin from mapping (assumes 8 entries per bank!) */
|
/* get pin from mapping (assumes 8 entries per bank!) */
|
||||||
gpio_t pin = pcint_mapping[bank * 8 + idx];
|
gpio_t pin = pcint_mapping[bank * 8 + idx];
|
||||||
/* re-construct mask from pin */
|
/* re-construct mask from pin */
|
||||||
uint8_t pin_mask = (1 << (_pin_num(pin)));
|
uint8_t pin_mask = (1 << (atmega_pin_num(pin)));
|
||||||
uint8_t idx_mask = (1 << idx);
|
uint8_t idx_mask = (1 << idx);
|
||||||
uint8_t port_value = (_SFR_MEM8(_pin_addr( pin )));
|
uint8_t port_value = (_SFR_MEM8(atmega_pin_addr( pin )));
|
||||||
uint8_t pin_value = ((port_value & pin_mask) != 0);
|
uint8_t pin_value = ((port_value & pin_mask) != 0);
|
||||||
uint8_t old_state = ((pcint_state[bank] & idx_mask) != 0);
|
uint8_t old_state = ((pcint_state[bank] & idx_mask) != 0);
|
||||||
gpio_isr_ctx_pcint_t *conf = &pcint_config[bank * 8 + idx];
|
gpio_isr_ctx_pcint_t *conf = &pcint_config[bank * 8 + idx];
|
||||||
|
|||||||
@ -631,6 +631,23 @@ ifneq (,$(filter w5100,$(USEMODULE)))
|
|||||||
USEMODULE += luid
|
USEMODULE += luid
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
ifneq (,$(filter ws281x_%,$(USEMODULE)))
|
||||||
|
USEMODULE += ws281x
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifneq (,$(filter ws281x,$(USEMODULE)))
|
||||||
|
FEATURES_OPTIONAL += arch_avr8
|
||||||
|
ifeq (,$(filter ws281x_%,$(USEMODULE)))
|
||||||
|
ifneq (,$(filter arch_avr8,$(FEATURES_USED)))
|
||||||
|
USEMODULE += ws281x_atmega
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
ifneq (,$(filter ws281x_atmega,$(USEMODULE)))
|
||||||
|
FEATURES_REQUIRED += arch_avr8
|
||||||
|
endif
|
||||||
|
USEMODULE += xtimer
|
||||||
|
endif
|
||||||
|
|
||||||
ifneq (,$(filter xbee,$(USEMODULE)))
|
ifneq (,$(filter xbee,$(USEMODULE)))
|
||||||
FEATURES_REQUIRED += periph_uart
|
FEATURES_REQUIRED += periph_uart
|
||||||
FEATURES_REQUIRED += periph_gpio
|
FEATURES_REQUIRED += periph_gpio
|
||||||
|
|||||||
@ -318,6 +318,10 @@ ifneq (,$(filter w5100,$(USEMODULE)))
|
|||||||
USEMODULE_INCLUDES += $(RIOTBASE)/drivers/w5100/include
|
USEMODULE_INCLUDES += $(RIOTBASE)/drivers/w5100/include
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
ifneq (,$(filter ws281x,$(USEMODULE)))
|
||||||
|
USEMODULE_INCLUDES += $(RIOTBASE)/drivers/ws281x/include
|
||||||
|
endif
|
||||||
|
|
||||||
ifneq (,$(filter xbee,$(USEMODULE)))
|
ifneq (,$(filter xbee,$(USEMODULE)))
|
||||||
USEMODULE_INCLUDES += $(RIOTBASE)/drivers/xbee/include
|
USEMODULE_INCLUDES += $(RIOTBASE)/drivers/xbee/include
|
||||||
endif
|
endif
|
||||||
|
|||||||
217
drivers/include/ws281x.h
Normal file
217
drivers/include/ws281x.h
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Marian Buschsieweke
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @defgroup drivers_ws281x WS2812/SK6812 RGB LED (NeoPixel)
|
||||||
|
* @ingroup drivers_actuators
|
||||||
|
* @brief Driver for the WS2812 or the SK6812 RGB LEDs sold as NeoPixel
|
||||||
|
*
|
||||||
|
* # Summary
|
||||||
|
*
|
||||||
|
* The WS2812 or SK6812 RGB LEDs, or more commonly known as NeoPixels, can be
|
||||||
|
* chained so that a single data pin of the MCU can control an arbitrary number
|
||||||
|
* of RGB LEDs.
|
||||||
|
*
|
||||||
|
* # Support
|
||||||
|
*
|
||||||
|
* The protocol to communicate with the WS281x is custom, so no hardware
|
||||||
|
* implementations can be used. Hence, the protocol needs to be bit banged in
|
||||||
|
* software. As the timing requirements are to strict to do this using
|
||||||
|
* the platform independent APIs for accessing @ref drivers_periph_gpio and
|
||||||
|
* @ref sys_xtimer, platform specific implementations of @ref ws281x_write are
|
||||||
|
* needed.
|
||||||
|
*
|
||||||
|
* ## ATmega
|
||||||
|
*
|
||||||
|
* A bit banging implementation for ATmegas clocked at 8MHz and at 16MHz is
|
||||||
|
* provided. Boards clocked at any other core frequency are not supported.
|
||||||
|
* (But keep in mind that most (all?) ATmega MCUs do have an internal 8 MHz
|
||||||
|
* oscillator, that could be enabled by changing the fuse settings.)
|
||||||
|
*
|
||||||
|
* @warning On 8MHz ATmegas, only pins at GPIO ports B, C, and D are supported.
|
||||||
|
* (On 16MHz ATmegas, any pin is fine.)
|
||||||
|
*
|
||||||
|
* ### Usage
|
||||||
|
*
|
||||||
|
* Add the following to your `Makefile` to use the ATmega backend:
|
||||||
|
*
|
||||||
|
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Makefile
|
||||||
|
* USEMODULE += ws281x_atmega
|
||||||
|
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
*
|
||||||
|
* @{
|
||||||
|
*
|
||||||
|
* @file
|
||||||
|
* @brief WS2812/SK6812 RGB LED Driver
|
||||||
|
*
|
||||||
|
* @author Marian Buschsieweke <marian.buschsieweke@ovgu.de>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef WS281X_H
|
||||||
|
#define WS281X_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "color.h"
|
||||||
|
#include "periph/gpio.h"
|
||||||
|
#include "ws281x_constants.h"
|
||||||
|
#include "xtimer.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The number of bytes to allocate in the data buffer per LED
|
||||||
|
*/
|
||||||
|
#define WS281X_BYTES_PER_DEVICE (3U)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Struct to hold initialization parameters for a WS281x RGB LED
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
/**
|
||||||
|
* @brief A statically allocated data buffer storing the state of the LEDs
|
||||||
|
*
|
||||||
|
* @pre Must be sized `numof * WS281X_BYTES_PER_DEVICE` bytes
|
||||||
|
*/
|
||||||
|
uint8_t *buf;
|
||||||
|
uint16_t numof; /**< Number of chained RGB LEDs */
|
||||||
|
gpio_t pin; /**< GPIO connected to the data pin of the first LED */
|
||||||
|
} ws281x_params_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Device descriptor of a WS281x RGB LED chain
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
ws281x_params_t params; /**< Parameters of the LED chain */
|
||||||
|
} ws281x_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initialize an WS281x RGB LED chain
|
||||||
|
*
|
||||||
|
* @param dev Device descriptor to initialize
|
||||||
|
* @param params Parameters to initialize the device with
|
||||||
|
*
|
||||||
|
* @retval 0 Success
|
||||||
|
* @retval -EINVAL Invalid argument
|
||||||
|
* @retval -EIO Failed to initialize the data GPIO pin
|
||||||
|
*/
|
||||||
|
int ws281x_init(ws281x_t *dev, const ws281x_params_t *params);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Writes the color data of the user supplied buffer
|
||||||
|
*
|
||||||
|
* @param dev Device descriptor of the LED chain to write to
|
||||||
|
* @param buf Buffer to write
|
||||||
|
* @param size Size of the buffer in bytes
|
||||||
|
*
|
||||||
|
* @pre Before the transmission starts @ref ws281x_prepare_transmission is
|
||||||
|
* called
|
||||||
|
* @post At the end of the transmission @ref ws281x_end_transmission is
|
||||||
|
* called
|
||||||
|
*
|
||||||
|
* This function can be used to drive a huge number of LEDs with small data
|
||||||
|
* buffers. However, after the return of this function the next chunk should
|
||||||
|
* be send within a few microseconds to avoid accidentally sending the end of
|
||||||
|
* transmission signal.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ {.c}
|
||||||
|
* uint8_t chunk[CHUNK_SIZE];
|
||||||
|
* ws281x_prepare_transmission(ws281x_dev);
|
||||||
|
* while (more_chunks_available()) {
|
||||||
|
* prepare_chunk(chunk);
|
||||||
|
* ws281x_write_buffer(ws281x_dev, chunk, sizeof(chunk));
|
||||||
|
* }
|
||||||
|
* ws281x_end_transmission(dev);
|
||||||
|
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
*/
|
||||||
|
void ws281x_write_buffer(ws281x_t *dev, const void *buf, size_t size);
|
||||||
|
|
||||||
|
#if defined(WS281X_HAVE_PREPARE_TRANSMISSION) || defined(DOXYGEN)
|
||||||
|
/**
|
||||||
|
* @brief Sets up everything needed to write data to the WS281X LED chain
|
||||||
|
*
|
||||||
|
* @param dev Device descriptor of the LED chain to write to
|
||||||
|
*/
|
||||||
|
void ws281x_prepare_transmission(ws281x_t *dev);
|
||||||
|
#else
|
||||||
|
static inline void ws281x_prepare_transmission(ws281x_t *dev)
|
||||||
|
{
|
||||||
|
(void)dev;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(WS281X_HAVE_END_TRANSMISSION) || defined(DOXYGEN)
|
||||||
|
/**
|
||||||
|
* @brief Ends the transmission to the WS2812/SK6812 LED chain
|
||||||
|
*
|
||||||
|
* @param dev Device descriptor of the LED chain to write to
|
||||||
|
*
|
||||||
|
* Does any cleanup the backend needs after sending data. In the simplest case
|
||||||
|
* it simply waits for 80µs to send the end of transmission signal.
|
||||||
|
*/
|
||||||
|
void ws281x_end_transmission(ws281x_t *dev);
|
||||||
|
#else
|
||||||
|
static inline void ws281x_end_transmission(ws281x_t *dev)
|
||||||
|
{
|
||||||
|
(void)dev;
|
||||||
|
xtimer_usleep(WS281X_T_END_US);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sets the color of an LED in the given data buffer
|
||||||
|
*
|
||||||
|
* @param dest Buffer to set the color in
|
||||||
|
* @param index The index of the LED to set the color of
|
||||||
|
* @param color The new color to apply
|
||||||
|
*
|
||||||
|
* @warning This change will not become active until @ref ws281x_write is
|
||||||
|
* called
|
||||||
|
*/
|
||||||
|
void ws281x_set_buffer(void *dest, uint16_t index, color_rgb_t color);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sets the color of an LED in the chain in the internal buffer
|
||||||
|
*
|
||||||
|
* @param dev Device descriptor of the LED chain to modify
|
||||||
|
* @param index The index of the LED to set the color of
|
||||||
|
* @param color The new color to apply
|
||||||
|
*
|
||||||
|
* @warning This change will not become active until @ref ws281x_write is
|
||||||
|
* called
|
||||||
|
*/
|
||||||
|
static inline void ws281x_set(ws281x_t *dev, uint16_t index, color_rgb_t color)
|
||||||
|
{
|
||||||
|
ws281x_set_buffer(dev->params.buf, index, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Writes the internal buffer to the LED chain
|
||||||
|
*
|
||||||
|
* @param dev Device descriptor of the LED chain to write to
|
||||||
|
*
|
||||||
|
* @note This function implicitly calls @ref ws281x_end_transmission
|
||||||
|
* @see ws281x_set
|
||||||
|
*/
|
||||||
|
static inline void ws281x_write(ws281x_t *dev)
|
||||||
|
{
|
||||||
|
ws281x_prepare_transmission(dev);
|
||||||
|
ws281x_write_buffer(dev, dev->params.buf,
|
||||||
|
(size_t)dev->params.numof * WS281X_BYTES_PER_DEVICE);
|
||||||
|
ws281x_end_transmission(dev);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* WS281X_H */
|
||||||
|
/** @} */
|
||||||
3
drivers/ws281x/Makefile
Normal file
3
drivers/ws281x/Makefile
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
SRC := ws281x.c # All other sources files provide ws281x_write as submodule
|
||||||
|
SUBMODULES := 1
|
||||||
|
include $(RIOTBASE)/Makefile.base
|
||||||
205
drivers/ws281x/atmega.c
Normal file
205
drivers/ws281x/atmega.c
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Marian Buschsieweke
|
||||||
|
*
|
||||||
|
* 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 `ws281x_write()` for ATmega MCUs
|
||||||
|
*
|
||||||
|
* @author Marian Buschsieweke <marian.buschsieweke@ovgu.de>
|
||||||
|
*
|
||||||
|
* @}
|
||||||
|
*/
|
||||||
|
#include <assert.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "atmega_gpio.h"
|
||||||
|
#include "ws281x.h"
|
||||||
|
#include "ws281x_params.h"
|
||||||
|
#include "ws281x_constants.h"
|
||||||
|
#include "periph_cpu.h"
|
||||||
|
#include "xtimer.h"
|
||||||
|
/*
|
||||||
|
* Data encoding according to the datasheets of the WS2812 and the SK6812:
|
||||||
|
* - Encoding of zero bit:
|
||||||
|
* 1. High for 325ns ± 125ns (average of WS2812 and SK6812)
|
||||||
|
* 2. Low for 850ns ± 100ns (average of WS2812 and SK6812)
|
||||||
|
* - Encoding of one bit:
|
||||||
|
* 1. High for 650ns ± 100ns (average of WS2812 and SK6812)
|
||||||
|
* 2. Low for 600ns ± 150ns (for both WS2812 and SK6812)
|
||||||
|
*
|
||||||
|
* While the data sheet claims the opposite, the precision requirement for
|
||||||
|
* the duration of the second phase (the low period) is in reality super
|
||||||
|
* lax. Therefore, we bit bang the high period precisely in inline assembly,
|
||||||
|
* and completely ignore the timing of the low phase. This works just fine.
|
||||||
|
*
|
||||||
|
* Further: For an 8MHz clock, we need a single CPU cycle access to the
|
||||||
|
* GPIO port. The is only possible with the `out` instruction, which takes
|
||||||
|
* the port to write to as immediate. Thus, it has to be known at compile
|
||||||
|
* time. We therefore simply provide 3 implementations for each of the
|
||||||
|
* three most commonly used GPIO ports. For the 16MHz version accessing the
|
||||||
|
* GPIO port via memory using the st instruction works fine, so no limitations
|
||||||
|
* on the GPIO por there.
|
||||||
|
*
|
||||||
|
* On final trick: A relative jump to the next instructions takes two
|
||||||
|
* CPU cycles; this, its 2 NOPs for the price of one (in terms of ROM
|
||||||
|
* consumption).
|
||||||
|
*
|
||||||
|
* High phase timings
|
||||||
|
*
|
||||||
|
* +------------+-------------------+-------------------+-------------------+
|
||||||
|
* | Data | Device/Frequency | Min | Max |
|
||||||
|
* +------------+-------------------+-------------------+-------------------+
|
||||||
|
* | 0 bit | WS2812 | 200ns | 500ns |
|
||||||
|
* | 1 bit | WS2812 | 550ns | 850ns (*) |
|
||||||
|
* +------------+-------------------+-------------------+-------------------+
|
||||||
|
* | 0 bit | SK6812 | 150ns | 450ns |
|
||||||
|
* | 1 bit | SK6812 | 450ns | 750ns |
|
||||||
|
* +------------+-------------------+-------------------+-------------------+
|
||||||
|
* | 0 bit | WS2812/SK6812 | 200ns | 450ns |
|
||||||
|
* | 1 bit | WS2812/SK6812 | 550ns | 750ns |
|
||||||
|
* +------------+-------------------+-------------------+-------------------+
|
||||||
|
* | 0 bit | 8 MHz | 2 Cycles (250ns) | 3 Cycles (375ns) |
|
||||||
|
* | 1 bit | 8 MHz | 5 Cycles (625ns) | 6 Cycles (750ns) |
|
||||||
|
* +------------+-------------------+-------------------+-------------------+
|
||||||
|
* | 0 bit | 16 MHz | 4 Cycles (250ns) | 7 Cycles (438ns) |
|
||||||
|
* | 1 bit | 16 MHz | 9 Cycles (563ns) | 12 Cycles (750ns) |
|
||||||
|
* +------------+-------------------+-------------------+-------------------+
|
||||||
|
*
|
||||||
|
* (*) The high time for encoding a 1 bit on the WS2812 has no upper bound in
|
||||||
|
* practise; A high period of 5 seconds has been reported to work reliable.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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;
|
||||||
|
uint16_t port_addr = atmega_port_addr(dev->params.pin);
|
||||||
|
uint8_t mask_on, mask_off;
|
||||||
|
|
||||||
|
{
|
||||||
|
uint8_t port_state = _SFR_MEM8(port_addr);
|
||||||
|
mask_on = port_state | (1 << atmega_pin_num(dev->params.pin));
|
||||||
|
mask_off = port_state & ~(1 << atmega_pin_num(dev->params.pin));
|
||||||
|
}
|
||||||
|
|
||||||
|
#if (CLOCK_CORECLOCK >= 7500000U) && (CLOCK_CORECLOCK <= 8500000U)
|
||||||
|
const uint8_t port_num = atmega_port_num(dev->params.pin);
|
||||||
|
switch (port_num) {
|
||||||
|
case PORT_B:
|
||||||
|
while (pos < end) {
|
||||||
|
uint8_t cnt = 8;
|
||||||
|
uint8_t data = *pos;
|
||||||
|
while (cnt > 0) {
|
||||||
|
__asm__ volatile( /* Cycles | 1 | 0 */
|
||||||
|
"out %[port], %[on]" "\n\t" /* 1 | x | x */
|
||||||
|
"sbrs %[data], 7" "\n\t" /* 1 | x | x */
|
||||||
|
"out %[port], %[off]" "\n\t" /* 1 | - | x */
|
||||||
|
"rjmp .+0" "\n\t" /* 2 | x | x */
|
||||||
|
"out %[port], %[off]" "\n\t" /* 1 | x | x */
|
||||||
|
/* Total CPU cycles for high period:
|
||||||
|
* 5 cycles for bit 1, 2 cycles for bit 0 */
|
||||||
|
: [data] "+r" (data)
|
||||||
|
: [port] "I" (_SFR_IO_ADDR(PORTB)),
|
||||||
|
[on] "r" (mask_on),
|
||||||
|
[off] "r" (mask_off)
|
||||||
|
);
|
||||||
|
cnt--;
|
||||||
|
data <<= 1;
|
||||||
|
}
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PORT_C:
|
||||||
|
while (pos < end) {
|
||||||
|
uint8_t cnt = 8;
|
||||||
|
uint8_t data = *pos;
|
||||||
|
while (cnt > 0) {
|
||||||
|
__asm__ volatile( /* Cycles | 1 | 0 */
|
||||||
|
"out %[port], %[on]" "\n\t" /* 1 | x | x */
|
||||||
|
"sbrs %[data], 7" "\n\t" /* 1 | x | x */
|
||||||
|
"out %[port], %[off]" "\n\t" /* 1 | - | x */
|
||||||
|
"rjmp .+0" "\n\t" /* 2 | x | x */
|
||||||
|
"out %[port], %[off]" "\n\t" /* 1 | x | x */
|
||||||
|
/* Total CPU cycles for high period:
|
||||||
|
* 5 cycles for bit 1, 2 cycles for bit 0 */
|
||||||
|
: [data] "+r" (data)
|
||||||
|
: [port] "I" (_SFR_IO_ADDR(PORTC)),
|
||||||
|
[on] "r" (mask_on),
|
||||||
|
[off] "r" (mask_off)
|
||||||
|
);
|
||||||
|
cnt--;
|
||||||
|
data <<= 1;
|
||||||
|
}
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PORT_D:
|
||||||
|
while (pos < end) {
|
||||||
|
uint8_t cnt = 8;
|
||||||
|
uint8_t data = *pos;
|
||||||
|
while (cnt > 0) {
|
||||||
|
__asm__ volatile( /* Cycles | 1 | 0 */
|
||||||
|
"out %[port], %[on]" "\n\t" /* 1 | x | x */
|
||||||
|
"sbrs %[data], 7" "\n\t" /* 1 | x | x */
|
||||||
|
"out %[port], %[off]" "\n\t" /* 1 | - | x */
|
||||||
|
"rjmp .+0" "\n\t" /* 2 | x | x */
|
||||||
|
"out %[port], %[off]" "\n\t" /* 1 | x | x */
|
||||||
|
/* Total CPU cycles for high period:
|
||||||
|
* 5 cycles for bit 1, 2 cycles for bit 0 */
|
||||||
|
: [data] "+r" (data)
|
||||||
|
: [port] "I" (_SFR_IO_ADDR(PORTD)),
|
||||||
|
[on] "r" (mask_on),
|
||||||
|
[off] "r" (mask_off)
|
||||||
|
);
|
||||||
|
cnt--;
|
||||||
|
data <<= 1;
|
||||||
|
}
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
assert(0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
#elif (CLOCK_CORECLOCK >= 15500000U) && (CLOCK_CORECLOCK <= 16500000U)
|
||||||
|
while (pos < end) {
|
||||||
|
uint8_t cnt = 8;
|
||||||
|
uint8_t data = *pos;
|
||||||
|
while (cnt > 0) {
|
||||||
|
__asm__ volatile( /* CPU Cycles | 1 | 0 */
|
||||||
|
"st %a[port], %[on]" "\n\t" /* 2 | x | x */
|
||||||
|
"sbrc %[data], 7" "\n\t" /* 1 | x | x */
|
||||||
|
"rjmp .+0" "\n\t" /* 2 | x | - */
|
||||||
|
"sbrc %[data], 7" "\n\t" /* 1 | x | x */
|
||||||
|
"rjmp .+0" "\n\t" /* 2 | x | - */
|
||||||
|
"sbrc %[data], 7" "\n\t" /* 1 | x | x */
|
||||||
|
"nop" "\n\t" /* 1 | x | - */
|
||||||
|
"st %a[port], %[off]" "\n\t" /* 2 | x | x */
|
||||||
|
/* Total CPU cycles for high period:
|
||||||
|
* 10 cycles for bit 1, 5 cycles for bit 0 */
|
||||||
|
: [data] "+r" (data)
|
||||||
|
: [on] "r" (mask_on),
|
||||||
|
[port] "e" (port_addr),
|
||||||
|
[off] "r" (mask_off)
|
||||||
|
);
|
||||||
|
cnt--;
|
||||||
|
data <<= 1;
|
||||||
|
}
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
#error "No low level WS281x implementation for ATmega CPUs for your CPU clock"
|
||||||
|
#endif
|
||||||
|
}
|
||||||
62
drivers/ws281x/include/ws281x_constants.h
Normal file
62
drivers/ws281x/include/ws281x_constants.h
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 Marian Buschsieweke
|
||||||
|
*
|
||||||
|
* 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 Constants for WS2812/SK6812 RGB LEDs
|
||||||
|
*
|
||||||
|
* @author Marian Buschsieweke <marian.buschsieweke@ovgu.de>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef WS281X_CONSTANTS_H
|
||||||
|
#define WS281X_CONSTANTS_H
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @name Timing parameters for WS2812/SK6812 RGB LEDs
|
||||||
|
* @{
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @brief Time in microseconds to pull the data line low to signal end of data
|
||||||
|
*
|
||||||
|
* For the WS2812 it is ≥ 50µs, for the SK6812 it is ≥ 80µs. We choose 80µs to
|
||||||
|
* be compatible with both.
|
||||||
|
*/
|
||||||
|
#define WS281X_T_END_US (80U)
|
||||||
|
/**@}*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @name Data encoding parameters for WS2812/SK6812 RGB LEDs
|
||||||
|
* @{
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @brief Offset for the red color component
|
||||||
|
*/
|
||||||
|
#define WS281X_OFFSET_R (1U)
|
||||||
|
/**
|
||||||
|
* @brief Offset for the green color component
|
||||||
|
*/
|
||||||
|
#define WS281X_OFFSET_G (0U)
|
||||||
|
/**
|
||||||
|
* @brief Offset for the blue color component
|
||||||
|
*/
|
||||||
|
#define WS281X_OFFSET_B (2U)
|
||||||
|
/**@}*/
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* WS281X_CONSTANTS_H */
|
||||||
|
/** @} */
|
||||||
72
drivers/ws281x/include/ws281x_params.h
Normal file
72
drivers/ws281x/include/ws281x_params.h
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 Marian Buschsieweke
|
||||||
|
*
|
||||||
|
* 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 Default configuration for WS2812/SK6812 RGB LEDs
|
||||||
|
*
|
||||||
|
* @author Marian Buschsieweke <marian.buschsieweke@ovgu.de>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef WS281X_PARAMS_H
|
||||||
|
#define WS281X_PARAMS_H
|
||||||
|
|
||||||
|
#include "board.h"
|
||||||
|
#include "ws281x.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @name Default configuration parameters for WS281x RGB LEDs
|
||||||
|
* @{
|
||||||
|
*/
|
||||||
|
#ifndef WS281X_PARAM_PIN
|
||||||
|
#define WS281X_PARAM_PIN (GPIO_PIN(0,0)) /**< GPIO pin connected to the data pin of the first LED */
|
||||||
|
#endif
|
||||||
|
#ifndef WS281X_PARAM_NUMOF
|
||||||
|
#define WS281X_PARAM_NUMOF (8U) /**< Number of LEDs chained */
|
||||||
|
#endif
|
||||||
|
#ifndef WS281X_PARAM_BUF
|
||||||
|
/**
|
||||||
|
* @brief Data buffer holding the LED states
|
||||||
|
*/
|
||||||
|
extern uint8_t ws281x_buf[WS281X_PARAM_NUMOF * WS281X_BYTES_PER_DEVICE];
|
||||||
|
#define WS281X_PARAM_BUF (ws281x_buf) /**< Data buffer holding LED states */
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef WS281X_PARAMS
|
||||||
|
/**
|
||||||
|
* @brief WS281x initialization parameters
|
||||||
|
*/
|
||||||
|
#define WS281X_PARAMS { \
|
||||||
|
.pin = WS281X_PARAM_PIN, \
|
||||||
|
.numof = WS281X_PARAM_NUMOF, \
|
||||||
|
.buf = WS281X_PARAM_BUF, \
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
/**@}*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initialization parameters for WS281x devices
|
||||||
|
*/
|
||||||
|
static const ws281x_params_t ws281x_params[] =
|
||||||
|
{
|
||||||
|
WS281X_PARAMS
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* WS281X_PARAMS_H */
|
||||||
|
/** @} */
|
||||||
59
drivers/ws281x/ws281x.c
Normal file
59
drivers/ws281x/ws281x.c
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Marian Buschsieweke
|
||||||
|
*
|
||||||
|
* 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 Driver for the WS2812 or the SK6812 RGB LEDs sold as NeoPixel
|
||||||
|
*
|
||||||
|
* @author Marian Buschsieweke <marian.buschsieweke@ovgu.de>
|
||||||
|
*
|
||||||
|
* @}
|
||||||
|
*/
|
||||||
|
#include <assert.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "ws281x.h"
|
||||||
|
#include "ws281x_constants.h"
|
||||||
|
#include "ws281x_params.h"
|
||||||
|
#include "periph/gpio.h"
|
||||||
|
|
||||||
|
/* Default buffer used in ws281x_params.h. Will be optimized out if unused */
|
||||||
|
uint8_t ws281x_buf[WS281X_PARAM_NUMOF * WS281X_BYTES_PER_DEVICE];
|
||||||
|
|
||||||
|
/* Some backend will need a custom init function. Declaring this as weak symbol
|
||||||
|
* allows them to provide their own. */
|
||||||
|
int __attribute__((weak)) ws281x_init(ws281x_t *dev,
|
||||||
|
const ws281x_params_t *params)
|
||||||
|
{
|
||||||
|
if (!dev || !params || !params->buf) {
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(dev, 0, sizeof(ws281x_t));
|
||||||
|
dev->params = *params;
|
||||||
|
|
||||||
|
if (gpio_init(dev->params.pin, GPIO_OUT)) {
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ws281x_set_buffer(void *_dest, uint16_t n, color_rgb_t c)
|
||||||
|
{
|
||||||
|
uint8_t *dest = _dest;
|
||||||
|
dest[WS281X_BYTES_PER_DEVICE * n + WS281X_OFFSET_R] = c.r;
|
||||||
|
dest[WS281X_BYTES_PER_DEVICE * n + WS281X_OFFSET_G] = c.g;
|
||||||
|
dest[WS281X_BYTES_PER_DEVICE * n + WS281X_OFFSET_B] = c.b;
|
||||||
|
}
|
||||||
@ -160,6 +160,9 @@ PSEUDOMODULES += vcnl4010
|
|||||||
PSEUDOMODULES += vcnl4020
|
PSEUDOMODULES += vcnl4020
|
||||||
PSEUDOMODULES += vcnl4040
|
PSEUDOMODULES += vcnl4040
|
||||||
|
|
||||||
|
# implementations of ws281x_write as submodules of ws281x:
|
||||||
|
PSEUDOMODULES += ws281x_%
|
||||||
|
|
||||||
# include variants of lpsxxx drivers as pseudo modules
|
# include variants of lpsxxx drivers as pseudo modules
|
||||||
PSEUDOMODULES += lps331ap
|
PSEUDOMODULES += lps331ap
|
||||||
PSEUDOMODULES += lps22hb
|
PSEUDOMODULES += lps22hb
|
||||||
|
|||||||
19
tests/driver_ws281x/Makefile
Normal file
19
tests/driver_ws281x/Makefile
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
BOARD ?= atmega328p
|
||||||
|
include ../Makefile.tests_common
|
||||||
|
|
||||||
|
# Update this to your needs
|
||||||
|
PIN ?= GPIO_PIN(0, 0)
|
||||||
|
N ?= 8
|
||||||
|
|
||||||
|
USEMODULE += ws281x
|
||||||
|
|
||||||
|
FEATURES_REQUIRED := arch_avr8 # <-- Currently only backend for AVR boards
|
||||||
|
|
||||||
|
# Only AVR boards CPU clocked at 8MHz or 16 MHz are supported. The Waspmote Pro
|
||||||
|
# is clocked at 14.7456 MHz :-/
|
||||||
|
BOARD_BLACKLIST := waspmote-pro
|
||||||
|
|
||||||
|
include $(RIOTBASE)/Makefile.include
|
||||||
|
|
||||||
|
CFLAGS += '-DWS281X_PARAM_PIN=$(PIN)'
|
||||||
|
CFLAGS += '-DWS281X_PARAM_NUMOF=$(N)'
|
||||||
119
tests/driver_ws281x/main.c
Normal file
119
tests/driver_ws281x/main.c
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Marian Buschsieweke
|
||||||
|
*
|
||||||
|
* 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 tests
|
||||||
|
* @{
|
||||||
|
*
|
||||||
|
* @file
|
||||||
|
* @brief Test application for the WS281x RGB LED driver
|
||||||
|
*
|
||||||
|
* @author Marian Buschsieweke <marian.buschsieweke@ovgu.de>
|
||||||
|
*
|
||||||
|
* @}
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include "ws281x.h"
|
||||||
|
#include "ws281x_params.h"
|
||||||
|
#include "xtimer.h"
|
||||||
|
|
||||||
|
static const color_rgb_t rainbow[] = {
|
||||||
|
{.r = 0x94, .g = 0x00, .b = 0xd3},
|
||||||
|
{.r = 0x4b, .g = 0x00, .b = 0x82},
|
||||||
|
{.r = 0x00, .g = 0x00, .b = 0xff},
|
||||||
|
{.r = 0x00, .g = 0xff, .b = 0x00},
|
||||||
|
{.r = 0xff, .g = 0xff, .b = 0x00},
|
||||||
|
{.r = 0xff, .g = 0x7f, .b = 0xd3},
|
||||||
|
{.r = 0xff, .g = 0x00, .b = 0x00},
|
||||||
|
};
|
||||||
|
|
||||||
|
#define RAINBOW_LEN ARRAY_SIZE(rainbow)
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
ws281x_t dev;
|
||||||
|
int retval;
|
||||||
|
|
||||||
|
puts(
|
||||||
|
"WS281x Test Application\n"
|
||||||
|
"=========================\n"
|
||||||
|
"\n"
|
||||||
|
"If you see an animated rainbow, the driver works as expected.\n"
|
||||||
|
"If the LEDs are flickering, check if the power supply is sufficient\n"
|
||||||
|
"(at least 4V). Also: The logic level has to be at least 0.7 * VDD,\n"
|
||||||
|
"so 3.3V logic with a 5V power supply is out of spec, but might work\n"
|
||||||
|
"OK.\n"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (0 != (retval = ws281x_init(&dev, &ws281x_params[0]))) {
|
||||||
|
printf("Initialization failed with error code %d\n", retval);
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
unsigned offset = 0;
|
||||||
|
puts("Animation: Moving rainbow...");
|
||||||
|
xtimer_ticks32_t last_wakeup = xtimer_now();
|
||||||
|
for (unsigned i = 0; i < 100; i++) {
|
||||||
|
for (uint16_t j = 0; j < dev.params.numof; j++) {
|
||||||
|
ws281x_set(&dev, j, rainbow[(j + offset) % RAINBOW_LEN]);
|
||||||
|
}
|
||||||
|
offset++;
|
||||||
|
ws281x_write(&dev);
|
||||||
|
xtimer_periodic_wakeup(&last_wakeup, 100 * US_PER_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
puts("Animation: Fading rainbow...");
|
||||||
|
last_wakeup = xtimer_now();
|
||||||
|
for (unsigned i = 0; i < RAINBOW_LEN; i++) {
|
||||||
|
for (unsigned j = 0; j < 100; j++) {
|
||||||
|
color_rgb_t col = {
|
||||||
|
.r = (uint8_t)(((unsigned)rainbow[i].r * j + 50) / 100),
|
||||||
|
.g = (uint8_t)(((unsigned)rainbow[i].g * j + 50) / 100),
|
||||||
|
.b = (uint8_t)(((unsigned)rainbow[i].b * j + 50) / 100),
|
||||||
|
};
|
||||||
|
for (uint16_t k = 0; k < dev.params.numof; k++) {
|
||||||
|
ws281x_set(&dev, k, col);
|
||||||
|
}
|
||||||
|
ws281x_write(&dev);
|
||||||
|
xtimer_periodic_wakeup(&last_wakeup, 10 * US_PER_MS);
|
||||||
|
}
|
||||||
|
for (unsigned j = 100; j > 0; j--) {
|
||||||
|
color_rgb_t col = {
|
||||||
|
.r = (uint8_t)(((unsigned)rainbow[i].r * j + 50) / 100),
|
||||||
|
.g = (uint8_t)(((unsigned)rainbow[i].g * j + 50) / 100),
|
||||||
|
.b = (uint8_t)(((unsigned)rainbow[i].b * j + 50) / 100),
|
||||||
|
};
|
||||||
|
for (uint16_t k = 0; k < dev.params.numof; k++) {
|
||||||
|
ws281x_set(&dev, k, col);
|
||||||
|
}
|
||||||
|
ws281x_write(&dev);
|
||||||
|
xtimer_periodic_wakeup(&last_wakeup, 10 * US_PER_MS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
puts("Animation: 100 rainbows. (You'll need a long chain for this)");
|
||||||
|
uint8_t buf[RAINBOW_LEN * WS281X_BYTES_PER_DEVICE];
|
||||||
|
for (unsigned i = 0; i < RAINBOW_LEN; i++) {
|
||||||
|
ws281x_set_buffer(buf, i, rainbow[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
ws281x_prepare_transmission(&dev);
|
||||||
|
for (unsigned i = 0; i < 100; i++) {
|
||||||
|
ws281x_write_buffer(&dev, buf, sizeof(buf));
|
||||||
|
}
|
||||||
|
ws281x_end_transmission(&dev);
|
||||||
|
|
||||||
|
/* wait some time to allow testers to verify the result */
|
||||||
|
xtimer_sleep(5);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user