1 前言
本文將基於STM32F4 Discovery板,從零開始設計並實現一個USB Audio的例子。
2 設計構思
所謂的USB AUDIO就是制作一個盒子,這個盒子可以通過USB連接到PC,PC端將其識別為Audio設備,然后在PC端播放音樂的時候,聲音可以通過盒子播放出來。
2.1 從原理框圖開始

如上圖所示,我們大概構思一下,為了實現USB AUDIO功能,我們使用一個MCU的USB外設連接PC端,整個流程是這樣: PC端播放音樂時,代表音樂的數據流從PC端通過USB傳輸到MCU端,MCU端然后將其轉發給一個外部Codec,最后通過Codec上連接的揚聲器或耳機播放音樂。
2.2 硬件支撐
這里選擇ST官方的STM32F4-DISCOVERY板來實現,之所以選擇這塊板子,就是因為其上有USB接口和Codec,正好符合我們設計的要求。
2.2.1 USB接口
如下圖為USB接口部分的電路:

這個一個將USB作為OTG的電路設計,在本設計中,我們只是將USB作為device來使用,因此,上圖我們關注下面部分就可以了。在本設計中,我們使用到全速USB,從上圖可以看出D+與D-引腳分別為PA12,PA11。
2.2.2 Codec部分
如下圖所示:

圖3
如上圖所示,這里的Codec為具體型號為CS43L22,MCU通過I2C接口(PB9,PB6)連接Codec,作為其控制接口,使用I2S(PC7,PC10,PC12,PA4)作為數據通道,此外,MCU使用PD4這個IO管腳控制Codec的reset。CS43L22的14,15腳連接到外面的耳機插孔,也就是說,我們可以通過插入耳機線的方式來收聽PC端播放的聲音。
2.3 軟件設計
為了簡化開發流程,這里使用CubeMx自動生成代碼工具來生成初始化代碼,首先基於Cube庫架構以及USB協議棧的特點,我們得先設計一個合理的軟件框架。

圖4
如上圖,藍色表示的模塊為標准模塊,不需要我們去修改它,將由CubeMx自動生成,而綠色部分則可能涉及到需要修改,其中BSP部分是需要自己添加的代碼,其他的都是由CubeMx生成。
各個模塊的工作流程如下設計:
- 初始化流程: 由main開始,它首先對將使用到的外設I2C,I2S初始化,這最終將調到HAL MSP底層部分實現對具體IO管腳和外設的初始化。同時main使用usb description的數據通過調用USB棧初始化接口來完成對USB接口的初始化,這一步還涉及到USB的枚舉過程。
- USB數據傳輸過程:PC端軟件在播放音樂后,通過USB通道向MCU傳輸音頻數據,音頻數據到達MCU時,首先觸發USB中斷,然后進入到HAL driver層,在回調到 usb conf模塊,接着進入到usb core,usb core再轉給usb audio class,最后音頻數據到達usb audio interface模塊,到達這里,就只剩下對音頻數據進行處理了。Usb audio interface模塊是一個數據接收到數據處理的一個中間對接模塊。
- USB音頻數據處理過程: usb audio interface 模塊將從USB stack底層傳上來的音頻數據轉發給Codec組件,最終通過Codec組件連接的耳機播放出來。
從以上的音頻數據流程來看,最主要的就是usbaudio interface模塊,它實現了從USB audio stack到codec驅動的對接。
接下來,我們來看看軟件層面上的實現。
3 軟件實現
還是老辦法,采用CubeMx這個工具來生成初始化代碼,這樣可以節省我們花費在基本外設上的調試初始參數時間。
3.1 創建CubeMx工程
由於我們使用到的硬件平台是STM32F4Discovery板,上面搭載的MCU型號是STM32F407VGT6,我們就以此型號創建一個名為Audio_Test的工程。
pinout:
外設有用到USB_OTG_FS(PA11,PA12device模式),I2C1(PB6,PB9),I2S3(PC12,PA4,PC10,PC7,半雙工主模式),此外Codec的reset使用PD4管腳控制,使用外部8M HSE。其pinout如下圖所示:

圖5
Clock configuration:

圖6
時鍾樹如上設置,主頻使用168M,I2S時鍾輸出初始化為96M。
Configuration:
- HAL層:
Usb_FS:使用默認參數。
I2C:100K速率,7位地址寬度,使用默認參數。
I2S:主發模式,標准16位寬,默認音頻為48K,如下圖:
並為I2S發送添加DMA,半字位寬:

