直接存儲(chǔ)器訪問(wèn)(Direct Memory Access),簡(jiǎn)稱(chēng)DMA。DMA是CPU一個(gè)用于數(shù)據(jù)從一個(gè)地址空間到另一地址空間“搬運(yùn)”(拷貝)的組件,數(shù)據(jù)拷貝過(guò)程不需CPU干預(yù),數(shù)據(jù)拷貝結(jié)束則通知CPU處理。因此,大量數(shù)據(jù)拷貝時(shí),使用DMA可以釋放CPU資源。
在STM32控制器中,芯片采用Cortex-M3架構(gòu),總線結(jié)構(gòu)有了很大的優(yōu)化,DMA占用另外的總線,并不會(huì)與CPU的系統(tǒng)總線發(fā)生沖突。也就是說(shuō),DMA的使用不會(huì)影響CPU的運(yùn)行速度。
DMA數(shù)據(jù)拷貝過(guò)程,典型的有:(1)內(nèi)存—>內(nèi)存,內(nèi)存間拷貝;(2)外設(shè)—>內(nèi)存,如uart、spi、i2c等總線接收數(shù)據(jù)過(guò)程;(3)內(nèi)存—>外設(shè),如uart、spi、i2c等總線發(fā)送數(shù)據(jù)過(guò)程。
串口有必要使用DMA嗎?
串口(UART)是一種低速的串行異步通信,適用于低速通信場(chǎng)景,通常使用的波特率小于或等于115200bps。對(duì)于小于或者等于115200bps波特率的,而且數(shù)據(jù)量不大的通信場(chǎng)景,一般沒(méi)必要使用DMA,或者說(shuō)使用DMA并未能充分發(fā)揮出DMA的作用。對(duì)于數(shù)量大,或者波特率提高時(shí),必須使用DMA以釋放CPU資源,因?yàn)楦卟ㄌ芈士赡軒?lái)CPU資源過(guò)度浪費(fèi)的問(wèn)題。
舉個(gè)例子:對(duì)于發(fā)送,使用循環(huán)發(fā)送,可能阻塞線程,需要消耗大量CPU資源“搬運(yùn)”數(shù)據(jù),浪費(fèi)CPU。對(duì)于發(fā)送,使用中斷發(fā)送,不會(huì)阻塞線程,但需浪費(fèi)大量中斷資源,CPU頻繁響應(yīng)中斷。以115200bps波特率,1s大約傳輸11520字節(jié),大約69us需響應(yīng)一次中斷,如波特率再提高,將消耗更多CPU資源。對(duì)于接收,如仍采用傳統(tǒng)的中斷模式接收,同樣會(huì)因?yàn)轭l繁中斷導(dǎo)致消耗大量CPU資源。因此,在高波特率傳輸場(chǎng)景下,串口非常有必要使用DMA。
DMA應(yīng)用中的幾個(gè)常見(jiàn)問(wèn)題
1、概念上的誤解
DMA傳輸是在DMA請(qǐng)求下,將數(shù)據(jù)從源端傳輸?shù)侥康亩?。常有人將DMA請(qǐng)求跟DMA的源端或目的端混為一談。這里,我們可以將DMA傳輸類(lèi)比成收發(fā)快遞,發(fā)件方即DMA源端,收件方即DMA目的端,而DMA請(qǐng)求端就是呼叫快遞的人。這個(gè)呼叫快遞的人可能是發(fā)件方、也可能是收件方,還可能是另外第三方。比方你要發(fā)個(gè)快遞,叫快遞的人可能是公司的前臺(tái)美眉。
具體到我們STM32應(yīng)用,比方通過(guò)DMA將內(nèi)存數(shù)據(jù)傳輸給UART DR寄存器發(fā)送出去,源端是存儲(chǔ)相關(guān)待發(fā)送數(shù)據(jù)的內(nèi)存區(qū)域,目的端是UART DR數(shù)據(jù)寄存器。至于DMA請(qǐng)求,可以是UART發(fā)送空事件【TXE】,也可以是定時(shí)器的某個(gè)周期性觸發(fā)事件等。
在STM32各個(gè)系列的參考手冊(cè)的DMA章節(jié)部分,都有類(lèi)似如下的DMA請(qǐng)求映射表。表格里填寫(xiě)的都只是針對(duì)各個(gè)DMA傳輸流的DMA請(qǐng)求事件,并非一定是源端或目的端。當(dāng)然,不排除作為源端或目的端同時(shí)又擔(dān)當(dāng)DMA請(qǐng)求角色的可能。
2、配置上容易忽視的問(wèn)題
我們?cè)谧鯠MA配置時(shí),比較容易忽視兩個(gè)小問(wèn)題。第一個(gè)就是源端和目的端的數(shù)據(jù)寬度的定義問(wèn)題,除了考慮配置滿足實(shí)際需要的數(shù)據(jù)寬度外,還要注意將源端、目的端二者數(shù)據(jù)訪問(wèn)寬度配置一致。不然的話,往往會(huì)導(dǎo)致些奇怪的問(wèn)題。
還有,就是 要注意DMA的傳輸方向別弄錯(cuò)了,到底是PERIPHERIAL到MEMORY還是MEMORY到PERIPHERIAL或者說(shuō)是Memory到Memory要配置正確。 尤其是在用CubeMx配置時(shí),這里有個(gè)默認(rèn)配置是PERIPHERIAL到MEMORY。如果說(shuō)你的真實(shí)意圖根本不是從PERIPHERIAL到MEMORY,而你無(wú)意中使用了這個(gè)默認(rèn)配置,結(jié)果可想而知,DMA傳輸根本沒(méi)法正常運(yùn)行。類(lèi)似配置方面的小細(xì)節(jié)要多加注意,忽略了往往會(huì)累死人。
3、DMA傳輸作用范圍問(wèn)題
前面將DMA傳輸類(lèi)比成發(fā)快遞,發(fā)快遞時(shí),快遞公司一般也沒(méi)法無(wú)處不到,那DMA傳輸也有同樣的問(wèn)題,各個(gè)DMA模塊往往有各自的服務(wù)范圍。比方以下圖STM32F4的一個(gè)框圖為例。DMA1可以輕松訪問(wèn)右邊黃色標(biāo)注出來(lái)的外設(shè),DMA2可以訪問(wèn)左邊分?jǐn)?shù)標(biāo)注出來(lái)的各個(gè)外設(shè)。如果我們?cè)诔绦蚶锏腄MA配置部分,將DMA1的源端或目的端安排為左邊的粉色標(biāo)注出來(lái)的外設(shè)、或者將DMA2的源端/目的端安排為右邊的黃色標(biāo)注出來(lái)的外設(shè),結(jié)果一定會(huì)讓你失望。因?yàn)槟闫谕L問(wèn)它到達(dá)不了的地方。
這個(gè)地方比較隱蔽,因?yàn)槲覀冊(cè)诖a里只是根據(jù)DMA請(qǐng)求自行指定源端和目的端,有時(shí)會(huì)忽視所用DMA的服務(wù)范圍。前面提過(guò),各個(gè)STM32系列參考手冊(cè)里DMA章節(jié)部分都有明確的基于各路DMA傳輸流的DMA請(qǐng)求事件源的描述和展示,但并未指定基于各個(gè)DMA請(qǐng)求的源端或目的端。所以我們?cè)诨谀硞€(gè)DMA請(qǐng)求來(lái)自指定源端或目的端時(shí),一定注意你安排的源/目的端是不是該DMA可以到達(dá)的地方,具體要結(jié)合手冊(cè)中功能框圖和總線訪問(wèn)架構(gòu)圖。比方說(shuō),下圖是STM32F7系列一個(gè)總線訪問(wèn)框架圖。不難看出,DMA1是訪問(wèn)不了AHB1外設(shè)和AHB2外設(shè)的。當(dāng)然,DMA2訪問(wèn)AHB1外設(shè)和AHB2外設(shè)沒(méi)有問(wèn)題。
比方,下圖是STM23H7系列一個(gè)總線訪問(wèn)框架圖,其中BDMA是沒(méi)法訪問(wèn)D1域或D2域的外設(shè)及內(nèi)存的。D2域的DMA1/DMA2沒(méi)法訪問(wèn)D1域中的DTCM/ITCM。D1域中的MDMA沒(méi)法訪問(wèn)D2域中的AHB2外設(shè)。
關(guān)于這些總線框架性的東西,在我們的STM32應(yīng)用中也要多加關(guān)注。比方有時(shí)在做通信數(shù)據(jù)傳輸時(shí)發(fā)現(xiàn),使用中斷沒(méi)問(wèn)題,用DMA就失敗。這時(shí)不妨查看下DMA訪問(wèn)的外設(shè)或內(nèi)存區(qū)域到底是不是它所能訪問(wèn)得到地方,如果不是就需要適當(dāng)調(diào)整下。
4、跟DCache有關(guān)的問(wèn)題
該問(wèn)題往往跟我們使用帶cache的M7內(nèi)核的STM32F7或STMH7系列芯片有關(guān),使用DMA傳輸時(shí)有時(shí)會(huì)遇到DMA訪問(wèn)到的數(shù)據(jù)不是實(shí)時(shí)的正確數(shù)據(jù)。這往往可能是因?yàn)镈MA要訪問(wèn)的內(nèi)存區(qū)域,跟CPU是共享的,同時(shí)又開(kāi)啟了相關(guān)區(qū)域的D-Cache屬性,即CPU訪問(wèn)該內(nèi)存區(qū)域數(shù)據(jù)時(shí)使用D-Cache,將內(nèi)存數(shù)據(jù)拷貝到D-Cache。之后,CPU訪問(wèn)相關(guān)數(shù)據(jù)時(shí)往往只在Cache里進(jìn)行。
比如下面的一個(gè)基于STM32F7芯片的經(jīng)典示例。首先CPU從flash里拷貝128字節(jié)常量數(shù)據(jù)到片內(nèi)SRAM,然后通過(guò)DMA將SRAM里的這128字節(jié)數(shù)據(jù)拷貝進(jìn)DTCM內(nèi)存區(qū)。最后通過(guò)CPU將DTCM里的數(shù)據(jù)跟FLASH里的原始數(shù)據(jù)進(jìn)行比較,這時(shí)會(huì)發(fā)現(xiàn)比較結(jié)果是二者內(nèi)容根本不一致。這是因?yàn)殚_(kāi)啟了SRAM的回寫(xiě)的Cache屬性,第一次將數(shù)據(jù)從flash拷貝進(jìn)SRAM過(guò)程中,數(shù)據(jù)還沒(méi)有真正寫(xiě)進(jìn)SRAM,還只是放在了Cache。當(dāng)我們通過(guò)DMA將SRAM相關(guān)區(qū)域的數(shù)據(jù)拷貝進(jìn)DTCM內(nèi)存區(qū)時(shí),并沒(méi)有將真正的來(lái)自FLASH區(qū)的數(shù)據(jù)拷貝過(guò)去,導(dǎo)致最后的數(shù)據(jù)比較失敗。
像這種情況下,我們可以有幾種方案來(lái)解決這個(gè)問(wèn)題:
1、在做將數(shù)據(jù)從SRAM拷貝到DTCM區(qū)之前,先做個(gè)D-Cache的清除操作【SCB_CleanDCache()】,將D-Cache里的數(shù)據(jù)寫(xiě)回到實(shí)際存儲(chǔ)區(qū)SRAM里。
2、我們可以通過(guò)MPU設(shè)置SRAM區(qū)域的MPU屬性,將其回寫(xiě)的Cache屬性【writeback】調(diào)整為透寫(xiě)的Cache屬性【writethrough】。
3、配置SRAM的MPU屬性為shareable共享屬性,令CPU訪問(wèn)它時(shí)不使用D-Cache。
4、將所有涉及到具有Cacheable可緩存屬性的存儲(chǔ)區(qū)域,都使用透寫(xiě)策略。這點(diǎn)可以通過(guò)配置M7內(nèi)核相關(guān)控制寄存器位實(shí)現(xiàn)。
再舉個(gè)實(shí)例,使用STM32F7芯片做UART的通信數(shù)據(jù)接收,UART接收到數(shù)據(jù)后觸發(fā)DMA,DMA將數(shù)據(jù)從UART_DR寄存器拷貝到片內(nèi)SRAM,CPU再?gòu)南鄳?yīng)的SRAM區(qū)取走數(shù)據(jù)送到某LCD顯示設(shè)備顯示輸出。這時(shí),你很可能會(huì)發(fā)現(xiàn)一個(gè)奇怪的現(xiàn)象,顯示設(shè)備輸出的數(shù)據(jù)永遠(yuǎn)都是第一次接收到的數(shù)據(jù),不管UART的發(fā)送方任何改變發(fā)送數(shù)據(jù),顯示出來(lái)的數(shù)據(jù)就是不變,只是跟第一次發(fā)送出來(lái)的數(shù)據(jù)相比是正確的。這是怎么回事呢?
原因是因?yàn)殚_(kāi)啟了SRAM的D-Cache屬性,CPU第一次從SRAM讀取數(shù)據(jù)后,同時(shí)又將數(shù)據(jù)放到D-Cache里,后面再來(lái)讀取SRAM相應(yīng)地址數(shù)據(jù)時(shí)并沒(méi)有前往SRAM,而是直接去D-Cache里提取數(shù)據(jù),從而導(dǎo)致每次顯示出來(lái)的數(shù)據(jù)總是第一次接收到的數(shù)據(jù),盡管UART那邊后續(xù)接收的數(shù)據(jù)在不停變化,但并沒(méi)有對(duì)D-Cache里的數(shù)據(jù)做同步更新。這時(shí)我們可以在CPU讀取SRAM數(shù)據(jù)前做個(gè)D-cache的清除操作,讓實(shí)際存儲(chǔ)器數(shù)據(jù)與D-Cache里數(shù)據(jù)同步更新,或者做D-Cache的失效操作,讓CPU無(wú)視D-cache直接從SRAM區(qū)讀取數(shù)據(jù),或者說(shuō)通過(guò)MPU配置禁用該SRAM區(qū)的Cache屬性。當(dāng)然,最終你選用哪種策略,得結(jié)合你的實(shí)際應(yīng)用來(lái)定。
串口DMA接收不定長(zhǎng)數(shù)據(jù)
1、在STM32的DMA資源
STM32F1系列的MCU有兩個(gè)DMA控制器(DMA2只存在于大容量產(chǎn)品中),DMA1有7個(gè)通道,DMA2有5個(gè)通道,每個(gè)通道專(zhuān)門(mén)用來(lái)管理來(lái)自于一個(gè)或者多個(gè)外設(shè)對(duì)存儲(chǔ)器的訪問(wèn)請(qǐng)求。還有一個(gè)仲裁器來(lái)協(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ǔ)器訪問(wèn)請(qǐng)求。每個(gè)數(shù)據(jù)流總共可以有多達(dá)8個(gè)通道(或稱(chēng)請(qǐng)求)。每個(gè)通道都有一個(gè)仲裁器,用于處理 DMA 請(qǐng)求間的優(yōu)先級(jí)。
2、DMA接收數(shù)據(jù)
DMA在接收數(shù)據(jù)的時(shí)候,串口接收DMA在初始化的時(shí)候就處于開(kāi)啟狀態(tài),一直等待數(shù)據(jù)的到來(lái),在軟件上無(wú)需做任何事情,只要在初始化配置的時(shí)候設(shè)置好配置就可以了。等到接收到數(shù)據(jù)的時(shí)候,告訴CPU去處理即可。
那么問(wèn)題來(lái)了,怎么知道數(shù)據(jù)是否接收完成呢?其實(shí),有很多方法:(1) 對(duì)于定長(zhǎng)的數(shù)據(jù) ,只需要判斷一下數(shù)據(jù)的接收個(gè)數(shù),就知道是否接收完成,這個(gè)很簡(jiǎn)單。(2) 對(duì)于不定長(zhǎng)的數(shù)據(jù) ,也有好幾種方法,麻煩的不會(huì)介紹,有興趣做復(fù)雜工作的同學(xué)可以在網(wǎng)上看看別人怎么做,下面這種方法是最簡(jiǎn)單的,充分利用了STM32的串口資源,效率也是非常之高:DMA+串口空閑中斷。這兩個(gè)資源配合,簡(jiǎn)直就是天衣無(wú)縫啊,無(wú)論接收什么不定長(zhǎng)的數(shù)據(jù),管你數(shù)據(jù)有多少,來(lái)一個(gè)我就收一個(gè),就像廣東人吃“山竹”,來(lái)一個(gè)吃一個(gè)。
STM32串口的狀態(tài)寄存器:
idle
idle說(shuō)明
當(dāng)我們檢測(cè)到觸發(fā)了串口總線空閑中斷的時(shí)候,就知道這一波數(shù)據(jù)傳輸完成了,然后我們就能得到這些數(shù)據(jù),去進(jìn)行處理即可。 這種方法是最簡(jiǎn)單的,根本不需要我們做多的處理,只需要配置好,串口就等著數(shù)據(jù)的到來(lái),DMA也是處于工作狀態(tài)的,來(lá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ù)(可有可無(wú))。
重新設(shè)置DMA下次要接收的數(shù)據(jù)字節(jié)數(shù)。**注意,數(shù)據(jù)傳輸數(shù)量范圍為0至65535。**這個(gè)寄存器只能在通道不工作(DMA_CCRx的EN=0)時(shí)寫(xiě)入。通道開(kāi)啟后該寄存器變?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í),無(wú)論通道是否開(kāi)啟,都不會(huì)發(fā)生任何數(shù)據(jù)傳輸。
給出信號(hào)量,發(fā)送接收到新數(shù)據(jù)標(biāo)志,供前臺(tái)程序查詢(xún)。
開(kāi)啟DMA通道,等待下一次的數(shù)據(jù)接收,注意,對(duì)DMA的相關(guān)寄存器配置寫(xiě)入,如重置DMA接收數(shù)據(jù)長(zhǎng)度,必須要在關(guān)閉DMA的條件進(jìn)行,否則操作無(wú)效。
3、注意事項(xiàng)
STM32的IDLE的中斷在串口無(wú)數(shù)據(jù)接收的情況下,是不會(huì)一直產(chǎn)生的,產(chǎn)生的條件是這樣的,當(dāng)清除IDLE標(biāo)志位后,必須有接收到第一個(gè)數(shù)據(jù)后,才開(kāi)始觸發(fā),一斷接收的數(shù)據(jù)斷流,沒(méi)有接收到數(shù)據(jù),即產(chǎn)生IDLE中斷。如果中斷發(fā)送數(shù)據(jù)幀的速率很快,MCU來(lái)不及處理此次接收到的數(shù)據(jù),中斷又發(fā)來(lái)數(shù)據(jù)的話,這里不能開(kāi)啟,否則數(shù)據(jù)會(huì)被覆蓋。有兩種方式解決:(1)在重新開(kāi)啟接收DMA通道之前,將Rx_Buf緩沖區(qū)里面的數(shù)據(jù)復(fù)制到另外一個(gè)數(shù)組中,然后再開(kāi)啟DMA,然后馬上處理復(fù)制出來(lái)的數(shù)據(jù)。(2) 建立雙緩沖 ,重新配置DMA_MemoryBaseAddr的緩沖區(qū)地址,那么下次接收到的數(shù)據(jù)就會(huì)保存到新的緩沖區(qū)中,不至于被覆蓋。
4、程序?qū)崿F(xiàn)
實(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ù)處理。
4.1串口的配置
很簡(jiǎn)單,基本與使用串口的時(shí)候一致,只不過(guò)一般我們是打開(kāi)接收緩沖區(qū)非空中斷,而現(xiàn)在是打開(kāi)空閑中斷——USART_ITConfig(DEBUG_USARTx, USART_IT_IDLE, ENABLE);。
/** *@briefUSARTGPIO配置,工作參數(shù)配置 *@param無(wú) *@retval無(wú) */ voidUSART_Config(void) { GPIO_InitTypeDefGPIO_InitStructure; USART_InitTypeDefUSART_InitStructure; //打開(kāi)串口GPIO的時(shí)鐘 DEBUG_USART_GPIO_APBxClkCmd(DEBUG_USART_GPIO_CLK,ENABLE); //打開(kāi)串口外設(shè)的時(shí)鐘 DEBUG_USART_APBxClkCmd(DEBUG_USART_CLK,ENABLE); //將USARTTx的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); //將USARTRx的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(); #ifUSE_USART_DMA_RX //開(kāi)啟串口空閑IDEL中斷 USART_ITConfig(DEBUG_USARTx,USART_IT_IDLE,ENABLE); //開(kāi)啟串口DMA接收 USART_DMACmd(DEBUG_USARTx,USART_DMAReq_Rx,ENABLE); /*使能串口DMA*/ USARTx_DMA_Rx_Config(); #else //使能串口接收中斷 USART_ITConfig(DEBUG_USARTx,USART_IT_RXNE,ENABLE); #endif #ifUSE_USART_DMA_TX //開(kāi)啟串口DMA發(fā)送 //USART_DMACmd(DEBUG_USARTx,USART_DMAReq_Tx,ENABLE); USARTx_DMA_Tx_Config(); #endif //使能串口 USART_Cmd(DEBUG_USARTx,ENABLE); }
4.2串口DMA配置
把DMA配置完成,就可以直接打開(kāi)DMA了,讓它處于工作狀態(tài),當(dāng)有數(shù)據(jù)的時(shí)候就能直接搬運(yùn)了。
#ifUSE_USART_DMA_RX staticvoidUSARTx_DMA_Rx_Config(void) { DMA_InitTypeDefDMA_InitStructure; //開(kāi)啟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
4.3接收完數(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)查詢(xún)。在中斷中先判斷我們的產(chǎn)生在中斷的類(lèi)型是不是idle中斷,如果是則進(jìn)行下一步,否則就無(wú)需理會(huì)。
/** ****************************************************************** *@brief串口中斷服務(wù)函數(shù) *@authorjiejie *@versionV1.0 *@date2018-xx-xx ****************************************************************** */ voidDEBUG_USART_IRQHandler(void) { #ifUSE_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 }
4.4Receive_DataPack()
這個(gè)才是真正的接收數(shù)據(jù)處理函數(shù),為什么我要將這個(gè)函數(shù)單獨(dú)封裝起來(lái)呢?因?yàn)檫@個(gè)函數(shù)其實(shí)是很重要的,因?yàn)槲业拇a兼容普通串口接收與空閑中斷,不一樣的接收類(lèi)型其處理也不一樣,所以直接封裝起來(lái)更好,在源碼中通過(guò)宏定義實(shí)現(xiàn)選擇接收的方式!更考慮了兼容操作系統(tǒng)的,可能我會(huì)在系統(tǒng)中使用dma+空閑中斷,所以,供前臺(tái)查詢(xún)的信號(hào)量就有可能不一樣,可能需要修改,我就把它封裝起來(lái)了。
/************************************************************ *@briefUart_DMA_Rx_Data *@paramNULL *@returnNULL *@authorjiejie *@githubhttps://github.com/jiejieTop *@date2018-xx-xx *@versionv1.0 *@note使用串口DMA接收時(shí)調(diào)用的函數(shù) ***********************************************************/ #ifUSE_USART_DMA_RX voidReceive_DataPack(void) { /*接收的數(shù)據(jù)長(zhǎng)度*/ uint32_tbuff_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 ",buff_length); /*重新賦值計(jì)數(shù)值,必須大于等于最大可能接收到的數(shù)據(jù)幀數(shù)目*/ USART_RX_DMA_CHANNEL->CNDTR=USART_RX_BUFF_SIZE; /*此處應(yīng)該在處理完數(shù)據(jù)再打開(kāi),如在DataPack_Process()打開(kāi)*/ DMA_Cmd(USART_RX_DMA_CHANNEL,ENABLE); /*(OS)給出信號(hào),發(fā)送接收到新數(shù)據(jù)標(biāo)志,供前臺(tái)程序查詢(xún)*/ /*標(biāo)記接收完成,在DataPack_Handle處理*/ Usart_Rx_Sta|=0xC000; /* DMA 開(kāi)啟,等待數(shù)據(jù)。注意,如果中斷發(fā)送數(shù)據(jù)幀的速率很快,MCU來(lái)不及處理此次接收到的數(shù)據(jù), 中斷又發(fā)來(lái)數(shù)據(jù)的話,這里不能開(kāi)啟,否則數(shù)據(jù)會(huì)被覆蓋。有2種方式解決: 1.在重新開(kāi)啟接收DMA通道之前,將Rx_Buf緩沖區(qū)里面的數(shù)據(jù)復(fù)制到另外一個(gè)數(shù)組中, 然后再開(kāi)啟DMA,然后馬上處理復(fù)制出來(lái)的數(shù)據(jù)。 2.建立雙緩沖,重新配置DMA_MemoryBaseAddr的緩沖區(qū)地址,那么下次接收到的數(shù)據(jù)就會(huì) 保存到新的緩沖區(qū)中,不至于被覆蓋。 */ }
-
mcu
+關(guān)注
關(guān)注
146文章
17149瀏覽量
351213 -
控制器
+關(guān)注
關(guān)注
112文章
16361瀏覽量
178071 -
STM32
+關(guān)注
關(guān)注
2270文章
10900瀏覽量
356046 -
串口
+關(guān)注
關(guān)注
14文章
1554瀏覽量
76523 -
dma
+關(guān)注
關(guān)注
3文章
561瀏覽量
100587
原文標(biāo)題:STM32串口收發(fā)數(shù)據(jù)為什么要使用DMA?有哪些常見(jiàn)問(wèn)題?
文章出處:【微信號(hào):雨飛工作室,微信公眾號(hào):雨飛工作室】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論