esp-open-rtos/extras/ws2812_i2s/ws2812_i2s.c

182 lines
6 KiB
C
Raw Permalink Normal View History

/**
* 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);
}