知識
DMA(Direct Memory Access,直接存儲器訪問) 用於在外設與存儲器之間以及存儲器與存儲器之間提供高速數據傳輸。
可以在無需任何 CPU 操作的情況下通過 DMA 快速移動數據。這樣節省的 CPU 資源可供其它操作使用。
DMA參數
傳輸模式:數據從哪里搬到哪里。三種可能的傳輸方向:存儲器到外設、外設到存儲器或存儲器到存儲器。
通道選擇:數據傳輸的是走哪條道路。
仲裁器:多個DMA傳輸時,優先級高的優先傳輸。
數據長度:每次傳輸的數據長度,可以1個字節,2個字節(半字),4個字節(字)
指針遞增:使能此模式以后,則下次傳輸的地址將是前一次傳輸的地址遞增 1(對於字節)、2(對於半字)或4(對於字)。
CubeMX 配置 DMA(以 USART2 為例)
配置
添加串口以及中斷
Pinout & Configuration頁中的Connectivity
,選擇USART2
:
-
Mode
:Asynchronous
(異步);Hardware Flow Control
(硬件流控) 選擇Disable
-
Configuration
-Parameter Settings
中 (任意設置都可以,但通訊雙方要匹配)- Baud Rate : 波特率,一般使用
115200
- Word Length : 字長 8
- Parity: 校驗
- Stop Bits : 停止位
- Baud Rate : 波特率,一般使用
-
Configuration
-NVIC Settings
中 : 勾選Enabled
(開啟中斷)
配置DMA
Configuration
-DMA Settings
中,點擊ADD
分別將USART2_RX
、USART_TX
添加到DMA Request
表中:Priority
:設置優先級為Low
DMA Request Settings
-Mode
:Normal
:只傳送一次(選擇此項)Circular
:不停地傳送(循環模式)Data Width
:選擇Byte
。
生產中斷初始化函數
在Pinout & Configuration中,System Core
,選擇NVIC
:
Configuration
-Parameter Settings
中 ,確認UsartX global interrupt
的Enable
是勾選的Configuration
-Code generation
中,確認UsartX global interrupt
、DMA1 channel 7 global interrupt
、DMA1 channel 6 global interrupt
的Select for init sequence ordering
是勾選的。
收尾工作
填寫有關的項目屬性、點擊右上角的GENERATE CODE
。
添加代碼
stm32f1xx_it.h
添加全局變量
實際上,這些變量可以封裝成函數,但是為了減少教程的篇幅,這里沒有這么做
/* USER CODE BEGIN EC */
/* 以下變量與DMA接收流程有關 */
volatile uint8_t rx_len = 0; // 獲取收到的數據長度
volatile uint8_t recv_end_flag = 0;
uint8_t rx_buffer[200] ;
/* USER CODE END EC */
stm32f1xx_it.c
修改中斷處理函數
void USART2_IRQHandler(void)
{
/* USAR CODE BEGIN USART2_IRQn 0 */
uint32_t tmp_flag = 0;
uint32_t temp;
tmp_flag =__HAL_UART_GET_FLAG(&huart2,UART_FLAG_IDLE); //獲取IDLE標志位
if((tmp_flag != RESET))//idle標志被置位
{
__HAL_UART_CLEAR_IDLEFLAG(&huart2);//清除標志位
temp = huart2.Instance->SR; //清除狀態寄存器SR,讀取SR寄存器可以實現清除SR寄存器的功能
temp = huart2.Instance->DR; //讀取數據寄存器中的數據
HAL_UART_DMAStop(&huart2); //
temp = hdma_usart2_rx.Instance->CNDTR;// 獲取DMA中未傳輸的數據個數,CNDTR寄存器分析見下面
rx_len = sizeof(rx_buffer) - temp; //總計數減去未傳輸的數據個數,得到已經接收的數據個數
recv_end_flag = 1; // 接受完成標志位置1
}
/* USAR CODE END USART2_IRQn 0 */
HAL_UART_IRQHandler(&huart2);
/* USAR CODE BEGIN USART2_IRQn 1 */
/* USAR CODE END USART2_IRQn 1 */
}
main.c
引入全局變量
/* USER CODE BEGIN PV */
/* 以下變量與DMA接收流程有關 */
extern volatile uint8_t rx_len; // 獲取收到的數據長度
extern volatile uint8_t recv_end_flag;
extern uint8_t rx_buffer[200] ;
/* USER CODE END PV */
添加printf
重定向
/* USER CODE BEGIN 0 */
#include "stdio.h"
#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif
/* 重定向printf */
PUTCHAR_PROTOTYPE
{
HAL_UART_Transmit(&huart2, (uint8_t*)&ch,1,HAL_MAX_DELAY);
return ch;
}
/* 重定向scanf */
int fgetc(FILE *f)
{
uint8_t ch = 0;
HAL_UART_Receive(&huart2, &ch, 1, 0xffff);
return ch;
}
/* USER CODE END 0 */
添加DMA初始化
/* USER CODE BEGIN 2 */
// 在 MX_USART2_UART_Init 之后添加這2行
__HAL_UART_ENABLE_IT(&huart2, UART_IT_IDLE); //使能idle中斷
HAL_UART_Receive_DMA(&huart2,rx_buffer, sizeof(rx_buffer)); //打開DMA接收,數據會存入rx_buffer數組中。(刪掉會導致第一次DMA異常)
/* USER CODE END 2 */
使用(在需要調用的地方,添加以下函數,以main
中的while(1)
為例):
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
if(recv_end_flag ==1)
{
//打印接收長度、數據
printf("rx_len=%d\r\n",rx_len);
printf("%s\r\n",rx_buffer);
for(uint8_t i=0;i<rx_len;i++)
{
rx_buffer[i]=0;//清接收緩存
}
rx_len=0;//清除計數
recv_end_flag=0;//清除接收結束標志位
}
HAL_UART_Receive_DMA(&huart2,rx_buffer,sizeof(rx_buffer));//重新打開DMA接收
}
/* USER CODE END 3 */
附錄:
typedef struct
{
__IO uint32_t CCR; /*!< DMA stream x configuration register */
__IO uint32_t CNDTR; /*!< DMA stream x **number of data register** */
__IO uint32_t CPAR; /*!< DMA stream x peripheral address register */
__IO uint32_t CMAR; /*!< DMA stream x memory address register */
} DMA_Channel_TypeDef;