I2S on ESP32
I2S on ESP32
I was thrilled to see I2S has made it into the MicroPython builds! However, I'm having trouble when I try to use it. I found several online examples of use, but the parameter names for the constructor have changed and the constants are not all defined. I tried to figure it out by looking at the 'C' code, but was not entirely successful. I'm sure this is documented somewhere (at a minimum in the 'C' code), but I wasn't able to figure out where. I'm using esp32-20210811-unstable-v1.16-198-g42d1a1635.bin on an MH-ET Live ESP32 board. I wanted to get input from a INMP441 microphone.
Here's the code I was trying.
from machine import I2S
from machine import Pin
sck_pin = Pin(14) # Bit clock output
ws_pin = Pin(13) # Word clock output
sd_pin = Pin(12) # Serial data input
samples = bytearray(2048) # Buffer for incoming microphone samples
audio_in = I2S(0,
sck=sck_pin, # I2S Clock Pin
ws=ws_pin,
sd=sd_pin,
mode=I2S.RX,
bits=16,
format=0,
rate=16000,
ibuf=256) # Number of input buffers?
#num_bytes_read = audio_in.readinto(samples)
I get the following error:
E (49145) I2S: /home/micropython/esp-idf-v4.2/components/driver/i2s.c:912 (i2s_driver_install):I2S buffer count less than 128 and more than 2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/lib/microphone.py", line 14, in <module>
OSError: (-258, 'ESP_ERR_INVALID_ARG')
The error report seems to imply I need either 1 buffer or more than 128 buffers. Playing around with the ibuf= value didn't solve this error.
Is there an example I should be following, or more current documentation I should be reading?
Thanks!
Here's the code I was trying.
from machine import I2S
from machine import Pin
sck_pin = Pin(14) # Bit clock output
ws_pin = Pin(13) # Word clock output
sd_pin = Pin(12) # Serial data input
samples = bytearray(2048) # Buffer for incoming microphone samples
audio_in = I2S(0,
sck=sck_pin, # I2S Clock Pin
ws=ws_pin,
sd=sd_pin,
mode=I2S.RX,
bits=16,
format=0,
rate=16000,
ibuf=256) # Number of input buffers?
#num_bytes_read = audio_in.readinto(samples)
I get the following error:
E (49145) I2S: /home/micropython/esp-idf-v4.2/components/driver/i2s.c:912 (i2s_driver_install):I2S buffer count less than 128 and more than 2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/lib/microphone.py", line 14, in <module>
OSError: (-258, 'ESP_ERR_INVALID_ARG')
The error report seems to imply I need either 1 buffer or more than 128 buffers. Playing around with the ibuf= value didn't solve this error.
Is there an example I should be following, or more current documentation I should be reading?
Thanks!
- pythoncoder
- Posts: 5956
- Joined: Fri Jul 18, 2014 8:01 am
- Location: UK
- Contact:
Re: I2S on ESP32
I'm also trying to make sense of I2S. I raised this comment on the docs which is being addressed. The fount of all wisdom on I2S is Mike Teachman's repo which has various examples. I recommend grabbing that repo as Mike Teachman wrote the underlying library.
These demos need some fettling: the example files have illegal names containing "-" signs and the pin declarations need checking for the platform in use.
So far I've had limited success. The continuous tone example works on STM, but only with an "unofficial" UDA1334A board. I can't get a peep out of the recommended Adafruit MAX98357A. Music playback on the UDA is lousy - just about recognisable as music.
[EDIT]
On ESP32 the MAX98357 works fine, at least with the small "side to side" demo.
It seems there is a problem on STM.
These demos need some fettling: the example files have illegal names containing "-" signs and the pin declarations need checking for the platform in use.
So far I've had limited success. The continuous tone example works on STM, but only with an "unofficial" UDA1334A board. I can't get a peep out of the recommended Adafruit MAX98357A. Music playback on the UDA is lousy - just about recognisable as music.
[EDIT]
On ESP32 the MAX98357 works fine, at least with the small "side to side" demo.
It seems there is a problem on STM.
Peter Hinch
Index to my micropython libraries.
Index to my micropython libraries.
Re: I2S on ESP32
Thanks for the quick reply! I had looked at the examples from miketeachman - that got me started but the examples also have parameter names that don't match. I think I figured those out from reading the C code, but I was unclear on what the ibuf input really specified. I see your example set it to 20000. I never went that high - I will try that and see what happens!
Re: I2S on ESP32
I have it working on esp32 with an INMP441 mic.
I just put some notes together on my wiring and then the I2S configuration block I am using: https://github.com/mocleiri/tensorflow- ... eech/esp32
I also have a script that you can run to sample audio to a 2 second wav file on the file system and then you can download in Thonny and verify you recorded something:
https://github.com/mocleiri/tensorflow- ... 2s_dump.py
put it on your board and then import i2s_dump to start it.
When I first wired things up I had the L/R selector at VCC which put it into right mode which didn't work. Mono is expecting the board to be in Left mode. (L/R connected to ground).
The ibuf value is the size of the buffer you want to have for the precision you are capturing at. If you are asking for 16-bit data then the size of the actual dma buffer may larger because it samples at 32 bits and then you need to only take the subset of the data that is at 16-bits. It hides the details from the programmer on the actual buffer size needed to support that size.
https://github.com/micropython/micropyt ... i2s.c#L230
On esp32 i2s you need to configure the number of dma buffers and their size. The way its setup is that it uses a fixed buffer size of 256 but then configures the number of buffers based on the ibuf size you provide.
The number of buffers is the result of the above function which takes into account the number of bits and it its stereo or mono.
You can see in the get_dma_bits method that in the rx case it actually needs to sample at 32 bits and then the implementation will throw out half the sampled data so you get 16 bits when reading from the I2S class.
You can see the logic here when the data is taken from the dma buffer and written into the user's buffer: https://github.com/micropython/micropyt ... i2s.c#L276
The ibuf value is purely used for the dma buffer size used with the i2s peripheral. You also need another buffer and that is what you read into when doing stuff with the data. So the sizing of that other buffer and then the mode becomes important so that you are sampling fast enough to get a contiguous audio stream and not having gaps due to not keeping up.
On esp32 using non-blocking mode its possible to keep up without having a large buffer. I was able to sample audio reliably with these settings:
I just put some notes together on my wiring and then the I2S configuration block I am using: https://github.com/mocleiri/tensorflow- ... eech/esp32
I also have a script that you can run to sample audio to a 2 second wav file on the file system and then you can download in Thonny and verify you recorded something:
https://github.com/mocleiri/tensorflow- ... 2s_dump.py
put it on your board and then import i2s_dump to start it.
When I first wired things up I had the L/R selector at VCC which put it into right mode which didn't work. Mono is expecting the board to be in Left mode. (L/R connected to ground).
The ibuf value is the size of the buffer you want to have for the precision you are capturing at. If you are asking for 16-bit data then the size of the actual dma buffer may larger because it samples at 32 bits and then you need to only take the subset of the data that is at 16-bits. It hides the details from the programmer on the actual buffer size needed to support that size.
https://github.com/micropython/micropyt ... i2s.c#L230
Code: Select all
STATIC uint32_t get_dma_buf_count(uint8_t mode, i2s_bits_per_sample_t bits, format_t format, int32_t ibuf) {
// calculate how many DMA buffers need to be allocated
uint32_t dma_frame_size_in_bytes =
(get_dma_bits(mode, bits) / 8) * (get_dma_format(mode, format) == I2S_CHANNEL_FMT_RIGHT_LEFT ? 2: 1);
uint32_t dma_buf_count = ibuf / (DMA_BUF_LEN_IN_I2S_FRAMES * dma_frame_size_in_bytes);
return dma_buf_count;
}
STATIC i2s_bits_per_sample_t get_dma_bits(uint8_t mode, i2s_bits_per_sample_t bits) {
if (mode == (I2S_MODE_MASTER | I2S_MODE_TX)) {
return bits;
} else { // Master Rx
// read 32 bit samples for I2S hardware. e.g. MEMS microphones
return I2S_BITS_PER_SAMPLE_32BIT;
}
}
The number of buffers is the result of the above function which takes into account the number of bits and it its stereo or mono.
You can see in the get_dma_bits method that in the rx case it actually needs to sample at 32 bits and then the implementation will throw out half the sampled data so you get 16 bits when reading from the I2S class.
You can see the logic here when the data is taken from the dma buffer and written into the user's buffer: https://github.com/micropython/micropyt ... i2s.c#L276
The ibuf value is purely used for the dma buffer size used with the i2s peripheral. You also need another buffer and that is what you read into when doing stuff with the data. So the sizing of that other buffer and then the mode becomes important so that you are sampling fast enough to get a contiguous audio stream and not having gaps due to not keeping up.
On esp32 using non-blocking mode its possible to keep up without having a large buffer. I was able to sample audio reliably with these settings:
Code: Select all
audio_in = I2S(
0,
sck=bck_pin,
ws=ws_pin,
sd=sdin_pin,
mode=I2S.RX,
bits=16,
format=I2S.MONO,
rate=16000,
ibuf=9600
)
mic_samples = bytearray(3200)
mic_samples_mv = memoryview(mic_samples)
Re: I2S on ESP32
Michael,
Thanks! This looks like it will be a big help, although it will take me some time to digest it all.
I was able to get samples into a buffer and watch the buffer contents change, although I don't know yet if the contents reflect the actual microphone data. Using a larger ibuf value got me past the error in my original post. Then I had to connect an ESP32 output pin to WS. I had it connected to L/R instead and had left WS floating. After fixing that I could see data out on the oscilloscope and watch the data in the buffer change. Your post will help me validate the data I'm getting.
I'm rather new to python but have written a reasonable amount of C. How do you find the constants that are defined for python? For example, I2S.MONO or I2S.RX ? I couldn't spot these when I went browsing through the C code for micropython. I'd much rather use predefined constants when I can instead of hard coded numbers.
Doug
Thanks! This looks like it will be a big help, although it will take me some time to digest it all.
I was able to get samples into a buffer and watch the buffer contents change, although I don't know yet if the contents reflect the actual microphone data. Using a larger ibuf value got me past the error in my original post. Then I had to connect an ESP32 output pin to WS. I had it connected to L/R instead and had left WS floating. After fixing that I could see data out on the oscilloscope and watch the data in the buffer change. Your post will help me validate the data I'm getting.
I'm rather new to python but have written a reasonable amount of C. How do you find the constants that are defined for python? For example, I2S.MONO or I2S.RX ? I couldn't spot these when I went browsing through the C code for micropython. I'd much rather use predefined constants when I can instead of hard coded numbers.
Doug
- pythoncoder
- Posts: 5956
- Joined: Fri Jul 18, 2014 8:01 am
- Location: UK
- Contact:
Re: I2S on ESP32
Code: Select all
>>> from machine import I2S
>>> dir(I2S)
['__class__', '__name__', 'readinto', 'write', '__bases__', '__dict__', 'MONO', 'RX', 'STEREO', 'TX', 'deinit', 'init', 'irq', 'shift']
Peter Hinch
Index to my micropython libraries.
Index to my micropython libraries.
Re: I2S on ESP32
Getting them from the python side is very clean and easy.
Here are some details on how they are setup on the C side.
They are setup in the local scope of the I2S class here: https://github.com/micropython/micropyt ... i2s.c#L666
The MP_QSTR_ prefix is not in the actual name just related to how string interning works.
This document has the low level details on the C setup for constants of different types:
https://micropython-usermod.readthedocs ... ds_06.html
Here are some details on how they are setup on the C side.
They are setup in the local scope of the I2S class here: https://github.com/micropython/micropyt ... i2s.c#L666
The MP_QSTR_ prefix is not in the actual name just related to how string interning works.
This document has the low level details on the C setup for constants of different types:
https://micropython-usermod.readthedocs ... ds_06.html
Re: I2S on ESP32
Thanks guys! I was aware of the dir command for python, but assumed (incorrectly, it seems) there were more constants it wasn't listing because the examples I was looking at had more constants than what dir showed. The other constants must have gone away when the I2S code was merged in the MicroPython builds.
Thanks for the pointers to the documentation - I suspect before this is all over I'll end up having to write some of my own libraries in C, and this will help!
Doug
Thanks for the pointers to the documentation - I suspect before this is all over I'll end up having to write some of my own libraries in C, and this will help!
Doug
- Mike Teachman
- Posts: 155
- Joined: Mon Jun 13, 2016 3:19 pm
- Location: Victoria, BC, Canada
Re: I2S on ESP32
I'm the author of the recent I2S PR. Sorry for being absent from this post, but from reading the discussion it looks like the questions raised have been answered.
Yesterday, I updated the I2S examples repo that I am maintaining. The examples are now clearer (thanks to Peter Hinch for the suggestions). I also added some wiring ideas, with photos.
https://github.com/miketeachman/micropy ... s-examples
I'm always interested to receive feedback on the examples in this repo. You can either raise an issue at the repo, or post questions to this forum.
My I2S development roadmap:
Yesterday, I updated the I2S examples repo that I am maintaining. The examples are now clearer (thanks to Peter Hinch for the suggestions). I also added some wiring ideas, with photos.
https://github.com/miketeachman/micropy ... s-examples
I'm always interested to receive feedback on the examples in this repo. You can either raise an issue at the repo, or post questions to this forum.
My I2S development roadmap:
- add a simple example for playing a WAV file, with play, pause, and resume features, plus an option for continuous looping.
- support for I2S on the rPi Pico
- support for I2S on the ESP8266
- pythoncoder
- Posts: 5956
- Joined: Fri Jul 18, 2014 8:01 am
- Location: UK
- Contact:
Timing sensitivity
As a general point, I2S is unusually sensitive to wiring. You can't just patch these things up with standard jumpers and expect them to work.
Mike Teachman's repo contains recommendations which really do need to be followed. I suspect (but can't prove) that the cause is timing sensitivity on the ws line: the chips use this to regenerate their internal clocks.
Mike Teachman's repo contains recommendations which really do need to be followed. I suspect (but can't prove) that the cause is timing sensitivity on the ws line: the chips use this to regenerate their internal clocks.
Peter Hinch
Index to my micropython libraries.
Index to my micropython libraries.