圖8
- MiddleWares:
USB選擇Audiodevice class,其配置參數如下:

圖9
這里都是默認參數。

圖10
在描述符參數內得為usb audio class修改兩個參數:
- PID得修改為0x5730(否則windows驅動會加載出錯)
- 序列號:序列號字符串內不能包含字母,只能是數據(否則windowsaudio驅動在枚舉后也不會將音頻數據傳輸下來)。
最后修改工程設置,將堆大小設為4K,棧大小設為1K,如下圖:

圖11
如此就可以生成工程了,我們生成IAR工程。
3.2 生成的IAR工程介紹

圖12
如上圖所示,生成的IAR工程,主要有User,Drivers,Middleware3個目錄。
- User目錄下為用戶源碼文件,用戶的主要修改也將集中在此目錄下,在這里,我們的主要工作是集中在usbd_audio_if.c文件,它對應着之前軟件框圖中的usbaudio interface模塊,主要是實現USB audio協議棧與Codec的對接。其他源文件都保持不變就可以了。
- Middlewares目錄對應着usb audio stack模塊,它由CubeMx自動生成,保持原樣就可以,不需要任何修改。
- Drivers目錄對應着HAL層,它包含CMSIS,HAL驅動。
3.3 開發
3.3.1 初次編譯測試
首先我們不做任何修改,先編譯一下工程,發現能順利編譯通過,並燒錄進STM32F4DISCOVERY板,運行后通過USB連接上電腦,發現在設備管理器中能正常識別到這個USB AUDIO設備,如下圖所示:

圖13
這說明,USB與PC端的連接是OK的,但不知道具體有沒有數據。我們使用USB分析儀TOTAL PHASE USB480這個設備對USB總線進行數據監控,能夠正常采集USB枚舉過程和播放音樂的通信數據,如下圖所示:

圖14
這表明,到目前為止,從PC端到USB端都是能正常工作的,從PC端發送過來的音頻數據已經到達usb audio interface模塊,目前只不過還沒有對這些數據進行處理,顯然,接下來的工作,我們就需要將這些音頻數據通過codec驅動發送出去,最終到達外部組件CS32L22.
3.3.2 添加codec驅動和audio bsp模塊
我們已經知道,我們需要為audio添加BSP模塊,在這里,我們將BSP歸屬於drivers類,因此,在drivers目錄下添加BSP目錄,通過之前的軟件架構圖我們可以知道,BSP包含Codec驅動(CS43L22)和Audio bsp模塊,因此,我們在BSP目錄下有添加了Codec的驅動源碼cs43l22.c與bsp_audio.c,如下圖所示:

