上一篇講了智慧手錶上音訊系統的架構和應用場景。從本篇開始講具體的,首先講音訊相關的驅動,主要包括IPC(inter-processor communication,核間通訊, 即AP/CP/ADSP之間的通訊)的driver 和audio的driver。首先說明一下,由於codec是我們公司自己設計的,內建在SOC裡,且driver在ADSP(ADSP用的是RTOS)上,driver程式碼不會像Linux ALSA那樣普適。
講驅動之前,先看用了哪些中斷和memory。 用到的中斷主要有IPC中斷和ADMA(Audio DMA)中斷。IPC本質上是中斷加Ring Buffer,用到內建codec以及藍芽通話時,系統就是靠ADMA中斷(這個中斷等間隔來)驅動的,即等間隔的ADMA 中斷驅動音訊系統轉起來,後面具體講。用到的memory主要有5塊,具體如下:1,ITCM(片內,放程式碼)。2,DTCM(片內,放資料)。 3,ADSP與其他core做資料互動用的share memory,這塊memory在SRAM上,對於做資料互動的雙方來說,都要能存取,且雙方都要設成uncacheable。4,存放codec採集到的音訊資料的memory,這塊memory ASIC設計時就是音訊專用的,對audio來說latency很小,不會有其他競爭導致audio 被卡。5,DDR中給audio用的memory,這塊memory屬於片外,在設計時就分配給audio用,既可以放程式碼,也可以放資料,根據需要將其中的部分設成cacheable或者uncacheable。下圖顯示了哪些memory要設成cacheable,哪些要設成uncacheable:
1, IPC driver
IPC就是核間通訊(AP/CP/ADSP之間的通訊),即核間進行資料互動。要進行資料互動,首先得定義好資料格式,讓互動的雙方都能解析。 互動資料包括控制資料(如使能一個stream)和音訊資料。上面說過,IPC本質上就是中斷加Ring Buffer(既然是Ring Buffer,就得有讀寫index)。Ring Buffer及其控制變數(讀寫index等)都在要通訊的兩核都能存取的share memory上(上面說的第3種memory)。如果核間是單向通訊,則只需要一個Ring Buffer。如果是雙向通訊,就需要兩個Ring Buffer。下圖給出了常見的雙向互動的示意圖:
傳送方把資料按照規定的格式組好後放在Ring Buffer內write index開始的地方,同時更新write index,然後給接收方發一箇中斷。接收方收到中斷後進入中斷服務程式,從Ring Buffer內read index開始的地方取指定大小的資料,同時更新read index,然後解析收到的資料,進行下一步的操作。
AP與ADSP之間就是以如上的方式互動控制命令和音訊資料的。此外IPC還有的作用包括輸出ADSP的log到UART以及把要dump的音訊資料從ADSP發給AP儲存成檔案等。由於ADSP的log沒法直接輸出,就需要把log通過IPC發給AP,AP再輸出到UART等。Dump音訊資料是音訊偵錯的一個非常重要的手段。ADSP上也沒法直接dump,只能藉助於IPC發到AP上儲存成檔案。調驅動時首先調的就是AP與ADSP之間的IPC,確保AP和ADSP之間通訊正常,ADSP的log輸出正常,能正常dump音訊資料,在IPC好的基礎上再去調其他的。新手在調IPC時常犯錯的是沒把用到的share memory設成uncacheable,導致有時候通訊正確,有時候通訊不正確,從而花不少時間去調查,有經驗的一看現象就大概知道原因了。
ADSP與CP之間僅僅互動語音資料(只有語音通話時才會用到,語音控制命令相關的都是ADSP與AP之間的互動),就對IPC進行了簡化,用固定的buffer(這塊buffer也是在雙方都能存取的share memory上)代替Ring Buffer,且只用一箇中斷(ADSP給CP發中斷)。示意如下圖:
上面說過語音通話時系統是靠ADMA中斷驅動的。通常10ms一個ADMA中斷,這個中斷驅動音訊系統轉起來。在一個loop裡,ADSP先從上圖中的Play buffer裡取CP放進去的要播放的語音資料,然後從ADSP audio buffer裡取採集到的語音資料,採集到的語音資料經處理(比如重取樣)後送到上圖中的Record Buffer裡,最後給CP發一個IPC中斷。CP收到中斷後先從Record buffer裡取出採集到的資料並做處理,然後把要播放的資料放進Play buffer裡。
2, audio driver
下圖是在上一篇架構(智慧手錶上的音訊(一):架構)中的硬體框圖:
從上圖看出在用內建codec和BT時音訊驅動是有差異的,下面就分兩種case來講。
2.1 內建codec
下圖是內建codec下驅動硬體框圖:
從上圖看出,驅動主要包括兩部分,內建codec和ADMA(audio DMA)。先看內建codec相關的。內建codec內部可分成模擬部分(analog, 包括ADC和DAC)和數位部分(digital, 即DFE(digital front-end,數位前端))。內建codec的驅動主要是設定暫存器:包括設定音訊時鐘、DFE和ADC、DAC等。再看ADMA相關的。既然是DMA,就得有descriptor。下圖是其結構體定義:
byte_counts是指ADMA buffer的大小。這個值定好了也就定好了ADMA中斷的間隔。 以ADMA中斷產生的條件是ADMA buffer為空為例,當ADMA buffer空了就產出一箇中斷。ADMA buffer的大小通常以毫秒計(這樣就可知道ADMA的中斷間隔是多少毫秒),知道了取樣率和通道數,就可算出位元組數。假設ADMA buffer 10ms,48K取樣,雙聲道,則ADMA buffer的大小是1920bytes (10*48*4 = 1920),所以說ADMA buffer的大小定好了也就定好了ADMA中斷的間隔。Src_addr是原地址,dst_addr是目的地址,會把音訊資料從原地址搬到目的地址。在採集方向上,src_addr是硬體地址, dst_addr是adsp裡audio buffer的地址。在播放方向上,src_addr是adsp裡audio buffer的地址硬體地址, dst_addr是硬體地址。next_desc指向下一個descriptor,通常在一個方向上有兩個descriptor,兩個descriptor互指。ADMA的驅動主要包括掛中斷、暫存器設定(上面descriptor裡內容以及中斷產生條件等)以及音訊資料的搬運。在採集方向上音訊資料主要是從硬體地址上搬到adsp的audio buffer裡,在播放方向上音訊資料主要是從adsp的audio buffer裡搬到硬體地址上。ADSP的audio buffer,兩個方向上既可以用不同的兩塊,也可以用相同的一塊。用兩塊時需要兩個ADMA中斷(一個方向上一個),相應的就有兩個中斷服務程式。因為兩個方向上不相關,處理相對簡單,缺點是浪費memory(ADSP上的memory是非常寶貴的)。用一塊時只需要一個ADMA中斷,因為兩個方向上相關,處理就複雜些(處理不好就造成資料踩踏),優點是節省memory。
2.2 藍芽
下圖是藍芽下驅動硬體框圖:
從上圖看出,驅動主要包括兩部分,ASSP(audio SSP(Synchronous Serial Port, 同步序列介面))和ADMA。ADMA跟內建codec的一樣,這裡就不講了。ASSP說白了就是設定是PCM匯流排還是I2S匯流排,設定的內容有音訊取樣的資料長度、取樣方式(上升沿取樣還是下降沿取樣)、對齊方式(左對齊還是右對齊)、影格同步化寬度等。
上面的兩種case,不管哪一種,首先要確保的是配好暫存器後ADMA中斷要有規律的等時長(比如10ms)來,如不是說明暫存器設定有問題,需要再去仔細看datasheet,理解設定的意思。可以在ISR(中斷服務程式)里加個log,如果log等間隔出現,就基本說明ADMA中斷等間隔來了。
先調內建codec下的音訊驅動。中斷正常後先調播放方向上的。往ADSP audio buffer裡寫正弦波,speaker裡放出tone音就說明播放方向上調好了。再調採集方向上的,把ADSP audio buffer裡採集到的資料通過IPC發給AP儲存成檔案,用音訊軟體(如CoolEdit)去聽,跟對著mic說的話一致就說明調好了。由於牽涉到硬體,偵錯過程中需要ASIC、analog和hardware相關人員的支援,比如暫存器是否配對了、某個PIN輸出是否符合期望等,畢竟在硬體上他們更有經驗。BT下的驅動只在藍芽語音通話時才用到,把內建codec下的語音通話調好了再去調BT下的驅動更容易,因為整個鏈路上只需要把內建codec下的驅動換成BT下的驅動,更方便驗證是否調好。BT下的驅動相對內建codec下的,ADMA等做些值的修改,主要調ASSP。ASSP配好暫存器後用示波器量訊號(SCLK/SYNC/DIN/DOUOT等),符合預期就可以了。最終用藍芽打電話,收發方向上聲音都正常就說明調好了。
Audio driver相當於地基,地基好了後就開始蓋樓(開發具體功能)了。從下篇開始講具體的功能,首先講音訊檔播放。