單片機
返回首頁

STM32—DMA存儲器到外設

2021-09-02 來源:eefocus

DMA簡介

DMA(Direct Memory Access)——直接存儲器存取,就像其名稱一樣,DMA的主要作用是搬數據,DMA可以把數據從存儲器搬到外設、從外設搬到存儲器、從存儲器搬到存儲器。DMA的特殊之處就是搬運數據不需要占用CPU,DMA控制器包含了DMA1和DMA2,其中DMA1由7個通道,DMA2有5個通道。


DMA框圖

了解外設先要理解其工作框圖:

在這里插入圖片描述

功能框圖主要分為三部分:

1.DMA請求

外設如果想要通過DMA傳輸數據,必先給DMA控制器發送DMA請求,DMA收到請求信號之后會傳回給外設一個應答信號,當外設應答后且DMA控制器收到應答信號后,就會啟動DMA的傳輸,直至傳輸完畢。但DMA有2個DMA控制器,15條通道,不同的通道對應著不同的外設請求,所以必須對通道和外設請求進行一一對口,各個通道對應的外設如下:

在這里插入圖片描述

2.通道

要注意的是DMA共有12個獨立可編程的通道,DMA1有7個、DMA2有5個,每個通道對應著不同的外設的請求,但同一時間只能接收一個。

3.仲裁器

當多個通道同時請求時,就要處理先后響應的問題,就像中斷里的優先級分組一樣。仲裁器管理DMA通道請求時主要依據倆點:

第一判斷:在DMA_CCRx 寄存器中設置有 4 個等級:非常高、高、中和低,先根據此優先級判斷響應的先后,如果優先級都一樣,進行第二判斷。

第二判斷:比較通道的編號,編號越低優先權越高。


DMA傳輸數據分析

使用DMA,核心技術就是配置數據的傳輸,主要分為三點:

1.傳輸的方向

2.傳輸的數量

3.傳輸的模式


1.傳輸的方向

DMA有三種數據傳輸方向:


一:存儲器——>外設

當我們使用從存儲器到外設傳輸時,以串口向電腦端發送數據為例。 DMA 外設寄存器的地址對應的就是串口數據寄存器的地址, DMA 存儲器的地址就是我們自定義的變量(相當于一個緩沖區,用來存儲通過串口發送到電腦的數據)的地址。方向我們設置外設為目

標地址。


二:外設——>存儲器

當我們使用從外設到存儲器傳輸時,以 ADC 采集為例。 DMA 外設寄存器的地址對應的就是 ADC 數據寄存器的地址, DMA 存儲器的地址就是我們自定義的變量(用來接收存儲 AD 采集的數據)的地址。方向我們設置外設為源地址。


三:存儲器——>存儲器

當我們使用從存儲器到存儲器傳輸時,以內部 FLASH 向內部 SRAM 復制數據為例。DMA 外設寄存器的地址對應的就是內部 FLASH(我們這里把內部 FALSH 當作一個外設來看)的地址, DMA 存儲器的地址就是我們自定義的變量(相當于一個緩沖區,用來存儲來自內部 FLASH 的數據)的地址。方向我們設置外設(即內部 FLASH)為源地址。跟上面兩個不一樣的是,這里需要把 DMA_CCR 位 14: MEM2MEM:存儲器到存儲器模式配

置為 1,啟動 M2M 模式。


2.傳輸的數量

以串口向電腦發送數據為例,我們可以一次性給電腦發送很多數據,具體多少由DMA_CNDTR 配置,這是一個 32 位的寄存器,一次最多只能傳輸 65535 個數據。而且源和目標的數據寬度必須一致,數據寬度可以設置為8/16/32位。

除此之外,還要設置源和目標倆邊數據指針的增量模式 ,即傳輸完一個數據后數據指針的移動模式,是加一?還是不變?以串口向電腦發送數據為例,要發送的數據很多,每發送完一個,那么存儲器的地址指針就應該加 1,而串口數據寄存器只有一個,那么外設的地址指針就固定不變。


3.傳輸的模式

數據傳輸的情況可以通過查詢標志位或者通過中斷來鑒別,每個DMA通道在DMA傳輸過半、傳輸完成、傳輸錯誤時都會有相應的標志位,如果使能相關的中斷后還會產生中斷。一次數據傳輸完成后還分倆種模式:是一次傳輸還是循環傳輸。

