From 27135d625282f519158289be683013bb7a4fd057 Mon Sep 17 00:00:00 2001 From: sheinz Date: Tue, 16 Aug 2016 10:10:35 +0300 Subject: [PATCH] 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__