STM32 Audio Codec ?

When working on my projects with STM32 MCUs, one common challenge I frequently encounter is the need to produce sound reliably and with sufficient quality. Initially, I relied on PWM to generate pseudo-analog signals to drive speakers. However, not satisfied with the sound quality, eventually I turned my attention to audio codecs that can produce high-quality sound without requiring much computational power. The audio codec is an IC that incorporates DAC, speaker amplifiers, and a Digital Signal Processing Unit. The primary goal of the audio codec is to produce sound based on the streamed digital audio data. It has other many functionalities, including volume control and ramp transitions. 

In this article, I aim to provide comprehensive guidance on generating sound using the STM32F4 Discovery board, which comes with the CS43L22 audio codec. Leveraging STM32CubeMX for peripheral configuration and the HAL API for library development, I will show the configuration of necessary peripherals and the library I developed in detail.

To stream stereo audio, the commonly used interface is I2S (Inter-IC sound, not confused by I2C).  This interface has a Bit clock (SCK), Word Select (WS), Serial Data (SD), and an optional Mater Clock Line (MCLK). Other details of the interface can be found following the article on Wikipedia. In addition, there is another Interface (Usually I2C) to configure the functioning of the audio codec. So, the next part of the article is to explain how to configure these peripherals. 

STM32 I2S and I2C Configuration for CS43L22

As usual, we start our project by configuring the necessary peripherals, I2C, and I2S interface in our case. I2C is needed to control the Audio codec, whereas I2S will allow us to stream audio data.  As you see from the schematic of the board, I2S3 and I2C1 are connected to the audio codec. In addition, we have PD4 that controls the Reset pin of the audio codec. Considering all these connections, we use CubeMx to configure the necessary pins and peripherals.

STM32F4 schematic
STM32F4 schematic

 

I2C1 configuration is quite straightforward. As shown in the Figure below, I just enable it and use default parameters. 

STM32 I2C configuration
STM32 I2C configuration

For I2S, we have to specify some additional parameters. The audio frequency I specified is 48 kHz, Data and Frame format is 16 Bits data on 16 Bits Frame. You can check other configurations in the picture below. Also, it is vital to enable the Master Clock Output.

STM32 I2S Configuration
STM32 I2S Configuration

In addition, we need to enable DMA. In the configuration I specified circular mode, meaning that DMA will keep streaming a buffer continuously. 

STM32 I2S DMA configuration
STM32 I2S DMA configuration

Also, do not forget to enable DMA Interrupts

STM32 I2S DMA Interrupt enable
STM32 I2S DMA Interrupt enable

Finally, as I said, we have a PD4 pin connected to the reset pin of the audio codec. I specify this pin as GPIO Out in the project and give a meaningful name. Later, we will use this pin to enable the audio codec.

Finally, we can save the file to generate the code. 

STM32 CS43L22 Audio Codec HAL library

Once we configured all the peripherals, we can start writing code to initialize the audio codec and stream audio data. However, explaining every detail of the audio codec is time-consuming and cumbersome. Therefore, I attached the header and source files to this article and I am going to go through the main points of the library.

The code snippet below is the header file of the library. There, I defined the I2C address of the audio codec and the addresses of the registers. In addition, we need I2C1 and I2S3 handlers. To access them I defined them again using the extern keyword and redefined them with different names. 

Other than that, we have prototypes of functions and numeric typedef to define different states of the audio codec. 

#ifndef INC_CS43L22_H_
#define INC_CS43L22_H_

#include "main.h"

// I2C address
#define CS43L22_I2C_ADDRESS             0b10010100


// I2C1 and I2S3 handlers
extern I2C_HandleTypeDef hi2c1;
extern I2S_HandleTypeDef hi2s3;

#define CS43L22_I2C             hi2c1
#define CS43L22_I2S             hi2s3

// PD4 Pin connected to the RST pin of the audio codec
#define CS43L22_RST_Pin   CS43L22_RESET_Pin
#define CS43L22_RST_Port  CS43L22_RESET_GPIO_Port
/*
 *
 * Register's addresses
 */
