这次主要介绍eMBInit( )函数。该函数是初始化modbus协议栈。其中会把协议的开始、禁止、发送、接收、解析、定时器等操作赋值给相应的函数,这样的好处可以防止底层变量被修改。同时,使得从机的状态固定在一个特定的状态。因为初始化同时初始化了RTU和ASCII两种模式,本例程只讲解RTU的初始化过程。废话不多说,直接交代代码。
eMBErrorCode
eMBInit( eMBMode eMode, UCHAR ucSlaveAddress, UCHAR ucPort, ULONG ulBaudRate, eMBParity eParity )
{
eMBErrorCode eStatus = MB_ENOERR;/* check preconditions */
/*防止从机的地址命名不合法*/
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;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;
}
一、eMBRTUStart()函数介绍,使能串口和定时器中断。先看代码
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 = STATE_RX_INIT;
vMBPortSerialEnable( TRUE, FALSE );
vMBPortTimersEnable( );EXIT_CRITICAL_SECTION( );
}
首先关闭所有中断;
然后给从机的状态赋值,初始化的从机状态为STATE_RX_INIT(从机初始化状态);
接着初始化串口中断,这里使能了接收中断,失能了发送中断;
再使能定时器中断;
最后使能中断;
二、eMBRTUStop()函数讲解,该函数eMBRTUStart()功能正好相反,不再详说。看代码
eMBRTUStop( void )
{
ENTER_CRITICAL_SECTION( );
vMBPortSerialEnable( FALSE, FALSE );
vMBPortTimersDisable( );
EXIT_CRITICAL_SECTION( );
}
三、eMBRTUSend()函数讲解,该函数是帧发送函数,通过组帧,再把帧数据发送出去
eMBErrorCode
eMBRTUSend( UCHAR ucSlaveAddress, const UCHAR * pucFrame, USHORT usLength )
{
eMBErrorCode eStatus = MB_ENOERR;
USHORT usCRC16;ENTER_CRITICAL_SECTION( );
/* Check if the receiver is still in idle state. If not we where to
* slow with processing the received frame and the master sent another
* frame on the network. We have to abort sending the frame.
*/
if( eRcvState == STATE_RX_IDLE )
{
/* First byte before the Modbus-PDU is the slave address. */
pucSndBufferCur = ( UCHAR * ) pucFrame - 1;
usSndBufferCount = 1;/* Now copy the Modbus-PDU into the Modbus-Serial-Line-PDU. */
pucSndBufferCur[MB_SER_PDU_ADDR_OFF] = ucSlaveAddress;//帧第一位
usSndBufferCount += usLength;//帧长度/* Calculate CRC16 checksum for Modbus-Serial-Line-PDU. */
usCRC16 = usMBCRC16( ( UCHAR * ) pucSndBufferCur, usSndBufferCount );//CRC校验位 低位在前 高位在后
ucRTUBuf[usSndBufferCount++] = ( UCHAR )( usCRC16 & 0xFF );
ucRTUBuf[usSndBufferCount++] = ( UCHAR )( usCRC16 >> 8 );
/* Activate the transmitter. */
//组帧之后,把从机的发送状态改为STATE_TX_XMIT,激活发送
eSndState = STATE_TX_XMIT;//
vMBPortSerialEnable( FALSE, TRUE );//串口发送中断
}
else
{
eStatus = MB_EIO;//如果从机不是接收空闲,返回IO错误类型
}
EXIT_CRITICAL_SECTION( );
return eStatus;
}
四、eMBRTUReceive()函数讲解
eMBErrorCode
eMBRTUReceive( UCHAR * pucRcvAddress, UCHAR ** pucFrame, USHORT * pusLength )
{
BOOL xFrameReceived = FALSE;
eMBErrorCode eStatus = MB_ENOERR;ENTER_CRITICAL_SECTION( );
assert( usRcvBufferPos < MB_SER_PDU_SIZE_MAX );//使用断言的方式,屏蔽错误。括号为真继续执行,为假直接返回/* Length and CRC check */
if( ( usRcvBufferPos >= MB_SER_PDU_SIZE_MIN )
&& ( usMBCRC16( ( UCHAR * ) ucRTUBuf, usRcvBufferPos ) == 0 ) )//为什么会返回0??
{
/* Save the address field. All frames are passed to the upper layed
* and the decision if a frame is used is done there.
*/
*pucRcvAddress = ucRTUBuf[MB_SER_PDU_ADDR_OFF];//提取帧头 从机地址/* Total length of Modbus-PDU is Modbus-Serial-Line-PDU minus
* size of address field and CRC checksum.
*/
*pusLength = ( USHORT )( usRcvBufferPos - MB_SER_PDU_PDU_OFF - MB_SER_PDU_SIZE_CRC );//计算帧长度/* Return the start of the Modbus PDU to the caller. */
*pucFrame = ( UCHAR * ) & ucRTUBuf[MB_SER_PDU_PDU_OFF];//把接收帧赋给pucFrame指针
xFrameReceived = TRUE;//接收数据为真
}
else
{
eStatus = MB_EIO;
}EXIT_CRITICAL_SECTION( );
return eStatus;
}
五、xMBRTUReceiveFSM()函数讲解。这是串口中断接收数据函数。
因为pxMBFrameCBByteReceived = xMBRTUReceiveFSM;
FreeModbus协议栈通过串口中断接收一帧数据,用户需在串口接收中断中回调prvvUARTRxISR()函数;
prvvUARTRxISR()函数:(portserial.c)
static void prvvUARTRxISR( void )
{
pxMBFrameCBByteReceived( );
}
在第一阶段中eMBInit()函数中赋值pxMBFrameCBByteReceived = xMBRTUReceiveFSM,发生接收中断时,最终调用xMBRTUReceiveFSM函数对数据进行接收;
xMBRTUReceiveFSM()函数:(mbrtu.c)
/*函数功能
*1:将接收到的数据存入ucRTUBuf[]中;
*2:usRcvBufferPos为全局变量,表示接收数据的个数;
*3:每接收到一个字节的数据,3.5T定时器清0
*/
BOOL
xMBRTUReceiveFSM( void )
{
BOOL xTaskNeedSwitch = FALSE;
UCHAR ucByte;assert( eSndState == STATE_TX_IDLE );
/* Always read the character. */
( void )xMBPortSerialGetByte( ( CHAR * ) & ucByte );//获取接收数据switch ( eRcvState )//根据从机的状态执行相应命令
{
/* If we have received a character in the init state we have to
* wait until the frame is finished.
*///初始化协议栈时,从机的状态是STATE_RX_INIT,因此在初始化时接收到数据时,会启动定时器中断,定时器清零。
case STATE_RX_INIT:
vMBPortTimersEnable( );
break;/* In the error state we wait until all characters in the
* damaged frame are transmitted.
*/
case STATE_RX_ERROR://接收的数据帧有问题状态也会清定时器
vMBPortTimersEnable( );
break;/* In the idle state we wait for a new character. If a character
* is received the t1.5 and t3.5 timers are started and the
* receiver is in the state STATE_RX_RECEIVCE.
*/
case STATE_RX_IDLE://接收空闲状态处理,接收首字节
usRcvBufferPos = 0;
ucRTUBuf[usRcvBufferPos++] = ucByte;
eRcvState = STATE_RX_RCV;/* Enable t3.5 timers. */
vMBPortTimersEnable( );
break;/* We are currently receiving a frame. Reset the timer after
* every character received. If more than the maximum possible
* number of bytes in a modbus frame is received the frame is
* ignored.
*/
case STATE_RX_RCV://接收其余的字节
if( usRcvBufferPos < MB_SER_PDU_SIZE_MAX )
{
ucRTUBuf[usRcvBufferPos++] = ucByte;
}
else
{
eRcvState = STATE_RX_ERROR;
}
vMBPortTimersEnable( );
break;
}
return xTaskNeedSwitch;
}
六、xMBRTUTransmitFSM()函数讲解。pxMBFrameCBTransmitterEmpty = xMBRTUTransmitFSM;
串口发送中断中调用prvvUARTTxReadyISR()函数,继续调用pxMBFrameCBTransmitterEmpty()函数,pxMBFrameCBTransmitterEmpty为函数指针,最终调用xMBRTUTransmitFSM()函数;
BOOL
xMBRTUTransmitFSM( void )
{
BOOL xNeedPoll = FALSE;assert( eRcvState == STATE_RX_IDLE );
switch ( eSndState )
{
/* We should not get a transmitter event if the transmitter is in
* idle state. */
case STATE_TX_IDLE:
/* enable receiver/disable transmitter. */
vMBPortSerialEnable( TRUE, FALSE );
break;case STATE_TX_XMIT:
/* check if we are finished. */
if( usSndBufferCount != 0 )
{
xMBPortSerialPutByte( ( CHAR )*pucSndBufferCur );
pucSndBufferCur++; /* next byte in sendbuffer. */
usSndBufferCount--;
}
else
{
xNeedPoll = xMBPortEventPost( EV_FRAME_SENT );
/* Disable transmitter. This prevents another transmit buffer
* empty interrupt. */
vMBPortSerialEnable( TRUE, FALSE );
eSndState = STATE_TX_IDLE;
}
break;
}return xNeedPoll;
}
七、xMBRTUTimerT35Expired()函数讲解,初始化
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:
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;
}
八、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.
*/
if( ulBaudRate > 19200 )
{
usTimerT35_50us = 35; /* 1800us. */
}
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;
源码讲解over,我也是第一次这么仔细的分析协议栈,不过还是挺有意思。
下篇讲解从机的接收机制,敬请期待