 Freemodbus啟動流程分析(xi)
							時(shi)間(jian):2018-09-25      來源(yuan):未知
							Freemodbus啟動流程分析(xi)
							時(shi)間(jian):2018-09-25      來源(yuan):未知 
							近項目(mu)有(you)用(yong)到modbus協(xie)議,于(yu)是在網(wang)上找了些資料成功將freemodbus移植(zhi)到m3,由于(yu)移植(zhi)過程較簡單(dan),網(wang)上教程也(ye)很(hen)多,這里我們就不再贅述(shu).我用(yong)到的freemodbus版本是V1.5,下面(mian)附上新(xin)的源(yuan)碼下載地址://www.freemodbus.org/index.php?idx=5
下(xia)(xia)面開始分析下(xia)(xia)freemodbus得啟動流程(cheng),老(lao)規矩我們還(huan)是從main()函數下(xia)(xia)手:
 
和freemodbus有關(guan)的函數只有三(san)個eMBInit(), eMBEnable(), eMBPoll().我們逐一來分析.
首先(xian)是eMBInit(),我(wo)們來看下(xia)源碼(ma):eMBErrorCode
eMBInit( eMBMode eMode, UCHAR ucSlaveAddress, UCHAR ucPort,
ULONG ulBaudRate, eMBParity eParity )
{
//錯誤狀態(tai)初始值
eMBErrorCode eStatus = MB_ENOERR;
//驗證從(cong)機地址
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;
//報文(wen)接(jie)收(shou)函(han)數
peMBFrameReceiveCur = eMBRTUReceive;
pvMBFrameCloseCur = MB_PORT_HAS_CLOSE ? vMBPortClose : NULL;
//接(jie)收狀態機
pxMBFrameCBByteReceived = xMBRTUReceiveFSM;
//發送狀態機
pxMBFrameCBTransmitterEmpty = xMBRTUTransmitFSM;
//報(bao)文到(dao)達間隔檢查
pxMBPortCBTimerExpired = xMBRTUTimerT35Expired;
//初始(shi)化(hua)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
{
//設定當前狀態(tai)
eMBCurrentMode = eMode;
eMBState = STATE_DISABLED;
}
}
}
return eStatus;
}
我這(zhe)次用到的是RTU模式,所以我們(men)就只(zhi)分析(xi)RTU模式下得工作模式.上邊的代(dai)碼比較(jiao)容易理解, 大家好逐行分析(xi),我們(men)首先來看下都(dou)傳了什么參(can)數:
eMBInit(MB_RTU, 0x09, 0x01, 9600, MB_PAR_NONE);
附上函數的聲明:eMBInit( eMBMode eMode, UCHAR ucSlaveAddress, UCHAR ucPort, ULONG ulBaudRate, eMBParity eParity )
MB_RTU:使用的是modbusRTU模式.
0x09:從(cong)機得地(di)址
0x01:串口(kou)號,這(zhe)里我(wo)們使(shi)用的是(shi)串口(kou)1
9600:波特率
MB_PAR_NONE:無校驗和
這個(ge)(ge)函(han)數(shu)的(de)任務就是(shi)根據你的(de)參數(shu)來配置(zhi)串(chuan)口(kou),定時器和(he)modbus中的(de)ID號這個(ge)(ge)函(han)數(shu)另外一(yi)(yi)個(ge)(ge)重要的(de)工作就是(shi)將一(yi)(yi)些全(quan)局指(zhi)(zhi)針(zhen)變量(liang)指(zhi)(zhi)向對應得函(han)數(shu),將來方便進(jin)行回調。
另外一個事情(qing)就是將eMBCurrentMode賦(fu)值(zhi)為MB_RTU, eMBState 賦(fu)值(zhi)為STATE_DISABLED;
我們需要對(dui)這些狀(zhuang)態有一些印(yin)象,因為后邊還是會用到。
下(xia)面來分析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;
}
這一段代(dai)碼呢(ni)比(bi)較少,我(wo)們來看第6行的判(pan)斷,這里eMBState 賦值為(wei)STATE_DISABLED是在(zai)
eMBInit()執行,所以執行if分支,看第9行代碼,發現并(bing)沒有這個函數,奧,,還記(ji)得他是函數指(zhi)針吧,在eMBInit()中被指(zhi)向了函數eMBRTUStart( void ),我們(men)來看下真正的(de)啟動函數原型: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 );
//啟動定(ding)時器(qi)
vMBPortTimersEnable();
EXIT_CRITICAL_SECTION( );
}
以上(shang)代碼比較簡單我就不逐一(yi)分析了(le)(le),我們需要注(zhu)意下eRcvState被賦值為 STATE_RX_INIT;還將串口接收(shou)中斷使(shi)能,關閉串口發送(song)。開(kai)(kai)啟定(ding)時(shi)器,記得哦(e),開(kai)(kai)啟了(le)(le)定(ding)時(shi)器并開(kai)(kai)啟了(le)(le)中斷,你肯定(ding)會(hui)想定(ding)時(shi)器多久后(hou)觸發中斷?我們來分析一(yi)下,首先我們找到初(chu)始(shi)化(hua)定(ding)時(shi)器的地方,eMBInit()èeMBRTUInit(),我們來看一(yi)下源碼: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 使用固(gu)定的時(shi)間間隔(ge),1750us
//其他情況,則要進行(xing)計算。
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;
}
串口(kou)初始化我(wo)們(men)就不多說了(le),接下來它在算出來了(le)一個數(shu)給了(le)usTimerT35_50us,然后調用xMBPortTimersInit( ( USHORT ) usTimerT35_50us ),這是(shi)在干嘛(ma)?
首先我們(men)來詳細說下(xia)usTimerT35_50us,這(zhe)里我首先要說下(xia)modbus的(de)協議(yi)規范了:
RTU方(fang)式的(de)MODBUS如何判別(bie)(bie)一幀(zhen)數據包(bao)哪?有的(de)協(xie)議(yi)里有開始(shi)標(biao)示、結束(shu)標(biao)示,通過判別(bie)(bie)開頭標(biao)示和(he)結束(shu)標(biao)示來(lai)表(biao)示一幀(zhen)完整的(de)數據幀(zhen)。但MODBUS RTU方(fang)式的(de)數據幀(zhen)并(bing)沒有開始(shi)標(biao)示和(he)結束(shu)標(biao)示,那MODBUS RTU怎(zen)么(me)判別(bie)(bie)一幀(zhen)數據幀(zhen)的(de)哪?我們還(huan)要看(kan)MDBUS協(xie)議(yi)棧(zhan)的(de)具體規定。先給大家(jia)看(kan)一個(ge)圖:
  
