性无码一区二区三区在线观看,少妇被爽到高潮在线观看,午夜精品一区二区三区,无码中文字幕人妻在线一区二区三区,无码精品国产一区二区三区免费

lihui710884923
認(rèn)證:VIP會員
作者動態(tài)
物聯(lián)網(wǎng)專題之NB-IoT項(xiàng)目框架(二)
2024-07-25 11:53
物聯(lián)網(wǎng)專題之介紹(一)
2024-07-11 18:37
單片機(jī)RTC的中斷剖析
2024-03-09 15:28
串口通訊的來龍去脈
2024-02-14 17:32
stm32單片機(jī)的USB燒錄程序
2023-09-08 22:48

單片機(jī)串口通訊的進(jìn)階

串口接收中斷斷幀是很重要的,那么該如何判斷一幀數(shù)據(jù)是否接受完成呢?常用的兩種方法是:

時間斷幀,我們可以通過在定時器里對兩幀數(shù)據(jù)的時間進(jìn)行比較,一幀數(shù)據(jù)里邊兩個數(shù)據(jù)的時間差是很短的,如果超過一定時間之后仍舊無心的數(shù)據(jù)被接收到,那么我們就可以判斷為一幀數(shù)據(jù)結(jié)束。

協(xié)議斷幀,這就好比我么兩個人約定好數(shù)據(jù)大致長度,或某個字符出現(xiàn)即為一幀數(shù)據(jù)的結(jié)束,由此可以來進(jìn)行斷幀,判斷為一幀數(shù)據(jù)的接收完成。

實(shí)現(xiàn)串口數(shù)據(jù)幀斷幀,有很多的方法,比如使用串口的IDLE中斷進(jìn)行斷幀,使用定時器根據(jù)時間斷幀、使用特殊標(biāo)識符進(jìn)行斷幀等等,剛開始工作的時候,我自己就寫好幾個版本,基本上一個項(xiàng)目一個版本,但是現(xiàn)在我使用的基本上只有這一個版本。

空閑中斷斷幀

使用串口的IDLE中斷:比如STM32有串口的IDLE中斷,在數(shù)據(jù)發(fā)送完成,串口進(jìn)入空閑的時候會產(chǎn)生一個IDLE中斷,在HAL中也使用到這種方式。但是,其他的單片機(jī)不一定有IDLE中斷,比如51單片機(jī)、MSP430等等,所以這個方式并不是萬能的,.方法二:使用特殊字符:給數(shù)據(jù)包的幀頭和幀尾使用一個或者一組特定的字符,也就是我們說的幀頭和幀尾。這個有一定的概率會造成數(shù)據(jù)包錯位或者數(shù)據(jù)包內(nèi)容和幀頭尾內(nèi)容一樣。如果數(shù)據(jù)幀幀尾丟失,著會這造成單片機(jī)一直等待數(shù)據(jù)幀幀尾,進(jìn)入死等情況。方法三:使用串口+定時的方式進(jìn)行斷幀。定時器主要用來計(jì)時數(shù)據(jù)幀與幀之間的時間,這要這個時間大于一個設(shè)定的閾值,超出這個時間就表示當(dāng)前數(shù)據(jù)幀發(fā)送完畢,下一次來的數(shù)據(jù),一定是一個新的數(shù)據(jù)幀。本次主要對方法三做一個說明。這也是我在項(xiàng)目中常用一種方式,這種方式可以適用于串口、RS485、RS422等通訊接口,其帶有循環(huán)緩存隊(duì)列的思想用在CAN總線通訊中也十分合適(CAN通訊不需要自己去判斷報文的幀頭幀尾),這樣的通訊設(shè)置方式能夠極大的降低數(shù)據(jù)掉包率。

使用資源串口 1個定時器 1個

實(shí)現(xiàn)基本原理采用循環(huán)隊(duì)列,將串口的數(shù)據(jù)流進(jìn)行接收,根據(jù)定時器計(jì)時單個BYTE之間的時間,只要這個時間超過一個閾值,則認(rèn)為下一個數(shù)據(jù)為新的數(shù)據(jù)幀開頭。(使用循環(huán)緩存隊(duì)列進(jìn)行數(shù)據(jù)接收、采用定時器進(jìn)行數(shù)據(jù)斷幀),數(shù)據(jù)接收采用的是單字節(jié)中斷接收方式。在查串口的中斷函數(shù)中,只進(jìn)行數(shù)據(jù)接收,不對數(shù)據(jù)進(jìn)行處理。在主函數(shù)中,才對數(shù)據(jù)幀的合法性進(jìn)行判斷和數(shù)據(jù)幀內(nèi)容進(jìn)行處理和應(yīng)答。

