Merge pull request #106 from MightyPork/ws2812b-driver
WS2812B driver & example
This commit is contained in:
commit
f360935800
5 changed files with 419 additions and 0 deletions
6
examples/ws2812_rainbow/Makefile
Normal file
6
examples/ws2812_rainbow/Makefile
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
# Makefile for the ws2812 Rainbow example
|
||||||
|
|
||||||
|
PROGRAM=ws2812_rainbow
|
||||||
|
EXTRA_COMPONENTS = extras/ws2812
|
||||||
|
|
||||||
|
include ../../common.mk
|
153
examples/ws2812_rainbow/ws2812_rainbow.c
Normal file
153
examples/ws2812_rainbow/ws2812_rainbow.c
Normal file
|
@ -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 <stdio.h> // printf
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#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
|
||||||
|
}
|
8
extras/ws2812/component.mk
Normal file
8
extras/ws2812/component.mk
Normal file
|
@ -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))
|
44
extras/ws2812/ws2812.c
Normal file
44
extras/ws2812/ws2812.c
Normal file
|
@ -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 <stdint.h>
|
||||||
|
|
||||||
|
#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);
|
||||||
|
}
|
208
extras/ws2812/ws2812.h
Normal file
208
extras/ws2812/ws2812.h
Normal file
|
@ -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 <stdint.h>
|
||||||
|
#include <stddef.h> // 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 */
|
Loading…
Reference in a new issue