一、DMA簡介
DMA(Direct Memory Access) 直接存盤器存取,是單片機的一個外設,它的主要功能是用來搬資料,但是不需要占用 CPU,即在傳輸資料的時候,CPU 可以干其他的事情,好像是多執行緒一樣,資料傳輸支持從外設到存盤器或者存盤器到存盤器,這里的存盤器可以是 SRAM 或者是 FLASH,DMA 控制器包含了 DMA1 和 DMA2,其中 DMA1 有 7 個通道,DMA2 有 5 個通道,這里的通道可以理解為傳輸資料的一種管道, 要注意的是 DMA2 只存在于大容量的單片機中,

二、DMA請求映像
DMA1 各個通道的請求映像

DMA2 各個通道的請求映像

其中 ADC3、SDIO 和 TIM8 的 DMA 請求只在大容量產品中存在,這個在具體專案時 要注意,
三、新建工程
1. 打開 STM32CubeMX 軟體,點擊“新建工程”

2. 選擇 MCU 和封裝

3. 配置時鐘
RCC 設定,選擇 HSE(外部高速時鐘) 為 Crystal/Ceramic Resonator(晶振/陶瓷諧振器)

選擇 Clock Configuration,配置系統時鐘 SYSCLK 為 72MHz
修改 HCLK 的值為 72 后,輸入回車,軟體會自動修改所有配置

4. 配置除錯模式
非常重要的一步,否則會造成第一次燒錄程式后續無法識別除錯器
SYS 設定,選擇 Debug 為 Serial Wire

四、DMA1
4.1 配置串口
在 Connectivity 中選擇 USART1 設定,并選擇 Asynchronous 異步通信

波特率為 115200 Bits/s,傳輸資料長度為 8 Bit,奇偶檢驗 None,停止位 1 ,接收和發送都使能,

使能串口接收中斷

4.2 配置DMA

點擊 DMA Settings 添加 USART1 TX 和 USART1 RX 分別對應DMA1 的通道4和通道5,
- Priority:
當發生多個 DMA 通道請求時,就意味著有先后回應處理的順序問題,這個就由仲裁器也管理,仲裁器管理 DMA 通道請求分為兩個階段,第一階段屬于軟體階段,可以在 DMA_CCRx 暫存器中設定,有 4 個等級:非常高、高、中和低四個優先級,第二階段屬于硬體階段,如果兩個或以上的 DMA 通道請求設定的優先級一樣,則他們優先級取決于通 道編號,編號越低優先權越高,比如通道 0 高于通道 1,在大容量產品和互聯型產品中,DMA1 控制器擁有高于 DMA2 控制器的優先級, - Mode:
Normal表示單次傳輸,傳輸一次后終止傳輸,
Circular表示回圈傳輸,傳輸完成后又重新開始繼續傳輸,不斷回圈永不停止, - Increment Address:
Peripheral表示外設地址自增,
Memory表示記憶體地址自增,
串口發送資料是將資料不斷存進串口的發送資料暫存器(USARTx_TDR),所以外接的地址是不遞增,而記憶體儲器存盤的是要發送的資料,所以地址指標要遞增才能將所以的資料發送出去, - Data Width:
Byte一個位元組,
Half Word半個字,等于兩位元組,
Word一個字,等于四位元組,
串口資料發送暫存器只能存盤8bit,每次發送一個位元組,所以資料長度選擇Byte,

4.3 生成代碼
輸入專案名和專案路徑

選擇應用的 IDE 開發環境 MDK-ARM V5

每個外設生成獨立的 ’.c/.h’ 檔案
不勾:所有初始化代碼都生成在 main.c
勾選:初始化代碼生成在對應的外設檔案, 如 GPIO 初始化代碼生成在 gpio.c 中,

點擊 GENERATE CODE 生成代碼

4.4 USART+DMA資料發送
新建一個變數
uint8_t sendBuff[] = "USART test by DMA\r\n";
在 man.c 中的主回圈添加以下代碼:
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
HAL_UART_Transmit_DMA(&huart1, (uint8_t *)sendBuff, sizeof(sendBuff));
HAL_Delay(1000);
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
通過串口助手可以看到在接收區有資料不斷的列印輸出