特點(diǎn)采用循環(huán)緩存數(shù)據(jù)隊(duì)列,有效的降低數(shù)據(jù)掉包風(fēng)險適用于串行通訊要求發(fā)送數(shù)據(jù)必須連續(xù),且?guī)c幀之間需要有一定的時間間隔。數(shù)據(jù)幀可以實(shí)現(xiàn)任意長度采用生產(chǎn)消費(fèi)者模型的設(shè)計(jì)思路


下面我就舉個自己設(shè)計(jì)的項(xiàng)目例子吧。本次使用STM32F103RCT6單片機(jī),當(dāng)然我也將這種方式用到過MSP430、8051、NRF24LE1等單片機(jī)中。

數(shù)據(jù)幀規(guī)定一個完整的數(shù)據(jù)包由開始字符、設(shè)備類型碼、設(shè)備地址碼、命令字、數(shù)據(jù)長度、數(shù)據(jù)區(qū)、CRC校驗(yàn)及結(jié)束字符組成。如下所示:

各區(qū)域含義如下:開始字符:1字節(jié),表示一個數(shù)據(jù)包的開始。固定為0xAA。設(shè)備類型:1字節(jié),表示設(shè)備類型,固定為0x01。所有設(shè)備都能接收廣播類型(0xFF)的命令請求設(shè)備地址:1字節(jié),表示設(shè)備在系統(tǒng)中的地址,由用戶可以自行定義,默認(rèn)值為0x01。所有設(shè)備都能接收廣播地址(0xFF)的命令請求。命令字:1字節(jié),即功能命令。見下文,按照規(guī)定的格式訪問設(shè)備。數(shù)據(jù)長度:1字節(jié),表示后面跟隨的有效數(shù)據(jù)區(qū)的字節(jié)數(shù),范圍0  -  240。數(shù)據(jù)區(qū):N字節(jié),為有效數(shù)據(jù),長度為前面數(shù)據(jù)長度定義的字節(jié)數(shù)。數(shù)據(jù)長度為0時沒有數(shù)據(jù)區(qū)。數(shù)據(jù)區(qū)的數(shù)值,參見命令描述。CRC校驗(yàn):2字節(jié),從開始字符到數(shù)據(jù)區(qū)最后一個字節(jié)的所有字符的16位CRC校驗(yàn)值。校驗(yàn)值低位再前,高位在后。結(jié)束字符:1字節(jié),表示數(shù)據(jù)包結(jié)束。固定為0x0E。

數(shù)據(jù)幀斷幀時間閾值在發(fā)送數(shù)據(jù)包的時候,要求先準(zhǔn)備好所有要發(fā)送的字符,連續(xù)發(fā)送出去,中間不得有較長時間停頓。多個數(shù)據(jù)包間必須有3.5個BYTE傳送時間的時間間隔。例: RS232傳輸位如下:1 起始位8 數(shù)據(jù)位,首先發(fā)送最低有效位0 位作為奇偶校驗(yàn)1 停止位

計(jì)算時間:T=3.5*( 1+數(shù)據(jù)位+奇偶校驗(yàn)+停止位) / 波特率如:使用9600,則間隔時間為4ms。在波特率大于19200的時候,使用固定的時間1.75ms

注意:既然定義了這樣的時間間隔,那么就必須要要求發(fā)送端發(fā)過來的數(shù)據(jù)幀也要滿足這個時間間隔的標(biāo)準(zhǔn)。

eg:約定總線的波特率為115200,如果發(fā)送端需要發(fā)送3個數(shù)據(jù)幀的時候,那么就必須要要求這個三個數(shù)據(jù)幀與幀的時間間隔必須要要大于1.75MS,否者接收端就會把這三個數(shù)據(jù)幀認(rèn)為是一個數(shù)據(jù)幀。

程序代碼實(shí)現(xiàn)首先需要對串口初始化:將單片機(jī)的串口配制成實(shí)際要求的串口波特率,和通訊格式,我這里默認(rèn)配置為115200,8,N,1的方式。串口要打開接收中斷,每接收一個BYTE數(shù)據(jù),就要進(jìn)入中斷一次。定時器也需要進(jìn)行初始化,將定時的時間間隔為50uS,并且啟動定時器,讓定時器間隔50uS進(jìn)入中斷函數(shù)一次。串口與定時器初始化的代碼我就不公布了,這是單片機(jī)開發(fā)人員的常規(guī)操作。而且每款單片機(jī)的初始化的方式不一樣。

接下來就進(jìn)入主題了,在進(jìn)入主題之前,先介紹下我所定義的一些結(jié)構(gòu)體類型,不然后面看起來會相當(dāng)?shù)某粤Γ菏紫?,定義了一個T_FramCtlType的數(shù)據(jù)結(jié)構(gòu)體。這個結(jié)構(gòu)體用來記錄幀與幀之間的時間間隔。主要是用在定時器的中斷函數(shù)中,這里大家有個相關(guān)的映象即可:

