串口DMA
DMA利用好無疑會讓串口使用起來更加高效,同時CPU還能處理自己的事情,但是DMA的使用卻讓代碼變得更加的復雜,也使串口配置變得更加麻煩!麻煩???也許只是學習的方式不對,代碼是變長了,但是思路清晰的話,也就是在原來串口加了點東西而已。本文讓你輕輕松松學會串口DMA!定長數據傳輸與不定長數據傳輸都教會你!通過此文,希望能讓更多人了解和會使用串口DMA,希望大家多多支持! 一、串口DMA的配置 本文針對串口2(USART2)如何進行DMA傳輸進行講解,如果采用其他串口,注意要修改相應端口配置以及DMA通道。 1、串口的DMA請求映像 通過我之前的博文《 STM32 | DMA配置和使用如此簡單(超詳細)》可以知道,串口2(USART2)的接收(USART2_RX)和發送(USART2_TX)分別在DMA1控制器的通道6和通道7。下圖為DMA1控制器的請求映像圖。 
2、資源說明 STM32F1中,USART2使用的是PA2(USART2_TX)和PA3(USART2_RX),為了方便閱讀,使用到的資源列在下表。 外設 GPIO口 DMA請求映像通道 備注 USART2_TX PA2 DMA1通道7 RAM->USART2的數據傳輸 USART2_RX PA3 DMA1通道6 USART2->RAM的數據傳 3、DMA初始化配置 USART2的DMA配置在《 STM32 | DMA配置和使用如此簡單(超詳細)》中分別講了庫函數版和寄存器版兩種配置,我這里直接搬用,不理解的可以看看《 STM32 | DMA配置和使用如此簡單(超詳細)》這篇文章。要注意的是庫函數最大優勢就是便于閱讀,所以在DMA初始化配置中我們使用庫函數。 首先,我們要先定義三個緩沖區(作全局定義),一個發送緩沖區,兩個接收緩沖區,兩個接收緩沖區是為了做雙緩沖區,目的是為了防止后一次傳輸的數據覆蓋前一次傳輸的數據,并且留出足夠的時間讓CPU處理緩沖區數據。雙緩沖在串口DMA中有著很重要的意義并起著很大的作用! - //USART2_MAX_TX_LEN和USART2_MAX_RX_LEN在頭文件進行了宏定義,分別指USART2最大發送長度和最大接收長度
- u8 USART2_TX_BUF[USART2_MAX_TX_LEN]; //發送緩沖,最大USART2_MAX_TX_LEN字節
- u8 u1rxbuf[USART2_MAX_RX_LEN]; //發送數據緩沖區1
- u8 u2rxbuf[USART2_MAX_RX_LEN]; //發送數據緩沖區2
- u8 witchbuf=0; //標記當前使用的是哪個緩沖區,0:使用u1rxbuf;1:使用u2rxbuf
- u8 USART2_TX_FLAG=0; //USART2發送標志,啟動發送時置1
- u8 USART2_RX_FLAG=0; //USART2接收標志,啟動接收時置
復制代碼 要說明的是,實際上發送緩沖區可能用不上,因為我們要發送的數據內容和大小都不一定每次都是固定的,可以是變化的,我們只需重新指派新的發送緩沖區地址即可,但是在初始化中我們還是要指定一個發送緩沖區地址。
下面是DMA1_USART2的初始化函數
- void DMA1_USART2_Init(void)
- {
- DMA_InitTypeDef DMA1_Init;
- NVIC_InitTypeDef NVIC_InitStructure;
-
- RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE); //使能DMA1時鐘
- //DMA_USART2_RX USART2->RAM的數據傳輸
- DMA_DeInit(DMA1_Channel6); //將DMA的通道6寄存器重設為缺省值
- DMA1_Init.DMA_PeripheralBaseAddr = (u32)(&USART2->DR); //啟動傳輸前裝入實際RAM地址
- DMA1_Init.DMA_MemoryBaseAddr = (u32)u1rxbuf; //設置接收緩沖區首地址
- DMA1_Init.DMA_DIR = DMA_DIR_PeripheralSRC; //數據傳輸方向,從外設讀取到內存
- DMA1_Init.DMA_BufferSize = USART2_MAX_RX_LEN; //DMA通道的DMA緩存的大小
- DMA1_Init.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外設地址寄存器不變
- DMA1_Init.DMA_MemoryInc = DMA_MemoryInc_Enable; //內存地址寄存器遞增
- DMA1_Init.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //數據寬度為8位
- DMA1_Init.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //數據寬度為8位
- DMA1_Init.DMA_Mode = DMA_Mode_Normal; //工作在正常模式
- DMA1_Init.DMA_Priority = DMA_Priority_High; //DMA通道 x擁有高優先級
- DMA1_Init.DMA_M2M = DMA_M2M_Disable; //DMA通道x沒有設置為內存到內存傳輸
-
- DMA_Init(DMA1_Channel6,&DMA1_Init); //對DMA通道6進行初始化
-
- //DMA_USART2_TX RAM->USART2的數據傳輸
- DMA_DeInit(DMA1_Channel7); //將DMA的通道7寄存器重設為缺省值
- DMA1_Init.DMA_PeripheralBaseAddr = (u32)(&USART2->DR); //啟動傳輸前裝入實際RAM地址
- DMA1_Init.DMA_MemoryBaseAddr = (u32)USART2_TX_BUF; //設置發送緩沖區首地址
- DMA1_Init.DMA_DIR = DMA_DIR_PeripheralDST; //數據傳輸方向,從內存發送到外設
- DMA1_Init.DMA_BufferSize = USART2_MAX_TX_LEN; //DMA通道的DMA緩存的大小
- DMA1_Init.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外設地址寄存器不變
- DMA1_Init.DMA_MemoryInc = DMA_MemoryInc_Enable; //內存地址寄存器遞增
- DMA1_Init.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //數據寬度為8位
- DMA1_Init.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //數據寬度為8位
- DMA1_Init.DMA_Mode = DMA_Mode_Normal; //工作在正常模式
- DMA1_Init.DMA_Priority = DMA_Priority_High; //DMA通道 x擁有高優先級
- DMA1_Init.DMA_M2M = DMA_M2M_Disable; //DMA通道x沒有設置為內存到內存傳輸
- DMA_Init(DMA1_Channel7,&DMA1_Init); //對DMA通道7進行初始化
-
- //DMA1通道6 NVIC 配置
- NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel6_IRQn; //NVIC通道設置
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3 ; //搶占優先級
- NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //子優先級
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
- NVIC_Init(&NVIC_InitStructure); //根據指定的參數初始化NVIC寄存器
-
- //DMA1通道7 NVIC 配置
- NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel7_IRQn; //NVIC通道設置
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3 ; //搶占優先級
- NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //子優先級
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
- NVIC_Init(&NVIC_InitStructure); //根據指定的參數初始化NVIC寄存器
- DMA_ITConfig(DMA1_Channel6,DMA_IT_TC,ENABLE); //開USART2 Rx DMA中斷
- DMA_ITConfig(DMA1_Channel7,DMA_IT_TC,ENABLE); //開USART2 Tx DMA中斷
- DMA_Cmd(DMA1_Channel6,ENABLE); //使DMA通道6停止工作
- DMA_Cmd(DMA1_Channel7,DISABLE); //使DMA通道7停止工作
-
- USART_DMACmd(USART2, USART_DMAReq_Tx, ENABLE); //開啟串口DMA發送
- USART_DMACmd(USART2, USART_DMAReq_Rx, ENABLE); //開啟串口DMA接收
- }
復制代碼 相應配置的講解在《 STM32 | DMA配置和使用如此簡單(超詳細)》中講解過,這里不再敘述,要注意的是,我們采用中斷方式進行DMA傳輸。正常情況下,我們傳輸完數據要進行相應的處理,那我們怎么知道什么時候數據傳輸完成了呢?這時候就借助DMA傳輸完成中斷。既然打開了中斷,那一定要注意中斷優先級,這里特別指出串口的中斷優先級應低于串口DMA通道的中斷優先級。
4、串口配置 前面已經完成了DMA的配置,而DMA是不能單獨使用的,所以不要忘了配置要用到的串口USART2。 - void Initial_UART2(unsigned long baudrate)
- {
- //GPIO端口設置
- GPIO_InitTypeDef GPIO_InitStructure;
- USART_InitTypeDef USART_InitStructure;
- NVIC_InitTypeDef NVIC_InitStructure;
-
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2 | RCC_APB2Periph_GPIOA, ENABLE); //使能USART2,GPIOA時鐘
-
- //USART2_TX GPIOA.2初始化
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; //PA.2
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //復用推挽輸出
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //GPIO速率50MHz
- GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化GPIOA.2
-
- //USART2_RX GPIOA.3初始化
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; //PA.3
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空輸入
- GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化GPIOA.3
-
- //USART 初始化設置
- USART_InitStructure.USART_BaudRate = baudrate; //串口波特率
- USART_InitStructure.USART_WordLength = USART_WordLength_8b; //字長為8位數據格式
- USART_InitStructure.USART_StopBits = USART_StopBits_1; //一個停止位
- USART_InitStructure.USART_Parity = USART_Parity_No ; //無奇偶校驗位
- USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //無硬件數據流控制
- USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收發模式
- USART_Init(USART2, &USART_InitStructure); //初始化串口2
-
- //中斷開啟設置
- USART_ITConfig(USART2, USART_IT_IDLE, ENABLE); //開啟檢測串口空閑狀態中斷
- USART_ClearFlag(USART2,USART_FLAG_TC); //清除USART2標志位
-
- USART_Cmd(USART2, ENABLE); //使能串口2
-
- NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn; //NVIC通道設置
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 8; //搶占優先級
- NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //響應優先級
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
- NVIC_Init(&NVIC_InitStructure); //根據指定的參數初始化NVIC寄存器
-
- DMA1_USART2_Init(); //DMA1_USART2初始化
- }
復制代碼
可以注意到,我這里定義的串口中斷搶占優先級低于DMA中斷的搶占優先級。細心點可以看到,我開啟了串口空閑中斷。為什么呢?因為通常情況下我們是不知道接收數據的長度的,這樣我們是沒有辦法利用DMA傳輸完成標志位來判斷是否完成接收,所以我們這里采用串口空閑中斷來判斷數據是否接收完成,接收完成了會進入串口空閑中斷。串口配置的最后進行了串口2(USART2)DMA的初始化,這樣直接初始化串口也直接包括了串口DMA的初始化,主函數初始化只需一步即可。 二、串口DMA的使用 前面我們已經完成了串口的配置及DMA的配置,接下來講講怎么使用。
- 發送數據:沒什么特殊的,我們只需指定DMA外設緩沖區首地址以及數據長度(即設置發送數據的地址)并開啟DMA1通道7即可。
- 接收數據:接收串口來數據有兩種,一種是不定長數據(也就是每次接收到數據的長度都不一樣),另一種是定長數據(接收到的數據每次都相同)。對于定長數據我們只需檢測DMA傳輸完成標志位即可;而對于不定長數據,可以通過串口的空閑中斷來判斷數據是否接收完成。所以,接收完不定長的數據后,需要重新賦值計數值,這樣才可以進行下一次數據傳輸。
詳細的使用下文進行介紹。 1、發送數據 發送數據上有兩種形式,一種是以數組的形式發送,此情況下要知道數組有效元素的個數;另一種就是類似“printf”的形式,此形式可以基于第一種情況稍作修改。 (1)數組的形式發送數據 使用串口DMA發送數據,默認情況下我們要關閉DMA1通道7(即串口2的DMA發送通道),因為一旦開啟通道7就會開始發送數據,沒有要發送數據時自然是要關閉的。 發送數據步驟如下:
- 判斷上一次發送數據是否完成,未完成就等待數據發送完成。有兩種方法,一種是直接判斷DMA傳輸完成標志位,另一種判斷我們自己定義全局變量USART2_TX_FLAG是否置1(即上一次發送未完成)。我更喜歡使用第二種方法。
- 指定發送緩沖區首地址,也就是待發送數據的地址。
- 指定待發送數據長度。
- 開啟DMA通道7啟動DMA發送。
- 產生DMA通道7傳輸完成中斷,清除中斷標志位。
- 關閉DMA通道7,并清除USART2_TX_FLAG(清0)。
接下來給出串口DMA發送數據的代碼 - //DMA 發送應用源碼
- void DMA_USART2_Tx_Data(u8 *buffer, u32 size)
- {
- while(USART2_TX_FLAG); //等待上一次發送完成(USART2_TX_FLAG為1即還在發送數據)
- USART2_TX_FLAG=1; //USART2發送標志(啟動發送)
- DMA1_Channel7->CMAR = (uint32_t)buffer; //設置要發送的數據地址
- DMA1_Channel7->CNDTR = size; //設置要發送的字節數目
- DMA_Cmd(DMA1_Channel7, ENABLE); //開始DMA發送
- }
- //DMA1通道7中斷
- void DMA1_Channel7_IRQHandler(void)
- {
- if(DMA_GetITStatus(DMA1_IT_TC7)!= RESET) //DMA接收完成標志
- {
- DMA_ClearITPendingBit(DMA1_IT_TC7); //清除中斷標志
- USART_ClearFlag(USART2,USART_FLAG_TC); //清除串口2的標志位
- DMA_Cmd(DMA1_Channel7, DISABLE ); //關閉USART2 TX DMA1 所指示的通道
- USART2_TX_FLAG=0; //USART2發送標志(關閉)
- }
- }
復制代碼
至此,串口DMA發送數據完成。細心點可以發現我這里居然直接配置寄存器,因為這樣簡單快捷,不用像庫函數那樣重新初始化DMA的配置。寄存器使用請查看《 STM32 | DMA配置和使用如此簡單(超詳細)》的寄存器那塊的內容。 (2)類似printf形式發送數據 上述學習,使用DMA_USART2_Tx_Data();函數可以很方便的發送一個數組的數據,但是有時候我們還是喜歡使用printf();,今天講串口DMA,當然要講到位,讓串口DMA也能實現強大的printf();功能。 —————————————————————。。。未完。。。———————————————————— 論壇看起來太不舒服了,有的格式沒辦法設置,而且代碼對其很麻煩,直接復制過來卻不對齊,反正我是看著不舒服的,如果喜歡還是去CSDN博客看吧! 原文已在CSDN發布,博文閱讀起來更舒服,CSDN博文鏈接: STM32 | 串口DMA很難?其實就是如此簡單。ǔ敿、附代碼) (https://blog.csdn.net/weixin_44524484/article/details/106029682
以下為原文目錄,一步一步教會你串口DMA!
|