miketeachman / micropython-i2s-examples Goto Github PK
View Code? Open in Web Editor NEWExamples for I2S support on microcontrollers that run MicroPython
License: MIT License
Examples for I2S support on microcontrollers that run MicroPython
License: MIT License
is it possible to play WAV files through internal DAC like LOBO port for minimal audio applications which doesn't require high quality?
As a first step I am attempting blocking playback on these platforms, using your demo scripts and samples. I have two playback devices, both of which seem functional. Both are from Adafruit, a UDA1334A and a MAX98357.
On STM (a Pyboard 1.1) I cannot get the MAX to produce any output. The UDA produces a tone correctly, but music is garbled and barely recognisable.
On the ESP32 reference board I tested side-to-side-8k-16bits-stereo.wav and music-16k-16bits-stereo.wav. On the MAX both worked (as far as can be established in mono). On the UDA, side to side worked, but the tones differed between channels, one sounding a little distorted. Music output was white noise.
Hello,
First of thank you for creating this awesome MicroPython library, very useful!
I was wondering if it's possible to play multiple .wav files simultaneously?
Thanks for any help.
hi, i'm working with INMP441 microphone on raspberry pi pico, and i connected the microphone by what should it be, but when i run the record_mic_to_sdcard_blocking.py
file, the result is not what it supposed to be
so i added a print after while num_sample_bytes_written_to_wav < RECORDING_SIZE_IN_BYTES:
here's the output :
>>> %Run -c $EDITOR_CONTENT
Recording size: 441000 bytes
========== START RECORDING ==========
10000
10000
10000
10000
10000
10000
10000
10000
10000
10000
10000
10000
10000
10000
10000
10000
10000
10000
10000
10000
10000
10000
10000
10000
10000
10000
10000
10000
10000
10000
10000
10000
10000
10000
10000
10000
10000
10000
10000
10000
10000
10000
10000
10000
10000
========== DONE RECORDING ==========
>>>
i checked that if i disconnect INMP441 from wires, what happens. and the output wasn't changed at all. only 10000
was printed on each line.
i don't think the problem should be from sd card
MicroPython v1.20.0 on 2023-04-26; Raspberry Pi Pico with RP2040
Type "help()" for more information.
thank you and sorry for my poor english🙏
Some projects like ESP32 Audio Input - MAX4466, MAX9814, SPH0645LM4H, INMP441 use the internal ADC via I2S for sampling. Availability of such functionality ?
Great job, thats is working well on my RP2040-PiZero with the PCM5102A.
I was wondering how I could add some gain control when sending wav data over I2S
Its probably not very hard but I am just a beginner
Thanks
Hi Mike,
Thanks for your work on the Micropython I2S class!
Playing mono wav files works fine but when I play a stereo file it sounds like it alternatingly fills the buffer with zeroes. So I hear one buffer full of sound and then for the same length silence.
Do you have any idea what could cause this? I am using code very similar to your example.
Thanks,
Guustaaf
Hi @miketeachman,
As promised, here I am with my test results.
I'm using a ICS-43432 I2S MEMS mic. It outputs 24-bit data, little endian, every 64 clock cycles.
Here is its data sheet:
https://invensense.tdk.com/wp-content/uploads/2015/02/ICS-43432_DS.pdf
My test program:
from machine import I2S, Pin
buf = bytearray(10000)
i2s = I2S(0, sck=Pin(6), ws=Pin(7), sd=Pin(8), mode=I2S.RX, bits=32, format=I2S.MONO, rate=44100, ibuf=40000)
while True:
ret = i2s.readinto(buf)
for i in range(0, 20, 4):
print(buf[i])
print(buf[i + 1])
print(buf[i + 2])
print(buf[i + 3])
print("------------")
Running on:
MicroPython v1.18 on 2022-01-17; Raspberry Pi Pico with RP2040
Some output:
0
102
2
0
------------
192
67
0
0
------------
192
71
2
0
------------
0
16
250
255
------------
192
79
250
255
------------
0
84
254
255
------------
192
93
248
255
------------
0
68
254
255
------------
192
57
254
255
------------
192
99
251
255
------------
0
202
0
0
------------
0
208
250
255
------------
0
60
253
255
------------
192
183
4
0
------------
192
21
255
255
------------
192
13
2
0
------------
192
241
0
0
------------
What I noticed is that the first byte is always 0 or 192.
I've tried the same mic with a Raspberry Pi and some C code and that is not the case.
Can you think of anything causing this?
Thanks!
I just set SAMPLE_SIZE_IN_BITS
to 32
, then it raised an exception, OSError: (-258, 'ESP_ERR_INVALID_ARG')
, how to solve this?
Board: ESP-WROOM-32
MicroPython: MicroPython v1.19.1 on 2022-06-18; ESP32 module with ESP32
# ======= AUDIO CONFIGURATION =======
TONE_FREQUENCY_IN_HZ = 340
SAMPLE_SIZE_IN_BITS = 32
FORMAT = I2S.MONO # only MONO supported in this example
SAMPLE_RATE_IN_HZ = 48_000
# ======= AUDIO CONFIGURATION =======
Hello!
Congratulations on your work, it's really great!
I am using Raspberry pico with an SD card. The card contains all the waves to run the examples, however I get this error. I have tried reading and writing to the SD card and it worked OK. What could be the problem? Thank you very much
Software used: easy_wav_player.py
I'm receiving PCM audio frames via a network interface and then in the callback for that I call I2S.write.
Everything works well apart from the audio is a little quiet, especially when compared to a locally generated tone.
I've tried to use I2S.shift on the data before i call write but I get the error:
TypeError: object with buffer protocol required
this is my code, the audio data is in msg. and spk is my I2S device
if ttype == 'aud':
I2S.shift(buf=msg, bits=16, shift=2)
spk.write(msg)
Hello,
First of all, thank you for your post. Great job.
I have a raspberry pi pico and I want to record audio to an SD card. I used your "record_mic_to_sdcard_non_blocking.py" file, and didin't change anything. I copied the "sdcard.py" file to the pico, and again didin't change anything. I have a microphone INMP441 and micro SD card module like this:
Everything together:
But something is wrong, and this happend:
Micropython firmware: v1.23.0 (latest)
What could be wrong? What am I doing wrong?
I'm a beginner so maybe I made some simple mistake.
Names with embedded -
characters cause a syntax error on import. I suggest changing them to _
to avoid confusing beginners.
Can i use this library with PCM5100A ?
First, Thanks for your great work here!
I was able to run your code for the pico/INMP441 and save the wav file to my SD card successfully. On reviewing the recordings its clear that volume is quite low. I am barely able hear my voice when speaking within a few inches of the mic. Since the microphone samples at 24 bits and your code is creating a 16 bit wav file is there a possibility that we are loosing the addition information in the signal?
the post here discusses this issue:
https://electronics.stackexchange.com/questions/647968/how-to-remove-inmp441-mems-microphone-low-frequency-noise
Thanks for your help!
Jim
When running the play_tone.py example I can hear a perfect sine wave. But then if I minimally change it to be non-blocking I just hear a buzzing sound. I must be missing something silly, but I just can't spot it. Maybe an official non-blocking minimal example that just plays a tone exactly like the blocking one would be a nice sanity check to have?
Here's my blocking code which is just the example stripped down to just the ESP32 parts and gpio pins changed to what I'm using:
import os
import math
import struct
import time
import micropython
from machine import I2S
from machine import Pin
def make_tone(rate, bits, frequency):
# create a buffer containing the pure tone samples
samples_per_cycle = rate // frequency
sample_size_in_bytes = bits // 8
samples = bytearray(samples_per_cycle * sample_size_in_bytes)
volume_reduction_factor = 32
range = pow(2, bits) // 2 // volume_reduction_factor
if bits == 16:
format = "<h"
else: # assume 32 bits
format = "<l"
for i in range(samples_per_cycle):
sample = range + int((range - 1) * math.sin(2 * math.pi * i / samples_per_cycle))
struct.pack_into(format, samples, i * sample_size_in_bytes, sample)
return samples
# ======= I2S CONFIGURATION =======
SCK_PIN = 18
WS_PIN = 23
SD_PIN = 19
I2S_ID = 0
BUFFER_LENGTH_IN_BYTES = 2000
# ======= I2S CONFIGURATION =======
# ======= AUDIO CONFIGURATION =======
TONE_FREQUENCY_IN_HZ = 440
SAMPLE_SIZE_IN_BITS = 16
FORMAT = I2S.MONO # only MONO supported in this example
SAMPLE_RATE_IN_HZ = 44100
# ======= AUDIO CONFIGURATION =======
audio_out = I2S(
I2S_ID,
sck=Pin(SCK_PIN),
ws=Pin(WS_PIN),
sd=Pin(SD_PIN),
mode=I2S.TX,
bits=SAMPLE_SIZE_IN_BITS,
format=FORMAT,
rate=SAMPLE_RATE_IN_HZ,
ibuf=BUFFER_LENGTH_IN_BYTES,
)
samples = make_tone(SAMPLE_RATE_IN_HZ, SAMPLE_SIZE_IN_BITS, TONE_FREQUENCY_IN_HZ)
print("========== START PLAYBACK ==========")
try:
while True:
num_written = audio_out.write(samples)
except (KeyboardInterrupt, Exception) as e:
print("caught exception {} {}".format(type(e).__name__, e))
# cleanup
audio_out.deinit()
print("Done")
And then here's the same thing with just the few changes that I think are necessary to turn that non-blocking:
import os
import math
import struct
import time
import micropython
from machine import I2S
from machine import Pin
def make_tone(rate, bits, frequency):
# create a buffer containing the pure tone samples
samples_per_cycle = rate // frequency
sample_size_in_bytes = bits // 8
samples = bytearray(samples_per_cycle * sample_size_in_bytes)
volume_reduction_factor = 32
range = pow(2, bits) // 2 // volume_reduction_factor
if bits == 16:
format = "<h"
else: # assume 32 bits
format = "<l"
for i in range(samples_per_cycle):
sample = range + int((range - 1) * math.sin(2 * math.pi * i / samples_per_cycle))
struct.pack_into(format, samples, i * sample_size_in_bytes, sample)
return samples
# ======= I2S CONFIGURATION =======
SCK_PIN = 18
WS_PIN = 23
SD_PIN = 19
I2S_ID = 0
BUFFER_LENGTH_IN_BYTES = 2000
# ======= I2S CONFIGURATION =======
# ======= AUDIO CONFIGURATION =======
TONE_FREQUENCY_IN_HZ = 440
SAMPLE_SIZE_IN_BITS = 16
FORMAT = I2S.MONO # only MONO supported in this example
SAMPLE_RATE_IN_HZ = 44100
# ======= AUDIO CONFIGURATION =======
audio_out = I2S(
I2S_ID,
sck=Pin(SCK_PIN),
ws=Pin(WS_PIN),
sd=Pin(SD_PIN),
mode=I2S.TX,
bits=SAMPLE_SIZE_IN_BITS,
format=FORMAT,
rate=SAMPLE_RATE_IN_HZ,
ibuf=BUFFER_LENGTH_IN_BYTES,
)
samples = make_tone(SAMPLE_RATE_IN_HZ, SAMPLE_SIZE_IN_BITS, TONE_FREQUENCY_IN_HZ)
def i2s_callback(arg):
num_written = audio_out.write(samples)
audio_out.irq(i2s_callback)
print("========== START PLAYBACK ==========")
num_written = audio_out.write(samples)
try:
while True:
pass
except (KeyboardInterrupt, Exception) as e:
print("caught exception {} {}".format(type(e).__name__, e))
# cleanup
audio_out.deinit()
print("Done")
I studied the micropython documentation and all the other examples and I think that should just work, yet it doesn't.
Any ideas welcome.
Hello,
For a personnal project, i try to use ESP32 WROOM (devkit1) with a PCM5102 and a SDcard.
I have try to use your examples, but it doesn't work at home...
I try with a MAX98357A and it work perfectly...
I'm going to describe the steps I took:
1/ flash micropython v 1.21 (this works for the other programs I've already done)
2/ connect the PCM with the ESP32 as shown. VIN = 3.3V, GND+SCK = GND, BLK = 32, DIN=33, LCK=25.
I didn't solder any of the jumpers to the board. Was this necessary?
3/I plugged in my earphones
4/ open thonny, copy and paste the code from the example "play_tone.py", save on the board as "test.py" and click on the green button of thonny (to upload and run the programme)
the result is : no sound on the ear phone.
I put the volume_reduction_factor at 1, nothing change...
The programm seem to run correctly, but no sound.
Do i miss a step?
Thank,
A.
Hi Mike,
first i would like to thank you for the code and explainations. I have request if you don't mind. can i play the WAV file from a URL insted of an SD card?
Hi. Great job in api machine i2s.
If I want to send audio data from a microphone using the API_i2s through DAC of an ST board, should I filter and decimate the data buffer output gotten from API_i2s to convert it in PCM data, or should I just take the data output and send it to DAC's buffer ?
Does the API_i2s transform from PDM to PCM or does it just work with PDM?
What kind of modulation is recording in the .py examples scripts: PCM or PDM?
Thanks for the great work Mike.
I found that the significant changes in the esp-idf i2s driver since V4.3, mainly to support mclk it seems, cause some of these examples to break (eg record_mic_to_sdcard_blocking.py), with these errors
E (19344) I2S: i2s_calculate_common_clock(1215): sample rate is too large
E (19344) I2S: i2s_calculate_clock(1264): Common clock calculate failed
E (19354) I2S: i2s_check_set_mclk(253): ESP32 only support to set GPIO0/GPIO1/GPIO3 as mclk signal, error GPIO number:1061222028
E (19364) I2S: i2s_set_pin(314): mclk config failed
I worked around this in my application, which does not use mclk by adding the following in machine_i2s.c
i2s_config.mclk_multiple = I2S_MCLK_MULTIPLE_256;
to i2s_config
and
pin_config.mck_io_num = I2S_PIN_NO_CHANGE;
to pin_config
Obviously this needs a more thorough fix, especially if mclk is to be suppported...
This occurs in various scripts, for example play-wav-from-flash-blocking.py
. The script includes platform detection code which checks for STM and ESP32, but then goes on to define pins in a way which is not STM-compatible.
SCK_PIN = 33
WS_PIN = 25
SD_PIN = 32
I2S_ID = 1
I appreciate this is obvious to experienced users, but these scripts are likely to be run by those new to MP.
If device doesn't have an SD card reader, it's currently not possible to pass a filename into the play method with out changing the underlying code.
For example in wavplayer.py:
def play(self, wav_file, loop=False):
if self.state == WavPlayer.PLAY:
raise ValueError("already playing a WAV file")
elif self.state == WavPlayer.PAUSE:
raise ValueError("paused while playing a WAV file")
else:
self.wav = open("/sd/{}".format(wav_file), "rb")
/sd is hard coded. What if I don't have this device? A non-breaking alternative would be to create a default variable into the method like this:
def play(self, wav_file, loop=False, rootpath="/sd"):
Then the open code "could" be something like:
self.wav = open(roopath.rstrip("/") + "/" + wav_file.lstrip("/"), "rb")
This way it shouldn't break existing code, but allows more devices to use the library without modifying underlying code.
Also, the library should probably check if the file exists instead of throwing a cryptic difficult to find and resolve error.
There are HAL differences between the STM32F4 and STM32L4 and STM32H7.
ports/stm32/machine_i2s.c includes certain .h files which eventually resolve to a header file located in the board directory like:
https://github.com/micropython/micropython/blob/d6dc4cb65a222bd05ec2746c37e457c56484e780/ports/stm32/boards/stm32f4xx_hal_conf_base.h
which points at the HAL files which are included from:
https://github.com/micropython/stm32lib/tree/1eebcda2c95d7593c68e1c81f042d23485baab26/STM32H7xx_HAL_Driver/Inc
The HAL API for STM32H7 is different and fails to compile when MICROPY_HW_I2S1 enabled.
mocleiri/tensorflow-micropython-examples#54
../../lib/stm32lib/STM32H7xx_HAL_Driver/Inc/stm32h7xx_hal_i2s.h:229:42: error: overflow in conversion from 'long unsigned int' to 'int8_t' {aka 'signed char'} changes value from '512' to '0' [-Werror=overflow]
229 | #define I2S_DATAFORMAT_32B (SPI_I2SCFGR_DATLEN_1)
| ^
machine_i2s.c:282:16: note: in expansion of macro 'I2S_DATAFORMAT_32B'
282 | return I2S_DATAFORMAT_32B;
| ^~~~~~~~~~~~~~~~~~
machine_i2s.c: In function 'i2s_init':
machine_i2s.c:583:9: warning: implicit declaration of function '__HAL_RCC_PLLI2S_ENABLE' [-Wimplicit-function-declaration]
583 | __HAL_RCC_PLLI2S_ENABLE(); // start I2S clock
| ^~~~~~~~~~~~~~~~~~~~~~~
machine_i2s.c: In function 'machine_i2s_init_helper':
machine_i2s.c:793:9: error: 'I2S_InitTypeDef' has no member named 'ClockSource'
793 | init->ClockSource = I2S_CLOCK_PLL;
| ^~
machine_i2s.c:793:25: error: 'I2S_CLOCK_PLL' undeclared (first use in this function); did you mean 'IS_RCC_PLL'?
793 | init->ClockSource = I2S_CLOCK_PLL;
| ^~~~~~~~~~~~~
| IS_RCC_PLL
machine_i2s.c:793:25: note: each undeclared identifier is reported only once for each function it appears in
cc1: all warnings being treated as errors
I think you should be able to use the $(MCU_SERIES) make file variable to use the h7 HAL when building an H7 board versus the standard HAL when building an f4 or l4.
I'm not very familiar with stm32 so I thought I would file this issue to see if its something that looks straight forward to you.
If its more complicated I'm happy to take a stab at fixing it and file a pull request if I can solve it.
Hi:
Thank you for the examples and the explanations.
One thing that I don't quite understand. What exactly triggers the callback, etc.? Presumably, it must be that there is some pointer in the DMA system but I was wondering how much "space" is available at that point.
If you have you have a DMA buffer of 10000 bytes then the callback is at about 5000 bytes or something like that?
Thank for any insight.
i use esp32 and max98357a play the wav, can i control volume? i just link the GAIN with GND, but i think it's volume too low
I'm hoping to do it without calling .deinit() so that the interface remains active. The following code stops playing if another task sets self.song=None
but it does not stop cleanly, issuing a continuous tone.
async def play(self):
swriter = self.swriter
wav_samples_mv = memoryview(wav_samples)
with open(self.song, "rb") as wav:
_ = wav.seek(44) # advance to first byte of Data section in WAV file
while (num_read := wav.readinto(wav_samples_mv)) and self.song is not None:
I2S.shift(buf=wav_samples_mv[:num_read], bits=16, shift=self.volume)
swriter.write(wav_samples_mv[:num_read])
await swriter.drain()
self.song = None
I'm guessing there's a way to issue swriter.write(silence)
. What is the minimum which would constitute silence?
[EDIT]
In practice filling wav_samples
(currently 15KiB) with zeros and outputting that fixes the problem, but I'd like to understand why this is necessary. As far as I can see the chip (UDA1334) datasheet sheds no light on this.
As an aside you might be interested to hear that asynchronous I2S playback on a Pyboard D SF2W works with my micro-gui library. This with 44.1KHz 16 bit audio and attendant buffers. With the display I'm using the GUI repeatedly blocks for ~40ms.
The old examples - https://github.com/miketeachman/micropython-esp32-i2s-examples show up first when you google for Micropython and I2S.
Can you add a message to them pointing to these new examples and pull request?
Thanks
This project doesn't have a license specified. For others to be able to use it, you need to supply one. Thanks!
Where did you get sd library, my pi pico has not it as module nor as machine.sdcard.
Thanks in advance
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.