From 27135d625282f519158289be683013bb7a4fd057 Mon Sep 17 00:00:00 2001 From: sheinz Date: Tue, 16 Aug 2016 10:10:35 +0300 Subject: [PATCH 1/3] i2s_dma: Implementation of I2S + DMA wrapper library --- core/include/common_macros.h | 8 ++ core/include/esp/iomux.h | 13 ++- extras/i2s_dma/README.md | 8 ++ extras/i2s_dma/component.mk | 9 ++ extras/i2s_dma/i2s_dma.c | 171 +++++++++++++++++++++++++++++++++++ extras/i2s_dma/i2s_dma.h | 116 ++++++++++++++++++++++++ 6 files changed, 322 insertions(+), 3 deletions(-) create mode 100644 extras/i2s_dma/README.md create mode 100644 extras/i2s_dma/component.mk create mode 100644 extras/i2s_dma/i2s_dma.c create mode 100644 extras/i2s_dma/i2s_dma.h diff --git a/core/include/common_macros.h b/core/include/common_macros.h index d4afe4f..7034787 100644 --- a/core/include/common_macros.h +++ b/core/include/common_macros.h @@ -37,6 +37,14 @@ #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)) +/* 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) instead of flash (aka irom). diff --git a/core/include/esp/iomux.h b/core/include/esp/iomux.h index 9948784..7cc0860 100644 --- a/core/include/esp/iomux.h +++ b/core/include/esp/iomux.h @@ -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)]); } -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; - 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) @@ -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 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); } diff --git a/extras/i2s_dma/README.md b/extras/i2s_dma/README.md new file mode 100644 index 0000000..4fb8901 --- /dev/null +++ b/extras/i2s_dma/README.md @@ -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. diff --git a/extras/i2s_dma/component.mk b/extras/i2s_dma/component.mk new file mode 100644 index 0000000..f04e6ee --- /dev/null +++ b/extras/i2s_dma/component.mk @@ -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)) diff --git a/extras/i2s_dma/i2s_dma.c b/extras/i2s_dma/i2s_dma.c new file mode 100644 index 0000000..6cccc72 --- /dev/null +++ b/extras/i2s_dma/i2s_dma.c @@ -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 + +// #define I2S_DMA_DEBUG + +#ifdef I2S_DMA_DEBUG +#include +#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< +#include +#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__ From 666f8212635d12be2c22bd4380eba8d154d53cfe Mon Sep 17 00:00:00 2001 From: sheinz Date: Tue, 16 Aug 2016 10:53:56 +0300 Subject: [PATCH 2/3] ws2812_i2s: WS2812 leds driver implementation using i2s_dma library --- examples/ws2812_i2s/Makefile | 6 + examples/ws2812_i2s/ws2812_i2s_colour_loop.c | 83 +++++++++ extras/ws2812_i2s/README.md | 15 ++ extras/ws2812_i2s/component.mk | 9 + extras/ws2812_i2s/ws2812_i2s.c | 181 +++++++++++++++++++ extras/ws2812_i2s/ws2812_i2s.h | 53 ++++++ 6 files changed, 347 insertions(+) create mode 100644 examples/ws2812_i2s/Makefile create mode 100644 examples/ws2812_i2s/ws2812_i2s_colour_loop.c create mode 100644 extras/ws2812_i2s/README.md create mode 100644 extras/ws2812_i2s/component.mk create mode 100644 extras/ws2812_i2s/ws2812_i2s.c create mode 100644 extras/ws2812_i2s/ws2812_i2s.h diff --git a/examples/ws2812_i2s/Makefile b/examples/ws2812_i2s/Makefile new file mode 100644 index 0000000..e639c57 --- /dev/null +++ b/examples/ws2812_i2s/Makefile @@ -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 diff --git a/examples/ws2812_i2s/ws2812_i2s_colour_loop.c b/examples/ws2812_i2s/ws2812_i2s_colour_loop.c new file mode 100644 index 0000000..022f010 --- /dev/null +++ b/examples/ws2812_i2s/ws2812_i2s_colour_loop.c @@ -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 +#include +#include + +#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); +} diff --git a/extras/ws2812_i2s/README.md b/extras/ws2812_i2s/README.md new file mode 100644 index 0000000..1cd6953 --- /dev/null +++ b/extras/ws2812_i2s/README.md @@ -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. + diff --git a/extras/ws2812_i2s/component.mk b/extras/ws2812_i2s/component.mk new file mode 100644 index 0000000..08b4449 --- /dev/null +++ b/extras/ws2812_i2s/component.mk @@ -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)) diff --git a/extras/ws2812_i2s/ws2812_i2s.c b/extras/ws2812_i2s/ws2812_i2s.c new file mode 100644 index 0000000..310869b --- /dev/null +++ b/extras/ws2812_i2s/ws2812_i2s.c @@ -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 +#include + +// #define WS2812_I2S_DEBUG + +#ifdef WS2812_I2S_DEBUG +#include +#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); +} diff --git a/extras/ws2812_i2s/ws2812_i2s.h b/extras/ws2812_i2s/ws2812_i2s.h new file mode 100644 index 0000000..fc12e26 --- /dev/null +++ b/extras/ws2812_i2s/ws2812_i2s.h @@ -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 +#include + +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__ From e96dc5c72225ec5592444086bb76af366dbe58f5 Mon Sep 17 00:00:00 2001 From: sheinz Date: Tue, 16 Aug 2016 11:09:18 +0300 Subject: [PATCH 3/3] is2_audio: Example of using i2s_dma library to output audio --- examples/i2s_audio/Makefile | 11 ++ examples/i2s_audio/i2s_audio_example.c | 197 +++++++++++++++++++++++++ 2 files changed, 208 insertions(+) create mode 100644 examples/i2s_audio/Makefile create mode 100644 examples/i2s_audio/i2s_audio_example.c diff --git a/examples/i2s_audio/Makefile b/examples/i2s_audio/Makefile new file mode 100644 index 0000000..d732ffa --- /dev/null +++ b/examples/i2s_audio/Makefile @@ -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)) diff --git a/examples/i2s_audio/i2s_audio_example.c b/examples/i2s_audio/i2s_audio_example.c new file mode 100644 index 0000000..78df426 --- /dev/null +++ b/examples/i2s_audio/i2s_audio_example.c @@ -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 +#include + +#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); +}