1. 程式人生 > >USB Audio設計與實現

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。這個計算公式為:



圖17

相關推薦

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就會選擇整數集合作為底層實現。 可以保