diff --git a/drivers/Makefile.dep b/drivers/Makefile.dep index 34e91a56fb9027abbfc1cd80c46ee3b3567dd30a..c9c0212f56962b8b7d76aa904fee6fd8f1d1347f 100644 --- a/drivers/Makefile.dep +++ b/drivers/Makefile.dep @@ -4,6 +4,10 @@ ifneq (,$(filter adxl345,$(USEMODULE))) FEATURES_REQUIRED += periph_i2c endif +ifneq (,$(filter apa102,$(USEMODULE))) + FEATURES_REQUIRED += periph_gpio +endif + ifneq (,$(filter at30tse75x,$(USEMODULE))) USEMODULE += xtimer FEATURES_REQUIRED += periph_i2c diff --git a/drivers/Makefile.include b/drivers/Makefile.include index dcbd298cfa6ad9a2de07f360599d6a3c949eb84c..5a1a902236f57be2c056f0e62038588e2c624521 100644 --- a/drivers/Makefile.include +++ b/drivers/Makefile.include @@ -142,3 +142,6 @@ endif ifneq (,$(filter sx127%,$(USEMODULE))) USEMODULE_INCLUDES += $(RIOTBASE)/drivers/sx127x/include endif +ifneq (,$(filter apa102,$(USEMODULE))) + USEMODULE_INCLUDES += $(RIOTBASE)/drivers/apa102/include +endif diff --git a/drivers/apa102/Makefile b/drivers/apa102/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..48422e909a47d7cd428d10fa73825060ccc8d8c2 --- /dev/null +++ b/drivers/apa102/Makefile @@ -0,0 +1 @@ +include $(RIOTBASE)/Makefile.base diff --git a/drivers/apa102/apa102.c b/drivers/apa102/apa102.c new file mode 100644 index 0000000000000000000000000000000000000000..f9cbacaf6f939b37aceb94aa847f0690cfa82b59 --- /dev/null +++ b/drivers/apa102/apa102.c @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2017 Freie Universität Berlin + * + * 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_apa102 + * @{ + * + * @file + * @brief APA 102 RGB LED driver implementation + * + * @author Hauke Petersen <hauke.petersen@fu-berlin.de> + * + * @} + */ + +#include <string.h> + +#include "assert.h" +#include "apa102.h" + +#define START (0x00000000) +#define END (0xffffffff) + +#define HEAD (0xe0000000) +#define BRIGHT (0x1f000000) +#define BLUE (0x00ff0000) +#define GREEN (0x0000ff00) +#define RED (0x000000ff) +#define BRIGHT_SHIFT (21U) +#define BLUE_SHIFT (16U) +#define GREEN_SHIFT (8U) + + +static inline void shift(const apa102_t *dev, uint32_t data) +{ + for (int i = 31; i >= 0; i--) { + gpio_write(dev->data_pin, ((data >> i) & 0x01)); + gpio_set(dev->clk_pin); + gpio_clear(dev->clk_pin); + } +} + +void apa102_init(apa102_t *dev, const apa102_params_t *params) +{ + assert(dev && params); + + memcpy(dev, params, sizeof(apa102_params_t)); + + gpio_init(dev->data_pin, GPIO_OUT); + gpio_init(dev->clk_pin, GPIO_OUT); + gpio_clear(dev->data_pin); + gpio_clear(dev->clk_pin); +} + +void apa102_load_rgba(const apa102_t *dev, const color_rgba_t vals[]) +{ + assert(dev && vals); + + shift(dev, START); + for (int i = 0; i < dev->led_numof; i++) { + uint32_t data = HEAD; + /* we scale the 8-bit alpha value to a 5-bit value by cutting off the + * 3 leas significant bits */ + data |= (((uint32_t)vals[i].alpha << BRIGHT_SHIFT) & BRIGHT); + data |= ((uint32_t)vals[i].color.b << BLUE_SHIFT); + data |= ((uint32_t)vals[i].color.g << GREEN_SHIFT); + data |= vals[i].color.r; + shift(dev, data); + } + shift(dev, END); +} diff --git a/drivers/apa102/include/apa102_params.h b/drivers/apa102/include/apa102_params.h new file mode 100644 index 0000000000000000000000000000000000000000..d160eda84f44209cfc7311b48843a8f91f137444 --- /dev/null +++ b/drivers/apa102/include/apa102_params.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2017 Freie Universität Berlin + * + * 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_apa102 + * @{ + * + * @file + * @brief APA102 board specific configuration + * + * @author Hauke Petersen <hauke.petersen@fu-berlin.de> + */ + +#ifndef APA102_PARAMS_H +#define APA102_PARAMS_H + +#include "board.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Set default configuration parameters for the APA102 driver + * @{ + */ +#ifndef APA102_PARAM_LED_NUMOF +#define APA102_PARAM_LED_NUMOF (64) /* many have 64 per meter... */ +#endif +#ifndef APA102_PARAM_DATA_PIN +#define APA102_PARAM_DATA_PIN (GPIO_PIN(0, 0)) +#endif +#ifndef APA102_PARAM_CLK_PIN +#define APA102_PARAM_CLK_PIN (GPIO_PIN(0, 1)) +#endif + +#define APA102_PARAMS_DEFAULT { .led_numof = APA102_PARAM_LED_NUMOF, \ + .data_pin = APA102_PARAM_DATA_PIN, \ + .clk_pin = APA102_PARAM_CLK_PIN } +/**@}*/ + +/** + * @brief APA102 configuration + */ +static const apa102_params_t apa102_params[] = +{ +#ifdef APA102_PARAMS_BOARD + APA102_PARAMS_BOARD, +#else + APA102_PARAMS_DEFAULT, +#endif +}; + +#ifdef __cplusplus +} +#endif + +#endif /* APA102_PARAMS_H */ +/** @} */ diff --git a/drivers/include/apa102.h b/drivers/include/apa102.h new file mode 100644 index 0000000000000000000000000000000000000000..df0c56ff9e11e167cdf096075c89ce1584bed533 --- /dev/null +++ b/drivers/include/apa102.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2017 Freie Universität Berlin + * + * 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_apa102 APA102 RGB LED + * @ingroup drivers_actuators + * @brief Driver for chained APA102 RGB LEDs + * @{ + * + * @file + * @brief Interface for controlling APA102 LEDs + * + * @author Hauke Petersen <hauke.petersen@fu-berlin.de> + */ + +#ifndef APA102_H +#define APA102_H + +#include "color.h" +#include "periph/gpio.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Configuration parameters for (chained) APA102 LEDs + */ +typedef struct { + int led_numof; /**< number of chained LEDs */ + gpio_t data_pin; /**< data pin */ + gpio_t clk_pin; /**< clock pin */ +} apa102_params_t; + +/** + * @brief Device descriptor definition for APA102 LEDs + */ +typedef apa102_params_t apa102_t; + +/** + * @brief Initialize (chained) APA102 LEDs + * + * @param[out] dev device descriptor + * @param[in] params device configuration + * + * @pre @p dev != NULL + * @pre @p params != NULL + */ +void apa102_init(apa102_t *dev, const apa102_params_t *params); + +/** + * @brief Apply the given color values to the connected LED(s) + * + * @param[in] dev device descriptor + * @param[in] vals color values, MUST be of size `dev->led_numof` + * + * @pre @p dev != NULL + * @pre @p vals != NULL + */ +void apa102_load_rgba(const apa102_t *dev, const color_rgba_t vals[]); + +#ifdef __cplusplus +} +#endif + +#endif /* APA102_H */ +/** @} */ diff --git a/sys/include/color.h b/sys/include/color.h index d4e6cd230b5616986553616f712f262f4698e7c4..8612e898b9a5472838fabdd139694901467b4106 100644 --- a/sys/include/color.h +++ b/sys/include/color.h @@ -38,6 +38,14 @@ typedef struct { uint8_t b; /**< blue value [0 - 255] */ } color_rgb_t; +/** + * @brief RGBA color value + */ +typedef struct { + color_rgb_t color; /**< RGB value */ + uint8_t alpha; /**< alpha value [0 - 255] */ +} color_rgba_t; + /** * @brief Data-structure for holding HSV colors */ @@ -47,7 +55,6 @@ typedef struct { float v; /**< value [0.0 - 1.0] */ } color_hsv_t; - /** * @brief Convert RGB color to HSV color * diff --git a/tests/driver_apa102/Makefile b/tests/driver_apa102/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..f579a21d0470948d3dc1bb3a6faf54baf00083d9 --- /dev/null +++ b/tests/driver_apa102/Makefile @@ -0,0 +1,8 @@ +APPLICATION = driver_apa102 +include ../Makefile.tests_common + +USEMODULE += apa102 +USEMODULE += color +USEMODULE += xtimer + +include $(RIOTBASE)/Makefile.include diff --git a/tests/driver_apa102/README.md b/tests/driver_apa102/README.md new file mode 100644 index 0000000000000000000000000000000000000000..a064b8528acf25f24880578f05fe98ed9cd16e25 --- /dev/null +++ b/tests/driver_apa102/README.md @@ -0,0 +1,21 @@ +# About +This test application is made for verification of the APA102 LED strip driver. + +# Usage +Connect a APA102 based LED strip to a board of your choice, build, and flash +this application. When run, you should see the strip changing its brightness +while lighting in red, then green, and then blue. After this sequence, you +should see a light moving up and down the strip, changing its color. + + +You might need to adjust the default parameters (number of LEDs on the strip and +pin configuration). You can do this simply by pre-setting the `CFLAGS` +environment variable, e.g.: +``` +$ CFLAGS="-DAPA102_PARAM_LED_NUMOF=78"" make all +``` + +To change the default pins, simply override the default parameters, e.g.: +``` +$ CFLAGS="-DAPA102_PARAM_DATA_PIN=GPIO_PIN\(2,3\) -DAPA102_PARAM_CLK_PIN=GPIO_PIN\(1,17\)" make all +``` diff --git a/tests/driver_apa102/main.c b/tests/driver_apa102/main.c new file mode 100644 index 0000000000000000000000000000000000000000..4482a4f6775c1c6ad50b9dab6a5194457e680ee4 --- /dev/null +++ b/tests/driver_apa102/main.c @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2017 Freie Universität Berlin + * + * 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 APA102 LED strip driver + * + * @author Hauke Petersen <hauke.petersen@fu-berlin.de> + * + * @} + */ + +#include <string.h> +#include <stdio.h> +#include <stdint.h> + +#include "xtimer.h" +#include "color.h" + +#include "apa102.h" +#include "apa102_params.h" + +/** + * @brief Switch to the next LED every 10ms + */ +#define STEP (200 * US_PER_MS) + +/** + * @brief Interval for dimming colors + */ +#define DIM (100 * US_PER_MS) + +/** + * @brief Step through brightness levels (0-255) in 32 steps + */ +#define BSTEP (8U) + +/** + * @brief Allocate the device descriptor + */ +static apa102_t dev; + +/** + * @brief Allocate a color_rgb_t struct for each LED on the strip + */ +static color_rgba_t leds[APA102_PARAM_LED_NUMOF]; + +static void setcolor(int color, uint8_t alpha) +{ + for (int i = 0; i < (int)APA102_PARAM_LED_NUMOF; i++) { + leds[i].alpha = alpha; + memset(&leds[i].color, 0, sizeof(color_rgb_t)); + switch (color) { + case 0: + leds[i].color.r = 255; + break; + case 1: + leds[i].color.g = 255; + break; + case 2: + leds[i].color.b = 255; + break; + } + } +} + +int main(void) +{ + int pos = 0; + int step = 1; + color_hsv_t col = { 0.0, 1.0, 1.0 }; + + puts("APA102 Test App"); + + /* initialize all LED color values to black (off) */ + memset(leds, 0, sizeof(color_rgba_t) * APA102_PARAM_LED_NUMOF); + + /* initialize the driver */ + apa102_init(&dev, &apa102_params[0]); + + puts("Initialization done."); + + /* set to each red, green, and blue, and fade each color in and out */ + for (int col = 0; col <= 2; col++) { + int i = 0; + for (; i <= (int)UINT8_MAX; i += BSTEP) { + setcolor(col, (uint8_t)i); + apa102_load_rgba(&dev, leds); + xtimer_usleep(DIM); + } + i -= BSTEP; + for (; i >= 0; i -= BSTEP) { + setcolor(col, (uint8_t)i); + apa102_load_rgba(&dev, leds); + xtimer_usleep(DIM); + } + } + + puts("Color Fading done"); + + /* reset color values */ + setcolor(-1, 255); + apa102_load_rgba(&dev, leds); + + xtimer_ticks32_t now = xtimer_now(); + + while (1) { + /* change the active color by running around the hue circle */ + col.h += 1.0; + if (col.h > 360.0) { + col.h = 0.0; + } + + /* set the active LED to the active color value */ + memset(&leds[pos].color, 0, sizeof(color_rgb_t)); + pos += step; + color_hsv2rgb(&col, &leds[pos].color); + + /* apply the values to the LED strip */ + apa102_load_rgba(&dev, leds); + + /* switch direction once reaching an end of the strip */ + if ((pos == (APA102_PARAM_LED_NUMOF - 1)) || (pos == 0)) { + step *= -1; + } + + xtimer_periodic_wakeup(&now, STEP); + } + + return 0; +}