DMA介紹
什么是DMA?
????????DMA(Direct Memory Access,直接存儲器訪問)提供在外設與內存、存儲器和存儲器之間的高速數據傳輸使用。它允許不同速度的硬件裝置來溝通,而不需要依賴于CPU,在這個時間中,CPU對于內存的工作來說就無法使用。
????????簡單描述: 就是一個數據搬運工!
DMA的意義
代替 CPU 搬運數據,為 CPU 減負。
1.數據搬運的工作比較耗時間;
2. 數據搬運工作時效要求高(有數據來就要搬走);
3. 沒啥技術含量(CPU 節約出來的時間可以處理更重要的事)。
搬運數據的方式
有三種方式:存儲器到存儲器、存儲器到外設、外設到存儲器
- 存儲器→存儲器(例如:復制某特別大的數據buf)
- 存儲器→外設 (例如:將某數據buf寫入串口TDR寄存器)
- 外設→存儲器 (例如:將串口RDR寄存器寫入某數據buf)。
????????這里的外設指的是spi、usart、iic、adc 等基于APB1 、APB2或AHB時鐘的外設,而這里的存儲器包括自身的閃存(flash)或者內存(SRAM)以及外設的存儲設備都可以作為訪問地源或者目的地。
存儲器→存儲器
存儲器→外設?
外設→存儲器?
?DMA框圖
說明:利用DMA進行外設的數據搬運,首先,外設需向DMA1進項請求,然后,經過DMA的仲裁之后,DMA訪問外設的數據進行搬運。?
DMA控制器?
注意:
一個通道每次只能搬運一個外設的數據!! 如果同時有多個外設的 DMA 請求,則按照優先級進行響應。 STM32F103C8T6 只有 DMA1 !
- DMA1有7個通道:
- DMA2 有 5 個通道:?
DMA優先級管理?
- 優先級管理采用軟件+硬件:
軟件: 每個通道的優先級可以在DMA_CCRx寄存器中設置,有4個等級:最高級>高級>中級>低級。
硬件: 如果2個請求,它們的軟件優先級相同,則較低編號的通道比較高編號的通道有較高的優先權。比如:如果軟件優先級相同,通道2優先于通道4。
?DMA傳輸方式與指針遞增模式
- 傳輸方式
DMA_Mode_Normal (正常模式) | 一次DMA數據傳輸完后,停止DMA傳送 ,也就是只傳輸一次。 |
DMA_Mode_Circular (循環傳輸模式) | 當傳輸結束時,硬件自動會將傳輸數據量寄存器進行重裝,進行下一輪的數據傳輸。 也就是多次傳輸模式。 |
- 指針遞增模式
????????外設和存儲器指針在每次傳輸后可以自動向后遞增或保持常量。當設置為增量模式時,下一個要傳輸的地址將是前一個地址加上增量值。
情況1:
?DMA數據對齊方式
- 數據寬度大的轉移到數據寬度小的時候,低位保留高位截斷
DMA寄存器?
- ?DMA中斷狀態寄存器(DMA_ISR)
- DMA中斷標志清除寄存器(DMA_IFCR)?
- ?DMA通道x配置寄存器(DMA_CCRx)(x = 1…7)
- ?DMA通道x傳輸數量寄存器(DMA_CNDTRx)(x = 1…7)
?16位寄存器,最多可以傳輸數量65536。
- DMA通道x外設地址寄存器(DMA_CPARx)(x = 1…7)
- DMA通道x存儲器地址寄存器(DMA_CMARx)(x = 1…7)?
DMA的庫函數
在hal.dma.c文件中的一些常用的函數:
打開dma1時鐘的函數:?
在hal.dma.h文件中的一些常用的宏函數:?
在hal_dma_ex.h文件中,獲取?傳輸完成標志位
在hal.def.h文件中還存在所需的下面的函數:?
?若要讀取外設的數據還需要相關串口的函數,如下:
?小實驗1:DMA內存到內存數據搬運
?實驗目的
使用DMA將一個大數組的數組搬運到另一個位置。
硬件清單
開發板、ST-Link、USB轉TTL
配置流程
文件代碼?
- dma.c文件代碼
#include "dma.h"
#include "stdio.h"#define BUF_SIZE 16uint32_t src_buf[BUF_SIZE] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};uint32_t dst_buf[BUF_SIZE] = {0};/**
* @breif DMA的初始化函數
* @note 打開時鐘,配置相關參數
* @param 無
* @retval 無
*/
DMA_HandleTypeDef dma_handle = {0};
void dma_init(void){__HAL_RCC_DMA1_CLK_ENABLE();dma_handle.Instance = DMA1_Channel1;dma_handle.Init.Direction = DMA_MEMORY_TO_MEMORY; /* 搬運數據的方式:內存到內存,外設到內存,內存到外設 *///內存相關的配置dma_handle.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; /* 源:內存數據對齊模式:一般是8位 */dma_handle.Init.MemInc = DMA_MINC_ENABLE; /* 源:內存數據指針遞增的方式 */dma_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; /* 目標:外設數據對齊模式:一般是8位 */dma_handle.Init.PeriphInc = DMA_PINC_ENABLE; /* 目標:外設數據指針遞增的方式 */dma_handle.Init.Mode = DMA_NORMAL; /* 傳輸的模式:循環,不循環。內存到內存不支持循環模式 */dma_handle.Init.Priority = DMA_PRIORITY_MEDIUM; /* 通道的優先級設置:有四種*/HAL_DMA_Init(&dma_handle);}/**
* @breif 封裝一個函數進行數據的轉運
* @note 利用DMA_Start函數進行數據的搬運,當搬運完成后查看標志位是否置1,然后進行打印
* @note 在DMA_Start()函數中,數據的長度要寫成sizeof(uint32_t)*BUF_SIZE,不能寫成BUF_SIZE
* @param 無
* @retval 無
*/
void dma_transmit(void){HAL_DMA_Start(&dma_handle,(uint32_t)src_buf,(uint32_t)dst_buf,sizeof(uint32_t)*BUF_SIZE);while(__HAL_DMA_GET_FLAG(&dma_handle,DMA_FLAG_TC1) == RESET);for(uint16_t i = 0;i < BUF_SIZE;i++){printf("傳輸的數據是:%d \r\n",dst_buf[i]);}
}
- dma.h文件代碼?
#ifndef __DMA_H__
#define __DMA_H__
#include "stm32f1xx.h"void dma_init(void);
void dma_transmit(void);#endif
- mian.c文件代碼?
#include "sys.h"
#include "led.h"
#include "delay.h"
#include "uart1.h"
#include "dma.h"int main(void)
{HAL_Init(); /* 初始化HAL庫 */stm32_clock_init(RCC_PLL_MUL9); /* 設置時鐘, 72Mhz */led_init(); /* LED初始化 */uart1_init(115200);printf("hello,world");dma_init();dma_transmit();while(1){ }
}
?注意事項:
- 關于數據傳輸中最后一行出現異常值(如從15突然跳變到1073872904),如下所示:
原因:緩沖區溢出或內存越界
解決方式:將上面的等號去掉,就會正常。
- *****關于dma.c文件中的while循環中的判斷條件:==RESET,而不能用!=SET*****
原因:!= SET 還可能代表其他未定義的狀態,例如:硬件錯誤、無效參數,導致條件判斷不準確。
因此,要直接使用官方推薦條件,可避免兼容性的問題。
小實驗2:內存到外設數據轉運
?實驗目的
使用DMA將一個大數據通過串口1發送
硬件清單
開發板、ST-Link、USB轉TTL
配置流程?
文件代碼?
- ?dma.c文件代碼
#include "dma.h"
#include "stdio.h"
extern UART_HandleTypeDef uart1_handle;/**
* @breif DMA的初始化函數
* @note 打開時鐘,配置相關參數
* @param 無
* @retval 無
*/
DMA_HandleTypeDef dma_handle = {0};
void dma_init(void){__HAL_RCC_DMA1_CLK_ENABLE();dma_handle.Instance = DMA1_Channel4; /* 查看表格:看所需的DMA通道:通道4, */dma_handle.Init.Direction = DMA_MEMORY_TO_PERIPH; /* 搬運數據的方式:內存到內存,外設到內存,內存到外設 *///內存相關的配置dma_handle.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; /* 源:內存數據對齊模式:一般是8位 */dma_handle.Init.MemInc = DMA_MINC_ENABLE; /* 源:內存數據指針遞增的方式 */dma_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; /* 目標:外設數據對齊模式:一般是8位 */dma_handle.Init.PeriphInc = DMA_PINC_DISABLE; /* 目標:串口發送寄存器數據指針是不能遞增的 */dma_handle.Init.Mode = DMA_NORMAL; /* 傳輸的模式:循環,不循環。內存到內存不支持循環模式 */dma_handle.Init.Priority = DMA_PRIORITY_MEDIUM; /* 通道的優先級設置:有四種*/HAL_DMA_Init(&dma_handle);__HAL_LINKDMA(&uart1_handle,hdmatx,dma_handle); /* 將內存和外設的地址進行連接,注意:dma句柄前面不用加&*/
}/**
* @breif 封裝一個函數進行數據的轉運
* @note 利用DMA_Start函數進行數據的搬運,當搬運完成后查看標志位是否置1,然后進行打印
* @note 在DMA_Start()函數中,數據的長度要寫成sizeof(uint32_t)*BUF_SIZE,不能寫成BUF_SIZE
* @param 無
* @retval 無
*/
?dma.h文件代碼
#ifndef __DMA_H__
#define __DMA_H__
#include "stm32f1xx.h"void dma_init(void);#endif
- main.c文件代碼?
#include "sys.h"
#include "led.h"
#include "delay.h"
#include "uart1.h"
#include "dma.h"extern UART_HandleTypeDef uart1_handle;
uint8_t send_buf[1000] = {0};int main(void)
{HAL_Init(); /* 初始化HAL庫 */stm32_clock_init(RCC_PLL_MUL9); /* 設置時鐘, 72Mhz */led_init(); /* LED初始化 */uart1_init(115200);
// printf("hello,world");dma_init();int i = 0;for(i = 0; i < 1000;i++){send_buf[i] = 'A';}HAL_UART_Transmit_DMA(&uart1_handle,send_buf,1000);while(1){ }
}
總結:
- 本代碼是將內存中的數據轉運到串口的發送數據的寄存器中,利用的函數HAL_UART_Transmit_DMA()函數,然后通過串口打印到串口調試助手的界面上。
- 與利用printf函數不同;
- 在將數據由內存轉運到內存中時,利用的是HAL_DMA_Start()函數,通過判斷傳輸完成標志位的函數__HAL_DMA_GET_FLAG()函數 ==SET(表明數據傳輸完成)。,然后利用printf()函數將數據打印出來。
- 在dma.c和main.c文件中用到串口初始化函數的句柄,所以,要注意利用extern聲明一下外部變量。
- 實驗現象
小實驗3:DMA外設到寄存器內存數據搬運?
實驗目的
使用DMA接收串口的數據
硬件清單
開發板、ST-Link、USB轉TTL?
函數的意義:聲明一個接收緩沖區(數組);這個函數返回接收完數據后剩余的數組長度。
可以用來計算,傳輸數據的長度:?
例:
uart1_rx_len = UART1_RX_BUF_SIZE-__HAL_DMA_GET_COUNTER(&dma_handle);
配置流程?
文件代碼?
- dma.c文件代碼
#include "dma.h"
#include "stdio.h"
extern UART_HandleTypeDef uart1_handle;extern uint8_t uart1_rx_buf[UART1_RX_BUF_SIZE]; /* UART1接收緩沖區 *//**
* @breif DMA的初始化函數
* @note 打開時鐘,配置相關參數
* @param 無
* @retval 無
*/
DMA_HandleTypeDef dma_handle = {0};
void dma_init(void){__HAL_RCC_DMA1_CLK_ENABLE();dma_handle.Instance = DMA1_Channel5; /* 查看表格:UART1_RX的DMA通道:通道5 */dma_handle.Init.Direction = DMA_PERIPH_TO_MEMORY; /* 搬運數據的方式:內存到內存,外設到內存,內存到外設 *///內存相關的配置dma_handle.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; /* 源:內存數據對齊模式:一般是8位 */dma_handle.Init.MemInc = DMA_MINC_ENABLE; /* 源:內存數據指針遞增的方式 */dma_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; /* 目標:外設數據對齊模式:一般是8位 */dma_handle.Init.PeriphInc = DMA_PINC_DISABLE; /* 目標:串口發送寄存器數據指針是不能遞增的 */dma_handle.Init.Mode = DMA_NORMAL; /* 傳輸的模式:循環,不循環。內存到內存不支持循環模式 */dma_handle.Init.Priority = DMA_PRIORITY_MEDIUM; /* 通道的優先級設置:有四種*/HAL_DMA_Init(&dma_handle);__HAL_LINKDMA(&uart1_handle,hdmarx,dma_handle); /* 將內存和外設的地址進行連接,注意:dma句柄前面不用加&*/ HAL_UART_Receive_DMA(&uart1_handle,uart1_rx_buf,UART1_RX_BUF_SIZE); /* 打開串口的DMA數據轉運,*/
}
- ?dma.h文件代碼
#ifndef __DMA_H__
#define __DMA_H__
#include "stm32f1xx.h"#define UART1_RX_BUF_SIZE 128
void dma_init(void);#endif
- uart1.c文件代碼?
- main.c文件代碼?
#include "sys.h"
#include "led.h"
#include "delay.h"
#include "uart1.h"
#include "dma.h"int main(void)
{HAL_Init(); /* 初始化HAL庫 */stm32_clock_init(RCC_PLL_MUL9); /* 設置時鐘, 72Mhz */led_init(); /* LED初始化 */uart1_init(115200);printf("hello,world\r\n");dma_init();while(1){ }
}
注意事項:
- 注意在串口中斷函數中書寫的DMA轉運數據的流程。?