近项目有用到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模式的接收发送机制分析。