Skip to content

Commit

Permalink
I2S driver fixes for IRQs, protocol, factoring (#4574)
Browse files Browse the repository at this point in the history
* I2S driver fixes for IRQs, protocol, factoring

All redundant ICACHE_FLASH_ATTR decorators were removed, we already do
this by default for all routines, anyway,

The actual ISR and its called function moved to to IRAM.  Used to be in flash
due to the decorator, which could lead to crashes.  Use ets_memset to mute
buffers in ISR.

Fix the I2S on-the-wire protocol by enabling the transmit delay I2STMS because
I2S is supposed to send the MSB one clock after LRCLK toggles.  This was
causing I2S to be twice as loud as intended in the best of cases, and causing
garbage/noise output when the MSB was set since data was effectively shifted.

Refactor the clock divider setting to be done in one function only, as there
is no reason to do the same complicated bit setting in two spots.

* Comment some add'l registers, use optimstic_yield

Comment the known and unknown I2S register settings for posterity, using
the ESP32 guide as a basis.

Use optimistic_yield() instead of esp_wdt_disable/enable when busy
waiting in blocking writes to ensure we don't hog the CPU completely.

Move the constant IO pins to #defines for easier understanding.
  • Loading branch information
earlephilhower authored and devyte committed Mar 27, 2018
1 parent a3a3654 commit decfbdd
Showing 1 changed file with 49 additions and 38 deletions.
87 changes: 49 additions & 38 deletions cores/esp8266/core_esp8266_i2s.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,12 @@
#include "i2s_reg.h"
#include "i2s.h"

extern void ets_wdt_enable(void);
extern void ets_wdt_disable(void);
// IOs used for I2S. Not defined in i2s.h, unfortunately.
// Note these are internal IOs numbers and not pins on an
// Arduino board. Users need to verify their particular wiring.
#define I2SO_WS 2
#define I2SO_DATA 3
#define I2SO_BCK 15

#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.
Expand Down Expand Up @@ -56,21 +60,21 @@ static uint32_t *i2s_slc_buf_pntr[SLC_BUF_CNT]; //Pointer to the I2S DMA buffer
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 int i2s_curr_slc_buf_pos=0; //position in the current buffer
static void (*i2s_callback) (void)=0; //Callback function should be defined as 'void ICACHE_FLASH_ATTR function_name()', placing the function in IRAM for faster execution. Avoid long computational tasks in this function, use it to set flags and process later.
static void (*i2s_callback) (void)=0; //Callback function should be defined as 'void ICACHE_RAM_ATTR function_name()', placing the function in IRAM for faster execution. Avoid long computational tasks in this function, use it to set flags and process later.

bool ICACHE_FLASH_ATTR i2s_is_full(){
bool i2s_is_full(){
return (i2s_curr_slc_buf_pos==SLC_BUF_LEN || i2s_curr_slc_buf==NULL) && (i2s_slc_queue_len == 0);
}

bool ICACHE_FLASH_ATTR i2s_is_empty(){
bool i2s_is_empty(){
return (i2s_slc_queue_len >= SLC_BUF_CNT-1);
}

int16_t ICACHE_FLASH_ATTR i2s_available(){
return (SLC_BUF_CNT - i2s_slc_queue_len) * SLC_BUF_LEN;
int16_t i2s_available(){
return (SLC_BUF_CNT - i2s_slc_queue_len) * SLC_BUF_LEN;
}

uint32_t ICACHE_FLASH_ATTR i2s_slc_queue_next_item(){ //pop the top off the queue
uint32_t ICACHE_RAM_ATTR i2s_slc_queue_next_item(){ //pop the top off the queue
uint8_t i;
uint32_t item = i2s_slc_queue[0];
i2s_slc_queue_len--;
Expand All @@ -82,13 +86,13 @@ uint32_t ICACHE_FLASH_ATTR i2s_slc_queue_next_item(){ //pop the top off the queu
//This routine is called as soon as the DMA routine has something to tell us. All we
//handle here is the RX_EOF_INT status, which indicate the DMA has sent a buffer whose
//descriptor has the 'EOF' field set to 1.
void ICACHE_FLASH_ATTR i2s_slc_isr(void) {
void ICACHE_RAM_ATTR i2s_slc_isr(void) {
uint32_t slc_intr_status = SLCIS;
SLCIC = 0xFFFFFFFF;
if (slc_intr_status & SLCIRXEOF) {
ETS_SLC_INTR_DISABLE();
struct slc_queue_item *finished_item = (struct slc_queue_item*)SLCRXEDA;
memset((void *)finished_item->buf_ptr, 0x00, SLC_BUF_LEN * 4);//zero the buffer so it is mute in case of underflow
ets_memset((void *)finished_item->buf_ptr, 0x00, SLC_BUF_LEN * 4);//zero the buffer so it is mute in case of underflow
if (i2s_slc_queue_len >= SLC_BUF_CNT-1) { //All buffers are empty. This means we have an underflow
i2s_slc_queue_next_item(); //free space for finished_item
}
Expand All @@ -102,7 +106,7 @@ void i2s_set_callback(void (*callback) (void)){
i2s_callback = callback;
}

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

Expand Down Expand Up @@ -150,7 +154,7 @@ void ICACHE_FLASH_ATTR i2s_slc_begin(){
SLCRXL |= SLCRXLS;
}

void ICACHE_FLASH_ATTR i2s_slc_end(){
void i2s_slc_end(){
ETS_SLC_INTR_DISABLE();
SLCIC = 0xFFFFFFFF;
SLCIE = 0;
Expand All @@ -166,15 +170,14 @@ void ICACHE_FLASH_ATTR i2s_slc_end(){
//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.

bool ICACHE_FLASH_ATTR i2s_write_sample(uint32_t sample) {
bool i2s_write_sample(uint32_t sample) {
if (i2s_curr_slc_buf_pos==SLC_BUF_LEN || i2s_curr_slc_buf==NULL) {
if(i2s_slc_queue_len == 0){
while(1){
if(i2s_slc_queue_len > 0){
break;
} else {
ets_wdt_disable();
ets_wdt_enable();
optimistic_yield(10000);
}
}
}
Expand All @@ -187,7 +190,7 @@ bool ICACHE_FLASH_ATTR i2s_write_sample(uint32_t sample) {
return true;
}

bool ICACHE_FLASH_ATTR i2s_write_sample_nb(uint32_t sample) {
bool i2s_write_sample_nb(uint32_t sample) {
if (i2s_curr_slc_buf_pos==SLC_BUF_LEN || i2s_curr_slc_buf==NULL) {
if(i2s_slc_queue_len == 0){
return false;
Expand All @@ -201,7 +204,7 @@ bool ICACHE_FLASH_ATTR i2s_write_sample_nb(uint32_t sample) {
return true;
}

bool ICACHE_FLASH_ATTR i2s_write_lr(int16_t left, int16_t right){
bool i2s_write_lr(int16_t left, int16_t right){
int sample = right & 0xFFFF;
sample = sample << 16;
sample |= left & 0xFFFF;
Expand All @@ -215,7 +218,7 @@ bool ICACHE_FLASH_ATTR i2s_write_lr(int16_t left, int16_t right){

static uint32_t _i2s_sample_rate;

void ICACHE_FLASH_ATTR i2s_set_rate(uint32_t rate){ //Rate in HZ
void i2s_set_rate(uint32_t rate){ //Rate in HZ
if(rate == _i2s_sample_rate) return;
_i2s_sample_rate = rate;

Expand All @@ -235,60 +238,68 @@ void ICACHE_FLASH_ATTR i2s_set_rate(uint32_t rate){ //Rate in HZ
}
}

//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 | ((sbd_div_best) << I2SBD) | ((scd_div_best) << I2SCD);
i2s_set_dividers( sbd_div_best, scd_div_best );
}

void ICACHE_FLASH_ATTR i2s_set_dividers(uint8_t div1, uint8_t div2){
void i2s_set_dividers(uint8_t div1, uint8_t div2) {
// Ensure dividers fit in bit fields
div1 &= I2SBDM;
div2 &= I2SCDM;

// !trans master(?), !bits mod(==16 bits/chanel), clear clock dividers
I2SC &= ~(I2STSM | (I2SBMM << I2SBM) | (I2SBDM << I2SBD) | (I2SCDM << I2SCD));
I2SC |= I2SRF | I2SMR | I2SRSM | I2SRMS | (div1 << I2SBD) | (div2 << I2SCD);

// I2SRF = Send/recv right channel first (? may be swapped form I2S spec of WS=0 => left)
// I2SMR = MSB recv/xmit first
// I2SRSM = Receive slave mode (?)
// I2SRMS, I2STMS = 1-bit delay from WS to MSB (I2S format)
// div1, div2 = Set I2S WS clock frequency. BCLK seems to be generated from 32x this
I2SC |= I2SRF | I2SMR | I2SRSM | I2SRMS | I2STMS | (div1 << I2SBD) | (div2 << I2SCD);
}

float ICACHE_FLASH_ATTR i2s_get_real_rate(){
float i2s_get_real_rate(){
return (float)I2SBASEFREQ/32/((I2SC>>I2SBD) & I2SBDM)/((I2SC >> I2SCD) & I2SCDM);
}

void ICACHE_FLASH_ATTR i2s_begin(){
void 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)

// Redirect control of IOs to the I2S block
pinMode(I2SO_WS, FUNCTION_1);
pinMode(I2SO_DATA, FUNCTION_1);
pinMode(I2SO_BCK, FUNCTION_1);

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

//Reset I2S
// 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)

// I2STXFMM, I2SRXFMM=0 => 16-bit, dual channel data shifted in/out
I2SFC &= ~(I2SDE | (I2STXFMM << I2STXFM) | (I2SRXFMM << I2SRXFM)); //Set RX/TX FIFO_MOD=0 (16-bit) and disable DMA (FIFO only)
I2SFC |= I2SDE; //Enable DMA
// I2STXCMM, I2SRXCMM=0 => Dual channel mode
I2SCC &= ~((I2STXCMM << I2STXCM) | (I2SRXCMM << I2SRXCM)); //Set RX/TX CHAN_MOD=0
i2s_set_rate(44100);
I2SC |= I2STXS; //Start transmission
}

void ICACHE_FLASH_ATTR i2s_end(){
void i2s_end(){
I2SC &= ~I2STXS;

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

pinMode(2, INPUT);
pinMode(3, INPUT);
pinMode(15, INPUT);
// Redirect IOs to user control/GPIO
pinMode(I2SO_WS, INPUT);
pinMode(I2SO_DATA, INPUT);
pinMode(I2SO_BCK, INPUT);

i2s_slc_end();
}

0 comments on commit decfbdd

Please sign in to comment.