協定介紹
FreeMODBUS 提供了RTU/ASCII 傳輸模式及TCP協定支持。FreeModbus遵循BSD許可證,這意味著用戶可以將FreeModbus套用於商業環境中。版本FreeModbus-V1.5提供如下的功能支持:
表1 FreeModbus-V1.5功能支持
代碼 | 描述 | 是否支持 | 備註 |
Master | 主機 | 否 | |
Slave | 從機 | 是 | |
MB_RTU | RTU模式 | 是 | |
MB_ASCII | ASCII模式 | 是 | |
MB_TCP | TCP模式 | 是 | |
0x01 | 讀線圈 | 是 | |
0x02 | 讀離散輸入 | 是 | |
0x03 | 讀保持暫存器 | 是 | |
0x04 | 讀輸入暫存器 | 是 | |
0x05 | 寫單個線圈 | 是 | |
0x06 | 寫單個暫存器 | 是 | |
0x07 | 讀異常狀態 | 否 | |
0x08 | 診斷 | 否 | |
0x0B | 獲取事件計數器 | 否 | |
0x0C | 獲取事件記錄 | 否 | |
0x0F | 寫多個線圈 | 是 | |
0x10 | 寫多個暫存器 | 是 | |
0x11 | 報告從機ID | 是 | 協定與文檔不一致 |
0x14 | 讀檔案記錄 | 否 | |
0x15 | 寫檔案記錄 | 否 | |
0x16 | 禁止寫暫存器 | 否 | |
0x17 | 讀/寫多個暫存器 | 是 | |
0x18 | 寫FIFO | 否 | |
0x2B | 封裝接口傳輸 | 否 | |
0x2B/0x0D | CANopen參考請求與應答 | 否 | |
0x2B/0x0E | 讀設備身份表示 | 否 |
硬體需求
FreeModbus協定對硬體的需求非常少——基本上任何具有串列接口,並且有一些能夠容納modbus數據幀的RAM的微控制器都足夠了。
u 一個異步串列接口,能夠支持接收緩衝區滿和傳送快取區空中斷。
u 一個能夠產生RTU傳輸所需要的t3.5字元逾時定時器的時鐘。
對於軟體部分,僅僅需要一個簡單的事件佇列。在使用作業系統的處理器上,可通過單獨定義一個任務完成Modbus時間的查詢。小點的微控制器往往不允許使用作業系統,在那種情況下,可以使用一個全局變數來實現該事件佇列(Atmel AVR 移植使用這種方式實現)。
實際的存儲器需求決定於所使用的Modbus模組的多少。下表列出了所支持的功能編譯後所需要的存儲器。ARM是使用GNUARM編譯器3.4.4使用-O1選項得到的。AVR項數值是使用WinAVR編譯器3.4.5使用-Os選項編譯得到的。
表2 FreeModbus對硬體的需求
Module | ARM Code | ARM RAM (static) | AVR Code | AVR RAM (static) |
Modbus RTU (Required) | 1132Byte | 272Byte | 1456Byte | 266Byte |
Modbus ASCII (Optional) | 1612Byte | 28Byte | 1222Byte | 16Byte |
Modbus Functions [1] | 1180Byte | 34Byte | 1602Byte | 34Byte |
Modbus Core (Required) | 924Byte | 180Byte | 608Byte | 75Byte |
Porting Layer (Required [2]) | 1756Byte | 16Byte | 704Byte | 7Byte |
Totals | 7304Byte | 530Byte | 5592Byte | 398Byte |
[1] 實際大小決定於可支持的Modbus功能碼的多少。功能碼可以在頭檔案mbconfig.h中進行配置。
[2] 決定於硬體。
移植
1、 物理層接口檔案的修改
在物理層,用戶只需完成串列口及逾時定時器的配置即可。具體應修改接口檔案portserial.c及porttimer.c。
u portserial.c中函式的修改:
1) void vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
此函式的功能為設定串口狀態。有兩個參數:xRxEnable及xTxEnable。當xRxEnable為真時,應使能串口接收及接收中斷。在RS485通訊系統中,還要注意將RS485接口晶片設為接收使能狀態;當xTxEnable為真時,應使能串口傳送及傳送中斷。在RS485通訊系統中,還要注意將RS485接口晶片設為傳送使能狀態。
2) void vMBPortClose( void )
此函式的功能是關閉Modbus通訊連線埠,具體的,應在此函式中關閉通訊連線埠的傳送使能及接收使能。
3) BOOL xMBPortSerialInit(UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity)
此函式的功能是初始化串列通訊連線埠。有四個參數:ucPORT、ulBaudRate、ucDataBits及eParity。參數ucPORT可以忽略;參數ulBaudRate是通訊連線埠的波特率,應根據此數值設定所使用硬體連線埠的波特率;參數ucDataBits為通訊時所使用的數據位寬,注意,若使用RTU模式,則有ucDataBits=8,若使用ASCII模式,則有ucDataBits=7,應根據此參數設定所使用硬體連線埠的數據位寬;eParity為校驗方式,eParity=MB_PAR_NONE為無校驗,此時硬體連線埠應設定為無校驗方式及兩個停止位,eParity=MB_PAR_ODD為奇校驗,此時硬體連線埠應設定為奇校驗方式及一個停止位,eParity= MB_PAR_EVEN為偶校驗,此時硬體連線埠應設定為偶校驗方式及一個停止位。函式返回值務必為TRUE。
4) BOOL xMBPortSerialPutByte(CHAR ucByte)
此函式的功能為通訊連線埠傳送一位元組數據。參數為:ucByte,待傳送的數據。應在此函式中編寫傳送一位元組數據的函式。注意,由於使用的是中斷髮送,故只需將數據放到傳送暫存器即可。函式返回值務必為TRUE。
5) BOOL xMBPortSerialGetByte( CHAR * pucByte )
此函式的功能為通訊連線埠接收一位元組數據。參數為:* pucByte,接收到的數據。應在此函式中編寫接收的函式。注意,由於使用的是中斷接收,故只需將接收暫存器的值放到* pucByte即可。函式返回值務必為TRUE。
6) void prvvUARTTxReadyISR(void)
傳送中斷函式。此函式無需修改。只需在用戶的傳送中斷函式中調用此函式即可,同時,用戶應在調用此函式後,清除傳送中斷標誌位。
7) void prvvUARTRxISR(void)
傳送中斷函式。此函式無需修改。只需在用戶的接收中斷函式中調用此函式即可,同時,用戶應在調用此函式後,清除接收中斷標誌位。
u porttimer.c中函式的修改:
1) BOOL xMBPortTimersInit( USHORT usTim1Timerout50us )
此函式的功能為初始化逾時定時器。參數為:usTim1Timerout50us,50us的個數。用戶應根據所使用的硬體初始化逾時定時器,使之能產生中斷時間為usTim1Timerout50us*50us的中斷。函式返回值務必為TRUE。
2) void vMBPortTimersEnable( )
此函式的功能為使能逾時定時器。用戶需在此函式中清除中斷標誌位、清零定時器計數值,並重新使能定時器中斷。
3) void vMBPortTimersDisable( )
此函式的功能為關閉逾時定時器。用戶需在此函式中清零定時器計數值,並關閉定時器中斷。
4) void TIMERExpiredISR( void )
定時器中斷函式。此函式無需修改。只需在用戶的定時器中斷中調用此函式即可,同時,用戶應在調用此函式後清除中斷標誌位。
2、 套用層回函式的修改
在套用層,用戶需要定義所需要使用的暫存器,並修改對應的回函式。回函式有如下幾個:
1) eMBErrorCode eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )
輸入暫存器回函式。* pucRegBuffer為要添加到協定中的數據,usAddress為輸入暫存器地址,usNRegs為要讀取暫存器的個數。用戶應根據要訪問的暫存器地址usAddress將相應輸入暫存器的值按順序添加到pucRegBuffer中。
2) eMBErrorCode eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode )
保持暫存器回函式。* pucRegBuffer為要協定中的數據,usAddress為輸入暫存器地址,usNRegs為訪問暫存器的個數,eMode為訪問類型(MB_REG_READ為讀保持暫存器,MB_REG_WRITE為寫保持暫存器)。用戶應根據要訪問的暫存器地址usAddress將相應輸入暫存器的值按順序添加到pucRegBuffer中,或將協定中的數據根據要訪問的暫存器地址usAddress放到相應保持暫存器中。
3) eMBErrorCode eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils, eMBRegisterMode eMode )
讀寫線圈回函式。* pucRegBuffer為要添加到協定中的數據,usAddress為線圈地址,usNCoils為要訪問線圈的個數,eMode為訪問類型(MB_REG_READ為讀線圈狀態,MB_REG_WRITE為寫線圈)。用戶應根據要訪問的線圈地址usAddress將相應線圈的值按順序添加到pucRegBuffer中,或將協定中的數據根據要訪問的線圈地址usAddress放到相應線圈中。
4) eMBErrorCode eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
讀離散線圈回函式。* pucRegBuffer為要添加到協定中的數據,usAddress為線圈地址,usNDiscrete為要訪問線圈的個數。用戶應根據要訪問的線圈地址usAddress將相應線圈的值按順序添加到pucRegBuffer中。
3、 套用層初始化及協定訪問
用戶只需在主函式中調用協定初始化代碼,及訊息處理函式即可。需用戶調用的函式有如下幾個:
1) eMBErrorCode eMBInit( eMBMode eMode, UCHAR ucSlaveAddress, UCHAR ucPort, ULONG ulBaudRate, eMBParity eParity )
協定初始化函式。eMode為所要使用的模式,用戶可選MB_RTU(RTU模式)、MB_ASCII(ASCII模式)或MB_TCP(TCP模式);ucSlaveAddress為從機地址,用戶根據需要,取值為1~247(0為廣播地址,248~255協定保留);ulBaudRate為通信波特率,用戶根據需要選用,但務必使主機能支持此波特率;eParity為校驗方式,用戶根據需要選用,但務必使主機能支持此校驗方式。
2) eMBErrorCode eMBSetSlaveID( UCHAR ucSlaveID, BOOL xIsRunning, UCHAR const *pucAdditional, USHORT usAdditionalLen )
從機ID設定函式。注意,ID表示的是設備的類型,不同於ucSlaveAddress(從機地址)。對同一通訊系統中,可以有相同的ucSlaveID,但不可以有相同的ucSlaveAddress。ucSlaveID為一位元組的設備ID號;xIsRunning為設備的運行狀態,0xFF為運行,0x00為停止;* pucAdditional為設備的附加描述,根據需要添加;usAdditionalLen為附加描述的長度(按位元組計算)。此函式不是必須調用的。但當一個Modbus通訊系統中有不同種設備時,應調用此函式添加對應設備的描述。
3) eMBErrorCode eMBPoll( void )
輪詢事件查詢處理函式。用戶需在主循環中調用此函式。對於使用作業系統的程式,應單獨創建一個任務,使作業系統能周期調用此函式。
初始化及運行
FreeModbus是基於訊息佇列的協定。協定通過檢測相應的訊息來完成對應功能。協定棧的初始化及運行流程如下:
1) 首先調用eMBErrorCode eMBInit( eMBMode eMode, UCHAR ucSlaveAddress, UCHAR ucPort, ULONG ulBaudRate, eMBParity eParity )完成物理層設備的初始化,主要包括:
BOOL xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )串口初始化,設定波特率、數據位數、校驗方式;BOOL xMBPortTimersInit( USHORT usTim1Timerout50us )定時器初始化,設定T35定時所需要的定時器常數。
2) 調用(此處非必需)eMBErrorCode eMBSetSlaveID( UCHAR ucSlaveID, BOOL xIsRunning,UCHAR const *pucAdditional, USHORT usAdditionalLen )指定設備ID。
3) 調用eMBErrorCode eMBEnable(void)使能協定棧,主要包括:static pvMBFrameStart pvMBFrameStartCur(函式指針)協定棧開始,將eRcvState設為STATE_RX_INIT狀態,調用void vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )使能接收,調用void vMBPortTimersEnable( )使能逾時定時器。
4) 在3中使能了逾時定時器,故經過T35時間後,發生第一次逾時中斷,在中斷中,向協定棧傳送訊息EV_READY(Startup finished),並調用void vMBPortTimersDisable( )關閉逾時定時器,同時將eRcvState設為STATE_RX_IDLE。此時,協定棧可以接收串口數據。注意,此處首先啟用一次逾時定時器是因為初始化完成時,串口有可能已經有數據,因為無法判斷第一個數據是請求的開始,故等待T35,接收下一幀請求。
5) 此時,主函式調用eMBErrorCode eMBPoll( void )檢測事件。
6) 若發生串口接收中斷,且eRcvState為STATE_RX_IDLE(4中已將eRcvState設為STATE_RX_IDLE),則向接收快取中存入接收到的字元,同時將eRcvState設為STATE_RX_RCV狀態,並清零逾時定時器。在下一個數據來到時,不斷將數據存入接收快取,並清零逾時定時器。
7) 如果沒有接收完成,則不可能發生逾時中斷。發生逾時中斷,說明T35時間內未收到新的串口數據,根據Modbus協定的規定,這指示著一幀請求數據接收完成。在中斷中,向協定棧傳送訊息EV_FRAME_RECEIVED(Frame received),等待協定棧處理此訊息。
8) 主函式調用eMBErrorCode eMBPoll( void )檢測到事件EV_FRAME_RECEIVED後,調用static peMBFrameReceive peMBFrameReceiveCur簡單判斷請求幀數據,並向協定棧傳送訊息EV_EXECUTE(Execute function)。
9) 主函式調用eMBErrorCode eMBPoll( void )檢測到事件EV_EXECUTE後,根據相應的請求代碼查找處理該功能的函式指針來處理該功能。若不是廣播訊息,則調用static peMBFrameSend peMBFrameSendCur傳送回復訊息,在此函式中,只把要回復的數據複製到了串口快取中,同時將eSndState設為STATE_TX_XMIT(Transmitter is in transfer state),並通過調用void vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )使能傳送中斷。注意,傳送中斷使能後,由於串口傳送暫存器本來就是空的,故在使能後將進入傳送中斷中。
10) 傳送中斷中,且eSndState為STATE_TX_XMIT(9中已將eSndState設為STATE_TX_XMIT),則將串口快取中的數據傳送出去,同時不斷對傳送字元個數統計,當傳送完成後,向協定棧傳送訊息EV_FRAME_SENT(Frame sent)。
11) 主函式調用eMBErrorCode eMBPoll( void )檢測到事件EV_FRAME_SENT後,不處理此訊息。
12) 當串口接收到數據後,協定棧將重複6-11處理訊息。
理解
1. 關於mbrtu.c檔案的理解
u 宏定義與變數
mbrtu.c檔案中定義了RTU模式下的宏定義、全局變數與功能函式。所包含的宏定義與全局變數定義如下:
/* ----------------------- Defines ------------------------------------------*/
#define MB_SER_PDU_SIZE_MIN 4 /*!< Minimum size of a Modbus RTU frame. */
#define MB_SER_PDU_SIZE_MAX 256 /*!< Maximum size of a Modbus RTU frame. */
#define MB_SER_PDU_SIZE_CRC 2 /*!< Size of CRC field in PDU. */
#define MB_SER_PDU_ADDR_OFF 0 /*!< Offset of slave address in Ser-PDU. */
#define MB_SER_PDU_PDU_OFF 1 /*!< Offset of Modbus-PDU in Ser-PDU. */
/* ----------------------- Type definitions ---------------------------------*/
typedef enum
{
STATE_RX_INIT, /*!< Receiver is in initial state. */
STATE_RX_IDLE, /*!< Receiver is in idle state. */
STATE_RX_RCV, /*!< Frame is beeing received. */
STATE_RX_ERROR /*!< If the frame is invalid. */
} eMBRcvState;
typedef enum
{
STATE_TX_IDLE, /*!< Transmitter is in idle state. */
STATE_TX_XMIT /*!< Transmitter is in transfer state. */
} eMBSndState;
/* ----------------------- Static variables ---------------------------------*/
static volatile eMBSndState eSndState;
static volatile eMBRcvState eRcvState;
volatile UCHAR ucRTUBuf[MB_SER_PDU_SIZE_MAX];
static volatile UCHAR *pucSndBufferCur;
static volatile USHORT usSndBufferCount;
static volatile USHORT usRcvBufferPos;
首先在宏定義中,指明了該模式下所支持的最小請求幀長度為4(1位元組地址+1位元組命令+2位元組校驗),最大請求幀長度為256,CRC為兩位元組,地址為第一位元組,PDU開始於第二位元組。
在全局變數中,只定義了一個串口快取數組ucRTUBuf[MB_SER_PDU_SIZE_MAX]。由於傳送與接收不是同步的,故可採用該快取數組實現Modbus協定。在接收過程中,將所接收到的數據直接存放於快取ucRTUBuf中,在傳送過程中,通過指針*pucSndBufferCur來訪問該數組。
u eMBErrorCode eMBRTUInit( UCHAR ucSlaveAddress, UCHAR ucPort, ULONG ulBaudRate, eMBParity eParity )
此函式為RTU模式的初始化函式。此函式中判斷串列口初始化是否成功(通過判斷串列口初始化函式的返回值實現。當然,查看返回值必然先調用該函式,從而完成連線埠初始化),如果成功,則根據波特率計算T35,初始化逾時定時器。
u void eMBRTUStart( void )
此函式為RTU模式開始函式。函式主要功能是,將接收狀態eRcvState設為STATE_RX_INIT(Receiver is in initial state),使能接收同時關閉傳送,使能逾時定時器。
u void eMBRTUStop( void )
此函式為RTU模式終止函式。函式主要功能是,關閉接收與傳送,關閉逾時定時器。
u eMBErrorCode eMBRTUReceive( UCHAR * pucRcvAddress, UCHAR ** pucFrame, USHORT * pusLength )
此函式為RTU接收數據幀信息提取函式。函式主要功能是,將接收幀(存放於快取)的地址指針賦給指針變數pucRcvAddress,將PDU編碼首地址賦給指針* pucFrame,將PDU長度地址賦給指針變數pusLength。使用指針訪問快取數組,而不是額外開闢快取存放幀信息,大大減少了記憶體的開支。
u eMBErrorCode eMBRTUSend( UCHAR ucSlaveAddress, const UCHAR * pucFrame, USHORT usLength )
此函式為RTU回復幀信息組織函式。函式的功能是,此函式首先使傳送內容指針pucSndBufferCur指向pucFrame之前的一個地址,並將該地址內容填充為ucSlaveAddress,並使用直接訪問方式向快取數組ucRTUBuf的相應地址記憶體入CRC校驗值。注意,此函式中,對ucRTUBuf的訪問既有間接方式(指針pucSndBufferCur與pucFrame),又有直接方式(直接向相應地址內寫值),比較難理解。
回復幀組織完後,將傳送狀態eSndState設為STATE_TX_XMIT(Transmitter is in transfer state),並禁止接收使能傳送。傳送一旦使能,就會進入傳送中斷,完成相應字元的傳送。
u BOOL xMBRTUReceiveFSM( void )
此函式描述了一個接收狀態機,供接收中斷調用。狀態機中,首先完成串口接收暫存器讀取,然後判斷相應接收狀態eRcvState,實現接收。在STATE_RX_INIT狀態,重置逾時定時器,等待逾時中斷(逾時中斷會把eRcvState設為STATE_RX_IDLE);在STATE_RX_ERROR狀態,同樣會重置逾時定時器等待逾時中斷;在STATE_RX_IDLE狀態,會將接收字元個數置零,同時向快取數組ucRTUBuf中存入接收到的字元,跳入狀態STATE_RX_RCV,並使重置逾時定時器;在STATE_RX_RCV狀態,不斷將接收到的字元存入快取,並統計接收計數,重置逾時定時器,接收計數大於幀最大長度時,會跳入STATE_RX_ERROR狀態。
在任何一處發生逾時中斷,都會將狀態eRcvState置為STATE_RX_IDLE。在接收過程(STATE_RX_RCV)中,發生逾時中斷,指示著一幀數據接收完成。
接收狀態機如圖1所示:
圖1 接收狀態機圖
u BOOL xMBRTUTransmitFSM( void )
此函式描述了一個接收狀態機,供傳送中斷調用。狀態機中,判斷相應傳送狀態eSndState,實現傳送。在STATE_TX_IDLE狀態,使能接收關閉傳送;在STATE_TX_XMIT狀態,調用底層串口傳送函式將快取中的字元傳送出去,並使傳送指針加1,待傳送字元數減1,待傳送數為0時,將向系統傳送事件EV_FRAME_SENT(Frame sent),同時使能接收關閉傳送,並轉向STATE_TX_IDLE狀態。
傳送狀態機如圖2所示:
圖2 傳送狀態機圖
u BOOL xMBRTUTimerT35Expired( void )
此函式描述了發生逾時中斷時應處理的事務,供逾時中斷調用。通過判讀接收狀態eRcvState來決定要處理的事務,思想上有點像摩爾類型的FSM的輸出邏輯。若中斷髮生於STATE_RX_INIT,則向系統傳送事件EV_READY(Startup finished);若中斷髮生於STATE_RX_RCV,則向系統傳送事件EV_FRAME_RECEIVED(Frame received);若中斷髮生於STATE_RX_ERROR,則跳出,不執行。在每個執行分支結束後,均關閉逾時定時器,並將eRcvState轉為STATE_RX_IDLE。當然,這兒不像FSM的輸出邏輯。
2. 關於mb.c檔案的理解
u 宏定義與變數
mb.c檔案中定義了一系列的宏定義、函式指針及全局變數,並使用優先編譯指令預編譯一些程式代碼。定義與優先編譯部分如下:
#if MB_RTU_ENABLED == 1
#include "mbrtu.h"
#endif
#if MB_ASCII_ENABLED == 1
#include "mbascii.h"
#endif
#if MB_TCP_ENABLED == 1
#include "mbtcp.h"
#endif
#ifndef MB_PORT_HAS_CLOSE
#define MB_PORT_HAS_CLOSE 0
#endif
/* ----------------------- Static variables ---------------------------------*/
static UCHAR ucMBAddress;
static eMBMode eMBCurrentMode;
static enum
{
STATE_ENABLED,
STATE_DISABLED,
STATE_NOT_INITIALIZED
} eMBState = STATE_NOT_INITIALIZED;
/* Functions pointer which are initialized in eMBInit( ). Depending on the
* mode (RTU or ASCII) the are set to the correct implementations.
*/
static peMBFrameSend peMBFrameSendCur;
static pvMBFrameStart pvMBFrameStartCur;
static pvMBFrameStop pvMBFrameStopCur;
static peMBFrameReceive peMBFrameReceiveCur;
static pvMBFrameClose pvMBFrameCloseCur;
/* Callback functions required by the porting layer. They are called when
* an external event has happend which includes a timeout or the reception
* or transmission of a character.
*/
BOOL( *pxMBFrameCBByteReceived ) ( void );
BOOL( *pxMBFrameCBTransmitterEmpty ) ( void );
BOOL( *pxMBPortCBTimerExpired ) ( void );
BOOL( *pxMBFrameCBReceiveFSMCur ) ( void );
BOOL( *pxMBFrameCBTransmitFSMCur ) ( void );
/* An array of Modbus functions handlers which associates Modbus function
* codes with implementing functions.
*/
static xMBFunctionHandler xFuncHandlers[MB_FUNC_HANDLERS_MAX] = {
#if MB_FUNC_OTHER_REP_SLAVEID_ENABLED > 0
{MB_FUNC_OTHER_REPORT_SLAVEID, eMBFuncReportSlaveID},
#endif
#if MB_FUNC_READ_INPUT_ENABLED > 0
{MB_FUNC_READ_INPUT_REGISTER, eMBFuncReadInputRegister},
#endif
#if MB_FUNC_READ_HOLDING_ENABLED > 0
{MB_FUNC_READ_HOLDING_REGISTER, eMBFuncReadHoldingRegister},
#endif
#if MB_FUNC_WRITE_MULTIPLE_HOLDING_ENABLED > 0
{MB_FUNC_WRITE_MULTIPLE_REGISTERS, eMBFuncWriteMultipleHoldingRegister},
#endif
#if MB_FUNC_WRITE_HOLDING_ENABLED > 0
{MB_FUNC_WRITE_REGISTER, eMBFuncWriteHoldingRegister},
#endif
#if MB_FUNC_READWRITE_HOLDING_ENABLED > 0
{MB_FUNC_READWRITE_MULTIPLE_REGISTERS, eMBFuncReadWriteMultipleHoldingRegister},
#endif
#if MB_FUNC_READ_COILS_ENABLED > 0
{MB_FUNC_READ_COILS, eMBFuncReadCoils},
#endif
#if MB_FUNC_WRITE_COIL_ENABLED > 0
{MB_FUNC_WRITE_SINGLE_COIL, eMBFuncWriteCoil},
#endif
#if MB_FUNC_WRITE_MULTIPLE_COILS_ENABLED > 0
{MB_FUNC_WRITE_MULTIPLE_COILS, eMBFuncWriteMultipleCoils},
#endif
#if MB_FUNC_READ_DISCRETE_INPUTS_ENABLED > 0
{MB_FUNC_READ_DISCRETE_INPUTS, eMBFuncReadDiscreteInputs},
#endif
};
頭檔案使用優先編譯指令,根據Modbus的配置檔案中的相應宏開關,預編譯所需的頭檔案,從而減小協定代碼量。
全局變數ucMBAddress與eMBCurrentMode分別表示從機地址與當前所選用的Modbus模式。
接下來定義了一系列的函式指針。在初始化函式中,會根據當前所選用的Modbus模式使這些函式指針指向相應模式下的功能函式。
關於功能代碼與功能函式,寫的特別巧妙:首先定義xMBFunctionHandler類型的結構體數組xFuncHandlers,對於數組中的每一個元素,都可看成一個結構體。xMBFunctionHandler結構體類型在檔案mbproto.h中定義如下:
typedef struct
{
UCHAR ucFunctionCode;
pxMBFunctionHandler pxHandler;
} xMBFunctionHandler;
pxMBFunctionHandler描述的是一種函式指針類型,在mbproto.h中定義如下:
typedef eMBException(*pxMBFunctionHandler) (UCHAR *pucFrame,USHORT *pusLength);
故xFuncHandlers中的每一個元素都具有兩個成員:ucFunctionCode(功能碼)與pxHandler(功能函式指針)。通過相應的宏開關,可選擇預編譯相應的功能函式(宏開關在檔案mbconfig.h中定義)。
u eMBErrorCode eMBInit( eMBMode eMode, UCHAR ucSlaveAddress, UCHAR ucPort, ULONG ulBaudRate, eMBParity eParity )
此函式為Modbus協定初始化函式。函式首先判斷從機地址ucSlaveAddress,若為廣播地址,或協定保留地址,或配置檔案中未規定的地址,均會使該函式返回一個錯誤MB_EINVAL(illegal argument)。若地址合法,則會將該地址賦給全局變數ucMBAddress,同時根據所選用的模式eMode(MB_RTU、MB_ASCII或MB_TCP)初始化相應的函式指針。
以RTU模式為例,pvMBFrameStartCur將指向協定開始函式void eMBRTUStart( void );pvMBFrameStopCur將指向協定終止函式eMBErrorCode eMBRTUSend( UCHAR ucSlaveAddress, const UCHAR * pucFrame, USHORT usLength );peMBFrameReceiveCur將指向接收幀信息提取函式eMBErrorCode eMBRTUReceive( UCHAR * pucRcvAddress, UCHAR ** pucFrame, USHORT * pusLength );pvMBFrameCloseCur將指向連線埠關閉函式void vMBPortClose( void )(portserial.c中定義);pxMBFrameCBByteReceived將指向接收中斷狀態機函式BOOL xMBRTUReceiveFSM( void );pxMBFrameCBTransmitterEmpty將指向傳送中斷狀態機函式BOOL xMBRTUTransmitFSM( void );pxMBPortCBTimerExpired將指向逾時中斷函式BOOL xMBRTUTimerT35Expired( void )。
完成相應函式指針的初始化之後,會調用該模式的初始化函式完成相應從機地址ucMBAddress、從機連線埠ucPort、從機通信速率ulBaudRate、從機校驗方式eParity的初始化。
u eMBErrorCode eMBTCPInit( USHORT ucTCPPort )
Modbus TCP模式初始化函式。只有當配置檔案使能對應的宏開關MB_TCP_ENABLED時,才會編譯該函式。該函式會初始化所使用的TCP/IP連線埠號,並初始化相應的函式指針。
u eMBErrorCode eMBRegisterCB( UCHAR ucFunctionCode, pxMBFunctionHandler pxHandler )
Modbus功能註冊函式。通過該函式,可以定義FreeModbus協定外的功能代碼,並註冊相應的功能函式。具體如何實現還沒具體看。
u eMBErrorCode eMBClose( void )
Modbus連線埠關閉函式。該函式通過函式指針pvMBFrameCloseCur(指向通訊連線埠關閉函式)來停止止Modbus連線埠上的通訊。
u eMBErrorCode eMBEnable( void )
Modbus協定開始函式。該函式通過函式指針pvMBFrameStartCur(指向相應模式下的使能函式)來使能Modbus通訊。
u eMBErrorCode eMBDisable( void )
Modbus協定終止函式。該函式通過函式指針pvMBFrameStopCur(指向相應模式下的終止函式)來終止Modbus協定。
u eMBErrorCode eMBPoll( void )
Modbus事件輪詢處理函式。該函式通過查詢底層返回來的事件eEvent來決定當前該處理的事務。
處理過程為:若事件為EV_READY(Startup finished),則跳出,等待下一次查詢;若事件為EV_FRAME_RECEIVED(Frame received),則通過函式指針peMBFrameReceiveCur(指向接收幀信息提取函式)來完成幀信息的提取,並向系統傳送EV_EXECUTE(Execute function)事件;若事件為EV_EXECUTE,則根據已經從幀信息中提取到的功能碼在xFuncHandlers中查詢對應的功能函式指針,查找到後通過函式指針調用相應的功能處理函式來完成幀信息的處理(向快取數組中存放回復PDU),完成處理後,通過函式指針peMBFrameSendCur調用幀傳送函式完成回復幀的傳送;若事件為EV_FRAME_SENT(Frame sent),則跳出,等待下一次查詢。
3. 關於mbconfig.h檔案的理解
此檔案為Modbus協定的配置檔案。在移植時,應根據所選用的目標處理器靈活修改此檔案,使之滿足需要而代碼最小。當然,若果你的處理器處理能力足夠強,可以保持默認配置,或是根據需要,增加相應的功能的配置宏。檔案內容及相應解釋如下:
#ifndef _MB_CONFIG_H
#define _MB_CONFIG_H
//外部C編譯器宏開關
#ifdef __cplusplus
PR_BEGIN_EXTERN_C
#endif
/* ----------------------- Defines ------------------------------------------*/
/*! \brief If Modbus ASCII support is enabled. */
#define MB_ASCII_ENABLED ( 1 )
/*! \brief If Modbus RTU support is enabled. */
#define MB_RTU_ENABLED ( 1 )
/*! \brief If Modbus TCP support is enabled. */
#define MB_TCP_ENABLED ( 0 )
/*! \brief The character timeout value for Modbus ASCII. */
#define MB_ASCII_TIMEOUT_SEC ( 1 )
/*! \brief Timeout to wait in ASCII prior to enabling transmitter. */
#ifndef MB_ASCII_TIMEOUT_WAIT_BEFORE_SEND_MS
#define MB_ASCII_TIMEOUT_WAIT_BEFORE_SEND_MS ( 0 )
#endif
/*! \brief Maximum number of Modbus functions codes the protocol stack */
#define MB_FUNC_HANDLERS_MAX ( 16 )
/*! \brief Number of bytes which should be allocated for the <em>Report Slave ID */
#define MB_FUNC_OTHER_REP_SLAVEID_BUF ( 32 )
/*! \brief If the <em>Report Slave ID</em> function should be enabled. */
#define MB_FUNC_OTHER_REP_SLAVEID_ENABLED ( 1 )
/*! \brief If the <em>Read Input Registers</em> function should be enabled. */
#define MB_FUNC_READ_INPUT_ENABLED ( 1 )
/*! \brief If the <em>Read Holding Registers</em> function should be enabled. */
#define MB_FUNC_READ_HOLDING_ENABLED ( 1 )
/*! \brief If the <em>Write Single Register</em> function should be enabled. */
#define MB_FUNC_WRITE_HOLDING_ENABLED ( 1 )
/*! \brief If the <em>Write Multiple registers</em> function should be enabled. */
#define MB_FUNC_WRITE_MULTIPLE_HOLDING_ENABLED ( 1 )
/*! \brief If the <em>Read Coils</em> function should be enabled. */
#define MB_FUNC_READ_COILS_ENABLED ( 1 )
/*! \brief If the <em>Write Coils</em> function should be enabled. */
#define MB_FUNC_WRITE_COIL_ENABLED ( 1 )
/*! \brief If the <em>Write Multiple Coils</em> function should be enabled. */
#define MB_FUNC_WRITE_MULTIPLE_COILS_ENABLED ( 1 )
/*! \brief If the <em>Read Discrete Inputs</em> function should be enabled. */
#define MB_FUNC_READ_DISCRETE_INPUTS_ENABLED ( 1 )
/*! \brief If the <em>Read/Write Multiple Registers</em> function should be enabled. */
#define MB_FUNC_READWRITE_HOLDING_ENABLED ( 1 )
/*! @} */
#ifdef __cplusplus
PR_END_EXTERN_C
#endif
#endif
技巧
1、 若Buffer的最後兩個位元組為16位CRC校驗值,則對整個Buffer校驗時,值為0;