近項目有用到modbus協議,于是在網上找了些資料成功將freemodbus移植到m3,由于移植過程較簡單,網上教程也很多,這里我們就不再贅述.我用到的freemodbus版本是V1.5,下面附上新的源碼下載地址:http://www.freemodbus.org/index.php?idx=5
下面開始分析下freemodbus得啟動流程,老規矩我們還是從main()函數下手:
和freemodbus有關的函數只有三個eMBInit(), eMBEnable(), eMBPoll().我們逐一來分析.
首先是eMBInit(),我們來看下源碼:eMBErrorCode
eMBInit( eMBMode eMode, UCHAR ucSlaveAddress, UCHAR ucPort,
ULONG ulBaudRate, eMBParity eParity )
{
//錯誤狀態初始值
eMBErrorCode eStatus = MB_ENOERR;
//驗證從機地址
if( ( ucSlaveAddress == MB_ADDRESS_BROADCAST ) ||
( ucSlaveAddress < MB_ADDRESS_MIN ) ||
( ucSlaveAddress > MB_ADDRESS_MAX ) )
{
eStatus = MB_EINVAL;
}
else
{
ucMBAddress = ucSlaveAddress;
switch ( eMode )
{
#if MB_RTU_ENABLED > 0
case MB_RTU:
pvMBFrameStartCur = eMBRTUStart;
pvMBFrameStopCur = eMBRTUStop;
peMBFrameSendCur = eMBRTUSend;
//報文接收函數
peMBFrameReceiveCur = eMBRTUReceive;
pvMBFrameCloseCur = MB_PORT_HAS_CLOSE ? vMBPortClose : NULL;
//接收狀態機
pxMBFrameCBByteReceived = xMBRTUReceiveFSM;
//發送狀態機
pxMBFrameCBTransmitterEmpty = xMBRTUTransmitFSM;
//報文到達間隔檢查
pxMBPortCBTimerExpired = xMBRTUTimerT35Expired;
//初始化RTU
eStatus = eMBRTUInit( ucMBAddress, ucPort, ulBaudRate, eParity );
break;
#endif
#if MB_ASCII_ENABLED > 0
case MB_ASCII:
pvMBFrameStartCur = eMBASCIIStart;
pvMBFrameStopCur = eMBASCIIStop;
peMBFrameSendCur = eMBASCIISend;
peMBFrameReceiveCur = eMBASCIIReceive;
pvMBFrameCloseCur = MB_PORT_HAS_CLOSE ? vMBPortClose : NULL;
pxMBFrameCBByteReceived = xMBASCIIReceiveFSM;
pxMBFrameCBTransmitterEmpty = xMBASCIITransmitFSM;
pxMBPortCBTimerExpired = xMBASCIITimerT1SExpired;
eStatus = eMBASCIIInit( ucMBAddress, ucPort, ulBaudRate, eParity );
break;
#endif
default:
eStatus = MB_EINVAL;
}
//
if( eStatus == MB_ENOERR )
{
if( !xMBPortEventInit() )
{
/* port dependent event module initalization failed. */
eStatus = MB_EPORTERR;
}
else
{
//設定當前狀態
eMBCurrentMode = eMode;
eMBState = STATE_DISABLED;
}
}
}
return eStatus;
}
我這次用到的是RTU模式,所以我們就只分析RTU模式下得工作模式.上邊的代碼比較容易理解, 大家好逐行分析,我們首先來看下都傳了什么參數:
eMBInit(MB_RTU, 0x09, 0x01, 9600, MB_PAR_NONE);
附上函數的聲明:eMBInit( eMBMode eMode, UCHAR ucSlaveAddress, UCHAR ucPort, ULONG ulBaudRate, eMBParity eParity )
MB_RTU:使用的是modbusRTU模式.
0x09:從機得地址
0x01:串口號,這里我們使用的是串口1
9600:波特率
MB_PAR_NONE:無校驗和
這個函數的任務就是根據你的參數來配置串口,定時器和modbus中的ID號這個函數另外一個重要的工作就是將一些全局指針變量指向對應得函數,將來方便進行回調。
另外一個事情就是將eMBCurrentMode賦值為MB_RTU, eMBState 賦值為STATE_DISABLED;
我們需要對這些狀態有一些印象,因為后邊還是會用到。
下面來分析eMBEnable():eMBErrorCode
eMBEnable( void )
{
eMBErrorCode eStatus = MB_ENOERR;
if( eMBState == STATE_DISABLED )
{
/* Activate the protocol stack. */
pvMBFrameStartCur( );
eMBState = STATE_ENABLED;
}
else
{
eStatus = MB_EILLSTATE;
}
return eStatus;
}
這一段代碼呢比較少,我們來看第6行的判斷,這里eMBState 賦值為STATE_DISABLED是在
eMBInit()執行,所以執行if分支,看第9行代碼,發現并沒有這個函數,奧,,還記得他是函數指針吧,在eMBInit()中被指向了函數eMBRTUStart( void ),我們來看下真正的啟動函數原型:void
eMBRTUStart( void )
{
ENTER_CRITICAL_SECTION( );
/* Initially the receiver is in the state STATE_RX_INIT. we start
* the timer and if no character is received within t3.5 we change
* to STATE_RX_IDLE. This makes sure that we delay startup of the
* modbus protocol stack until the bus is free.
*/
//eRcvState 初始化狀態
eRcvState = STATE_RX_INIT;
//使能接收,禁止發送
vMBPortSerialEnable( TRUE, FALSE );
//啟動定時器
vMBPortTimersEnable();
EXIT_CRITICAL_SECTION( );
}
以上代碼比較簡單我就不逐一分析了,我們需要注意下eRcvState被賦值為 STATE_RX_INIT;還將串口接收中斷使能,關閉串口發送。開啟定時器,記得哦,開啟了定時器并開啟了中斷,你肯定會想定時器多久后觸發中斷?我們來分析一下,首先我們找到初始化定時器的地方,eMBInit()èeMBRTUInit(),我們來看一下源碼:eMBErrorCode
eMBRTUInit( UCHAR ucSlaveAddress, UCHAR ucPort, ULONG ulBaudRate,
eMBParity eParity )
{
eMBErrorCode eStatus = MB_ENOERR;
ULONG usTimerT35_50us;
( void )ucSlaveAddress;
ENTER_CRITICAL_SECTION();
/* Modbus RTU uses 8 Databits. */
if( xMBPortSerialInit( ucPort, ulBaudRate, 8, eParity ) != TRUE )
{
eStatus = MB_EPORTERR;
}
else
{
/* If baudrate > 19200 then we should use the fixed timer values
* t35 = 1750us. Otherwise t35 must be 3.5 times the character time.
*/
//如果波特率超過19200 使用固定的時間間隔,1750us
//其他情況,則要進行計算。
if( ulBaudRate > 19200 )
{
usTimerT35_50us = 35; /* 1750us. */
}
else
{
/* The timer reload value for a character is given by:
*
* ChTimeValue = Ticks_per_1s / ( Baudrate / 11 )
* = 11 * Ticks_per_1s / Baudrate
* = 220000 / Baudrate
* The reload for t3.5 is 1.5 times this value and similary
* for t3.5.
*/
usTimerT35_50us = ( 7UL * 220000UL ) / ( 2UL * ulBaudRate );
}
//初始化定時器
if( xMBPortTimersInit( ( USHORT ) usTimerT35_50us ) != TRUE )
{
eStatus = MB_EPORTERR;
}
}
EXIT_CRITICAL_SECTION( );
return eStatus;
}
串口初始化我們就不多說了,接下來它在算出來了一個數給了usTimerT35_50us,然后調用xMBPortTimersInit( ( USHORT ) usTimerT35_50us ),這是在干嘛?
首先我們來詳細說下usTimerT35_50us,這里我首先要說下modbus的協議規范了:
RTU方式的MODBUS如何判別一幀數據包哪?有的協議里有開始標示、結束標示,通過判別開頭標示和結束標示來表示一幀完整的數據幀。但MODBUS RTU方式的數據幀并沒有開始標示和結束標示,那MODBUS RTU怎么判別一幀數據幀的哪?我們還要看MDBUS協議棧的具體規定。先給大家看一個圖:
MODBUS協議里面說一幀數據和下一幀數據之間的間隔至少是3.5個字符。好了,新的問題又來了3.5個字符是多長時間,我們來看段代碼中的說明:
* If baudrate > 19200 then we should use the fixed timer values
* t35 = 1750us. Otherwise t35 must be 3.5 times the character time.
*/
波特率大于19200時候我們用固定時長來判斷,小于19200時我們還是要知道3.5個字符是多久,我們知道他和波特率有關,我們假設波特率是9600,那1s能傳9600位, 一個字符是11位(8個數據位,1個起始位,1個停止位, 1個校驗位),那T3.5就等價于1000/(9600/11)*3.5(估算為4ms)。
Freemodbus實現t3.5的方法是在定時器設置一個基準時間50us(通過設置預分頻的數值TIM_Prescaler),再根據T3.5的大小來計算出計數值(TIM_Period)。好了我們這里就點到為止,我們只需要將定時器配置基準時間為50us。
誒,,,我們剛到哪里了?
剛調用了一個鉤子函數pvMBFrameStartCur( );隨后將eMBState狀態改編為STATE_ENABLED;
到這里我們總結一下,剛剛我們已經執行了兩個函數,分別是eMBInit(MB_RTU, 0x09, 0x01, 9600, MB_PAR_NONE)和eMBEnable(); 有兩個狀態發生了改變,eMBState狀態改變為STATE_ENABLED,eRcvState 狀態改變為 STATE_RX_INIT; 并且在大約4ms后會產生一次定時器中斷。
接下來我們先去看下這個定時器中斷都干了什么?然后再去看eMBPoll();void TIM4_IRQHandler(void)
{
if (TIM_GetITStatus(TIM4, TIM_IT_Update) != RESET)
{
//清除定時器T4溢出中斷標志位
TIM_ClearITPendingBit(TIM4, TIM_IT_Update);
prvvTIMERExpiredISR( );
}
}
判斷確定發生中斷以后,又調用了一個prvvTIMERExpiredISR( )è pxMBPortCBTimerExpired()這又是一個鉤子函數,我們來看下代碼:
BOOL
xMBRTUTimerT35Expired( void )
{
BOOL xNeedPoll = FALSE;
switch ( eRcvState )
{
/* Timer t35 expired. Startup phase is finished. */
//這是一個啟動狀態,運行到這里說明啟動狀態完成。
case STATE_RX_INIT:
xNeedPoll = xMBPortEventPost( EV_READY );
break;
/* A frame was received and t35 expired.
* Notify the listener that
* a new frame was received. */
case STATE_RX_RCV:
//發送事件,接收到完整的modbus數據
xNeedPoll = xMBPortEventPost( EV_FRAME_RECEIVED );
break;
/* An error occured while receiving the frame. */
case STATE_RX_ERROR:
break;
/* Function called in an illegal state. */
default:
assert( ( eRcvState == STATE_RX_INIT ) ||
( eRcvState == STATE_RX_RCV ) ||
( eRcvState == STATE_RX_ERROR ) );
}
//禁止定時器
vMBPortTimersDisable( );
//串口接收狀態 變為空閑狀態。
eRcvState = STATE_RX_IDLE;
return xNeedPoll;
}
上來是根據eRcvState的值來運行對應分支的,eRcvState的值又是什么呢?我們之前說過,在eMBRTUStart()中eRcvState = STATE_RX_INIT;那我們就知道要執行哪些代碼了,首先調用了 xNeedPoll = xMBPortEventPost( EV_READY ); xMBPortEventPost()就是將事件類型賦給eQueuedEvent,并將xEventInQueue置為TRUE代表有事件發生。BOOL
xMBPortEventPost( eMBEventType eEvent )
{
//有事件標志更新
xEventInQueue = TRUE;
//設定事件標志
eQueuedEvent = eEvent;
return TRUE;
}
我們繼續回到xMBRTUTimerT35Expired(),他在將對應的事件發送給eQueuedEvent后關閉了定時器,并將eRcvState置為STATE_RX_IDLE
下面開始分析eMBPoll();static UCHAR *ucMBFrame;
static UCHAR ucRcvAddress;
static UCHAR ucFunctionCode;
static USHORT usLength;
static eMBException eException;
int i;
eMBErrorCode eStatus = MB_ENOERR;
eMBEventType eEvent;
/* Check if the protocol stack is ready. */
//eMBEnable()==> eMBState = STATE_ENABLED;
if( eMBState != STATE_ENABLED )
{
return MB_EILLSTATE;
}
/* Check if there is a event available.
If not return control to caller.
* Otherwise we will handle the event. */
//查詢事件
if( xMBPortEventGet( &eEvent ) == TRUE )
{
switch ( eEvent )
{
case EV_READY:
break;
case EV_FRAME_RECEIVED:
//接收報文函數,傳入參數從機地址,報文指針,長度
//實際上調用了eMBRTUReceive 位于mbrtu.c
eStatus = peMBFrameReceiveCur( &ucRcvAddress,
&ucMBFrame, &usLength );
if( eStatus == MB_ENOERR )
{
/* Check if the frame is for us.
If not ignore the frame. */
//驗證報文從機地址
if( ( ucRcvAddress == ucMBAddress ) ||
( ucRcvAddress == MB_ADDRESS_BROADCAST ) )
{
//發送事件,報文到達,可以進行處理
( void )xMBPortEventPost( EV_EXECUTE );
}
}
break;
case EV_EXECUTE:
ucFunctionCode = ucMBFrame[MB_PDU_FUNC_OFF];
eException = MB_EX_ILLEGAL_FUNCTION;
for( i = 0; i < MB_FUNC_HANDLERS_MAX; i++ )
{
/* No more function handlers registered. Abort. */
if( xFuncHandlers[i].ucFunctionCode == 0 )
{
break;
}
else if( xFuncHandlers[i].ucFunctionCode == ucFunctionCode )
{
eException = xFuncHandlers[i].pxHandler( ucMBFrame, &usLength );
break;
}
}
/* If the request was not sent to the broadcast address we
* return a reply. */
if( ucRcvAddress != MB_ADDRESS_BROADCAST )
{
if( eException != MB_EX_NONE )
{
/* An exception occured. Build an error frame. */
usLength = 0;
ucMBFrame[usLength++] = ( UCHAR )( ucFunctionCode | MB_FUNC_ERROR );
ucMBFrame[usLength++] = eException;
}
if( ( eMBCurrentMode == MB_ASCII ) && MB_ASCII_TIMEOUT_WAIT_BEFORE_SEND_MS )
{
vMBPortTimersDelay( MB_ASCII_TIMEOUT_WAIT_BEFORE_SEND_MS );
}
eStatus = peMBFrameSendCur( ucMBAddress, ucMBFrame, usLength );
}
break;
case EV_FRAME_SENT:
break;
}
}
return MB_ENOERR;
這里代碼比較多,我們逐行分析下。
13-16行檢測eMBState是否為STATE_ENABLED?之前我們在eMBEnable()已經將eMBState置為STATE_ENABLED
23-89行檢測是否有事件發生,并執行事件對應的處理函數。
eMBPoll()就是一直在檢測是否有事件,有就處理。我們先來看xMBPortEventGet( &eEvent )BOOL
xMBPortEventGet( eMBEventType * eEvent )
{
BOOL xEventHappened = FALSE;
//若有事件更新
if( xEventInQueue )
{
//獲得事件
*eEvent = eQueuedEvent;
xEventInQueue = FALSE;
xEventHappened = TRUE;
}
return xEventHappened;
}
代碼比較簡單,就是檢測是否有事件,并將事件回傳給掉它的函數。剛剛我們發生了一個事件,還記得嗎?在第一次定時器中斷的時候 執行了這樣一句話xNeedPoll = xMBPortEventPost( EV_READY );
可惜EV_READY事件只是告訴主程序協議棧初始化成功,并沒有做實際的事情。
好了,到這里我們大概將freemodbus的啟動流程分析了一下,我們來總結一下。
eMBInit():配置了定時器和串口,并規定了modbus的啟動、停止、發送以及接收數據的函數具體形式。
eMBEnable():打開定時器并開啟中斷, 使能串口接收并開啟中斷。
eMBPoll():檢測是否有事件發生,并處理對應的事件,包括接收到完整數據幀的事件,處理數據幀的事件。
協議棧剩下的工作就是接收串口的數據并進行分析處理,并會通過串口返回對應的回應幀。我們將會在下面分析modbus RTU模式的接收發送機制分析。