#define CS43l22_ID_REG                  0x01
#define CS43l22_POWCON1_REG               0x02
#define CS43l22_POWCON2_REG               0x04
#define CS43l22_CLKCON_REG              0x05
#define CS43l22_INTERCON1_REG           0x06
#define CS43l22_INTERCON2_REG           0x07
#define CS43l22_PASSA_REG               0x08
#define CS43l22_PASSB_REG               0x09
#define CS43l22_ZC_SR_REG               0x0a
#define CS43l22_GANGCON_REG             0x0c
#define CS43l22_PLAYCON1_REG            0x0d
#define CS43l22_MISCON_REG              0x0e
#define CS43l22_PLAYCON2_REG            0x0f
#define CS43l22_PASSVOLACON_REG         0x14
#define CS43l22_PASSVOLBCON_REG         0x15
#define CS43l22_PCMAVOLCON_REG          0x1a
#define CS43l22_PCMBVOLCON_REG          0x1b
#define CS43l22_BEEPFREQDUR_REG         0x1c
#define CS43l22_BEEPVOL_REG                0x1d
#define CS43l22_BEEPTONE_REG            0x1e
#define CS43l22_TONECON_REG             0x1f
#define CS43l22_MASAVOL_REG             0x20
#define CS43l22_MASBVOL_REG             0x21
#define CS43l22_HEADAVOL_REG             0x22
#define CS43l22_HEADBVOL_REG             0x23
#define CS43l22_SPKAVOL_REG             0x24
#define CS43l22_SPKBVOL_REG             0x25
#define CS43l22_PCMCHSW_REG             0x26
#define CS43l22_LIMCON1_REG             0x27
#define CS43l22_LIMCON2_REG             0x28
#define CS43l22_LIMATTACK_REG             0x29
#define CS43l22_STATUS_REG                 0x2e
#define CS43l22_BATCOMP_REG             0x2f
#define CS43l22_VPBATLEVEL_REG             0x30
#define CS43l22_SPKSTATUS_REG             0x31
#define CS43l22_CHARPUMPFREQREG         0x34

int cs43l22_init();
void cs43l22_deinit();
void cs43l22_start();
int cs43l22_play(int16_t *pbuffer, uint32_t size);
int cs43l22_pause();
int cs43l22_resume();
int cs43l22_stop();
int cs43l22_set_volume(uint8_t volume);
void cs43l22_read_reg(uint8_t reg, uint8_t *reg_value);
int cs43l22_unmute();
int cs43l22_mute();

void AUDIO_I2S_TxCpltCallback(void);
void AUDIO_I2S_TxHalfCpltCallback(void);
typedef enum
{
    PLAY,
    STOP,
    RESUME,
    PAUSE,
    NOTREADY,
    READY
} codec_state;

codec_state get_cs43l22_state();
#endif /* INC_CS43L22_H_ */

The source file of the library is quite long, so I will not copy and paste the whole text into this article. Nevertheless, you can find it below. 

One of the main functions of the library is the initialization function (cs43l22_init) which takes care of preparing the audio codec for producing sound. First, it sets and resets the RST pin (PD4 in our case). Then, it powers off the codec and defines the I2S format followed by volume adjustment. In addition, the library contains functions to mute (cs43l22_mute) and unmute (cs43l22_unmute) the audio codec. Finally, there are functions to play (cs43l22_play), pause (cs43l22_pause), resume (cs43l22_resume), and stop (cs43l22_stop) audio streaming. Please note that it uses I2S DMA mode to do these operations. 

Once you include the header file, we can test the audio codec. We need to call the initialization function and stream some audio data. For that purpose, I created a buffer containing a sine signal.

The code below shows all these steps. It is worth mentioning that once you start playing the sound, it will keep sending data to the audio codec continuously unless you call a function to either stop or pause the data stream. 

#include "cs43l22.h"

// macros to define the sine signal
#define SIN_FREQ       1000
#define SAMPLING_RATE  48000
#define BUFFER_LENGTH  SAMPLING_RATE / SIN_FREQ
int16_t buffer_audio[2 * BUFFER_LENGTH];

int main(void)
{
/*
functions to initialize peripherals 
*/
cs43l22_init();
// sine signal  
for(int i = 0; i < BUFFER_LENGTH;i++)
  {
      buffer_audio[2 * i] = 10000 * sin(2 * 3.14 * SIN_FREQ * i / SAMPLING_RATE);
      buffer_audio[2 * i + 1] = 10000 * sin(2 * 3.14 * SIN_FREQ * i / SAMPLING_RATE);
  }
// playing the sound
cs43l22_play(buffer_audio, 2 * BUFFER_LENGTH);
while(1)
{

}
}

If you follow all steps properly, you should hear a beep from the headphone connected to the audio jack of the board.

Additional STM32 Programming Resources

The video below is the video tutorial of the STM32F4 audio codec. Although, this video tutorial is for the USB Sound Card, the first paper is dedicated to explaining the steps I mentioned in this article. 

If you have any questions and comments, feel free to drop a comment