diff --git a/examples/ws2812_rainbow/Makefile b/examples/ws2812_rainbow/Makefile new file mode 100644 index 0000000..dad3155 --- /dev/null +++ b/examples/ws2812_rainbow/Makefile @@ -0,0 +1,6 @@ +# Makefile for the ws2812 Rainbow example + +PROGRAM=ws2812_rainbow +EXTRA_COMPONENTS = extras/ws2812 + +include ../../common.mk diff --git a/examples/ws2812_rainbow/ws2812_rainbow.c b/examples/ws2812_rainbow/ws2812_rainbow.c new file mode 100644 index 0000000..4d37acc --- /dev/null +++ b/examples/ws2812_rainbow/ws2812_rainbow.c @@ -0,0 +1,153 @@ +/** + * @file es2812_rainbow.c + * @author Ondřej Hruška, 2016 + * + * @brief Example of a rainbow effect with + * WS2812 connected to GPIO2. + * + * This demo is in the public domain. + */ + +#include "espressif/esp_common.h" +#include "FreeRTOS.h" +#include "task.h" +#include "esp/uart.h" // uart_set_baud +#include // printf +#include + +#include "ws2812.h" + + +#define delay_ms(ms) vTaskDelay((ms) / portTICK_RATE_MS) + + +/** GPIO number used to control the RGBs */ +static const uint8_t pin = 2; + + +/** + * @brief "rainbow" animation with a single RGB led. + */ +void demo_single(void) +{ + // duration between color changes + const uint8_t delay = 25; + + ws2812_rgb_t x = {.num = 0xFF0000}; // RED color + + while (1) { + // iterate through the spectrum + + // note: This would be _WAY_ easier with HSL + + while(x.g < 0xFF) { x.g++; ws2812_set(pin, x.num); delay_ms(delay); } // R->RG + while(x.r > 0x00) { x.r--; ws2812_set(pin, x.num); delay_ms(delay); } // RG->G + while(x.b < 0xFF) { x.b++; ws2812_set(pin, x.num); delay_ms(delay); } // G->GB + while(x.g > 0x00) { x.g--; ws2812_set(pin, x.num); delay_ms(delay); } // GB->B + while(x.r < 0xFF) { x.r++; ws2812_set(pin, x.num); delay_ms(delay); } // B->BR + while(x.b > 0x00) { x.b--; ws2812_set(pin, x.num); delay_ms(delay); } // BR->R + } +} + + +/** + * @brief "rainbow" effect on a RGB strip (30 pixels - can be adjusted) + * + * This example shows how to use the "procedural generation" of colors. + * + * The pixel colors are calculated on the fly, which saves RAM + * (especially with large displays). + */ +void demo_strip(void *pvParameters) +{ + const uint8_t anim_step = 10; + const uint8_t anim_max = 250; + + // Number of your "pixels" + const uint8_t pixel_count = 30; + + // duration between color changes + const uint8_t delay = 25; + + ws2812_rgb_t color = WS2812_RGB(anim_max, 0, 0); + uint8_t step = 0; + + ws2812_rgb_t color2 = WS2812_RGB(anim_max, 0, 0); + uint8_t step2 = 0; + + while (1) { + + color = color2; + step = step2; + + // Start a data sequence (disables interrupts) + ws2812_seq_start(); + + for (uint8_t i = 0; i < pixel_count; i++) { + + // send a color + ws2812_seq_rgb(pin, color.num); + + // now we have a few hundred nanoseconds + // to calculate the next color + + if (i == 1) { + color2 = color; + step2 = step; + } + + switch (step) { + case 0: color.g += anim_step; if (color.g >= anim_max) step++; break; + case 1: color.r -= anim_step; if (color.r == 0) step++; break; + case 2: color.b += anim_step; if (color.b >= anim_max) step++; break; + case 3: color.g -= anim_step; if (color.g == 0) step++; break; + case 4: color.r += anim_step; if (color.r >= anim_max) step++; break; + case 5: color.b -= anim_step; if (color.b == 0) step = 0; break; + } + } + + // End the data sequence, display colors (interrupts are restored) + ws2812_seq_end(); + + // wait a bit + delay_ms(delay); + } +} + + +void user_init(void) +{ + uart_set_baud(0, 115200); + printf("--- RGB Rainbow demo ---"); + + // Configure the GPIO + gpio_enable(pin, GPIO_OUTPUT); + + // Select a demo function: + +#if true +# define demo demo_strip +#else +# define demo demo_single +#endif + + + // Choose how to run it: + +#if true + + // Blocking function - works OK, because WiFi isn't + // initialized yet & we're hogging the CPU. + + printf("Starting a blocking function.\r\n"); + demo(NULL); + +#else + + // Start a task. This is a real-life example, + // notice the glitches due to NMI. + + printf("Starting a task. There may be glitches!\r\n"); + xTaskCreate(&demo, (signed char *)"strip demo", 256, NULL, 10, NULL); +#endif +} diff --git a/extras/ws2812/component.mk b/extras/ws2812/component.mk new file mode 100644 index 0000000..df2fc59 --- /dev/null +++ b/extras/ws2812/component.mk @@ -0,0 +1,8 @@ +# Component makefile for extras/ws2812 + +INC_DIRS += $(ws2812_ROOT) + +# args for passing into compile rule generation +ws2812_SRC_DIR = $(ws2812_ROOT) + +$(eval $(call component_compile_rules,ws2812)) \ No newline at end of file diff --git a/extras/ws2812/ws2812.c b/extras/ws2812/ws2812.c new file mode 100644 index 0000000..c1e04be --- /dev/null +++ b/extras/ws2812/ws2812.c @@ -0,0 +1,44 @@ +/** + * @file ws2812b.c + * @brief ESP8266 driver for WS2812B + * @author Ondřej Hruška, (c) 2016 + * + * MIT License + */ + +#include "espressif/esp_common.h" // sdk_os_delay_us +#include "FreeRTOS.h" +#include "task.h" +#include + +#include "ws2812.h" + + +/** Set one RGB LED color */ +void ws2812_set(uint8_t gpio_num, uint32_t rgb) +{ + ws2812_seq_start(); + ws2812_seq_rgb(gpio_num, rgb); + ws2812_seq_end(); +} + + +/** Set many RGBs */ +void ws2812_set_many(uint8_t gpio_num, uint32_t *rgbs, size_t count) +{ + ws2812_seq_start(); + + for (size_t i = 0; i < count; i++) { + uint32_t rgb = *rgbs++; + ws2812_seq_rgb(gpio_num, rgb); + } + + ws2812_seq_end(); +} + + +/** Set one RGB to black (when used as indicator) */ +void ws2812_off(uint8_t gpio_num) +{ + ws2812_set(gpio_num, 0x000000); +} diff --git a/extras/ws2812/ws2812.h b/extras/ws2812/ws2812.h new file mode 100644 index 0000000..d614e60 --- /dev/null +++ b/extras/ws2812/ws2812.h @@ -0,0 +1,208 @@ +/** + * @file ws2812.h + * @brief ESP8266 driver for WS2812 + * + * This is a simple bit-banging driver for WS2812. + * + * It will work for WS2812, WS2812B and possibly others, + * with small alterations. + * + * The WS2812 protocol takes roughly 1µs per bit, + * thus ~24µs per "pixel", plus 50µs to indicate + * the end of data. + * + * @note + * The GPIO must be configured for output before trying + * to set colors! + * + * @attention + * Due to the precise timing required, interrupts are + * disabled until the data is sent. However, we currently + * can't disable the NMI, so under some conditions + * (heavy network load), the timing may be disturbed. + * + * @author Ondřej Hruška, (c) 2016 + * + * MIT License + */ + +#ifndef WS2812_DRV_H +#define WS2812_DRV_H + +#include +#include // size_t + +#include "espressif/esp_common.h" // sdk_os_delay_us +#include "esp/gpio.h" + +/** + * @brief Struct for easy manipulation of RGB colors. + * + * Set components in the xrgb.r (etc.) and you will get + * the hex in xrgb.num. + */ +typedef union { + + /** Struct for access to individual color components */ + struct __attribute__((packed)) { + uint8_t b; + uint8_t g; + uint8_t r; + }; + + /** RGB color as a single uint32_t */ + uint32_t num; + +} ws2812_rgb_t; + + +/** + * @brief Macro to compose the RGB struct. + * + * You can also use {.num = 0xFF0000} to set the hex directly. + */ +#define WS2812_RGB(r, g, b) {.num = (((r) & 0xFF) << 16) | (((g) & 0xFF) << 8) | ((b) & 0xFF)} + + +/** + * @brief Set a single RGB LED color, and display it. + * + * @param gpio_num : data line GPIO number + * @param rgb : RGB color - 0x00RRGGBB + */ +void ws2812_set(uint8_t gpio_num, uint32_t rgb); + + +/** + * @brief Set color of multiple RGB LEDs, and display it. + * + * @note + * Due to the precise timing required, interrupts are + * disabled until all data is sent (roughly 25*count + 50µs) + * + * @param gpio_num : data line GPIO number + * @param rgbs : array of RGB colors in the 0x00RRGGBB format + * @param count : number of elements in the array + */ +void ws2812_set_many(uint8_t gpio_num, uint32_t *rgbs, size_t count); + + +/** + * @brief Turn a single WS2812B off + * + * This is a companion function to ws2812_set(). + * + * It sets the color to BLACK, thus effectively + * turning the RGB LED off. + * + * @param gpio_num : data line GPIO number + */ +void ws2812_off(uint8_t gpio_num); + + + +/////////////////////////////////////////////////////////////////// +/// +/// The following part of the driver is used internally, and can +/// also be used for "procedural generation" of colors. +/// +/// - first call ws2812b_seq_start(), +/// - then repeatedly ws2812b_seq_rgb() with your colors +/// - and end the sequence with ws2812b_seq_end() +/// +/////////////////////////////////////////////////////////////////// + + + +// Ugly way to get short delays. Works for 80 MHz. + +// 400 ns +#ifndef WS2812_SHORT_DELAY +#define WS2812_SHORT_DELAY() for (volatile uint32_t __j = 1; __j > 0; __j--) +#endif + +// 800 ns +#ifndef WS2812_LONG_DELAY +#define WS2812_LONG_DELAY() for (volatile uint32_t __j = 3; __j > 0; __j--) +#endif + + +/** + * @brief Send a byte on the data line. + * + * The WS2812B is a form of PWM: + * - each bit takes roughly 1 µs (but can be longer) + * - the duration of the ON part determines the value + * - 800 ns -> "1" + * - 400 ns -> "0" + * + * The timing must be very precise, you may need to adjust + * it according for particular RGB model. + * + * @param gpio_num : data line GPIO number + * @param byte : byte to send + */ +static inline +void ws2812_byte(uint8_t gpio_num, uint8_t byte) +{ + for (register volatile uint8_t i = 0; i < 8; i++) { + gpio_write(gpio_num, 1); + + // duty cycle determines the bit value + + if (byte & 0x80) { + WS2812_LONG_DELAY(); + gpio_write(gpio_num, 0); + WS2812_SHORT_DELAY(); + } else { + WS2812_SHORT_DELAY(); + gpio_write(gpio_num, 0); + WS2812_LONG_DELAY(); + } + + byte <<= 1; // shift to next bit + } +} + + +/** + * @brief Send a color to the RGB strip. + * + * This function must be called inside the data sequence. + * + * @param gpio_num : data line GPIO number + * @param rgb : 0xRRGGBB color + */ +static inline +void ws2812_seq_rgb(uint8_t gpio_num, uint32_t rgb) +{ + ws2812_byte(gpio_num, (rgb & 0x00FF00) >> 8); + ws2812_byte(gpio_num, (rgb & 0xFF0000) >> 16); + ws2812_byte(gpio_num, (rgb & 0x0000FF) >> 0); +} + + +/** + * @brief Start a data sequence. + */ +static inline +void ws2812_seq_start(void) +{ + // interruption when sending data would break the timing + taskENTER_CRITICAL(); +} + + +/** + * @brief End the data sequence and display the colors. + */ +static inline +void ws2812_seq_end(void) +{ + taskEXIT_CRITICAL(); + + sdk_os_delay_us(50); // display the loaded colors +} + + +#endif /* WS2812_DRV_H */