Audio Peripheral Driver Guide
There are many audio codecs available on the market and each requires a driver to work with the audio class. The driver is referred as the Audio Peripheral Driver in the general audio class architecture. The amount of code necessary to port a specific audio codec to the audio class greatly depends on the codec’s complexity.
Micrium does NOT develop audio codec drivers. It is your responsibility to develop the Audio Peripheral Driver for your audio hardware. Micrium provides a template of the Audio Peripheral Driver than can be used to as a starting point for your driver. You can also read the different sections below.
General Information
An Audio Peripheral Driver template containing empty functions is provided. It is located in the folder \Micrium\Software\uC-USB-Device-V4\Class\Audio\Drivers\Template. You can start from it to write your driver.
No particular memory interface is required by the audio peripheral driver model. Therefore, the audio peripheral may use the assistance of a Direct Memory Access (DMA) controller to transfer audio data.
It is highly recommended to use a DMA implementation of the driver as it will offload the CPU and ensure better overall audio performances.
Figure - Typical Audio Codec Interfacing a MCU presents a typical stereo codec and how it interfaces with a microcontroller (MCU).
An audio codec will usually have two interfaces with the microcontroller:
One interface to configure and control the audio codec. This interface could be I2C or SPI for instance.
One interface to transfer audio data. This interface could be stereo audio I 2 S or any other serial data communication protocols.
As shown by the figure above, the audio peripheral driver may have to deal with two different peripherals of the MCU to communicate with the audio codec.
Memory Allocation
Memory allocation in the driver can be simplified by the use of memory allocation functions available from Micrium’s µC/LIB module. µC/LIB’s memory allocation functions provide allocation of memory from dedicated memory space or general purpose heap. The driver may use the pool functionality offered by µC/LIB. Memory pools use fixed-sized blocks that can be dynamically allocated and freed during application execution. Memory pools may be convenient to manage objects needed by the driver. The objects could be for instance data structures mandatory for DMA operations. For more information on using µC/LIB memory allocation functions, consult the µC/LIB documentation.
API
All audio peripheral drivers must declare different instances of the appropriate driver API structure as global variables within the source code. Each API structure is an ordered list of function pointers utilized by the audio class when device hardware services are required. Each structure will encompass some functions belonging to a category: common, Output Terminal, Feature Unit, Mixer Unit, Selector Unit and AudioStreaming (AS) interface. The API structure will then be passed to the appropriate
USBD_Audio_XX_Add()
functions. Theses different API structures offers two possibilities to handle the terminal and unit IDs management within a given codec driver function:
either have one driver function for all terminals or units or AS interfaces. In that case, IDs must be managed.
or one function per terminal or unit or AS. ID passed as argument of the driver function by the audio class can be ignored as there is a one-to-one relation between the function and the terminal or unit or AS.
Sample device driver API structures are shown below.
const USBD_AUDIO_DRV_COMMON_API USBD_Audio_DrvCommonAPI_Template = {
USBD_Audio_DrvInit (1)
};
const USBD_AUDIO_DRV_AC_OT_API USBD_Audio_DrvOT_API_Template = {
USBD_Audio_DrvCtrlOT_CopyProtSet (2)
};
const USBD_AUDIO_DRV_AC_FU_API USBD_Audio_DrvFU_API_Template = {
USBD_Audio_DrvCtrlFU_MuteManage, (3)
USBD_Audio_DrvCtrlFU_VolManage, (4)
USBD_Audio_DrvCtrlFU_BassManage, (5)
USBD_Audio_DrvCtrlFU_MidManage, (6)
USBD_Audio_DrvCtrlFU_TrebleManage, (7)
USBD_Audio_DrvCtrlFU_GraphicEqualizerManage, (8)
USBD_Audio_DrvCtrlFU_AutoGainManage, (9)
USBD_Audio_DrvCtrlFU_DlyManage, (10)
USBD_Audio_DrvCtrlFU_BassBoostManage, (11)
USBD_Audio_DrvCtrlFU_LoudnessManage (12)
};
const USBD_AUDIO_DRV_AC_MU_API USBD_Audio_DrvMU_API_Template = {
USBD_Audio_DrvCtrlMU_CtrlManage (13)
};
const USBD_AUDIO_DRV_AC_SU_API USBD_Audio_DrvSU_API_Template = {
USBD_Audio_DrvCtrlSU_InPinManage (14)
};
const USBD_AUDIO_DRV_AS_API USBD_Audio_DrvAS_API_Template = {
USBD_Audio_DrvAS_SamplingFreqManage, (15)
USBD_Audio_DrvAS_PitchManage, (16)
USBD_Audio_DrvStreamStart, (17)
USBD_Audio_DrvStreamStop, (18)
USBD_Audio_DrvStreamRecordRx, (19)
USBD_Audio_DrvStreamPlaybackTx (20)
};(1) Audio peripherals initialization.
(2) Set Copy Protection Level.
(3) Get or set m ute state.
(4) Get or set volume level.
(5) Get or set bass level.
(6) Get or set middle level.
(7) Get or set treble level.
(8) Get or set graphical equalizer level.
(9) Get or set auto gain state.
(10) Get or set delay value.
(11) Get or set bass boost state.
(12) Get or set loudness state.
(13) Get or set mix status.
(14) Get or set selected Input Pin of a particular Selector Unit.
(15) Get or set sampling frequency.
(16) Get or set pitch state.
(17) Start record or playback stream.
(18) Stop record or playback stream.
(19) Get a ready record buffer from codec.
(20) Provide a ready playback buffer to codec.
The audio peripheral driver functions can be divided into three API groups as shown in the Table - Audio Peripheral Driver API Groups.
Group | Function | Required? | Notes |
|---|---|---|---|
Initialization | Yes |
| |
AudioControl interface | No | Relates to Output Terminal control. | |
Yes | Relates to Feature Unit controls. | ||
Yes | Relates to Feature Unit controls. | ||
No | Relates to Feature Unit controls. | ||
No | Relates to Feature Unit controls. | ||
No | Relates to Feature Unit controls. | ||
No | Relates to Feature Unit controls. | ||
No | Relates to Feature Unit controls. | ||
No | Relates to Feature Unit controls. | ||
No | Relates to Feature Unit controls. | ||
No | Relates to Feature Unit controls. | ||
No | Relates to Mixer Unit control. | ||
No | Relates to Selector Unit control. | ||
AudioStreaming (AS) interface | Yes | Relates to AS endpoint controls. | |
No | Relates to AS endpoint controls. | ||
Yes if at least one record or playback AS interface defined. |
| ||
Yes if at least one record or playback AS interface defined. |
| ||
Yes if at least one record AS interface defined. |
| ||
Yes if at least one playback AS interface defined. |
|
Optional functions can be declared as null pointers if the audio chip does not support the associated functionality.
It is the audio peripheral driver developers’ responsibility to ensure that the required functions listed within the API are properly implemented and that the order of the functions within the API structure is correct.
Audio peripheral driver API function names may not be unique. Name clashes between audio peripheral drivers are avoided by never globally prototyping device driver functions and ensuring that all references to functions within the driver are obtained by pointers within the API structure. The developer may arbitrarily name the functions within the source file so long as the API structure is properly declared. The user application should never need to call API functions. Unless special care is taken, calling device driver functions may lead to unpredictable results due to reentrancy.
The details of each audio peripheral driver API function are described in the Audio Peripheral Driver API Reference.
Using Audio Processing Stream Functions
The audio peripheral driver has access to stream API offered by the Audio Processing module presented in Table - Audio Processing Module Stream API. Basically, this stream API allows the audio peripheral driver to get buffers to transfer audio data to/from an audio codec or any other types of audio chip.
Function | Description |
|---|---|
Gets a buffer from an AS interface pool. | |
Signals to the record task a record buffer is ready. | |
Signals the end of the audio transfer to the playback task. | |
Updates one of the ring buffer queue indexes. |
In order to better understand the use of this stream API, we will consider the typical audio stereo codec configuration shown by Figure - Typical Audio Codec Interfacing a MCU. Moreover, a DMA controller used by the I2S controller will be assumed. Figure - Playback Stream Functions summarizes the use of stream functions for a playback stream. Please refer to Figure - Playback Stream Dataflow as a complement to know what is happening in the Audio Processing module.
(1) The host has opened the stream by selecting the operational AS interface. It then sets the sampling frequency (for instance, 48 kHz). The function
USBD_Audio_DrvCtrlAS_SamplingFreqManage()
will be called for that operation. The sampling frequency is configured by accessing some codec registers. The register access will be accomplished by sending several I2C commands.
(2) Once the playback stream priming is completed within the Audio Processing module, that is a certain number of audio buffers has been accumulated, the function
USBD_Audio_DrvStreamStart
is called. Usually, you may have to enable playback operations within the codec through some registers configuration. Here again, I2C controller will be used. The function
USBD_Audio_PlaybackTxCmpl()
is called by the driver to signal the audio transfer completion. The driver can call USBD_Audio_PlaybackTxCmpl() up to the number of buffers it can queue.
(3) The playback task will receive an AudioStreaming interface handle and will submit to the audio peripheral driver a ready buffer by calling
USBD_Audio_DrvStreamPlaybackTx
. The initial DMA transfer will be prepared with the first ready buffer. Note that the driver should start the initial DMA transfer after accumulating at least two ready buffers. This allows to start a sort of pipeline in which the audio peripheral driver won't wait after the playback task for providing a ready buffer to prepare the next DMA transfer. Once the pipeline is launched, any subsequent call to USBD_Audio_DrvStreamPlaybackTx() should store the ready buffer. Any buffer memory management method may be used to store the ready buffer (for instance, double-buffering, circular buffer, etc.).
Depending on the DMA controller, you may have to configure some registers and/or a DMA descriptor in order to describe the transfer. The DMA controller moves the audio data from the memory to the I 2 S controller. This one will forward the data to the codec that will play audio data to the speaker.
(4) A DMA interrupt will be fired upon transfer completion. An ISR associated to this interrupt is called. This ISR processes the DMA transfer completion by freeing the consumed buffer. For that, the function
USBD_Audio_PlaybackBufFree()
is called. This function updates one of the indexes of the ring buffer queue. The ISR continues by signaling to the playback task that the audio transfer has finished with
USBD_Audio_PlaybackTxCmpl
. Once again, the playback will provide a ready buffer via USBD_Audio_DrvStreamPlaybackTx() as described in step (2). The ISR will get a new ready buffer from its internal buffer storage. A new DMA transfer is prepared and started. If no playback buffer is available from the internal storage, you may have to play a silence buffer to keep the driver in sync with audio class, that is you want to continue receiving DMA transfer completion interrupt to re-signal the audio transfer completion to the playback task. The silence buffer is filled with zeros interpreted by the codec as silence. The silence buffer can be allocated and initialized in the function
USBD_Audio_DrvInit()
.
(5) The host decides to stop the stream. The function
USBD_Audio_DrvStreamStop
is called. You should abort any ongoing DMA transfers. You don't have to call
USBD_Audio_PlaybackBufFree()
to free any aborted buffers nor to free ready buffers stored internally in the driver and not yet processed. The buffers are implicitly freed by the audio class during the ring buffer queue reset. You can also disable the playback operation on the codec if it is needed.