Giter Club home page Giter Club logo

micropython-i2s-examples's Introduction

MicroPython I2S Examples

This repository provides MicroPython example code, showing how to use the I2S protocol with development boards supporting MicroPython. The I2S protocol can be used to play WAV audio files through a speaker or headphone, or to record microphone audio to a WAV file on a SD card.

The examples are supported on 4 ports: stm32, esp32, rp2, and mimxrt.

To use I2S with MicroPython on the Pyboards, ESP32, Raspberry Pi Pico, and mimxrt boards you will need to install a version of MicroPython firmware that supports I2S. For these ports, I2S is supported in the v1.20 release and all nightly builds.

Development Boards Tested

  • stm32 port:
    • Pyboard D SF2W
    • Pyboard V1.1
  • esp32 port:
    • Adafruit Huzzah Feather ESP32 with external SD card
    • Lolin D32 Pro
    • Lolin D32 with external SD card
    • TinyPico with external SD card
  • rp2 port:
    • Raspberry Pi Pico
  • mimxrt port:
    • Teensy 4.0/4.1
    • MIMXRT evaluation boards

I2S Microphone Boards Tested

  • INMP441 microphone module available on ebay, aliexpress, amazon
  • MSM261S4030H0 microphone module available on ebay, aliexpress, amazon
  • Adafruit I2S MEMS Microphone Breakout - SPH0645LM4H. See Workaround note below.

I2S DAC and Amplifier Boards Tested

  • Adafruit I2S 3W Class D Amplifier Breakout - MAX98357A
  • Adafruit I2S Stereo Decoder - UDA1334A Breakout
  • I2S PCM5102 Stereo DAC Decoder available on ebay, aliexpress, amazon
  • Wondom 2 x 30W Class D Audio Amplifier Board & DAC, based on TI TAS5756 device
  • Custom board, based on TI TAS2505 Digital Input Class-D Speaker Amplifier
  • Teensy audio sheild for Teensy 4.x

Quick Start - play an audio tone through ear phones

The easiest way to get started with I2S is playing a pure tone to ear phones using a DAC board such as the I2S UDA1334A breakout board or the I2S PCM5102 Stereo DAC Decoder board. Here are the steps:

  1. Download and program the appropriate firmware that supports the I2S protocol into the MicroPython development board

  2. Load the example code play_tone.py into a text editor, found in the examples folder

  3. Make the following wiring connections using a quality breadboard and jumper wires. Use the GPIO pins that are listed in the example code file. Refer to the section on Hardware Wiring Recommendations below.

    UDA1334A board pin Pyboard V1.1 pin Pyboard D pin ESP32 pin Pico Pin Teensy 4.1 pin
    3V0 3V3 3V3 3V3 3V3 3.3V
    GND GND GND GND GND GND
    BCLK Y6 Y6 32 16 4
    WSEL Y5 Y5 25 17 3
    DIN Y8 Y8 33 18 2
    PCM5102 board pin Pyboard V1.1 pin Pyboard D pin ESP32 pin Pico Pin Teensy 4.1 pin
    VIN 3V3 3V3 3V3 3V3 3.3V
    GND GND GND GND GND GND
    SCK GND GND GND GND GND
    BCK Y6 Y6 32 16 4
    LCK Y5 Y5 25 17 3
    DIN Y8 Y8 33 18 2
  4. Establish a REPL connection to the board

  5. Copy the code from the editor e.g. ctrl-A, ctrl-C

  6. Ctrl-E in the REPL

  7. Paste code into the REPL

  8. Ctrl-D in the REPL to run the code

  9. Result: the tone should play in the ear phones

  10. Try different tone frequencies

MicroPython examples

MicroPython example code is contained in the examples folder. WAV files used in the examples are contained in the wav folder.

Each example file has configuration parameters, marked with

# ======= AUDIO CONFIGURATION =======

and

# ======= I2S CONFIGURATION =======

Each example supports all MicroPython ports that offer I2S. This has the benefit of having fewer example files, but comes at the cost of large example files (e.g. many blocks of code that start with lines like elif os.uname().machine.count("Raspberry"):). Examples can be simplified by removing the blocks of port-specific code that are not needed for a particular development board.

PyBoard GPIO Pins

All Pyboard V1.1 and Pyboard D examples use the following I2S peripheral ID and GPIO pins

I2S ID SCK pin WS pin SD pin
2 Y6 Y5 Y8

To use different GPIO mappings refer to the sections below

ESP32 GPIO Pins

All ESP32 examples use the following I2S peripheral ID and GPIO pins

I2S ID SCK pin WS pin SD pin
0 32 25 33

To use different GPIO mappings refer to the sections below

Raspberry Pi Pico GPIO Pins

All Pico examples use the following I2S peripheral ID and GPIO pins

I2S ID SCK pin WS pin SD pin
0 16 17 18

To use different GPIO mappings refer to the sections below

MIMXRT (Teensy 4.0/4.1) GPIO Pins

All MIMXRT examples are designed for the Teensy 4.0/4.1 boards and use the following I2S peripheral ID and GPIO pins. Note: the Teensy 4.1 is a better choice compared to the Teensy 4.0. The Teensy 4.1 has a built-in SD card slot that can be used with the machine.SDCard class, which offers fast SD card access. The Teensy 4.0 requires an external SD card module, which only works with the slower sdcard.py driver.

For transmitting to a DAC:

I2S ID SCK pin WS pin SD pin
2 4 3 2

For receiving from a microphone:

I2S ID SCK pin WS pin SD pin
1 21 20 8

To use different GPIO mappings refer to the sections below

Easy WAV Player example

The file easy_wav_player.py contains an easy-to-use micropython example for playing WAV files. This example requires an SD card (to store the WAV files). Pyboards have a built in SD card. Some ESP32 development boards have a built-in SD Card, such as the Lolin D32 Pro. Other devices, such as the TinyPico and Raspberry Pi Pico require an external SD card module to be wired in. Additionally, for the Raspberry Pi Pico sdcard.py needs to be copied to the Pico's filesystem to enable SD card support.

Instructions

  1. Wire up the hardware. e.g. connect the I2S playback module to the development board, and connect an external SD Card Module (if needed). See tips on hardware wiring below. The example uses the default GPIO pins outlined above. These can be customized, if needed.
  2. copy file wavplayer.py to the internal flash file system using a command line tool such as ampy or rshell.
  3. copy the WAV file(s) you want to play to an SD card. Plug the SD card into the SD card Module.
  4. configure the file easy_wav_player.py to specify the WAV file(s) to play
  5. copy the file easy_wav_player.py to the internal flash file system using a command line tool such as ampy or rshell.
  6. run easy_wav_player.py by importing the file into the REPL. e.g. import easy_wav_player
  7. try various ways of playing a WAV file, using the pause(), resume(), and stop() methods

MP3 files can be converted to WAV files using online applications such as online-convert

WAV file tag data can be inspected using a downloadable application such as MediaInfo. This application is useful to check the sample rate, stereo versus mono, and sample bit size (16, 24, or 32 bits)

Pyboard GPIO mappings for SCK, WS, SD

On Pyboard devices I2S compatible GPIO pins are mapped to a specific I2S hardware bus. The tables below show this mapping. For example, the GPIO pin "Y6" can only be used with I2S ID=2.

Pyboard D with MicroPython WBUS-DIP28 adapter

I2S ID SCK pin WS pin SD pin
1 X6,W29 X5,W16 Y4
2 Y1,Y6,Y9 Y3,Y5 Y8,W24

Pyboard V1.0/V1.1

I2S ID SCK pin WS pin SD pin
2 Y6,Y9 Y4,Y5 Y8,X22

ESP32 GPIO mappings for SCK, WS, SD

All ESP32 GPIO pins can be used for I2S, with attention to special cases:

  • GPIO34 to GPIO39 are input-only
  • GPIO strapping pins: see note below on using strapping pins

Strapping Pin consideration: The following ESP32 GPIO strapping pins should be used with caution. There is a risk that the state of the attached hardware can affect the boot sequence. When possible, use other GPIO pins.

  • GPIO0 - used to detect boot-mode. Bootloader runs when pin is low during powerup. Internal pull-up resistor.
  • GPIO2 - used to enter serial bootloader. Internal pull-down resistor.
  • GPIO4 - technical reference indicates this is a strapping pin, but usage is not described. Internal pull-down resistor.
  • GPIO5 - used to configure SDIO Slave. Internal pull-up resistor.
  • GPIO12 - used to select flash voltage. Internal pull-down resistor.
  • GPIO15 - used to configure silencing of boot messages. Internal pull-up resistor.

