From e96dc5c72225ec5592444086bb76af366dbe58f5 Mon Sep 17 00:00:00 2001 From: sheinz Date: Tue, 16 Aug 2016 11:09:18 +0300 Subject: [PATCH] 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); +}