這篇文章主要介紹“STM32中串口DMA的簡(jiǎn)介及功能用法”,在日常操作中,相信很多人在STM32中串口DMA的簡(jiǎn)介及功能用法問題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”STM32中串口DMA的簡(jiǎn)介及功能用法”的疑惑有所幫助!接下來,請(qǐng)跟著小編一起來學(xué)習(xí)吧!
創(chuàng)新互聯(lián)網(wǎng)站建設(shè)公司,提供成都做網(wǎng)站、網(wǎng)站建設(shè),網(wǎng)頁設(shè)計(jì),建網(wǎng)站,PHP網(wǎng)站建設(shè)等專業(yè)做網(wǎng)站服務(wù);可快速的進(jìn)行網(wǎng)站開發(fā)網(wǎng)頁制作和功能擴(kuò)展;專業(yè)做搜索引擎喜愛的網(wǎng)站,是專業(yè)的做網(wǎng)站團(tuán)隊(duì),希望更多企業(yè)前來合作!
在使用stm32或者其他單片機(jī)的時(shí)候,會(huì)經(jīng)常使用到串口通訊,那么如何有效地接收數(shù)據(jù)呢?假如這段數(shù)據(jù)是不定長(zhǎng)的有如何高效接收呢?
同學(xué)A:數(shù)據(jù)來了就會(huì)進(jìn)入串口中斷,在中斷中讀取數(shù)據(jù)就行了!
中斷就是打斷程序正常運(yùn)行,怎么能保證高效呢?經(jīng)常把主程序打斷,主程序還要不要運(yùn)行了?
同學(xué)B:串口可以配置成用DMA的方式接收數(shù)據(jù),等接收完畢就可以去讀取了!
這個(gè)同學(xué)是對(duì)的,我們可以使用DMA去接收數(shù)據(jù),不過DMA需要定長(zhǎng)才能產(chǎn)生接收中斷,如何接收不定長(zhǎng)的數(shù)據(jù)呢?
題外話:其實(shí),上面的問題是很有必要思考一下的,不斷思考,才能進(jìn)步。
DMA:全稱Direct Memory Access,即直接存儲(chǔ)器訪問
DMA 傳輸將數(shù)據(jù)從一個(gè)地址空間復(fù)制到另外一個(gè)地址空間。CPU只需初始化DMA即可,傳輸動(dòng)作本身是由 DMA 控制器來實(shí)現(xiàn)和完成。典型的例子就是移動(dòng)一個(gè)外部?jī)?nèi)存的區(qū)塊到芯片內(nèi)部更快的內(nèi)存區(qū)。這樣的操作并沒有讓處理器參與處理,CPU可以干其他事情,當(dāng)DMA傳輸完成的時(shí)候產(chǎn)生一個(gè)中斷,告訴CPU我已經(jīng)完成了,然后CPU知道了就可以去處理數(shù)據(jù)了,這樣子提高了CPU的利用率,因?yàn)镃PU是大腦,主要做數(shù)據(jù)運(yùn)算的工作,而不是去搬運(yùn)數(shù)據(jù)。DMA 傳輸對(duì)于高效能嵌入式系統(tǒng)算法和網(wǎng)絡(luò)是很重要的。
STM32F1系列的MCU有兩個(gè)DMA控制器(DMA2只存在于大容量產(chǎn)品中),DMA1有7個(gè)通道,DMA2有5個(gè)通道,每個(gè)通道專門用來管理來自于一個(gè)或者多個(gè)外設(shè)對(duì)存儲(chǔ)器的訪問請(qǐng)求。還有一個(gè)仲裁器來協(xié)調(diào)各個(gè)DMA請(qǐng)求的優(yōu)先權(quán)。
而STM32F4/F7/H7系列的MCU有兩個(gè)DMA控制器總共有16個(gè)數(shù)據(jù)流(每個(gè)DMA控制器8個(gè)),每一個(gè)DMA控制器都用于管理一個(gè)或多個(gè)外設(shè)的存儲(chǔ)器訪問請(qǐng)求。每個(gè)數(shù)據(jù)流總共可以有多達(dá)8個(gè)通道(或稱請(qǐng)求)。每個(gè)通道都有一個(gè)仲裁器,用于處理 DMA 請(qǐng)求間的優(yōu)先級(jí)。
DMA在接收數(shù)據(jù)的時(shí)候,串口接收DMA在初始化的時(shí)候就處于開啟狀態(tài),一直等待數(shù)據(jù)的到來,在軟件上無需做任何事情,只要在初始化配置的時(shí)候設(shè)置好配置就可以了。等到接收到數(shù)據(jù)的時(shí)候,告訴CPU去處理即可。
那么問題來了,怎么知道數(shù)據(jù)是否接收完成呢?
其實(shí),有很多方法:
對(duì)于定長(zhǎng)的數(shù)據(jù),只需要判斷一下數(shù)據(jù)的接收個(gè)數(shù),就知道是否接收完成,這個(gè)很簡(jiǎn)單,暫不討論。
對(duì)于不定長(zhǎng)的數(shù)據(jù),其實(shí)也有好幾種方法,麻煩的我肯定不會(huì)介紹,有興趣做復(fù)雜工作的同學(xué)可以在網(wǎng)上看看別人怎么做,下面這種方法是最簡(jiǎn)單的,充分利用了stm32的串口資源,效率也是非常之高。
DMA+串口空閑中斷
這兩個(gè)資源配合,簡(jiǎn)直就是天衣無縫啊,無論接收什么不定長(zhǎng)的數(shù)據(jù),管你數(shù)據(jù)有多少,來一個(gè)我就收一個(gè),就像廣東人吃“山竹”,來一個(gè)吃一個(gè)~(最近風(fēng)好大,我好怕)。
可能很多人在學(xué)習(xí)stm32的時(shí)候,都不知道idle是啥東西,先看看stm32串口的狀態(tài)寄存器:
當(dāng)我們檢測(cè)到觸發(fā)了串口總線空閑中斷的時(shí)候,我們就知道這一波數(shù)據(jù)傳輸完成了,然后我們就能得到這些數(shù)據(jù),去進(jìn)行處理即可。這種方法是最簡(jiǎn)單的,根本不需要我們做多的處理,只需要配置好,串口就等著數(shù)據(jù)的到來,dma也是處于工作狀態(tài)的,來一個(gè)數(shù)據(jù)就自動(dòng)搬運(yùn)一個(gè)數(shù)據(jù)。
串口接收完數(shù)據(jù)是要處理的,那么處理的步驟是怎么樣呢?
暫時(shí)關(guān)閉串口接收DMA通道,有兩個(gè)原因:1.防止后面又有數(shù)據(jù)接收到,產(chǎn)生干擾,因?yàn)榇藭r(shí)的數(shù)據(jù)還未處理。2.DMA需要重新配置。
清DMA標(biāo)志位。
從DMA寄存器中獲取接收到的數(shù)據(jù)字節(jié)數(shù)(可有可無)。
重新設(shè)置DMA下次要接收的數(shù)據(jù)字節(jié)數(shù),注意,數(shù)據(jù)傳輸數(shù)量范圍為0至65535。這個(gè)寄存器只能在通道不工作(DMA_CCRx的EN=0)時(shí)寫入。通道開啟后該寄存器變?yōu)橹蛔x,指示剩余的待傳輸字節(jié)數(shù)目。寄存器內(nèi)容在每次DMA傳輸后遞減。數(shù)據(jù)傳輸結(jié)束后,寄存器的內(nèi)容或者變?yōu)?;或者當(dāng)該通道配置為自動(dòng)重加載模式時(shí),寄存器的內(nèi)容將被自動(dòng)重新加載為之前配置時(shí)的數(shù)值。當(dāng)寄存器的內(nèi)容為0時(shí),無論通道是否開啟,都不會(huì)發(fā)生任何數(shù)據(jù)傳輸。
給出信號(hào)量,發(fā)送接收到新數(shù)據(jù)標(biāo)志,供前臺(tái)程序查詢。
開啟DMA通道,等待下一次的數(shù)據(jù)接收,注意,對(duì)DMA的相關(guān)寄存器配置寫入,如重置DMA接收數(shù)據(jù)長(zhǎng)度,必須要在關(guān)閉DMA的條件進(jìn)行,否則操作無效。
注意事項(xiàng)
STM32的IDLE的中斷在串口無數(shù)據(jù)接收的情況下,是不會(huì)一直產(chǎn)生的,產(chǎn)生的條件是這樣的,當(dāng)清除IDLE標(biāo)志位后,必須有接收到第一個(gè)數(shù)據(jù)后,才開始觸發(fā),一斷接收的數(shù)據(jù)斷流,沒有接收到數(shù)據(jù),即產(chǎn)生IDLE中斷。如果中斷發(fā)送數(shù)據(jù)幀的速率很快,MCU來不及處理此次接收到的數(shù)據(jù),中斷又發(fā)來數(shù)據(jù)的話,這里不能開啟,否則數(shù)據(jù)會(huì)被覆蓋。有兩種方式解決:
在重新開啟接收DMA通道之前,將Rx_Buf緩沖區(qū)里面的數(shù)據(jù)復(fù)制到另外一個(gè)數(shù)組中,然后再開啟DMA,然后馬上處理復(fù)制出來的數(shù)據(jù)。
建立雙緩沖,重新配置DMA_MemoryBaseAddr的緩沖區(qū)地址,那么下次接收到的數(shù)據(jù)就會(huì)保存到新的緩沖區(qū)中,不至于被覆蓋。
實(shí)驗(yàn)效果: 當(dāng)外部給單片機(jī)發(fā)送數(shù) 據(jù)的時(shí)候,假設(shè)這幀數(shù)據(jù)長(zhǎng)度是1000個(gè)字節(jié),那么在單片機(jī)接收到一個(gè)字節(jié)的時(shí)候并不會(huì)產(chǎn)生串口中斷,只是DMA在背后默默地把數(shù)據(jù)搬運(yùn)到你指定的緩沖區(qū)里面。當(dāng)整幀數(shù)據(jù)發(fā)送完畢之后串口才會(huì)產(chǎn)生一次中斷,此時(shí)可以利用DMA_GetCurrDataCounter()
函數(shù)計(jì)算出本次的數(shù)據(jù)接受長(zhǎng)度,從而進(jìn)行數(shù)據(jù)處理。
串口的配置很簡(jiǎn)單,基本與使用串口的時(shí)候一致,只不過一般我們是打開接收緩沖區(qū)非空中斷,而現(xiàn)在是打開空閑中斷——USART_ITConfig(DEBUG_USARTx, USART_IT_IDLE, ENABLE);
。
/** * @brief USART GPIO 配置,工作參數(shù)配置 * @param 無 * @retval 無 */ void USART_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; // 打開串口GPIO的時(shí)鐘 DEBUG_USART_GPIO_APBxClkCmd(DEBUG_USART_GPIO_CLK, ENABLE); // 打開串口外設(shè)的時(shí)鐘 DEBUG_USART_APBxClkCmd(DEBUG_USART_CLK, ENABLE); // 將USART Tx的GPIO配置為推挽復(fù)用模式 GPIO_InitStructure.GPIO_Pin = DEBUG_USART_TX_GPIO_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure); // 將USART Rx的GPIO配置為浮空輸入模式 GPIO_InitStructure.GPIO_Pin = DEBUG_USART_RX_GPIO_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStructure); // 配置串口的工作參數(shù) // 配置波特率 USART_InitStructure.USART_BaudRate = DEBUG_USART_BAUDRATE; // 配置 針數(shù)據(jù)字長(zhǎng) USART_InitStructure.USART_WordLength = USART_WordLength_8b; // 配置停止位 USART_InitStructure.USART_StopBits = USART_StopBits_1; // 配置校驗(yàn)位 USART_InitStructure.USART_Parity = USART_Parity_No ; // 配置硬件流控制 USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 配置工作模式,收發(fā)一起 USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; // 完成串口的初始化配置 USART_Init(DEBUG_USARTx, &USART_InitStructure); // 串口中斷優(yōu)先級(jí)配置 NVIC_Configuration(); #if USE_USART_DMA_RX // 開啟 串口空閑IDEL 中斷 USART_ITConfig(DEBUG_USARTx, USART_IT_IDLE, ENABLE); // 開啟串口DMA接收 USART_DMACmd(DEBUG_USARTx, USART_DMAReq_Rx, ENABLE); /* 使能串口DMA */ USARTx_DMA_Rx_Config(); #else // 使能串口接收中斷 USART_ITConfig(DEBUG_USARTx, USART_IT_RXNE, ENABLE); #endif #if USE_USART_DMA_TX // 開啟串口DMA發(fā)送 // USART_DMACmd(DEBUG_USARTx, USART_DMAReq_Tx, ENABLE); USARTx_DMA_Tx_Config(); #endif // 使能串口 USART_Cmd(DEBUG_USARTx, ENABLE); }
串口DMA配置
把DMA配置完成,就可以直接打開DMA了,讓它處于工作狀態(tài),當(dāng)有數(shù)據(jù)的時(shí)候就能直接搬運(yùn)了。
#if USE_USART_DMA_RX static void USARTx_DMA_Rx_Config(void) { DMA_InitTypeDef DMA_InitStructure; // 開啟DMA時(shí)鐘 RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); // 設(shè)置DMA源地址:串口數(shù)據(jù)寄存器地址*/ DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)USART_DR_ADDRESS; // 內(nèi)存地址(要傳輸?shù)淖兞康闹羔? DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)Usart_Rx_Buf; // 方向:從內(nèi)存到外設(shè) DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; // 傳輸大小 DMA_InitStructure.DMA_BufferSize = USART_RX_BUFF_SIZE; // 外設(shè)地址不增 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 內(nèi)存地址自增 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; // 外設(shè)數(shù)據(jù)單位 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; // 內(nèi)存數(shù)據(jù)單位 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; // DMA模式,一次或者循環(huán)模式 //DMA_InitStructure.DMA_Mode = DMA_Mode_Normal ; DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; // 優(yōu)先級(jí):中 DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh; // 禁止內(nèi)存到內(nèi)存的傳輸 DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; // 配置DMA通道 DMA_Init(USART_RX_DMA_CHANNEL, &DMA_InitStructure); // 清除DMA所有標(biāo)志 DMA_ClearFlag(DMA1_FLAG_TC5); DMA_ITConfig(USART_RX_DMA_CHANNEL, DMA_IT_TE, ENABLE); // 使能DMA DMA_Cmd (USART_RX_DMA_CHANNEL,ENABLE); } #endif
接收完數(shù)據(jù)處理
因?yàn)榻邮胀陻?shù)據(jù)之后,會(huì)產(chǎn)生一個(gè)idle中斷,也就是空閑中斷,那么我們就可以在中斷服務(wù)函數(shù)中知道已經(jīng)接收完了,就可以處理數(shù)據(jù)了,但是中斷服務(wù)函數(shù)的上下文環(huán)境是中斷,所以,盡量是快進(jìn)快出,一般在中斷中將一些標(biāo)志置位,供前臺(tái)查詢。在中斷中先判斷我們的產(chǎn)生在中斷的類型是不是idle中斷,如果是則進(jìn)行下一步,否則就無需理會(huì)。
/** ****************************************************************** * @brief 串口中斷服務(wù)函數(shù) * @author jiejie * @version V1.0 * @date 2018-xx-xx ****************************************************************** */ void DEBUG_USART_IRQHandler(void) { #if USE_USART_DMA_RX /* 使用串口DMA */ if(USART_GetITStatus(DEBUG_USARTx,USART_IT_IDLE)!=RESET) { /* 接收數(shù)據(jù) */ Receive_DataPack(); // 清除空閑中斷標(biāo)志位 USART_ReceiveData( DEBUG_USARTx ); } #else /* 接收中斷 */ if(USART_GetITStatus(DEBUG_USARTx,USART_IT_RXNE)!=RESET) { Receive_DataPack(); } #endif }
Receive_DataPack()
這個(gè)才是真正的接收數(shù)據(jù)處理函數(shù),為什么我要將這個(gè)函數(shù)單獨(dú)封裝起來呢?因?yàn)檫@個(gè)函數(shù)其實(shí)是很重要的,因?yàn)槲业拇a兼容普通串口接收與空閑中斷,不一樣的接收類型其處理也不一樣,所以直接封裝起來更好,在源碼中通過宏定義實(shí)現(xiàn)選擇接收的方式!更考慮了兼容操作系統(tǒng)的,可能我會(huì)在系統(tǒng)中使用dma+空閑中斷,所以,供前臺(tái)查詢的信號(hào)量就有可能不一樣,可能需要修改,我就把它封裝起來了。不過無所謂,都是一樣的。
/************************************************************ * @brief Uart_DMA_Rx_Data * @param NULL * @return NULL * @author jiejie * @github https://github.com/jiejieTop * @date 2018-xx-xx * @version v1.0 * @note 使用串口 DMA 接收時(shí)調(diào)用的函數(shù) ***********************************************************/ #if USE_USART_DMA_RX void Receive_DataPack(void) { /* 接收的數(shù)據(jù)長(zhǎng)度 */ uint32_t buff_length; /* 關(guān)閉DMA ,防止干擾 */ DMA_Cmd(USART_RX_DMA_CHANNEL, DISABLE); /* 暫時(shí)關(guān)閉dma,數(shù)據(jù)尚未處理 */ /* 清DMA標(biāo)志位 */ DMA_ClearFlag( DMA1_FLAG_TC5 ); /* 獲取接收到的數(shù)據(jù)長(zhǎng)度 單位為字節(jié)*/ buff_length = USART_RX_BUFF_SIZE - DMA_GetCurrDataCounter(USART_RX_DMA_CHANNEL); /* 獲取數(shù)據(jù)長(zhǎng)度 */ Usart_Rx_Sta = buff_length; PRINT_DEBUG("buff_length = %d\n ",buff_length); /* 重新賦值計(jì)數(shù)值,必須大于等于最大可能接收到的數(shù)據(jù)幀數(shù)目 */ USART_RX_DMA_CHANNEL->CNDTR = USART_RX_BUFF_SIZE; /* 此處應(yīng)該在處理完數(shù)據(jù)再打開,如在 DataPack_Process() 打開*/ DMA_Cmd(USART_RX_DMA_CHANNEL, ENABLE); /* (OS)給出信號(hào) ,發(fā)送接收到新數(shù)據(jù)標(biāo)志,供前臺(tái)程序查詢 */ /* 標(biāo)記接收完成,在 DataPack_Handle 處理*/ Usart_Rx_Sta |= 0xC000; /* DMA 開啟,等待數(shù)據(jù)。注意,如果中斷發(fā)送數(shù)據(jù)幀的速率很快,MCU來不及處理此次接收到的數(shù)據(jù), 中斷又發(fā)來數(shù)據(jù)的話,這里不能開啟,否則數(shù)據(jù)會(huì)被覆蓋。有2種方式解決: 1. 在重新開啟接收DMA通道之前,將Rx_Buf緩沖區(qū)里面的數(shù)據(jù)復(fù)制到另外一個(gè)數(shù)組中, 然后再開啟DMA,然后馬上處理復(fù)制出來的數(shù)據(jù)。 2. 建立雙緩沖,重新配置DMA_MemoryBaseAddr的緩沖區(qū)地址,那么下次接收到的數(shù)據(jù)就會(huì) 保存到新的緩沖區(qū)中,不至于被覆蓋。 */ }
到此,關(guān)于“STM32中串口DMA的簡(jiǎn)介及功能用法”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)砀鄬?shí)用的文章!