注意:如果不開啟串口中斷,則程式只能發送一次資料,程式不能判斷DMA傳輸是否完成,USART一直處于busy狀態,
4.5 USART+DMA資料接收
在 main.c 頭部添加全域變數 Buffer
/* Private variables ---------------------------------------------------------*/
UART_HandleTypeDef huart1;
/* USER CODE BEGIN PV */
uint8_t Buffer[1];
/* USER CODE END PV */
在 stm32f1xx_it.c 頭部宣告全域變數 Buffer
/* External variables --------------------------------------------------------*/
extern UART_HandleTypeDef huart1;
/* USER CODE BEGIN EV */
extern uint8_t Buffer[1];
/* USER CODE END EV */
在 main.c 中,while 回圈前,串口初始化后,添加接收中斷開啟函式,這樣在第一次接收到資料的時候才會觸發中斷,
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_DMA_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
HAL_UART_Receive_DMA(&huart1, (uint8_t *)Buffer, 1);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
在 stm32f1xx_it.c 這個檔案的最下面添加 HAL_UART_RxCpltCallback()
/* USER CODE BEGIN 1 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART1)
{
HAL_UART_Receive_DMA(&huart1, (uint8_t *)Buffer, 1);
HAL_UART_Transmit_DMA(&huart1, (uint8_t *)Buffer, 1);
}
}
/* USER CODE END 1 */
通過串口助手發送 OK,可以看到接收到 O,這是因為設定的接收資料是一個字符,如果要接收更多字符,請加大 Buffer,

4.6 串口IDLE空閑中斷+DMA資料接收
特點:
- 可以實作任意字串接收并輸出,
- 在串口無資料接收的情況下,不會產生,當清除IDLE標志位后,必須有接收到第一個資料后,才開始觸發,一但接收的資料斷流,沒有接收到資料,即產生IDLE中斷,
在 main.c 中添加以下變數:
uint8_t recvBuff[BUFFER_SIZE]; //接收資料快取陣列
volatile uint8_t recvLength = 0; //接收一幀資料的長度
volatile uint8_t recvDndFlag = 0; //一幀資料接收完成標志
在 main.h 中添加以下宏定義與變數:
#define BUFFER_SIZE 256
extern uint8_t recvBuff[BUFFER_SIZE]; //接收資料快取
extern volatile uint8_t recvLength; //接收一幀資料的長度
extern volatile uint8_t recvDndFlag; //一幀資料接收完成標志
在 main.c 中,while 回圈前,串口初始化后,添加空閑中斷和DMA接收開啟函式,這樣在第一次接收到資料的時候才會觸發中斷,
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_DMA_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); //使能IDLE中斷
HAL_UART_Receive_DMA(&huart1, recvBuff, BUFFER_SIZE);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
在 stm32f1xx_it.c 這個檔案的最下面修改 USART1_IRQHandler()
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
uint32_t tmpFlag = 0;
uint32_t temp;
tmpFlag =__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE); //獲取IDLE標志位
if((tmpFlag != RESET))//idle標志被置位
{
__HAL_UART_CLEAR_IDLEFLAG(&huart1);//清除標志位
HAL_UART_DMAStop(&huart1); //
temp = __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);// 獲取DMA中未傳輸的資料個數
recvLength = BUFFER_SIZE - temp; //總計數減去未傳輸的資料個數,得到已經接收的資料個數
recvDndFlag = 1; // 接受完成標志位置1
HAL_UART_Transmit_DMA(&huart1, recvBuff, recvLength);
recvLength = 0;//清除計數
recvDndFlag = 0;//清除接收結束標志位
memset(recvBuff,0,recvLength);
HAL_UART_Receive_DMA(&huart1, recvBuff, BUFFER_SIZE);//重新打開DMA接收,不然只能接收一次資料
}
/* USER CODE END USART1_IRQn 0 */
HAL_UART_IRQHandler(&huart1);
/* USER CODE BEGIN USART1_IRQn 1 */
/* USER CODE END USART1_IRQn 1 */
}
通過串口助手發送不定長資料

五、注意事項
用戶代碼要加在 USER CODE BEGIN N 和 USER CODE END N 之間,否則下次使用 STM32CubeMX 重新生成代碼后,會被洗掉,

? 由 Leung 寫于 2021 年 1 月 18 日
? 參考:STM32CubeMX系列教程6:直接存盤器訪問 (DMA)
《嵌入式-STM32開發指南》第二部分 基礎篇 - 第7章DMA(HAL庫)
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/250741.html
標籤:其他

