學完本章你將能夠:

  • 說明 PDM 與 I2S PCM 的差別,並為你的麥克風選對週邊。

  • 設定 DMA 循環雙緩衝,在半滿/全滿回呼裡與 Audio 任務同步。

  • 計算 16 kHz / 16-bit / mono 的頻寬與 10 ms 幀大小。

  • 在路徑 A 用 UART 接收外部模組的「已辨識結果」,仍維持同樣的任務邊界。

語音系統的物理起點是空氣壓力變化。STM32 不會「聽懂」,它只會把電壓或 數位串流變成記憶體裡的整數陣列。本章把這條管線做穩——穩了,後面的 AI 才有乾淨的輸入。

取樣參數

教學預設:16 kHz、16-bit、單聲道。一幀 10 ms 含

\[16000 \times 0.010 \times 2 = 320\ \text{bytes} = 160\ \text{samples}.\]

雙緩衝就是兩個 320 bytes(或更大,例如 20 ms)。CPU 必須在下一半被 覆寫前處理完當前半——這就是 Audio 任務高優先權的理由。

麥克風選型

課堂常見麥克風
型號 介面 注意
INMP441 / ICS-43434 I2S,直接出 PCM 3.3 V、WS/SCK/SD,對 F4 友善
MP34DT01(Discovery) PDM 需 SAI/I2S PDM 濾波或軟體 CIC
MAX4466 等類比 ADC + 定時器觸發 DMA 簡單但雜訊與偏壓要處理
路徑 A 模組自帶麥 模組內部完成 STM32 只收 UART 文字/代碼

路徑 B 建議 INMP441:少一層 PDM 轉換,課堂時間夠用。接線時 WS(LRCLK) 決定左右聲道,單顆麥通常把 L/R 腳拉到固定電位。

I2S + DMA 循環模式

CubeMX:

  1. 開啟 I2S2(或 SAI),Master Receive,Philips 標準,16-bit, 16 kHz。確認 PLLI2S 精準。

  2. DMA 設 Circular、Half Word、中等優先權。先開 DMA 再開 I2S。

  3. NVIC:DMA 中斷優先權必須可呼叫 FreeRTOS API。

#define FRAME_SAMP 160 static int16_t s_pcm[2][FRAME_SAMP];

void audio_start(void) if (HAL_I2S_Receive_DMA(&hi2s2, (uint16_t *)s_pcm[0], FRAME_SAMP * 2U) != HAL_OK) xEventGroupSetBits(g_evt, EVT_FAULT);

半滿對應 |s_pcm[0]|,全滿對應 |s_pcm[1]|。回呼裡只記錄 「哪一銀行效」並 |xSemaphoreGiveFromISR|,不要在 ISR 做 FFT。

void vTaskAudio(void *arg) (void)arg; audio_start(); for (;;) xSemaphoreTake(g_audio_sem, portMAX_DELAY); const int16_t *src = audio_bank_ptr(); /* 0 或 1 */ xQueueSend(g_audio_q, src, 0); /* 滿則丟最舊策略見完整檔 */ heartbeat_touch(TASK_AUDIO);

佇列元素若是整幀,複製 320 bytes 尚可接受。更省的做法是佇列只傳 bank 索引,消費端必須在下一幀到來前用完——延遲約束更硬。

路徑 A:UART 幀當「音訊」

外部模組通常輸出像 OK wakeup 或二進位指令碼。STM32 側:

  • USART RX DMA 環形緩衝,或 byte-IT + 組包狀態機。

  • 以換行或固定長度切幀,組成 |asr_raw_t| 丟給 ASR 任務。

  • 不要在 UART ISR 裡 |strcmp|。

任務圖與路徑 B 相同:只是 Audio 任務的輸入從 I2S 換成 UART。這讓第 9 章可以共用 Command 以後的所有程式。

兩條擷取路徑在 ASR 任務匯合

簡易能量偵測

即使走路徑 B,在跑 NN 前先算幀能量,可過濾靜音、省算力:

uint32_t pcm_energy(const int16_t *x, size_t n) uint64_t acc = 0; for (size_t i = 0; i < n; i++) int32_t v = x[i]; acc += (uint64_t)(v * v); return (uint32_t)(acc / n);

能量低於門檻就不必喚醒後續 KWS。門檻需依麥克風與房間校正——印出能量 值 10 秒是值得的課堂活動。

實驗

firmware/lab07_audio_capture/ 含兩份實作: audio_i2s.c(路徑 B)與 audio_uart_mod.c(路徑 A)。 預設宏 |APP_AUDIO_PATH| 切換。路徑 A 實驗:模組或 PC 終端送一行 文字,板子回顯能量或字串長度。路徑 B:UART 印每幀 RMS,對麥說話應 看到數字跳升。

H7 開啟 D-Cache 時,DMA 寫入的 PCM 對 CPU 可能是舊資料。必須把緩衝 放進 non-cacheable 區,或在使用前 |SCB_InvalidateDCache_by_Addr|。F4 無 D-Cache,較少踩這坑。

把幀長改為 20 ms,重算陣列大小與 I2S DMA 長度(長度單位是取樣數不是 byte,HAL 文件要看清楚)。量 DMA 中斷間隔是否變成 20 ms。再故意把 Audio 任務優先權降到 0,對麥吹氣,觀察能量是否出現「一段正常、一段 卡住」——那就是即時性被破壞的樣子。