Skip to content

I2s experimental #2501

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
186 changes: 135 additions & 51 deletions cores/esp8266/core_esp8266_i2s.c
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
/*
/*
i2s.c - Software I2S library for esp8266

Code taken and reworked from espessif's I2S example

Copyright (c) 2015 Hristo Gochkov. All rights reserved.
This file is part of the esp8266 core for Arduino environment.

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
Expand All @@ -30,31 +30,31 @@
extern void ets_wdt_enable(void);
extern void ets_wdt_disable(void);

#define SLC_BUF_CNT (8) //Number of buffers in the I2S circular buffer
#define SLC_BUF_LEN (64) //Length of one buffer, in 32-bit words.

//We use a queue to keep track of the DMA buffers that are empty. The ISR will push buffers to the back of the queue,
//the mp3 decode will pull them from the front and fill them. For ease, the queue will contain *pointers* to the DMA
//buffers, not the data itself. 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.

struct slc_queue_item {
uint32 blocksize:12;
uint32 datalen:12;
uint32 unused:5;
uint32 sub_sof:1;
uint32 eof:1;
uint32 owner:1;
uint32 buf_ptr;
uint32 next_link_ptr;
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;
uint32_t * buf_ptr;
struct slc_queue_item * next_link_ptr;
};

static uint32_t i2s_slc_queue[SLC_BUF_CNT-1];
static size_t SLC_BUF_CNT = 0; //Number of buffers in the I2S circular buffer
static size_t SLC_BUF_LEN = 0; //Length of one buffer, in 32-bit words.

static uint32_t ** i2s_slc_queue;
static uint8_t i2s_slc_queue_len;
static uint32_t *i2s_slc_buf_pntr[SLC_BUF_CNT]; //Pointer to the I2S DMA buffer data
static struct slc_queue_item i2s_slc_items[SLC_BUF_CNT]; //I2S DMA buffer descriptors
static uint32_t *i2s_curr_slc_buf=NULL;//current buffer for writing
static uint32_t * i2s_slc_buf_pntr=NULL; // Pointer to the I2S DMA buffer data
static struct slc_queue_item * i2s_slc_items; //I2S DMA buffer descriptors
static uint32_t * i2s_curr_slc_buf=NULL;//current buffer for writing
static int i2s_curr_slc_buf_pos=0; //position in the current buffer

bool ICACHE_FLASH_ATTR i2s_is_full(){
Expand All @@ -65,9 +65,9 @@ bool ICACHE_FLASH_ATTR i2s_is_empty(){
return (i2s_slc_queue_len >= SLC_BUF_CNT-1);
}

uint32_t ICACHE_FLASH_ATTR i2s_slc_queue_next_item(){ //pop the top off the queue
uint32_t * ICACHE_FLASH_ATTR i2s_slc_queue_next_item(){ //pop the top off the queue
uint8_t i;
uint32_t item = i2s_slc_queue[0];
uint32_t * item = i2s_slc_queue[0];
i2s_slc_queue_len--;
for(i=0;i<i2s_slc_queue_len;i++)
i2s_slc_queue[i] = i2s_slc_queue[i+1];
Expand All @@ -92,23 +92,42 @@ void ICACHE_FLASH_ATTR i2s_slc_isr(void) {
}
}

void ICACHE_FLASH_ATTR i2s_init(size_t count, size_t length){
SLC_BUF_CNT = count;
SLC_BUF_LEN = length;

const int nsamples = count * length;
i2s_slc_buf_pntr = (uint32_t *)malloc(nsamples * sizeof(uint32_t));
for (int i = 0; i < nsamples; ++i)
i2s_slc_buf_pntr[i] = 0;
i2s_slc_items = (struct slc_queue_item *)malloc(sizeof(*i2s_slc_items) * SLC_BUF_CNT);
i2s_slc_queue = (uint32_t **)malloc(sizeof(uint32_t *) * (SLC_BUF_CNT-1));

for (int i = 0;i < SLC_BUF_CNT;++i) {
i2s_slc_items[i].unused = 0;
i2s_slc_items[i].owner = 1;
i2s_slc_items[i].eof = 1;
i2s_slc_items[i].sub_sof = 0;
i2s_slc_items[i].datalen = SLC_BUF_LEN * 4;
i2s_slc_items[i].blocksize = SLC_BUF_LEN * 4;
i2s_slc_items[i].buf_ptr = i2s_slc_buf_pntr + i * SLC_BUF_LEN;
i2s_slc_items[i].next_link_ptr = i < (SLC_BUF_CNT - 1) ?
&i2s_slc_items[i + 1] :
&i2s_slc_items[0];
}
}

void ICACHE_FLASH_ATTR i2s_deinit(){
free(i2s_slc_buf_pntr);
i2s_slc_buf_pntr = NULL;
free(i2s_slc_items);
i2s_slc_items = NULL;
free(i2s_slc_queue);
i2s_slc_queue = NULL;
}

void ICACHE_FLASH_ATTR i2s_slc_begin(){
i2s_slc_queue_len = 0;
int x, y;

for (x=0; x<SLC_BUF_CNT; x++) {
i2s_slc_buf_pntr[x] = malloc(SLC_BUF_LEN*4);
for (y=0; y<SLC_BUF_LEN; y++) i2s_slc_buf_pntr[x][y] = 0;

i2s_slc_items[x].unused = 0;
i2s_slc_items[x].owner = 1;
i2s_slc_items[x].eof = 1;
i2s_slc_items[x].sub_sof = 0;
i2s_slc_items[x].datalen = SLC_BUF_LEN*4;
i2s_slc_items[x].blocksize = SLC_BUF_LEN*4;
i2s_slc_items[x].buf_ptr = (uint32_t)&i2s_slc_buf_pntr[x][0];
i2s_slc_items[x].next_link_ptr = (int)((x<(SLC_BUF_CNT-1))?(&i2s_slc_items[x+1]):(&i2s_slc_items[0]));
}

ETS_SLC_INTR_DISABLE();
SLCC0 |= SLCRXLR | SLCTXLR;
Expand Down Expand Up @@ -148,7 +167,24 @@ void ICACHE_FLASH_ATTR i2s_slc_end(){
SLCRXL &= ~(SLCRXLAM << SLCRXLA); // clear RX descriptor address
}

//This routine pushes a single, 32-bit sample to the I2S buffers. Call this at (on average)
uint32_t * i2s_get_buffer() {
if (i2s_curr_slc_buf_pos==SLC_BUF_LEN || i2s_curr_slc_buf==NULL) {
if(i2s_slc_queue_len == 0){
return NULL;
}
ETS_SLC_INTR_DISABLE();
i2s_curr_slc_buf = (uint32_t *)i2s_slc_queue_next_item();
ETS_SLC_INTR_ENABLE();
i2s_curr_slc_buf_pos=0;
}
return i2s_curr_slc_buf;
}

void i2s_put_buffer() {
i2s_curr_slc_buf_pos=SLC_BUF_LEN;
}

//This routine pushes a single, 32-bit sample to the I2S buffers. Call this at (on average)
//at least the current sample rate. You can also call it quicker: it will suspend the calling
//thread if the buffer is full and resume when there's room again.

Expand Down Expand Up @@ -188,52 +224,100 @@ bool ICACHE_FLASH_ATTR i2s_write_sample_nb(uint32_t sample) {
}

bool ICACHE_FLASH_ATTR i2s_write_lr(int16_t left, int16_t right){
int sample = right & 0xFFFF;
uint32_t sample = right & 0xFFFF;
sample = sample << 16;
sample |= left & 0xFFFF;
return i2s_write_sample(sample);
}

bool ICACHE_FLASH_ATTR i2s_write_lr_nb(int16_t left, int16_t right){
uint32_t sample = right & 0xFFFF;
sample = sample << 16;
sample |= left & 0xFFFF;
return i2s_write_sample_nb(sample);
}

// END DMA
// =========
// START I2S


static uint32_t _i2s_sample_rate;
static uint32_t _i2s_sample_rate = 0;

void ICACHE_FLASH_ATTR i2s_set_rate(uint32_t rate){ //Rate in HZ
if(rate == _i2s_sample_rate) return;
_i2s_sample_rate = rate;
uint32_t i2s_clock_div = (I2SBASEFREQ/(_i2s_sample_rate*32)) & I2SCDM;
uint8_t i2s_bck_div = (I2SBASEFREQ/(_i2s_sample_rate*i2s_clock_div*2)) & I2SBDM;
//os_printf("Rate %u Div %u Bck %u Frq %u\n", _i2s_sample_rate, i2s_clock_div, i2s_bck_div, I2SBASEFREQ/(i2s_clock_div*i2s_bck_div*2));

//!trans master, !bits mod, rece slave mod, rece msb shift, right first, msb right
I2SC &= ~(I2STSM | (I2SBMM << I2SBM) | (I2SBDM << I2SBD) | (I2SCDM << I2SCD));
I2SC |= I2SRF | I2SMR | I2SRSM | I2SRMS | ((i2s_bck_div-1) << I2SBD) | ((i2s_clock_div-1) << I2SCD);
// Find closest divider
uint32_t basefreq = 160000000L;
int bestfreq = 0;
uint32_t i2s_clkm_div, i2s_bck_div;

// CLK_I2S = CPU_FREQ / I2S_CLKM_DIV_NUM
// BCLK = CLK_I2S / I2S_BCK_DIV_NUM
// WS = BCLK/ 2 / (16 + I2S_BITS_MOD)
// Note that I2S_CLKM_DIV_NUM must be >5 for I2S data
// I2S_CLKM_DIV_NUM - 5 - 63
// I2S_BCK_DIV_NUM - 2 - 63
for (int bckdiv = 2; bckdiv < 64; bckdiv++) {
for (int clkmdiv = 5; clkmdiv < 64; clkmdiv++) {
uint32_t testfreq = basefreq / (bckdiv * clkmdiv * 32);
if (abs(_i2s_sample_rate - testfreq) < abs(_i2s_sample_rate - bestfreq)) {
bestfreq = testfreq;
i2s_clkm_div = clkmdiv;
i2s_bck_div = bckdiv;
}
}
}

// Apply the sample rate
// ~I2S_TRANS_SLAVE_MOD (TX master mode)
// ~I2S_BITS_MOD
// I2S_RIGHT_FIRST
// I2S_MSB_RIGHT
// I2S_RECE_SLAVE_MOD (RX slave mode)
// I2S_RECE_MSB_SHIFT (??)
// I2S_TRANS_MSB_SHIFT (??)

// !trans master, !bits mod,
// rece slave mod, rece msb shift, right first, msb right
I2SC &= ~( I2STSM | // TX master mode
I2STMS | // TX LSB first
(I2SBMM << I2SBM) | // clear bits mode
(I2SBDM << I2SBD) | // clear bck_div
(I2SCDM << I2SCD)); // clear clkm_div
I2SC |= I2SRF | // right first
I2SMR | // MSB first
I2SRSM | // RX slave mode
I2SRMS | // receive MSB shift
// bits_mode == 0 (16bit)
((i2s_bck_div & I2SBDM) << I2SBD) | // set bck_div
((i2s_clkm_div & I2SCDM) << I2SCD); // set clkm_div
}

void ICACHE_FLASH_ATTR i2s_begin(){
_i2s_sample_rate = 0;
i2s_slc_begin();

pinMode(2, FUNCTION_1); //I2SO_WS (LRCK)
pinMode(3, FUNCTION_1); //I2SO_DATA (SDIN)
pinMode(15, FUNCTION_1); //I2SO_BCK (SCLK)

I2S_CLK_ENABLE();
I2SIC = 0x3F;
I2SIE = 0;

//Reset I2S
I2SC &= ~(I2SRST);
I2SC |= I2SRST;
I2SC &= ~(I2SRST);

I2SFC &= ~(I2SDE | (I2STXFMM << I2STXFM) | (I2SRXFMM << I2SRXFM)); //Set RX/TX FIFO_MOD=0 and disable DMA (FIFO only)
I2SFC |= I2SDE; //Enable DMA
I2SCC &= ~((I2STXCMM << I2STXCM) | (I2SRXCMM << I2SRXCM)); //Set RX/TX CHAN_MOD=0
i2s_set_rate(44100);

// defaults to 44100 if unset
i2s_set_rate(_i2s_sample_rate == 0 ? 44100 : _i2s_sample_rate);

I2SC |= I2STXS; //Start transmission
}

Expand Down
11 changes: 8 additions & 3 deletions cores/esp8266/i2s.h
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
/*
/*
i2s.h - Software I2S library for esp8266

Copyright (c) 2015 Hristo Gochkov. All rights reserved.
This file is part of the esp8266 core for Arduino environment.

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
Expand Down Expand Up @@ -40,17 +40,22 @@ speed.
extern "C" {
#endif

void i2s_init(uint32_t count, uint32_t length); // allocate `count` of `length` buffers. total `count` * `length` * `sizeof(uint32_t)` bytes.
void i2s_deinit(); // free the allocated buffers
void i2s_begin();
void i2s_end();
void i2s_set_rate(uint32_t rate);//Sample Rate in Hz (ex 44100, 48000)
bool i2s_write_sample(uint32_t sample);//32bit sample with channels being upper and lower 16 bits (blocking when DMA is full)
bool i2s_write_sample_nb(uint32_t sample);//same as above but does not block when DMA is full and returns false instead
bool i2s_write_lr(int16_t left, int16_t right);//combines both channels and calls i2s_write_sample with the result
bool i2s_write_lr_nb(int16_t left, int16_t right);//same as above but does not block when DMA is full and returns false instead
uint32_t * i2s_get_buffer(); // buffer available for writing, nullptr if not applicatable.
void i2s_put_buffer(); // yup you guessed it.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did not guess it ;)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry should provide better documentations...

the basic idea is call i2s_get_buffer(), and after you're done with the buffer, call i2s_put_buffer() to return the buffer to the subsystem.
maybe this should be called i2s_commit_buffer() instead.

current i2s has 8 of 64x4 bytes buffer, that's an undocumented secret to the user.
i'll improve the buffer initialization routines maybe lator.

my idea is to modify i2s_init(size_t count, size_t size, size_t channels, size_t sizeof_sample) to malloc() count of size * channels * sizeof_sample bytes buffers.

bool i2s_is_full();//returns true if DMA is full and can not take more bytes (overflow)
bool i2s_is_empty();//returns true if DMA is empty (underflow)

#ifdef __cplusplus
}
#endif

#endif
#endif