Merge pull request #189 from sheinz/feature/i2s_dma

i2s_dma wrapper, ws2812_i2s driver and examples.
This commit is contained in:
Johan Kanflo 2016-08-17 06:59:30 +02:00 committed by GitHub
commit 84ee8d493c
14 changed files with 877 additions and 3 deletions

View file

@ -37,6 +37,14 @@
#define VAL2FIELD_M(fieldname, value) (((value) & fieldname##_M) << fieldname##_S) #define VAL2FIELD_M(fieldname, value) (((value) & fieldname##_M) << fieldname##_S)
#define SET_FIELD_M(regbits, fieldname, value) (((regbits) & ~FIELD_MASK(fieldname)) | VAL2FIELD_M(fieldname, value)) #define SET_FIELD_M(regbits, fieldname, value) (((regbits) & ~FIELD_MASK(fieldname)) | VAL2FIELD_M(fieldname, value))
/* Set bits in reg with specified mask.
*/
#define SET_MASK_BITS(reg, mask) (reg) |= (mask)
/* Clear bits in reg with specified mask
*/
#define CLEAR_MASK_BITS(reg, mask) (reg) &= ~(mask)
/* Use the IRAM macro to place functions into Instruction RAM (IRAM) /* Use the IRAM macro to place functions into Instruction RAM (IRAM)
instead of flash (aka irom). instead of flash (aka irom).

View file

@ -43,10 +43,17 @@ inline static esp_reg_t gpio_iomux_reg(const uint8_t gpio_number)
return &(IOMUX.PIN[gpio_to_iomux(gpio_number)]); return &(IOMUX.PIN[gpio_to_iomux(gpio_number)]);
} }
inline static void iomux_set_function(uint8_t iomux_num, uint32_t func) /**
* Set IOMUX function.
*
* @param iomux_num Index of IOMUX register. Can be converted from GPIO number
* with gpio_to_iomux.
* @param iomux_func GPIO function definition IOMUX_GPIOn_FUNC_*
*/
inline static void iomux_set_function(uint8_t iomux_num, uint32_t iomux_func)
{ {
uint32_t prev = IOMUX.PIN[iomux_num] & ~IOMUX_PIN_FUNC_MASK; uint32_t prev = IOMUX.PIN[iomux_num] & ~IOMUX_PIN_FUNC_MASK;
IOMUX.PIN[iomux_num] = IOMUX_FUNC(func) | prev; IOMUX.PIN[iomux_num] = iomux_func | prev;
} }
inline static void iomux_set_direction_flags(uint8_t iomux_num, uint32_t dir_flags) inline static void iomux_set_direction_flags(uint8_t iomux_num, uint32_t dir_flags)
@ -76,7 +83,7 @@ inline static void iomux_set_gpio_function(uint8_t gpio_number, bool output_enab
{ {
const uint8_t iomux_num = gpio_to_iomux(gpio_number); const uint8_t iomux_num = gpio_to_iomux(gpio_number);
const uint32_t func = iomux_num > 11 ? 0 : 3; const uint32_t func = iomux_num > 11 ? 0 : 3;
iomux_set_function(iomux_num, func); iomux_set_function(iomux_num, IOMUX_FUNC(func));
iomux_set_direction_flags(iomux_num, output_enable ? IOMUX_PIN_OUTPUT_ENABLE : 0); iomux_set_direction_flags(iomux_num, output_enable ? IOMUX_PIN_OUTPUT_ENABLE : 0);
} }

View file

@ -0,0 +1,11 @@
PROGRAM=i2s_audio_example
EXTRA_COMPONENTS = extras/spiffs extras/i2s_dma
FLASH_SIZE = 32
# spiffs configuration
SPIFFS_BASE_ADDR = 0x200000
SPIFFS_SIZE = 0x100000
include ../../common.mk
$(eval $(call make_spiffs_image,files))

View file

@ -0,0 +1,197 @@
/* i2s_audio_example.c - Plays wav file from spiffs.
*
* This example demonstrates how to use I2S with DMA to output audio.
* The example is tested with TDA5143 16 bit DAC. But should work with
* any I2S DAC.
*
* The example reads a file with name "sample.wav" from the file system and
* feeds audio samples into DMA subsystem which outputs it into I2S bus.
* Currently only 44100 Hz 16 bit 2 channel audio is supported.
*
* In order to test this example you need to place a file with name "sample.wav"
* into directory "files". This file will be uploaded into spiffs on the device.
* The size of the sample file must be less than 1MB to fit into spiffs image.
* The format of the sample file must be 44100Hz, 16bit, 2 channels.
* Also you need a DAC connected to ESP8266 to convert I2S stream to analog
* output. Three wire must be connected: DATA, WS, CLOCK.
*
* This sample code is in the public domain.,
*/
#include "esp/uart.h"
#include "FreeRTOS.h"
#include "queue.h"
#include "task.h"
#include "esp8266.h"
#include "fcntl.h"
#include "unistd.h"
#include <stdio.h>
#include <stdint.h>
#include "esp_spiffs.h"
#include "i2s_dma/i2s_dma.h"
// Very simple WAV header, ignores most fields
typedef struct __attribute__((packed)) {
uint8_t ignore_0[22];
uint16_t num_channels;
uint32_t sample_rate;
uint8_t ignore_1[6];
uint16_t bits_per_sample;
uint8_t ignore_2[4];
uint32_t data_size;
uint8_t data[];
} dumb_wav_header_t;
// When samples are not sent fast enough underrun condition occurs
volatile uint32_t underrun_counter = 0;
#define DMA_BUFFER_SIZE 2048
#define DMA_QUEUE_SIZE 8
// Circular list of descriptors
static dma_descriptor_t dma_block_list[DMA_QUEUE_SIZE];
// Array of buffers for circular list of descriptors
static uint8_t dma_buffer[DMA_QUEUE_SIZE][DMA_BUFFER_SIZE];
// Queue of empty DMA blocks
static xQueueHandle dma_queue;
/**
* Create a circular list of DMA descriptors
*/
static inline void init_descriptors_list()
{
memset(dma_buffer, 0, DMA_QUEUE_SIZE * DMA_BUFFER_SIZE);
for (int i = 0; i < DMA_QUEUE_SIZE; i++) {
dma_block_list[i].owner = 1;
dma_block_list[i].eof = 1;
dma_block_list[i].sub_sof = 0;
dma_block_list[i].unused = 0;
dma_block_list[i].buf_ptr = dma_buffer[i];
dma_block_list[i].datalen = DMA_BUFFER_SIZE;
dma_block_list[i].blocksize = DMA_BUFFER_SIZE;
if (i == (DMA_QUEUE_SIZE - 1)) {
dma_block_list[i].next_link_ptr = &dma_block_list[0];
} else {
dma_block_list[i].next_link_ptr = &dma_block_list[i + 1];
}
}
// The queue depth is one smaller than the amount of buffers we have,
// because there's always a buffer that is being used by the DMA subsystem
// *right now* and we don't want to be able to write to that simultaneously
dma_queue = xQueueCreate(DMA_QUEUE_SIZE - 1, sizeof(uint8_t*));
}
// DMA interrupt handler. It is called each time a DMA block is finished processing.
static void dma_isr_handler(void)
{
portBASE_TYPE task_awoken = pdFALSE;
if (i2s_dma_is_eof_interrupt()) {
dma_descriptor_t *descr = i2s_dma_get_eof_descriptor();
if (xQueueIsQueueFullFromISR(dma_queue)) {
// List of empty blocks is full. Sender don't send data fast enough.
int dummy;
underrun_counter++;
// Discard top of the queue
xQueueReceiveFromISR(dma_queue, &dummy, &task_awoken);
}
// Push the processed buffer to the queue so sender can refill it.
xQueueSendFromISR(dma_queue, (void*)(&descr->buf_ptr), &task_awoken);
}
i2s_dma_clear_interrupt();
portEND_SWITCHING_ISR(task_awoken);
}
static bool play_data(int fd)
{
uint8_t *curr_dma_buf;
// Get a free block from the DMA queue. This call will suspend the task
// until a free block is available in the queue.
if (xQueueReceive(dma_queue, &curr_dma_buf, portMAX_DELAY) == pdFALSE) {
// Or timeout occurs
printf("Cound't get free blocks to push data\n");
}
int read_bytes = read(fd, curr_dma_buf, DMA_BUFFER_SIZE);
if (read_bytes <= 0) {
return false;
}
return true;
}
void play_task(void *pvParameters)
{
esp_spiffs_init();
if (esp_spiffs_mount() != SPIFFS_OK) {
printf("Error mount SPIFFS\n");
}
int fd = open("sample.wav", O_RDONLY);
if (fd < 0) {
printf("Error opening file\n");
return;
}
dumb_wav_header_t wav_header;
read(fd, (void*)&wav_header, sizeof(wav_header));
printf("Number of channels: %d\n", wav_header.num_channels);
printf("Bits per sample: %d\n", wav_header.bits_per_sample);
printf("Sample rate: %d\n", wav_header.sample_rate);
printf("Data size: %d\n", wav_header.data_size);
if (wav_header.bits_per_sample != 16) {
printf("Only 16 bit per sample is supported\n");
return;
}
if (wav_header.num_channels != 2) {
printf("Only 2 channels is supported\n");
return;
}
i2s_clock_div_t clock_div = i2s_get_clock_div(
wav_header.sample_rate * 2 * 16);
printf("i2s clock dividers, bclk=%d, clkm=%d\n",
clock_div.bclk_div, clock_div.clkm_div);
i2s_pins_t i2s_pins = {.data = true, .clock = true, .ws = true};
i2s_dma_init(dma_isr_handler, clock_div, i2s_pins);
while (1) {
init_descriptors_list();
i2s_dma_start(dma_block_list);
lseek(fd, sizeof(dumb_wav_header_t), SEEK_SET);
while (play_data(fd)) {};
i2s_dma_stop();
vQueueDelete(dma_queue);
printf("underrun counter: %d\n", underrun_counter);
vTaskDelay(1000 / portTICK_RATE_MS);
}
close(fd);
}
void user_init(void)
{
uart_set_baud(0, 115200);
xTaskCreate(play_task, (signed char *)"test_task", 1024, NULL, 2, NULL);
}

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

8
extras/i2s_dma/README.md Normal file
View file

@ -0,0 +1,8 @@
# Wrapper around hardware I2S and DMA subsystems of ESP8266
ESP8266 has hardware I2S bus support. I2S is a serial bus interface used for
connecting digital audio devices. But can be used to produce sequence of pulses
with reliable timings for example to control a strip of WS2812 leds.
This library is just a wrapper around tricky I2S initialization.
It sets necessary registers, enables I2S clock etc.

View file

@ -0,0 +1,9 @@
# Component makefile for extras/i2s_dma
# expected anyone using i2s_dma driver includes it as 'i2s_dma/i2s_dma.h'
INC_DIRS += $(i2s_dma_ROOT)..
# args for passing into compile rule generation
i2s_dma_SRC_DIR = $(i2s_dma_ROOT)
$(eval $(call component_compile_rules,i2s_dma))

171
extras/i2s_dma/i2s_dma.c Normal file
View file

@ -0,0 +1,171 @@
/**
* 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 "i2s_dma.h"
#include "esp/iomux.h"
#include "esp/i2s_regs.h"
#include "esp/interrupts.h"
#include "common_macros.h"
#include <stdlib.h>
// #define I2S_DMA_DEBUG
#ifdef I2S_DMA_DEBUG
#include <stdio.h>
#define debug(fmt, ...) printf("%s" fmt "\n", "i2s_dma: ", ## __VA_ARGS__);
#else
#define debug(fmt, ...)
#endif
// The following definitions is taken from ESP8266_MP3_DECODER demo
// https://github.com/espressif/ESP8266_MP3_DECODER/blob/master/mp3/driver/i2s_freertos.c
// It is requred to set clock to I2S subsystem
void sdk_rom_i2c_writeReg_Mask(uint32_t block, uint32_t host_id,
uint32_t reg_add, uint32_t Msb, uint32_t Lsb, uint32_t indata);
#ifndef i2c_bbpll
#define i2c_bbpll 0x67
#define i2c_bbpll_en_audio_clock_out 4
#define i2c_bbpll_en_audio_clock_out_msb 7
#define i2c_bbpll_en_audio_clock_out_lsb 7
#define i2c_bbpll_hostid 4
#endif
#define i2c_writeReg_Mask(block, host_id, reg_add, Msb, Lsb, indata) \
sdk_rom_i2c_writeReg_Mask(block, host_id, reg_add, Msb, Lsb, indata)
#define i2c_writeReg_Mask_def(block, reg_add, indata) \
i2c_writeReg_Mask(block, block##_hostid, reg_add, reg_add##_msb, \
reg_add##_lsb, indata)
void i2s_dma_init(i2s_dma_isr_t isr, i2s_clock_div_t clock_div, i2s_pins_t pins)
{
// reset DMA
SET_MASK_BITS(SLC.CONF0, SLC_CONF0_RX_LINK_RESET);
CLEAR_MASK_BITS(SLC.CONF0, SLC_CONF0_RX_LINK_RESET);
// clear DMA int flags
SLC.INT_CLEAR = 0xFFFFFFFF;
SLC.INT_CLEAR = 0;
// Enable and configure DMA
SLC.CONF0 = SET_FIELD(SLC.CONF0, SLC_CONF0_MODE, 0); // does it really needed?
SLC.CONF0 = SET_FIELD(SLC.CONF0, SLC_CONF0_MODE, 1);
// Do we really need to set and clear?
SET_MASK_BITS(SLC.RX_DESCRIPTOR_CONF, SLC_RX_DESCRIPTOR_CONF_INFOR_NO_REPLACE |
SLC_RX_DESCRIPTOR_CONF_TOKEN_NO_REPLACE);
CLEAR_MASK_BITS(SLC.RX_DESCRIPTOR_CONF, SLC_RX_DESCRIPTOR_CONF_RX_FILL_ENABLE |
SLC_RX_DESCRIPTOR_CONF_RX_EOF_MODE | SLC_RX_DESCRIPTOR_CONF_RX_FILL_MODE);
if (isr) {
_xt_isr_attach(INUM_SLC, isr);
SET_MASK_BITS(SLC.INT_ENABLE, SLC_INT_ENABLE_RX_EOF);
SLC.INT_CLEAR = 0xFFFFFFFF;
_xt_isr_unmask(1<<INUM_SLC);
}
// start transmission
SET_MASK_BITS(SLC.RX_LINK, SLC_RX_LINK_START);
if (pins.data) {
iomux_set_function(gpio_to_iomux(3), IOMUX_GPIO3_FUNC_I2SO_DATA);
}
if (pins.clock) {
iomux_set_function(gpio_to_iomux(15), IOMUX_GPIO15_FUNC_I2SO_BCK);
}
if (pins.ws) {
iomux_set_function(gpio_to_iomux(2), IOMUX_GPIO2_FUNC_I2SO_WS);
}
// enable clock to i2s subsystem
i2c_writeReg_Mask_def(i2c_bbpll, i2c_bbpll_en_audio_clock_out, 1);
// reset I2S subsystem
CLEAR_MASK_BITS(I2S.CONF, I2S_CONF_RESET_MASK);
SET_MASK_BITS(I2S.CONF, I2S_CONF_RESET_MASK);
CLEAR_MASK_BITS(I2S.CONF, I2S_CONF_RESET_MASK);
// select 16bits per channel (FIFO_MOD=0), no DMA access (FIFO only)
CLEAR_MASK_BITS(I2S.FIFO_CONF, I2S_FIFO_CONF_DESCRIPTOR_ENABLE);
I2S.FIFO_CONF = SET_FIELD(I2S.FIFO_CONF, I2S_FIFO_CONF_RX_FIFO_MOD, 0);
I2S.FIFO_CONF = SET_FIELD(I2S.FIFO_CONF, I2S_FIFO_CONF_TX_FIFO_MOD, 0);
//trans master&rece slave,MSB shift,right_first,msb right
CLEAR_MASK_BITS(I2S.CONF, I2S_CONF_TX_SLAVE_MOD);
I2S.CONF = SET_FIELD(I2S.CONF, I2S_CONF_BITS_MOD, 0);
I2S.CONF = SET_FIELD(I2S.CONF, I2S_CONF_BCK_DIV, 0);
I2S.CONF = SET_FIELD(I2S.CONF, I2S_CONF_CLKM_DIV, 0);
SET_MASK_BITS(I2S.CONF, I2S_CONF_RIGHT_FIRST | I2S_CONF_MSB_RIGHT |
I2S_CONF_RX_SLAVE_MOD | I2S_CONF_RX_MSB_SHIFT | I2S_CONF_TX_MSB_SHIFT);
I2S.CONF = SET_FIELD(I2S.CONF, I2S_CONF_BCK_DIV, clock_div.bclk_div);
I2S.CONF = SET_FIELD(I2S.CONF, I2S_CONF_CLKM_DIV, clock_div.clkm_div);
}
// Base frequency for I2S subsystem is independent from CPU clock.
#define BASE_FREQ (160000000L)
i2s_clock_div_t i2s_get_clock_div(int32_t freq)
{
i2s_clock_div_t div = {0, 0};
int32_t best_freq = 0;
for (uint32_t bclk_div = 1; bclk_div < 64; bclk_div++) {
for (uint32_t clkm_div = 1; clkm_div < 64; clkm_div++) {
int32_t curr_freq = BASE_FREQ / (bclk_div * clkm_div);
if (abs(freq - curr_freq) < abs(freq - best_freq)) {
best_freq = curr_freq;
div.clkm_div = clkm_div;
div.bclk_div = bclk_div;
}
}
}
debug("Requested frequency: %d, set frequency: %d\n", freq, best_freq);
debug("clkm_div: %d, bclk_div: %d\n", div.clkm_div, div.bclk_div);
return div;
}
void i2s_dma_start(dma_descriptor_t *descr)
{
// configure DMA descriptor
SLC.RX_LINK = SET_FIELD(SLC.RX_LINK, SLC_RX_LINK_DESCRIPTOR_ADDR, 0);
SLC.RX_LINK = SET_FIELD(SLC.RX_LINK, SLC_RX_LINK_DESCRIPTOR_ADDR, (uint32_t)descr);
// enable DMA in i2s subsystem
SET_MASK_BITS(I2S.FIFO_CONF, I2S_FIFO_CONF_DESCRIPTOR_ENABLE);
//Start transmission
SET_MASK_BITS(I2S.CONF, I2S_CONF_TX_START);
}
void i2s_dma_stop()
{
SLC.RX_LINK = SET_FIELD(SLC.RX_LINK, SLC_RX_LINK_DESCRIPTOR_ADDR, 0);
CLEAR_MASK_BITS(I2S.FIFO_CONF, I2S_FIFO_CONF_DESCRIPTOR_ENABLE);
}

116
extras/i2s_dma/i2s_dma.h Normal file
View file

@ -0,0 +1,116 @@
/**
* 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 __I2S_DMA_H__
#define __I2S_DMA_H__
#include <stdint.h>
#include <stdbool.h>
#include "esp/slc_regs.h"
typedef void (*i2s_dma_isr_t)(void);
typedef struct dma_descriptor {
uint32_t blocksize:12;
uint32_t datalen:12;
uint32_t unused:5;
uint32_t sub_sof:1;
uint32_t eof:1;
uint32_t owner:1;
void* buf_ptr;
struct dma_descriptor *next_link_ptr;
} dma_descriptor_t;
typedef struct {
uint8_t bclk_div;
uint8_t clkm_div;
} i2s_clock_div_t;
typedef struct {
bool data;
bool clock;
bool ws;
} i2s_pins_t;
/**
* Initialize I2S and DMA subsystems.
*
* @param isr ISR handler. Can be NULL if interrupt handling is not needed.
* @param clock_div I2S clock configuration.
* @param pins I2S pin configuration. Specifies which pins are enabled in I2S.
*/
void i2s_dma_init(i2s_dma_isr_t isr, i2s_clock_div_t clock_div, i2s_pins_t pins);
/**
* Calculate I2S dividers for the specified frequency.
*
* I2S_FREQ = 160000000 / (bclk_div * clkm_div)
* Base frequency is independent from the CPU frequency.
*/
i2s_clock_div_t i2s_get_clock_div(int32_t freq);
/**
* Start I2S transmittion.
*
* @param descr Pointer to the first descriptor in the linked list of descriptors.
*/
void i2s_dma_start(dma_descriptor_t *descr);
/**
* Stop I2S transmittion.
*/
void i2s_dma_stop();
/**
* Clear interrupt in the I2S ISR handler.
*
* It is intended to be called from ISR.
*/
inline void i2s_dma_clear_interrupt()
{
SLC.INT_CLEAR = 0xFFFFFFFF;
}
/**
* Check if it is EOF interrupt.
*
* It is intended to be called from ISR.
*/
inline bool i2s_dma_is_eof_interrupt()
{
return (SLC.INT_STATUS & SLC_INT_STATUS_RX_EOF);
}
/**
* Get pointer to a descriptor that caused EOF interrupt.
* It is the last processed descriptor.
*
* It is intended to be called from ISR.
*/
inline dma_descriptor_t *i2s_dma_get_eof_descriptor()
{
return (dma_descriptor_t*)SLC.RX_EOF_DESCRIPTOR_ADDR;
}
#endif // __I2S_DMA_H__

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__