學完本章你將能夠:
說明 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:
開啟 I2S2(或 SAI),Master Receive,Philips 標準,16-bit, 16 kHz。確認 PLLI2S 精準。
DMA 設 Circular、Half Word、中等優先權。先開 DMA 再開 I2S。
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 以後的所有程式。
簡易能量偵測
即使走路徑 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,對麥吹氣,觀察能量是否出現「一段正常、一段 卡住」——那就是即時性被破壞的樣子。