作者:良知猶存
轉載授權以及圍觀->歡迎添加WxId:Allen-Iverson-me-LYN
DMA,全稱Direct Memory Access,即直接存儲器訪問。DMA傳輸將數據從一個地址空間複製到另一個地址空間,提供在外設和存儲器之間或者存儲器和存儲器之間的高速數據傳輸。當CPU初始化這個傳輸動作,傳輸動作本身是由DMA控制器來實現和完成的。DMA傳輸方式無需CPU直接控制傳輸,也沒有中斷處理方式那樣保留現場和恢復現場過程,通過硬件爲RAM和IO設備開闢一條直接傳輸數據的通道,使得CPU的效率大大提高。
主要特點:
•DMA上多達7個可獨立配置的通道(請求)
•每個通道都連接到專用的硬件DMA請求,軟件觸發爲
每個頻道也都支持。該配置由軟件完成。
•DMA通道的請求之間的優先級是軟件可編程的(4
級別由非常高,高,中,低)或硬件組成(在相等的情況下)
(請求1的優先級高於請求2的優先級,依此類推)
•獨立的源和目標傳輸大小(字節,半字,字),模擬
包裝和拆箱。源/目標地址必須與數據對齊
尺寸。
•支持循環緩衝區管理
•3個事件標誌(DMA半傳輸,DMA傳輸完成和DMA傳輸錯誤)
在每個通道的單箇中斷請求中進行邏輯或運算
•內存到內存的傳輸
•外圍到內存和內存到外圍,以及外圍到外圍
轉移
•訪問閃存,SRAM,APB和AHB外設作爲源和目標
•可編程的數據傳輸數量:最多65535
DMA通道對應的外設情況(F0系列):
很多博文會講解裏面的詳細定義,對於每一處寄存器的詳細操作指導,所以我就不會去多寫了。
我只想表達,DMA是一種可以快速相關外設數據交互的一種方法,我們學習哪裏用它,怎麼用它,至於細節學習,大家去網上所搜DMA相信息即可。
之前講過DMA的數據發送,現在補充上DMA的接收數據部分。
利用DMA接收串口數據的配置,大致分爲:1.初始化串口並開啓DMAR接收功能,配置DMA的外設到內存的數據接收功能,2.等待串口中斷提示,並進行處理數據,3.清空DMA,重新等待數據
01 配置串口與DMA
其中USART2->CR3寄存器的第6 bit用來設置DMA的接收配置,此處比較重要我們設置爲USART_CR3_DMAR。 USART2->CR1寄存器中開啓幀信息接收完成之後的中斷,USART_CR1_IDLEIE,這處可以幫助我們節省CPU的不必要開支,開啓此處中斷類型,我們只需要在每幀信息接收完成之後,usart才觸發中斷,我們再解析。其餘爲正常的usart配置。
GPIOD->MODER |= GPIO_MODER_MODER5_1| //USART2_TX
GPIO_MODER_MODER6_1; //USART2_RX
/* set baudrate */
USART2->BRR = USART_Baudrate;//115200
USART2->CR3 |= USART_CR3_DMAT|USART_CR3_DMAR;//USART_CR3_OVRDIS;// 不需要覆寫
/*enable usart2 and enable tx*/
USART2->CR1 |= USART_CR1_UE | USART_CR1_TE | USART_CR1_RE | USART_CR1_IDLEIE;
配置DMA:
首先根據上方的映射表,我們初始化了usart2,而usart2 RX對應的DMA通道爲DMA1 channel5,所以我們進行DMA1 ch5通道配置。
第一個爲DMA1_Channel5->CPAR寄存器,在下表可知,用來設置對應接收數據的外設的地址,而USART的接收數據的寄存器爲RDR,所以寄存器配置爲DMA1_Channel5->CPAR = (uint32_t)&(USART2->RDR);
設置需要放數據的指定內存位置,根據指導手冊可知CMAR爲DMA通道用來放置數據的內存地址,所以此處設置爲我們定義好的變量的地址。
設置單次傳送數據量的大小,此處最大可設置爲65535byte的數據大小。
最後開啓DMA通道。
void USART2_DMA_Recive(uint8_t *p_data,uint16_t length)
{
DMA1_Channel5->CPAR = (uint32_t)&(USART2->RDR); //Peripheral address
DMA1_Channel5->CMAR = (uint32_t)p_data; //memory address
DMA1_Channel5->CNDTR = length; //Set the length
DMA1_Channel5->CCR |= DMA_CCR_MINC | DMA_CCR_EN;
}
02 等待串口中斷,處理數據
此時在debug中,在我們定義好的DMA內存變量,地址在RAM初始完成,後續串口數據接收的時候,DMA會直接將數據置於p_data所對應的內存。
然後在中斷服務函數我們可以將我們數據進行處理,Uart_Channel_isr[1](USART2->RDR);,這個函數爲我自己寫的處理函數,中斷裏面函數都是數據複製,所以我直接在中斷執行,一般大家會在中斷寫個標誌,在主循環進行解析。但是DMA接收配合USART_ISR_IDLE標誌在STM32平臺下並不友好,如果主循環用來解析數據的時候,空閒中斷還沒有產生,本幀數據還尚未接收完成,但是由於內存數據已經在實時寫入,DMA1_Channel5->CNDTR已經有所變化,並早於空閒中斷產生,所以主循環就不能用DMA1_Channel5->CNDTR所接收數據長度進行解析了。一般建議,如果解析數據只是單純的挪移,此時候直接在中斷處理即可,對主程序並沒有大的阻塞。
void USART2_IRQHandler(void)
{
if ((USART2->ISR & USART_ISR_ORE) == USART_ISR_ORE)
{
USART2->ICR |= USART_ICR_ORECF;
}
if ((USART2->ISR & USART_ISR_IDLE) == USART_ISR_IDLE) //The new frame data receive
{
USART2->ICR |= USART_ICR_IDLECF;
Uart_Channel_isr[1](USART2->RDR);/*讀取解析數據*/
}
}
03 恢復DMA,清空CNDTR,等待下次數據到來
處理完數據之後,及時將DMA標誌以及CNDTR清空,否則CNDTR一直不清空,會導致下次接收數據的時候,造成通道的佔用,數據使用過之後,就清理掉CNDTR,這樣保證每次接收數據的通道有足夠的位置。
static void Usart2_Dma_Reload(uint16_t length)/*清空數據*/
{
DMA1_Channel5->CCR &= ~(DMA_CCR_EN); //disable the dma
DMA1_Channel5->CNDTR = length; //Set the length
DMA1_Channel5->CCR |= DMA_CCR_EN; //enable the dma
}
————————END————————
如果大家覺得有用,可以關注我,有更多的文章。