天天看點

freemodbus代碼解讀

freeModbus的代碼庫還是很好用的,本人在wince和C8051F410下均移植成功(隻用到RTU模式)。但freeModbus提供的文檔比較少,隻能對照着Modbus協定一點點試着讀懂源代碼。下面是閱讀代碼期間的跟蹤筆記:

  1、eMBErrorCode為枚舉類型變量,代表錯誤碼,共有8個錯誤代号。常用的是MB_ENOERR,即沒有錯誤。

  2、eMBMode枚舉類型變量代表裝置的工作模式,分别是MB_RTU、MB_ASCII和MB_TCP。

  3、eMBEventType枚舉類型變量定義了event的類型,分别是EV_READY,代表Startup啟動完成;EV_FRAME_RECEIVED代表接收到幀;EV_EXECUTE代表執行功能函數;EV_FRAME_SENT代表幀已發送。

  4、eMBParity枚舉類型變量代表奇偶校驗選項,分别是MB_PAR_NONE無校驗,MB_PAR_ODD奇校驗,和MB_PAR_EVEN偶校驗。

  5、mb.c檔案中的靜态變量ucMBAddress儲存設備位址,此變量在eMBInit函數中初始化。

  6、在C51Modbus中将freeModbus庫中的 源碼 進行了更改,例如盡量不使用函數指針,而是直接調用相關功能函數,根據eMBCurrentMode中的工作模式,來判斷調用哪個函數。在freeModbus庫中某些函數聲明前加上reentrant,這是Keil編譯器特有的關鍵詞。這樣做帶來的一個不足是:不能動态綁定函數,進而導緻庫代碼失去可移植性。這樣做是C51編譯器與ANSI标準不相容的特殊性導緻的。

  7、ENTER_CRITICAL_SECTION()和EXIT_CRITICAL_SECTION()宏,實際上就是關閉和打開全局中斷。

  8、帶xMBPort字首的函數都屬于port layer層,也就是獨立于ModBus協定棧。

  9、freeModbus庫中函數名稱的第一個字母表示傳回值類型,例如e表示傳回enum枚舉類型;v表示void無傳回值;x表示BOOL布爾類型。注意這條規則并不是總成立,但主要函數基本上還是符合此規則的。第一個字母後的MB代表是屬于ModBus協定棧的函數。

  10、port.h檔案中宏#define F_MCU 定義了單片機的工作頻率。需要用其值計算Uart0定時器和Tick定時器的重裝入值。

  11、在程式主函數main中,使用協定棧的方法是:

  eStatus = eMBInit( MB_RTU, 0x0A, 0, 9600, MB_PAR_EVEN );

  

  eStatus = eMBEnable( );

  for( ;; )

  {

  ( void )eMBPoll( );

  ……

  }

  12、在port layer層的xMBPortSerialInit函數中,需要根據傳入的波特率、奇偶校驗、資料位長度設定來配置Uart0及其使用的定時器。

  13、在port layer層的vMBPortSerialEnable函數中配置接收和發送使能,由于在單片機的寄存器SCON0中隻有接收使能控制位REN0,而沒有發送使能控制位,是以在portserial.c檔案中又定義了一個TxEnable變量,用來表示發送的使能狀态。若同時關閉接收和發送,則要關閉Uart0中斷,即讓ES0 = 0。

  14、eMBRTUInit函數中的變量usTimerT35_50us代表如果50us進行一次Tick的話,T35逾時的Tick次數。這個公式很重要:

  usTimerT35_50us = ( 7UL * 220000UL ) / ( 2UL * ulBaudRate );

  函數xMBPortTimersInit要以變量usTimerT35_50us為傳入參數,對T35逾時定時器進行設定。

  15、在mbrtu.c檔案中定義了兩個狀态變量,一個是接收狀态變量eRcvState,為eMBRcvState枚舉類型,有4個狀态,在使能ModBus協定棧後賦予STATE_RX_INIT,即初始狀态;另一個是發送狀态變量eSndState,為eMBSndState枚舉類型,有兩個狀态,初始化為發送idle狀态,即STATE_TX_IDLE。

  16、mb.c檔案中的eMBState狀态變量為枚舉類型,代表裝置的工作狀态,有3種狀态,分别是“未初始化”、 “使能”和“禁止”狀态。調用完eMBInit 函數後要調用eMBEnable函數來使能ModBus協定棧,在其中将eMBState狀态變量從“未初始化狀态”變為“使能狀态”,然後使能序列槽和打開T35定時器。

  17、如果T35定時器逾時并産生中斷,則要調用xMBRTUTimerT35Expired函數,其内部是一個狀态機轉換的switch,根據目前接收狀态來通過xMBPortEventPost發送事件通知,然後關閉T35定時器,并将目前接收狀态設定為STATE_RX_IDLE。

  18、eMBException枚舉型變量表示Exception的類型,共有10種Exception,在ModBus協定中有定義。

  19、在eMBPoll( )中,首先通過xMBPortEventGet函數取event,如果沒有則退出,若有event的話便根據event類型進行相應處理。EV_READY是在協定棧初始化後xMBRTUTimerT35Expired函數發出來的,表示startup完成;EV_FRAME_RECEIVED是xMBRTUTimerT35Expired函數在T35逾時後發出的,表示已經收到了一幀,需要進行成幀處理,調用eMBRTUReceive函數;EV_EXECUTE是在處理EV_FRAME_RECEIVED過程中最後一步,如果此幀的位址符合本機位址,則發出EV_EXECUTE事件,進行應用層的處理。

  20、在eMBRTUReceive函數中首先檢視幀大小是否符合要求,然後進行CRC校驗。此函數的原型是:

  eMBRTUReceive( UCHAR * pucRcvAddress, UCHAR ** pucFrame, USHORT * pusLength )

  第一個參數是為了傳回幀中的位址,也就是幀中第一個位元組;第二個傳入的參數以後要當做數組來使用,是以用了指針的指針類型;第三個參數表示PDU的長度,也就是幀中除去位址位元組和CRC校驗位元組後的長度。

  21、在eMBPoll( )中處理EV_EXECUTE事件,首先從PDU中提取出FunctionCode,然後根據FunctionCode找到相應的處理函數。xMBFunctionHandler結構體類型變量xFuncHandlers中定義了各個FunctionCode對應的處理函數pxHandler,函數的第一個參數ucMBFrame是PDU的存儲位址,第二個參數usLength傳回PDU的長度。如果幀不是一個廣播幀,則需要裝置發出一個回複,如果前面有錯誤發生,則要回複一個錯誤報告幀。

  22、在Keil中程式需要使用大模式編譯,否則會出現error c249: 'data': segment too large的錯誤。

  23、若使用波特率為9600,則 t3.5= ( 11 * 3.5 ) / 9600 = 4.01 ms。 不能使用8位模式的Timer,因為11.0590MHz主頻在最大48分頻後,最長的逾時時間為1.11ms,不能滿足T35的逾時要求。

