USB Audio設(shè)計(jì)與實(shí)現(xiàn)
1前言
本文將基于STM32F4 Discovery板,從零開始設(shè)計(jì)并實(shí)現(xiàn)一個(gè)USB Audio的例子。
2設(shè)計(jì)構(gòu)思所謂的USB AUDIO就是制作一個(gè)盒子,這個(gè)盒子可以通過USB連接到PC,PC端將其識(shí)別為Audio設(shè)備,然后在PC端播放音樂的時(shí)候,聲音可以通過盒子播放出來。
2.1從原理框圖開始圖1
如上圖所示,我們大概構(gòu)思一下,為了實(shí)現(xiàn)USB%20AUDIO功能,我們使用一個(gè)MCU的USB外設(shè)連接PC端,整個(gè)流程是這樣:%20PC端播放音樂時(shí),代表音樂的數(shù)據(jù)流從PC端通過USB傳輸?shù)組CU端,MCU端然后將其轉(zhuǎn)發(fā)給一個(gè)外部Codec,最后通過Codec上連接的揚(yáng)聲器或耳機(jī)播放音樂。
2.2硬件支撐這里選擇ST官方的STM32F4-DISCOVERY板來實(shí)現(xiàn),之所以選擇這塊板子,就是因?yàn)槠渖嫌蠻SB接口和Codec,正好符合我們?cè)O(shè)計(jì)的要求。
2.2.1%20USB接口如下圖為USB接口部分的電路:
圖2
這個(gè)一個(gè)將USB作為OTG的電路設(shè)計(jì),在本設(shè)計(jì)中,我們只是將USB作為device來使用,因此,上圖我們關(guān)注下面部分就可以了。在本設(shè)計(jì)中,我們使用到全速USB,從上圖可以看出D+與D-引腳分別為PA12,PA11。
如下圖所示:
圖3
如上圖所示,這里的Codec為具體型號(hào)為CS43L22,MCU通過I2C接口(PB9,PB6)連接Codec,作為其控制接口,使用I2S(PC7,PC10,PC12,PA4)作為數(shù)據(jù)通道,此外,MCU使用PD4這個(gè)IO管腳控制Codec的reset。CS43L22的14,15腳連接到外面的耳機(jī)插孔,也就是說,我們可以通過插入耳機(jī)線的方式來收聽PC端播放的聲音。
2.3 軟件設(shè)計(jì)為了簡(jiǎn)化開發(fā)流程,這里使用CubeMx自動(dòng)生成代碼工具來生成初始化代碼,首先基于Cube庫架構(gòu)以及USB協(xié)議棧的特點(diǎn),我們得先設(shè)計(jì)一個(gè)合理的軟件框架。
圖4
如上圖,藍(lán)色表示的模塊為標(biāo)準(zhǔn)模塊,不需要我們?nèi)バ薷乃?,將由CubeMx自動(dòng)生成,而綠色部分則可能涉及到需要修改,其中BSP部分是需要自己添加的代碼,其他的都是由CubeMx生成。
各個(gè)模塊的工作流程如下設(shè)計(jì):
初始化流程: 由main開始,它首先對(duì)將使用到的外設(shè)I2C,I2S初始化,這最終將調(diào)到HAL MSP底層部分實(shí)現(xiàn)對(duì)具體IO管腳和外設(shè)的初始化。同時(shí)main使用usb description的數(shù)據(jù)通過調(diào)用USB棧初始化接口來完成對(duì)USB接口的初始化,這一步還涉及到USB的枚舉過程。
USB數(shù)據(jù)傳輸過程:PC端軟件在播放音樂后,通過USB通道向MCU傳輸音頻數(shù)據(jù),音頻數(shù)據(jù)到達(dá)MCU時(shí),首先觸發(fā)USB中斷,然后進(jìn)入到HAL driver層,在回調(diào)到 usb conf模塊,接著進(jìn)入到usb core,usb core再轉(zhuǎn)給usb audio class,最后音頻數(shù)據(jù)到達(dá)usb audio interface模塊,到達(dá)這里,就只剩下對(duì)音頻數(shù)據(jù)進(jìn)行處理了。Usb audio interface模塊是一個(gè)數(shù)據(jù)接收到數(shù)據(jù)處理的一個(gè)中間對(duì)接模塊。
USB音頻數(shù)據(jù)處理過程: usb audio interface 模塊將從USB stack底層傳上來的音頻數(shù)據(jù)轉(zhuǎn)發(fā)給Codec組件,最終通過Codec組件連接的耳機(jī)播放出來。
從以上的音頻數(shù)據(jù)流程來看,最主要的就是usbaudio interface模塊,它實(shí)現(xiàn)了從USB audio stack到codec驅(qū)動(dòng)的對(duì)接。
接下來,我們來看看軟件層面上的實(shí)現(xiàn)。
還是老辦法,采用CubeMx這個(gè)工具來生成初始化代碼,這樣可以節(jié)省我們花費(fèi)在基本外設(shè)上的調(diào)試初始參數(shù)時(shí)間。
3.1 創(chuàng)建CubeMx工程由于我們使用到的硬件平臺(tái)是STM32F4Discovery板,上面搭載的MCU型號(hào)是STM32F407VGT6,我們就以此型號(hào)創(chuàng)建一個(gè)名為Audio_Test的工程。
pinout:
外設(shè)有用到USB_OTG_FS(PA11,PA12device模式),I2C1(PB6,PB9),I2S3(PC12,PA4,PC10,PC7,半雙工主模式),此外Codec的reset使用PD4管腳控制,使用外部8M HSE。其pinout如下圖所示:
圖5
Clock configuration:
圖6
時(shí)鐘樹如上設(shè)置,主頻使用168M,I2S時(shí)鐘輸出初始化為96M。
Configuration:
HAL層:
Usb_FS:使用默認(rèn)參數(shù)。
I2C:100K速率,7位地址寬度,使用默認(rèn)參數(shù)。
I2S:主發(fā)模式,標(biāo)準(zhǔn)16位寬,默認(rèn)音頻為48K,如下圖:
圖7
并為I2S發(fā)送添加DMA,半字位寬:
圖8
MiddleWares:
USB選擇Audiodevice class,其配置參數(shù)如下:
圖9
這里都是默認(rèn)參數(shù)。
圖10
在描述符參數(shù)內(nèi)得為usb audio class修改兩個(gè)參數(shù):
PID得修改為0x5730(否則windows驅(qū)動(dòng)會(huì)加載出錯(cuò))
序列號(hào):序列號(hào)字符串內(nèi)不能包含字母,只能是數(shù)據(jù)(否則windowsaudio驅(qū)動(dòng)在枚舉后也不會(huì)將音頻數(shù)據(jù)傳輸下來)。
最后修改工程設(shè)置,將堆大小設(shè)為4K,棧大小設(shè)為1K,如下圖:
圖11
如此就可以生成工程了,我們生成IAR工程。
3.2 生成的IAR工程介紹圖12
如上圖所示,生成的IAR工程,主要有User,Drivers,Middleware3個(gè)目錄。
User目錄下為用戶源碼文件,用戶的主要修改也將集中在此目錄下,在這里,我們的主要工作是集中在usbd_audio_if.c文件,它對(duì)應(yīng)著之前軟件框圖中的usbaudio interface模塊,主要是實(shí)現(xiàn)USB audio協(xié)議棧與Codec的對(duì)接。其他源文件都保持不變就可以了。
Middlewares目錄對(duì)應(yīng)著usb audio stack模塊,它由CubeMx自動(dòng)生成,保持原樣就可以,不需要任何修改。
Drivers目錄對(duì)應(yīng)著HAL層,它包含CMSIS,HAL驅(qū)動(dòng)。
首先我們不做任何修改,先編譯一下工程,發(fā)現(xiàn)能順利編譯通過,并燒錄進(jìn)STM32F4DISCOVERY板,運(yùn)行后通過USB連接上電腦,發(fā)現(xiàn)在設(shè)備管理器中能正常識(shí)別到這個(gè)USB AUDIO設(shè)備,如下圖所示:
圖13
這說明,USB與PC端的連接是OK的,但不知道具體有沒有數(shù)據(jù)。我們使用USB分析儀TOTAL PHASE USB480這個(gè)設(shè)備對(duì)USB總線進(jìn)行數(shù)據(jù)監(jiān)控,能夠正常采集USB枚舉過程和播放音樂的通信數(shù)據(jù),如下圖所示:
圖14
這表明,到目前為止,從PC端到USB端都是能正常工作的,從PC端發(fā)送過來的音頻數(shù)據(jù)已經(jīng)到達(dá)usb audio interface模塊,目前只不過還沒有對(duì)這些數(shù)據(jù)進(jìn)行處理,顯然,接下來的工作,我們就需要將這些音頻數(shù)據(jù)通過codec驅(qū)動(dòng)發(fā)送出去,最終到達(dá)外部組件CS32L22.
3.3.2 添加codec驅(qū)動(dòng)和audio bsp模塊我們已經(jīng)知道,我們需要為audio添加BSP模塊,在這里,我們將BSP歸屬于drivers類,因此,在drivers目錄下添加BSP目錄,通過之前的軟件架構(gòu)圖我們可以知道,BSP包含Codec驅(qū)動(dòng)(CS43L22)和Audio bsp模塊,因此,我們?cè)贐SP目錄下有添加了Codec的驅(qū)動(dòng)源碼cs43l22.c與bsp_audio.c,如下圖所示:
圖15
其中cs43l22.c為codec cs32l22的驅(qū)動(dòng),我們可以從ST的組件驅(qū)動(dòng)中找到它,并copy過來直接使用,不需要修改任何代碼。而bsp_audio.c是我們自己寫的,它的任務(wù)是為usbd_audio_if.c與cs43l22.c提供服務(wù),讓這兩個(gè)模塊勝利對(duì)接。
3.3.2.1 Codec與HAL的對(duì)接首先我們來看Codec驅(qū)動(dòng)文件cs43l22.c源文件,這個(gè)文件需要使用這個(gè)外部需要提供的接口:
AUDIO_IO_Init()
AUDIO_IO_DeInit()
AUDIO_IO_Write()
AUDIO_IO_Read()
這個(gè)都是Codec的基本控制接口,是通過I2C來控制的。都是需要用戶在驅(qū)動(dòng)外部來提供這些接口給到驅(qū)動(dòng),于是,我們?cè)赽sp_audio.c文件中來提供這個(gè)接口的實(shí)現(xiàn):
//---------------------forc43l22port--------------------------//
staticvoidI2Cx_Error(uint8_tAddr)
{
/*De-initializetheIOEcomunicationBUS*/
HAL_I2C_DeInit(&hi2c1);
/*Re-InitiaizetheIOEcomunicationBUS*/
//I2Cx_Init();
//MX_I2C1_Init();
}
staticvoidCODEC_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);
}
voidAUDIO_IO_Init(void)
{
//I2Cx_Init();
}
voidAUDIO_IO_DeInit(void)
{
}
/**
*@briefWritesasingledata.
*@paramAddr:I2Caddress
*@paramReg:Regaddress
*@paramValue:Datatobewritten
*/
staticvoidI2Cx_Write(uint8_tAddr,uint8_tReg,uint8_tValue)
{
HAL_StatusTypeDefstatus=HAL_OK;
status=HAL_I2C_Mem_Write(&hi2c1,Addr,(uint16_t)Reg,I2C_MEMADD_SIZE_8BIT,&Value,1,I2C_TIMEOUT);
/*Checkthecommunicationstatus*/
if(status!=HAL_OK)
{
/*I2Cerroroccured*/
I2Cx_Error(Addr);
}
}
voidAUDIO_IO_Write(uint8_tAddr,uint8_tReg,uint8_tValue)
{
I2Cx_Write(Addr,Reg,Value);
}
/**
*@briefReadsasingledata.
*@paramAddr:I2Caddress
*@paramReg:Regaddress
* @retval Data to be read