然后我定義了一個消息和循環(huán)緩存的數(shù)據(jù)結(jié)構(gòu)體:

ComMsgType單個數(shù)據(jù)幀的最大長度,我這里定義為為250個字節(jié),大家在使用的時候,可以根據(jù)自己的協(xié)議設(shè)計(jì)要求,可以自行定義。MsgFifoType是串口消息的循環(huán)緩存隊(duì)列和幀時間的一個結(jié)構(gòu)體,可以緩存10個數(shù)據(jù)幀,后面我們所有的操作都是圍繞著這個結(jié)構(gòu)體進(jìn)行操作。接下來是協(xié)議相關(guān)的,我這里宏定義了數(shù)據(jù)幀的幀頭,幀尾和數(shù)據(jù)幀類容的結(jié)構(gòu)體。

下面開始看看代碼的編寫,首先是結(jié)構(gòu)體數(shù)據(jù)的初始化。

注意:COMMsgFifo[_COMx]這個是我為了適配多路串口同時使用,所以定義了多個MsgFifoType類型的緩存,一路串口一個COMMsgFifo緩存池。所以這里需要稍微把思路轉(zhuǎn)換下。緊接著就是BandIndex的值更改。這個值主要為了定義我們幀時間間隔閾值。

比如,我這里使用的是115200的波特率,那么BandIndex的值為6.后面進(jìn)行斷幀的時候只需要根據(jù)這個值去查找Tim3_5ByteTab數(shù)值,確定出斷幀閾值。

一切準(zhǔn)備就緒的時候,就可以開始接收數(shù)據(jù)了,當(dāng)串口中斷接收到一個BYTE的時候,會進(jìn)入串口中斷函數(shù)。

中斷函數(shù)就只干了一件事行,把接收到的數(shù)據(jù)dat通過Pro_ComDataSaveToBuf()函數(shù)寫入到緩存中。

在Pro_ComDataSaveToBuf()函數(shù)中,首先要使能T_FramCtl結(jié)構(gòu)體中的超時檢測機(jī)制,然后對Counter清零。接下來就是將這個數(shù)據(jù)寫到合適的位置,根據(jù)當(dāng)前緩存數(shù)據(jù)幀的位置,以及數(shù)據(jù)幀Len的長度,同時Len要自加。COMMsgFifo[_COMx].Msg[COMMsgFifo[_COMx].AddWrite].Data[COMMsgFifo[_COMx].Msg[COMMsgFifo[_COMx].AddWrite].Len++] = _Dat;這句代碼很關(guān)鍵,需要多花點(diǎn)時間進(jìn)行研究。雖然這里只寫了簡單的基于,但是卻反復(fù)的調(diào)用了MsgFifoType結(jié)構(gòu)體中的成員。接下來的if語句,也僅僅是對單個數(shù)據(jù)幀的長度進(jìn)行判斷,避免數(shù)據(jù)幀長度超過250個自己,而造成內(nèi)存越界。

到了這里,我們開始接收數(shù)據(jù)了,超時機(jī)制檢測已經(jīng)啟動了。在看看定時器中斷是怎么操作的?

定時器是按照50us周期進(jìn)行中斷的,里面也有其他的功能計(jì)時,但是與我們串口斷幀相關(guān)的就一個函數(shù),pro_ComFramTim50uS()。

在這個函數(shù)中,看以看到,當(dāng)T_FramCtl.Enable使能之后,T_FramCtl.Counter開始自加,如果串口中斷中一直有數(shù)據(jù)來,這個T_FramCtl.Counter的值會一直被清零,T_FramCtl.Counter值一直達(dá)不到閾值條件。當(dāng)串口中斷接收數(shù)據(jù)完畢之后,串口沒有數(shù)據(jù)了,由于T_FramCtl.Counter自加,當(dāng)計(jì)數(shù)達(dá)到35之后,也就是我們之間設(shè)置的1.75ms,這時就滿足了if (COMMsgFifo[_COMx].T_FramCtl.Counter > Tim3_5ByteTab[COMMsgFifo[_COMx].BandIndex] )的條件。判斷條件if ( COMMsgFifo[_COMx].Remain < COM_RXTX_FIFO_SIZE )是為了判斷緩存池是否滿了,如果沒有滿COMMsgFifo[_COMx].Remain才自加,其表示當(dāng)前緩存數(shù)據(jù)幀的數(shù)目,另外COMMsgFifo[_COMx].AddWrite也自加一次,為下一次數(shù)組幀寫的位置加1。最后還有個重要的事情COMMsgFifo[_COMx].T_FramCtl.Enable = 0關(guān)閉超時檢測機(jī)制。

