標籤:
AN2548 -- 使用 STM32F101xx 和 STM32F103xx 的 DMA 控制器DMA控制器
DMA是AMBA的先進高效能匯流排(AHB)上的裝置,它有2個AHB連接埠:
一個是從連接埠,用於配置DMA,另一個是主連接埠,使得DMA可以在不同的從裝置之間傳輸資料。
DMA的作用是在沒有Cortex-M3核心的幹預下,在後台完成資料轉送。
在傳輸資料的過程中,主處理器可以執行其它任務,只有在整個資料區塊傳輸結束後,
需要處理這些資料時才會中斷主處理器的操作。
它可以在對系統效能產生較小影響的情況下,實現大量資料的傳輸。
DMA主要用來為不同的外設模組實現集中的資料緩衝儲存區(通常在系統的SRAM中)。
與分布式的解決方案(每個外設需要實現自己的資料存放區)相比,
這種解決方案無論在晶片使用面積還是功耗方面都要更勝一籌。
STM32F10XXX的DMA控制器充分利用了Cortex-M3哈佛架構和多層匯流排系統的優勢,
達到非常低的DMA資料轉送延時和CPU響應中斷延遲。
DMA的主要特性
DMA具有以下的特性:
- 7個DMA通道(通道1至7)支援單向的從源端到目標端的資料轉送
- 硬體DMA通道優先順序和可程式化的軟體DMA通道優先順序
- 支援儲存空間到儲存空間、儲存空間到外設、外設到儲存空間、外設到外設的資料轉送(儲存空間可以是SRAM或者快閃記憶體)
- 能夠對硬體/軟體傳輸進行控制
- 傳輸時自動增加儲存空間和外設指標
- 可程式化傳輸資料字長度
- 自動的匯流排錯誤管理
- 迴圈模式/非迴圈模式
- 可傳輸高達65536個資料字
DMA旨在為所有外設提供相對較大的資料緩衝區,這些緩衝區一般位於系統的SRAM中。
每一個通道在特定的時間裡分配給唯一的外設,串連到同一個DMA通道的外設
(表1中的通道1到通道7)不能夠同時使用DMA功能。
效能分析
STM32F10xxx有兩個主模組——Cortex-M3處理器和DMA。
他們通過匯流排矩陣串連到從匯流排、快閃記憶體匯流排、SRAM匯流排和AHB系統匯流排。
從匯流排的另一端串連到兩個APB匯流排,以服務所有的嵌入的外設(參見圖1)。
匯流排矩陣有兩個主要的特性,實現系統效能的最大化和減少延時:
●輪詢優先順序方案
●多層結構和匯流排挪用
輪詢優先順序方案
NVIC和Cortex-M3處理器實現了高效能低延時中斷方案。
所有的Cortex-M3指令都或者是單周期執行指令,或者可以在匯流排周期級上被中斷。
為了在系統層面上保持這個優點,DMA和匯流排矩陣必須確保DMA不能夠長時間佔用匯流排。
輪詢優先順序方案能夠確保,如有必要,CPU能夠每兩個匯流排周期就去訪問其它從匯流排。
因此,在CPU看來第一個資料的最大匯流排系統延時,就是一個匯流排周期(最大兩個APB刻度)。
多層結構和匯流排挪用
多層結構允許兩個主裝置同時執行資料轉送,只要他們定址到不同的從模組。
在Cortex-M3哈佛架構基礎上,這種多層結構提高了資料的並行性,
因此減少了代碼執行時間並且最佳化了DMA效率。
由於從Flash儲存空間取指是通過完全獨立的匯流排,
所以DMA和CPU只是在需要通過同一個從匯流排進行資料訪問時才會產生競爭。
另外,在其它DMA控制器工作於高載模式時,STM32F10xxx的DMA資料轉送只使用單個匯流排周期(匯流排挪用)。
使用匯流排挪用存取機制時,CPU進行資料訪問所等待的最大時間是很短的(一個匯流排周期) 。
通常,CPU對SRAM的訪問是與DMA操作交替地進行,CPU訪問SRAM的同時DMA就在通過APB匯流排訪問外設。
儘管使用DMA的高載模式可以提高(DMA訪問外設)資料轉送速度,
但不可避免地是CPU的執行速度被拖慢。顯示了匯流排挪用和突發機制的區別。
圖2 DMA傳輸的匯流排挪用機制和突發機制
極端的情況發生在CPU從記憶體的一個地方複製一塊資料到記憶體中的另一個地方。
這種情況下,軟體的執行須等到整個DMA傳輸完畢才能進行。
實際上,CPU大部分時間是在做資料處理(寄存器的讀/寫),
比較少地進行資料訪問,因此CPU和DMA對資料的存取還算是交替進行著。
STM32F10xxx匯流排結構固有的並行性,加上DMA匯流排挪用機制,
保證了CPU不會長時間地等待從SRAM中讀取資料。
採用匯流排挪用機制的DMA因此能夠更高效地使用匯流排,從而顯著地減少了軟體執行的時間。
DMA延遲
DMA完成從外設到SRAM儲存空間的資料轉送有三個步驟:
- 1. DMA請求仲裁
- 2. 從外設中讀取資料(DMA源)
- 3. 將讀取的資料寫入到SRAM中(DMA目標)
當DMA把資料從記憶體中傳輸到外設(例如SPI傳送),操作步驟如下:
- 1. DMA請求仲裁
- 2. 從SRAM中讀取資料(DMA源)
- 3. 將讀取到的資料通過APB匯流排寫入到外設中(DMA目標)
服務每個DMA通道的總時間,
tS = tA + tACC + tSRAM 這裡,
tA是仲裁時間, tA = 1個AHB刻度
tACC是訪問外設時間, tACC = 1個AHB刻度(匯流排矩陣仲裁) + 2個APB刻度(實際的資料轉送) + 1個AHB刻度(匯流排同步)
tSRAM是讀寫SRAM的時間, tSRAM = 1個AHB刻度(匯流排矩陣仲裁) + 1個AHB刻度(單一的讀/寫操作) 或者 + 2個AHB刻度(先讀SRAM再寫SRAM的情況)
當DMA通道空閑或者是前一個DMA通道的第3步操作完成後,DMA控制器比較所有掛起的DMA請求的優先順序
(先比較軟體優先順序;軟體優先順序相同時,再比較硬體優先順序),
高優先順序的通道將會被服務,DMA開始執行第2步操作。
當一個通道正在服務時(第2、3步操作進行中),沒有其他的通道能夠被服務,不管它的優先順序如何。
當至少同時使能了兩個DMA通道時,最高優先順序通道的DMA延遲時間為正在傳輸的時間(不包括仲裁階段),
加上下個將被服務的DMA通道(掛起優先順序最高的通道)資料轉送的時間。
資料匯流排頻寬節流設定
資料匯流排頻寬節流設定主要是因為APB匯流排比系統SRAM和AHB匯流排速度慢。
對於最高優先順序的DMA通道,必須考慮以下兩種情況:(參見圖3)
1. 當不止一個DMA通道被使能時,最高優先順序的通道在APB匯流排上佔用的資料頻寬必須低於APB最高傳輸率的25%。
APB匯流排傳輸的所有時間必須考慮在內,即2個APB刻度加上用來仲裁/同步的2個AHB刻度。
2. 儘管高速/高優先順序DMA傳輸通常發生在APB2上(更快的APB匯流排),但是CPU和其他DMA通道可以訪問APB1上的外設。
大約3/4的APB傳輸是在APB1上完成的,最小的APB2頻率依賴於最快的DMA通道資料頻寬。
最大的APB時鐘分頻因子由下列的等式給出: fAHB > (2 x N2 + 6 x N1 + 6) x Bmax
如果 N2 < N1 則 N1 < (fAHB/ Bmax)/8 其中fAHB是AHB時鐘頻率, N1和N2分別是APB1和APB2的時鐘分頻因子, Bmax是APB2上的最大資料頻寬,單位為傳輸次數/秒。
圖3 DMA傳輸過程中APB匯流排的佔用情況
通道優先順序選擇
為了實現外設資料的連續傳輸,相關的DMA通道必須能夠維持外設資料轉送率,
確保DMA服務的延遲時間少於連續兩個外設資料的時間間隔。
高速/高頻寬外設必須擁有最高的DMA優先順序,
這確保了最大的資料延遲對於這些外設都是可以忍受的,而且可以避免溢出和下溢的情況。
在相同頻寬需求的情況下,推薦給工作在從模式下(不能對資料轉送速度進行控制)的外設分配較高的優先順序,
工作在主模式(能夠控制資料流)下的外設分配相對低的優先順序。
預設情況下,通道和硬體優先順序(從1到7)的分配,
是按照最快的外設分配最高優先順序的順序來分配的。
當然,在某些運用場合下也許這種分配並不適用;
此時,使用者可以為每一個通道配置軟體優先順序(分4種,從非常高到低),
軟體優先順序優先於硬體優先順序。
當同時使用幾個外設(不管有沒有使用DMA)時,使用者必須確保內部系統能夠維持應用所要求的總資料頻寬,
必須權衡以下兩個因素,找到一個折中方案:
應用需求
以SPI介面為例,SPI介面資料頻寬是通過傳輸速率除以SPI的資料字長度而得到的
(因為資料是一個緊接著下一個傳輸的)。假設SPI的傳輸速率是18Mbps,
資料是以8位傳輸的,操作配置在單工模式下,因此,
內部資料頻寬需求是2.25M傳輸/秒;
如果SPI配置為16位元模式,則資料頻寬將是1.125M傳輸/秒。
注意: 當使用SPI的16位元模式時,同樣傳輸速率下,
資料頻寬除以2,即只需要1.125M/秒的傳輸。
強烈推薦,儘可能地使用16位元模式,以減少匯流排佔用和功耗。
內部資料頻寬
內部資料頻寬依賴於以下兩個條件:
- 匯流排頻率 -- 可獲得的資料頻寬與匯流排時鐘頻率是成正比的
- 匯流排類型 ─ AHB資料轉送需要2個刻度(SRAM先寫後讀訪問需要3個周期)。
資料通過APB匯流排傳輸給外設需要花費2個APB刻度加上兩個AHB刻度用來做匯流排仲裁和資料同步。
推薦DMA對匯流排的佔用保持在2/3以下,這樣才能保證一個合理的系統和CPU的效能水平。
使用DMA實現GPIO快速資料轉送
這個例子示範了如何將不同的外設用於DMA請求和資料轉送,
這個機制允許在沒有使用CPU的情況下實現簡單的快速並行同步介面。
定時器3和串連到TIM3_TRIG 的DMA通道6,用來實現擷取資料的介面。
在GPIO的連接埠上可以擷取16位並行資料。
一個外部時鐘訊號作用在定時器3的外部觸發器輸入端,
在外部觸發器上升沿,定時器產生一個DMA請求。
由於GPIO資料寄存器地址已設定到DMA通道6的外設地址,
DMA控制器在每一次DMA請求時從GPIO連接埠讀取資料,並把它儲存到SRAM的緩衝器中。
This example shows how to use different peripherals for DMA request and data transfer.
This mechanism allows to implement simple fast parallel synchronous interfaces without using the CPU (for example a camera interface).
Timer 3 and DMA1 channel 6 connected to TIM3_TRIG are used to implement this data acquisition interface.
An 16-bit parallel data is available on the GPIO port and an external clock signal applied on the external trigger input of Timer 3.
On the rising edge of the external trigger, the timer generates a DMA request.
As the GPIO data register address is set to DMA1 channel 6 peripheral address,
the DMA controller reads the data from the GPIO port on each DMA request, and stores it into an SRAM buffer.
This example shows how the DMA can be used to acquire data from a GPIO (parallel) port,
synchronised with an (external) clock signal. (for the sake of this demo, the clock is generated by software toggling GPIOA pin 6).
The control of the DMA channel is done through the TIM3 channel 1 (Input Capture Mode) which is using the DMA channel 6.
This is a non standard utilisation of the DMA as the peripheral controlling the DMA request (TIM3) is
neither the source, nor the destination of the DMA data transfer.
Instead, the data source is a GPIO port (PD0-PD15) - programmed in GPIO input mode and
the data destination is a RAM buffer (accessed by the DMA Channel 6 in circular mode)
The TIMCLK frequency is set to 72 MHz, and used in Input Capture/DMA Mode.
The system clock is set to 72MHz.
Hardware and Software environment
- This example runs on STM32F10x High-Density, STM32F10x Medium-Density and STM32F10x Low-Density Devices.
- This example has been tested with STMicroelectronics STM3210B-EVAL evaluation boards
and can be easily tailored to any other supported device and development board.
- STM3210B-EVAL Set-up
- Connect a signal generator on PD.00 to PD.15.
- In the example, the PA.06 (capture clock signal) is driven internally (by SW).
By removing the SW control on this pin (leaving the GPIO in input floating mode), an external clock signal can be used.
Alternativelly, PA.06 may be driven externally (leaving PA.06 in input floating mode - alternate function).
#include "stm32f10x.h"TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;TIM_ICInitTypeDef TIM_ICInitStructure;DMA_InitTypeDef DMA_InitStructure;__IO uint16_t Parallel_Data_Buffer[ 512 ];ErrorStatus HSEStartUpStatus;void RCC_Configuration( void );void GPIO_Configuration( void );int main( void ){ /* System Clocks Configuration ---------------------------------------------*/ RCC_Configuration( ); /* GPIO Configuration ------------------------------------------------------*/ GPIO_Configuration( ); /* DMA Channel6 Configuration ----------------------------------------------*/ DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t) &GPIOD->IDR; DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t) Parallel_Data_Buffer; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; DMA_InitStructure.DMA_BufferSize = 512; 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_VeryHigh; DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; DMA_Init( DMA1_Channel6, &DMA_InitStructure ); /* Enable DMA Channel6 */ DMA_Cmd( DMA1_Channel6, ENABLE ); /* TIM3 Configuration ------------------------------------------------------*/ /* TIM3CLK = 72 MHz, Prescaler = 0, TIM3 counter clock = 72 MHz */ /* Time base configuration */ TIM_TimeBaseStructure.TIM_Period = 256; TIM_TimeBaseStructure.TIM_Prescaler = 0; TIM_TimeBaseStructure.TIM_ClockDivision = 0; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit( TIM3, &TIM_TimeBaseStructure ); /* Input Capture Mode configuration: Channel1 */ TIM_ICInitStructure.TIM_Channel = TIM_Channel_1; TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; TIM_ICInitStructure.TIM_ICFilter = 0; TIM_ICInit( TIM3, &TIM_ICInitStructure ); /* Enable TIM3 DMA */ TIM_DMACmd( TIM3, TIM_DMA_CC1, ENABLE ); /* Enable TIM3 counter */ TIM_Cmd( TIM3, ENABLE ); while ( 1 ) { /* Trigger TIM3 IC event => DMA request by toggling PA.06 */ GPIO_ResetBits( GPIOA, GPIO_Pin_6 ); GPIO_SetBits( GPIOA, GPIO_Pin_6 ); }}void RCC_Configuration( void ){ /* RCC system reset(for debug purpose) */ RCC_DeInit( ); /* Enable HSE */ RCC_HSEConfig( RCC_HSE_ON ); /* Wait till HSE is ready */ HSEStartUpStatus = RCC_WaitForHSEStartUp( ); if ( HSEStartUpStatus == SUCCESS ) { /* Enable Prefetch Buffer */ FLASH_PrefetchBufferCmd( FLASH_PrefetchBuffer_Enable ); /* Flash 2 wait state */ FLASH_SetLatency( FLASH_Latency_2 ); /* HCLK = SYSCLK */ RCC_HCLKConfig( RCC_SYSCLK_Div1 ); /* PCLK2 = HCLK */ RCC_PCLK2Config( RCC_HCLK_Div1 ); /* PCLK1 = HCLK/2 */ RCC_PCLK1Config( RCC_HCLK_Div2 ); /* ADCCLK = PCLK2/4 */ RCC_ADCCLKConfig( RCC_PCLK2_Div4 ); /* PLLCLK = 8MHz * 9 = 72 MHz */ RCC_PLLConfig( RCC_PLLSource_HSE_Div1, RCC_PLLMul_9 ); /* Enable PLL */ RCC_PLLCmd( ENABLE ); /* Wait till PLL is ready */ while ( RCC_GetFlagStatus( RCC_FLAG_PLLRDY ) == RESET ) { } /* Select PLL as system clock source */ RCC_SYSCLKConfig( RCC_SYSCLKSource_PLLCLK ); /* Wait till PLL is used as system clock source */ while ( RCC_GetSYSCLKSource( ) != 0x08 ) { } } /* Enable TIM3 clock */ RCC_APB1PeriphClockCmd( RCC_APB1Periph_TIM3, ENABLE ); /* Enable DMA clock */ RCC_AHBPeriphClockCmd( RCC_AHBPeriph_DMA1, ENABLE ); /* GPIOA and GPIOD clock enable */ RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOD, ENABLE ); }void GPIO_Configuration( void ){ GPIO_InitTypeDef GPIO_InitStructure; /* GPIOA Configuration: PA6 GPIO Output -> TIM3 Channel1 in Input */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init( GPIOA, &GPIO_InitStructure );}
STM32 GPIO fast data transfer with DMA