學完本章你將能夠:

  • 指出 Cortex-M4 核心、匯流排矩陣、Flash/SRAM、GPIO 與週邊的關係。

  • 解釋為什麼時鐘樹錯了,UART 鮑率與 I2S 取樣率都會一起錯。

  • 分清輪詢、中斷、DMA 三種資料搬運,並知道語音該用哪一種。

  • 讀懂啟動檔、向量表與 |Reset_Handler| 在 |main| 之前做了什麼。

還沒寫應用程式之前,先建立一張「晶片內部地圖」。之後 FreeRTOS 的 Tick、 I2S 的 DMA、Watchdog 的獨立時鐘,都會回到這張地圖。

從核心到腳位

STM32F429 這類 MCU 可以想成四層:

  1. Arm Cortex-M4F:執行 Thumb-2 指令、巢狀中斷(NVIC)、 可選 FPU。FreeRTOS 的 context switch 就是在這裡存/還暫存器。

  2. 匯流排矩陣:I-Code、D-Code、System、AHB1/2、APB1/2。 CPU、DMA、USB 可以同時走不同路徑,這是「語音 DMA 不卡 CPU」的 硬體前提。

  3. 記憶體:內部 Flash 放程式與常數;SRAM 放堆疊、堆、 .bss 與 DMA 緩衝。F429 約 2 MB Flash、256 KB SRAM, 夠跑 FreeRTOS + 路徑 A;路徑 B 的 Mel 特徵與 NN 權重通常需要 H7 的更大 SRAM 或外部 SDRAM。

  4. 週邊:GPIO、TIM、USART、I2C、SPI、I2S、ADC、RTC、IWDG、 WWDG。應用程式幾乎不直接碰核心,而是設定這些區塊。

STM32 內部層次(教學用簡圖)

記憶體模型:你的變數住哪裡

連結腳本(.ld)決定符號落點。對語音專案特別重要的三件事:

  • 堆疊:每個 FreeRTOS 任務有自己的堆疊,從 FreeRTOS heap 配置,不是啟動檔那個主堆疊(MSP)包辦一切。

  • DMA 緩衝:必須在 DMA 能看到的 SRAM,且對齊快取(H7 有 D-Cache 時還要做 cache clean/invalidate)。

  • 大陣列:梅爾頻譜或模型張量不要放在任務堆疊上,應設為 |static| 或從 heap 配置。

F4 的 CCM RAM 很快,但 DMA 通常不能存取 CCM。若你把 I2S 緩衝 放進 CCM,現象是「設定看起來都對,緩衝永遠是 0」。這是課堂上最高頻的 陷阱之一。

時鐘樹:萬物的節拍器

上電後 MCU 跑在內部 HSI(16 MHz 量級)。CubeMX 的 Clock Configuration 會把 HSE \(\rightarrow\) PLL \(\rightarrow\) SYSCLK,再分頻給 AHB/APB。 本課程建議:

  • F429:SYSCLK 180 MHz(注意過驅動與 Flash wait state)。

  • APB1 計時器時鐘要能整除出 1 MHz 或 10 kHz,方便 PWM。

  • I2S 需要獨立的精準時鐘(PLLI2S)。取樣率 16 kHz 算錯 1%, 語音特徵就會漂,辨識率會無聲地崩掉。

  • IWDG 掛在 LSI,即使主時鐘停了(Stop 模式)仍能復位——這是 第 12 章低功耗能「睡死也能醒」的原因。

三種資料搬運

輪詢、中斷、DMA
方式 CPU 在做什麼 本專案用法
輪詢 一直讀旗標 初始化、極短的暫存器確認
中斷 事件來了才進去 ISR UART 收 ASR 位元組、按鍵、EXTI
DMA CPU 零介入搬一長段 I2S 音訊、USART 大量遙測

ISR 裡只做最少的事:清旗標、給 Semaphore、|xQueueSendFromISR|。 推論、字串解析、|printf| 全部留給任務。違反這條,輕則優先權反轉, 重則在 ISR 裡開銷過大導致丟音。

啟動到 main 的路

重置後硬體跳到 Flash 開頭的向量表。|Reset_Handler|(在 startup_stm32f429xx.s)會:複製 .data、清 .bss、呼叫 |SystemInit|(設 FPU、重定位向量、預設時鐘), 再進 |main|。Cube 產生的 |main| 通常先 |HAL_Init|、 |SystemClock_Config|、再初始化週邊,最後才建立 FreeRTOS 任務並 |osKernelStart|——那之後 |main| 就再也不回來了。

int main(void) HAL_Init(); /* SysTick 先給 HAL,稍後交給 FreeRTOS */ SystemClock_Config(); /* 時鐘樹一次設對 */ MX_GPIO_Init(); MX_DMA_Init(); /* 必須在 I2S/USART 之前 */ MX_I2S2_Init(); MX_USART3_UART_Init(); osKernelInitialize(); app_create_objects(); /* Queue / Sem / EventGroup */ app_create_tasks(); osKernelStart(); /* 不再返回 */ while (1) /* 不應該走到這裡 */

CubeMX 勾選 FreeRTOS 後,HAL 的時間基準會改成 TIM 而把 SysTick 讓給 RTOS。若你手動改 FreeRTOSConfig.h 卻忘了這件事,會出現 「延遲函數亂跳、HAL_Delay 卡死」的幽靈 bug。

中斷優先權與 RTOS

Cortex-M 的優先權數字越小越緊急。FreeRTOS 要求:任何會呼叫 |*FromISR| 的中斷,其邏輯優先權不能高過 |configMAX_SYSCALL_INTERRUPT_PRIORITY|。語音 DMA 完成中斷應設在 「可以呼叫 FreeRTOS API」的範圍內;硬體故障(HardFault)則不走這套。

打開你即將使用的 .ioc,在 Clock Configuration 截一張圖,標出 SYSCLK、APB1 Timer clock、I2S clock。再打開 startup 檔,找到向量表裡 |DMA1_Stream4_IRQHandler| 的位置(名稱依實際流而變)。把這兩張圖 貼進實驗筆記本——第 8 章會用到。