圖15
其中cs43l22.c為codec cs32l22的驅動,我們可以從ST的組件驅動中找到它,並copy過來直接使用,不需要修改任何代碼。而bsp_audio.c是我們自己寫的,它的任務是為usbd_audio_if.c與cs43l22.c提供服務,讓這兩個模塊勝利對接。
3.3.2.1 Codec與HAL的對接
首先我們來看Codec驅動文件cs43l22.c源文件,這個文件需要使用這個外部需要提供的接口:
- AUDIO_IO_Init()
- AUDIO_IO_DeInit()
- AUDIO_IO_Write()
- AUDIO_IO_Read()
這個都是Codec的基本控制接口,是通過I2C來控制的。都是需要用戶在驅動外部來提供這些接口給到驅動,於是,我們在bsp_audio.c文件中來提供這個接口的實現:
- //---------------------for c43l22 port--------------------------//
- static void I2Cx_Error(uint8_t Addr)
- {
- /* De-initialize the IOE comunication BUS */
- HAL_I2C_DeInit(&hi2c1);
- /* Re-Initiaize the IOE comunication BUS */
- //I2Cx_Init();
- //MX_I2C1_Init();
- }
- static void CODEC_Reset(void)
- {
- HAL_GPIO_WritePin(AUDIO_RESET_GPIO_Port, AUDIO_RESET_Pin, GPIO_PIN_RESET);
- HAL_Delay(5);
- HAL_GPIO_WritePin(AUDIO_RESET_GPIO_Port, AUDIO_RESET_Pin, GPIO_PIN_SET);
- HAL_Delay(5);
- }
- void AUDIO_IO_Init(void)
- {
- //I2Cx_Init();
- }
- void AUDIO_IO_DeInit(void)
- {
- }
- /**
- * @brief Writes a single data.
- * @param Addr: I2C address
- * @param Reg: Reg address
- * @param Value: Data to be written
- */
- static void I2Cx_Write(uint8_t Addr, uint8_t Reg, uint8_t Value)
- {
- HAL_StatusTypeDef status = HAL_OK;
- status = HAL_I2C_Mem_Write(&hi2c1, Addr, (uint16_t)Reg, I2C_MEMADD_SIZE_8BIT, &Value, 1, I2C_TIMEOUT);
- /* Check the communication status */
- if(status != HAL_OK)
- {
- /* I2C error occured */
- I2Cx_Error(Addr);
- }
- }
- void AUDIO_IO_Write(uint8_t Addr, uint8_t Reg, uint8_t Value)
- {
- I2Cx_Write(Addr, Reg, Value);
- }
- /**
- * @brief Reads a single data.
- * @param Addr: I2C address
- * @param Reg: Reg address
- * @retval Data to be read
- */
- static uint8_t I2Cx_Read(uint8_t Addr, uint8_t Reg)
- {
- HAL_StatusTypeDef status = HAL_OK;
- uint8_t Value = 0;
- status = HAL_I2C_Mem_Read(&hi2c1, Addr, Reg, I2C_MEMADD_SIZE_8BIT, &Value, 1, I2C_TIMEOUT);
- /* Check the communication status */
- if(status != HAL_OK)
- {
- /* Execute user timeout callback */
- I2Cx_Error(Addr);
- }
- return Value;
- }
- uint8_t AUDIO_IO_Read(uint8_t Addr, uint8_t Reg)
- {
- return I2Cx_Read(Addr, Reg);
- }
由於在main函數中已經對I2C初始化過了,因此,在AUDIO_IO_Init函數中不需要再次初始化。
就這樣,就完成了Codec驅動與與HAL的對接。
3.3.2.2 usb audiointerface與codec的對接
我們打開usb audio interface源碼文件usbd_audio.if.c文件,此文件由CubeMx自動生成,已經自動給出了一些關於usb audio class的函數,且這些函數體內容都是空白的,毫無疑問,接下來的工作,我們就是要完成這個空白的內容,就好比做填空題一樣,當然,在做這些”填空題”的過程中,我們將使用到Codec驅動提供的接口,這一過程,就是usb audiointerface與codec的對接過程。
按照這一清晰思路,我們首先找到usbd_audio_if.c的一個接口:
- static int8_t AUDIO_Init_FS(uint32_t AudioFreq, uint32_t Volume, uint32_t options)
- {
- /* USER CODE BEGIN 0 */
- return (USBD_OK);
- /* USER CODE END 0 */
- }
這是個空白函數,它是在USB枚舉結束並收到set_configuration消息時會被調用到,我們利用他來實現對codec的初始化。在bsp_audio.c文件中,我們添加一個函數,如下:
- static void I2Sx_Init(uint32_t AudioFreq)
- {
- /* Initialize the haudio_i2s Instance parameter */
- hi2s3.Instance = SPI3;
- /* Disable I2S block */
- __HAL_I2S_DISABLE(&hi2s3);
- hi2s3.Init.Mode = I2S_MODE_MASTER_TX;
- hi2s3.Init.Standard = I2S_STANDARD;
- hi2s3.Init.DataFormat = I2S_DATAFORMAT_16B;
- hi2s3.Init.AudioFreq = AudioFreq;
- hi2s3.Init.CPOL = I2S_CPOL_LOW;
- hi2s3.Init.MCLKOutput = I2S_MCLKOUTPUT_ENABLE;
- if(HAL_I2S_GetState(&hi2s3) == HAL_I2S_STATE_RESET)
- {
- HAL_I2S_MspInit(&hi2s3);
- }
- /* Init the I2S */
- HAL_I2S_Init(&hi2s3);
- }
- const uint32_t I2SFreq[8] = {8000, 11025, 16000, 22050, 32000, 44100, 48000, 96000};
- const uint32_t I2SPLLN[8] = {256, 429, 213, 429, 426, 271, 258, 344};
- const uint32_t I2SPLLR[8] = {5, 4, 4, 4, 4, 6, 3, 1};
- uint8_t BSP_AUDIO_OUT_Init(uint16_t OutputDevice, uint8_t Volume, uint32_t AudioFreq)
- {
- uint32_t deviceid = 0x00;
- uint8_t ret = AUDIO_ERROR;
- uint8_t index = 0, freqindex = 0xFF;
- RCC_PeriphCLKInitTypeDef RCC_ExCLKInitStruct;
- //get the according P,N value and set into config,this is for audio clock provide
- for(index = 0; index < 8; index++)
- {
- if(I2SFreq[index] == AudioFreq)
- {
- freqindex = index;
- }
- }
- HAL_RCCEx_GetPeriphCLKConfig(&RCC_ExCLKInitStruct);
- if(freqindex != 0xFF)
- {
- RCC_ExCLKInitStruct.PeriphClockSelection = RCC_PERIPHCLK_I2S;
- RCC_ExCLKInitStruct.PLLI2S.PLLI2SN = I2SPLLN[freqindex];
- RCC_ExCLKInitStruct.PLLI2S.PLLI2SR = I2SPLLR[freqindex];
- HAL_RCCEx_PeriphCLKConfig(&RCC_ExCLKInitStruct);
- }
- else
- {
- RCC_ExCLKInitStruct.PeriphClockSelection = RCC_PERIPHCLK_I2S;
- RCC_ExCLKInitStruct.PLLI2S.PLLI2SN = 258;
- RCC_ExCLKInitStruct.PLLI2S.PLLI2SR = 3;
- HAL_RCCEx_PeriphCLKConfig(&RCC_ExCLKInitStruct);
- }
- //reset the Codec register
- CODEC_Reset();
- deviceid = cs43l22_drv.ReadID(AUDIO_I2C_ADDRESS);
- if((deviceid & CS43L22_ID_MASK) == CS43L22_ID)
- {
- /* Initialize the audio driver structure */
- audio_drv = &cs43l22_drv;
- ret = AUDIO_OK;
- }
- else
- {
- ret = AUDIO_ERROR;
- }
- if(ret == AUDIO_OK)
- {
- audio_drv->Init(AUDIO_I2C_ADDRESS, OutputDevice, Volume, AudioFreq);
- /* I2S data transfer preparation:
- Prepare the Media to be used for the audio transfer from memory to I2S peripheral */
- /* Configure the I2S peripheral */
- I2Sx_Init(AudioFreq);
- }
- return AUDIO_OK;
- }
在BSP_AUDIO_OUT_Init()這個函數內,根據所傳入的采樣率,程序在預定義的數組內選擇出MCU內部時鍾樹對I2S時鍾的一個合理的分頻值,並設置進時鍾樹配置內,然后再對codec進行初始化,最后對I2S外設初始化。
這個選擇I2S時鍾合理分頻值的過程是根據STM32F407的參考手冊中的建議來做的,如下參考手冊中的28.4.4中表126:

在48K采樣率下,假設時鍾樹下的PLLM VCO=1MHz情況下,且MCK使能,為了盡可能輸出靠近期望的時鍾,此時應該將時鍾樹內的PLL2SN設為258,且PLL2SR設為3,I2S內部的預分頻因子I2SDIV設為3,以及零散因子I2SODD設為1。這個計算公式為:
也就是(1M*258/3)/[(16*2)*((2*3)+1)]=47991.07142857143,差不多48K。
PLL2SN,與PLL2SR的設置在上述代碼中都有所體現,但是,預分頻因子I2SDIV和零散因子I2SODD又是在哪里設置的呢?答案是在代碼調用HAL_I2S_Init()時,在這個HAL接口內部會根據I2S的Audio Frequency(CubeMx中的I2S的Configuration中配置的參數),以及I2S的輸入時鍾頻率和MCK是否使能這些前提條件來自動計算出預分頻因子I2SDIV和零散因子I2SODD的值,以此來盡可能匹配輸出想要的位時鍾,也對應着采樣率48K。
搞懂了這些之后,我們馬上將其代碼進行對接:
- static int8_t AUDIO_Init_FS(uint32_t AudioFreq, uint32_t Volume, uint32_t options)
- {
- /* USER CODE BEGIN 0 */
- BSP_AUDIO_OUT_Init(OUTPUT_DEVICE_AUTO, Volume, AudioFreq);
- return (USBD_OK);
- /* USER CODE END 0 */
- }
接下來下一個需要對接的接口:
- static int8_t AUDIO_DeInit_FS(uint32_t options)
- {
- /* USER CODE BEGIN 1 */
- BSP_AUDIO_OUT_Stop(CODEC_PDWN_SW);
- return (USBD_OK);
- /* USER CODE END 1 */
- }
很明顯,這個個反初始化的接口,它的具體實現如下:
- uint8_t BSP_AUDIO_OUT_Stop(uint32_t Option)
- {
- /* Call the Media layer stop function */
- HAL_I2S_DMAStop(&hi2s3);
- /* Call Audio Codec Stop function */
- if(audio_drv->Stop(AUDIO_I2C_ADDRESS, Option) != 0)
- {
- return AUDIO_ERROR;
- }
- else
- {
- if(Option == CODEC_PDWN_HW)
- {
- /* Wait at least 1ms */
- HAL_Delay(1);
- /* Reset the pin */
- //BSP_IO_WritePin(AUDIO_RESET_PIN, RESET);
- HAL_GPIO_WritePin(AUDIO_RESET_GPIO_Port, AUDIO_RESET_Pin, GPIO_PIN_RESET);
- }
- /* Return AUDIO_OK when all operations are correctly done */
- return AUDIO_OK;
- }
- }
先關閉I2S的DMA,在調用Codec的停止接口。
OK,下一個:- static int8_t AUDIO_AudioCmd_FS (uint8_t* pbuf, uint32_t size, uint8_t cmd)
- {
- /* USER CODE BEGIN 2 */
- switch(cmd)
- {
- case AUDIO_CMD_START:
- BSP_AUDIO_OUT_Play((uint16_t *)pbuf, size);
- break;
- case AUDIO_CMD_PLAY:
- BSP_AUDIO_OUT_ChangeBuffer((uint16_t *)pbuf, size);
- break;
- }
- return (USBD_OK);
- /* USER CODE END 2 */
- }
第一次USB audio stack接收到USB OUT數據時會回調這個接口並傳入AUDIO_CMD_START參數,這里的處理代碼是:
- uint8_t BSP_AUDIO_OUT_Play(uint16_t* pBuffer, uint32_t Size)
- {
- /* Call the audio Codec Play function */
- if(audio_drv->Play(AUDIO_I2C_ADDRESS, pBuffer, Size) != 0)
- {
- return AUDIO_ERROR;
- }
- else
- {
- /* Update the Media layer and enable it for play */
- HAL_I2S_Transmit_DMA(&hi2s3, pBuffer, DMA_MAX(Size/AUDIODATA_SIZE));
- return AUDIO_OK;
- }
- }
很明顯,它是調用Codec驅動處理數據,也就是通過I2S的DMA方式發送給Codec。
然后I2S的DMA會產生傳輸完成中斷和半傳輸完成中斷,在這兩個中斷處理上,會回調到AUDIO_AudioCmd_FS()接口,並且此時傳入的參數變為AUDIO_CMD_PLAY,此時,音頻數據的處理函數為:- void BSP_AUDIO_OUT_ChangeBuffer(uint16_t *pData, uint16_t Size)
- {
- HAL_I2S_Transmit_DMA(&hi2s3, pData, Size);
- }
也是通過I2S的DMA將數據傳輸給外部Codec。
上述過程涉及到另外兩個usbd_audio_if接口函數,即I2S的DMA半傳輸完成和傳輸完成中斷回調,如下所示:- void TransferComplete_CallBack_FS(void)
- {
- /* USER CODE BEGIN 7 */
- USBD_AUDIO_Sync(&hUsbDeviceFS, AUDIO_OFFSET_FULL);
- /* USER CODE END 7 */
- }
- void HalfTransfer_CallBack_FS(void)
- {
- /* USER CODE BEGIN 8 */
- USBD_AUDIO_Sync(&hUsbDeviceFS, AUDIO_OFFSET_HALF);
- /* USER CODE END 8 */
- }
此代碼為CubeMx自動生成,且在自動生成的代碼中就已經調用了usb audio class函數USBD_AUDIO_Sync(),在這里,對於這個,我們是不需要添加任何額外代碼的。之前我們說過,在USBD_AUDIO_Sync()函數內部,會實現對AUDIO_AudioCmd_FS()的回調,目的是,需要及時將數據緩沖中另一半准備好的數據也通過I2S的DMA傳輸給外部Codec,這個不間斷的傳輸,才能實現音頻播放的連貫性。
此外,在USB設備端,后續接收到的音頻數據會緊接着之前的數據進行存放,這里實現了一個數據環形緩沖區,來實現了USB接收端與I2S輸出端數據有效的緩存。
接下來看下一個usbd_audio_if接口函數對接:
- static int8_t AUDIO_VolumeCtl_FS (uint8_t vol)
- {
- /* USER CODE BEGIN 3 */
- BSP_AUDIO_OUT_SetVolume(vol);
- return (USBD_OK);
- /* USER CODE END 3 */
- }
很明顯,這個是音量控制接口,也對接下:
- uint8_t BSP_AUDIO_OUT_SetVolume(uint8_t Volume)
- {
- /* Call the codec volume control function with converted volume value */
- if(audio_drv->SetVolume(AUDIO_I2C_ADDRESS, Volume) != 0)
- {
- return AUDIO_ERROR;
- }
- else
- {
- /* Return AUDIO_OK when all operations are correctly done */
- return AUDIO_OK;
- }
- }
直接調用Codec啟動的相應接口。需要注意地是,實際上,在PC端進行音量的調節,並不會向USB端發送相應的音量調節指令,這里只是象征性的對接下,實際上在USB AUDIO中代碼並不會允許到這里,音量的放大和變小直接體現在音頻數據本身內。
下一個:- static int8_t AUDIO_MuteCtl_FS (uint8_t cmd)
- {
- /* USER CODE BEGIN 4 */
- BSP_AUDIO_OUT_SetMute(cmd);
- return (USBD_OK);
- /* USER CODE END 4 */
- }
靜音控制,其實現為:
- uint8_t BSP_AUDIO_OUT_SetMute(uint32_t Cmd)
- {
- /* Call the Codec Mute function */
- if(audio_drv->SetMute(AUDIO_I2C_ADDRESS, Cmd) != 0)
- {
- return AUDIO_ERROR;
- }
- else
- {
- /* Return AUDIO_OK when all operations are correctly done */
- return AUDIO_OK;
- }
- }
很簡單,直接調用codec驅動的靜音接口。靜音接口與音量控制不同,在PC端進行靜音操作會發送相應的mute指令,進而運行到這里。
OK,就這樣,usbd_audio_if模塊的接口基本上對接到這樣就可以了。
4 測試驗證
將代碼編譯后燒錄進STM32F4DISCVOERY板進行驗證。

