STM32F103系列微控制器(基于ARM Cortex-M3內核)集成了**DMA(Direct Memory Access,直接內存訪問)**控制器,用于在存儲器與外設、存儲器與存儲器之間高效傳輸數據,減少CPU的干預,從而提升系統性能。本文將詳細介紹STM32F103的DMA原理、架構、功能特性及使用方法,結合實際代碼示例說明如何在開發中應用DMA,特別以STM32CubeMX和HAL庫為工具。
1. DMA原理
DMA是一種硬件機制,允許數據在存儲器(Flash、SRAM)或外設(UART、SPI、ADC等)之間直接傳輸,而無需CPU逐字節處理。其核心思想是將數據搬運任務交給DMA控制器,CPU只需配置DMA通道并啟動傳輸,傳輸完成后通過中斷或查詢方式獲取結果。
1.1 工作原理
- DMA控制器:STM32F103的DMA控制器管理多個通道,每個通道負責特定的數據傳輸任務。
- 數據流:
- 存儲器到存儲器:如SRAM到SRAM。
- 外設到存儲器:如ADC數據到SRAM。
- 存儲器到外設:如SRAM數據到UART發送緩沖區。
- 觸發機制:
- DMA傳輸可由軟件觸發(手動啟動)或硬件觸發(外設請求,如UART發送完成)。
- 中斷支持:
- 傳輸完成(TC)、半傳輸(HT)或傳輸錯誤(TE)時可觸發中斷。
- 優勢:
- 降低CPU負載,適合高吞吐量任務(如ADC采樣、音頻流)。
- 提高實時性,適合多任務系統。
1.2 STM32F103的DMA架構
STM32F103的DMA控制器分為DMA1和DMA2(部分高密度型號支持DMA2),具體特性如下:
- DMA1:
- 7個通道(Channel 1-7)。
- 連接到APB1/APB2外設(如UART、SPI、ADC)及存儲器。
- DMA2(僅高密度型號,如STM32F103ZET6):
- 5個通道(Channel 1-5)。
- 通常用于高級外設或額外存儲器訪問。
- 通道優先級:
- 每個通道可配置高、中、低優先級,解決多通道沖突。
- 數據寬度:
- 支持8位、16位、32位數據傳輸。
- 傳輸模式:
- 單次傳輸:傳輸固定長度數據后停止。
- 循環模式:傳輸完成后自動重新開始,適合連續數據流。
- 增量模式:支持源/目標地址自動遞增或固定。
- FIFO:STM32F103的DMA無獨立FIFO,依賴外設緩沖區或直接傳輸。
1.3 DMA工作流程
- 配置DMA通道:
- 設置源地址、目標地址、數據長度。
- 配置傳輸方向(存儲器到外設、外設到存儲器、存儲器到存儲器)。
- 設置數據寬度、優先級、增量模式等。
- 啟動傳輸:
- 軟件觸發(設置ENABLE位)或硬件觸發(外設信號)。
- 數據傳輸:
- DMA控制器從源地址讀取數據,寫入目標地址。
- 完成處理:
- 傳輸完成后,觸發中斷或置位標志,通知CPU處理結果。
2. STM32F103 DMA功能特性
- 通道數量:
- DMA1:7通道,支持ADC1/2、SPI1/2、UART1-3、I2C1/2、TIM1-4等。
- DMA2(若有):5通道,支持高級外設或額外存儲器。
- 傳輸方向:
- 存儲器到存儲器(僅DMA1支持)。
- 外設到存儲器(如ADC采樣到SRAM)。
- 存儲器到外設(如SRAM數據到UART)。
- 中斷支持:
- 傳輸完成中斷(TCIF)。
- 半傳輸中斷(HTIF)。
- 傳輸錯誤中斷(TEIF)。
- 數據對齊:
- 支持字節(8位)、半字(16位)、字(32位)傳輸。
- 源和目標數據寬度可不同(如8位外設到16位SRAM)。
- 循環模式:
- 啟用后,傳輸完成后自動重新加載計數器,適合連續數據流。
- 優先級管理:
- 軟件配置通道優先級(Very High, High, Medium, Low)。
- 硬件仲裁確保高優先級通道優先訪問總線。
3. DMA使用方法
以下以STM32F103C8T6為例,結合STM32CubeMX和HAL庫,介紹如何實現DMA傳輸,重點以ADC DMA(外設到存儲器)和UART DMA(存儲器到外設)為例。
3.1 開發環境準備
- 硬件:
- STM32F103C8T6開發板(如“藍板”)。
- ST-Link V2調試器。
- USB-TTL模塊(如CH340)用于串口調試。
- 軟件:
- STM32CubeMX:配置外設和DMA。
- STM32CubeIDE:編寫和調試代碼。
- STM32CubeProgrammer:燒錄程序。
- 串口終端(如PuTTY):查看輸出。
3.2 示例1:ADC DMA(外設到存儲器)
目標:使用ADC1連續采樣多個通道的數據,通過DMA傳輸到SRAM緩沖區。
3.2.1 STM32CubeMX配置
- 創建項目:
- 打開STM32CubeMX,選擇STM32F103C8T6。
- 配置ADC:
- 在“Analog”中啟用ADC1。
- 配置通道(如IN0、IN1,連接到PA0、PA1)。
- 設置為“Continuous Conversion Mode”(連續轉換)。
- 啟用“DMA Continuous Requests”。
- 配置DMA:
- 在ADC1的“DMA Settings”中,添加DMA請求:
- 選擇DMA1 Channel 1(ADC1默認通道)。
- 設置:
- Mode:Circular(循環模式)。
- Data Width:Half Word(16位,ADC為12位數據)。
- Increment Address:Memory(目標地址遞增)。
- Priority:Medium。
- 在ADC1的“DMA Settings”中,添加DMA請求:
- 配置時鐘:
- 設置HSE(8MHz晶振),PLL倍頻到72MHz,APB2為72MHz,ADC時鐘分頻到12MHz(最大14MHz)。
- 生成代碼:
- 在“Project Manager”中設置項目名稱、路徑,選擇“STM32CubeIDE”。
- 生成代碼。
3.2.2 代碼實現
在STM32CubeIDE中,修改main.c
實現ADC DMA采樣:
#include "main.h"
#include "adc.h"
#include "dma.h"
#include "usart.h"
#include "stdio.h"#define ADC_CHANNEL_COUNT 2
uint16_t adcData[ADC_CHANNEL_COUNT]; // 存儲ADC采樣數據void SystemClock_Config(void);int main(void) {HAL_Init();SystemClock_Config();MX_DMA_Init();MX_ADC1_Init();MX_USART1_UART_Init(); // 串口用于調試// 啟動ADC DMAHAL_ADC_Start_DMA(&hadc1, (uint32_t*)adcData, ADC_CHANNEL_COUNT);while (1) {// 在循環模式下,DMA自動更新adcDatachar msg[50];sprintf(msg, "ADC1: %u, ADC2: %u\r\n", adcData[0], adcData[1]);HAL_UART_Transmit(&huart1, (uint8_t*)msg, strlen(msg), 100);HAL_Delay(1000); // 每秒打印一次}
}// DMA傳輸完成回調(可選)
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) {// 可在此處理傳輸完成后的邏輯
}
說明:
HAL_ADC_Start_DMA
啟動ADC和DMA,數據自動傳輸到adcData
數組。- 循環模式下,DMA自動更新緩沖區,CPU無需干預。
- 串口(UART1)打印ADC采樣值,用于調試。
3.2.3 測試
- 燒錄程序到STM32F103。
- 連接PA0/PA1到模擬信號(如電位器)。
- 在串口終端(如PuTTY,115200波特率)查看ADC采樣值。
3.3 示例2:UART DMA(存儲器到外設)
目標:通過DMA將SRAM中的數據發送到UART1。
3.3.1 STM32CubeMX配置
- 配置UART:
- 啟用USART1(PA9-TX,PA10-RX)。
- 設置波特率(如115200),模式為異步。
- 在“DMA Settings”中,添加DMA請求:
- 選擇DMA1 Channel 4(USART1_TX默認通道)。
- 設置:
- Mode:Normal(單次傳輸)。
- Data Width:Byte(8位,UART數據)。
- Increment Address:Memory。
- Priority:Medium。
- 生成代碼:
- 同ADC示例,生成STM32CubeIDE項目。
3.3.2 kazhuCode實現
修改main.c
實現UART DMA發送:
#include "main.h"
#include "dma.h"
#include "usart.h"
#include "string.h"uint8_t txData[] = "Hello, STM32 DMA!\r\n";void SystemClock_Config(void);void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) {// 傳輸完成回調HAL_UART_Transmit(&huart1, (uint8_t*)"TX Complete\r\n", 13, 100);
}int main(void) {HAL_Init();SystemClock_Config();MX_DMA_Init();MX_USART1_UART_Init();// 啟動UART DMA發送HAL_UART_Transmit_DMA(&huart1, txData, strlen((char*)txData));while (1) {HAL_Delay(1000); // 每秒發送一次HAL_UART_Transmit_DMA(&huart1, txData, strlen((char*)txData));}
}
說明:
HAL_UART_Transmit_DMA
啟動DMA傳輸,將txData
發送到UART1。- 傳輸完成后,觸發
HAL_UART_TxCpltCallback
中斷。 - 單次模式下,需手動重新啟動傳輸。
3.3.3 測試
- 燒錄程序。
- 連接USB-TTL模塊(PA9->RX,PA10->TX)。
- 在串口終端查看輸出“Hello, STM32 DMA!”。
4. 關鍵配置注意事項
- DMA初始化順序:
- 在
main.c
中,MX_DMA_Init
必須在其他外設初始化(如MX_ADC1_Init
、MX_USART1_Init
)之前調用,確保DMA時鐘啟用。
- 在
- 通道選擇:
- 每個外設有固定DMA通道(如ADC1->DMA1 Channel 1,USART1_TX->DMA1 Channel 4)。參考STM32F103參考手冊。
- 數據對齊:
- 源和目標數據寬度需匹配(如ADC為16位,緩沖區應為
uint16_t
)。
- 源和目標數據寬度需匹配(如ADC為16位,緩沖區應為
- 中斷管理:
- 啟用DMA中斷(如
HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn)
)。 - 在中斷回調中處理傳輸完成或錯誤。
- 啟用DMA中斷(如
- 循環模式 vs 單次模式:
- 循環模式適合連續數據流(如ADC采樣)。
- 單次模式適合一次性傳輸(如UART發送固定字符串)。
- 優先級沖突:
- 多通道同時傳輸時,設置高優先級給關鍵任務(如ADC優于UART)。
5. 常見問題及解決
- DMA不工作:
- 檢查DMA通道是否與外設匹配。
- 確保
MX_DMA_Init
在其他外設初始化前調用。 - 驗證源/目標地址正確,數據長度非零。
- 數據錯誤:
- 檢查數據寬度(Byte/Half Word/Word)是否與外設匹配。
- 確保目標緩沖區有足夠空間。
- 中斷未觸發:
- 確認中斷使能(
HAL_DMA_Start_IT
或外設API)。 - 檢查NVIC配置(優先級、使能)。
- 確認中斷使能(
- 總線沖突:
- 避免多個DMA通道同時訪問同一存儲器區域。
- 調整通道優先級或降低傳輸速率。
6. 擴展應用
- 多通道ADC:使用DMA采集多個ADC通道數據,存儲到數組,適合信號處理。
- SPI DMA:實現高速SPI數據傳輸(如與LCD或Flash通信)。
- FreeRTOS集成:結合DMA中斷和信號量,優化實時任務調度。
- 雙緩沖技術:在循環模式下使用兩個緩沖區交替存儲數據,避免數據覆蓋。
7. 學習資源
- 官方文檔:
- STM32F103參考手冊(DMA章節)。
- STM32CubeMX用戶手冊。
- 教程:
- 正點原子/野火的STM32 DMA教程。
- ST社區論壇的DMA應用筆記(如AN2548)。
- 工具:
- STM32CubeMonitor:監控DMA傳輸數據。
- 邏輯分析儀:調試外設信號。
8. 總結
STM32F103的DMA控制器通過高效的數據傳輸,顯著降低CPU負載,適合高吞吐量場景。DMA1支持7通道,DMA2(部分型號)支持5通道,覆蓋ADC、UART、SPI等外設。使用STM32CubeMX配置DMA參數,結合HAL庫(如HAL_ADC_Start_DMA
、HAL_UART_Transmit_DMA
)可快速實現傳輸。關鍵注意初始化順序、通道選擇和數據對齊,通過中斷和循環模式優化性能。