FROM:https://blog.csdn.net/gdjason/article/details/51019219
什么是DMA —- Directional Memory Access, 直接存儲器存取用來提供在外設和存儲器之間或者存儲器和存儲器之間的高速數據傳輸。無須CPU干預,數據可以通過DMA快速地移動,這就節省了CPU的資源來做其他操作
我們通過以下幾方面學習串口DMA:
一、如何理解DMA
對於DMA,打個比方就很好理解:
角色預設: 淘寶店主 —- STM32 MCU
快遞員 —- 外設(如UART,SPI)
發貨室 —- DMA
1、首先你是一個淘寶店主,如果每次發貨收貨都要跟快遞溝通交涉會很浪費時間和精力。
2、然后你就自己建了一個發貨室,發貨室里有好多個貨櫃箱子,每個箱子上都寫着快遞名字(如果申通快遞,順豐快遞等)。
3、每次發什么快遞,你就找到對應的貨櫃箱子,把貨物放進去即可,然后跟快遞通知一聲。
4、快遞取走快件。
5、如果是收貨,快遞直接把快件放到對應的櫃子,然后通知你一下。
6、你過來提取貨物。
通過上面的方式,你可以不需要直接跟快遞打交道,就可以輕松發貨成功,DMA處理方式跟上面例子是一樣的。
如果下圖:
二、STM32 DMA 配置
那么DMA在STM32上是具體怎么實現的呢? 我們先了解一下STM32關於DMA的相關配置。
1、兩個DMA控制器有12個通道(DMA1有7個通道,DMA2有5個通道)
ps:對應我們例子,就是有兩個大的發貨室,一個有7個貨櫃,另個有5個貨櫃。
2、在同一個DMA模塊上,多個請求間的優先權可以通過軟件編程設置(共有四級:很高、高、中等和低),優先權設置相等時由硬件決定(請求0優先於請求1,依此類推)
ps: 店主可以跟每個快遞公司簽訂協議,可以在貨櫃前貼上加急(很高),很急(高),急(中),一般(低), 如果同時有幾個快遞員過來取貨,優先根據上面的優先級先取件。
3、獨立數據源和目標數據區的傳輸寬度(字節、半字、全字),模擬打包和拆包的過程。源和目標地址必須按數據傳輸寬度對齊。
ps: 指的是貨件大小
4、支持循環的緩沖器管理(會把原來的數據覆蓋)
5、每個通道都有3個事件標志(DMA半傳輸、DMA傳輸完成和DMA傳輸出錯),這3個事件標志邏輯或成為一個單獨的中斷請求。
ps: 送快遞出現的異常情況(送到了一半,送完,快遞出錯)
解釋到這里,不知道大家能不能理解呢。后面是具體的配置。
1、DMA 對應通道如下圖
DMA1:
DMA2:
2、DMA配置
1)數據傳輸的目的地和來源
對應我的例子,就是送快遞還是取快遞。
2)定義DMA通道的DMA緩存的大小
ps: 即貨櫃大小,能存多少個快件
3)外設地址寄存器遞增與否
4)內存地址寄存器遞增與否
5)設定了外設數據寬度
6)設定了內存數據寬度
7)設置了DMA的工作模式
8)DMA通道的軟件優先級
9)使能或關閉DMA通道的內存到內存傳輸
三、 編程
串口用DMA方式發送和接收,分以下幾步:
1)串口初始化
2)DMA初始化
3)發送數據
4)接收數據
我們按部就班:
1) 串口初始化 — 使用串口一
1 #define DMASIZE 1024 2 3 // 配置串口一的發送和接收的GPIO口功能,以及中斷 4 static void _uart1_gpio_init(void) 5 { 6 NVIC_InitTypeDef NVIC_InitStructure; 7 GPIO_InitTypeDef GPIO_InitStructure; 8 9 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | 10 RCC_APB2Periph_USART1 | 11 RCC_APB2Periph_AFIO, ENABLE) ; 12 13 GPIOA->CRH&=0XFFFFF00F; 14 GPIOA->CRH|=0X000008B0;//IO狀態設置 10pin_上拉輸入 9pin_推挽輸出 15 16 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; 17 /* Configure USART1 Rx as input floating */ 18 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; 19 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; 20 GPIO_Init(GPIOA, &GPIO_InitStructure); 21 22 /* Configure USART1 Tx as alternate function push-pull */ 23 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; 24 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; 25 GPIO_Init(GPIOA, &GPIO_InitStructure); 26 27 28 /* Enable the USART1 Interrupt */ 29 NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQChannel; 30 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; 31 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; 32 NVIC_Init(&NVIC_InitStructure); 33 34 USART_ClearFlag(USART1, USART_FLAG_TC); /* 清發送外城標志,Transmission Complete flag */ 35 36 USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);// 采用空閑中斷,目的是在產生空閑中斷時,說明接收或者發送已經結束,此時可以讀取DMA中的數據了。 37 //USART_ITConfig(USART1, USART_IT_TC, ENABLE); 38 //USART_ITConfig(USART1, USART_IT_FE, ENABLE); 39 } 40 // 設置對應串口的波特率 41 static void _uart_setbaudrate(USART_TypeDef* USARTx,u32 value) 42 { 43 USART_InitTypeDef USART_InitStructure; 44 USART_InitStructure.USART_BaudRate =value; 45 USART_InitStructure.USART_WordLength = USART_WordLength_8b; 46 USART_InitStructure.USART_StopBits = USART_StopBits_1; 47 USART_InitStructure.USART_Parity = USART_Parity_No; 48 USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; 49 USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; 50 USART_Init(USARTx, &USART_InitStructure); 51 USART_Cmd(USARTx, ENABLE); 52 }
2)初始化DMA
1 u8 sendbuf[1024]; 2 u8 receivebuf[1024]; 3 static void _uart1_dma_configuration() 4 { 5 DMA_InitTypeDef DMA_InitStructure; 6 7 /* DMA1 Channel6 (triggered by USART1 Rx event) Config */ 8 RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1 , 9 ENABLE); 10 11 /* DMA1 Channel5 (triggered by USART1 Rx event) Config */ 12 DMA_DeInit(DMA1_Channel5); 13 DMA_InitStructure.DMA_PeripheralBaseAddr = USART1_DR_Base;// 初始化外設地址,相當於“哪家快遞” 14 DMA_InitStructure.DMA_MemoryBaseAddr =(u32)receivebuf;// 內存地址,相當於幾號櫃 15 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//外設作為數據來源,即為收快遞 16 DMA_InitStructure.DMA_BufferSize = DMASIZE ;// 緩存容量,即櫃子大小 17 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 外設地址不遞增,即櫃子對應的快遞不變 18 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;// 內存遞增 19 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //外設字節寬度,即快遞運輸快件大小度量(按重量算,還是按體積算) 20 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;// 內存字節寬度,即店主封裝快遞的度量(按重量,還是按體質進行封裝) 21 DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;// 正常模式,即滿了就不在接收了,而不是循環存儲 22 DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;// 優先級很高,對應快遞就是加急 23 DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; // 內存與外設通信,而非內存到內存 24 DMA_Init(DMA1_Channel5, &DMA_InitStructure);// 把參數初始化,即擬好與快遞公司的協議 25 26 DMA_Cmd(DMA1_Channel5, ENABLE);// 啟動DMA,即與快遞公司簽訂合同,正式生效 27 28 /* DMA1 Channel4 (triggered by USART1 Tx event) Config */ 29 DMA_DeInit(DMA1_Channel4); 30 DMA_InitStructure.DMA_PeripheralBaseAddr = USART1_DR_Base; // 外設地址,串口1, 即發件的快遞 31 DMA_InitStructure.DMA_MemoryBaseAddr =(u32)sendbuf;// 發送內存地址 32 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;// 外設為傳送數據目的地,即發送數據,即快遞是發件 33 DMA_InitStructure.DMA_BufferSize = 0; //發送長度為0,即未有快遞需要發送 34 DMA_Init(DMA1_Channel4, &DMA_InitStructure);//初始化 35 36 USART_ITConfig(USART1, USART_IT_TC, ENABLE);// 使能串口發送完成中斷 37 USART_DMACmd(USART1, USART_DMAReq_Tx|USART_DMAReq_Rx, ENABLE);// 使能DMA串口發送和接受請求 38 }
3、數據發送
流程:串口發送數據,全部數據發送完畢后,會產生一個發送中斷,所以
發送數據分為兩部分,
A、發送數據
B、中斷處理
1 A、發送數據 2 u16 Uart_Send_Data(void* buffer, u16 size) 3 { 4 if(!size) return 0;// 判斷長度是否有效 5 while (DMA_GetCurrDataCounter(DMA1_Channel4));// 檢查DMA發送通道內是否還有數據 6 if(buffer) memcpy(sendbuf, buffer,(size > 1024?1024:size)); 7 //DMA發送數據-要先關 設置發送長度 開啟DMA 8 DMA_Cmd(DMA1_Channel4, DISABLE); 9 DMA1_Channel4->CNDTR = size;// 設置發送長度 10 DMA_Cmd(DMA1_Channel4, ENABLE); // 啟動DMA發送 11 return size; 12 } 13 14 B、中斷處理 15 1)中斷處理相關准備工作 16 typedef enum _UartEvent_ 17 { 18 E_uart_0 = 0,// 沒有事件 19 E_uart_tc=0x40, //發送完成 20 E_uart_idle=0x80, //接收完成 21 }UartEvent; 22 u16 receivelen = 0;// 聲明接收數據長度 23 UartEvent event;//申明一個事件參數 24 25 //清除DMA 緩存,並終止DMA 26 void Uart_Dma_Clr(void) 27 { 28 DMA_Cmd(DMA1_Channel4, DISABLE); 29 DMA1_Channel4->CNDTR=0; 30 DMA_Cmd(DMA1_Channel5, DISABLE); 31 DMA1_Channel5->CNDTR=DMASIZE ; 32 DMA_Cmd(DMA1_Channel5, ENABLE); 33 } 34 // 獲取一個事件,事件分為發送完成事件和接收完成事件,可以根據事件進行進行處理 35 UartEvent Uart_Get_Event(void) 36 { 37 UartEvent e; 38 if(!DMA1_Channel5->CNDTR) Uart_Dma_Clr();// 如果產生一個事件后,接收數據通道已經沒有了緩存空間,進行清除DMA清空 39 return event; 40 } 41 // 清除對應的事件 42 void Uart_Clr_Event(UartEvent event_in) 43 { 44 event&=~event_in; 45 } 46 47 48 2) 中斷處理,當所有數據發送完畢,串口1產生一個發送完成中斷 49 void Uatr1_Back_IRQHandler() 50 { 51 u8 tem; 52 if(USART_GetITStatus(USART1,USART_IT_IDLE)!= RESET) 53 { 54 tem=USART1->SR;//先讀SR,然后讀DR才能清除 55 tem=USART1->DR; 56 tem=tem; 57 Uart_Set_Event(E_uart_idle); 58 receivelen =DMASIZE - DMA1_Channel5->CNDTR;// 總的buf長度減去剩余buf長度,得到接收到數據的長度 59 USART_ClearITPendingBit(USART1, USART_IT_IDLE); 60 } 61 62 **if(USART_GetITStatus(USART1,USART_IT_TC)!= RESET) // 全部數據發送完成,產生該標記** 63 { 64 USART_ClearITPendingBit(USART1, USART_IT_TC); // 清除完成標記 65 DMA_Cmd(DMA1_Channel4, DISABLE); // 關閉DMA 66 DMA1_Channel4->CNDTR=0; // 清除數據長度 67 Uart_Set_Event(E_uart_tc); //設置發送完成事件 68 } 69 }
4、接收數據
根據上圖描述,流程如下:
1、串口接收到數據
2、DMA自動取走數據
3、DMA把數據存到內存receive[1024]中
4、串口接收完畢后會產生一個空閑中斷
根據上面流程,我們接收數據需要做到兩步:
1)串口產生一個空閑中斷后,設置一個接收完成事件
中斷處理:
1 void Uatr1_Back_IRQHandler() 2 { 3 u8 tem; 4 **if(USART_GetITStatus(USART1,USART_IT_IDLE)!= RESET)** 5 { 6 tem=USART1->SR;//先讀SR,然后讀DR才能清除 7 tem=USART1->DR;// 清除DR 8 tem=tem; // 防止編譯器警告 9 Uart_Set_Event(E_uart_idle);// 設置接收完成(空閑)事件 10 receivelen =DMASIZE - DMA1_Channel5->CNDTR;// 總的buf長度減去剩余buf長度,得到接收到數據的長度 11 USART_ClearITPendingBit(USART1, USART_IT_IDLE); // 清除空閑中斷 12 } 13 14 if(USART_GetITStatus(USART1,USART_IT_TC)!= RESET) // 全部數據發送完成,產生該標記 15 { 16 USART_ClearITPendingBit(USART1, USART_IT_TC); // 清除完成標記 17 DMA_Cmd(DMA1_Channel4, DISABLE); // 關閉DMA 18 DMA1_Channel4->CNDTR=0; // 清除數據長度 19 Uart_Set_Event(E_uart_tc); //設置發送完成事件 20 } 21 }
2)接收數據函數檢測事件,如果發現是接收完成事件,取走數據,並且做相關清除操作
1 u8 Uart_Receive_Data(u8*recbuf u16 *revLen) 2 { 3 u8 *str; 4 if( event & E_uart_idle) // 是否產生空閑中斷 5 { 6 str = Uart_Get_Data(revLen); 7 memcpy(recbuf,receivebuf,*revLen); 8 Uart_Clr_Event(E_uart_idle); 9 Uart_Dma_Clr(); 10 return TRUE; 11 } 12 else 13 { 14 revLen = 0; 15 return FALSE; 16 } 17 }
好了,到此DMA已經講完了,有點長!!!
小結:
1、DMA其實就是個自動緩存器,數據來了,緩存到指定位置。發送數據則把緩存數據發送出去。
2、串口空閑中斷,實測在接收完成數據后,空閑閑置時產生的,而發送數據不會產生該中斷。
3、串口發送完成中斷,實測在全部數據發送成功后,才會產生中斷。
上面這些知識是微小見識,歡迎拍磚!