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