MODBUS協議里面說一幀數(shu)據(ju)(ju)和(he)下一幀數(shu)據(ju)(ju)之間的間隔至少(shao)是3.5個(ge)字符(fu)。好了,新的問題(ti)又來了3.5個(ge)字符(fu)是多長時間,我們來看(kan)段代碼中的說明:
* If baudrate > 19200 then we should use the fixed timer values
* t35 = 1750us. Otherwise t35 must be 3.5 times the character time.
*/
波(bo)特率大于(yu)(yu)19200時候(hou)我(wo)(wo)們(men)(men)(men)用固定時長來判(pan)斷(duan),小于(yu)(yu)19200時我(wo)(wo)們(men)(men)(men)還是(shi)要知道(dao)3.5個(ge)(ge)(ge)字(zi)符是(shi)多久,我(wo)(wo)們(men)(men)(men)知道(dao)他和波(bo)特率有關(guan),我(wo)(wo)們(men)(men)(men)假設波(bo)特率是(shi)9600,那1s能傳9600位(wei)(wei)(wei), 一個(ge)(ge)(ge)字(zi)符是(shi)11位(wei)(wei)(wei)(8個(ge)(ge)(ge)數據位(wei)(wei)(wei),1個(ge)(ge)(ge)起始位(wei)(wei)(wei),1個(ge)(ge)(ge)停止位(wei)(wei)(wei), 1個(ge)(ge)(ge)校驗(yan)位(wei)(wei)(wei)),那T3.5就等(deng)價于(yu)(yu)1000/(9600/11)*3.5(估算為4ms)。
Freemodbus實現t3.5的方法是在定(ding)時器設(she)置一個(ge)基(ji)準(zhun)時間(jian)50us(通過設(she)置預分頻的數值TIM_Prescaler),再根(gen)據T3.5的大小來計算出計數值(TIM_Period)。好了我(wo)們(men)這(zhe)里就點到(dao)為止(zhi),我(wo)們(men)只需要將定(ding)時器配(pei)置基(ji)準(zhun)時間(jian)為50us。
誒(e^),,,我們剛到哪里(li)了(le)?
剛(gang)調用了一個(ge)鉤子函數(shu)pvMBFrameStartCur( );隨后將eMBState狀態改編為STATE_ENABLED;
到這里我(wo)(wo)們總結一(yi)下(xia),剛(gang)(gang)剛(gang)(gang)我(wo)(wo)們已經執行了兩個函數(shu),分別(bie)是(shi)eMBInit(MB_RTU, 0x09, 0x01, 9600, MB_PAR_NONE)和(he)eMBEnable(); 有兩個狀態(tai)發生(sheng)了改變(bian)(bian),eMBState狀態(tai)改變(bian)(bian)為STATE_ENABLED,eRcvState 狀態(tai)改變(bian)(bian)為 STATE_RX_INIT; 并(bing)且(qie)在大約4ms后會(hui)產生(sheng)一(yi)次定時器中(zhong)斷。
接下(xia)來我(wo)們(men)先去看(kan)下(xia)這個定時器中斷(duan)都干了什(shen)么(me)?然后再去看(kan)eMBPoll();void TIM4_IRQHandler(void)
{
if (TIM_GetITStatus(TIM4, TIM_IT_Update) != RESET)
{
//清除定時器T4溢出(chu)中(zhong)斷標(biao)志位
TIM_ClearITPendingBit(TIM4, TIM_IT_Update);
prvvTIMERExpiredISR( );
}
}
判斷(duan)確定發生中斷(duan)以后,又調用(yong)了一(yi)個prvvTIMERExpiredISR( )è pxMBPortCBTimerExpired()這又是(shi)一(yi)個鉤子(zi)函數,我們來(lai)看下(xia)代碼:
BOOL
xMBRTUTimerT35Expired( void )
{
BOOL xNeedPoll = FALSE;
switch ( eRcvState )
{
/* Timer t35 expired. Startup phase is finished. */
//這(zhe)是一個啟(qi)動狀態,運行到這(zhe)里說明啟(qi)動狀態完成。
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:
//發(fa)送事件(jian),接收到完整的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 ) );
}
//禁(jin)止定時(shi)器(qi)
vMBPortTimersDisable( );
//串口接收狀態(tai) 變為空閑狀態(tai)。
eRcvState = STATE_RX_IDLE;
return xNeedPoll;
}
上來是(shi)(shi)根據eRcvState的值來運行對應分(fen)支(zhi)的,eRcvState的值又是(shi)(shi)什么呢?我們之前說過,在eMBRTUStart()中eRcvState = STATE_RX_INIT;那我們就(jiu)知(zhi)道要執行哪(na)些(xie)代碼了(le),首先調(diao)用了(le) xNeedPoll = xMBPortEventPost( EV_READY ); xMBPortEventPost()就(jiu)是(shi)(shi)將事(shi)(shi)件類型賦給eQueuedEvent,并將xEventInQueue置為TRUE代表(biao)有事(shi)(shi)件發生(sheng)。BOOL
xMBPortEventPost( eMBEventType eEvent )
{
//有事件標志更新
xEventInQueue = TRUE;
//設(she)定事件標志
eQueuedEvent = eEvent;
return TRUE;
}
我們繼續回到xMBRTUTimerT35Expired(),他在將對應的(de)事(shi)件(jian)發送(song)給(gei)eQueuedEvent后關(guan)閉了(le)定時器,并將eRcvState置為STATE_RX_IDLE
下(xia)面開(kai)始分析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:
//接收報(bao)文函數,傳入參數從機(ji)地(di)址,報(bao)文指針,長度
//實際上調用(yong)了(le)eMBRTUReceive 位于mbrtu.c
eStatus = peMBFrameReceiveCur( &ucRcvAddress,
&ucMBFrame, &usLength );
if( eStatus == MB_ENOERR )
{
/* Check if the frame is for us.
If not ignore the frame. */
//驗證報文從機地(di)址
if( ( ucRcvAddress == ucMBAddress ) ||
( ucRcvAddress == MB_ADDRESS_BROADCAST ) )
{
//發送事件,報文(wen)到(dao)達,可以進行處理
( 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;
這里代碼比較多,我們(men)逐行(xing)分析(xi)下。
13-16行檢測eMBState是否為(wei)STATE_ENABLED?之前我們在eMBEnable()已經將eMBState置為(wei)STATE_ENABLED
23-89行檢測(ce)是(shi)否有(you)事件(jian)發(fa)生,并執行事件(jian)對應(ying)的處理函數。
eMBPoll()就(jiu)是(shi)一直在檢測是(shi)否有事件(jian),有就(jiu)處(chu)理。我們先來看(kan)xMBPortEventGet( &eEvent )BOOL
xMBPortEventGet( eMBEventType * eEvent )
{
BOOL xEventHappened = FALSE;
//若(ruo)有事件更(geng)新
if( xEventInQueue )
{
//獲(huo)得事件
*eEvent = eQueuedEvent;
xEventInQueue = FALSE;
xEventHappened = TRUE;
}
return xEventHappened;
}
代碼比較簡單,就是檢測是否(fou)有事件,并(bing)將(jiang)事件回傳給(gei)掉它的函數(shu)。剛(gang)剛(gang)我們發生了一個事件,還記(ji)得嗎?在第(di)一次(ci)定時器中斷的時候 執行(xing)了這樣一句話xNeedPoll = xMBPortEventPost( EV_READY );
可惜EV_READY事(shi)件(jian)只是告訴主程序協議棧(zhan)初(chu)始化成功,并沒(mei)有做實際(ji)的事(shi)情。
好了(le),到這里我們大概將freemodbus的啟(qi)動流程(cheng)分析(xi)了(le)一下(xia),我們來總結一下(xia)。
eMBInit():配置了定時器和串口,并規定了modbus的啟動、停止(zhi)、發送以及接收數(shu)據(ju)的函數(shu)具體(ti)形式。
eMBEnable():打開定(ding)時(shi)器并開啟中斷, 使能(neng)串口接收并開啟中斷。
eMBPoll():檢測(ce)是(shi)否有(you)事件發(fa)生,并處(chu)理對應的事件,包括接(jie)收到完(wan)整(zheng)數據(ju)幀(zhen)的事件,處(chu)理數據(ju)幀(zhen)的事件。
協議(yi)棧剩(sheng)下(xia)的工作就(jiu)是接(jie)收串口(kou)的數據并(bing)進行分析處理,并(bing)會通過(guo)串口(kou)返(fan)回對應(ying)的回應(ying)幀。我們將會在下(xia)面(mian)分析modbus RTU模式的接(jie)收發送機制分析。

