1. 程式人生 > >stm32——DMA

stm32——DMA

DMA部分我用到的相對簡單,當然,可能這是新東西,我暫時還用不到它的複雜功能吧。下面用問答的形式表達我的思路。

DMA的定義

直接儲存器存取(Direct Memory Access,DMA)是電腦科學中的一種記憶體訪問技術。它允許某些電腦內部的硬體子系統(電腦外設),可以獨立地直接讀寫系統儲存器,而不需繞道 CPU。在同等程度的CPU負擔下,DMA是一種快速的資料傳送方式。它允許不同速度的硬體裝置來溝通,而不需要依於 CPU的大量中斷請求。

DMA有什麼用?

直接儲存器存取用來提供在外設和儲存器之間或者儲存器和儲存器之間的高速資料傳輸。無須CPU的干預,通過DMA資料可以快速地移動。這就節省了CPU的資源來做其他操作。

有多少個DMA資源?

有兩個DMA控制器,DMA1有7個通道,DMA2有5個通道。

資料從什麼地方送到什麼地方?

  • 外設到SRAM(I2C/UART等獲取資料並送入SRAM); 
    SRAM的兩個區域之間;
  • 外設到外設(ADC讀取資料後送到TIM1控制其產生不同的PWM佔空比);
  • SRAM到外設(SRAM中預先儲存的資料送入DAC產生各種波形);

  • 還有一些目前還搞不清楚的。

DMA可以傳遞多少資料?

傳統的DMA的概念是用於大批量資料的傳輸,但是我理解,在STM32中,它的概念被擴充套件了,也許更多的時候快速是其應用的重點。資料可以從1~65535個。

DMA控制器與仲裁器

現在越來越多的微控制器採用DMA技術,提供外設和儲存器之間或者儲存器之間的高速資料傳輸。當 CPU 初始化這個傳輸動作,傳輸動作本身是由 ==DMA 控制器== 來實行和完成。STM32就有一個DMA控制器,它有7個通道,每個通道專門用來管理一個或多個外設對儲存器訪問的請求,還有一個==仲裁器==來協調各個DMA請求的優先權。

DMA 控制器和Cortex-M3核共享系統資料匯流排執行直接儲存器資料傳輸。當CPU和DMA同時訪問相同的目標(RAM或外設)時,DMA請求可能會停止 CPU訪問系統匯流排達若干個週期,匯流排仲裁器執行迴圈排程,以保證CPU至少可以得到一半的系統匯流排(儲存器或外設)頻寬。

在發生一個事件後,外設傳送一個請求訊號到DMA控制器。DMA控制器根據通道的優先權處理請求。當DMA控制器開始訪問外設的時候,DMA控制器立即傳送給外設一個應答訊號。當外設從DMA控制器得到應答訊號時,外設立即釋放它的請求。一旦外設釋放了這個請求,DMA控制器同時撤銷應答訊號。如果發生更多的請求時,外設可以啟動下次處理。

總之,每個DMA傳送由3個操作組成: 
1. 從外設資料暫存器或者從DMA_CMARx暫存器指定地址的儲存器單元執行載入操作。 
2. 存資料到外設資料暫存器或者存資料到DMA_CMARx暫存器指定地址的儲存器單元。 
3. 執行一次DMA_CNDTRx暫存器的遞減操作。該暫存器包含未完成的運算元目。

image

仲裁器根據通道請求的優先順序來啟動外設/儲存器的訪問。優先順序分為兩個等級:軟體(4個等級:最高、高、中等、低)、硬體(有較低編號的通道比擁有較高編號的通道有較高的優先權)。

可以在DMA傳輸過半、傳輸完成和傳輸錯誤時產生中斷。

STM32中DMA的不同中斷(傳輸完成、半傳輸、傳輸完成)通過“線或”方式連線至NVIC,需要在中斷例程中進行判斷。

進行DMA配置前,不要忘了在RCC設定中使能DMA時鐘。STM32的DMA控制器掛在AHB總線上。

DMA總共有7個通道,各個通道的DMA對映關係如下:

image

