STM32CubeMX學習筆記(7)——DMA接口使用

一、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. 配置調試模式
非常重要的一步,否則會造成第一次燒錄程序后續(xù)無法識別調試器
SYS 設置,選擇 Debug 為 Serial Wire

四、DMA1

4.1 配置串口

Connectivity 中選擇 USART1 設置,并選擇 Asynchronous 異步通信


波特率為 115200 Bits/s。傳輸數據長度為 8 Bit。奇偶檢驗 None,停止位 1 ,接收和發(fā)送都使能

使能串口接收中斷

4.2 配置DMA


點擊 DMA Settings 添加 USART1 TX 和 USART1 RX 分別對應DMA1 的通道4和通道5。

  • Priority
    當發(fā)生多個 DMA 通道請求時,就意味著有先后響應處理的順序問題,這個就由仲裁器也管理。仲裁器管理 DMA 通道請求分為兩個階段。第一階段屬于軟件階段,可以在 DMA_CCRx 寄存器中設置,有 4 個等級:非常高、高、中和低四個優(yōu)先級。第二階段屬于硬件階段,如果兩個或以上的 DMA 通道請求設置的優(yōu)先級一樣,則他們優(yōu)先級取決于通 道編號,編號越低優(yōu)先權越高,比如通道 0 高于通道 1。在大容量產品和互聯型產品中,DMA1 控制器擁有高于 DMA2 控制器的優(yōu)先級。
  • Mode
    Normal 表示單次傳輸,傳輸一次后終止傳輸。
    Circular 表示循環(huán)傳輸,傳輸完成后又重新開始繼續(xù)傳輸,不斷循環(huán)永不停止。
  • Increment Address
    Peripheral 表示外設地址自增。
    Memory 表示內存地址自增。
    串口發(fā)送數據是將數據不斷存進串口的發(fā)送數據寄存器(USARTx_TDR)。所以外接的地址是不遞增。而內存儲器存儲的是要發(fā)送的數據,所以地址指針要遞增才能將所以的數據發(fā)送出去。
  • Data Width
    Byte 一個字節(jié)。
    Half Word 半個字,等于兩字節(jié)。
    Word 一個字,等于四字節(jié)。
    串口數據發(fā)送寄存器只能存儲8bit,每次發(fā)送一個字節(jié),所以數據長度選擇Byte。

4.3 生成代碼

輸入項目名和項目路徑


選擇應用的 IDE 開發(fā)環(huán)境 MDK-ARM V5

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

點擊 GENERATE CODE 生成代碼

4.4 USART+DMA數據發(fā)送

新建一個變量

uint8_t sendBuff[] = "USART test by DMA\r\n";

在 man.c 中的主循環(huán)添加以下代碼:

  /* 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 */
}

通過串口助手可以看到在接收區(qū)有數據不斷的打印輸出


注意:如果不開啟串口中斷,則程序只能發(fā)送一次數據,程序不能判斷DMA傳輸是否完成,USART一直處于busy狀態(tài)。

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 循環(huán)前,串口初始化后,添加接收中斷開啟函數,這樣在第一次接收到數據的時候才會觸發(fā)中斷。

/**
  * @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 */

通過串口助手發(fā)送 OK,可以看到接收到 O,這是因為設置的接收數據是一個字符,如果要接收更多字符,請加大 Buffer。


4.6 串口IDLE空閑中斷+DMA數據接收

特點:

  • 可以實現任意字符串接收并輸出。
  • 在串口無數據接收的情況下,不會產生,當清除IDLE標志位后,必須有接收到第一個數據后,才開始觸發(fā),一但接收的數據斷流,沒有接收到數據,即產生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 循環(huán)前,串口初始化后,添加空閑中斷和DMA接收開啟函數,這樣在第一次接收到數據的時候才會觸發(fā)中斷。

/**
  * @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 */
}

通過串口助手發(fā)送不定長數據


五、注意事項

用戶代碼要加在 USER CODE BEGIN NUSER CODE END N 之間,否則下次使用 STM32CubeMX 重新生成代碼后,會被刪除。


? 由 Leung 寫于 2021 年 1 月 18 日

? 參考:STM32CubeMX系列教程6:直接存儲器訪問 (DMA)
    《嵌入式-STM32開發(fā)指南》第二部分 基礎篇 - 第7章DMA(HAL庫)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容