ws2812_i2s: WS2812 leds driver implementation using i2s_dma library
This commit is contained in:
parent
27135d6252
commit
666f821263
6 changed files with 347 additions and 0 deletions
6
examples/ws2812_i2s/Makefile
Normal file
6
examples/ws2812_i2s/Makefile
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
# Makefile for the ws2812_i2s example
|
||||||
|
|
||||||
|
PROGRAM=ws2812_i2s_example
|
||||||
|
EXTRA_COMPONENTS = extras/i2s_dma extras/ws2812_i2s
|
||||||
|
|
||||||
|
include ../../common.mk
|
83
examples/ws2812_i2s/ws2812_i2s_colour_loop.c
Normal file
83
examples/ws2812_i2s/ws2812_i2s_colour_loop.c
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
/**
|
||||||
|
* Example of ws2812_i2s library usage.
|
||||||
|
*
|
||||||
|
* This example shows light that travels in circle with fading tail.
|
||||||
|
* As ws2812_i2s library using hardware I2S the output pin is GPIO3 and
|
||||||
|
* can not be changed.
|
||||||
|
*
|
||||||
|
* This sample code is in the public domain.,
|
||||||
|
*/
|
||||||
|
#include "espressif/esp_common.h"
|
||||||
|
#include "FreeRTOS.h"
|
||||||
|
#include "task.h"
|
||||||
|
#include "esp/uart.h"
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include "ws2812_i2s/ws2812_i2s.h"
|
||||||
|
|
||||||
|
const uint32_t led_number = 60;
|
||||||
|
const uint32_t tail_fade_factor = 2;
|
||||||
|
const uint32_t tail_length = 8;
|
||||||
|
|
||||||
|
static void fade_pixel(ws2812_pixel_t *pixel, uint32_t factor)
|
||||||
|
{
|
||||||
|
pixel->red = pixel->red / factor;
|
||||||
|
pixel->green = pixel->green / factor;
|
||||||
|
pixel->blue = pixel->blue / factor;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int fix_index(int index)
|
||||||
|
{
|
||||||
|
if (index < 0) {
|
||||||
|
return (int)led_number + index;
|
||||||
|
} else if (index >= led_number) {
|
||||||
|
return index - led_number;
|
||||||
|
} else {
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static ws2812_pixel_t next_colour()
|
||||||
|
{
|
||||||
|
ws2812_pixel_t colour = {0, 0, 0};
|
||||||
|
colour.red = rand() % 256;
|
||||||
|
colour.green = rand() % 256;
|
||||||
|
colour.blue = rand() % 256;
|
||||||
|
|
||||||
|
return colour;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void demo(void *pvParameters)
|
||||||
|
{
|
||||||
|
ws2812_pixel_t pixels[led_number];
|
||||||
|
int head_index = 0;
|
||||||
|
|
||||||
|
ws2812_i2s_init(led_number);
|
||||||
|
|
||||||
|
memset(pixels, 0, sizeof(ws2812_pixel_t) * led_number);
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
pixels[head_index] = next_colour();
|
||||||
|
for (int i = 0; i < led_number; i++) {
|
||||||
|
head_index = fix_index(head_index + 1);
|
||||||
|
pixels[head_index] = pixels[fix_index(head_index-1)];
|
||||||
|
for (int ii = 1; ii < tail_length; ii++) {
|
||||||
|
fade_pixel(&pixels[fix_index(head_index - ii)], tail_fade_factor);
|
||||||
|
}
|
||||||
|
memset(&pixels[fix_index(head_index - tail_length)], 0,
|
||||||
|
sizeof(ws2812_pixel_t));
|
||||||
|
|
||||||
|
ws2812_i2s_update(pixels);
|
||||||
|
vTaskDelay(20 / portTICK_RATE_MS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void user_init(void)
|
||||||
|
{
|
||||||
|
uart_set_baud(0, 115200);
|
||||||
|
|
||||||
|
xTaskCreate(&demo, (signed char *)"ws2812_i2s", 256, NULL, 10, NULL);
|
||||||
|
}
|
15
extras/ws2812_i2s/README.md
Normal file
15
extras/ws2812_i2s/README.md
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
# WS2812 led driver
|
||||||
|
|
||||||
|
This driver uses I2S and DMA subsystems to drive WS2812 leds.
|
||||||
|
The idea to use I2S to control WS2812 leds belongs to [CNLohr](https://github.com/CNLohr).
|
||||||
|
|
||||||
|
## Pros
|
||||||
|
|
||||||
|
* Not using CPU to generate pulses.
|
||||||
|
* Interrupt neutral. Reliable operation even with high network load.
|
||||||
|
|
||||||
|
## Cons
|
||||||
|
|
||||||
|
* Using RAM for DMA buffer. 12 bytes per pixel.
|
||||||
|
* Can not change output PIN. Use I2S DATA output pin which is GPIO3.
|
||||||
|
|
9
extras/ws2812_i2s/component.mk
Normal file
9
extras/ws2812_i2s/component.mk
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
# Component makefile for extras/ws2812_i2s
|
||||||
|
|
||||||
|
# expected anyone using ws2812_i2s driver includes it as 'ws2812_i2s/ws2812_i2s.h'
|
||||||
|
INC_DIRS += $(ws2812_i2s_ROOT)..
|
||||||
|
|
||||||
|
# args for passing into compile rule generation
|
||||||
|
ws2812_i2s_SRC_DIR = $(ws2812_i2s_ROOT)
|
||||||
|
|
||||||
|
$(eval $(call component_compile_rules,ws2812_i2s))
|
181
extras/ws2812_i2s/ws2812_i2s.c
Normal file
181
extras/ws2812_i2s/ws2812_i2s.c
Normal file
|
@ -0,0 +1,181 @@
|
||||||
|
/**
|
||||||
|
* The MIT License (MIT)
|
||||||
|
*
|
||||||
|
* Copyright (c) 2016 sheinz (https://github.com/sheinz)
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
#include "ws2812_i2s.h"
|
||||||
|
#include "i2s_dma/i2s_dma.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <malloc.h>
|
||||||
|
|
||||||
|
// #define WS2812_I2S_DEBUG
|
||||||
|
|
||||||
|
#ifdef WS2812_I2S_DEBUG
|
||||||
|
#include <stdio.h>
|
||||||
|
#define debug(fmt, ...) printf("%s" fmt "\n", "ws2812_i2s: ", ## __VA_ARGS__);
|
||||||
|
#else
|
||||||
|
#define debug(fmt, ...)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define MAX_DMA_BLOCK_SIZE 4095
|
||||||
|
#define DMA_PIXEL_SIZE 12 // each colour takes 4 bytes
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Amount of zero data to produce WS2812 reset condition.
|
||||||
|
* DMA data must be multiple of 4
|
||||||
|
* 16 bytes of 0 gives ~50 microseconds of low pulse
|
||||||
|
*/
|
||||||
|
#define WS2812_ZEROES_LENGTH 16
|
||||||
|
|
||||||
|
static uint8_t i2s_dma_zero_buf[WS2812_ZEROES_LENGTH] = {0};
|
||||||
|
|
||||||
|
static dma_descriptor_t *dma_block_list;
|
||||||
|
static uint32_t dma_block_list_size;
|
||||||
|
|
||||||
|
static void *dma_buffer;
|
||||||
|
static uint32_t dma_buffer_size;
|
||||||
|
|
||||||
|
#ifdef WS2812_I2S_DEBUG
|
||||||
|
volatile uint32_t dma_isr_counter = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static volatile bool i2s_dma_processing = false;
|
||||||
|
|
||||||
|
static void dma_isr_handler(void)
|
||||||
|
{
|
||||||
|
if (i2s_dma_is_eof_interrupt()) {
|
||||||
|
#ifdef WS2812_I2S_DEBUG
|
||||||
|
dma_isr_counter++;
|
||||||
|
#endif
|
||||||
|
i2s_dma_processing = false;
|
||||||
|
}
|
||||||
|
i2s_dma_clear_interrupt();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Form a linked list of descriptors (dma blocks).
|
||||||
|
* The last two blocks are zero block and stop block.
|
||||||
|
* The last block is a stop terminal block. It has no data and no next block.
|
||||||
|
*/
|
||||||
|
static inline void init_descriptors_list(uint8_t *buf, uint32_t total_dma_data_size)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < dma_block_list_size; i++) {
|
||||||
|
dma_block_list[i].owner = 1;
|
||||||
|
dma_block_list[i].eof = 0;
|
||||||
|
dma_block_list[i].sub_sof = 0;
|
||||||
|
dma_block_list[i].unused = 0;
|
||||||
|
dma_block_list[i].buf_ptr = buf;
|
||||||
|
|
||||||
|
if (total_dma_data_size >= MAX_DMA_BLOCK_SIZE) {
|
||||||
|
dma_block_list[i].datalen = MAX_DMA_BLOCK_SIZE;
|
||||||
|
dma_block_list[i].blocksize = MAX_DMA_BLOCK_SIZE;
|
||||||
|
total_dma_data_size -= MAX_DMA_BLOCK_SIZE;
|
||||||
|
buf += MAX_DMA_BLOCK_SIZE;
|
||||||
|
} else {
|
||||||
|
dma_block_list[i].datalen = total_dma_data_size;
|
||||||
|
dma_block_list[i].blocksize = total_dma_data_size;
|
||||||
|
total_dma_data_size = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i == (dma_block_list_size - 2)) { // zero block
|
||||||
|
dma_block_list[i].buf_ptr = i2s_dma_zero_buf;
|
||||||
|
dma_block_list[i].datalen = WS2812_ZEROES_LENGTH;
|
||||||
|
dma_block_list[i].blocksize = WS2812_ZEROES_LENGTH;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i == (dma_block_list_size - 1)) { // stop block
|
||||||
|
// it needs a valid buffer even if no data to output
|
||||||
|
dma_block_list[i].buf_ptr = i2s_dma_zero_buf;
|
||||||
|
dma_block_list[i].datalen = 0;
|
||||||
|
dma_block_list[i].blocksize = WS2812_ZEROES_LENGTH;
|
||||||
|
dma_block_list[i].next_link_ptr = 0;
|
||||||
|
|
||||||
|
// the last stop block should trigger interrupt
|
||||||
|
dma_block_list[i].eof = 1;
|
||||||
|
} else {
|
||||||
|
dma_block_list[i].next_link_ptr = &dma_block_list[i + 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ws2812_i2s_init(uint32_t pixels_number)
|
||||||
|
{
|
||||||
|
dma_buffer_size = pixels_number * DMA_PIXEL_SIZE;
|
||||||
|
dma_block_list_size = dma_buffer_size / MAX_DMA_BLOCK_SIZE;
|
||||||
|
|
||||||
|
if (dma_buffer_size % MAX_DMA_BLOCK_SIZE) {
|
||||||
|
dma_block_list_size += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
dma_block_list_size += 2; // zero block and stop block
|
||||||
|
|
||||||
|
debug("allocating %d dma blocks\n", dma_block_list_size);
|
||||||
|
|
||||||
|
dma_block_list = (dma_descriptor_t*)malloc(
|
||||||
|
dma_block_list_size * sizeof(dma_descriptor_t));
|
||||||
|
|
||||||
|
debug("allocating %d bytes for DMA buffer\n", dma_buffer_size);
|
||||||
|
dma_buffer = malloc(dma_buffer_size);
|
||||||
|
memset(dma_buffer, 0xFA, dma_buffer_size);
|
||||||
|
|
||||||
|
init_descriptors_list(dma_buffer, dma_buffer_size);
|
||||||
|
|
||||||
|
i2s_clock_div_t clock_div = i2s_get_clock_div(3333333);
|
||||||
|
i2s_pins_t i2s_pins = {.data = true, .clock = false, .ws = false};
|
||||||
|
|
||||||
|
debug("i2s clock dividers, bclk=%d, clkm=%d\n",
|
||||||
|
clock_div.bclk_div, clock_div.clkm_div);
|
||||||
|
|
||||||
|
i2s_dma_init(dma_isr_handler, clock_div, i2s_pins);
|
||||||
|
}
|
||||||
|
|
||||||
|
const IRAM_DATA int16_t bitpatterns[16] =
|
||||||
|
{
|
||||||
|
0b1000100010001000, 0b1000100010001110, 0b1000100011101000, 0b1000100011101110,
|
||||||
|
0b1000111010001000, 0b1000111010001110, 0b1000111011101000, 0b1000111011101110,
|
||||||
|
0b1110100010001000, 0b1110100010001110, 0b1110100011101000, 0b1110100011101110,
|
||||||
|
0b1110111010001000, 0b1110111010001110, 0b1110111011101000, 0b1110111011101110,
|
||||||
|
};
|
||||||
|
|
||||||
|
void ws2812_i2s_update(ws2812_pixel_t *pixels)
|
||||||
|
{
|
||||||
|
while (i2s_dma_processing) {};
|
||||||
|
|
||||||
|
uint16_t *p_dma_buf = dma_buffer;
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < (dma_buffer_size / DMA_PIXEL_SIZE); i++) {
|
||||||
|
// green
|
||||||
|
*p_dma_buf++ = bitpatterns[pixels[i].green & 0x0F];
|
||||||
|
*p_dma_buf++ = bitpatterns[pixels[i].green >> 4];
|
||||||
|
|
||||||
|
// red
|
||||||
|
*p_dma_buf++ = bitpatterns[pixels[i].red & 0x0F];
|
||||||
|
*p_dma_buf++ = bitpatterns[pixels[i].red >> 4];
|
||||||
|
|
||||||
|
// blue
|
||||||
|
*p_dma_buf++ = bitpatterns[pixels[i].blue & 0x0F];
|
||||||
|
*p_dma_buf++ = bitpatterns[pixels[i].blue >> 4];
|
||||||
|
}
|
||||||
|
|
||||||
|
i2s_dma_processing = true;
|
||||||
|
i2s_dma_start(dma_block_list);
|
||||||
|
}
|
53
extras/ws2812_i2s/ws2812_i2s.h
Normal file
53
extras/ws2812_i2s/ws2812_i2s.h
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
/**
|
||||||
|
* The MIT License (MIT)
|
||||||
|
*
|
||||||
|
* Copyright (c) 2016 sheinz (https://github.com/sheinz)
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
#ifndef __WS2812_I2S_H__
|
||||||
|
#define __WS2812_I2S_H__
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t red;
|
||||||
|
uint8_t green;
|
||||||
|
uint8_t blue;
|
||||||
|
} ws2812_pixel_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize i2s and dma subsystems to work with ws2812 led strip.
|
||||||
|
*
|
||||||
|
* Please note that each pixel will take 12 bytes of RAM.
|
||||||
|
*
|
||||||
|
* @param pixels_number Number of pixels in the strip.
|
||||||
|
*/
|
||||||
|
void ws2812_i2s_init(uint32_t pixels_number);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update ws2812 pixels.
|
||||||
|
*
|
||||||
|
* @param pixels Array of 'pixels_number' pixels. The array must contain all
|
||||||
|
* the pixels.
|
||||||
|
*/
|
||||||
|
void ws2812_i2s_update(ws2812_pixel_t *pixels);
|
||||||
|
|
||||||
|
#endif // __WS2812_I2S_H__
|
Loading…
Reference in a new issue