這樣,一個數(shù)據(jù)幀就被完成的接收到數(shù)據(jù)緩存中了,生產(chǎn)者已經(jīng)完成。后面所有的數(shù)據(jù)幀都是按照這個機(jī)制,進(jìn)行數(shù)據(jù)產(chǎn)生;接下來就是數(shù)據(jù)消費(fèi)者,數(shù)據(jù)處理了。

數(shù)據(jù)處理,我是放在主循環(huán)while(1)中

由DealUartProtocolEvent()函數(shù)進(jìn)行數(shù)據(jù)處理。

在DealUartProtocolEvent()函數(shù)中,首先判讀COMMsgFifo[COMx].Remain 的值,只有大于0的時候,才表示緩存隊(duì)列中有數(shù)據(jù)幀需要處理。接下來數(shù)據(jù)幀需要通過pro_UartPacketAnalyse()函數(shù)進(jìn)行數(shù)據(jù)幀合法性進(jìn)行判斷,如果數(shù)據(jù)幀合法,則將數(shù)據(jù)幀的類容傳遞給InstructCMD_Uart結(jié)構(gòu)體(當(dāng)然包含設(shè)備類型、設(shè)備地址、命令字、數(shù)據(jù)內(nèi)容、數(shù)據(jù)長度等參數(shù))。由pro_UartFrameProtocolParse()函數(shù)對數(shù)據(jù)內(nèi)容和功能進(jìn)行解析。這里我就不對這個函數(shù)進(jìn)行展示了。最后就是循環(huán)隊(duì)列的COMMsgFifo[COMx].AddRead自加,實(shí)現(xiàn)首位相連循環(huán)的讀,同時不要忘記了COMMsgFifo[COMx].Remain減一。這樣,一個數(shù)據(jù)幀在串口接收的時候就已經(jīng)實(shí)現(xiàn)了數(shù)據(jù)斷幀,后面在主函數(shù)中處理的不過是后期的數(shù)據(jù)幀處理而已。

最后的最后,在讓我展示下,數(shù)據(jù)幀是如何進(jìn)行合法性判斷的。

大致內(nèi)容,就是先判斷了幀頭、然后是設(shè)備類型、設(shè)備ID、數(shù)據(jù)幀長度、幀尾,以及CRC校驗(yàn)值。只有都通過了才將數(shù)據(jù)幀的內(nèi)容傳遞出去,否者就傳遞固定的錯誤應(yīng)答和應(yīng)答代碼。

實(shí)際測試:說了這么多,那就實(shí)際測試一下吧。藍(lán)色的為發(fā)送數(shù)據(jù)幀,紅色的設(shè)備應(yīng)答數(shù)據(jù)幀。

錯誤功能碼定義:

第一個數(shù)據(jù)幀 AA FF FF 23 00 0C 25 0E 為正常完整的數(shù)據(jù)幀。所以設(shè)備正常應(yīng)答。第二個數(shù)據(jù)幀F(xiàn)F FF 22 00 9C 24 0E 缺少幀頭,應(yīng)答數(shù)據(jù)幀應(yīng)答錯誤功能碼2F,數(shù)據(jù)長度為1,數(shù)據(jù)內(nèi)容為80(幀頭錯誤)第三個數(shù)據(jù)幀AA FF FF 24 01 3C 27 0E 缺功能碼為24,數(shù)據(jù)長度為1,但是數(shù)據(jù)區(qū)內(nèi)容丟失,后面的兩個字節(jié)為CRC16校驗(yàn)和幀尾,應(yīng)答數(shù)據(jù)幀應(yīng)答錯誤功能碼2F,數(shù)據(jù)長度為1,數(shù)據(jù)內(nèi)容為83(長度錯誤)第四個數(shù)據(jù)幀AA FF FF 25 00 AC 26 缺少幀尾,應(yīng)答數(shù)據(jù)幀應(yīng)答錯誤功能碼2F,數(shù)據(jù)長度為1,數(shù)據(jù)內(nèi)容為83(長度錯誤)

總結(jié)一下:這種斷幀方式,基本上是比較通用的一種方式,我也運(yùn)用很多的項(xiàng)目設(shè)計(jì)中,特別是針對低端的單片機(jī),當(dāng)然STM32的IDLE中斷也是一個很優(yōu)秀的方式。

聲明:本內(nèi)容為作者獨(dú)立觀點(diǎn),不代表電子星球立場。未經(jīng)允許不得轉(zhuǎn)載。授權(quán)事宜與稿件投訴,請聯(lián)系:editor@netbroad.com
覺得內(nèi)容不錯的朋友,別忘了一鍵三連哦!
贊 2
收藏 2
關(guān)注 210
成為作者 賺取收益
全部留言
0/200
成為第一個和作者交流的人吧