前言:現代 MCU 經常需要處理大量外設數據(ADC、UART、SPI、I²C 等),如果把這些數據搬運完全交給 CPU,會佔用大量 CPU 週期,降低系統響應能力。**DMA(Direct Memory Access,直接內存訪問)**就是解決這個問題的利器:它可以在不佔用 CPU 的情況下,把外設數據自動搬運到內存(或把內存數據搬到外設),顯著降低 CPU 負擔、提升實時性與吞吐量。
目錄
一、DMA 是什麼?為什麼要用?
二、DMA 的總體架構
三、DMA 的典型傳輸方向與使用場景
四、DMA 的關鍵配置項與概念
五、DMA 的工作時序與中斷
六、常見誤區與調試要點
七、性能注意事項與優化建議
八、ADC+DMA
一、DMA 是什麼?為什麼要用?
DMA 的本質:一個硬件控制器,負責在外設寄存器、內存和外設之間直接搬運數據,搬運過程中 CPU 不參與數據逐字節拷貝(但仍需做配置和處理完成後的邏輯)。
主要優點:
- 降低 CPU 佔用:尤其適合大量或高速數據採集(如音頻、傳感器陣列、圖像流等)。
- 更穩定的實時性:減少中斷頻率或避免中斷上下文開銷。
- 更高吞吐量:硬件搬運比軟件循環拷貝更快、更節省總線帶寬。
適用場景舉例:ADC 多通道高速採樣、UART 大量數據接收、SPI 快速數據傳輸、內存到內存的批量拷貝、PWM 波形輸出緩衝等。
二、DMA 的總體架構
- STM32F1 系列通常有 DMA1(個別器件還有 DMA2)。
- DMA 被劃分為 通道(Channel),每個通道映射到一個或多個外設事件(例如 ADC、USART、SPI 等)。通道數量有限(例如 DMA1 通常有 7 個通道)。
- 每個通道有自己的配置寄存器,控制源/目的地址、方向、數據寬度、優先級、傳輸長度、模式(循環/普通)等。
- DMA 與外設通過**請求映射(request mapping)**關聯:某個外設觸發數據準備(如 ADC EOC),會產生 DMA 請求到對應通道。
三、DMA 的典型傳輸方向與使用場景
- 外圍設備 → 內存(Peripheral-to-Memory)
場景:ADC 將轉換結果推送到內存、USART RX 接收數據寫內存。是最常見的用於數據採集的方向。 - 內存 → 外圍設備(Memory-to-Peripheral)
場景:從一個內存緩衝區把數據寫到 DAC、UART TX、SPI TX(比如發送音頻或圖像數據)。 - 內存 → 內存(Memory-to-Memory)
場景:大塊內存數據搬運(在 F1 上支持有限,注意映射與配置)。用於緩衝區複製、DMA 驅動的塊傳輸等。 - 雙向 / 循環模式(實際是模式選擇)
循環(Circular)適合連續流數據採集;普通(Normal)適合一次性傳輸。
四、DMA 的關鍵配置項與概念
- 源地址(Peripheral address)與目的地址(Memory address)
DMA 在搬運時會從源地址讀取數據並寫到目的地址;比如 ADC->內存: 源=ADC_data_register,目的=緩衝區首地址。 - 傳輸方向(Direction):見上一節。
- 傳輸長度(Number of Data):要搬運的數據項個數(不是字節數,依賴數據寬度)。
- 數據寬度(Peripheral & Memory Data Size):常見為 8-bit、16-bit、32-bit。ADC(12-bit)一般使用 16-bit 寬度(half-word),UART 通常用 8-bit。數據寬度必須匹配硬件與緩衝格式。
- 地址遞增(Peripheral increment / Memory increment):是否在每次傳輸後自動增加地址。外設寄存器通常禁用地址遞增(固定地址),內存緩衝通常啓用遞增。
- 傳輸模式(Normal / Circular):
- Normal:完成指定數量傳輸後停止並觸發完成中斷。
- Circular:完成後自動重新開始(圍繞緩衝區),適合連續採樣。
- 優先級(Priority):在多通道競爭 DMA 總線時決定搶佔順序(高優先級通道更早獲得總線)。
- 中斷/標誌:半傳中斷(Half Transfer)、傳輸完成中斷(Transfer Complete)、傳輸錯誤中斷。半傳在循環模式下非常有用——可以實現“前半緩衝處理同時後半緩衝接收”的方案。
五、DMA 的工作時序與中斷
- 啓動步驟(概念層面):
- 配置 DMA(源/目地址、長度、寬度、遞增、模式、優先級)。
- 配置外設(使能外設 DMA 請求,比如 ADC_DMACmd)。
- 使能 DMA 通道與相關中斷(如果需要)。
- 啓動外設觸發(ADC 軟件觸發或定時器觸發),數據開始流入緩衝區。
- 半傳中斷(HT)與傳輸完成中斷(TC):
在循環模式下,半傳中斷可以觸發處理已填滿的緩衝區一半數據,而 DMA 繼續寫入另一半;傳輸完成中斷則表示一輪緩衝寫滿。利用這兩個事件可以實現無縫“生產者-消費者”模式。 - 錯誤處理:DMA 也可能產生傳輸錯誤(比如總線錯誤),要有相應的檢測與恢復策略(重啓 DMA、報警等)。
六、常見誤區與調試要點
- 誤區:DMA 一開就萬事大吉
DMA 確實減輕 CPU,但錯誤配置(地址錯誤、長度錯誤、寬度不匹配)會導致數據錯位、溢出或硬件掛起。調試時重點檢查外設數據寄存器地址、數據寬度和內存緩衝區大小。 - 誤區:半傳中斷和全傳中斷二者可互相替代
半傳中斷用於“流式處理”更低延遲;若只用全傳中斷,會在緩衝寫滿後才處理,可能加大延遲。 - 調試技巧:
- 先用短緩衝進行功能驗證(例如 4 個採樣點);
- 打開半傳/全傳中斷並在 ISR 中簡單翻轉 IO(示波器觀察)確認觸發節奏;
- 檢查 DMA 優先級與競爭關係;
- 確認內存對齊(16-bit/32-bit)與 CPU 訪問不會引起未對齊故障。
- 跨域緩存問題(高級):F1 的 Cortex-M3 沒有數據緩存問題,然而在更高端系列(如 Cortex-M7)需要考慮 D-cache 與 DMA 的一致性:使用緩存刷新/失效或使用不可緩存內存區。
七、性能注意事項與優化建議
- 選擇合適的數據寬度:儘量讓外設與內存寬度匹配,減少額外的對齊/移動操作(例如 ADC 用 16-bit 存儲)。
- 優先級與通道分配:把關鍵通道設高優先級;避免多個高頻外設共享同一 DMA 通道。
- 中斷開銷最小化:把處理儘量放到任務或主循環,不在 ISR 中做大量計算;使用半傳中斷只做標記並在主循環處理。
- 使用循環模式處理持續數據流:比如 ADC 連續採樣或串口持續接收,循環模式可以避免頻繁重啓 DMA。
- 考慮緩衝大小權衡:小緩衝降低延遲但增加中斷頻率;大緩衝降低中斷負擔但增加響應延遲。
八、ADC+DMA
- 當需要實時採集多通道數據(如傳感器陣列、音頻採樣、振動分析等)時,CPU 去輪詢或處理中斷會消耗大量計算資源,甚至跟不上數據速率。
- ADC + DMA 可以實現:ADC 將轉換結果自動寫入內存緩衝區,CPU 僅在緩衝半滿或滿時處理數據,極大提高效率與實時性。
配置流程:
- 設計緩衝區:為採樣結果準備好連續的內存數組(例如
uint16_t buffer[NUM_SAMPLES];)。通常選擇 16-bit 單元來保持 ADC 的 12-bit 數據。 - 配置 ADC:
- 打開 ADC 時鐘,設置採樣時間、分辨率(12-bit)、觸發模式(軟件或定時器觸發)、掃描模式(若為多通道自動掃描)。
- 對於多通道自動採樣,啓用掃描模式並在序列寄存器中設置通道順序。
- 配置 DMA(外設 → 內存):
- 源地址:ADC 數據寄存器地址(固定);目的地址:緩衝區首地址;
- 數據寬度:一般選擇 16-bit(half-word);
- 內存地址遞增:啓用;外設地址遞增:禁用;
- 模式:如果是持續採樣(例如定時器觸發連續採樣),選擇 Circular(循環) 模式;若是一次性採集選擇 Normal。
- 啓用半傳/全傳中斷(根據需要)。
- 把 ADC 的 DMA 請求打開(告訴 ADC 在 EOC 時發出 DMA 請求,讓 DMA 自動搬運數據)。
- 啓動 ADC(與觸發器)與 DMA 通道:在正確的時序下啓動 DMA,然後啓動 ADC & 觸發(如果用定時器觸發,啓動定時器)。
- 在中斷/任務中處理數據:
- 如果使用循環模式並啓用了半傳/全傳中斷:在半傳中斷中處理緩衝區前半,在全傳中斷中處理後半;處理完後繼續讓 DMA 循環寫入。
- 如果使用普通模式:在傳輸完成中斷中處理整個緩衝區並可重新配置/重啓 DMA。
下一章節將詳細講解代碼部分。