STM32系统学习——DMA(直接储存器访问)
DMA主要功能是傳輸數(shù)據(jù),但是不需要占用CPU,即在傳輸數(shù)據(jù)時,CPU可以做別的事,像多線程。數(shù)據(jù)傳輸從外設(shè)到存儲器或者從存儲器到存儲器。DMA控制器包含了DMA1和DMA2,其中DMA1有7個通道,DMA2有5個通道,可以理解為傳輸數(shù)據(jù)的一種管道。要注意的是,DMA2只存在于大容量單片機中。?
一、DMA框圖解析?
DMA控制器獨立于內(nèi)核,屬于一個單獨外設(shè),結(jié)構(gòu)結(jié)合下圖來看?
?
1.DMA請求?
如果外設(shè)想通過DMA傳輸數(shù)據(jù),必須先向DMA控制器發(fā)送DMA請求,DMA收到請求信號后,控制器會給外設(shè)一個應答信號,當外設(shè)應答且DMA控制器收到應答信號后,就會啟動DMA傳輸,直到傳輸完畢。?
DMA有DMA1和DMA2兩個控制器,DMA1有兩個控制器,DMA1有7個通道,DMA2有5個通道,不同DMA控制器的通道有不同的外設(shè)請求。?
2、通道?
DMA有12個獨立可編程的通道,DMA1有7個通道,DMA2有5個通道,每個通道對應不同外設(shè)的DMA請求。雖然每個通道可以接收多個外設(shè)請求,但是同一時間只能接收一個,不能同時接收多個。?
3、仲裁器?
當同時有多個DMA請求時,就意味著有先后響應的問題,這個就由仲裁器管理。仲裁器管理DMA請求分為2個階段:第一階段屬于軟件階段,可以在MDA_CCRx寄存器中設(shè)置,有 4 個等級:非常高、高、中和低四個優(yōu)先級。第二階段屬于硬件階段,如果兩個或以上的 DMA 通道請求設(shè)置的優(yōu)先級一樣,則他們優(yōu)先級取決于通道編號,編號越低優(yōu)先權(quán)越高,比如通道 0 高于通道 1。在大容量產(chǎn)品和互聯(lián)型產(chǎn)品中,DMA1 控制器擁有高于 DMA2 控制器的優(yōu)先級。
二、DMA數(shù)據(jù)配置?
使用DMA,最核心的就是配置要傳輸?shù)臄?shù)據(jù)。?
1、從哪兒來,到哪兒去?
DMA傳輸數(shù)據(jù) 的方向有3個:外設(shè)到存儲器,存儲器到外設(shè),存儲器到存儲器。具體方向由DMA_CCR中第四位DIR配置:0表示外設(shè)到存儲器,1表示存儲器到外設(shè)。涉及的地址由DMA_CPAR配置,存儲器地址由DMA_CMAR配置。?
1)從外設(shè)到存儲器?
以ADC采集為例,DMA外部寄存器地址對應ADC數(shù)據(jù)寄存器地址,DMA存儲器地址是我們自定義的變量的地址。方向設(shè)置為源地址。?
2)存儲器到外設(shè)?
存儲器到外設(shè)傳輸以串口向電腦端發(fā)送為例,DMA 外設(shè)寄存器的地址對應的就是串口數(shù)據(jù)寄存器的地址,DMA 存儲器的地址就是我們自定義的變量(相當于一個緩沖區(qū),用來存儲通過串口發(fā)送到電腦的數(shù)據(jù))的地址。方向我們設(shè)置外設(shè)為目標地址。?
3)存儲器到存儲器?
當我們使用從存儲器到存儲器傳輸時,以內(nèi)部 FLASH 向內(nèi)部 SRAM 復制數(shù)據(jù)為例。?
DMA外設(shè)寄存器的地址對應的就是內(nèi)部 FLASH(我們這里把內(nèi)部 FALSH 當作一個外設(shè)來看)的地址,DMA 存儲器的地址就是我們自定義的變量(相當于一個緩沖區(qū),用來存儲來自內(nèi)部 FLASH 的數(shù)據(jù))的地址。方向我們設(shè)置外設(shè)(即內(nèi)部 FLASH)為源地址。跟上面兩個不一樣的是,這里需要把 DMA_CCR 位 14:MEM2MEM:存儲器到存儲器模式配置為 1,啟動 M2M 模式。?
2、要傳什么,單位是多少?
以串口向電腦發(fā)送數(shù)據(jù)為例,我們可以一次性給電腦發(fā)送很多數(shù)據(jù),具體多少由DMA_CNDTR 配置,這是一個 32位的寄存器,一次最多只能傳輸 65535 個數(shù)據(jù)。?
要想數(shù)據(jù)傳輸正確,源和目標地址存儲的數(shù)據(jù)寬度還必須一致,串口數(shù)據(jù)寄存器是 8位的,所以我們定義的要發(fā)送的數(shù)據(jù)也必須是 8 位。外設(shè)的數(shù)據(jù)寬度由 DMA_CCR 的PSIZE[1:0]配置,可以是 8/16/32位,存儲器的數(shù)據(jù)寬度由 DMA_CCR 的 MSIZE[1:0]配置,可以是 8/16/32 位。?
在 DMA 控制器的控制下,數(shù)據(jù)要想有條不紊的從一個地方搬到另外一個地方,還必須正確設(shè)置兩邊數(shù)據(jù)指針的增量模式。外設(shè)的地址指針由 DMA_CCRx 的 PINC 配置,存儲器的地址指針由 MINC 配置。以串口向電腦發(fā)送數(shù)據(jù)為例,要發(fā)送的數(shù)據(jù)很多,每發(fā)送完一個,那么存儲器的地址指針就應該加 1,而串口數(shù)據(jù)寄存器只有一個,那么外設(shè)的地址指針就固定不變。具體的數(shù)據(jù)指針的增量模式由實際情況決定。?
3、什么時候傳輸完成?
數(shù)據(jù)什么時候傳輸完成,我們可以通過查詢標志位或者通過中斷的方式來鑒別。每個DMA 通道在 DMA 傳輸過半、傳輸完成和傳輸錯誤時都會有相應的標志位,如果使能了該類型的中斷后,則會產(chǎn)生中斷。有關(guān)各個標志位的詳細描述請參考 DMA 中斷狀態(tài)寄存器DMA_ISR的詳細描述。?
傳輸完成還分兩種模式,是一次傳輸還是循環(huán)傳輸,一次傳輸很好理解,即是傳輸一次之后就停止,要想再傳輸?shù)脑?#xff0c;必須關(guān)斷 DMA 使能后再重新配置后才能繼續(xù)傳輸。循環(huán)傳輸則是一次傳輸完成之后又恢復第一次傳輸時的配置循環(huán)傳輸,不斷的重復。具體的由 DMA_CCR寄存器的 CIRC 循環(huán)模式位控制。
三、DMA初始化結(jié)構(gòu)體?
結(jié)構(gòu)體 xxx_InitTypeDef 定義在stm32f10x_xxx.h(后面xxx為外設(shè)名稱)文件中,庫函數(shù)xxx_Init定義在stm32f10x_xxx.c文件中。
1) DMA_PeripheralBaseAddr:外設(shè)地址,設(shè)定 DMA_CPAR 寄存器的值;一般設(shè)置為外設(shè)的數(shù)據(jù)寄存器地址,如果是存儲器到存儲器模式則設(shè)置為其中一個存儲器地址。?
2) DMA_Memory0BaseAddr:存儲器地址,設(shè)定 DMA_CMAR 寄存器值;一般設(shè)置為我們自定義存儲區(qū)的首地址。?
3) DMA_DIR:傳輸方向選擇,可選外設(shè)到存儲器、存儲器到外設(shè)。它設(shè)定DMA_CCR 寄存器的 DIR[1:0]位的值。這里并沒有存儲器到存儲器的方向選擇,當使用存儲器到存儲器時,只需要把其中一個存儲器當作外設(shè)使用即可。?
4) DMA_BufferSize:設(shè)定待傳輸數(shù)據(jù)數(shù)目,初始化設(shè)定 DMA_CNDTR 寄存器的值。?
5) DMA_PeripheralInc:如果配置為 DMA_PeripheralInc_Enable,使能外設(shè)地址自動遞增功能,它設(shè)定 DMA_CCR 寄存器的 PINC 位的值;一般外設(shè)都是只有一個數(shù)據(jù)寄存器,所以一般不會使能該位。?
6) DMA_MemoryInc:如果配置為DMA_MemoryInc_Enable,使能存儲器地址自動遞增功能,它設(shè)定 DMA_CCR 寄存器的 MINC 位的值;我們自定義的存儲區(qū)一般都是存放多個數(shù)據(jù)的,所以要使能存儲器地址自動遞增功能。?
7) DMA_PeripheralDataSize:外設(shè)數(shù)據(jù)寬度,可選字節(jié)(8位)、半字(16位)和字(32位),它設(shè)定 DMA_CCR寄存器的 PSIZE[1:0]位的值。?
8) DMA_MemoryDataSize:存儲器數(shù)據(jù)寬度,可選字節(jié)(8 位)、半字(16 位)和字(32位),它設(shè)定 DMA_CCR 寄存器的 MSIZE[1:0]位的值。當外設(shè)和存儲器之間傳數(shù)據(jù)時,兩邊的數(shù)據(jù)寬度應該設(shè)置為一致大小。?
9) DMA_Mode:DMA 傳輸模式選擇,可選一次傳輸或者循環(huán)傳輸,它設(shè)定DMA_CCR 寄存器的 CIRC 位的值。例程我們的 ADC 采集是持續(xù)循環(huán)進行的,所以使用循環(huán)傳輸模式。?
10) DMA_Priority:軟件設(shè)置通道的優(yōu)先級,有 4 個可選優(yōu)先級分別為非常高、高、中和低,它設(shè)定 DMA_CCR 寄存器的 PL[1:0]位的值。DMA 通道優(yōu)先級只有在多個 DMA 通道同時使用時才有意義,如果是單個通道,優(yōu)先級可以隨便設(shè)置。?
11) DMA_M2M:存儲器到存儲器模式,使用存儲器到存儲器時用到,設(shè)定DMA_CCR 的位 14MEN2MEN 即可啟動存儲器到存儲器模式。
四、存儲器到存儲器的實驗?
先定義一個靜態(tài)的源數(shù)據(jù),存放在內(nèi)部Flash存儲器中,使用DMA傳輸,把源數(shù)據(jù)拷貝到目標地址上(內(nèi)部SRAM),最后對比源數(shù)據(jù)和目標地址的數(shù)據(jù),看看是否準確傳輸。?
1、思路要點?
1)使能DMA時鐘?
2)配置DMA數(shù)據(jù)參數(shù)?
3)使能DMA,進行傳輸?
4)等待傳輸完成,并對源數(shù)據(jù)和目標地址數(shù)據(jù)進行比較。?
2、DMA宏定義以及變量定義
aSRC_Const_Buffer[BUFFER_SIZE]定義用來存放源數(shù)據(jù),并且使用了const關(guān)鍵字修飾,即常量類型,使得變量存儲在內(nèi)部Flash空間上。
3、DMA數(shù)據(jù)配置
void DMA_Config(void) 2 { 3 DMA_InitTypeDef DMA_InitStructure; 4 5 // 開啟 DMA 時鐘 6 RCC_AHBPeriphClockCmd(DMA_CLOCK, ENABLE); 7 // 源數(shù)據(jù)地址 8 DMA_InitStructure.DMA_PeripheralBaseAddr = 9 (uint32_t)aSRC_Const_Buffer; 10 // 目標地址 11 DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)aDST_Buffer; 12 // 方向:外設(shè)到存儲器(這里的外設(shè)是內(nèi)部的 FLASH) 13 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; 14 // 傳輸大小 15 DMA_InitStructure.DMA_BufferSize = BUFFER_SIZE; 16 // 外設(shè)(內(nèi)部的 FLASH)地址遞增 17 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable; 18 // 內(nèi)存地址遞增 19 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; 20 // 外設(shè)數(shù)據(jù)單位 21 DMA_InitStructure.DMA_PeripheralDataSize = 22 DMA_PeripheralDataSize_Word; 23 // 內(nèi)存數(shù)據(jù)單位 24 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word; 25 // DMA 模式,一次或者循環(huán)模式 26 DMA_InitStructure.DMA_Mode = DMA_Mode_Normal ; 27 //DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; 28 // 優(yōu)先級:高 29 DMA_InitStructure.DMA_Priority = DMA_Priority_High; 30 // 使能內(nèi)存到內(nèi)存的傳輸 31 DMA_InitStructure.DMA_M2M = DMA_M2M_Enable; 32 // 配置 DMA 通道 33 DMA_Init(DMA_CHANNEL, &DMA_InitStructure); 34 // 使能 DMA 35 DMA_Cmd(DMA_CHANNEL,ENABLE); 36 }使用 DMA_InitTypeDef 結(jié)構(gòu)體定義一個 DMA 初始化變量,這個結(jié)構(gòu)體內(nèi)容我們之前已經(jīng)有詳細講解。?
調(diào)用 RCC_AHBPeriphClockCmd 函數(shù)開啟 DMA時鐘,使用 DMA控制器之前必須開啟對應的時鐘。?
源地址和目標地址使用之前定義的數(shù)組首地址,傳輸?shù)臄?shù)據(jù)量為宏 BUFFER_SIZE 決定,源和目標地址指針地址遞增,使用一次傳輸模式不能循環(huán)傳輸,因為只有一個 DMA通道,優(yōu)先級隨便設(shè)置,最后調(diào)用 DMA_Init 函數(shù)完成 DMA 的初始化配置。?
DMA_ClearFlag函數(shù)用于清除DMA標志位,代碼用到傳輸完成標志位,使用之前先清除傳輸完成標志位以免產(chǎn)生不必要干擾。DMA_ClearFlag 函數(shù)需要 1 個形參,即事件標志位,可選有傳輸完成標志位、半傳輸標志位、FIFO 錯誤標志位、傳輸錯誤標志位等等,非常多,我們這里選擇傳輸完成標志位,由宏 DMA_FLAG_TC 定義。?
DMA_Cmd 函數(shù)用于啟動或者停止 DMA 數(shù)據(jù)傳輸,它接收兩個參數(shù),第一個是 DMA通道,另外一個是開啟 ENABLE 或者停止 DISABLE。
4、存儲器數(shù)據(jù)對比
1 uint8_t Buffercmp(const uint32_t* pBuffer, 2 uint32_t* pBuffer1, uint16_t BufferLength) 3 { 4 /* 數(shù)據(jù)長度遞減 */ 5 while (BufferLength--) { 6 /* 判斷兩個數(shù)據(jù)源是否對應相等 */ 7 if (*pBuffer != *pBuffer1) { 8 /* 對應數(shù)據(jù)源不相等馬上退出函數(shù),并返回 0 */ 9 return 0; 10 } 11 /* 遞增兩個數(shù)據(jù)源的地址指針 */ 12 pBuffer++; 13 pBuffer1++; 14 } 15 /* 完成判斷并且對應數(shù)據(jù)相對 */ 16 return 1; 17 }判斷指定長度的兩個數(shù)據(jù)源是否完全相等,如果完全相等返回 1;只要其中一對數(shù)據(jù)不相等返回 0。它需要三個形參,前兩個是兩個數(shù)據(jù)源的地址,第三個是要比較數(shù)據(jù)長度。?
5、main函數(shù)
首先定義一個變量用來保存存儲器數(shù)據(jù)比較結(jié)果。?
RGB 彩色燈用來指示程序進程,使用之前需要初始化它,LED_GPIO_Config 定義在bsp_led.c 文件中。開始設(shè)置 RGB 彩色燈為紫色,LED_PURPLE 是定義在 bsp_led.h 文件的一個宏定義。?
Delay函數(shù)只是一個簡單的延時函數(shù)。?
調(diào)用 DMA_Config 函數(shù)完成 DMA 數(shù)據(jù)流配置并啟動 DMA 數(shù)據(jù)傳輸。?
DMA_GetFlagStatus 函數(shù)獲取 DMA 事件標志位的當前狀態(tài),這里獲取 DMA 數(shù)據(jù)傳輸完成這個標志位,使用循環(huán)持續(xù)等待直到該標志位被置位,即 DMA 傳輸完成這個事件發(fā)生,然后退出循環(huán),運行之后程序。?
確定 DMA 傳輸完成之后就可以調(diào)用 Buffercmp 函數(shù)比較源數(shù)據(jù)與 DMA 傳輸后目標地址的數(shù)據(jù)是否一一對應。TransferStatus 保存比較結(jié)果,如果為 1 表示兩個數(shù)據(jù)源一一對應相等說明 DMA 傳輸成功;相反,如果為 0 表示兩個數(shù)據(jù)源數(shù)據(jù)存在不等情況,說明 DMA傳輸出錯。?
如果 DMA傳輸成功設(shè)置 RGB彩色燈為藍色,如果 DMA傳輸出錯設(shè)置 RGB彩色燈為紅色。
總結(jié)
以上是生活随笔為你收集整理的STM32系统学习——DMA(直接储存器访问)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: STM32单片机,禁止系统启动时的变量初
- 下一篇: STM32F103mini教程通用定时器