外設的事件連線至相應DMA通道,每個通道均可以通過軟體觸發實現儲存器內部的DMA資料傳輸(M2M模式)

Tips:庫2.0中函式RCC_AHBPeriphClockCmd的引數由“RCC_AHBPeriph_DMA”改成“RCC_AHBPeriph_DMA1”(如果是DMA1控制器的話)。

DMA的傳輸標誌位(CHTIFx、CTCIFx、CGIFx)由硬體設定為“1”,但需要軟體清零,在中斷服務程式中清除。當CGIFx(全域性中斷標誌位)清零後,CHTIFx 和 CTCIFx均清零。

過程:怎樣啟用DMA?首先,眾所周知的是初始化,任何裝置啟用前都要對其進行初始化,要對模組初始化,還要先了解該模組相應的結構及其函式,以便正確的設定;由於DMA較為複雜,我就只談談DMA的基本結構和和常用函式,這些都是ST公司提供在庫函式中的。

1、 下面程式碼是一個標準DMA設定,當然實際應用中可根據實際情況進行裁減:

DMA_DeInit(DMA_Channel1);
  • 1

上面這句是給DMA配置通道,根據ST提供的資料,STM3210Fx中DMA包含7個通道(CH1~CH7),也就是說可以為外設或memory提供7座“橋樑”(請允許我使用橋樑一詞,我覺得更容易理解,哈哈,別“拍磚”呀!);

DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address;
  • 1

上面語句中的DMA_InitStructure是一個DMA結構體,在庫中有聲明瞭,當然使用時就要先定義 了;DMA_PeripheralBaseAddr是該結構體中一個數據成員,給DMA一個起始地址,好比是一個buffer起始地址,資料流程是:外設 暫存器à DMA_PeripheralBaseAddàmemory中變數空間(或flash中資料空間等),ADC1_DR_Address是我定義的一個地址 變數;

DMA_InitStructure.DMA_MemoryBaseAddr = (u32)ADC_ConvertedValue;
  • 1

上面這句很顯然是DMA要連線在Memory中變數的地址,ADC_ConvertedValue是我自己在memory中定義的一個變數;

DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
  • 1

上面的這句是設定DMA的傳輸方向,就如前面我所說的,DMA可以雙向傳輸,也可以單向傳輸,這裡設定的是單向傳輸,如果需要雙向傳輸:把DMA_DIR_PeripheralSRC改成DMA_DIR_PeripheralDST即可。

DMA_InitStructure.DMA_BufferSize = 2;
  • 1

上面的這句是設定DMA在傳輸時緩衝區的長度,前面有定義過了buffer的起始地址:ADC1_DR_Address ,為了安全性和可靠性,一般需要給buffer定義一個儲存片區,這個引數的單位有三種類型:Byte、HalfWord、word,我設定的2個 half-word(見下面的設定);32位的MCU中1個half-word佔16 bits。

DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
  • 1

上面的這句是設定DMA的外設遞增模式,如果DMA選用的通道(CHx)有多個外設連線,需要使用外設遞增模式:DMA_PeripheralInc_Enable;我的例子裡DMA只與ADC1建立了聯絡,所以選用DMA_PeripheralInc_Disable

DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
  • 1

上面的這句是設定DMA的記憶體遞增模式,DMA訪問多個記憶體引數時,需要使用DMA_MemoryInc_Enable,當DMA只訪問一個記憶體引數時,可設定成:DMA_MemoryInc_Disable。

DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
  • 1

上面的這句是設定DMA在訪問時每次操作的資料長度。有三種資料長度型別,前面已經講過了,這裡不在敘述。

DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
  • 1

與上面雷同。在此不再說明。

DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
  • 1

上面的這句是設定DMA的傳輸模式:連續不斷的迴圈模式,若只想訪問一次後就不要訪問了(或按指令操作來反問,也就是想要它訪問的時候就訪問,不要它訪問的時候就停止),可以設定成通用模式:DMA_Mode_Normal

DMA_InitStructure.DMA_Priority = DMA_Priority_High;
  • 1

上面的這句是設定DMA的優先級別:可以分為4級:VeryHigh,High,Medium,Low.

DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
  • 1

上面的這句是設定DMA的2個memory中的變數互相訪問的

DMA_Init(DMA_Channel1,&DMA_InitStructure);
  • 1

前面那些都是對DMA結構體成員的設定,在次再統一對DMA整個模組做一次初始化,使得DMA各成員與上面的引數一致。

DMA_Cmd(DMA_Channel1,ENABLE);
  • 1

哈哈哈!這一句我想我就不羅嗦了,大家一看就明白。

至此,整個DMA總算設定好了,但是,DMA通道又是怎樣與外設聯絡在一起的呢?哈哈,這也是我當初最想知道的一個事情,別急!容我想喝口茶~~哈哈哈!

要使DMA與外設建立有效連線,這不是DMA自身的事情,是各個外設的事情,每個外設都有 一個xxx_DMACmd(XXXx,Enable )函式,如果使DMA與ADC建立有效聯絡,就使用ADC_DMACmd(ADC1,Enable); (這裡我啟用了ADC中的ADC1模組)。

一個簡單的例子 transfer a word data buffer from FLASH memory to embedded SRAM memory. 
在V3.1.2庫的位置

STM32F10x_StdPeriph_Lib_V3.1.2\Project\STM32F10x_StdPeriph_Examples\DMA\FLASH_RAM


DMA_DeInit(DMA1_Channel6);
  //peripheral base address
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)SRC_Const_Buffer;
  //memory base address   
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)DST_Buffer;
  //資料傳輸方向    Peripheral is source               
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
//緩衝區大小 Number of data to be transferred (0 up to 65535).資料傳輸數目     
DMA_InitStructure.DMA_BufferSize = BufferSize;
   // the Peripheral address register is incremented       
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable;
  //the memory address register is incremented
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
//the Peripheral data width       
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word; 
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
//the DMAy Channelx will be used in memory-to-memory transfer
//DMA通道的操作可以在沒有外設請求的情況下進行,這種操作就是儲存器到儲存器模式。
DMA_InitStructure.DMA_M2M = DMA_M2M_Enable;   
DMA_Init(DMA1_Channel6, &DMA_InitStructure);


DMA_ITConfig(DMA1_Channel6, DMA_IT_TC, ENABLE);



DMA_Cmd(DMA1_Channel6, ENABLE);

外設的DMA請求映像

要使DMA與外設建立有效連線,這不是DMA自身的事情,是各個外設的事情,每個外設都有 一個

xxx_DMACmd(XXXx,Enable )函式,如果使DMA與ADC建立有效聯絡,就使用 ADC_DMACmd

(ADC1,Enable); (這裡我啟用了ADC中的ADC1模組)。

DMA_DeInit(DMA1_Channel1);
DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&AD_Value;   
//u16  AD_Value[2];   不加&應該也可以  陣列名 代表地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_BufferSize = 2;      //############## 改了
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;  //##############     改了
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel1, &DMA_InitStructure);


DMA_Cmd(DMA1_Channel1, ENABLE);


ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_InitStructure.ADC_ScanConvMode = ENABLE;
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfChannel = 2;      //##############     改了
ADC_Init(ADC1, &ADC_InitStructure);
//內部溫度感測器  新增這一句 

ADC_TempSensorVrefintCmd(ENABLE);
//##############     改了

//################ Channel 10(電位器)
ADC_RegularChannelConfig(ADC1, ADC_Channel_10, 1, ADC_SampleTime_13Cycles5);
//###### 內部溫度感測器  Channel 16 ###################
ADC_RegularChannelConfig(ADC1, ADC_Channel_16, 2, ADC_SampleTime_55Cycles5);

  使能ADC1的DMA請求映像
  ADC_DMACmd(ADC1, ENABLE);


ADC_Cmd(ADC1, ENABLE);

   //使用之前一定要校準
ADC_ResetCalibration(ADC1);

while(ADC_GetResetCalibrationStatus(ADC1));


ADC_StartCalibration(ADC1);

while(ADC_GetCalibrationStatus(ADC1));


ADC_SoftwareStartConvCmd(ADC1, ENABLE);