24、freeModbus 1.5庫,在使用過程中發現了一個bug,即如果在PDU中發送的寄存器資料長度與要讀寫的寄存器的數量不符,隻要CRC校驗正确,freeModbus便不會檢測出來。例如寫多個寄存器指令中,标明寫寄存器的數量為2,也就是後面接的資料長度為4,但随後的資料隻為2個位元組,即一個寄存器的資料,freeModbus不會發現此錯誤,同時會将後面的CRC校驗值認作是寫第二個寄存器的資料。這個bug實際上是通過Modbus調試精靈1.024的一個寫多寄存器bug發現的。

協定棧以及定時器初始化T35第一次逾時—>eMBPoll STATE_RX_IDLE—>收到資料中斷—>prvvUARTRxISR—>pxMBFrameCBByteReceived—>xMBRTUReceiveFSM接收資料 —> STATE_RX_RCV—>T35逾時—> eMBPollEV_FRAME_RECEIVED(peMBFrameReceiveCur->eMBRTUReceive)提取完整資料幀—> eMBPoll case EV_EXECUTE:xFuncHandlers[i].pxHandler(eMBRegInputCB)對接收的資料進行處理—> peMBFrameSendCur—>eMBRTUSend(&STATE_RX_IDLE)—>STATE_TX_XMIT

   序列槽發送完成中斷—> prvvUARTTxReadyISR—> FSMpxMBFrameCBTransmitterEmpty—>xMBRTUTransmitFSM(& STATE_TX_XMIT)—>xMBPortSerialPutByte—>發送資料。