USB Audio設計與實現
這個一個將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,如下圖:圖7
併為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:
圖16在48K取樣率下,假設時鐘樹下的PLLM VCO=1MHz情況下,且MCK使能,為了儘可能輸出靠近期望的時鐘,此時應該將時鐘樹內的PLL2SN設為258,且PLL2SR設為3,I2S內部的預分頻因子I2SDIV設為3,以及零散因子I2SODD設為1。這個計算公式為:
相關推薦
USB Audio設計與實現
這個一個將USB作為OTG的電路設計,在本設計中,我們只是將USB作為device來使用,因此,上圖我們關注下面部分就可以了。在本設計中,我們使用到全速USB,從上圖可以看出D+與D-引腳分別為PA12,PA11。2.2.2 Codec部分如下圖所示:圖3如上圖所示,這裡的Codec為具體型號為CS43L22
jQuery架構設計與實現(2.1.4版本)
需要 引入 hasclass 8.4 uri and hub 組織 移除 市面上的jQuery書太多了,良莠不齊,看了那麽多總覺得少點什麽 對"幹貨",我不喜歡就事論事的寫代碼,我想把自己所學的知識點,代碼技巧,設計思想,代碼模式能很好的表達出來,所以考慮通過分析jQuer
畢業設計-證券宣傳手機微網站的設計與實現
信息 browser .com 接受 熱點 互聯網 計算機網絡 業務 結構 本文介紹基於.net的證券公司宣傳微網站手機網頁的設計與實現方法。 隨著計算機技術的快速發展,基於Web的計算機網絡金融、證券宣傳或交易網站已成為現代金融理財發展的熱點,B/S(Browser/Se
MVC實戰之排球計分(四)—— View設計與實現
service family 角色 元素 需要 rom 之前 con xsl (view)視圖 視圖是用戶看到並與之交互的界面。對老式的Web應用程序來說,視圖就是由HTML元素組成的界面,在新式的Web應用程序中,HTML依舊在視圖中扮演著重要的角色,但一些新的技術已層出
MVC實戰之排球計分(五)—— Controller的設計與實現
需要 strong 技術 ret web src alt 點擊 cnblogs 控制器 控制器接受用戶的輸入並調用模型和視圖去完成用戶的需求。所以當單擊Web頁面中的超鏈接和發送HTML表單時, 控制器本身不輸出任何東西和做任何處理。它只是接收請求並決定調用哪個模型構件去處
stm32視頻教程分享:心率檢測儀的設計與實現
stm32視頻教程分享:心率檢測儀的設計與實現 STM32系列是基於專為要求高性能、低成本、低功耗的嵌入式應用專門設計的ARM Cortex-M3內核。 本項目主要講述了通過心律傳感器采集我們的心律數據,然後通過串口傳送到上位機中,上位機用Qt
MVC之排球比賽計分程序 ——(四)視圖的設計與實現
元素 role view logs image 技術 size 之前 log (view)視圖 視圖是用戶看到並與之交互的界面。對老式的Web應用程序來說,視圖就是由HTML元素組成的界面,在新式的Web應用程序中,HTML依舊在視圖中扮演著重要的角色,但一些新的技術
MVC之排球比賽計分程序 ——(三)model類的設計與實現
比賽 用戶 count class 包括 result 控制 類的設計 可能 實體類是現實實體在計算機中的表示。它貫穿於整個架構,負擔著在各層次及模塊間傳遞數據的職責。一般來說,實體類可以分為“貧血實體類”和“充血實體類”,前者僅僅保存實體的屬性,而後者還包含一些實體間的關
模型類的設計與實現(四)
介紹 傳遞數據 規則 添加 play using ota 實體類 重要 實體類是現實實體在計算機中的表示。它貫穿於整個架構,負擔著在各層次及模塊間傳遞數據的職責。 一般來說,實體類可以分為“貧血實體類”和“充血實體類”,前者僅僅保存實體的屬性,而後者還包含一些實體間的關系與
jQuery技術內幕:深入解析jQuery架構設計與實現原理
源碼 att root 功能 技術內幕 瀏覽器 sel 緩存 callbacks jQuery源碼(jquery-1.7.1.js)的總體結構:(function( window, undefined ) {// 構造jQuery對象 var jQuery = (fun
軟件設計與實現
一個 基礎 建模 分析 解決 是什麽 哪些 模型 動態 我們寫軟件就是要解決用戶的需求,我麽需要表達和傳遞下面的信息,在“需求分析”階段,我們要搞清楚在問題領域中的現實世界中,都有哪些實體,如何抽象出我們真正的關心的屬性,實體之間的關系是什麽,在這個基礎上,用戶的需求是什麽
自己主動升級系統的設計與實現(續2) -- 添加斷點續傳功能 (附最新源代碼)
blog down 決定 top lin dom itl com 關於 一.緣起 之前已經寫了兩篇關於自己主動升級系統OAUS的設計與實現的文章(第一篇、第二篇)。在為OAUS服務端添加自己主動檢測文件變更的功能(這樣每次部署版本號升級時,能夠節省非常多時間。
隊列順序存儲 - 設計與實現 - API函數
http 出隊 插入 tmp .cpp tdi tree 順序 位置 隊列是一種特殊的線性表 隊列僅在線性表的兩端進行操作 隊頭(Front):取出數據元素的一端 隊尾(Rear):插入數據元素的一端 隊列不同意在中間部位進行操作! queu
IM系統中聊天記錄模塊的設計與實現
人的 dex auto 由於 模型 速度 開發 構造 qlite 看到很多開發IM系統的朋友都想實現聊天記錄存儲和查詢這一不可或缺的功能,這裏我就把自己前段時間為傲瑞通(OrayTalk)開發聊天記錄模塊的經驗分享出來,供需要的朋友參考下。 一.總體設計 1.存儲位置
視頻教程免費分享:嵌入式stm32項目開發之心率檢測儀的設計與實現
視頻教程免費分享:嵌入式stm32項目開發之心率檢測儀的設計與實現 本課程主要基於心率檢測儀的設計與實現講解STM32開發技術,STM32開發板廣泛應用於儀器儀表、家用電器、醫用設備、航空航天、專用設備的智能化管理、機器人及過程控制等領域,完成數據監控、數據處理、數據傳遞等功
《Linux內核設計與實現》讀書筆記(八)- 中斷下半部的處理
sym dmesg 重新編譯 warn dad style lsp 之前 res 在前一章也提到過,之所以中斷會分成上下兩部分,是由於中斷對時限的要求非常高,需要盡快的響應硬件。 主要內容: 中斷下半部處理 實現中斷下半部的機制 總結中斷下半部的實現 中斷實現
《Linux內核設計與實現》讀書筆記(十二)- 內存管理
enable vmalloc 緩沖 turn lean png border 編譯 不一致 內核的內存使用不像用戶空間那樣隨意,內核的內存出現錯誤時也只有靠自己來解決(用戶空間的內存錯誤可以拋給內核來解決)。 所有內核的內存管理必須要簡潔而且高效。 主要內容: 內
《Linux內核設計與實現》讀書筆記(十六)- 頁高速緩存和頁回寫
第一次 源碼 進行 lose 減少 文件緩存 掩碼 recycle 創建 主要內容: 緩存簡介 頁高速緩存 頁回寫 1. 緩存簡介 在編程中,緩存是很常見也很有效的一種提高程序性能的機制。 linux內核也不例外,為了提高I/O性能,也引入了緩存機
Geomystery(幾何迷城)的遊戲引擎設計與實現
isp sum output body ide 信息 orm hid 的人 在這裏介紹Geomystery(幾何迷城)的遊戲引擎設計與實現。 業務邏輯:引擎采用模塊化的MVC(Model模型,View視圖,Controller控制)設計方式,這樣有助於運用多種設計模式
Redis 設計與實現(第六章) -- 整數集合(intset)
相同 spa edi redis cnblogs 保存 空間 數值 一個數 概述 1.intset概述 2.intset實現 3.intset升級 intset概述 整數集合是Redis集合鍵的底層實現之一,當值都為整數時,redis就會選擇整數集合作為底層實現。 可以保