1
0
mirror of https://github.com/esp8266/Arduino.git synced 2025-04-21 10:26:06 +03:00

I2s input API and examples (#4539)

Enables I2S stereo input via DMA using new API calls:

. i2s_rxtx_begin(bool rx, rool tx);
. i2s_read_sample(uint32_t *l, uint32_t *r);

Original API calls will only enable TX, so this is backwards compatible.

Add simple I2S input example code using Arduino serial plotter.

Add UDP transmit of I2S microphone data to a PC (remote microphone).

Clean up and reorganize code to share RX and TX logic as much as
possible.  Fix a potential WDT error while in blocking sample read
and write.
This commit is contained in:
Earle F. Philhower, III 2018-04-02 07:37:21 -07:00 committed by GitHub
parent 7ae8f98e57
commit 8ae553d99e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 479 additions and 141 deletions

View File

@ -24,104 +24,205 @@
#include "osapi.h" #include "osapi.h"
#include "ets_sys.h" #include "ets_sys.h"
#include "i2s_reg.h" #include "i2s_reg.h"
#include "i2s.h" #include "i2s.h"
#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 I2S transmitter 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.
// For RX, it's a little different. The buffers in i2s_slc_queue are
// placed onto the list when they're filled by DMA
typedef struct slc_queue_item {
uint32_t blocksize : 12;
uint32_t datalen : 12;
uint32_t unused : 5;
uint32_t sub_sof : 1;
uint32_t eof : 1;
volatile uint32_t owner : 1; // DMA can change this value
uint32_t * buf_ptr;
struct slc_queue_item * next_link_ptr;
} slc_queue_item_t;
typedef struct i2s_state {
uint32_t * slc_queue[SLC_BUF_CNT];
volatile uint8_t slc_queue_len;
uint32_t * slc_buf_pntr[SLC_BUF_CNT]; // Pointer to the I2S DMA buffer data
slc_queue_item_t slc_items[SLC_BUF_CNT]; // I2S DMA buffer descriptors
uint32_t * curr_slc_buf; // Current buffer for writing
uint32_t curr_slc_buf_pos; // Position in the current buffer
void (*callback) (void);
// Callback function should be defined as 'void ICACHE_RAM_ATTR function_name()',
// and be placed in IRAM for faster execution. Avoid long computational tasks in this
// function, use it to set flags and process later.
} i2s_state_t;
// RX = I2S receive (i.e. microphone), TX = I2S transmit (i.e. DAC)
static i2s_state_t *rx = NULL;
static i2s_state_t *tx = NULL;
// Last I2S sample rate requested
static uint32_t _i2s_sample_rate;
// IOs used for I2S. Not defined in i2s.h, unfortunately. // IOs used for I2S. Not defined in i2s.h, unfortunately.
// Note these are internal IOs numbers and not pins on an // Note these are internal GPIO numbers and not pins on an
// Arduino board. Users need to verify their particular wiring. // Arduino board. Users need to verify their particular wiring.
#define I2SO_WS 2
#define I2SO_DATA 3 #define I2SO_DATA 3
#define I2SO_BCK 15 #define I2SO_BCK 15
#define I2SO_WS 2
#define I2SI_DATA 12
#define I2SI_BCK 13
#define I2SI_WS 14
#define SLC_BUF_CNT (8) //Number of buffers in the I2S circular buffer static bool _i2s_is_full(const i2s_state_t *ch) {
#define SLC_BUF_LEN (64) //Length of one buffer, in 32-bit words. if (!ch) {
return false;
//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 return (ch->curr_slc_buf_pos==SLC_BUF_LEN || ch->curr_slc_buf==NULL) && (ch->slc_queue_len == 0);
//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;
};
static uint32_t i2s_slc_queue[SLC_BUF_CNT-1];
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 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_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 i2s_is_full(){
return (i2s_curr_slc_buf_pos==SLC_BUF_LEN || i2s_curr_slc_buf==NULL) && (i2s_slc_queue_len == 0);
} }
bool i2s_is_empty(){ bool i2s_is_full() {
return (i2s_slc_queue_len >= SLC_BUF_CNT-1); return _i2s_is_full( tx );
}
bool i2s_rx_is_full() {
return _i2s_is_full( rx );
}
static bool _i2s_is_empty(const i2s_state_t *ch) {
if (!ch) {
return false;
}
return (ch->slc_queue_len >= SLC_BUF_CNT-1);
}
bool i2s_is_empty() {
return _i2s_is_empty( tx );
}
bool i2s_rx_is_empty() {
return _i2s_is_empty( rx );
}
static int16_t _i2s_available(const i2s_state_t *ch) {
if (!ch) {
return 0;
}
return (SLC_BUF_CNT - ch->slc_queue_len) * SLC_BUF_LEN;
} }
int16_t i2s_available(){ int16_t i2s_available(){
return (SLC_BUF_CNT - i2s_slc_queue_len) * SLC_BUF_LEN; return _i2s_available( tx );
} }
uint32_t ICACHE_RAM_ATTR i2s_slc_queue_next_item(){ //pop the top off the queue int16_t i2s_rx_available(){
return _i2s_available( rx );
}
// Pop the top off of the queue and return it
static uint32_t * ICACHE_RAM_ATTR i2s_slc_queue_next_item(i2s_state_t *ch) {
uint8_t i; uint8_t i;
uint32_t item = i2s_slc_queue[0]; uint32_t *item = ch->slc_queue[0];
i2s_slc_queue_len--; ch->slc_queue_len--;
for(i=0;i<i2s_slc_queue_len;i++) for ( i = 0; i < ch->slc_queue_len; i++) {
i2s_slc_queue[i] = i2s_slc_queue[i+1]; ch->slc_queue[i] = ch->slc_queue[i+1];
}
return item; return item;
} }
//This routine is called as soon as the DMA routine has something to tell us. All we // Append an item to the end of the queue from receive
//handle here is the RX_EOF_INT status, which indicate the DMA has sent a buffer whose static void ICACHE_RAM_ATTR i2s_slc_queue_append_item(i2s_state_t *ch, uint32_t *item) {
//descriptor has the 'EOF' field set to 1. // Shift everything up, except for the one corresponding to this item
void ICACHE_RAM_ATTR i2s_slc_isr(void) { for (int i=0, dest=0; i < ch->slc_queue_len; i++) {
uint32_t slc_intr_status = SLCIS; if (ch->slc_queue[i] != item) {
SLCIC = 0xFFFFFFFF; ch->slc_queue[dest++] = ch->slc_queue[i];
if (slc_intr_status & SLCIRXEOF) {
ETS_SLC_INTR_DISABLE();
struct slc_queue_item *finished_item = (struct slc_queue_item*)SLCRXEDA;
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
} }
i2s_slc_queue[i2s_slc_queue_len++] = finished_item->buf_ptr; }
if (i2s_callback) i2s_callback(); if (ch->slc_queue_len < SLC_BUF_CNT - 1) {
ETS_SLC_INTR_ENABLE(); ch->slc_queue[ch->slc_queue_len++] = item;
} else {
ch->slc_queue[ch->slc_queue_len] = item;
} }
} }
void i2s_set_callback(void (*callback) (void)){ static void ICACHE_RAM_ATTR i2s_slc_isr(void) {
i2s_callback = callback; ETS_SLC_INTR_DISABLE();
uint32_t slc_intr_status = SLCIS;
SLCIC = 0xFFFFFFFF;
if (slc_intr_status & SLCIRXEOF) {
slc_queue_item_t *finished_item = (slc_queue_item_t *)SLCRXEDA;
// Zero the buffer so it is mute in case of underflow
ets_memset((void *)finished_item->buf_ptr, 0x00, SLC_BUF_LEN * 4);
if (tx->slc_queue_len >= SLC_BUF_CNT-1) {
// All buffers are empty. This means we have an underflow
i2s_slc_queue_next_item(tx); // Free space for finished_item
}
tx->slc_queue[tx->slc_queue_len++] = finished_item->buf_ptr;
if (tx->callback) {
tx->callback();
}
}
if (slc_intr_status & SLCITXEOF) {
slc_queue_item_t *finished_item = (slc_queue_item_t *)SLCTXEDA;
// Set owner back to 1 (SW) or else RX stops. TX has no such restriction.
finished_item->owner = 1;
i2s_slc_queue_append_item(rx, finished_item->buf_ptr);
if (rx->callback) {
rx->callback();
}
}
ETS_SLC_INTR_ENABLE();
} }
void i2s_slc_begin(){ void i2s_set_callback(void (*callback) (void)) {
i2s_slc_queue_len = 0; tx->callback = callback;
int x, y; }
for (x=0; x<SLC_BUF_CNT; x++) { void i2s_rx_set_callback(void (*callback) (void)) {
i2s_slc_buf_pntr[x] = malloc(SLC_BUF_LEN*4); rx->callback = callback;
for (y=0; y<SLC_BUF_LEN; y++) i2s_slc_buf_pntr[x][y] = 0; }
i2s_slc_items[x].unused = 0; static bool _alloc_channel(i2s_state_t *ch) {
i2s_slc_items[x].owner = 1; ch->slc_queue_len = 0;
i2s_slc_items[x].eof = 1; for (int x=0; x<SLC_BUF_CNT; x++) {
i2s_slc_items[x].sub_sof = 0; ch->slc_buf_pntr[x] = (uint32_t *)malloc(SLC_BUF_LEN * sizeof(ch->slc_buf_pntr[0][0]));
i2s_slc_items[x].datalen = SLC_BUF_LEN*4; if (!ch->slc_buf_pntr[x]) {
i2s_slc_items[x].blocksize = SLC_BUF_LEN*4; // OOM, the upper layer will free up any partially allocated channels.
i2s_slc_items[x].buf_ptr = (uint32_t)&i2s_slc_buf_pntr[x][0]; return false;
i2s_slc_items[x].next_link_ptr = (int)((x<(SLC_BUF_CNT-1))?(&i2s_slc_items[x+1]):(&i2s_slc_items[0])); }
memset(ch->slc_buf_pntr[x], 0, SLC_BUF_LEN * sizeof(ch->slc_buf_pntr[x][0]));
ch->slc_items[x].unused = 0;
ch->slc_items[x].owner = 1;
ch->slc_items[x].eof = 1;
ch->slc_items[x].sub_sof = 0;
ch->slc_items[x].datalen = SLC_BUF_LEN * 4;
ch->slc_items[x].blocksize = SLC_BUF_LEN * 4;
ch->slc_items[x].buf_ptr = (uint32_t*)&ch->slc_buf_pntr[x][0];
ch->slc_items[x].next_link_ptr = (x<(SLC_BUF_CNT-1))?(&ch->slc_items[x+1]):(&ch->slc_items[0]);
}
return true;
}
static bool i2s_slc_begin() {
if (tx) {
if (!_alloc_channel(tx)) {
return false;
}
}
if (rx) {
if (!_alloc_channel(rx)) {
return false;
}
} }
ETS_SLC_INTR_DISABLE(); ETS_SLC_INTR_DISABLE();
@ -129,32 +230,44 @@ void i2s_slc_begin(){
SLCC0 &= ~(SLCRXLR | SLCTXLR); SLCC0 &= ~(SLCRXLR | SLCTXLR);
SLCIC = 0xFFFFFFFF; SLCIC = 0xFFFFFFFF;
//Configure DMA // Configure DMA
SLCC0 &= ~(SLCMM << SLCM); //clear DMA MODE SLCC0 &= ~(SLCMM << SLCM); // Clear DMA MODE
SLCC0 |= (1 << SLCM); //set DMA MODE to 1 SLCC0 |= (1 << SLCM); // Set DMA MODE to 1
SLCRXDC |= SLCBINR | SLCBTNR; //enable INFOR_NO_REPLACE and TOKEN_NO_REPLACE SLCRXDC |= SLCBINR | SLCBTNR; // Enable INFOR_NO_REPLACE and TOKEN_NO_REPLACE
SLCRXDC &= ~(SLCBRXFE | SLCBRXEM | SLCBRXFM); //disable RX_FILL, RX_EOF_MODE and RX_FILL_MODE SLCRXDC &= ~(SLCBRXFE | SLCBRXEM | SLCBRXFM); // Disable RX_FILL, RX_EOF_MODE and RX_FILL_MODE
//Feed DMA the 1st buffer desc addr //Feed DMA the 1st buffer desc addr
//To send data to the I2S subsystem, counter-intuitively we use the RXLINK part, not the TXLINK as you might //To send data to the I2S subsystem, counter-intuitively we use the RXLINK part, not the TXLINK as you might
//expect. The TXLINK part still needs a valid DMA descriptor, even if it's unused: the DMA engine will throw //expect. The TXLINK part still needs a valid DMA descriptor, even if it's unused: the DMA engine will throw
//an error at us otherwise. Just feed it any random descriptor. //an error at us otherwise. Just feed it any random descriptor.
SLCTXL &= ~(SLCTXLAM << SLCTXLA); // clear TX descriptor address SLCTXL &= ~(SLCTXLAM << SLCTXLA); // clear TX descriptor address
SLCTXL |= (uint32)&i2s_slc_items[1] << SLCTXLA; //set TX descriptor address. any random desc is OK, we don't use TX but it needs to be valid
SLCRXL &= ~(SLCRXLAM << SLCRXLA); // clear RX descriptor address SLCRXL &= ~(SLCRXLAM << SLCRXLA); // clear RX descriptor address
SLCRXL |= (uint32)&i2s_slc_items[0] << SLCRXLA; //set RX descriptor address if (!rx) {
SLCTXL |= (uint32)&tx->slc_items[1] << SLCTXLA; // Set fake (unused) RX descriptor address
} else {
SLCTXL |= (uint32)&rx->slc_items[0] << SLCTXLA; // Set real RX address
}
if (!tx) {
SLCRXL |= (uint32)&rx->slc_items[1] << SLCRXLA; // Set fake (unused) TX descriptor address
} else {
SLCRXL |= (uint32)&tx->slc_items[0] << SLCRXLA; // Set real TX address
}
ETS_SLC_INTR_ATTACH(i2s_slc_isr, NULL); ETS_SLC_INTR_ATTACH(i2s_slc_isr, NULL);
SLCIE = SLCIRXEOF; //Enable only for RX EOF interrupt SLCIE = (tx?SLCIRXEOF:0) | (rx?SLCITXEOF:0); // Enable appropriate EOF IRQ
ETS_SLC_INTR_ENABLE(); ETS_SLC_INTR_ENABLE();
//Start transmission // Start transmission ("TX" DMA always needed to be enabled)
SLCTXL |= SLCTXLS; SLCTXL |= SLCTXLS;
SLCRXL |= SLCRXLS; if (tx) {
SLCRXL |= SLCRXLS;
}
return true;
} }
void i2s_slc_end(){ static void i2s_slc_end(){
ETS_SLC_INTR_DISABLE(); ETS_SLC_INTR_DISABLE();
SLCIC = 0xFFFFFFFF; SLCIC = 0xFFFFFFFF;
SLCIE = 0; SLCIE = 0;
@ -162,19 +275,32 @@ void i2s_slc_end(){
SLCRXL &= ~(SLCRXLAM << SLCRXLA); // clear RX descriptor address SLCRXL &= ~(SLCRXLAM << SLCRXLA); // clear RX descriptor address
for (int x = 0; x<SLC_BUF_CNT; x++) { for (int x = 0; x<SLC_BUF_CNT; x++) {
free(i2s_slc_buf_pntr[x]); if (tx) {
free(tx->slc_buf_pntr[x]);
tx->slc_buf_pntr[x] = NULL;
}
if (rx) {
free(rx->slc_buf_pntr[x]);
rx->slc_buf_pntr[x] = NULL;
}
} }
} }
//This routine pushes a single, 32-bit sample to the I2S buffers. Call this at (on average) // These routines push a single, 32-bit sample to the I2S buffers. Call at (on average)
//at least the current sample rate. You can also call it quicker: it will suspend the calling // at least the current sample rate.
//thread if the buffer is full and resume when there's room again. static bool _i2s_write_sample(uint32_t sample, bool nb) {
if (!tx) {
return false;
}
bool i2s_write_sample(uint32_t sample) { if (tx->curr_slc_buf_pos==SLC_BUF_LEN || tx->curr_slc_buf==NULL) {
if (i2s_curr_slc_buf_pos==SLC_BUF_LEN || i2s_curr_slc_buf==NULL) { if (tx->slc_queue_len == 0) {
if(i2s_slc_queue_len == 0){ if (nb) {
while(1){ // Don't wait if nonblocking, just notify upper levels
if(i2s_slc_queue_len > 0){ return false;
}
while (1) {
if (tx->slc_queue_len > 0) {
break; break;
} else { } else {
optimistic_yield(10000); optimistic_yield(10000);
@ -182,26 +308,20 @@ bool i2s_write_sample(uint32_t sample) {
} }
} }
ETS_SLC_INTR_DISABLE(); ETS_SLC_INTR_DISABLE();
i2s_curr_slc_buf = (uint32_t *)i2s_slc_queue_next_item(); tx->curr_slc_buf = (uint32_t *)i2s_slc_queue_next_item(tx);
ETS_SLC_INTR_ENABLE(); ETS_SLC_INTR_ENABLE();
i2s_curr_slc_buf_pos=0; tx->curr_slc_buf_pos=0;
} }
i2s_curr_slc_buf[i2s_curr_slc_buf_pos++]=sample; tx->curr_slc_buf[tx->curr_slc_buf_pos++]=sample;
return true; return true;
} }
bool i2s_write_sample(uint32_t sample) {
return _i2s_write_sample(sample, false);
}
bool 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) { return _i2s_write_sample(sample, true);
if(i2s_slc_queue_len == 0){
return false;
}
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;
}
i2s_curr_slc_buf[i2s_curr_slc_buf_pos++]=sample;
return true;
} }
bool i2s_write_lr(int16_t left, int16_t right){ bool i2s_write_lr(int16_t left, int16_t right){
@ -211,15 +331,45 @@ bool i2s_write_lr(int16_t left, int16_t right){
return i2s_write_sample(sample); return i2s_write_sample(sample);
} }
// END DMA bool i2s_read_sample(int16_t *left, int16_t *right, bool blocking) {
// ========= if (!rx) {
// START I2S return false;
}
if (rx->curr_slc_buf_pos==SLC_BUF_LEN || rx->curr_slc_buf==NULL) {
if (rx->slc_queue_len == 0) {
if (!blocking) {
return false;
}
while (1) {
if (rx->slc_queue_len > 0){
break;
} else {
optimistic_yield(10000);
}
}
}
ETS_SLC_INTR_DISABLE();
rx->curr_slc_buf = (uint32_t *)i2s_slc_queue_next_item(rx);
ETS_SLC_INTR_ENABLE();
rx->curr_slc_buf_pos=0;
}
uint32_t sample = rx->curr_slc_buf[rx->curr_slc_buf_pos++];
if (left) {
*left = sample & 0xffff;
}
if (right) {
*right = sample >> 16;
}
return true;
}
static uint32_t _i2s_sample_rate; void i2s_set_rate(uint32_t rate) { //Rate in HZ
if (rate == _i2s_sample_rate) {
void i2s_set_rate(uint32_t rate){ //Rate in HZ return;
if(rate == _i2s_sample_rate) return; }
_i2s_sample_rate = rate; _i2s_sample_rate = rate;
uint32_t scaled_base_freq = I2SBASEFREQ/32; uint32_t scaled_base_freq = I2SBASEFREQ/32;
@ -227,8 +377,8 @@ void i2s_set_rate(uint32_t rate){ //Rate in HZ
uint8_t sbd_div_best=1; uint8_t sbd_div_best=1;
uint8_t scd_div_best=1; uint8_t scd_div_best=1;
for (uint8_t i=1; i<64; i++){ for (uint8_t i=1; i<64; i++) {
for (uint8_t j=i; j<64; j++){ for (uint8_t j=i; j<64; j++) {
float new_delta = fabs(((float)scaled_base_freq/i/j) - rate); float new_delta = fabs(((float)scaled_base_freq/i/j) - rate);
if (new_delta < delta_best){ if (new_delta < delta_best){
delta_best = new_delta; delta_best = new_delta;
@ -246,29 +396,55 @@ void i2s_set_dividers(uint8_t div1, uint8_t div2) {
div1 &= I2SBDM; div1 &= I2SBDM;
div2 &= I2SCDM; div2 &= I2SCDM;
// !trans master(?), !bits mod(==16 bits/chanel), clear clock dividers // trans master(active low), recv master(active_low), !bits mod(==16 bits/chanel), clear clock dividers
I2SC &= ~(I2STSM | (I2SBMM << I2SBM) | (I2SBDM << I2SBD) | (I2SCDM << I2SCD)); I2SC &= ~(I2STSM | I2SRSM | (I2SBMM << I2SBM) | (I2SBDM << I2SBD) | (I2SCDM << I2SCD));
// I2SRF = Send/recv right channel first (? may be swapped form I2S spec of WS=0 => left) // I2SRF = Send/recv right channel first (? may be swapped form I2S spec of WS=0 => left)
// I2SMR = MSB recv/xmit first // I2SMR = MSB recv/xmit first
// I2SRSM = Receive slave mode (?)
// I2SRMS, I2STMS = 1-bit delay from WS to MSB (I2S format) // 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 // 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); I2SC |= I2SRF | I2SMR | I2SRMS | I2STMS | (div1 << I2SBD) | (div2 << I2SCD);
} }
float i2s_get_real_rate(){ float i2s_get_real_rate(){
return (float)I2SBASEFREQ/32/((I2SC>>I2SBD) & I2SBDM)/((I2SC >> I2SCD) & I2SCDM); return (float)I2SBASEFREQ/32/((I2SC>>I2SBD) & I2SBDM)/((I2SC >> I2SCD) & I2SCDM);
} }
void i2s_begin() { bool i2s_rxtx_begin(bool enableRx, bool enableTx) {
_i2s_sample_rate = 0; if (tx || rx) {
i2s_slc_begin(); i2s_end(); // Stop and free any ongoing stuff
}
// Redirect control of IOs to the I2S block if (enableTx) {
pinMode(I2SO_WS, FUNCTION_1); tx = (i2s_state_t*)calloc(1, sizeof(*tx));
pinMode(I2SO_DATA, FUNCTION_1); if (!tx) {
pinMode(I2SO_BCK, FUNCTION_1); // Nothing to clean up yet
return false; // OOM Error!
}
pinMode(I2SO_WS, FUNCTION_1);
pinMode(I2SO_DATA, FUNCTION_1);
pinMode(I2SO_BCK, FUNCTION_1);
}
if (enableRx) {
rx = (i2s_state_t*)calloc(1, sizeof(*rx));
if (!rx) {
i2s_end(); // Clean up any TX or pin changes
return false; // OOM error!
}
pinMode(I2SI_WS, OUTPUT);
pinMode(I2SI_BCK, OUTPUT);
pinMode(I2SI_DATA, INPUT);
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTDI_U, FUNC_I2SI_DATA);
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTCK_U, FUNC_I2SI_BCK);
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTMS_U, FUNC_I2SI_WS);
}
_i2s_sample_rate = 0;
if (!i2s_slc_begin()) {
// OOM in SLC memory allocations, tear it all down and abort!
i2s_end();
return false;
}
I2S_CLK_ENABLE(); I2S_CLK_ENABLE();
I2SIC = 0x3F; I2SIC = 0x3F;
@ -280,26 +456,52 @@ void i2s_begin() {
I2SC &= ~(I2SRST); I2SC &= ~(I2SRST);
// I2STXFMM, I2SRXFMM=0 => 16-bit, dual channel data shifted in/out // 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 | (I2STXFMM << I2STXFM) | (I2SRXFMM << I2SRXFM)); // Set RX/TX FIFO_MOD=0 and disable DMA (FIFO only)
I2SFC |= I2SDE; //Enable DMA I2SFC |= I2SDE; // Enable DMA
// I2STXCMM, I2SRXCMM=0 => Dual channel mode // I2STXCMM, I2SRXCMM=0 => Dual channel mode
I2SCC &= ~((I2STXCMM << I2STXCM) | (I2SRXCMM << I2SRXCM)); //Set RX/TX CHAN_MOD=0 I2SCC &= ~((I2STXCMM << I2STXCM) | (I2SRXCMM << I2SRXCM)); // Set RX/TX CHAN_MOD=0
i2s_set_rate(44100); i2s_set_rate(44100);
I2SC |= I2STXS; //Start transmission
if (rx) {
// Need to prime the # of samples to receive in the engine
I2SRXEN = SLC_BUF_LEN;
}
I2SC |= (rx?I2SRXS:0) | (tx?I2STXS:0); // Start transmission/reception
return true;
} }
void i2s_end(){ void i2s_begin() {
I2SC &= ~I2STXS; i2s_rxtx_begin(false, true);
}
//Reset I2S void i2s_end() {
// Disable any I2S send or receive
// ? Maybe not needed since we're resetting on the next line...
I2SC &= ~(I2STXS | I2SRXS);
// Reset I2S
I2SC &= ~(I2SRST); I2SC &= ~(I2SRST);
I2SC |= I2SRST; I2SC |= I2SRST;
I2SC &= ~(I2SRST); I2SC &= ~(I2SRST);
// Redirect IOs to user control/GPIO
pinMode(I2SO_WS, INPUT);
pinMode(I2SO_DATA, INPUT);
pinMode(I2SO_BCK, INPUT);
i2s_slc_end(); i2s_slc_end();
if (tx) {
pinMode(I2SO_DATA, INPUT);
pinMode(I2SO_BCK, INPUT);
pinMode(I2SO_WS, INPUT);
free(tx);
tx = NULL;
}
if (rx) {
pinMode(I2SI_DATA, INPUT);
pinMode(I2SI_BCK, INPUT);
pinMode(I2SI_WS, INPUT);
free(rx);
rx = NULL;
}
} }

View File

@ -40,7 +40,8 @@ speed.
extern "C" { extern "C" {
#endif #endif
void i2s_begin(); void i2s_begin(); // Enable TX only, for compatibility
bool i2s_rxtx_begin(bool enableRx, bool enableTx); // Allow TX and/or RX, returns false on OOM error
void i2s_end(); void i2s_end();
void i2s_set_rate(uint32_t rate);//Sample Rate in Hz (ex 44100, 48000) void i2s_set_rate(uint32_t rate);//Sample Rate in Hz (ex 44100, 48000)
void i2s_set_dividers(uint8_t div1, uint8_t div2);//Direct control over output rate void i2s_set_dividers(uint8_t div1, uint8_t div2);//Direct control over output rate
@ -48,10 +49,15 @@ float i2s_get_real_rate();//The actual Sample Rate on output
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(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_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(int16_t left, int16_t right);//combines both channels and calls i2s_write_sample with the result
bool i2s_read_sample(int16_t *left, int16_t *right, bool blocking); // RX data returned in both 16-bit outputs.
bool i2s_is_full();//returns true if DMA is full and can not take more bytes (overflow) 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) bool i2s_is_empty();//returns true if DMA is empty (underflow)
bool i2s_rx_is_full();
bool i2s_rx_is_empty();
int16_t i2s_available();// returns the number of samples than can be written before blocking int16_t i2s_available();// returns the number of samples than can be written before blocking
int16_t i2s_rx_available();// returns the number of samples than can be written before blocking
void i2s_set_callback(void (*callback) (void)); void i2s_set_callback(void (*callback) (void));
void i2s_rx_set_callback(void (*callback) (void));
#ifdef __cplusplus #ifdef __cplusplus
} }

View File

@ -0,0 +1,55 @@
/*
I2S stereo microphone (input) example
Run using the Arduion Serial Plotter to see waveform.
Released to the Public Domain by Earle F. Philhower, III
For the Google AIY Voice Hat Microphone daughterboard, part
of the Raspberry Pi AIY cardboard box, the I2S stereo pinout
looking at the board top with the RPI logo on the left hand
side:
+-- ------------------------------------ --+
left RPI | (1) GND (2) DIN (3) BCLK (4) LRCLK (5) 3.3V | AIY right
+---------------------------------------------+
The I2S pins are on different pins depending on your board.
The *internal GPIO number* which is NOT NECESSARIALY the
same as the pin numbers, are as follows:
I2SI_DATA = GPIO12
IS2I_BCK = GPIO13
I2SI_WS/LRCLK = GPIO14
On the D1 mini the I2SI pins map to the following D pins:
I2SI_DATA = GPIO12 = D6
IS2I_BCK = GPIO13 = D7
I2SI_WS/LRCLK = GPIO14 = D5
Expect different D pins on different ESP8266 boards, and of
course be sure to wire up VCC(3.3V) and GND.
*/
#include <ESP8266WiFi.h>
#include <i2s.h>
void setup() {
Serial.begin(115200);
WiFi.forceSleepBegin();
delay(500);
i2s_rxtx_begin(true, false); // Enable I2S RX
i2s_set_rate(11025);
delay(1000);
while (1) {
int16_t l, r;
i2s_read_sample(&l, &r, true);
char withScale[256];
sprintf(withScale, "%d %d", l, r);
Serial.println(withScale);
yield();
}
}
void loop() {
/* Nothing here */
}

View File

@ -0,0 +1,75 @@
/*
I2S stereo microphone (input) UDP transmitter
Needs a UDP listener (netcat/etc.) on port 8266 on the PC
Under Linux:
nc -u -p 8266 -l | play -t raw -r 11025 -b 16 -c 2 -e signed-integer -
Released to the Public Domain by Earle F. Philhower, III
*/
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#include <i2s.h>
// Set your network here
const char *SSID = "....";
const char *PASS = "....";
WiFiUDP udp;
// Set your listener PC's IP here:
const IPAddress listener = { 192, 168, 1, 2 };
const int port = 8266;
int16_t buffer[100][2]; // Temp staging for samples
void setup() {
Serial.begin(115200);
// Connect to WiFi network
Serial.println();
Serial.println();
Serial.print("Connecting to ");
Serial.println(SSID);
WiFi.begin(SSID, PASS);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected");
Serial.print("My IP: ");
Serial.println(WiFi.localIP());
i2s_rxtx_begin(true, false); // Enable I2S RX
i2s_set_rate(11025);
Serial.print("\nStart the listener on ");
Serial.print(listener);
Serial.print(":");
Serial.println(port);
Serial.println("ex: nc -u -p 8266 -l | play -t raw -r 11025 -b 16 -c 2 -e signed-integer -");
udp.beginPacket(listener, port);
udp.write("I2S Receiver\r\n");
udp.endPacket();
}
void loop() {
static int cnt = 0;
// Each loop will send 100 raw samples (400 bytes)
// UDP needs to be < TCP_MSS which can be 500 bytes in LWIP2
for (int i = 0; i < 100; i++) {
i2s_read_sample(&buffer[i][0], &buffer[i][1], true);
}
udp.beginPacket(listener, port);
udp.write((uint8_t*)buffer, sizeof(buffer));
udp.endPacket();
cnt++;
if ((cnt % 100) == 0) {
Serial.printf("%d\n", cnt);
}
}