Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Table of Contents

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.

...

Tip

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

Anchor
Figure - Typical Audio Codec Interfacing a MCU
Figure - Typical Audio Codec Interfacing a MCU

Panel
borderWidth0
titleFigure - Typical Audio Codec Interfacing a MCU

Typical Audio Codec Interfacing a MCUImage Added


An audio codec will usually have two interfaces with the microcontroller:

...

Sample device driver API structures are shown below.

Anchor
Listing - Audio Peripheral Driver Interface API
Listing - Audio Peripheral Driver Interface API

Code Block
languagecpp
linenumberstrue
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)
};


Panel

(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 the Table - Audio Peripheral Driver API Groups.

Anchor
Table - Audio Peripheral Driver API Groups
Table - Audio Peripheral Driver API Groups

Panel
borderWidth0
titleTable - Audio Peripheral Driver API Groups


GroupFunctionRequired?Notes
Initialization USBD_Audio_DrvInit Yes
AudioControl interface USBD_Audio_DrvCtrlOT_CopyProtSet() NoRelates to Output Terminal control.
USBD_Audio_DrvCtrlFU_MuteManage YesRelates to Feature Unit controls.
USBD_Audio_DrvCtrlFU_VolManage() YesRelates to Feature Unit controls.
USBD_Audio_DrvCtrlFU_BassManage() NoRelates to Feature Unit controls.
USBD_Audio_DrvCtrlFU_MidManage() NoRelates to Feature Unit controls.
USBD_Audio_DrvCtrlFU_TrebleManage() NoRelates to Feature Unit controls.
USBD_Audio_DrvCtrlFU_GraphicEqualizerManage() NoRelates to Feature Unit controls.
USBD_Audio_DrvCtrlFU_AutoGainManage NoRelates to Feature Unit controls.
USBD_Audio_DrvCtrlFU_DlyManage() NoRelates to Feature Unit controls.
USBD_Audio_DrvCtrlFU_BassBoostManage NoRelates to Feature Unit controls.
USBD_Audio_DrvCtrlFU_LoudnessManage NoRelates to Feature Unit controls.
USBD_Audio_DrvCtrlMU_CtrlManage() NoRelates to Mixer Unit control.
USBD_Audio_DrvCtrlSU_InPinManage NoRelates to Selector Unit control.
AudioStreaming (AS) interface USBD_Audio_DrvAS_SamplingFreqManage YesRelates to AS endpoint controls.
USBD_Audio_DrvAS_PitchManage NoRelates to AS endpoint controls.
USBD_Audio_DrvStreamStart Yes if at least one record or playback AS interface defined.
USBD_Audio_DrvStreamStop Yes if at least one record or playback AS interface defined.
USBD_Audio_DrvStreamRecordRx Yes if at least one record AS interface defined.
USBD_Audio_DrvStreamPlaybackTx 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. 

...

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.

Anchor
Table - Audio Processing Module Stream API
Table - Audio Processing Module Stream API

Panel
borderWidth0
titleTable - Audio Processing Module Stream API


FunctionDescription
USBD_Audio_RecordBufGet Gets a buffer from an AS interface pool.
USBD_Audio_RecordRxCmpl Signals to the record task a record buffer is ready.
USBD_Audio_PlaybackTxCmpl Signals the end of the audio transfer to the playback task.
USBD_Audio_PlaybackBufFree 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.

Anchor
Figure - Playback Stream Functions
Figure - Playback Stream Functions

Panel
borderWidth0
titleFigure - Playback Stream Functions

Image Added


Panel
bgColor#f0f0f0

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

Tip

The audio peripheral driver should support at least the double-buffering to optimize the playback stream processing.

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

Tip

The DMA implementation in this example processes the playback buffers one after the other using a single interrupt. Depending on your DMA controller, it may be possible to optimize the performance by using several DMA channels. In that case, you could pipeline the DMA transfers. The DMA controller may also offer to link DMA descriptors. In that case, you could get several ready buffers and link several DMA descriptors.

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


Figure - Record Stream Functions summarizes the use of stream functions for a record stream. Please refer to  as to Figure - Record Stream Dataflow as a complement to know that is happening in the Audio Processing module.

Anchor
Figure - Record Stream Functions
Figure - Record Stream Functions

Panel
borderWidth0
titleFigure - Record Stream Functions

Image Added


Panel
bgColor#f0f0f0

(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) The Audio Processing will call the function USBD_Audio_DrvStreamStart() to start record operations on the codec side. Operations consists in enabling record operations within the codec through some registers configuration. The I2C controller will be used for that. Then, the function USBD_Audio_RecordBufGet()   is called by the driver to obtain an empty buffer. This function also specifies the buffer length. The driver does not have to figure out how many bytes is needed depending on the sampling frequency the number of channels and the bit resolution. This is taken into account by the Audio Processing layer. For sampling frequencies such 22.050 kHz, 44.1 kHz not producing an integer number of audio samples per milliseconds, a data rate adjustment is used (refer to Record Stream for more details about this data rate adjustment). With all the buffer's information, you should prepare the initial DMA read transfer. 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 I2S controller to the memory.

Tip

If the DMA offers multiple channels or is able to link several DMA descriptors, you can call USBD_Audio_RecordBufGet() to obtain several buffers.

(3) 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 signaling to the record task that a buffer is ready with the function USBD_Audio_RecordRxCmpl . The ready buffer should be stored in an internal buffer storage. Any buffer memory management method may be used to store the ready buffer (for instance, double-buffering, circular buffer, etc.). The ISR continues by getting a new empty buffer with USBD_Audio_RecordBufGet() . A new DMA transfer is prepared and started. If no empty record buffer is available after calling USBD_Audio_RecordBufGet(), that is a null pointer is returned, you may have to get some record data using a dummy buffer to keep the driver in sync with audio class, that is you want to continue receiving DMA transfer completion interrupt to re-attempt to get an empty buffer. The record data stored in the dummy buffer is basically lost. The dummy buffer can be allocated in the function USBD_Audio_DrvInit() .

Tip

The audio peripheral driver should support at least the double-buffering to optimize the record stream processing.


Tip

The DMA implementation in this example processes the record buffers one after the other using a single interrupt. Depending on your DMA controller, it may be possible to optimize the performance by using several DMA channels. In that case, you could pipeline the DMA transfers. The DMA controller may also offer to link DMA descriptors. In that case, you could obtain several empty record buffers with USBD_Audio_RecordBufGet() and link several DMA descriptors.

(4) Upon reception of the signal, the record task will call the function USBD_Audio_DrvStreamRecordRx . It will get a ready buffer from the driver's internal buffer storage and submit it to the USB side.

(5) The host decides to stop the stream. The function USBD_Audio_DrvStreamStop()  is called. You should abort any ongoing DMA transfers. You can also disable the record operation on the codec if it is needed.


Statistics

As described in the section Audio Statistics, the audio class offered some stream statistics that may be useful during your development. An audio statistics structure ( USBD_AUDIO_STAT ) specific to each AS interface can be retrieved by the application and consulted during debugging.  describes  Table - USBD_AUDIO_STAT Structure Fields Description describes all the fields of  USBD_AUDIO_STAT. Among them, there are four interesting for the driver:

...

You can use the macro  AUDIO_DRV_STAT_INC() by specifying an AS handle and the name of the field to update statistics. Listing - AUDIO_DRV_STAT_INC() Usage shows an example of AUDIO_DRV_STAT_INC() usage.

Anchor
Listing - AUDIO_DRV_STAT_INC() Usage
Listing - AUDIO_DRV_STAT_INC() Usage

Code Block
languagecpp
linenumberstrue
 static  void  Streaming_I2S_DMA_ISR_Handler (CPU_INT08U  ch)
{
    USBD_AUDIO_DRV_DATA  *p_drv_data;
    CPU_INT08U           *p_buf;
    CPU_INT16U            buf_len;
 
 
    p_drv_data =  AudioDrvDataPtr;

    ...
    if (DMA write interrupt) {
    
        ...
        p_buf = Codec_PlaybackCircularBufGet(p_drv_data,
                                            &buf_len);
        if (p_buf != (CPU_INT08U *)0) {
                                                                (1)
            USBD_AUDIO_DRV_STAT_INC(DMA_AsHandleTbl[ch], AudioDrv_Playback_DMA_NbrXferCmpl);
            
            /* $$$$ Prepare a DMA transfer. */
        } else {
            /* $$$$ Prepare a DMA transfer to play a silence buffer. */
                                                                (2)
            USBD_AUDIO_DRV_STAT_INC(DMA_AsHandleTbl[ch], AudioDrv_Playback_DMA_NbrSilenceBuf);
        }
        ...
    }
    if (DMA write interrupt) {
    
        ...
        p_buf = (CPU_INT08U *)USBD_Audio_RecordBufGet(DMA_AsHandleTbl[ch],
                                                     &buf_len);
        if (p_buf != (CPU_INT08U *)0) {
                                                                (3)
            USBD_AUDIO_DRV_STAT_INC(DMA_AsHandleTbl[ch], AudioDrv_Record_DMA_NbrXferCmpl);
            
            /* $$$$ Prepare a DMA transfer. */
        } else {
            /* $$$$ Prepare a DMA transfer with the dummy record buffer to keep in sync with audio class. */
                                                                (4)        
            USBD_AUDIO_DRV_STAT_INC(DMA_AsHandleTbl[ch], AudioDrv_Record_DMA_NbrDummyBuf);
        }
        ...
    }
    ...
}


Panel
bgColor#f0f0f0

(1) You can count a playback DMA transfer completed once you receive the interrupt indicating transfer completion. You can increase the counter AudioDrv_Playback_DMA_NbrXferCmpl if a new playback buffer has been successfully obtained as shown or you could increase it before getting a ready buffer from the internal storage. In that case, you will also count DMA transfers using the silence buffer.

(2) If no buffer is available, you may have to play a silence buffer to keep in sync with the audio class. In that case, increase the counter AudioDrv_Playback_DMA_NbrSilenceBuf .

(3) You can count a record DMA transfer completed once you receive the interrupt indicating transfer completion. You can increase the counter AudioDrv_Record_DMA_NbrXferCmpl if a new empty record buffer has been successfully obtained as shown or you could increase it before the function USBD_Audio_RecordBufGet() . In that case, you will also count DMA transfer using the dummy buffer.

(4) If no empty buffer is available, you may have to use a dummy buffer to get record data and to keep in sync with the audio class. In that case, increase the counter AudioDrv_Record_DMA_NbrDummyBuf counter.