最終驗證是OK的,可以從耳機上聽到PC端播放的音樂。
嵌入式學習交流群:561213221
5 結束語
在CubeMx上對中間件USB配置時,將USB audio class的音頻采樣率設置為48K,那個這個參數會再USB枚舉期間會傳遞給windows的audio驅動,在枚舉通過后,后續通過USB傳輸的音頻數據都將是固定以48K采樣率來的,也就是192bytes/ms,也就是說,不管PC端播放什么音樂,windows的audio驅動都會固定以48K采樣率向USB端口進行傳輸。這種特性是由windows的audio驅動決定的。
I2S外設向codec傳輸的時鍾是可以改變的,在本應用中是用不着改變,這個是因為USB端固定以48K采樣率接收數據,那么I2S也可以固定以48K采樣率所對應的速度向Codec傳輸速度,這個特點,正式因為USB audio的固定傳輸特性所決定的。若換成播放本地U盤音頻文件或連接iPhone並播放iPhone的音樂時,則I2S外設的時鍾是根據每次播放的具體音樂所對應的采樣率來配置I2S的時鍾的,這種機制稍微有所不同,這里只需注意下,理解了就可以了。
在本例中,從I2S傳輸數據的速率是48K的采樣率,但實際精度卻是47991.07142857143。這個與標准的48K還是有所偏差的,實際上,無論USB端和I2S端的傳輸速度在理論上有多匹配,在實際上,多少都會存在些偏差,這也就意味着,在USB與I2S這兩個”入口”與”出口”之間的緩存,在隨着時間流逝,如不進行任何處理,這個緩存理論上一定會爆掉或掏空。那么這里就需要針對這個緩存這種現象的一種處理,或者叫做算法,算法的好壞在一定程度上決定了音質的好壞。而本例中,我們使用的是CubeMx生成的默認的最簡單的算法,我們不做深入討論,只是讓大家有這么一個概念即可。