STM32F4 串口DMA
串口DMA方式收發(fā)
筆者使用的是STM32F407VET6,共包含6路串口,頁尾處程序已將全部串口的DMA收發(fā)配置完成,本文僅以串口1為例進行講解。(查看代碼可直接跳至第二節(jié)或頁尾處下載)
1 STM32F4 DMA 簡介
DMA,全稱為:Direct Memory Access,即直接存儲器訪問。DMA 傳輸方式無需 CPU 直接控制傳輸,也沒有中斷處理方式那樣保留現(xiàn)場和恢復(fù)現(xiàn)場的過程,通過硬件為 RAM 與 I/O 設(shè)備開辟一條直接傳送數(shù)據(jù)的通路,能使 CPU 的效率大為提高。
STM32F4 最多有 2 個 DMA 控制器(DMA1 和 DMA2),共 16 個數(shù)據(jù)流(每個控制器 8 個),每一個 DMA 控制器都用于管理一個或多個外設(shè)的存儲器訪問請求。每個數(shù)據(jù)流總共可以有多達 8個通道(或稱請求)。每個數(shù)據(jù)流通道都有一個仲裁器,用于處理 DMA 請求間的優(yōu)先級。
它可以處理一下事務(wù):
- 外設(shè)到儲存器的傳輸
- 儲存器到外設(shè)的傳輸
- 儲存器到儲存器的傳輸
注意:DMA1 控制器 AHB 外設(shè)端口與 DMA2 控制器的情況不同,不連接到總線矩陣,因此,僅 DMA2 數(shù)據(jù)流能夠執(zhí)行存儲器到
存儲器的傳輸。
其中,數(shù)據(jù)流的多通道選擇,是通過 DMA_SxCR 寄存器控制的,如圖1所示:
上圖可以看出,DMA_SxCR 控制數(shù)據(jù)流到底使用哪一個通道,每個數(shù)據(jù)流有 8 個通道可供選擇,但每次只能選擇其中一個通道進行 DMA 傳輸,DMA2 的各數(shù)據(jù)流通道映射表,如表 1 所示
表1 DMA2數(shù)據(jù)流映射表
上表就列出了 DMA2 所有可能的選擇情況,來總共 64 種組合,比如本章我們要實現(xiàn)串口1的 DMA 發(fā)送,即USART1_TX,就必須選擇 DMA2 的數(shù)據(jù)流 7,通道 4,來進行 DMA 傳輸。這里注意一下,有的外設(shè)(比如 USART1_RX)可能有多個通道可以選擇,隨意選擇一個就可以。
重要寄存器簡介
(1) DMA 中斷狀態(tài)寄存器
該寄存器總共有 2 個:DMA_LISR 和 DMA_HISR,每個寄存器管理 4 數(shù)據(jù)流(總共 8 個),DMA_LISR 寄存器用于管理數(shù)據(jù)流 0~3,而 DMA_HISR 用于管理數(shù)據(jù)流 4~7。如果開啟了 DMA_LISR 中這些位對應(yīng)的中斷,則在達到條件后就會跳到中斷服務(wù)函數(shù)里面去,即使沒開啟,也可以通過查詢這些位來獲得當前 DMA 傳輸?shù)臓顟B(tài)。這里常用的是 TCIFx位,即數(shù)據(jù)流 x 的 DMA 傳輸完成與否標志。
注意:此寄存器為只讀寄存器,所以在這些位被置位之后,只能通過【中斷標志清除寄存器】來清除。
(2)DMA 中斷標志清除寄存器
該寄存器同樣有 2 個:DMA_LIFCR 和 DMA_HIFCR,同樣是每個寄存器控制 4 個數(shù)據(jù)流。該寄存器為只寫寄存器,其各位就是用來清除 【中斷狀態(tài)寄存器】的對應(yīng)位的,通過寫 1 清除。
(3) DMA 數(shù)據(jù)流 x 配置寄存器(DMA_SxCR)
該寄存器控制著 DMA 的很多相關(guān)信息,包括數(shù)據(jù)寬度、外設(shè)及存儲器的寬度、優(yōu)先級、增量模式、傳輸方向、中斷允許、使能等都是通過該寄存器來設(shè)置的。所以 DMA_ SxCR 是 DMA 傳輸?shù)暮诵目刂萍拇嫫鳌?/p>
(4)DMA 數(shù)據(jù)流 x 數(shù)據(jù)項數(shù)寄存器(DMA_SxNDTR)
這個寄存器控制 DMA 數(shù)據(jù)流 x 的每次傳輸所要傳輸?shù)臄?shù)據(jù)量。其設(shè)置范圍為 0~65535。并且該寄存器的值會隨著傳輸?shù)倪M行而減少,當該寄存器的值為 0 的時候就代表此次數(shù)據(jù)傳輸已經(jīng)全部發(fā)送完成了。所以可以通過這個寄存器的值來知道當前DMA 傳輸?shù)倪M度。
注意:這里是數(shù)據(jù)項數(shù)目,而不是指的字節(jié)數(shù)。比如設(shè)置數(shù)據(jù)位寬為 16 位,那么傳輸一次(一個項)就是 2 個字節(jié)
(5)DMA 數(shù)據(jù)流 x 的外設(shè)地址寄存器(DMA_SxPAR)
該寄存器用來存儲 STM32F4 外設(shè)的地址,比如使用串口 1,那么該寄存器必須寫入 0x40011004(其實就是&USART1_DR)。
(6) DMA 數(shù)據(jù)流 x 的存儲器地址寄存器
由于 STM32F4 的 DMA 支持雙緩存,所以存儲器地址寄存器有兩個:DMA_SxM0AR 和 DMA_SxM1AR,其中 DMA_SxM1AR 僅在雙緩沖模式下,才有效。比如使用 USART1_TX_BUF[USART_LEN] 數(shù)組來做存儲器,那么在DMA_SxM0AR 中寫入 &USART1_TX_BUF 就可以了。
2 收發(fā)配置
2.1串口配置(使能DMA收發(fā))
重點:使能串口1的接收、發(fā)送和串口1的DMA接收、發(fā)送并使能串口1的空閑中斷
/*------------------------------------------------* 函數(shù)名:void Init_USART1(u32 pclk2,u32 bound)* 功 能:初始化IO 串口1* 參 數(shù):pclk2: PCLK2時鐘頻率(Mhz)bound: 波特率 * 返回值: 無------------------------------------------------*/void Init_USART1(u32 pclk2,u32 bound){ float temp;u16 mantissa;u16 fraction; temp=(float)(pclk2*1000000)/(bound*16);//得到USARTDIV@OVER8=0mantissa=temp; //得到整數(shù)部分fraction=(temp-mantissa)*16; //得到小數(shù)部分@OVER8=0 mantissa<<=4;mantissa+=fraction; RCC->AHB1ENR|=1<<0; //使能PORTA口時鐘 RCC->APB2ENR|=1<<4; //使能串口1時鐘 GPIO_Set(GPIOA,PIN9|PIN10,GPIO_MODE_AF,GPIO_OTYPE_PP,GPIO_SPEED_50M,GPIO_PUPD_PU);//PA9,PA10,復(fù)用功能,上拉輸出GPIO_AF_Set(GPIOA,9,7); //PA9,AF7GPIO_AF_Set(GPIOA,10,7);//PA10,AF7 //波特率設(shè)置USART1->BRR=mantissa; //波特率設(shè)置 USART1->CR1&=~(1<<15); //設(shè)置OVER8=0 USART1->CR1|=1<<3; //串口發(fā)送使能 USART1->CR3|=1<<7; //使能串口1的DMA發(fā)送#if EN_USART1_RX //如果使能了接收 USART1->CR1|=1<<2; //串口接收使能USART1->CR3|=1<<6; //使能串口1的DMA接收 USART1->CR1|=1<<4; //使能空閑中斷 MY_NVIC_Init(3,3,USART1_IRQn,2);//組2,最低優(yōu)先級 #endifUSART1->CR1|=1<<13; //串口使能}2.2兩個變量
發(fā)送和接收的數(shù)據(jù)都將以如下兩個變量為指定儲存器。
#define USART_LEN 50 //定義最大接收字節(jié)數(shù) 50u8 USART1_TX_BUF[USART_LEN];u8 USART1_RX_BUF[USART_LEN];2.3 DMA配置
(1)使能DMA2時鐘,并等待數(shù)據(jù)流可配置 。
(2)設(shè)置外設(shè)地址
(3)設(shè)置儲存器地址
(4)設(shè)置傳輸數(shù)據(jù)量
(5)設(shè)置數(shù)據(jù)流7的配置信息
(6)開啟數(shù)據(jù)流7的傳輸完成中斷
/*------------------------------------------------* 函數(shù)名:void MYDMA_Config(DMA_Stream_TypeDef *DMA_Streamx,u8 chx,u32 par,u32 mar,u16 ndtr,u8 dir)* 功 能:配置DMA* 參 數(shù):DMA_Streamx: DMA數(shù)據(jù)流(DMA1_Stream0~7/DMA2_Stream0~7)chx: DMA通道選擇(范圍:0~7)par: 外設(shè)地址mar: 存儲器地址ndtr: 數(shù)據(jù)傳輸量dir: 數(shù)據(jù)傳輸方向(DMA_DIR_PeripheralToMemory / DMA_DIR_MemoryToPeripheral / DMA_DIR_MemoryToMemory)* 返回值: 無------------------------------------------------*/void MYDMA_Config(DMA_Stream_TypeDef *DMA_Streamx,u8 chx,u32 par,u32 mar,u16 ndtr,u8 dir){ DMA_TypeDef *DMAx;u8 streamx;if((u32)DMA_Streamx>(u32)DMA2)//得到當前stream是屬于DMA2還是DMA1{DMAx=DMA2;RCC->AHB1ENR|=1<<22;//DMA2時鐘使能 }else {DMAx=DMA1; RCC->AHB1ENR|=1<<21;//DMA1時鐘使能 }while(DMA_Streamx->CR&0X01);//等待DMA可配置 streamx=(((u32)DMA_Streamx-(u32)DMAx)-0X10)/0X18; //得到stream通道號if(streamx>=6)DMAx->HIFCR|=0X3D<<(6*(streamx-6)+16); //清空之前該stream上的所有中斷標志else if(streamx>=4)DMAx->HIFCR|=0X3D<<6*(streamx-4); //清空之前該stream上的所有中斷標志else if(streamx>=2)DMAx->LIFCR|=0X3D<<(6*(streamx-2)+16);//清空之前該stream上的所有中斷標志else DMAx->LIFCR|=0X3D<<6*streamx; //清空之前該stream上的所有中斷標志DMA_Streamx->PAR=par; //DMA外設(shè)地址DMA_Streamx->M0AR=mar; //DMA存儲器0地址DMA_Streamx->NDTR=ndtr; //n個數(shù)據(jù)項DMA_Streamx->CR=0; //先全部復(fù)位CR寄存器值 switch(dir){case DMA_DIR_PeripheralToMemory: //外設(shè)到存儲器模式DMA_Streamx->CR&=~(1<<6);DMA_Streamx->CR&=~(1<<7);break;case DMA_DIR_MemoryToPeripheral: DMA_Streamx->CR|=1<<6;DMA_Streamx->CR&=~(1<<7);break;case DMA_DIR_MemoryToMemory: DMA_Streamx->CR&=~(1<<6);DMA_Streamx->CR|=1<<7;break;default:break;}DMA_Streamx->CR|=0<<8; //非循環(huán)模式(即使用普通模式)DMA_Streamx->CR|=0<<9; //外設(shè)非增量模式DMA_Streamx->CR|=1<<10; //存儲器增量模式DMA_Streamx->CR|=0<<11; //外設(shè)數(shù)據(jù)長度:8位DMA_Streamx->CR|=0<<13; //存儲器數(shù)據(jù)長度:8位DMA_Streamx->CR|=1<<16; //中等優(yōu)先級DMA_Streamx->CR|=0<<21; //外設(shè)突發(fā)單次傳輸DMA_Streamx->CR|=0<<23; //存儲器突發(fā)單次傳輸DMA_Streamx->CR|=(u32)chx<<25;//通道選擇//DMA_Streamx->FCR=0X21; //FIFO控制寄存器DMA2_Stream7->CR|=1<<4; //使能傳輸完成中斷MY_NVIC_Init(2,1,DMA2_Stream7_IRQn,2);}2.4 設(shè)置MDA狀態(tài)標志
注意:如果連續(xù)運行兩個發(fā)送函數(shù),如下,則可能在第一個還未發(fā)送完成時就會直接執(zhí)行第二次發(fā)送。
myDMAprintf(USART1,"usart = %d\tch = %f\r\n",1,1.567);myDMAprintf(USART1,"usart = %d\tch = %f\r\n",1,1.567);其運行效果如圖1所示,第一次僅發(fā)送了"us"即被第二次發(fā)送覆蓋了。
運行效果
圖1 運行效果
故需設(shè)置相應(yīng)的標志位,對每次發(fā)送的狀態(tài)進行標記,若正在進行傳輸,則等待,實現(xiàn)如下:
typedef enum {BUSY,IDLE }DMA_Flag;volatile DMA_Flag DMA2_Stream7_Flag = IDLE; //USART12.5 DMA中斷函數(shù)
每次傳輸完成(串口發(fā)送完成)后,都會觸發(fā)一次中斷,此時只需在中斷函數(shù)中清除相應(yīng)標志位并對發(fā)送狀態(tài)進行標記即可。//對應(yīng)USART1發(fā)送 void DMA2_Stream7_IRQHandler(void) {if((DMA2->HISR&(1<<27))){DMA2->HIFCR|=1<<27;DMA2_Stream7_Flag = IDLE;} }2.6 DMA初始化
查詢手冊可知,串口1發(fā)送為DMA2的數(shù)據(jù)流7,通道4,并為內(nèi)存到外設(shè)模式,而串口1接收為DMA2的數(shù)據(jù)流5,通道4,并為外設(shè)到內(nèi)存模式。注意:此處需提前開啟一次DMA接收,否則第一次接收會產(chǎn)生錯誤數(shù)據(jù)。
//USART1發(fā)送 --- DMA2,數(shù)據(jù)流7,CH4---USART1_TXD 外設(shè)為串口1,存儲器為USART1_TX_BUF,長度為:USART_LEN MYDMA_Config(DMA2_Stream7,4,(u32)&USART1->DR,(u32)USART1_TX_BUF,USART_LEN,DMA_DIR_MemoryToPeripheral); //USART1接收 --- DMA2,數(shù)據(jù)流5,CH4---USART1_RXD 外設(shè)為串口1,存儲器為USART1_RX_BUF,長度為:USART_LEN MYDMA_Config(DMA2_Stream5,4,(u32)&USART1->DR,(u32)USART1_RX_BUF,USART_LEN,DMA_DIR_PeripheralToMemory); MYDMA_Enable(DMA2_Stream5,(u32)USART1_RX_BUF,USART_LEN);//開始一次DMA傳輸!2.6 開啟一次串口DMA傳輸
配置DMA數(shù)據(jù)流、內(nèi)存地址及傳輸量。
void MYDMA_Enable(DMA_Stream_TypeDef *DMA_Streamx, u32 mar, u16 ndtr) {DMA_Streamx->CR&=~(1<<0); //關(guān)閉DMA傳輸 while(DMA_Streamx->CR&0X1); //確保DMA可以被設(shè)置 DMA_Streamx->M0AR=mar; //DMA存儲器地址DMA_Streamx->NDTR=ndtr; //DMA傳輸數(shù)據(jù)量 DMA_Streamx->CR|=1<<0; //開啟DMA傳輸 }2.7 格式化發(fā)送
該部分已封裝為類似printf()的發(fā)送函數(shù),大致為3個部分:
(1)格式化數(shù)據(jù)為字符串;
(2)判斷發(fā)送狀態(tài),若“忙”,則等待。
(3)設(shè)置好儲存器地址,使能發(fā)送并設(shè)置發(fā)送狀態(tài);
/*------------------------------------------------ * 函數(shù)名:Status myDMAprintf(USART_TypeDef *USARTx, const char *format, ...) * 功 能:仿 printf 函數(shù) * 參 數(shù):*USARTx: 串口號*pString: 打印內(nèi)容... : 變量 * 返回值: 狀態(tài) ------------------------------------------------*/ Status myDMAprintf(USART_TypeDef *USARTx, const char *format, ...) {va_list args; u16 len;if(format == NULL)return 1;va_start(args, format);if(USARTx == USART1){len = vsnprintf((char *)USART1_TX_BUF, USART_LEN, format, args);while(DMA2_Stream7_Flag != IDLE);MYDMA_Enable(DMA2_Stream7,(u32)USART1_TX_BUF,len);DMA2_Stream7_Flag = BUSY;}else if(USARTx == USART2){len = vsnprintf((char *)USART2_TX_BUF, USART_LEN, format, args);while(DMA1_Stream6_Flag != IDLE);MYDMA_Enable(DMA1_Stream6,(u32)USART2_TX_BUF,len);DMA1_Stream6_Flag = BUSY;} else if(USARTx == USART3){len = vsnprintf((char *)USART3_TX_BUF, USART_LEN, format, args);while(DMA1_Stream3_Flag != IDLE);MYDMA_Enable(DMA1_Stream3,(u32)USART3_TX_BUF,len);DMA1_Stream3_Flag = BUSY;} else if(USARTx == UART4){len = vsnprintf((char *)UART4_TX_BUF, USART_LEN, format, args);while(DMA1_Stream4_Flag != IDLE);MYDMA_Enable(DMA1_Stream4,(u32)UART4_TX_BUF,len);DMA1_Stream4_Flag = BUSY;} else if(USARTx == UART5){len = vsnprintf((char *)UART5_TX_BUF, USART_LEN, format, args);while(DMA1_Stream7_Flag != IDLE);MYDMA_Enable(DMA1_Stream7,(u32)UART5_TX_BUF,len);DMA1_Stream7_Flag = BUSY;} else if(USARTx == USART6){len = vsnprintf((char *)USART6_TX_BUF, USART_LEN, format, args);while(DMA2_Stream6_Flag != IDLE);MYDMA_Enable(DMA2_Stream6,(u32)USART6_TX_BUF,len);DMA2_Stream6_Flag = BUSY;}elsereturn 2;va_end(args);return 0; }2.8串口DMA接收
當串口進入空閑狀態(tài)時即開啟一次DMA接收,下次數(shù)據(jù)到來時DMA會自動搬運數(shù)據(jù)至指定的儲存器(此處為USART1_TX_BUF),搬運完成后會再次觸發(fā)空閑中斷,此時清除空閑中斷標志位、DMA傳輸完成標志位和傳輸錯誤標志位,并清除接收內(nèi)存,開啟下一次接收。void USART1_IRQHandler(void) {u8 temp;u16 len;if(USART1->SR&(1<<4))//檢測到線路空閑{ //軟件序列清除IDLE標志位temp = USART1->SR;temp = USART1->DR;DMA2_Stream5->CR &=~(1<<0); //關(guān)閉DMA傳輸,準備重新配置DMA2->HIFCR|=1<<11; //清除DMA2_Steam5傳輸完成標志DMA2->HIFCR|=1<<9; //清除DMA2_Steam5傳輸錯誤標志len = USART_LEN - (uint16_t)(DMA2_Stream5->NDTR);myDMAprintf(USART1,"len = %d,data: %s",len,USART1_RX_BUF);}mymemset(USART1_RX_BUF,0,(u32)len);MYDMA_Enable(DMA2_Stream5,(u32)USART1_RX_BUF,USART_LEN);//開始一次DMA傳輸! }至此,串口1的發(fā)送和接收已全部配置完成,其他5個串口的配置類似。
程序鏈接
完整的6路串口DMA發(fā)送&接收程序:串口DMA發(fā)送&接收(v2.1)
參考文章
STM32—無需中斷來實現(xiàn)使用DMA接收串口數(shù)據(jù)
串口1配合DMA接收不定長數(shù)據(jù)(空閑中斷+DMA接收)
STM32 串口采用DMA方式收發(fā)
原文鏈接:https://blog.csdn.net/jack__linux/article/details/86486838
總結(jié)
以上是生活随笔為你收集整理的STM32F4 串口DMA的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: EndNoteX9 使用笔记
- 下一篇: kindle刷机ttl_亚马逊卡大树ki