Raspberry Pi Pico GPIO mappings for SCK, WS, SD

All Pico GPIO pins can be used for I2S, with one limitation. The WS pin number must be one greater than the SCK pin number.

MIMXRT GPIO mappings for SCK, WS, SD, MCK

On boards supporting NXP i.MX RT processors I2S compatible GPIO pins are mapped to a specific I2S hardware bus. In addition, GPIO pins are further specified as either Transmit or Receive. The tables below show this mapping for 3 boards. For example, the GPIO pin 4 can be used with I2S ID=2 and transmitting to a DAC. Other I2S pin mapping combinations exist, but are not needed for simple-to-use I2S hardware, such as the INMP441 microphone, or the I2S PCM5102 Stereo DAC Decoder.

Teensy 4.1

For transmitting to a DAC:

I2S ID SCK pin WS pin SD pin MCK pin
1 26,36 27,37 39,7 23
2 4 3 2 33

For receiving from a microphone:

I2S ID SCK pin WS pin SD pin MCK pin
1 21 20 38,8 23

Teensy 4.0

For transmitting to a DAC:

I2S ID SCK pin WS pin SD pin MCK pin
1 26,36 27,37 7 23
2 4 3 2 33

For receiving from a microphone:

I2S ID SCK pin WS pin SD pin MCK pin
1 21 20 8 23

Seeed Arch Mix

For transmitting to a DAC:

I2S ID SCK pin WS pin SD pin MCK pin
1 J4 14 J4 15 J4 13 J4 09

For receiving from a microphone:

I2S ID SCK pin WS pin SD pin MCK pin
1 J4 11 J4 10 J4 12 J4 09

Generating a Master Clock using machine.PWM

Some audio codecs require a Master Clock signal at a typical frequency of 256 * sampling frequency.
Only the mimxrt port supports Master Clock generation in the I2S class. For other ports, the machine.PWM class offers a convenient way to generate the Master Clock signal. Here is some example code showing how to generate a Master Clock for the teensy audio shield.

Hardware Wiring Recommendations

I have found the best audio quality is acheived when:

  1. wires are short
  2. modules are connected with header pins and 10cm long female-female jumpers, OR
  3. solid core 22 AWG wire

headers

jumper

wire_22_awg

The following images show example connections between microcontroller boards and breakout boards. The following colour conventions are used for the signals:

Signal Colour
+3.3V Red
GND Black
SCK White
WS Blue
SD Yellow

UDA1334A DAC board with Pyboard V1.1

Connections made with Female-Female jumpers and header pins

pybv11_uda_jump

Connections made with 22 AWG wire

pybv11_uda_wire

UDA1334A DAC board with Pyboard D

Connections made with Female-Female jumpers and header pins

pybd_uda

UDA1334A DAC board with ESP32

Connections made with Female-Female jumpers and header pins

esp32_uda

INMP441 microphone board with Pyboard V1.1

Connections made with Female-Female jumpers and header pins

pybv11_mic

INMP441 microphone board with Pyboard D

Connections made with Female-Female jumpers and header pins

pybd_mic

INMP441 microphone board with ESP32

Connections made with Female-Female jumpers and header pins

esp32_mic

Projects that use I2S

  1. Micro-gui audio demo
  2. Street Sense
  3. Tensorflow Micropython Examples - Micro-speech

Explaining the I2S protocol with buckets and water

The I2S protocol is different than other protocols such as I2C and SPI. Those protocols are transactional. A controller requests data from a peripheral and waits for a reply. I2S is a streaming protocol. Data flows continuously, ideally without gaps.

It's interesting to use a water and bucket analogy for the MicroPython I2S implementation. Consider writing a DAC using I2S. The internal buffer(ibuf) can be considered as a large bucket of water, with a hole in the bottom that drains the bucket. The water streaming out of the bottom is analogous to the flow of audio samples going into the I2S hardware. That flow must be constant and at a fixed rate. The user facing buffer is like a small bucket that is used to fill the large bucket. In the case of I2S writes, the small bucket is used to transport audio samples from a Wav file "lake" and fill the large bucket (ibuf). Imagine a person using the small bucket to move audio samples from the Wav file to the large bucket; if the large bucket becomes full, the person might go do another task, and come back later to see if there is more room in the large bucket. When they return, if there is space in the large bucket, they will pour some more water (samples) into the large bucket. Initially, the large buffer is empty. Almost immediately after water is poured into the large bucket audio samples stream out of the bottom and sound is heard almost immediately. After the last small bucket is poured into the large bucket it will take some time to drain the large bucket -- sound will be heard for some amount of time after the last small bucket is poured in.

