以SPI2 為例,將存儲器中的數據,通過DMA方式搬運到外設,也就是往外發SPI_TX,DMA配置步驟:
1、選擇DMA1還是DMA2:通過圖1可查看到SPI2是在DMA1表里,所以選擇DMA1。
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1,ENABLE);
2、選擇數據流:該配置應該放在所有信息配置完在使能。
DMA_Cmd(DMAX_StreamY, ENABLE);
其中X = 1、2,Y=0、1…7,有兩個DMA,分別是DMA1和DMA2,每個DMA控制器有又有8個數據流。
問題1:一個外設怎么知道選哪個數據流呢?
答:先查看參考手冊,找到DMA1/2請求映射表,如下圖1、2
圖1
圖2
比如現在用到外設SPI2_TX(存儲器的數據搬運到外設,所以得找發送) ,選擇數據流4。
DMA_Cmd(DMA1_Stream4, ENABLE);
3、通道選擇,有8個通道,不是隨便選擇的,得查看圖1和圖2,SPI2外設所對應的通道0。
DMA_InitStructure.DMA_Channel = DMA_Channel_0;
4、設置外設基地址。
DMA_InitStructure.DMA_PeripheralBaseAddr = 0Xxxxx;
問題2:怎么知道當前選用外設的基地址?看人家例子都是寫好了的,到時換其他外設,這基地址又不知道該寫多少了。
答:先找到這個表,如圖3,可以看到SPI2在0x4000 3800~0x4000 3BFF范圍內。
圖3
按照右邊的提示“第769頁的xxxxx映射”提示找到下表圖4,找到SPI_DR數據寄存器。
圖4
可以查看SPI_DR具體信息圖5,該寄存器表示:已接收或者要發送的數據。說明找到這個偏移地址是沒有錯的,DMA就從該地址上搬走或送來數據。
圖5
通過以上查找最終地址是0x4000 3800 + 0x0C = 0x4000 380C
DMA_InitStructure.DMA_PeripheralBaseAddr =0x4000 380C;
5、設置存儲數據的地址,DMA從存儲器取數據,該存儲器在內存中的的首地址。
u32 buffer[] = {0,1,2,3,4,5};
DMA_InitStructure.DMA_Memory0BaseAddr = (u32) buffer;
6、設置DMA傳輸方向(存儲器到外設、存儲器到存儲器或外設到存儲器)
DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral; //存儲器到外設
7、設置DMA 緩沖區的容量大小,查看資料,圖6,DMA傳輸最大的數量是65535。
圖6
DMA_InitStructure.DMA_BufferSize = (uint32_t)0xFFFF; //0xFFFF轉換十進制就是65535。
設置DMA緩沖器的大小關系到另外兩個參數設,源(存儲器)和目標(外設)傳輸數據的寬度,是以字節、半字或字為單位進行搬運。
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
注意:
(1)、設置的緩沖區大小65535,不能單純理解65535個字節,還得看設置的數據寬度是多少,數據寬度設置的是半字(16bit),即16 *65535 bit。
(2)、源(存儲器)和目標(外設)傳輸數據的寬度要設置一樣。
8、設置外設和存儲器地址是否要遞增
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外設遞增不地址,是一直從SPI2地址上獲取數據的
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //存儲器地址要遞增,否則數據會被覆蓋掉
9、 設置DMA 工作模式,有兩種模式DMA_Mode_Normal 和 DMA_Mode_Circular。
(1)、正常模式,即DMA只傳輸一次。當傳輸完一次后,還想再傳一次,需重啟DMA_Cmd(DMA1_Stream4, ENABLE);
(2)、循環模式可用於處理循環緩沖區和連續數據流(例如ADC掃描模式)。
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //如果是單次讀取,可用正常模式
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular ;//掃描讀取可用循環模式,存儲器到存儲器不能選擇循環模式
10、設置DMA優先級(有四種低、中、高、最高),多個外設需要用到DMA時,就需要設置優先級。
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
11、設置FIFO模式,用於在源數據傳輸到目標 之前 臨時存放 這些數據,也就是說數據先存放到FIFO中,待FIFO數據量達到一定閾值,再將數據傳輸到目標。
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;//不使用FIFO模式
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;//可選擇1/4、1/2、3/4和full四種,不使用FIFO模式,這參數沒必要寫
12、設置外設和存儲器突發傳輸,DMA控制可以產生單次傳輸或4個、8個和16個節拍的增量突發傳輸
(1)、單次傳輸時,每個DMA請求產生一次(一個節拍,這樣好理解突發傳輸)數據傳輸(傳輸數據寬度可以是字節、半字和字為單位)。
(2)、突發傳輸時,每個DMA請求相應地生成4個、8個或16個節拍傳輸數據(傳輸數據寬度可以是字節、半字和字為單位),期間不被中斷。
比如:此例子中傳輸數據寬度設置是半字,即16bit
DMA緩沖區大小設置的是最大值65535
單次傳輸,DMA請求產生一次(一個節拍)數據傳輸,總共傳輸數據 16 / 8* 65535 = 131070Byte, 大概傳輸了128KByte
突發傳輸4個節拍,4*128 = 513KByte
突發傳輸8個節拍,8*128 = 1MByte
突發傳輸16個節拍,16*128 = 2MByte
一次DMA請求傳輸的數據量越大,占用DMA時間越長,期間不會被中斷,如果系統還有其他外設需要用DMA時,就得考慮一次傳輸的數據量了。
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single; //選擇單次傳輸
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; // 選擇單次傳輸
完整配置:
DMA_InitTypeDef DMA_InitStructure; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1,ENABLE); //使能DMA1 時鍾 DMA_DeInit(DMA1_Stream4); //初始化為默認復位值 DMA_InitStructure.DMA_Channel = DMA_Channel_0; //SPI2 對應的是通道0 DMA_InitStructure.DMA_PeripheralBaseAddr =0x4000 380C; //外設地址 DMA_InitStructure.DMA_Memory0BaseAddr = (u32) buffer; //存儲器的首地址 DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral; //存儲器到外設 DMA_InitStructure.DMA_BufferSize = (uint32_t)0xFFFF; //設置DMA緩沖大小,最大值65535 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; //寬度選擇半字進行傳輸,也就是16bit DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; //寬度選擇半字進行傳輸,也就是16bit DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外設地址不遞增 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //存儲器地址遞增 DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //正常模式 DMA_InitStructure.DMA_Priority = DMA_Priority_High; //設置優先級 高 DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable; //不使用FIFO模式 DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull; //設置FIFO閾值,不使用FIFO模式,此參數可不用管 DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single; //設置存儲器單次傳輸,DMA發一次請求,傳一個半字 DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; //設置外設單次傳輸,DMA發一次請求,傳一個半字 DMA_Init(DMA1_Stream4, &DMA_InitStructure); //進行初始化 DMA_Cmd(DMA1_Stream4, ENABLE); //使能DMA1的第四個數據流