前言:
串口通訊對于所有的嵌入式工程師十分常見,對于一個與外界交互的系統(tǒng)必須依賴一些手段,比如串口、USB、紅外、GPRS之類的數(shù)據(jù)通訊傳輸方式。而串口作為一種廉價的短距離可靠的通訊方式得到了廣泛應用。
廢話少說了,就此打住,進入正題。
本文主要從軟件結(jié)構(gòu)上講解如何在資源比較缺乏的系統(tǒng)上實現(xiàn)通訊協(xié)議的串口通訊編程,以及如何優(yōu)化程序效率,從而使系統(tǒng)更快、更穩(wěn)定運行。
正文:
我們以51單片機為例。51中一般針對串口通訊編程,通常采取中斷接受查詢發(fā)送的方式。中斷函數(shù)在接受數(shù)據(jù)到達時被重復調(diào)用,其實是個重復入棧的過程,所以不宜將函數(shù)寫的太長,函數(shù)太長一般會導致棧太深占用系統(tǒng)資源,二是處理時間過長,可能導致通訊出錯。為了防止在處理數(shù)據(jù)過程中不受干擾,通常在處理接受數(shù)據(jù)前關(guān)閉中斷,處理完后再開。
通常的的編程方式如下:
staticvoidUartInterruptService(void)interrupt4
{
ES=0;
RI=0;
uart_process(SBUF);
ES=1;
}
下面重點介紹數(shù)據(jù)處理函數(shù)uart_process(SBUF);
其實很多時候,對于通訊傳輸?shù)臄?shù)據(jù)處理才是關(guān)鍵,尤其對于設計通訊協(xié)議而言。筆者在剛剛做的一個系統(tǒng)上就碰到這樣的問題,當系統(tǒng)龐大了,資源十分有限的情況下,數(shù)據(jù)處理一旦占用資源太多,效率太低將導致系統(tǒng)崩潰而無法運行。
到了這里,很多工程師可能會考慮開個大的緩沖區(qū)FIFO將接收到的數(shù)據(jù)保存在緩沖區(qū),然后對其進行解析、判斷進行下一步程序編寫,當然這在系統(tǒng)資源比較豐富的情況下是沒有問題的,ARM上采取的就是這樣的方式。但如何系統(tǒng)龐大呢,留給的資源缺乏則不行。這樣做的一個很大缺點必須是將數(shù)據(jù)幀接收完了才能夠判斷,降低了效率和運行速度。
其實還有另外的方式,可以采取在每接收一個字節(jié)就對其解析,解析完判斷轉(zhuǎn)到下一個狀態(tài),并將其中的有用數(shù)據(jù)存儲在相應的數(shù)據(jù)結(jié)構(gòu)中去,可以采取狀態(tài)機實現(xiàn)。
將狀態(tài)機設計為兩個控制狀態(tài),一是串口狀態(tài)——uart_state,一是命令類型狀態(tài)——cmd_state.
(1)狀態(tài)機開始狀態(tài):串口狀態(tài)為CMD_NO
(2)接受到STX_CMD,狀態(tài)變?yōu)镃MD_START.
(3)接下來將自動進入接受命令幀的狀態(tài),再開啟命令狀態(tài)的狀態(tài)機,對發(fā)送來的有用數(shù)據(jù)進行解析,保存,校驗等。處理完畢后將uart_state設為CMD_END狀態(tài)進行下一步的接受完畢判斷,將cmd_state設置為初始的NO_CMD狀態(tài)。
(4)最后進行ETX_CMD判斷,判斷數(shù)據(jù)接收是否完畢。
voiduart_process(U8u8)
{
if(uart_state==CMD_NO)
{
if(u8==STX_CMD)
{
uart_state=CMD_START;
}
}
elseif(uart_state==CMD_START)
{
switch(cmd_state)
{
caseNO_CMD:
cmd_state=u8;
break;
caseCOST_CMD:
//解析存儲有用數(shù)據(jù)到相應數(shù)據(jù)結(jié)構(gòu)中
//進行CRC校驗
……
uart_state=CMD_END;
cmd_state=NO_CMD;
CRC=0;
break;
……
}
……
}
elseif(uart_state==CMD_END)
{
uart_state=CMD_NO;
if(u8==ETX_CMD)
{
//接受完畢
//可以考慮拋出一個消息main函數(shù)循環(huán)中進行響應處理。
}
}
}
接下來我們要討論解析后我們數(shù)據(jù)存儲的問題,其實在資源比較足夠的情況下或者能夠擠出data區(qū)的情況下可以考慮用結(jié)構(gòu)體,我們構(gòu)造好相應結(jié)構(gòu)體,將接收到的數(shù)據(jù)存儲進去,要應用的時候就十分方便。但這也有個矛盾,一般c51定義的結(jié)構(gòu)體都被存儲在data區(qū),一般通訊的字節(jié)量大空間必然不夠,存在一個矛盾,可以采用聯(lián)合體union進行存儲效果會好一點。當然也可以在保存數(shù)據(jù)時采用定義在xdata區(qū)(片外)的buffer來存儲。這樣在一定程序上優(yōu)化了程序的執(zhí)行效率,在程序處理立即拋出消息處理,提高了通訊數(shù)據(jù)的處理速度。對于通常資源比較豐富的系統(tǒng),比如ARM上一般采取的做法是這樣的,將數(shù)據(jù)存在緩沖區(qū),接收完一幀數(shù)據(jù)后再轉(zhuǎn)換成相應的數(shù)據(jù)結(jié)構(gòu),再進行分析、校驗。
總體來說,這種采取狀態(tài)機實時解析串口通訊數(shù)據(jù)的方式在一定程序提高了程序運行效率,使軟件架構(gòu)清晰明了,程序可擴展性大,有利于后續(xù)開發(fā)。以上是筆者的一點愚見,歡迎指教。
網(wǎng)友評論:學習啦.
網(wǎng)友評論:在我發(fā)出STX_CMD命令時,uart-state狀態(tài)機將進入CMD_START狀態(tài),等待下一個命令的輸入,而此時命令狀態(tài)機cmd_state是NO_CMD狀態(tài),好了,現(xiàn)在外面發(fā)過來一個COST_CMD的命令,由于此時cmd_state正處在NO_CMD的狀態(tài),將執(zhí)行caseNO_CMD的語句,也就是將cmd_state的狀態(tài)進入COST_CMD,然后退出switch語句,這樣就相當于沒有去執(zhí)行caseCOST_CMD狀態(tài)的指令,這個命令就無效了,不知道我說清楚沒有
網(wǎng)友評論:這種只能適合一些情況,不能適合所有~~
內(nèi)存小就換個大點的嘛,現(xiàn)在的單片機差型號那么多,肯定能找到合適的.
網(wǎng)友評論:你沒有想清楚,我可以將cmd_state默認設置為NO_CMD,來個COST_CMD也就是cmd_state=u8,此時cmd_state=COST_CMD,接下來對命令幀有用數(shù)據(jù)進行解析.仔細分析下就很清楚的.
30樓說的很對,不是適合所有的情況,但大多數(shù)情況下還是合適的,如果通信協(xié)議加上上層的GUI設計的,通訊數(shù)據(jù)傳輸速度將影響整個設備的響應速度.因為不僅僅要傳輸數(shù)據(jù)還要解析,刷屏幕等,速度稍微快點響應上將好多了.
網(wǎng)友評論:誰需要直接可以工作的C51程序,可以來交流一下,QQ28591262
網(wǎng)友評論:我在使用modbusRTU模式時候遇到超時的問題,也用的是狀態(tài)機站好功能碼啥的都接受好了到了接受數(shù)據(jù)那一步了然后假如突然對方不發(fā)了怎么辦啊,不就一直停在那個狀態(tài)了啊,另一個消息來了就沒法收了呵呵,雖然一般不會出現(xiàn)但是假如出現(xiàn)了喃然后就要用到modbus里面的間隔判斷了然后那個間隔時間太短就1.5個字符,這要怎么搞啊,用定時器是不是可以?有沒其他的方法喃!因為設備內(nèi)部要跟DSP通信,還要跟外部的設備通信所以就要用到兩個串口,如果兩個都用定時器的話,那怎么搞一會定時器還給他們兩用完了.
網(wǎng)友評論:可以仿下TCP/IP的超時處理機制.
協(xié)議每執(zhí)行到一個狀態(tài),進行一次檢查.
網(wǎng)友評論:開8個BYTE的緩沖足夠了~中斷函數(shù)只負責接收,在主程序循環(huán)中處理.
staticvoidUartInterruptService(void)interrupt4
{
ES=0;
RI=0;
cSerialReciveBuffer[cSerialWriteBufferPos]=cData;
if(cSerialWriteBufferPos>=UART0_RX_BUFFER_SIZE)
{
cSerialWriteBufferPos=0;
}
cSerialReciveNum++;
ES=1;
}
//主函數(shù)循環(huán)處理
while(cSerialReciveNum)
{
}
網(wǎng)友評論:你這種方式極其不好,程序效率低下,不能夠老在while(1)循環(huán)處理一些東西,降低了程序消息,采取消息機制會好多了,關(guān)于你所說的緩沖處理我不做評價.
網(wǎng)友評論:這個也是收到數(shù)據(jù)馬上處理,如果收到多個就處理多個,設置緩沖就是為了提高效率啊~~數(shù)據(jù)不在中斷中處理是好事~
網(wǎng)友評論:while(1)一直被循環(huán)調(diào)用,一般處理是放在外面再通過消息機制發(fā)消息到while(1)里判斷處理,如果連串口數(shù)據(jù)處理也放在while(1)里程序?qū)懙暮荛L,我是比較反對這種將main弄的很長的做法:)
網(wǎng)友評論:可以編寫一個串口處理的函數(shù),把串口數(shù)據(jù)處理放在一個函數(shù)里面,這樣你就會覺得舒服多了~呵呵~~
我一直認為提高效率的方法是中斷函數(shù)盡量精簡~~
網(wǎng)友評論:大家寫串口的時候都是怎么搞的,開緩沖的多還是不開的多,還是剛開始不開,以后成為高手后就開了.
網(wǎng)友評論:是不開緩沖的少,我只是探討下一個特殊情況下的處理以及如何優(yōu)化程序.我不是什么高手.
網(wǎng)友評論:因為能夠多仔細考慮一些東西,在平臺相互移植時候就會避免一些問題:)
網(wǎng)友評論:你的狀態(tài)機是很高明的
網(wǎng)友評論:中間數(shù)據(jù)解析的時候就用了結(jié)構(gòu)體做緩沖存儲數(shù)據(jù).我見到有個網(wǎng)友在我的基礎上改了下狀態(tài)機用于他的項目的一個文檔,他針對他的應用自己設置了下狀態(tài)的切換我覺得挺好的.我這個只是提供一個架構(gòu),具體應用大家仔細修改下就可以了.另外我絕對對于資源比較缺乏的ARM芯片比如LPC的就可以采用我的方法.
網(wǎng)友評論:說白了就是把數(shù)據(jù)處理放在中斷里運行,這樣不利于程序移植和維護,如果不是迫不得已不要這樣干。
網(wǎng)友評論:受教了
其實只要我們在處理手里的案子,多注意下,像樓主說說的狀態(tài)機之類的處理手
法已經(jīng)應用很普遍了,在臺灣過來的程式中特普遍
關(guān)鍵融會貫通,自己也能熟練的用這種處理方式,這個是難點
網(wǎng)友評論:其實我對TCP/IP理解在串口通訊之前,因為出現(xiàn)了一些問題才去思考一些解決的方法。過陣子貼個TCP/IP協(xié)議中實現(xiàn)上層的HTTP協(xié)議中的狀態(tài)機,里面對于狀態(tài)的校驗和重發(fā)就做的很好。還是一句老話:“由儉入奢易,由奢入儉難”,很多一些從ARM上跑的好的程序到其他平臺出問題都可能出這樣那樣的問題,這樣一個好的架構(gòu)是很有必要的。當然不是說我這個東西有什么高明的地方,只是平時寫code的時候多想想一些問題可能會有意想不到的收獲!
網(wǎng)友評論:這樣寫的話得保證發(fā)來的第二、第三、、、、等等數(shù)不別的器件當成地址或別的啊
網(wǎng)友評論:我在這個壇子另外個帖子里面提到避免這點,比如我將STX和ETX設為HEX,中間的數(shù)據(jù)幀為ASCII就不避免了……
網(wǎng)友評論:跟著大家學習一下^_^
網(wǎng)友評論:幫頂
網(wǎng)友評論:頂...以后有這樣的文章多發(fā)一下..呵呵
網(wǎng)友評論:狀態(tài)機的方式我也常用,應該是比較好的方法,但是我反對把解析等操作放在中斷種進行。36樓中斷中只接受數(shù)據(jù)放到FIFO中也許是不錯的方法,樓下說while(1)效率低,可能他沒有考慮到,確實不能這樣傻等,效率太低,可以改進一下,采用輪詢,每隔1mS或其他時間查詢一下,同時可以
計算超時。不過我建議將解析幀頭放到接收中斷中,用一個緩沖區(qū)來放數(shù)據(jù),中斷中幀頭同步之后設定標志并依次接收存儲每一個數(shù)據(jù),中途有奇偶校驗錯誤或者緩沖區(qū)益處則等待超時,主程序輪詢并設定超時,超時后關(guān)中斷,開始解析數(shù)據(jù)。把命令結(jié)構(gòu)體直接指向緩沖區(qū)即可取得所需的數(shù)據(jù)。
現(xiàn)在的這種常用的通信協(xié)議都是幀頭同步,幀尾超時來處理,所以我常用上面的方法。不過需要注意避免主程序和中斷競爭數(shù)據(jù)。
網(wǎng)友評論:采用多機器通訊做同步最好了,無論小系統(tǒng),還是大系統(tǒng)都統(tǒng)統(tǒng)可以~
采用緩沖方式效率高啊,放在主函數(shù)中檢測,經(jīng)過實際應用,效果非常好,不是傻等啊.
網(wǎng)友評論:不懂將所有的活都放到串口中斷中去完成,會不會影響通訊質(zhì)量和占用系統(tǒng)資源阿請教
網(wǎng)友評論:SIGNAL(SIG_USART_RECV)
{
unsignedcharUARTemp;
UARTemp=UDR0;
UARTTimer=0x2;
if(!(UARTStatu&_BV(HeadIn)))
{
if(UARTemp!=HeadChar)
return;
else
{
UARTStatu|=_BV(HeadIn);
return;
}
}
if(!(UARTStatu&_BV(LengtIn)))
{
DataLong=UARTemp;
if(DataLong>MAXLength)
{
UARTStatu=0;
return;
}
UARTbuffer[0]=UARTemp;
ByteOfRead=0;
UARTStatu|=_BV(LengtIn);
return;
}
if(ByteOfRead<DataLong)
{
UARTbuffer[ByteOfRead+1]=UARTemp;
if(ByteOfRead<MAXLength)
ByteOfRead++;
if(ByteOfRead==DataLong)
{
if(UARTemp==Crc8fun(UARTbuffer,DataLong))
UARTStatu|=_BV(PackageOk);
else
UARTStatu=0;
}
}
}
網(wǎng)友評論:在大量數(shù)據(jù)交換通訊應用中,使用狀態(tài)機確實會拉低系統(tǒng)實時性,使用超時解析對于大量數(shù)據(jù)突發(fā)訪問會帶來溢出風險,視不同應用采取不同處理方法,實時性要求很強的通訊我一般在協(xié)議上下功夫,定義一個結(jié)束符,一旦判斷到結(jié)束符在中斷中解析完并發(fā)送消息到隊列中,保證不丟失消息,但需要評估解析需要消耗的CPU運行時間必須小于一個字節(jié)的接收時間。
網(wǎng)友評論:不錯,讀這樣的文章如飲甘露。
網(wǎng)友評論:“為了防止在處理數(shù)據(jù)過程中不受干擾,通常在處理接受數(shù)據(jù)前關(guān)閉中斷,處理完后再開。
static void UartInterruptService(void) interrupt 4
{
ES = 0;
RI = 0;
uart_process(SBUF);
ES=1;
}”
--------51的串口中斷函數(shù)里就不用再關(guān)閉串口中斷允許了吧,因為自己是不能搶占自己的。
“其實很多時候,對于通訊傳輸?shù)臄?shù)據(jù)處理才是關(guān)鍵,尤其對于設計通訊協(xié)議而言。筆者在剛剛做的一個系統(tǒng)上就碰到這樣的問題,當系統(tǒng)龐大了,資源十分有限的情況下,數(shù)據(jù)處理一旦占用資源太多,效率太低將導致系統(tǒng)崩潰而無法運行。”
---------這很有可能是沒有充足的堆棧空間造成的。一般至少得留下十幾個字節(jié)的RAM做堆棧。
網(wǎng)友評論:不錯,值得一看
網(wǎng)友評論:做個記號,以后用的時候方便!
網(wǎng)友評論:來一下!
網(wǎng)友評論:陳明計和周航慈有詳細的介紹,陳明計的在《嵌入式實時操作系統(tǒng)SMALL RTOS51原理及應用》,周航慈在那本介紹ucos的書里面
網(wǎng)友評論:呵呵,我做串口都怎么做的!
網(wǎng)友評論:if(RI)
{
RI=0;
//P22=0;
Incept_data=SBUF;
if(Incept_Status==0x00) //中斷處于接收起始幀階段
{
if(Incept_data==Frame_Head)
{ Incept_Status=0x01;//更新接收狀態(tài)標志為接收命令碼
//P23=0;
}
else
Incept_Status=0x00;
}
if(Incept_Status==0x01)
{
if(Incept_data==Frame_SetSimulate_Orde) //如果是設置幀命令,則立設置標志
{ //P34=0;
command_marker=Frame_SetSimulate_Orde;
Incept_Status=0x02;//更新接收狀態(tài)標志為接收幀長碼
}
else if(Incept_data==Frame_SetDigital_Orde)
{ command_marker=Frame_SetDigital_Orde;
Incept_Status=0x02; // P11=0;
}
else if(Incept_data==Frame_Gather_Orde) //實時數(shù)據(jù)采集幀命令,則立設置標志
{
command_marker=Frame_Gather_Orde;
Incept_Status=0x02;
}
syscheckData=0; //準備進行數(shù)據(jù)和校驗
return;
}
if(Incept_Status==0x02) //接收幀長
{// P35=0;
FrameLen=Incept_data;
syscheckData+=FrameLen; //更新累加和校驗信息
Incept_Status=0x03; //更新接收狀態(tài)標志為接收數(shù)據(jù)碼
return;
}
if(Incept_Status==0x03)
{
Incept_DataStream[RecCount]=Incept_data;
syscheckData+=Incept_DataStream[RecCount]; //更新累加和校驗信息
RecCount++;
if(command_marker==Frame_SetSimulate_Orde)
{
if(RecCount==FrameLen)
{ // P36=0;
RecCount=0;
Incept_Status=0x04; //更新接收狀態(tài)為接收校驗碼信息
}
}
if(command_marker==Frame_SetDigital_Orde)
{
if(RecCount==FrameLen)
{
RecCount=0;
Incept_Status=0x04; //更新接收狀態(tài)為接收校驗碼信息
}
}
if(command_marker==Frame_Gather_Orde)
{
if(RecCount==FrameLen)
{
RecCount=0;
Incept_Status=0x04 ; ////更新接收狀態(tài)為接收校驗碼信息
}
}
return;
}
if(Incept_Status==0x04)
{
checkData=Incept_data;
if(syscheckData!=checkData) //如校驗出錯
Incept_Status=0x00;
else
{Incept_Status=0x0537=0;} //P15=0;
syscheckData=0;
return;
}
if(Incept_Status==0x05)
{
if(Incept_data!=Frame_End)//錯誤幀標志
b_valid_Frame=0;
else
{ b_valid_Frame=1;} //P16=0;
Incept_Status=0x00;
}
}
}
網(wǎng)友評論:編程序每個人的思路都不同,感覺收發(fā)都在中斷里做會更好,這個程序進來就關(guān)中斷不是太好吧?
網(wǎng)友評論:挖墳?
網(wǎng)友評論:正遇到這個問題,不過我現(xiàn)在也是樓主這么做的,
網(wǎng)友評論:頂一個,學習一下!
網(wǎng)友評論:
我總感覺樓主的程序是垃圾程序的典范!
嗨!
看出來了,俺水平太差,看了半天我也沒有理解樓主是什么意思!
哪位給我指點指點!
其實很多時候,對于通訊傳輸?shù)臄?shù)據(jù)處理才是關(guān)鍵,尤其對于設計通訊協(xié)議而言。筆者在剛剛做的一個系統(tǒng)上就碰到這樣的問題,當系統(tǒng)龐大了,資源十分有限的情況下,數(shù)據(jù)處理一旦占用資源太多,效率太低將導致系統(tǒng)崩潰而無法運行。
到了這里,很多工程師可能會考慮開個大的緩沖區(qū)FIFO將接收到的數(shù)據(jù)保存在緩沖區(qū),然后對其進行解析、判斷進行下一步程序編寫,當然這在系統(tǒng)資源比較豐富的情況下是沒有問題的,ARM上采取的就是這樣的方式。但如何系統(tǒng)龐大呢,留給的資源缺乏則不行。這樣做的一個很大缺點必須是將數(shù)據(jù)幀接收完了才能夠判斷,降低了效率和運行速度。
其實還有另外的方式,可以采取在每接收一個字節(jié)就對其解析,解析完判斷轉(zhuǎn)到下一個狀態(tài),并將其中的有用數(shù)據(jù)存儲在相應的數(shù)據(jù)結(jié)構(gòu)中去,可以采取狀態(tài)機實現(xiàn)。
這段意思是不是這樣:樓主以為先判斷后接收效率高、省資源。 先接收后判斷就“降低了效率和運行速度”。
我就沒有看出到底哪里省資源了?
效率怎么就高了?
誰能指點一下?多謝!
網(wǎng)友評論:直接把處理函數(shù)寫在中斷處理函數(shù)中不利于代碼移植重用和維護,在大程序中這種架構(gòu)不應提倡
網(wǎng)友評論:NO,NO
要用FIFO
要用CRC
網(wǎng)友評論:小頂一下
網(wǎng)友評論:頂頂
網(wǎng)友評論:不錯,挺好的
網(wǎng)友評論:static void UartInterruptService(void) interrupt 4
{
ES = 0;
RI = 0;
uart_process(SBUF);
ES=1;
}
LZ的開篇確實值得質(zhì)疑,上面66樓網(wǎng)友講的正根:“ES不可能自己搶占自己。”串口中斷
硬件內(nèi)部已經(jīng)設計好了這種關(guān)系:(Intel硬件設計師是干什么的?如果我是硬件設計師,我怎么會把這種關(guān)系留給軟件設計員?)——首次ES中斷發(fā)生并進入ISR之后,必然阻斷后面的同級中斷(如若有的話)并使其進入中斷隊列排序等待,只有當前ISR完成,RETI 退出串口中斷程序之后,此前紀錄的、位于隊列中的ES=1才會再次觸發(fā)串口中斷。所以,LZ的開篇程序是不是一開始就沒寫好?后面.....
static void UartInterruptService(void) interrupt 4
{
//ES = 0;
RI = 0;
uart_process(SBUF);
// ES=1;
}
其實我最近一直不好處理的問題是:你在一幀數(shù)據(jù)接收過程中,如果半中間突然應該來的數(shù)據(jù)中止不來了,怎么辦?你的while(RI !=1) ; RI=0;該如何處理才不至于死等?才可能盡快退出當前狀態(tài)?——你如何盡快感知到通信失效?我知道是利用T0超時,可是怎么寫才簡單?接收100字節(jié)一幀,就要設置100次T0初值?太呆板了吧。(當然可以用宏指令)如若每接收到第50字節(jié)時就發(fā)生中止或錯誤接收現(xiàn)象,只好一幀重新來過,以前的都白干!
如何才能提高通信系統(tǒng)可靠性?
網(wǎng)友評論:mark下串口通信,最近正在搞這個。