If the person is too slow to refill the large bucket it will run dry and the water flow stops, a condition called "underflow" -- there will be a gap in sound produced.

Does a water analogy help to explain I2S? comments welcome !

bucket_analogy

FAQ

Q: Are there sizing guidelines for the internal buffer (ibuf)?
A: For playback of wave files, a good starting point is to size the ibuf = 2x user buffer size. For example, if the user buffer is 10kB, ibuf could be sized at 20kB. If gaps are detected in the audio playback increasing the size of ibuf may mitigate the gaps. For microphone recordings, ibuf will often need to be much larger. Unlike reads, data writes to most SD cards are not consistent - some writes can take much longer. For example, the average write speed might be 1000kB/s, but some writes may slow to 10kB/s. Having a large ibuf accommodates these periods of slow data writes. Consider starting with ibuf = 4x user buffer size. Increase the ibuf size if gaps are detected in the recording.

Q: How many seconds of audio data is held in the internal buffer (ibuf)?
A: T[seconds] = ibuf-size-in-bytes / sample-rate-in-samples-per-second / num-channels / sample-size-in-bytes
stereo = 2 channels, mono = 1 channel.

Q: Are there sizing guidelines for the user buffer?
A: Smaller sizes will favour efficient use of heap space, but suffer from the inherent inefficiency of more switching between filling and emptying. A larger user buffer size suffers from a longer time of processing the samples or time to fill from a SD card - this longer time may block critical time functions from running. A good starting point is a user buffer of 5kB.

Q: What conditions causes gaps in the sample stream?
A: For writes to a DAC, a gap will happen when the internal buffer is filled at a slower rate than samples being sent to the I2S DAC. This is called underflow. For reads from a microphone, a gap will happen when the internal buffer is emptied at a slower rate than sample data is being read from the microphone. This is called overflow.

Q: Does the MicroPython I2S class support devices that need a MCK signal?
A: Yes, one port currently supports MCK. The mimxrt port of I2S allows a MCK output to be defined. Most devices that are popular with users do not need a MCK signal.

Workaround for Adafruit I2S MEMS Microphone Breakout - SPH0645LM4H

This is a well designed breakout board based on the SPH0645LM4H microphone device. Users need to be aware that the SPH0645LM4H device implements non-standard Philips I2S timing. When used with the ESP32, all audio samples coming from the I2S microphone are shifted to the left by one bit. This increases the sound level by 6dB. More details on this problem are outlined a StreetSense project log.
Workaround: Use the static I2S class method shift() to right shift all samples that are read from the microphone.

Attributions

"Le blues de la vache" by Le Collectif Unifié de la Crécelle is licensed under CC BY-NC-SA 2.1

micropython-i2s-examples's People

Contributors

miketeachman avatar mocleiri avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

micropython-i2s-examples's Issues

ESP32 with 32bitdepth not working

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 =======

Example scripts: Pin names are platform specific

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.

INMP441 sound quality issue with Pico.

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

Playback is problemtic on STM and ESP32

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.

Gain Factor on wav file in wavplayer

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

Examples broken under esp-idf V4.4

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...

format PDM or PCM?

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?

Root location of audio should probably not be hard coded

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.

no change on INMP441 microphone result

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🙏

Having trouble porting play_tone.py to non-blocking

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.

RP2040 + ICS-43432 issue

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!

Add support for stm32 H7 series boards

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.

Sd library

Where did you get sd library, my pi pico has not it as module nor as machine.sdcard.
Thanks in advance

examples doesnt work with PCM5102A.

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.

Shift before writing to speaker

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)

What triggers the callback, etc.?

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.

"ValueError: music-16k-16bits-stereo.wav: not found"

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

stereo on rp2040 using I2S PCM5102 Stereo DAC Decoder

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

play the Audio file from a URL

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?

Is there a way to cancel playback?

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.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.