轉載自:http://blog.sina.com.cn/s/blog_4aca3d5d01011a1d.html
1. FreeModbus協議分析
協議必須首先調用初始化功能eMBinit()函數。後調用eMBEnable(),最後,在循環體或者單獨一個任務中調用eMBPoll()函數。
2. 應用層協議
2.1. 系統的啓動
2.1.1. eMBInit()函數的源碼分析
以RTU方式爲例,首先,檢查調用的地址是否合法。如不合法,返回錯誤。如果合法則繼續執行,
首先,針對RTU方式還是ASCII方式,選擇不同的編譯模塊。
對需要調用的函數指針進行復制。如果移植需要改變其他用途,則要修改相應的指針,包括如下賦值:
pvMBFrameStartCur = eMBRTUStart;
pvMBFrameStopCur = eMBRTUStop;
peMBFrameSendCur = eMBRTUSend;
peMBFrameReceiveCur = eMBRTUReceive;
pvMBFrameCloseCur = MB_PORT_HAS_CLOSE ? vMBPortClose : NULL;
pxMBFrameCBByteReceived = xMBRTUReceiveFSM;
pxMBFrameCBTransmitterEmpty = xMBRTUTransmitFSM;
pxMBPortCBTimerExpired = xMBRTUTimerT35Expired;
然後調用eStatus = eMBRTUInit( ucMBAddress, ucPort, ulBaudRate, eParity );具體初始化通訊端口。
2.1.2. eMBRTUInit
eMBRTUInit這個函數主要幹兩件事:
第一, 初始化串口:
if( xMBPortSerialInit( ucPort, ulBaudRate, 8, eParity ) != TRUE )
{
eStatus = MB_EPORTERR;
}
這個函數在portserial.c中,需要用戶在移植的時候根據自己的處理器編寫。
第二, 初始化計時器:首先要根據波特率計算一下是3.5~5.0個字節週期的時間,然後再調用xMBPortTimersInit( ( USHORT ) usTimerT35_50us ),初始化計時器。這個函數在porttimer.c中,需要用戶在移植的時候根據自己的處理器編寫。
2.1.3. eMBEnable源碼分析
首先,看看Modbus功能是否是被關閉的,如果不是被關閉(可能是沒有被初始化或者已經打開),就返回錯誤。
如果是disable狀態,就幹下面兩件事:
l 調用pvMBFrameStartCur()。由於這是個函數指針,在模塊eMBInit中,指向了eMBRTUStart函數
n 在源代碼中有這樣一段註釋:,意思是,首先設置成STATE_RX_INIT,然後打開計時器,等待t3.5以後,進入STATE_RX_IDLE狀態。
n 看源代碼中,首先有設置Receiver的狀態,後調用vMBPortSerialEnable,設置接收狀態,然後打開定時器。
n 當定時器中斷後,自動調用中斷服務程序,在中斷服務程序中,只調用了pxMBPortCBTimerExpired,而這是一個函數指針,在RTU方式初始化時,被指向了xMBRTUTimerT35Expired()函數。
n xMBRTUTimerT35Expired函數在mbrtu.c中,在這裏,我們只看第一種方式,就是進入初始化狀態,在t35時間以後,只調用了一個xNeedPoll = xMBPortEventPost( EV_READY );
n xMBPortEventPost函數就是在事件隊列里加了一個EV_RDY事件。
l 然後,將eMB狀態改爲使能狀態,
l 初始化結束。
2.2. 總線偵聽eMBPoll()
首先,判斷系統是否被使能,如果沒有,則返回錯誤值。
然後,檢查是否有事件發生,如果有,則根據不同類型的事件響應:
l 如果是EV_RDY,表示系統剛剛進入偵聽狀態,則什麼都不做;
l 如果狀態爲EV_FRAME_RECEIVED,也就是接收到完整的幀,做下面兩件事情:
n 調用eStatus=peMBFrameReceiveCur( &ucRcvAddress, &ucMBFrame, &usLength )。這是一個函數指針,在eMBInit中,被初始化指向eMBRTUReceive。
n eMBRTUReceive這個函數首先校驗幀的長度和CRC,然後從協議中解析出地址、數據和長度。
n 然後檢查地址,如果是廣播地址或者是本機地址,就調用xMBPortEventPost( EV-EXECUTE),將接收器的狀態更改爲EV_EXECUTE。
l 如果狀態爲EV_EXECUTE,就在函數列表中檢查,有沒有與命令字段相符合的函數來解析相應則執行該函數,否則返回非法功能代碼。
2.3. 數據發送
發送數據通過指針eMBRTUSend,調用eMBRTUSend函數。
2.3.1. eMBRTUSend函數
這個函數的作用就是打包,將數據打包成幀。
l 首先,檢查接收狀態。因爲MODBUS是基於RS-485半雙工通訊,所以當正在接收數據時,不發送該幀。
l 如果總線空,就將數據打包,將地址和CRC加入數據幀
l 將總線狀態改爲發送。
2.4. 功能註冊
l 對於指定的功能代碼,需要一個功能回調函數來處理,格式如下。
eMBException eMXXXXXX ( UCHAR * pucFrame, USHORT * usLen )
l 需要通過函數eMBRegisterCB(功能代碼,函數名)加到處理代碼中。具體源碼分析從略。
2.4.1. prvvUARTTxReadyISR()
總線狀態改爲發送後,會在發送緩衝時,自動調用prvvUARTTxReadyISR()中斷服務程序。prvvUARTTxReadyISR()只調用了一個函數,就是pxMBFrameCBTransmitterEmpty ()。
2.4.2. pxMBFrameCBByteReceived ()
pxMBFrameCBTransmitterEmpty ()是一個指針,指向了xMBRTUTransmitFSM函數。
3. 數據鏈路層協議
數據鏈路層是最基本的打包部分,將數據打包成幀,送到應用層。在數據鏈路層協議中,使用中斷方式來接受。那麼每次接收到字符就自動調用接收字符的ISR程序。按照規定,應該將中斷服務程序安裝給prvvUARTRxISR(void)函數。實際上這個函數只調用了一個函數:
pxMBFrameCBByteReceived(),這個指針調用了xMBRTUReceiveFSM函數。
3.1. xMBRTUReceiveFSM()函數
函數首先檢查是不是處於發送狀態。如果處於發送狀態,直接退出。
l 首先調用xMBPortSerialGetByte( ( CHAR * ) & ucByte ),獲取從串口讀到的字符。
l 然後檢查接受狀態:
n 如果是錯誤狀態或者處於初始化狀態,那麼直接等待,錯過該幀。
n 如果是STATE_RX_IDLE空閒狀態,則將指針重置,將收到的第一個字節存儲到緩衝區,並將狀態改爲STATE_RX_RCV狀態。
n 如果處於接收狀態,就判斷,如果緩衝區未滿,就將收到的字節放入緩衝區,否則改爲錯誤狀態。
l 不管在任何狀態,最後都開啓了t35計時器。在t35結束的時候,通過指針調用了xMBRTUTimerT35Expired()函數。
l xMBRTUTimerT35Expired()函數檢查狀態,如果是接收狀態那就表明,已經有t35這麼長的時間裏,沒有收到任新字節,當前的幀結束。在隊列裏增加一個EV_FRAME_RECEIVED事件。
l 如果是錯誤狀態,什麼都不做。
l 然後關掉計時器,將狀態改爲空閒。
3.2. xMBRTUTransmitFSM()函數
xMBRTUTransmitFSM首先判斷總線是否忙,如果忙,則終止。如果不忙,則繼續,根據發送狀態變量:
l 如果當前爲STATE_TX_IDLE(空閒)狀態,則打開端口發送
l 如果當前狀態爲STATE_TX_XMIT,則進一步判斷髮送隊列是否爲空,
n 如果不空,則發送下一個字符
n 如果空,說明發送完成,關閉發送端口,改爲偵聽,並將狀態改爲空閒。
4. 傳輸控制
除了傳輸控制以外,還有傳輸控制的若干函數。通過下面幾個指針來調用:
pvMBFrameStopCur()
pvMBFrameCloseCur()
4.1. pvMBFrameStopCur()函數
pvMBFrameStopCur是一個函數指針,在RTU方式下,它指向eMBRTUStop()函數。該函數做下面幾件事情:
l 關閉偵聽和發送
l 關閉定時器
4.2. pvMBFrameCloseCur()函數
這個指針指向一個叫做vMBPortClose()的函數,該函數目前只有在mbport.h中的聲明,而沒有實現。需要等到後面的版本再實現。