一次傳輸: 傳輸一次后就停止,要想再傳的話,必須關閉DMA使能后重新配置后方能繼續傳輸。

循環傳輸: 一次傳輸完成后又恢復第一次傳輸時的配置循環傳輸,不斷重復。


代碼部分

編程要點:

1.配置USART通信功能

2.設置DMA工作參數

3.使能DMA


DMA初始化結構體

固件庫編程中對一個外設的操作主要通過配置外設的初始化結構體來完成,雖然最終操作的是寄存器,但相比較寄存器,還是采用庫函數比較方便。


 typedef struct

 {

 uint32_t DMA_PeripheralBaseAddr; // 外設地址

 uint32_t DMA_MemoryBaseAddr; // 存儲器地址

 uint32_t DMA_DIR; // 傳輸方向

 uint32_t DMA_BufferSize; // 傳輸數目

 uint32_t DMA_PeripheralInc; // 外設地址增量模式

 uint32_t DMA_MemoryInc; // 存儲器地址增量模式

 uint32_t DMA_PeripheralDataSize; // 外設數據寬度

 uint32_t DMA_MemoryDataSize; // 存儲器數據寬度

 uint32_t DMA_Mode; // 模式選擇

 uint32_t DMA_Priority; // 通道優先級

 uint32_t DMA_M2M; // 存儲器到存儲器模式

 } DMA_InitTypeDef;


下面利用《零死角玩轉 STM32F103》的話介紹一下結構體成員:

1.DMA_PeripheralBaseAddr:外設地址,設定 DMA_CPAR 寄存器的值;一般設置為外設的數據寄存器地址,如果是存儲器到存儲器模式則設置為其中一個存儲器地址。


== 2.DMA_Memory0BaseAddr == : 存儲器地址,設定 DMA_CMAR 寄存器值;一般設置為我們自定義存儲區的首地址。零死角玩轉 STM32F103—MINI


3.DMA_DIR:傳輸方向選擇,可選外設到存儲器、存儲器到外設。它設定

DMA_CCR 寄存器的 DIR[1:0]位的值。這里并沒有存儲器到存儲器的方向選擇,

當使用存儲器到存儲器時,只需要把其中一個存儲器當作外設使用即可。


4. DMA_BufferSize:設定待傳輸數據數目,初始化設定 DMA_CNDTR 寄存器的值。


5. DMA_PeripheralInc:如果配置為 DMA_PeripheralInc_Enable,使能外設地址自動遞增功能,它設定 DMA_CCR 寄存器的 PINC 位的值;一般外設都是只有一個數據寄存器,所以一般不會使能該位。


6. DMA_MemoryInc:如果配置為 DMA_MemoryInc_Enable,使能存儲器地址自動遞增功能,它設定 DMA_CCR 寄存器的 MINC 位的值;我們自定義的存儲區一般都是存放多個數據的,所以要使能存儲器地址自動遞增功能。


7.DMA_PeripheralDataSize:外設數據寬度,可選字節(8 位)、半字(16 位)和字(32位),它設定 DMA_CCR 寄存器的 PSIZE[1:0]位的值。


8. DMA_MemoryDataSize:存儲器數據寬度,可選字節(8 位)、半字(16 位)和字(32位),它設定 DMA_CCR 寄存器的 MSIZE[1:0]位的值。當外設和存儲器之間傳數據時,兩邊的數據寬度應該設置為一致大小。


9. DMA_Mode: DMA 傳輸模式選擇,可選一次傳輸或者循環傳輸,它設定

DMA_CCR 寄存器的 CIRC 位的值。例程我們的 ADC 采集是持續循環進行的,所

以使用循環傳輸模式。


10.DMA_Priority:軟件設置通道的優先級,有 4 個可選優先級分別為非常高、高、中和低,它設定 DMA_CCR 寄存器的 PL[1:0]位的值。 DMA 通道優先級只有在多個 DMA 通道同時使用時才有意義,如果是單個通道,優先級可以隨便設置。


==11. DMA_M2M == :存 儲器 到存 儲器 模式 ,使 用存儲 器到 存儲 器時 用到, 設定DMA_CCR 的位 14 MEN2MEN 即可啟動存儲器到存儲器模式。


USART配置函數

此代碼結合串口通信章節內容理解更佳,此處不多于講解。USART配置詳解


void DEBUG_UART_Config(void)

