Skip to content
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

I2s input API and examples #4539

Merged
merged 17 commits into from
Apr 2, 2018
Merged

Conversation

earlephilhower
Copy link
Collaborator

@earlephilhower earlephilhower commented Mar 21, 2018

-edit- Ready for testing/review.

Runs well enough to send I2S recorded data to a PC and play over its speakers. Pretty cool!

Rewrite the I2S utils to support stereo I2S microphone inputs.

Add API calls:

  • i2s_rxtx_begin(bool rx, bool tx);
  • bool i2s_read_sample(int16_t *l, int16_t *r, bool blocking);

See issue #4496 for the trail.

Much of the I2S TX infrastructure can be used to handle I2S input,
so move the per-channel data structures to a struct for easier
changes.
@earlephilhower
Copy link
Collaborator Author

earlephilhower commented Mar 21, 2018

Looks like the new code is working to some degree. Hooking up a real I2S mike and graphing the i2s_read_sample(l,r,nb) gives:
image

Initial testing of I2S reception API
Add API calls:
i2s_rxtx_begin(bool rx, rool tx);
i2s_read_sample(uint32_t *l, uint32_t *r);
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_FLASH_ATTR function_name()',
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can the callback really be in Flash? Shouldn't this read "... defined as void ICACHE_RAM_ATTR function_name()"?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

You may have hit upon the cause of some random I2S crashes I've seen at idle. The decorator comes from the original author (maybe cut-n-pasted from the mp3 example at some point?). I just mirrored what was there in the new functions. I'll go over and adjust and remove as needed before finishing.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The actual I2S interrupt handler is defined "ICACHE_FLASHATTR" in master!

#define I2SI_WS 14


static bool ICACHE_FLASH_ATTR _i2s_is_full(const i2s_state_t *ch) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

why do all of these have the ICACHE_FLASH_ATTR decorator, when that is our default?

