Merge pull request #189 from sheinz/feature/i2s_dma
i2s_dma wrapper, ws2812_i2s driver and examples.
This commit is contained in:
commit
84ee8d493c
14 changed files with 877 additions and 3 deletions
|
@ -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).
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
11
examples/i2s_audio/Makefile
Normal file
11
examples/i2s_audio/Makefile
Normal 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))
|
197
examples/i2s_audio/i2s_audio_example.c
Normal file
197
examples/i2s_audio/i2s_audio_example.c
Normal 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);
|
||||
}
|
6
examples/ws2812_i2s/Makefile
Normal file
6
examples/ws2812_i2s/Makefile
Normal 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
|
83
examples/ws2812_i2s/ws2812_i2s_colour_loop.c
Normal file
83
examples/ws2812_i2s/ws2812_i2s_colour_loop.c
Normal 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
8
extras/i2s_dma/README.md
Normal 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.
|
9
extras/i2s_dma/component.mk
Normal file
9
extras/i2s_dma/component.mk
Normal 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
171
extras/i2s_dma/i2s_dma.c
Normal 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
116
extras/i2s_dma/i2s_dma.h
Normal 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__
|
15
extras/ws2812_i2s/README.md
Normal file
15
extras/ws2812_i2s/README.md
Normal 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.
|
||||
|
9
extras/ws2812_i2s/component.mk
Normal file
9
extras/ws2812_i2s/component.mk
Normal 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))
|
181
extras/ws2812_i2s/ws2812_i2s.c
Normal file
181
extras/ws2812_i2s/ws2812_i2s.c
Normal 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);
|
||||
}
|
53
extras/ws2812_i2s/ws2812_i2s.h
Normal file
53
extras/ws2812_i2s/ws2812_i2s.h
Normal 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__
|
Loading…
Reference in a new issue