Bug - I2S communication disrupted by arbitrary function call following I2C initialization.
Discussed in https://github.com/orgs/micropython/discussions/14009
<div type='discussions-op-text'>
<sup>Originally posted by ajbradburn March 3, 2024</sup>
Bug - I2S communication disrupted by arbitrary function call following I2C initialization.
I have discovered that I2S communication is somehow disrupted by initializing I2C, and then calling any function.
A work-around is to do something as simple a call ‘print’ after the arbitrary function call.
Here is an example of a script that demonstrates the bug:
from machine import I2C, Pin, I2S
def emptyFunction():
return
i2c = I2C(0, scl=3, sda=8)
ser = emptyFunction()
print('Safety Print') # CRITICALLY IMPORTANT. This print statement enables I2S to work properly. Some combination of the i2c assignment, and then a function call afterwards makes the I2S sound terrible. Simply printing something resolves the issue… Comment this print call to experience bug.
bclk = Pin(5) # Clock Pin
lrc = Pin(6) # Channel Select Pin
din = Pin(4) # Data Pin
audio_out = I2S(
1,
sck=bclk, ws=lrc, sd=din,
mode=I2S.TX,
bits=16,
format=I2S.MONO,
rate=44100,
ibuf=1024
)
def playAudio(file):
print(file)
if True:
wav_file = file
wav = open(wav_file,'rb')
pos = wav.seek(44)
wav_samples = bytearray(1024)
wav_samples_mv = memoryview(wav_samples)
audio_out.shift
while True:
num_read = wav.readinto(wav_samples_mv)
if num_read == 0:
break
num_written = 0
# Increase the volume.
#I2S.shift(buf=wav_samples_mv, bits=16, shift=1)
while num_written < num_read:
num_written += audio_out.write(wav_samples_mv[num_written:num_read])
wav.close()
playAudio('snake.wav')
audio_out.deinit()
I have tested this with blocking, and non blocking I2S implementation with the same result.
I discovered this while trying to operate a PCA9685 and a Max98357 on an ESP32-S3-WROOM-1 using Micropython v1.22.2 (2024-02-22).
Files can be found here:
https://github.com/ajbradburn/ESP32-PuppetRemote/blob/main/code/sound_test.py
https://github.com/ajbradburn/ESP32-PuppetRemote/blob/main/code/sound_test2.py
https://github.com/ajbradburn/ESP32-PuppetRemote/blob/main/code/snake.wav</div>
stmhal: I2S class for pyb module (RFC)
I am beginning to work on a new class to support I2S (Inter-IC-Sound) in uPy. I am admittedly in over my head but now making some steady progress - using spi.c as a model I have managed to create a skeleton I2S class that - seems to - successfully initialize the SPI2 / I2S2 peripheral in I2S mode with a hard-coded list of pins corresponding to those used on my DSP-amplifier board. It does not yet initialize any clock parameters or implement any data transfer functions.
I would like to get some early feedback because the usage pattern (and therefore the API) for I2S is going to be significantly different from SPI even though they are based on the same peripheral hardware in the STM32F4xx processors, and I would like to avoid implementing anything that is too ugly or awkward to merge into the project.
To illustrate the difference between I2S and SPI usage, here are some tasks that I expect to implement with I2S (all of which we can currently do at some level with firmware developed in C, but uPy will give us more flexibility to develop advanced applications.)
- Read audio data (.wav file or other format) from SD card and send data stream via I2S to audio codec chip, (possibly implementing sample-rate conversion on the way.)
- Record audio data from same codec chip to SD
- Receive data from codec, apply filtering, and concurrently send back out to codec for playback.
- Send test signal to codec for playback and concurrently receive data from a microphone via the codec for room acoustic analysis.
All of these cases imply that the I2S data transfer gets set up and runs indefinitely, unlike the data bursts usually seen in SPI - DMA is probably even more critical here than for SPI. Also, it is important to note that only instances 2 and 3 of the SPI hardware support I2S, so it doesn't neatly map to the X,Y position scheme of the pyboard. On the pyboard, as well as my own board, SPI/I2S3 are all but inaccessible due to conflict with the SD card and LED's. On the other hand, the only common platform for most others to easily use I2S with uPy is the STM32FDISCOVERY board, which has a codec available on I2S3.
My initial proposal for the API would be to initialize I2S with a list of pins rather than an instance or position number (necessary for me because the pins used for I2S on my own board are not the same as the SPI2 pins on the pyboard.) The list would have the form [clock, word_select, tx_pin, rx_pin]. The init function will check that each pin is valid for the function designated by its position, it will check that all pins are members of the same I2S instance (2 or 3) and check that the I2S instance is available and not already in use by an existing SPI object (this last check may not be strictly necessary). The only other required parameter will be whether it is a master or slave, and the clock configuration parameters if it is a master. All other parameters will be defined by kwargs set to some reasonable defaults - probably the ones used by my own project ;-)
The list of pins will implicitly determine whether I2S operates in simplex or duplex mode (if either rx_pin or tx_pin == 0 then that direction is omitted.) Likewise, the position of the pin name in the list determines which of the data pins is an input and which is an output.
I'm still trying to decide whether to pass in a list of pin objects or simply pin descriptor strings (i.e. wether to pass pyb.Pin.cpu.B10 or just 'B10')
I'm going to have more questions along the way so I would hope to keep this ticket open for discussion.
-Bryan