DMA(Direct Memory Access)常譯為“存儲器直接存取”。早在Intel的8086平台上就有了DMA應用了。 一個完整的微控制器通常由CPU、存儲器和外設等元件構成。這些元件一般在結構和功能上都是獨立的,而各個元件的協調和互動就由CPU完成。如此一來,CPU作為整個晶片的核心,其處理的工作量是很大的。如果CPU先從A外設拿到一個資料送給B外設使用,同時C外設又需要D外設提供一個資料。。。這樣的資料搬運工作将使CPU的負荷顯得相當繁重。 嚴格的說,搬運資料隻是CPU的比較不重要的一種工作。CPU最重要的工作室進行資料運算,從加減乘除到一些進階的運算,包括浮點、積分、微分、FFT等。CPU還需要負責複雜的中斷申請和響應,以保證晶片的實時性能。 理論上常見的控制外設,比如Usart、I2C、SPI甚至是USB等通信接口,單純的利用CPU進行協定模拟也是可以實作的,比如51單片機經常使用I/O口模拟I2C協定通信。但這樣既浪費了CPU的資源,同時實作後的性能表現往往和使用專門的硬體子產品實作的效果相差甚遠。從這個角度來看,各個外設控制器的存在,無疑降低了CPU的負擔,解放了CPU的資源。 資料搬運這一工作占用了大部分的CPU資源,成為了降低CPU的工作效率的主要原因之一。于是需要一種硬體結構分擔CPU這一職能 —— DMA。 從資料搬運的角度看,如果要把存儲位址A的數值賦給另外一個位址上B的變量,CPU實作過程為首先讀出A位址上的資料存儲在一個中間變量,然後再轉送到B位址的變量上。使用DMA則不需要中間變量,直接将A位址的數值傳送到B位址的變量裡。無疑減輕了CPU的負擔,也提高了資料搬運的效率。 stm32中 DMA1有7個通道,DMA2有5個通道。DMA挂載的時鐘為AHB總線,其時鐘為72Mhz,是以可以實作高速資料搬運。 stm32的DMA1通道一覽表
本例實作使用CPU和DMA搬運同一組資料,通過計時,比較兩者的搬運效率。 直接操作寄存器 DMA的中斷狀态寄存器(DMA_ISR):
TEIFx:通道x的傳輸錯誤标志(x = 1 … 7) (Channel x transfer error flag) 硬體設定這些位。在DMA_IFCR寄存器的相應位寫入’1’可以清除這裡對應的标志位。 0:在通道x沒有傳輸錯誤(TE); 1:在通道x發生了傳輸錯誤(TE)。 HTIFx:通道x的半傳輸标志(x = 1 … 7) (Channel x half transfer flag) 硬體設定這些位。在DMA_IFCR寄存器的相應位寫入’1’可以清除這裡對應的标志位。 0:在通道x沒有半傳輸事件(HT); 1:在通道x産生了半傳輸事件(HT)。 TCIFx:通道x的傳輸完成标志(x = 1 … 7) (Channel x transfer complete flag) 硬體設定這些位。在DMA_IFCR寄存器的相應位寫入’1’可以清除這裡對應的标志位。 0:在通道x沒有傳輸完成事件(TC); 1:在通道x産生了傳輸完成事件(TC)。 DMA_IFCR中斷标志清除寄存器: 結構類似DMA_ISR。 CTEIFx:清除通道x的傳輸錯誤标志(x = 1 … 7) (Channel x transfer error clear) 這些位由軟體設定和清除。 0:不起作用 1:清除DMA_ISR寄存器中的對應TEIF标志。 CHTIFx:清除通道x的半傳輸标志(x = 1 … 7) (Channel x half transfer clear) 這些位由軟體設定和清除。 0:不起作用 1:清除DMA_ISR寄存器中的對應HTIF标志。 CTCIFx:清除通道x的傳輸完成标志(x = 1 … 7) (Channel x transfer complete clear) 這些位由軟體設定和清除。 0:不起作用 1:清除DMA_ISR寄存器中的對應TCIF标志。 CGIFx:清除通道x的全局中斷标志(x = 1 … 7) (Channel x global interrupt clear) 這些位由軟體設定和清除。 0:不起作用 1:清除DMA_ISR寄存器中的對應的GIF、TEIF、HTIF和TCIF标志。 DMA通道配置寄存器(DMA_CCRx):
MEM2MEM:存儲器到存儲器模式 (Memory to memory mode) 該位由軟體設定和清除。 0:非存儲器到存儲器模式; 1:啟動存儲器到存儲器模式。 PL:通道優先級 (Channel priority level) 這些位由軟體設定和清除。 00:低 01:中 10:高 11:最高 MSIZE:存儲器資料寬度 (Memory size) 這些位由軟體設定和清除。 00:8位 01:16位 10:32位 11:保留 PSIZE:外設資料寬度 (Peripheral size) 這些位由軟體設定和清除。 00:8位 01:16位 10:32位 11:保留 MINC:存儲器位址增量模式 (Memory increment mode) 該位由軟體設定和清除。 0:不執行存儲器位址增量操作 1:執行存儲器位址增量操作 PINC:外設位址增量模式 (Peripheral increment mode) 該位由軟體設定和清除。 0:不執行外設位址增量操作 1:執行外設位址增量操作 CIRC:循環模式 (Circular mode) 該位由軟體設定和清除。 0:不執行循環操作 1:執行循環操作 DIR:資料傳輸方向 (Data transfer direction) 該位由軟體設定和清除。 0:從外設讀 1:從存儲器讀 TEIE:允許傳輸錯誤中斷 (Transfer error interrupt enable) 該位由軟體設定和清除。 0:禁止TE中斷 0:允許TE中斷 HTIE:允許半傳輸中斷 (Half transfer interrupt enable) 該位由軟體設定和清除。 0:禁止HT中斷 0:允許HT中斷 TCIE:允許傳輸完成中斷 (Transfer complete interrupt enable) 該位由軟體設定和清除。 0:禁止TC中斷 0:允許TC中斷 EN:通道開啟 (Channel enable) 該位由軟體設定和清除。 0:通道不工作 1:通道開啟 DMA通道x傳輸數量寄存器(DMA_CNDTRx)(x = 1…7) 低16位有效。這個寄存器控制通道每次傳輸的資料量,資料傳輸數量為0至65535。該寄存器會随着傳輸的進行而遞減,為0表示已經發送完成。 DMA外設位址寄存器(DMA_CPARx) 32位寄存器。外設資料寄存器的基位址,作為資料傳輸的源或目标。 DMA存儲位址寄存器(DMA_CMARx) 存儲器位址[31:0],存儲器位址作為資料傳輸的源或目标。 代碼如下: (system.h 和 stm32f10x_it.h 等相關代碼參照 stm32 直接操作寄存器開發環境配置) User/main.c
001 | #include <stm32f10x_lib.h> |
008 | #define LED1 PAout(4) |
009 | #define LED2 PAout(5) |
010 | #define LED3 PAout(6) |
012 | void Gpio_Init( void ); |
016 | uc32 SRC_Const_Buffer[32] = |
018 | 0x01020304,0x05060708,0x090A0B0C,0x0D0E0F10, |
019 | 0x11121314,0x15161718,0x191A1B1C,0x1D1E1F20, |
020 | 0x21222324,0x25262728,0x292A2B2C,0x2D2E2F30, |
021 | 0x31323334,0x35363738,0x393A3B3C,0x3D3E3F40, |
022 | 0x41424344,0x45464748,0x494A4B4C,0x4D4E4F50, |
023 | 0x51525354,0x55565758,0x595A5B5C,0x5D5E5F60, |
024 | 0x61626364,0x65666768,0x696A6B6C,0x6D6E6F70, |
025 | 0x71727374,0x75767778,0x797A7B7C,0x7D7E7F80 |
036 | u16 StartTime=0,CPUSpendTime=0,DMASpendTime=0;; |
039 | Rcc_Init(9); //系統時鐘設定 |
041 | Usart1_Init(72,9600); |
043 | Tim_Init(TIM_2,65535,71); //初始化TIM2定時器,設定重裝值和分頻值,計時時間為1us/次 |
045 | Dma_Init(DMA1_Channel1,(u32)SRC_Const_Buffer,(u32)DST_Buffer); //初始化DMA,外設位址示例 &USART1->DR |
047 | Nvic_Init(1,0,DMA1_Channel1_IRQChannel,4); //設定搶占優先級為0,響應優先級為0,中斷分組為4 |
051 | StartTime = TIM2->CNT; |
055 | DST_Buffer[i]=SRC_Const_Buffer[i]; |
059 | CPUSpendTime = TIM2->CNT - StartTime; |
061 | printf ( "\r\n the CPU spend : %dus! \r\n" ,CPUSpendTime); |
063 | if ( strncmp (( const char *)SRC_Const_Buffer,( const char *)DST_Buffer,32) ==0) //驗證傳輸效果,判斷兩數組是否相同 |
065 | printf ( "\r\n CPU Transmit Success! \r\n" ); |
067 | printf ( "\r\n CPU Transmit Fail! \r\n" ); |
072 | while (i<32) //清空目标數組,準備DMA搬運 |
078 | StartTime = TIM2->CNT; |
080 | Dma_Enable(DMA1_Channel1,32); //DMA搬運 |
082 | while ( DMA1_Channel1 -> CNDTR != 0); //等待傳輸完成 |
084 | DMASpendTime= TIM2->CNT - StartTime; |
086 | printf ( "\r\n the DMA spend : %dus! \r\n" ,DMASpendTime); |
088 | if ( strncmp (( const char *)SRC_Const_Buffer,( const char *)DST_Buffer,32) ==0) //驗證傳輸效果,判斷兩數組是否相同 |
090 | printf ( "\r\n DMA Transmit Success! \r\n" ); |
092 | printf ( "\r\n DMA Transmit Fail! \r\n" ); |
099 | void Gpio_Init( void ) |
101 | RCC->APB2ENR|=1<<2; //使能PORTA時鐘 |
103 | GPIOA->CRL&=0x0000FFFF; // PA0~3設定為浮空輸入,PA4~7設定為推挽輸出 |
104 | GPIOA->CRL|=0x33334444; |
108 | GPIOA -> CRH&=0xFFFFF00F; //設定USART1 的Tx(PA.9)為第二功能推挽,50MHz;Rx(PA.10)為浮空輸入 |
109 | GPIOA -> CRH|=0x000008B0; |
User/stm32f10x_it.c
01 | #include "stm32f10x_it.h" |
10 | void DMAChannel1_IRQHandler( void ) //和啟動檔案有關,STM32F10x.s中 和 STM32F10x_md.s DMA中斷接口函數不同 |
13 | if ( DMA1 ->ISR & (1<<1)) //傳輸完成中斷 |
17 | DMA1->IFCR |= 1<<1; //清除傳輸完成中斷 |
20 | if ( DMA1 ->ISR & (1<<2)) //半傳輸完成中斷 |
23 | DMA1 ->IFCR |= 1<<2; //清除半傳輸完成中斷 |
26 | if ( DMA1 ->ISR & (1<<3)) //傳輸錯誤中斷 |
29 | DMA1 ->IFCR |= 1<<3; //清除傳輸錯誤中斷 |
32 | DMA1 ->IFCR |= 1<<0; //清除此通道的中斷 |
Library/src/dma.c
01 | #include <stm32f10x_lib.h> |
07 | //傳輸方向:存儲器 -> 存儲器模式 ,32位資料模式,存儲器增量模式 |
09 | // DMA_CHx :選擇DMA控制器通道,DMA1有1-7,DMA2有1-4 |
13 | void Dma_Init(DMA_Channel_TypeDef * DMA_CHx,u32 P_Address ,u32 M_Address) |
18 | DMA_CHx -> CCR &= 0xFFFF0000; //複位 |
20 | DMA_CHx -> CCR |= 1<<1; //允許傳輸完成中斷 |
21 | //DMA_CHx -> CCR |= 1<<2; //允許半傳輸中斷 |
22 | DMA_CHx -> CCR |= 1<<3; //允許傳輸錯誤中斷 讀寫一個保留的位址區域,将會産生DMA傳輸錯誤 |
26 | DMA_CHx -> CCR |= 0<<4; //設定資料傳輸方向 0:從外設讀 1:從存儲器讀 |
27 | DMA_CHx -> CCR |= 0<<5; //0:不執行循環操作 1:執行循環操作 |
30 | DMA_CHx -> CCR |= 1<<6; //0:不執行外設位址增量操作 1:執行外設位址增量操作 |
31 | DMA_CHx -> CCR |= 1<<7; //0:不執行存儲器位址增量操作 1:執行存儲器位址增量操作 |
34 | DMA_CHx -> CCR |= 0<<8; //外設資料寬度,由[9:8]兩位控制 |
35 | DMA_CHx -> CCR |= 1<<9; //00:8位 01:16位 10:32位 11:保留 |
38 | DMA_CHx -> CCR |= 0<<10; //存儲器資料寬度,由[11:10]兩位控制 |
39 | DMA_CHx -> CCR |= 1<<11; //00:8位 01:16位 10:32位 11:保留 |
42 | DMA_CHx -> CCR |= 1<<12; //通道優先級,由[13:12]兩位控制 |
43 | DMA_CHx -> CCR |= 1<<13; //00:低 01:中 10:高 11:最高 |
45 | DMA_CHx -> CCR |= 1<<14; //0:非存儲器到存儲器模式; 1:啟動存儲器到存儲器模式。 |
48 | DMA_CHx -> CPAR = (u32)P_Address; //設定外設寄存器位址 |
49 | DMA_CHx -> CMAR = (u32)M_Address; //設定資料存儲器位址 |
57 | // DMA_CHx :選擇DMA控制器通道,DMA1有1-7,DMA2有1-4 |
59 | void Dma_Enable(DMA_Channel_TypeDef * DMA_CHx,u16 Number) |
61 | DMA_CHx -> CCR &= ~(1<<0); //關閉上一次DMA傳輸 |
62 | DMA_CHx -> CNDTR = Number; //資料傳輸量 |
63 | DMA_CHx -> CCR |= 1<<0; //開始DMA傳輸 |
Library/inc/dma.h
1 | #include <stm32f10x_lib.h> |
3 | void Dma_Init(DMA_Channel_TypeDef * DMA_CHx,u32 P_Adress ,u32 M_Address); |
4 | void Dma_Enable(DMA_Channel_TypeDef * DMA_CHx,u16 Number); |
直接操作寄存器輸出: the CPU spend : 972us! CPU Transmit Success! the DMA spend : 5us! DMA Transmit Success! 庫函數操作 mian.c
001 | #include "stm32f10x.h" |
006 | #define BufferSize 32 |
008 | vu16 LeftDataCounter; |
011 | uc32 SRC_Const_Buffer[BufferSize] = |
013 | 0x01020304,0x05060708,0x090A0B0C,0x0D0E0F10, |
014 | 0x11121314,0x15161718,0x191A1B1C,0x1D1E1F20, |
015 | 0x21222324,0x25262728,0x292A2B2C,0x2D2E2F30, |
016 | 0x31323334,0x35363738,0x393A3B3C,0x3D3E3F40, |
017 | 0x41424344,0x45464748,0x494A4B4C,0x4D4E4F50, |
018 | 0x51525354,0x55565758,0x595A5B5C,0x5D5E5F60, |
019 | 0x61626364,0x65666768,0x696A6B6C,0x6D6E6F70, |
020 | 0x71727374,0x75767778,0x797A7B7C,0x7D7E7F80 |
023 | u32 DST_Buffer[BufferSize]; |
024 | u8 i=0,DMASpendTime=0,CPUSpendTime=0; |
026 | void RCC_Configuration( void ); |
027 | void GPIO_Configuration( void ); |
028 | void NVIC_Configuration( void ); |
029 | void USART_Configuration( void ); |
030 | void DMA_Configuration( void ); |
036 | GPIO_Configuration(); |
037 | NVIC_Configuration(); |
038 | USART_Configuration(); |
046 | DST_Buffer[i]=SRC_Const_Buffer[i]; |
058 | DMA_Cmd(DMA1_Channel6,ENABLE); |
059 | while (LeftDataCounter != 0); //等待傳輸完成 |
064 | if ( strncmp (( const char *)SRC_Const_Buffer,( const char *)DST_Buffer,BufferSize) ==0) |
066 | printf ( "\r\n Transmit Success! \r\n" ); |
068 | printf ( "\r\n Transmit Fail! \r\n" ); |
071 | printf ( "\r\n the CPU spend : %dus! \r\n" ,CPUSpendTime); |
072 | printf ( "\r\n the DMA spend : %dus! \r\n" ,DMASpendTime); |
076 | void DMA_Configuration( void ) |
078 | DMA_InitTypeDef DMA_InitStructure; |
080 | DMA_DeInit(DMA1_Channel6); |
081 | DMA_InitStructure.DMA_PeripheralBaseAddr = (u32) SRC_Const_Buffer; |
082 | DMA_InitStructure.DMA_MemoryBaseAddr = (u32) DST_Buffer; |
083 | DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; |
084 | DMA_InitStructure.DMA_BufferSize = BufferSize; |
085 | DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable; |
086 | DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; |
087 | DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word; |
088 | DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word; |
089 | DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; |
090 | DMA_InitStructure.DMA_Priority = DMA_Priority_High; |
091 | DMA_InitStructure.DMA_M2M = DMA_M2M_Enable; |
092 | DMA_Init(DMA1_Channel6,&DMA_InitStructure); |
094 | DMA_ITConfig(DMA1_Channel6,DMA_IT_TC,ENABLE); |
097 | void GPIO_Configuration( void ) |
099 | GPIO_InitTypeDef GPIO_InitStructure; |
100 | GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; |
101 | GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; |
102 | GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; |
103 | GPIO_Init(GPIOA , &GPIO_InitStructure); |
104 | GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; |
105 | GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; |
106 | GPIO_Init(GPIOA , &GPIO_InitStructure); |
111 | void RCC_Configuration( void ) |
114 | ErrorStatus HSEStartUpStatus; |
119 | RCC_HSEConfig(RCC_HSE_ON); |
121 | HSEStartUpStatus = RCC_WaitForHSEStartUp(); |
123 | if (HSEStartUpStatus == SUCCESS) |
126 | RCC_HCLKConfig(RCC_SYSCLK_Div1); |
128 | RCC_PCLK2Config(RCC_HCLK_Div1); |
130 | RCC_PCLK1Config(RCC_HCLK_Div2); |
132 | FLASH_SetLatency(FLASH_Latency_2); |
134 | FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable); |
136 | RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9); |
140 | while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET); |
142 | RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); |
144 | while (RCC_GetSYSCLKSource() != 0x08); |
147 | RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_USART1, ENABLE); |
148 | RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); |
150 | //RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR|RCC_APB1Periph_BKP|RCC_APB1Periph_WWDG, ENABLE); |
155 | void USART_Configuration( void ) |
157 | USART_InitTypeDef USART_InitStructure; |
158 | USART_ClockInitTypeDef USART_ClockInitStructure; |
160 | USART_ClockInitStructure.USART_Clock = USART_Clock_Disable; |
161 | USART_ClockInitStructure.USART_CPOL = USART_CPOL_Low; |
162 | USART_ClockInitStructure.USART_CPHA = USART_CPHA_2Edge; |
163 | USART_ClockInitStructure.USART_LastBit = USART_LastBit_Disable; |
164 | USART_ClockInit(USART1 , &USART_ClockInitStructure); |
166 | USART_InitStructure.USART_BaudRate = 9600; |
167 | USART_InitStructure.USART_WordLength = USART_WordLength_8b; |
168 | USART_InitStructure.USART_StopBits = USART_StopBits_1; |
169 | USART_InitStructure.USART_Parity = USART_Parity_No; |
170 | USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; |
171 | USART_InitStructure.USART_Mode = USART_Mode_Rx|USART_Mode_Tx; |
172 | USART_Init(USART1,&USART_InitStructure); |
174 | USART_Cmd(USART1,ENABLE); |
177 | void NVIC_Configuration( void ) |
179 | NVIC_InitTypeDef NVIC_InitStructure; |
181 | NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0); |
183 | NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel6_IRQn; |
184 | NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; |
185 | NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; |
186 | NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; |
187 | NVIC_Init(&NVIC_InitStructure); |
192 | int fputc ( int ch, FILE *f) |
194 | USART_SendData(USART1,(u8) ch); |
195 | while (USART_GetFlagStatus(USART1,USART_FLAG_TC) == RESET); |
stm32f10x_it.c
01 | #include "stm32f10x_it.h" |
06 | extern vu16 LeftDataCounter; |
09 | void SysTick_Handler( void ) |
14 | void DMA1_Channel6_IRQHandler( void ) |
17 | LeftDataCounter = DMA_GetCurrDataCounter(DMA1_Channel6); //擷取剩餘待傳輸資料 |
18 | DMA_ClearITPendingBit(DMA1_IT_GL6); |
庫函數輸出: Transmit Success! the CPU spend : 68us! the DMA spend : 7us!