STM32-01-認識單片機
STM32-02-基礎知識
STM32-03-HAL庫
STM32-04-時鐘樹
STM32-05-SYSTEM文件夾
STM32-06-GPIO
STM32-07-外部中斷
STM32-08-串口
STM32-09-IWDG和WWDG
STM32-10-定時器
STM32-11-電容觸摸按鍵
STM32-12-OLED模塊
STM32-13-MPU
STM32-14-FSMC_LCD
文章目錄
- STM32-15-DMA
- 1. DMA與中斷的區別
- 1. DMA
- 2. 中斷
- 2. DMA介紹
- 3. DMA結構框圖
- 1. DMA框圖
- 2. DMA處理過程
- 3. DMA通道
- 4. DMA相關寄存器
- 5. DMA相關HAL庫驅動
- 6. 代碼實現
STM32-15-DMA
1. DMA與中斷的區別
**DMA
(Direct Memory Access
,直接內存訪問)**和中斷是兩種不同的機制,用于管理計算機系統中外圍設備與處理器之間的數據傳輸和處理。
1. DMA
工作原理:
- 獨立傳輸:DMA允許外設直接與系統內存交換數據,而不需要通過處理器(CPU)。當需要大量數據傳輸時,DMA控制器接管傳輸任務,釋放CPU去執行其他任務。
- 傳輸過程:DMA傳輸數據時,CPU啟動DMA傳輸,然后DMA控制器接管整個傳輸過程。傳輸完成后,DMA控制器通過中斷通知CPU傳輸完成。
作用:
- 提高效率:DMA減少了CPU在數據傳輸過程中的參與,使得CPU能夠執行其他任務,從而提高系統的整體效率。
- 減少延遲:由于DMA可以獨立進行傳輸,因此數據傳輸的延遲更低,特別是在處理大塊數據時。
- 應用場景:DMA廣泛應用于音頻、視頻數據流、網絡數據包的傳輸、存儲設備的數據讀寫等場景。
對程序的影響:
- 復雜度增加:引入DMA需要對DMA控制器進行配置,可能增加程序的復雜性。
- 同步問題:在DMA傳輸過程中,程序需要處理好數據同步問題,避免數據不一致性問題。
2. 中斷
工作原理:
- 中斷觸發:中斷是外設通過中斷信號通知CPU某個事件發生,如輸入設備有新數據可讀取,定時器到期等。
- 中斷處理:CPU響應中斷后,暫停當前任務,跳轉到相應的中斷處理程序(ISR)執行。當中斷處理程序執行完畢后,CPU恢復先前任務的執行。
作用:
- 實時響應:中斷機制使CPU能夠實時響應外設事件,保證系統對外部事件的快速反應。
- 事件驅動:中斷使得程序可以基于事件驅動,而不是定期輪詢外設狀態,從而節省CPU資源。
- 應用場景:鍵盤輸入、鼠標移動、網絡數據包到達、定時器事件等。
對程序的影響:
- 中斷處理程序設計:中斷處理程序需要盡可能簡短、快速,避免長時間占用CPU。
- 中斷優先級管理:系統中可能有多個中斷源,需要合理設計中斷優先級,以確保關鍵中斷能夠及時響應。
- 上下文切換開銷:中斷會引起上下文切換,帶來一定的性能開銷。
總結
- DMA:適用于大量數據傳輸,降低CPU負載,提高系統效率。
- 中斷:適用于實時事件響應,保證系統對外部事件的快速處理。
兩者結合使用,可以構建高效、實時的嵌入式系統。例如,DMA用于大數據塊的傳輸,而中斷用于觸發DMA傳輸和處理傳輸完成事件。
2. DMA介紹
- **DMA:**即直接存儲器訪問。DMA傳輸方式無需CPU直接控制傳輸,也沒有中斷處理方式那樣保留現場和恢復現場的過程,通過硬件為RAM與I/O設備開辟一條直接傳送數據的通路,能使CPU的效率大為提高。
- STM32F103內部有2個DMA控制器,
DMA1
有7
個通道,DMA2
有5
個通道。每個通道專門用來管理來自于一個或多個外設對存儲器訪問的請求,還有一個仲裁器來協調各個DMA請求的優先權。 - 特性:
- 每個通道都直接連接專用的硬件DMA請求,每個通道都支持軟件觸發。這些功能通過軟件來配置。
- 在七個請求間的優先權可以通過軟件編程設置,當軟件相同時,由硬件決定。
- 獨立的源和目標數據區的傳輸寬度,模擬打包和拆包的過程。源和目標地址必須按數據傳輸寬度對齊。
- 支持循環的緩沖器管理。
- 每個通道都有3個事件標志,這3個事件標志邏輯或成為一個單獨的中斷請求。
- 存儲器和存儲器間的傳輸。
- 外設和存儲器,存儲器和外設的傳輸。
- 閃存、SRAM、外設的SRAM、APB1、APB2和AHB外設均可作為訪問的源和目標。
- 可編程的數據傳輸數目最大為65535。
3. DMA結構框圖
1. DMA框圖
-
①DMA請求
如果外設想要通過DMA來傳輸數據,必須先給DMA控制器發送DMA請求,DMA收到請求信號之后,控制器會給外設一個應答信號,當外設應答后且DMA控制器收到應答信號之后,就會啟動DMA的傳輸,直到傳輸完畢。
-
②DMA通道
DMA具有12個獨立可編程的通道,其中DMA1有7個通道,DMA2有5個通道,每個通道對應不同的外設的DMA請求。雖然每個通道可以接收多個外設的請求,但是同一時間只能接收一個,不能同時接收多個。
-
③DMA優先級
當發生多個DMA通道請求時,就意味著有先后響應處理的順序問題,這個就由仲裁器管理。仲裁器管理DMA通道請求分為兩個階段。第一階段屬于軟件階段,可以在
DMA_CCRx
寄存器中設置,有4個等級:非常高,高,中和低四個優先級。第二階段屬于硬件階段,如果兩個或以上的DMA通道請求設置的優先級一樣,則他們優先級取決于通道編號,編號越低優先權越高,比如通道0高于通道1。在大容量產品和互聯型產品中,DMA1控制器擁有高于DMA2控制器的優先級。
2. DMA處理過程
DMA(Direct Memory Access,直接內存訪問)是一種允許外設直接與系統內存進行數據傳輸的機制,不需要CPU的直接干預。以下是DMA處理過程的詳細描述:
DMA處理過程
- 配置DMA控制器:
- 源地址:設置數據傳輸的源地址(可以是外設寄存器地址或內存地址)。
- 目標地址:設置數據傳輸的目標地址(可以是外設寄存器地址或內存地址)。
- 傳輸方向:指定數據傳輸的方向,是從外設到內存,還是從內存到外設。
- 數據長度:設置需要傳輸的數據長度(字節數)。
- 傳輸模式:選擇傳輸模式,可以是單次傳輸、塊傳輸或連續傳輸模式。
- 啟動DMA傳輸:
- 觸發傳輸:在配置完成后,通過設置DMA控制器的啟動位,開始數據傳輸。
- 數據搬運:DMA控制器接管數據傳輸任務,將數據從源地址搬運到目標地址。這個過程中,DMA控制器直接與內存控制器和外設總線進行交互,不需要CPU干預。
- 中斷通知:
- 傳輸完成中斷:數據傳輸完成后,DMA控制器產生中斷信號,通知CPU傳輸已經完成。CPU執行相應的中斷服務程序(ISR)處理后續任務。
- 錯誤處理:如果在傳輸過程中發生錯誤(如總線錯誤),DMA控制器也會產生中斷,通知CPU進行錯誤處理。
3. DMA通道
DMA1通道與外設的對應關系
DMA2通道與外設的對應關系
4. DMA相關寄存器
寄存器 | 名稱 | 作用 |
---|---|---|
DMA_CCRx | DMA通道x配置寄存器 | 用于配置DMA(核心控制寄存器) |
DMA_ISR | DMA中斷狀態寄存器 | 用于查詢當前DMA傳輸狀態 |
DMA_IFCR | DMA中斷標志清除寄存器 | 用來清除DMA_ISR對應位 |
DMA_CNDTRx | DMA通道x傳輸數量寄存器 | 用于控制DMA通道x每次傳輸的數據量 |
DMA_CPARx | DMA通道x外設地址寄存器 | 用于存儲STM32外設地址 |
DMA_CMARx | DMA通道x存儲器地址寄存器 | 用于存放存儲器的地址 |
USART_CR3 | USART控制寄存器3 | 用于使能串口DMA發送 |
-
DMA通道x配置寄存器(DMA_CCRx)
-
DMA中斷狀態寄存器(DMA_ISR)
-
DMA 中斷標志清除寄存器(DMA_IFCR)
-
DMA通道x傳輸數量寄存器(DMA_CNDTRx)
該寄存器控制著DMA通道x的每次傳輸所要傳輸的數據量。其設置范圍為
0~65535
。并且該寄存器的值隨著傳輸的進行而減少,當該寄存器的值為0
的時候就代表此次數據傳輸己經全部發送完成。可以通過這個寄存器的值來獲取當前DMA傳輸的進度。 -
DMA通道x外設地址寄存器(DMA_CPARx)
用來存儲 STM32 外設的地址。
-
DMA通道x存儲器地址寄存器(DMA_CMARx)
用來存放存儲器的地址。
5. DMA相關HAL庫驅動
-
使能DMA時鐘
__HAL_RCC_DMA1_CLK_ENABLE();
-
初始化DMA
HAL_StatusTypeDef HAL_DMA_Init(DMA_HandleTypeDef *hdma);typedef struct { uint32_t Direction; /* 傳輸方向,例如存儲器到外設 DMA_MEMORY_TO_PERIPH */uint32_t PeriphInc; /* 外設(非)增量模式,非增量模式 DMA_PINC_DISABLE */ uint32_t MemInc; /* 存儲器(非)增量模式,增量模式 DMA_MINC_ENABLE */ uint32_t PeriphDataAlignment; /* 外設數據大小:8/16/32 位 */uint32_t MemDataAlignment; /* 存儲器數據大小:8/16/32 位 */uint32_t Mode; /* 模式:循環模式/普通模式 */ uint32_t Priority; /* DMA 優先級:低/中/高/非常高 */ }DMA_InitTypeDef;__HAL_LINKDMA(&g_uart1_handler, hdmatx, g_dma_handle);
-
使能串口的DMA發送,啟動傳輸
HAL_UART_Transmit_DMA()HAL_StatusTypeDef HAL_UART_DMAStop(UART_HandleTypeDef *huart); /* 停止 */ HAL_StatusTypeDef HAL_UART_DMAPause(UART_HandleTypeDef *huart); /* 暫停 */ HAL_StatusTypeDef HAL_UART_DMAResume(UART_HandleTypeDef *huart); /* 恢復 */
-
查詢DMA傳輸狀態
//查詢DMA傳輸通道的狀態 __HAL_DMA_GET_FLAG(&g_dma_handle, DMA_FLAG_TC4);//獲取當前傳輸剩余數據量 __HAL_DMA_GET_COUNTER(&g_dma_handle);//設置對應的DMA數據流傳輸的數據量大小 __HAL_DMA_SET_COUNTER (&g_dma_handle, 1000);
-
DMA中斷使用
//通用中斷處理函數 void HAL_DMA_IRQHandler();//相關中斷回調函數 //發送完成回調函數 void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart); //發送一半回調函數 void HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef *huart); //接收完成回調函數 void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart); //接收一半回調函數 void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart); //傳輸出錯回調函數 void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart);
6. 代碼實現
-
功能
每按下按鍵KEY0,串口1就會以DMA方式發送數據,同時在LCD上面顯示傳送進度。打開串口調試助手,可以收到DMA發送的內容。LED0閃爍用于提示程序正在運行。
-
DMA初始化函數
void dma_init(DMA_Channel_TypeDef* DMAx_CHx) {if((uint32_t)DMAx_CHx > (uint32_t)DMA1_Channel7){__HAL_RCC_DMA2_CLK_ENABLE();}else{__HAL_RCC_DMA2_CLK_ENABLE();}//將DMA與USART1連接起來__HAL_LINKDMA(&g_uart1_handle, hdmatx, g_dma_handle); g_dma_handle.Instance = DMAx_CHx; //USART1_TX使用的DMA通道為DMA_Channel4g_dma_handle.Init.Direction = DMA_MEMORY_TO_PERIPH; //模式選擇為從存儲器到外設g_dma_handle.Init.PeriphInc = DMA_PINC_DISABLE; //外設非增量模式g_dma_handle.Init.MemInc = DMA_MINC_ENABLE; //存儲器增量模式g_dma_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; //外設數據長度為8位g_dma_handle.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; //存儲器數據長度為8位g_dma_handle.Init.Priority = DMA_PRIORITY_MEDIUM; //中等優先級HAL_DMA_Init(&g_dma_handle); }
-
主函數
int main(void) {uint16_t i, k;uint16_t len;uint8_t mask = 0;float pro = 0; /* 進度 */HAL_Init(); /* 初始化HAL庫 */sys_stm32_clock_init(RCC_PLL_MUL9); /* 設置時鐘, 72Mhz */delay_init(72); /* 延時初始化 */usart_init(115200); /* 串口初始化為115200 */led_init(); /* 初始化LED */lcd_init(); /* 初始化LCD */key_init(); /* 初始化按鍵 */dma_init(DMA1_Channel4); /* 初始化串口1 TX DMA */lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);lcd_show_string(30, 70, 200, 16, 16, "DMA TEST", RED);lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);lcd_show_string(30, 110, 200, 16, 16, "KEY0:Start", RED);len = sizeof(TEXT_TO_SEND);k = 0;for (i = 0; i < SEND_BUF_SIZE; i++) /* 填充ASCII字符集數據 */{if (k >= len) /* 入換行符 */{if (mask){g_sendbuf[i] = 0x0a;k = 0;}else{g_sendbuf[i] = 0x0d;mask++;}}else /* 復制TEXT_TO_SEND語句 */{mask = 0;g_sendbuf[i] = TEXT_TO_SEND[k];k++;}}i = 0;while (1){if (key_scan(0) == 1) /* KEY0按下 */{printf("\r\nDMA DATA:\r\n");lcd_show_string(30, 130, 200, 16, 16, "Start Transimit....", BLUE);lcd_show_string(30, 150, 200, 16, 16, " %", BLUE); /* 顯示百分號 */HAL_UART_Transmit_DMA(&g_uart1_handle, g_sendbuf, SEND_BUF_SIZE);/* 等待DMA傳輸完成,此時我們來做另外一些事情,比如點燈 * 實際應用中,傳輸數據期間,可以執行另外的任務 */while (1){if ( __HAL_DMA_GET_FLAG(&g_dma_handle, DMA_FLAG_TC4)) /* 等待 DMA1_Channel4 傳輸完成 */{__HAL_DMA_CLEAR_FLAG(&g_dma_handle, DMA_FLAG_TC4);HAL_UART_DMAStop(&g_uart1_handle); /* 傳輸完成以后關閉串口DMA */break;}pro = DMA1_Channel4->CNDTR; /* 得到當前還剩余多少個數據 */len = SEND_BUF_SIZE; /* 總長度 */pro = 1 - (pro / len); /* 得到百分比 */pro *= 100; /* 擴大100倍 */lcd_show_num(30, 150, pro, 3, 16, BLUE);} lcd_show_num(30, 150, 100, 3, 16, BLUE); /* 顯示100% */lcd_show_string(30, 130, 200, 16, 16, "Transimit Finished!", BLUE); /* 提示傳送完成 */}i++;delay_ms(10);if (i == 20){LED0_TOGGLE(); /* LED0閃爍,提示系統正在運行 */i = 0;}} }
-
實驗結果