static bool ICACHE_FLASH_ATTR _i2s_is_full(const i2s_state_t *ch) {
if (!ch) {
return false;
} else {
Copy link
Collaborator

Choose a reason for hiding this comment

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

remove else, no need for it. Just let the flow get to the following return statement.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done.

static bool ICACHE_FLASH_ATTR _i2s_is_empty(const i2s_state_t *ch) {
if (!ch) {
return false;
} else {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Remove else.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

done

static int16_t ICACHE_FLASH_ATTR _i2s_available(const i2s_state_t *ch) {
if (!ch) {
return 0;
} else {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Remove else.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

done

I2SC &= ~(I2STSM | (I2SBMM << I2SBM) | (I2SBDM << I2SBD) | (I2SCDM << I2SCD));
I2SC |= I2SRF | I2SMR | I2SRSM | I2SRMS | ((sbd_div_best) << I2SBD) | ((scd_div_best) << I2SCD);
I2SC &= ~(I2STSM | I2SRSM | /*(I2SBMM << I2SBM) |*/ (I2SBDM << I2SBD) | (I2SCDM << I2SCD));
I2SC |= I2SRF | I2SMR | I2SRMS | ((sbd_div_best) << I2SBD) | ((scd_div_best) << I2SCD);
Copy link
Collaborator

Choose a reason for hiding this comment

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

What does the change in this line mean? (add comment)

}

void ICACHE_FLASH_ATTR i2s_set_dividers(uint8_t div1, uint8_t div2){
div1 &= I2SBDM;
div2 &= I2SCDM;

I2SC &= ~(I2STSM | (I2SBMM << I2SBM) | (I2SBDM << I2SBD) | (I2SCDM << I2SCD));
I2SC &= ~(I2STSM | I2SRSM | (I2SBMM << I2SBM) | (I2SBDM << I2SBD) | (I2SCDM << I2SCD));
Copy link
Collaborator

Choose a reason for hiding this comment

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

what does the change in this line mean? (add comment)

SLCC0 &= ~(SLCMM << SLCM); // Clear DMA MODE
SLCC0 |= (1 << SLCM); // Set DMA MODE to 1
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

//Feed DMA the 1st buffer desc addr
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should this comment be updated to better reflect the new logic below?

if (!tx) {
return; // OOM Error!
}
pinMode(2, FUNCTION_1); // I2SO_WS (LRCK)
Copy link
Collaborator

Choose a reason for hiding this comment

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

These pin numbers should probably be #defined like the I2SI_lol ones

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

done

i2s_slc_end();

if (tx) {
pinMode(2, INPUT);
Copy link
Collaborator

Choose a reason for hiding this comment

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

#define pin numbers above

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

done

@devyte
Copy link
Collaborator

devyte commented Mar 22, 2018

Overall, looks pretty good, especially if it works!

@earlephilhower
Copy link
Collaborator Author

Sorry for the ton of updates. There's no way to consolidate responses into a single one. The ICACHE_FLASH_ATTR was a good catch, and may be a real bug in the existing ISR. The rest, I'm still hacking away and will clean things up. I've built DMA and serial units in Verilog and programmed them on many different systems, but always w/a good block diagram and register reference. W/O those for the 8266, it's a lot more challenging.

@earlephilhower
Copy link
Collaborator Author

Oh, and stereo input seems to be working too. This is the demo app running on a hacked AIY voice hat:
image

est_wdt_(dis/en)able were being called instead of yield in the
i2s_(read/write)_sample branches, but those calls don't actually
reset the WDT.  Replace with optimistic_yield(10ms).
@devyte
Copy link
Collaborator

devyte commented Mar 23, 2018

@earlephilhower does it make sense to add some unit tests for this?

@earlephilhower
Copy link
Collaborator Author

I'll keep that in mind, but because this is running and capturing data via an external pin I'm not sure what can actually be tested. If there was a loopback mode, that would be ideal. Send data on one iface, read it back on the other, compare.

@devyte
Copy link
Collaborator

devyte commented Mar 28, 2018

@earlephilhower this has conflicts now

@earlephilhower
Copy link
Collaborator Author

Yup, I figured it would get borked with the latest bugfix. I'll see if I can untangle it.

Fix mistake in merge.  I2S recevive was in slave mode (external clock
driven).
Mostly for my edification, list the IO pins and where to find them on
the D1 mini and other boards, and how to use the AIY Voice Hat.
Some I2S configuration and DMA tweaks makes 16 bits per sample per
channel work, giving a basically identical interface as xmit.

The received data values now span the full range of +/-32767 as well.
@earlephilhower earlephilhower changed the title WIP - I2s input API I2s input API and examples Mar 28, 2018
@earlephilhower
Copy link
Collaborator Author

earlephilhower commented Mar 28, 2018

I've tested and added an example I2S microphone send-over-UDP example. Runs pretty well if I do say so!

At this stage ready for review and testing by those in the original issue.

Very simple example sends I2S microphone data using UDP over WiFi.
Runs very well and proves the I2S receive DMA buffer management is
working pretty well.
Clean up a few dangling 1-line-ifs, make the new I2S code check for
an OOM error and return it up to the app instead of crashing when it
tries to memset a NULL pointer.  Old comments updated to new structure.
@earlephilhower earlephilhower merged commit 8ae553d into esp8266:master Apr 2, 2018
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
Copy link
Collaborator

Choose a reason for hiding this comment

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

I come after the battle with saying that I'm not sure MSS applies to UDP.
UDP is only limited by MTU which is always 1460 on ESP8266 (defined and given by PHY).

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Are you sure about that? I started with ~1024 bytes per packet, but no UDP packets ever made it to the PC with the low-mem LWIP. At high-mem it did work. Dropping to 400 bytes or so made things work under both so I left it there. It's also better for I2S latency, since you want to minimize the amount of buffering, anyway.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes, I'm sure that it should work, and I'm now sure I have to check why it does not :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants