
最近在NUCLEO-N6這塊板子上把音頻輸入做通了用的是STM32N6的SAI外設配合新一代GPDMA來搬運數據整個過程里踩了不少坑也把N6這顆MCU在音頻采集場景下的脾氣摸了個七七八八。這篇東西不是照抄參考手冊是我實際調通之后沉淀下來的操作記錄覆蓋了方案選型、CubeMX配置、GPDMA初始化、音頻代碼實現和調試排障適合手里有NUCLEO-N6、正準備用SAI做音頻采集的工程師參考。STM32N6這代芯片大家的注意力基本都被Cortex-M55和NPU吸引了這沒錯但音頻采集這種基礎功能用N6做反而非常舒服。主頻800MHzRAM又大GPDMA結構和老F4/F7系列完全不一樣不再受DMA1/DMA2通道綁定外設的約束配起來靈活得多。SAI還是那個SAI穩定、抗造配合GPDMA的循環模式可以做到CPU零干預持續采集音頻數據這對后續做語音識別、聲紋處理、或者簡單的頻譜分析都非常關鍵。1. 項目概述與方案選型1.1 為什么用STM32N6做音頻輸入如果你只是想在MCU上采集個音頻、做做FFT或者跑個關鍵詞識別很多人第一反應還是H7甚至F4。但N6的優勢是實打實的第一主頻800MHz的M55內核跑算法綽綽有余采集進來之后不管做波束成形還是跑NPU推理都不用擔心CPU算力被音頻搬運占掉太多。哪怕PCM數據直接進內存再同時轉發給NPU做處理整個鏈路都搞得定。第二片上RAM最高可以到幾兆字節具體看你用的N6B3還是N6F3系列這意味著音頻緩沖不再捉襟見肘。F4時代那點內存緩沖開大了就爆SRAM只能做處理完就丟N6上你可以直接開一兩秒的環形緩沖后續做事件檢測、聲音回看都方便。第三GPDMA這個外設是真的為高吞吐數據搬運設計的。它不是老DMA那種通道固定、還得分主從模式的結構而是每個通道都可以綁定任意帶DMA請求的外設支持循環模式、鏈表模式、可靠的安全/非安全屬性配置。做音頻采集時一條GPDMA通道在后臺把SAI收下來的數據連續不斷搬到內存里CPU完全不參與只有半buffer滿和全buffer滿的時候觸發一次中斷給個信號。另一個容易被忽略的點是STM32N6自帶安全區和非安全區TrustZone功能。這在音頻采集里也很有用比如你希望把音頻數據放在安全區內存應用代碼跑在非安全區通過安全的API去拿數據那GPCDMA和SAI就得跟GTZC的配置配合好。如果只是普通采集把外設都配成非安全就能省掉很多麻煩。1.2 SAI和GPDMA的組合優勢在哪SAISerial Audio Interface是ST單片機上專門為數字音頻設計的串行接口一根幀同步線FS、一根位時鐘BCLK、一到兩根數據線SD和常用的I2S編解碼器、I2S數字麥克風都能無縫對接。SAI在N6上支持獨立的發送和接收塊A和B可以配置成TDM模式、I2S模式、LSB/MSB對齊模式靈活性非常高。不直接用I2S外設因為STM32N6沒有單獨叫I2S的外設I2S協議其實是靠SAI來模擬的。你在CubeMX里選SAI然后把協議選成I2S standard就行。這個細節如果沒弄清楚找外設列表的時候會懵一圈。GPDMA則是ST這幾年新推的DMA架構相比老一代DMA它有幾個很實用的特性多通道可以自由映射不用擔心SAI接在DMA1還是DMA2上也不用查通道映射表。支持循環模式Circular Mode這是音頻采集的剛需DMA搬完一整個緩沖區自動回繞繼續搬配合半滿/全滿中斷做雙緩沖數據永遠不會斷。支持8/16/32位數據傳輸寬度對齊SAI接收到的音頻幀可以直接按32位左聲道右聲道拼在同一個字里或者16位單聲道/雙聲道16位搬運省去后面拆包的功夫。觸發方式很靈活可以選擇在FIFO達到某個閾值時發起搬運也可以每個數據字都觸發后者非常適合音頻這種等時數據流。所以GPU呢其實我是想說GPDMA SAI 中斷回調這套鏈路做下來比老式SAI DMA FIFO中斷的方案方便得多關鍵是緩沖區管理邏輯非常清晰半滿中斷處理前半段數據全滿中斷處理后半段數據來回交替永遠不丟數據。1.3 整體系統架構整個音頻采集系統的框圖拆開看大概是這樣的音頻信號源接在NUCLEO-N6板卡擴展口上的數字麥克風或音頻編解碼器。我用的是INMP441這種I2S接口的MEMS麥克風模塊供電3.3V信號線只要四根SCK位時鐘、WS幀同步、SD數據、L/R聲道選擇。這比模擬麥克風運放ADC的老方案省太多事。硬件連接SCK接SAI1的SCK引腳WS接SAI1的FS引腳SD接SAI1的SD_A引腳。片上搬運SAI1通過GPDMA通道把音頻數據搬到一個用戶自定義的緩沖區緩沖區開成4字節對齊的uint32_t數組。中斷通知GPDMA半滿/全滿各觸發一次回調在回調里只是置標志位實際處理放主循環或者交給NPU/信號處理模塊。數據處理把采集到的PCM數據從32位幀中拆出有效位做音量計算、存到SD卡或者送給NPU。這樣分層下來每一層都可以單獨調試。信號有沒有、波形對不對、數據有沒有搬進內存、內存里的數據格式是否正常一步一步排查非常方便。2. 硬件連接與引腳配置2.1 NUCLEO-N6板卡資源盤點NUCLEO-N6這塊板子和之前NUCLEO家族的板卡布局基本一致但核心芯片換成了STM32N6。開發板上提供了ST-LINK調試器、三個用戶LED、兩個按鈕、Arduino Uno V3擴展接口以及ST Morpho全引腳擴展排針。做音頻輸入時用得最多的就是Morpho排針因為SAI1的引腳不一定在Arduino接口上全部引出。你需要在CubeMX里看你選的引腳具體映射到哪個位置然后在原理圖上找到對應的排針編號用杜邦線或者飛線連到外部模塊。NUCLEO-N6板卡的板載ST-LINK用的是調試口也和USB轉串口復用這個不影響音頻功能但是調試時很方便可以直接開一個串口打印調試信息。2.2 音頻前端選擇從PDM麥克風到編解碼器音頻輸入的前端方案基本就三類PDM數字麥克風接DFSDM外設或者某些支持PDM解調的SAI。PDM的好處是只傳1-bit數據流由MCU內部做抽取濾波得到PCM數據缺點是PDM解調本身會吃掉一些CPU周期而且DFSDM和SAI在N6上具體支持情況需要查對應型號的勘誤表。I2S接口MEMS麥克風比如INMP441、ICS-43434內部已經做了PDM到PCM的轉換輸出標準的I2S格式PCM數據MCU這邊只需要配置SAI接收即可。我最終選用INMP441接線最簡單而且模塊很便宜幾塊錢一片。音頻編解碼器比如CS42L51、WM8960、TLV320AIC23這類它們通常有ADC和DAC可以錄音也可以放音還帶麥克風前置放大和線路輸入。接Codec的好處是后續可以播放但調試時多一個I2C控制通道需要先配置Codec的寄存器才能讓SAI收到有效數據。如果只是驗證SAI和GPDMA的鏈路通不通強烈建議先拿INMP441這種I2S麥克風省去I2C配置的干擾。等鏈路通了再換Codec做雙向音頻也不遲。2.3 SAI引腳映射與時鐘生成在NUCLEO-N6上我用的是SAI1 Block A信號線定義SAI1_SCK串行位時鐘由SAI1主機產生一般配置成64 * fs也就是每幀64個位時鐘這樣左右聲道各32位16位有效數據也在這個32位槽里左對齊。SAI1_FS幀同步信號在I2S標準協議里一個幀周期是左右各一個聲道FS低電平表示左聲道高電平表示右聲道具體極性可以在CubeMX里翻轉。SAI1_SD_A數據線接收外部麥克風或Codec返回的PCM數據。N6的SAI外部MCK主時鐘輸出可以配置為PLL2或PLL3生成的音頻主時鐘。比如我取fs48kHzMCK頻率配置為512 * fs 24.576MHz這個頻率是廣播音頻里非常標準的主時鐘后續接Codec時也是它想要的工作頻率。需要注意INMP441這種I2S MEMS麥克風不需要MCK它自己從BCLK恢復時鐘但CS42L51這類Codec通常必須要MCK。CubeMX里時鐘樹配置的思路是這樣PLL1跑系統時鐘800MHz這個別亂動。PLL2里留一路給SAI的時鐘源比如PLL2_Q輸出48MHz然后分頻得到24.576MHz的SAE? 不SAI_MCLK就出來了。需要確認N6的SAI時鐘樹SAI_CK PLL2_Q / 分頻系數。分頻后的頻率要小于SAI最高時鐘限制同時能夠被需要的主時鐘頻率整除。具體計算如果你想MCK24.576MHz而PLL2_Q輸出49.152MHz那分頻系數就是2直接得到24.576MHz如果PLL2_Q輸出73.728MHz分頻系數就是3得到24.576MHz。這些都能在CubeMX時鐘樹頁面里實時看到實際頻率配置的時候務必小心紅色的超頻警告看到頁面變黃變紅就要調整。3. CubeMX配置與GPDMA初始化3.1 核心配置步驟我以STM32CubeMX STM32CubeIDE為基礎一步步走一遍配置過程。第一步選擇MCU型號。NUCLEO-N6對應的是STM32N6B3系列或者你用的具體型號在CubeMX的MCU選擇器里輸入STM32N6B3K? 如果找不到選擇STM32N6系列再在Board Selector里選NUCLEO-N6開發板。選中板卡后CubeMX會自動初始化外部時鐘、調試口和LED引腳省去很多配置工作。第二步配置SAI。在Categories左側選擇Multimedia - SAI1勾選Block AMode選擇Master Receiver。為什么是Master Receiver因為你希望N6產生BCLK和FS然后從麥克風/Codec那邊接收數據。如果選Slave模式就需要外部設備提供位時鐘和幀同步一般I2S麥克風都是被動接收時鐘所以N6必須做主機。Block A參數配置參考ProtocolI2S standard實際就是生成I2S時序Data Size32位因為INMP441一個時隙是32位有效數據24位正好按32位讀進來再移位Frame Length32位Frame Sync ActiveHigh level? 實際上INMP441的WS在高電平時是右聲道CubeMX里可以配成FS Active During Slot 0然后再根據實際波形調整。Frame Sync Offset1 bitI2S標準里FS比數據提前一位時鐘這是標準Output DriveEnable保證驅動能力FIFO Threshold默認即可也可以配成Quarter Empty/Full影響不大第三步配置時鐘樹。把Audio Clock Source選到PLL2或PLL3某個輸出然后看頁面上的頻率是否正確。我這邊配PLL2_Q為49.152MHzSAI分頻系數2得到24.576MHz的MCK。48kHz采樣率下MCK512*fs剛好。第四步配置GPDMA。在SAI1的DMA Settings選項卡里Add一個DMA request選擇GPDMA1通道。這里要注意N6里DMA控制器叫做GPDMA1和GPDMA2別找DMA1/DMA2。點擊DMA請求之后配置如下DirectionPeripheral to MemoryModeCircular循環重中之重PriorityHigh音頻實時性要求Increment Address內存地址遞增外設地址固定Data WidthWord32位——和SAI的Data Size對齊還有一個關鍵點GPDMA有個FIFO閾值配置。在內存和FIFO之間搬運時可以選擇什么時候觸發搬運請求。我的配置是FIFO Threshold Full或Half整體效率差不多但如果你發現中斷頻率過高、CPU負載大可以適當調高閾值。第五步配置NVIC。到NVIC Settings選項卡打開SAI1 global interrupt、GPDMA中斷。很多人配完GPDMA忘了使能中斷導致回調永遠不執行。3.2 GPDMA與SAI的握手機制SAI和GPDMA之間的握手和老的DMA有點類似SAI接收FIFO每進來一個或幾個數據字就會向GPDMA發一個請求GPCDMA根據配置的burst大小把數據從FIFO搬到內存。這里要明白一個概念GPDMA是按傳輸粒度工作的。它把一整塊數據搬運看成若干次burst每次burst可以搬4、8、16個數據單元。對音頻而言每次SAI收到一個32位幀就往FIFO里塞一個字GPDMA通過FIFO把數據攢到一定數量后一次性搬走減少對總線的占用。我看到一些工程師在配GPDMA時照抄H7的配置其實沒必要。N6的CubeMX里配置相對簡單你只需要確認Data Width Word32位保證一次搬運正好是一個音頻幀左右聲道各16位或24位拼成一個32位Word。Burst Size 1或者4都行配置成1對時序要求最嚴格配置成4能減少GPDMA觸發次數但對FIFO門限有要求。如果出現音頻數據中間有間隙采到的波形是斷斷續續的很可能是FIFO門限配置過高導致小批量數據沒能及時搬走、FIFO溢出丟字這時可以把門限調低一些。3.3 安全區/非安全區配置對音頻鏈路的影響N6的TrustZone安全功能不是擺設如果你在CubeMX里啟用了TrustZoneTrustZone Enabled那SAI和GPDMA默認可能是安全外設而你的應用程序是Non-Secure的這就導致了無論怎么調DMA數據都進不了Non-Secure內存。我實際遇到的癥狀是GPDMA中斷回調能觸發但是收到的數據緩沖區一直是全0或者說DMA搬運根本沒執行報Bus Error。查了半天最后定位到是GTZC的TZSC配置那里SAI1和GPDMA1被劃到了Secure域而Non-Secure的應用程序無法直接訪問這些安全外設的寄存器DMA請求也就沒法正常路由。解決辦法有兩種方案一如果你不需要 TrustZone就在CubeMX里關閉 TrustZone所有外設默認都是 Non-Secure省心。方案二如果項目確實需要安全隔離就得在 TrustZone 初始化文件里把 SAI1、GPDMA1 以及它們對應的中斷 EXTI 通道都配置為 Non-Secure同時把音頻緩沖區所在的 RAM 區域標記為 Non-Secure然后在 Non-Secure 應用里正常使用。關于中斷這一點很多人會漏。N6的中斷控制器里同一個外設的中斷源有 Secure 和 Non-Secure 兩種觸發路徑。如果你在外設安全屬性已經是 Non-Secure但中斷優先級分組或者 NVIC 里設置不對中斷可能進了 Secure 那邊然后卡死。調試這種問題最簡單的方法是先關閉 TrustZone把功能跑通再逐步打開安全隔離這樣能大幅縮小排查范圍。4. 音頻數據采集代碼實現4.1 初始化流程與HAL函數CubeMX生成工程之后SAI和GPDMA的初始化代碼已經自動生成了核心集中在MX_SAI1_Init()和MX_GPCDMA_Init()函數里。啟動音頻采集不需要像老庫那樣自己寫一整套DMA啟動代碼直接調HAL函數uint32_t audio_buf[AUDIO_BUF_SAMPLES] __attribute__((aligned(4))); HAL_StatusTypeDef ret; ret HAL_SAI_Receive_DMA(hsai1, (uint8_t *)audio_buf, AUDIO_BUF_SAMPLES); if (ret ! HAL_OK) { Error_Handler(); }注意AUDIO_BUF_SAMPLES的單位是樣本數對于32位音頻幀每個樣本就是32位。緩沖區開成uint32_t數組這樣一次中斷觸發的要么是前半段半滿、要么是后半段全滿處理時很方便。HAL_SAI_Receive_DMA內部會把GPCDMA配置成 Circular 模式并且把用戶緩沖區指針寫到DMA目的地址寄存器。啟動后只要SAI有數據進來GPCDMA就會自動搬運不需要CPU干預。4.2 半滿中斷與全滿中斷的雙緩沖機制這一節是整個代碼的核心。GPCDMA在循環模式下搬運到緩沖區中點的時刻會觸發一次傳輸半完成中斷搬運到緩沖區末尾時觸發一次傳輸完成中斷然后地址自動回繞到緩沖區開頭繼續搬。這個機制天然形成了雙緩沖前半段緩沖區正在被GPCDMA寫入后半段緩沖區已經穩定可以處理。后半段緩沖區正在被GPCDMA寫入時前半段緩沖區穩定可以處理。HAL庫里面當然有現成的回調接口你可以自己實現volatile uint8_t buf_half_ready 0; volatile uint8_t buf_full_ready 0; void HAL_SAI_RxHalfCpltCallback(SAI_HandleTypeDef *hsai) { if (hsai-Instance SAI1) { buf_half_ready 1; } } void HAL_SAI_RxCpltCallback(SAI_HandleTypeDef *hsai) { if (hsai-Instance SAI1) { buf_full_ready 1; } }回調里不要做耗時工作只置標志位。中斷里做數據處理有兩個問題一是占用中斷時間會導致中斷響應變慢更糟的是如果GPCDMA正在搬后半段而你又在中斷里訪問了音頻緩沖區并且做大量運算你的運算指令和DMA搬運指令爭搶總線可能導致DMA搬運不及時、數據丟失。主循環里就可以根據標志位來處理數據了while (1) { if (buf_half_ready) { buf_half_ready 0; process_audio(audio_buf[0], AUDIO_BUF_SAMPLES / 2); } if (buf_full_ready) { buf_full_ready 0; process_audio(audio_buf[AUDIO_BUF_SAMPLES / 2], AUDIO_BUF_SAMPLES / 2); } }4.3 緩沖區數據處理從32位幀中提取有效PCM數據INMP441輸出的數據格式是24位有效數據左對齊在32位時隙里低位無意義大致排列如下位31..位824位PCM樣本二進制補碼位7..位0無效位通常為0為了得到干凈的單聲道16位PCM數據需要先右移8位再截取低16位int16_t sample (int16_t)(audio_word 8);這樣取出來的就是可以直接送錄音文件、或者做音量計算的PCM數據。如果你接的是雙聲道Codec那么一個32位字的高16位是左聲道、低16位是右聲道I2S標準下。處理邏輯int16_t left (int16_t)(audio_word 16); int16_t right (int16_t)(audio_word 0xFFFF);有的Codec支持24位有效數據那還需要再多移一位右移8位后只保留高16位或者右移1位得到帶符號24位。關鍵是搞清楚外部設備的輸出格式這個直接決定了數據對不對。我在調試時驗證數據是否正常的方法很簡單采集一段1kHz正弦波把收到的PCM數據導出來在電腦上畫圖或者用FFT看頻譜。如果FFT峰值正好落在1kHz而且沒有明顯諧波說明鏈路完全正常。當然如果只是想在嵌入式里快速驗證也可以算一算這段數據的RMS值——正弦波的RMS應該大約是峰值的0.707倍而且不會是滿幅噪聲。5. 調試驗證與常見問題排查5.1 波形驗證與數據完整性檢查音頻采集最怕的就是有中斷但數據是錯的。我建議按照從信號源到軟件處理的順序一層層檢查第一步用示波器或者邏輯分析儀看SAI1_SCK和SAI1_FS引腳確認位時鐘和幀時鐘存在。如果BCLK或FS根本沒有說明MCK時鐘配置有問題回到CubeMX時鐘樹檢查PLL2/SAI分頻。第二步看SAI1_SD_A引腳有沒有數據。INMP441在靜音環境里輸出的是接近0的PCM數據在示波器上看起來像是接近中間電平的噪聲對著麥克風說話引腳上應該有明顯的幅度變化。如果SD引腳完全沒動靜可能麥克風供電不足或者VDD沒有接好也可能是麥克風的L/R選擇引腳沒配置正確INMP441的L/R引腳接地左聲道輸出接VDD右聲道輸出。第三步看程序里的原始緩沖區。在調試器里把audio_buf數組的hex數據導出來手動檢查幾個樣本是否落在合理范圍。正常語音數據不應該全0也不應該全0xFFFFFF全0xFFFFFF往往表示SAI沒接收到有效信號或者Data Size配置和數據不對齊。第四步算RMS或者峰值。跑一個簡單的處理函數float calc_rms(uint32_t *buf, uint32_t len) { uint64_t sum 0; for (uint32_t i 0; i len; i) { int16_t s (int16_t)(buf[i] 8); sum (uint64_t)(s * s); } return sqrtf((float)sum / len); }對著麥克風說話RMS值會明顯跳變就說明從麥克風到SAI到GPCDMA到內存的整條鏈路都是通的。5.2 常見問題排查表調試過程中我遇到過幾類問題整理成一個速查表按發生頻率從高到低排列現象可能原因解決辦法數據全0或者全0xFFFFFFFFSAI接收沒有數據或者GPDMA數據寬度/地址遞增配置錯誤先確認SCK/FS引腳波形檢查DMA的Data Width和Increment Address用HAL_SAI_Receive_IT先試試中斷模式能否收到數據GPDMA中斷不觸發NVIC未使能GPDMA中斷GPDMA請求沒接到SAI外設安全屬性與內存安全屬性不匹配檢查CubeMX里NVIC使能確認GPDMA通道Request是SAI1_RXTrustZone隔離時檢查GTZC配置采集到的數據只有左聲道或者只有右聲道數據線接錯、幀同步極性不對、外部設備聲道選擇引腳設置不對核對原理圖在CubeMX把Frame Sync Active polarity取反檢查INMP441的L/R引腳數據波形有周期性中斷GPDMA FIFO閾值配置過高數據溢出或者半滿/全滿標志競爭沒處理干凈降低FIFO Threshold保證on回調里只置標志位不在中斷里做耗時處理MCK頻率不對Codec收不到數據時鐘樹配置錯誤PLL2Q輸出頻率和SAI分頻計算錯誤在CubeMX時鐘樹頁面仔細看SAI_CK的頻率用示波器量MCK引腳的實際頻率必須等于你配置的期望值運行時偶發HardFaultGPDMA訪問越界緩沖區地址不是4字節對齊TrustZone安全/非安全屬性不一致給緩沖區加 aligned(4)檢查DMA傳輸長度是否超過緩沖區大小確認內存區域安全屬性這里面最隱蔽的是最后一個HardFault問題。GPDMA不像老DMA那樣訪問非法地址就默默停掉它有時候會觸發總線錯誤中斷進而進HardFault。如果排查HardFault時想到DMA那首先要確認audio_buf是不是4字節對齊uint32_t數組本來就會對齊但如果你是手動分配的局部變量可能棧地址不是4字節對齊其次確認AUDIO_BUF_SAMPLES是否和DMA配置的傳輸長度完全一致如果DMA搬運到緩沖區末尾再加1個樣本問題就來了。5.3 我的調試經驗總結這一路調下來幾個心得分享給大家。第一音頻調試必須從信號源頭開始排查。別急著看代碼先拿示波器或者邏輯分析儀確認BCLK、FS、SD引腳波形。只要波形對了問題就縮小到GPDMA配置和軟件處理這兩塊。我見過有人調了三天找不到原因最后發現是麥克風L/R引腳懸空導致數據一直在兩個聲道間跳來跳去。第二先用HAL庫的阻塞模式驗證SAI本身能不能收到數據。比如HAL_SAI_Receive(hsai1, buf, 1, 1000)阻塞接收一個樣本如果連這個都超時說明外設配置或者硬件連接就有問題沒必要急著上DMA。這個步驟是很多初學者跳過的。第三GPDMA的Priority不要配成Low。雖然Priority不影響正確性但在N6這種多DMA外設并存的環境里Low優先級可能在系統繁忙的時候被一直拖欠音頻數據流就會斷。配High或者Very High代價可以忽略。第四緩沖區大小要跟你的處理節奏匹配。在48kHz采樣率下如果緩沖區開1024個樣本4096字節每1024/48000 21.3ms觸發一次中斷主循環必須在這個時間范圍內把數據處理完否則就會出現overrun問題。如果你的處理邏輯比較重要么把緩沖區開大要么用DMA鏈表把人分成更多小塊。我的經驗是緩沖區越大系統越穩但實時性越差反過來緩沖區小延遲低但主循環壓力大。N6有充足RAM我建議直接開到4096或8192個樣本給MCU留足余量。5.4 后續可以怎么擴展這套邏輯調通之后擴展空間非常大。因為GPCDMA SAI本質上就是一段持續不斷的PCM比特流在進內存你只需要在處理器里寫各種算法消費這些PCM數據即可。比較實用的擴展方向接音頻Codec實現雙向音頻即SAI同時配收發兩個BlockBlock A收、Block B發再用GPDMA兩條通道分別搬運收發數據這就是一個完整的音頻交互相機。把緩沖區里的PCM數據灌給N6的NPU做關鍵詞識別。N6的Neural-ART NPU很適合跑一些小尺寸音頻分類模型DMA采進來的數據直接作為輸入張量的一部分省去讀寫外部存儲的延遲。用GPCDMA鏈表模式做更細粒度的緩沖管理比如把不同的音頻事件錄音開始、靜音段檢測用鏈表串聯起來實現硬件級的數據流調度。如果對時延敏感還可以開啟GPDMA的異常中斷Transfer Error Interrupt在代碼里對錯誤狀態做實時監控避免音頻流長期運行后出現靜默故障。我個人的建議是先把基礎鏈路跑穩再逐層加復雜功能。音頻采集這東西前面鏈路通了后面做各種花活只是時間問題。最后分享一個小技巧如果你手頭沒有獨立的音頻源可以用手機播放一個1kHz正弦波的音頻文件然后把手機揚聲器對著麥克風——這是一種最原始但又十分有效的測試方式。把采集到的數據算一下FFT看到1kHz有個尖峰整條SAIGPDMA鏈路基本就穩了。我在多個項目里都靠這一招快速驗證音頻前端幾乎每次都能在5分鐘內確認問題出在硬件還是軟件。