{

GPIO_InitTypeDef GPIO_InitStructure;

USART_InitTypeDef USART_InitStructure;

/* 第一步:初始化GPIO */

// 打開串口GPIO的時鐘

DEBUG_USART_GPIO_APBxClkCmd(DEBUG_USART_GPIO_CLK, ENABLE);

// 將USART Tx的GPIO配置為推挽復用模式

GPIO_InitStructure.GPIO_Pin = DEBUG_USART_TX_GPIO_PIN;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_Init(DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure);


  // 將USART Rx的GPIO配置為浮空輸入模式

GPIO_InitStructure.GPIO_Pin = DEBUG_USART_RX_GPIO_PIN;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;

GPIO_Init(DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStructure);

/* 第二步:配置串口的初始化結構體 */

// 打開串口外設的時鐘

DEBUG_USART_APBxClkCmd(DEBUG_USART_CLK, ENABLE);

// 配置串口的工作參數

// 配置波特率

USART_InitStructure.USART_BaudRate = DEBUG_USART_BAUDRATE;

// 配置 針數據字長

USART_InitStructure.USART_WordLength = USART_WordLength_8b;

// 配置停止位

USART_InitStructure.USART_StopBits = USART_StopBits_1;

// 配置校驗位

USART_InitStructure.USART_Parity = USART_Parity_No ;

// 配置硬件流控制

USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;

// 配置工作模式,收發一起

USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;

// 完成串口的初始化配置

USART_Init(DEBUG_USARTx, &USART_InitStructure);


/* 第三步:使能串口 */

// 使能串口

USART_Cmd(DEBUG_USARTx, ENABLE);

}


DMA配置函數

此函數主要是打開DMA外設時鐘、配置DMA初始化結構體、使能DMA通道


// 串口對應的DMA請求通道

#define  USART_TX_DMA_CHANNEL     DMA1_Channel4

// 外設寄存器地址,USART1的數據寄存器地址(數據寄存器包含USART將發送或接收的數據)

#define  USART_DR_ADDRESS        (USART1_BASE+0x04)

// 一次發送的數據量

#define  SENDBUFF_SIZE            5000


uint8_t SendBuff[SENDBUFF_SIZE];

/* 定義的數組就存儲在存儲器SRAM中 */


void USARTx_DMA_Config(void)

{

DMA_InitTypeDef DMA_InitStructure;

RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);

   // 設置DMA源地址:串口數據寄存器地址*/

       DMA_InitStructure.DMA_PeripheralBaseAddr = USART_DR_ADDRESS;

// 內存地址(要傳輸的變量的指針)

DMA_InitStructure.DMA_MemoryBaseAddr = (u32)SendBuff;

// 方向:從內存到外設

DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;

// 傳輸大小

DMA_InitStructure.DMA_BufferSize = SENDBUFF_SIZE;

// 外設地址不增     

DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;

// 內存地址自增

DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;

// 外設數據單位

DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;

// 內存數據單位

DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;  

// DMA模式,一次或者循環模式

DMA_InitStructure.DMA_Mode = DMA_Mode_Normal ;

//DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;

// 優先級:中

DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; 

// 禁止內存到內存的傳輸

DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;

// 配置DMA通道    

DMA_Init(USART_TX_DMA_CHANNEL, &DMA_InitStructure);

// 使能DMA

DMA_Cmd (USART_TX_DMA_CHANNEL,ENABLE);

}


主函數

int main(void)

{

uint32_t i;

DEBUG_UART_Config();

USARTx_DMA_Config();

  /*填充將要發送的數據*/

  for(i=0;i  {

    SendBuff[i] = 'P';    

  }

   /* USART1向DMA發出請求 */

USART_DMACmd(DEBUG_USARTx, USART_DMAReq_Tx, ENABLE);


}

進入單片機查看更多內容>>
相關視頻
  • TI 新一代 C2000? 微控制器:全方位助力伺服及馬達驅動應用

  • MSP430電容觸摸技術 - 防水Demo演示

  • 直播回放: Microchip Timberwolf? 音頻處理器在線研討會

  • 新唐 8051單片機教程

  • 基于靈動MM32W0系列MCU的指夾血氧儀控制及OTA升級應用方案分享

  • 基于靈動MM32SPIN系列MCU的無感FOC便攜冰箱應用方案分享

    相關電子頭條文章
萝卜大香蕉