mirror of
https://github.com/esp8266/Arduino.git
synced 2025-04-19 23:22:16 +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:
parent
7ae8f98e57
commit
8ae553d99e
@ -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"
|
||||||
|
|
||||||
// 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_CNT (8) // Number of buffers in the I2S circular buffer
|
||||||
#define SLC_BUF_LEN (64) // Length of one buffer, in 32-bit words.
|
#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,
|
// We use a queue to keep track of the DMA buffers that are empty. The ISR
|
||||||
//the mp3 decode will pull them from the front and fill them. For ease, the queue will contain *pointers* to the DMA
|
// will push buffers to the back of the queue, the I2S transmitter will pull
|
||||||
//buffers, not the data itself. The queue depth is one smaller than the amount of buffers we have, because there's
|
// them from the front and fill them. For ease, the queue will contain
|
||||||
//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
|
// *pointers* to the DMA buffers, not the data itself. The queue depth is
|
||||||
//simultaneously.
|
// 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 {
|
// For RX, it's a little different. The buffers in i2s_slc_queue are
|
||||||
uint32 blocksize:12;
|
// placed onto the list when they're filled by DMA
|
||||||
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];
|
typedef struct slc_queue_item {
|
||||||
static uint8_t i2s_slc_queue_len;
|
uint32_t blocksize : 12;
|
||||||
static uint32_t *i2s_slc_buf_pntr[SLC_BUF_CNT]; //Pointer to the I2S DMA buffer data
|
uint32_t datalen : 12;
|
||||||
static struct slc_queue_item i2s_slc_items[SLC_BUF_CNT]; //I2S DMA buffer descriptors
|
uint32_t unused : 5;
|
||||||
static uint32_t *i2s_curr_slc_buf=NULL;//current buffer for writing
|
uint32_t sub_sof : 1;
|
||||||
static int i2s_curr_slc_buf_pos=0; //position in the current buffer
|
uint32_t eof : 1;
|
||||||
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.
|
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.
|
||||||
|
// Note these are internal GPIO numbers and not pins on an
|
||||||
|
// Arduino board. Users need to verify their particular wiring.
|
||||||
|
#define I2SO_DATA 3
|
||||||
|
#define I2SO_BCK 15
|
||||||
|
#define I2SO_WS 2
|
||||||
|
#define I2SI_DATA 12
|
||||||
|
#define I2SI_BCK 13
|
||||||
|
#define I2SI_WS 14
|
||||||
|
|
||||||
|
static bool _i2s_is_full(const i2s_state_t *ch) {
|
||||||
|
if (!ch) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return (ch->curr_slc_buf_pos==SLC_BUF_LEN || ch->curr_slc_buf==NULL) && (ch->slc_queue_len == 0);
|
||||||
|
}
|
||||||
|
|
||||||
bool 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);
|
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() {
|
bool i2s_is_empty() {
|
||||||
return (i2s_slc_queue_len >= SLC_BUF_CNT-1);
|
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++) {
|
||||||
|
if (ch->slc_queue[i] != item) {
|
||||||
|
ch->slc_queue[dest++] = ch->slc_queue[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ch->slc_queue_len < SLC_BUF_CNT - 1) {
|
||||||
|
ch->slc_queue[ch->slc_queue_len++] = item;
|
||||||
|
} else {
|
||||||
|
ch->slc_queue[ch->slc_queue_len] = item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ICACHE_RAM_ATTR i2s_slc_isr(void) {
|
||||||
|
ETS_SLC_INTR_DISABLE();
|
||||||
uint32_t slc_intr_status = SLCIS;
|
uint32_t slc_intr_status = SLCIS;
|
||||||
SLCIC = 0xFFFFFFFF;
|
SLCIC = 0xFFFFFFFF;
|
||||||
if (slc_intr_status & SLCIRXEOF) {
|
if (slc_intr_status & SLCIRXEOF) {
|
||||||
ETS_SLC_INTR_DISABLE();
|
slc_queue_item_t *finished_item = (slc_queue_item_t *)SLCRXEDA;
|
||||||
struct slc_queue_item *finished_item = (struct slc_queue_item*)SLCRXEDA;
|
// 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
|
ets_memset((void *)finished_item->buf_ptr, 0x00, SLC_BUF_LEN * 4);
|
||||||
if (i2s_slc_queue_len >= SLC_BUF_CNT-1) { //All buffers are empty. This means we have an underflow
|
if (tx->slc_queue_len >= SLC_BUF_CNT-1) {
|
||||||
i2s_slc_queue_next_item(); //free space for finished_item
|
// 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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
i2s_slc_queue[i2s_slc_queue_len++] = finished_item->buf_ptr;
|
|
||||||
if (i2s_callback) i2s_callback();
|
|
||||||
ETS_SLC_INTR_ENABLE();
|
ETS_SLC_INTR_ENABLE();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void i2s_set_callback(void (*callback) (void)) {
|
void i2s_set_callback(void (*callback) (void)) {
|
||||||
i2s_callback = callback;
|
tx->callback = callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
void i2s_slc_begin(){
|
void i2s_rx_set_callback(void (*callback) (void)) {
|
||||||
i2s_slc_queue_len = 0;
|
rx->callback = callback;
|
||||||
int x, y;
|
}
|
||||||
|
|
||||||
for (x=0; x<SLC_BUF_CNT; x++) {
|
static bool _alloc_channel(i2s_state_t *ch) {
|
||||||
i2s_slc_buf_pntr[x] = malloc(SLC_BUF_LEN*4);
|
ch->slc_queue_len = 0;
|
||||||
for (y=0; y<SLC_BUF_LEN; y++) i2s_slc_buf_pntr[x][y] = 0;
|
for (int x=0; x<SLC_BUF_CNT; x++) {
|
||||||
|
ch->slc_buf_pntr[x] = (uint32_t *)malloc(SLC_BUF_LEN * sizeof(ch->slc_buf_pntr[0][0]));
|
||||||
|
if (!ch->slc_buf_pntr[x]) {
|
||||||
|
// OOM, the upper layer will free up any partially allocated channels.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
memset(ch->slc_buf_pntr[x], 0, SLC_BUF_LEN * sizeof(ch->slc_buf_pntr[x][0]));
|
||||||
|
|
||||||
i2s_slc_items[x].unused = 0;
|
ch->slc_items[x].unused = 0;
|
||||||
i2s_slc_items[x].owner = 1;
|
ch->slc_items[x].owner = 1;
|
||||||
i2s_slc_items[x].eof = 1;
|
ch->slc_items[x].eof = 1;
|
||||||
i2s_slc_items[x].sub_sof = 0;
|
ch->slc_items[x].sub_sof = 0;
|
||||||
i2s_slc_items[x].datalen = SLC_BUF_LEN*4;
|
ch->slc_items[x].datalen = SLC_BUF_LEN * 4;
|
||||||
i2s_slc_items[x].blocksize = SLC_BUF_LEN*4;
|
ch->slc_items[x].blocksize = SLC_BUF_LEN * 4;
|
||||||
i2s_slc_items[x].buf_ptr = (uint32_t)&i2s_slc_buf_pntr[x][0];
|
ch->slc_items[x].buf_ptr = (uint32_t*)&ch->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]));
|
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();
|
||||||
@ -130,31 +231,43 @@ void i2s_slc_begin(){
|
|||||||
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;
|
||||||
|
if (tx) {
|
||||||
SLCRXL |= SLCRXLS;
|
SLCRXL |= SLCRXLS;
|
||||||
}
|
}
|
||||||
|
|
||||||
void i2s_slc_end(){
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
// Don't wait if nonblocking, just notify upper levels
|
||||||
|
return false;
|
||||||
|
}
|
||||||
while (1) {
|
while (1) {
|
||||||
if(i2s_slc_queue_len > 0){
|
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
|
void i2s_set_rate(uint32_t rate) { //Rate in HZ
|
||||||
if(rate == _i2s_sample_rate) 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;
|
||||||
@ -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) {
|
||||||
|
tx = (i2s_state_t*)calloc(1, sizeof(*tx));
|
||||||
|
if (!tx) {
|
||||||
|
// Nothing to clean up yet
|
||||||
|
return false; // OOM Error!
|
||||||
|
}
|
||||||
pinMode(I2SO_WS, FUNCTION_1);
|
pinMode(I2SO_WS, FUNCTION_1);
|
||||||
pinMode(I2SO_DATA, FUNCTION_1);
|
pinMode(I2SO_DATA, FUNCTION_1);
|
||||||
pinMode(I2SO_BCK, 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_begin() {
|
||||||
|
i2s_rxtx_begin(false, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void i2s_end() {
|
void i2s_end() {
|
||||||
I2SC &= ~I2STXS;
|
// Disable any I2S send or receive
|
||||||
|
// ? Maybe not needed since we're resetting on the next line...
|
||||||
|
I2SC &= ~(I2STXS | I2SRXS);
|
||||||
|
|
||||||
// Reset I2S
|
// Reset I2S
|
||||||
I2SC &= ~(I2SRST);
|
I2SC &= ~(I2SRST);
|
||||||
I2SC |= I2SRST;
|
I2SC |= I2SRST;
|
||||||
I2SC &= ~(I2SRST);
|
I2SC &= ~(I2SRST);
|
||||||
|
|
||||||
// Redirect IOs to user control/GPIO
|
i2s_slc_end();
|
||||||
pinMode(I2SO_WS, INPUT);
|
|
||||||
|
if (tx) {
|
||||||
pinMode(I2SO_DATA, INPUT);
|
pinMode(I2SO_DATA, INPUT);
|
||||||
pinMode(I2SO_BCK, INPUT);
|
pinMode(I2SO_BCK, INPUT);
|
||||||
|
pinMode(I2SO_WS, INPUT);
|
||||||
i2s_slc_end();
|
free(tx);
|
||||||
|
tx = NULL;
|
||||||
|
}
|
||||||
|
if (rx) {
|
||||||
|
pinMode(I2SI_DATA, INPUT);
|
||||||
|
pinMode(I2SI_BCK, INPUT);
|
||||||
|
pinMode(I2SI_WS, INPUT);
|
||||||
|
free(rx);
|
||||||
|
rx = NULL;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
55
libraries/esp8266/examples/I2SInput/I2SInput.ino
Normal file
55
libraries/esp8266/examples/I2SInput/I2SInput.ino
Normal 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 */
|
||||||
|
}
|
75
libraries/esp8266/examples/I2STransmit/I2STransmit.ino
Normal file
75
libraries/esp8266/examples/I2STransmit/I2STransmit.ino
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user