ws2812_i2s: WS2812 leds driver implementation using i2s_dma library

This commit is contained in:
sheinz 2016-08-16 10:53:56 +03:00
parent 27135d6252
commit 666f821263
6 changed files with 347 additions and 0 deletions

View 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

View 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);
}

View 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.

View 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))

View 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);
}

View 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__