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 ); }