Thanks to visit codestin.com
Credit goes to github.com

Skip to content

stmhal: I2S and non-blocking DMA transfers #1422

Closed
@blmorris

Description

@blmorris

In the process of developing an I2S driver for uPy I have run into a few issues that I thought might be relevant to a broader discussion of hardware peripheral implementations.

So far with I2S I've managed to muddle along by finding the closest approximations to what I need in the existing code and adapting that, learning both basic C programming and uPy's specific implementation details as I go. At this point I have gotten a bit stuck; as far as I can tell, the next steps in integrating I2S require some techniques that currently don't have any close approximations in the uPy code, I thought that a discussion of these issues may point the way to some implementation enhancements beyond I2S.

As background, the way the existing serial hardware drivers are implemented (broadly referring here to UART, SPI, I2C, and CAN), a buffer gets set up in memory and data gets sent from the buffer out through the peripheral, or is received from the peripheral and stored in the buffer. Even in those cases where DMA is implemented, the transactions are blocking (DMA transactions implement a busy-wait loop); a command doesn't yield control until the transfer has completed successfully or timed out.

I2S will need to work differently. Even the shortest audio tracks - single words, sound effects, etc - will be a substantial fraction of a second in length. A sentence is a few seconds, a music track will usually be a few minutes, and there is nothing to preclude a .wav file from being a few GB in size and running for several hours. A 16-bit stereo track at 44.1kHz (CD-quality) has a data rate of 176.4 kBytes / sec; 48kHz brings that to 192 kBytes / sec. Even one second of audio far exceeds the available RAM on the pyboard. (Using MP3's does not ameliorate the problem, compressed audio needs to be decompressed before being sent to a codec via I2S.)

ST's HAL provides I2S functions which support polling, interrupts, and DMA, as it does for all other serial peripherals. Within uPy polling simply won't work for I2S; nothing else will get a chance to run when an I2S transaction is running continuously for minutes at a time, and it isn't clear that there would even be an opportunity to get new data in from the SD Card. Likewise the interrupt methods will generate 96,000 interrupts per second for 48kHz stereo simplex - possibly twice that for duplex - and if any interrupts are missed because a gc cycle is in progress then there will be glitches in the audio.

The data transfer requirements of I2S audio can be met with DMA, and ST has provided standalone examples of how to do this using their HAL. It isn't yet clear to me how to integrate these into the uPy framework, mostly because there are techniques used which aren't yet implemented anywhere in uPy.

Currently the peripherals which support DMA (SPI, I2C, and DAC) will initialize DMA immediately before a transaction, transfer the data (receiving into or transmitting from a single buffer), block in a busy-wait loop until the transaction is completed, and then immediately de-initialize DMA for the peripheral.

This won't work for I2S; in order to maintain channel synchronization the DMA should remain active as long as the I2S port is enabled. To ensure glitch-free playback, double-buffering is used: when the Tx / Rx complete interrupt is caught the callback must immediately reinitiate the transfer using another pre-filled buffer, and then proceed to refill the just-used buffer with new data before the second buffer is empty. This must continue as long as the file contains new data. (Note: the wave.py module does the work of opening .wav files and providing the playback parameters and frame data.)

Given that wave file playback can continue for long periods without interruption, it also needs to be non-blocking. In my case, I have a system that will be monitoring the acoustic environment in real time, adjusting the volume and possibly even changing the digital equalization while audio playback is in process.

One of the mechanisms provided by the HAL are Tx/RxCplt and HalfCplt callbacks. For demostration purposes I can use them to print test messages or toggle LED's to show that they are working, but it isn't clear to me if or how they should be integrated into the Python callback functions; none of the callbacks implemented in uPy currently utilize the HAL callbacks.

I know that all of this can be done on the pyboard's processor, and I intend to get it working somehow in uPy. I have a colleague who has made I2S work in a standalone application who can help me with the C implementation details.

I wanted to bring this up in a separate discussion from #1361 because implementation of these features will require significant divergence from anything that currently exists in uPy, and may influence further development of existing peripheral drivers - in particular the possibility of non-blocking SPI and I2C transfers, possibly also extending to DAC and ADC. It would be nice to get some ideas before proceeding, even if it ultimately requires a few iterations to get a consensus on the design.

Metadata

Metadata

Assignees

No one assigned

    Labels

    portsRelates to multiple ports, or a new/proposed port

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions