您的位置:首页 > 其它

Freemodbus原理分析

2017-10-28 16:38 302 查看
//流程概括

1)初始化串口和定时器,串口先初始化为接收使能,定时器为3.5T时间溢出,启动定时器

2)定时器第一次溢出,更新时间标志

eQueuedEvent = EV_READY;

vMBPortTimersDisable( ); //暂时关掉定时器,为节能,也为后面的接收准备

3) 串口中断接收,说明有来自主机的命令数据

①启动定时器,这有2方面考虑,一是判断接收数据是否延时,二是判断接收数据完成,这样就能有效的实现中断能够接收不等长的数据了

这种方法是不适用DMA中断的另外一种,也更契合Modbus的标准。

② 执行xMBRTUReceiveFSM,就是将串口数据放到接收缓存中,不做任何处理

③ 接收完成后,定时器中断会把 xMBPortEventPost( EV_FRAME_RECEIVED );事件更新。

4)eMBPoll会循环查询 “事件标志”

看到事件标识“EV_FRAME_RECEIVED”执行eMBRTUReceive,这个就是对接收数据进行筛选,处理,

处理完成后,更新事件标志xMBPortEventPost( EV_EXECUTE );

5)根据接收数据筛选的命令,执行对应的功能函数,如果是读命令,则是执行eMBException

eMBFuncReadHoldingRegister( UCHAR * pucFrame, USHORT * usLen ),在这个函数里,还要编写一个自己的项目处理函数eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode )

6)eMBPoll这时候根据事件标志“EV_EXECUTE”,执行eMBRTUSend( UCHAR ucSlaveAddress, const UCHAR * pucFrame, USHORT usLength )

这个函数其实就是打包要回复的数据功能,打包完成后,会启动串口发送中断,

eSndState = STATE_TX_XMIT;

vMBPortSerialEnable( FALSE, TRUE );

串口发送中断,进一步执行xMBRTUTransmitFSM( void )

将要回复的数据通过串口逐字节的发送出去,

然后再次将串口设置为 接收中断,以便接收下一次的查询,同时刷新事件标志

xMBPortEventPost( EV_FRAME_SENT );

vMBPortSerialEnable( TRUE, FALSE );

eSndState = STATE_TX_IDLE;

//代码分析-RTU举例

一、eMBInit(MB_RTU, 9, 0, 9600, MB_PAR_NONE);

1、  eMBErrorCode    eStatus = MB_ENOERR;

ucMBAddress = ucSlaveAddress;
//函数指针赋值
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 );
//eStatus =

xMBPortSerialInit( ucPort, ulBaudRate, 8, eParity )

xMBPortTimersInit( ( USHORT ) usTimerT35_50us )

xMBPortEventInit(  ) )

eMBCurrentMode = eMode;
eMBState = STATE_DISABLED;
xEventInQueue = FALSE;

二、eMBErrorCode  eMBEnable( void )

pvMBFrameStartCur(  );

eRcvState = STATE_RX_INIT;
vMBPortSerialEnable( TRUE, FALSE );  //使能接收中断
vMBPortTimersEnable(  );
eMBState = STATE_ENABLED;

三、timer第一次中断

xMBPortEventPost( EV_READY );

xEventInQueue = TRUE;
eQueuedEvent = EV_READY;

vMBPortTimersDisable(  );
eRcvState = STATE_RX_IDLE;

四、串口/RS485第一次接收到第一个字节
prvvUARTRxISR();

xMBPortSerialGetByte( ( CHAR * ) & ucByte );
usRcvBufferPos = 0;
ucRTUBuf[usRcvBufferPos++] = ucByte;
eRcvState = STATE_RX_RCV;

/* 启动定时器,因为一个字节传输时间不能超过定时器溢出时间(3.5T)这是modbus要求的 */
vMBPortTimersEnable( );

(定时器启动后,这个溢出时间一般都会在一个字节传输完成,否则就停止接收了,
所以下面的动作应该是在定时器未溢出的时间内进行的)

串口接下来再接收数据:
xMBPortSerialGetByte( ( CHAR * ) & ucByte );
ucRTUBuf[usRcvBufferPos++] = ucByte;
vMBPortTimersEnable();    //从这里就可以看出,每接收1个字节,重置一下定时器,
//所以正常来说,接收的过程中,定时器不会溢出的。

串口接收完成后,定时器会第二次溢出:
xMBPortEventPost( EV_FRAME_RECEIVED );

xEventInQueue = TRUE;
eQueuedEvent = EV_FRAME_RECEIVED;

五、在这个过程中,eMBPoll会一直查询 事件标志,接收完成后
xMBPortEventGet( &eEvent )

1.
case EV_FRAME_RECEIVED:
eStatus = peMBFrameReceiveCur( &ucRcvAddress, &ucMBFrame, &usLength );//��ѯ���ս��������������浽ucMBFrame

//其实执行的是eMBRTUReceive

BOOL            xFrameReceived = FALSE;
eMBErrorCode    eStatus = MB_ENOERR;

usMBCRC16( ( UCHAR * ) ucRTUBuf, usRcvBufferPos ) == 0 )    //判断CRC校验

*pucRcvAddress = ucRTUBuf[MB_SER_PDU_ADDR_OFF];
*pusLength = ( USHORT )( usRcvBufferPos - MB_SER_PDU_PDU_OFF - MB_SER_PDU_SIZE_CRC );
*pucFrame = ( UCHAR * ) & ucRTUBuf[MB_SER_PDU_PDU_OFF];		//指针赋值
xFrameReceived = TRUE;							//只需要传输指针即可,不要大堆数据赋值,
//这才是高手应该做的工作

( void )xMBPortEventPost( EV_EXECUTE );	//指针传输完成后,事件标志切换成执行,下面就该执行回复了
break;

2.回复,也是在eMBPoll中执行的
case EV_EXECUTE:
ucFunctionCode = ucMBFrame[MB_PDU_FUNC_OFF];//命令码
eException = MB_EX_ILLEGAL_FUNCTION;		//异常码初始化
for( i = 0; i < MB_FUNC_HANDLERS_MAX; i++ )//查找命令码,执行对应的函数,下面是正确命令码
{
if( xFuncHandlers[i].ucFunctionCode == 0 )
{
break;
}
else if( xFuncHandlers[i].ucFunctionCode == ucFunctionCode )//ÏàµÈ£¬Ôò˵Ã÷±¾»úÖ§³Ö´Ë¹¦ÄÜÂë
{
eException = xFuncHandlers[i].pxHandler( ucMBFrame, &usLength );//Ö´ÐÐÏàÓ¦µÄ¹¦ÄÜÂëµÄ´¦Àíº¯Êý
break;
}
}

/* 接收的数据有误则给返回指针赋值错误码信息 */
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;
}
eStatus = peMBFrameSendCur( ucMBAddress, ucMBFrame, usLength );
}
break;

3.以{0x01,0x03,0x00,0x00,0x00,05,0x85,0xC9}读命令为例,
那么此时:
*pucRcvAddress = 0x01;
*pusLength = 8-1-2=5;
*pucFrame = ( UCHAR * ) & ucRTUBuf[MB_SER_PDU_PDU_OFF];  //注意,这里的接收指针已经把第一个字节-地址和校验给剔除去了,
//所以后面的所有对pucFrame的处理,对应的数组是
//{0x03,0x00,0x00,0x00,0x05}

ucFunctionCode = 0x03;

//执行处理函数,传送的是 *pucFrame,usLen
eMBFuncReadHoldingRegister( UCHAR * pucFrame, USHORT * usLen )		//这个函数主要功能是打包返回数据
{
USHORT          usRegAddress;
USHORT          usRegCount;
UCHAR          *pucFrameCur;
eMBException    eStatus = MB_EX_NONE;
eMBErrorCode    eRegStatus;

usRegAddress = ( USHORT )( pucFrame[MB_PDU_FUNC_READ_ADDR_OFF] << 8 );
usRegAddress |= ( USHORT )( pucFrame[MB_PDU_FUNC_READ_ADDR_OFF + 1] );   //起始地址
usRegAddress++;								//这里比较奇葩,非要把命令中的起始地址加1,
//这可能与modbus标准定义有关吧,
//因为Modbus定义起始地址不为0,最小为1,
//所以这里加1了

usRegCount = ( USHORT )( pucFrame[MB_PDU_FUNC_READ_REGCNT_OFF] << 8 );
usRegCount = ( USHORT )( pucFrame[MB_PDU_FUNC_READ_REGCNT_OFF + 1] );	//要读的寄存器个数

pucFrameCur = &pucFrame[MB_PDU_FUNC_OFF];					//将pucFrame指针赋值
//给局部指针pucFrameCur
*usLen = MB_PDU_FUNC_OFF;							//usLen=0

/* First byte contains the function code. */
*pucFrameCur++ = MB_FUNC_READ_HOLDING_REGISTER;				//打包指针第一个字节是功能码0x03,虽然别扭
//后面有妙用 下面该pucFrameCur[1]了
*usLen += 1;								//usLen = 1;

/* Second byte in the response contain the number of bytes. */
*pucFrameCur++ = ( UCHAR ) ( usRegCount * 2 );				//第2个字节为  字节数 5x2,下面该
//pucFramecur[2]
*usLen += 1;								//usLen = 2

/* Make callback to fill the buffer. */
eRegStatus = eMBRegHoldingCB( pucFrameCur, usRegAddress, usRegCount, MB_REG_READ );  //这个函数功能是赋值,将本地
//的真正寄存器值赋值到pucFrame
//中,这里已经开始从pucFrame[2]
//开始了,指针的威力真的特别大

//做完上面的打包工作,开始准备发送出去
peMBFrameSendCur( ucMBAddress, ucMBFrame, usLength );
//这里就把地址0x01,前面的打包数据指针ucMBFrame,以及打包的数据长度传送给该函数
//其实执行的是
eMBRTUSend( UCHAR ucSlaveAddress, const UCH
ce87
AR * pucFrame, USHORT usLength )
{
OS_CPU_SR cpu_sr=0;
eMBErrorCode    eStatus = MB_ENOERR;
USHORT          usCRC16;

if( eRcvState == STATE_RX_IDLE )
{

pucSndBufferCur = ( UCHAR * ) pucFrame - 1;   //此处非常非常巧妙,传送的数据指针中没有设备ID
//这里的发送指针执行传送指针的前一位,这个用法不知道危险不,
//但是好牛B,效率特别高,不用再copy了,也不用定义一个大数组了
usSndBufferCount = 1;				//为了后面的使用

/* 现在copy 数据到缓存中,其实只需要拷贝几个字节而已,很神奇的套路*/
pucSndBufferCur[MB_SER_PDU_ADDR_OFF] = ucSlaveAddress;			//这样就把地址ID给加上了
usSndBufferCount += usLength;						//用于定位crc校验

/* Calculate CRC16 checksum for Modbus-Serial-Line-PDU. */
usCRC16 = usMBCRC16( ( UCHAR * ) pucSndBufferCur, usSndBufferCount );
ucRTUBuf[usSndBufferCount++] = ( UCHAR )( usCRC16 & 0xFF );
ucRTUBuf[usSndBufferCount++] = ( UCHAR )( usCRC16 >> 8 );

/* Activate the transmitter. */
eSndState = STATE_TX_XMIT;						//激活发送器
vMBPortSerialEnable( FALSE, TRUE );				       //使能串口发送中断
}

//然后在串口发送中断里执行	xMBRTUTransmitFSM( void )
xMBRTUTransmitFSM( void )
{

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 );			//发送完毕后,更新事件标志为 XXX_SEND
/* Disable transmitter. This prevents another transmit buffer
* empty interrupt. */
vMBPortSerialEnable( TRUE, FALSE );					//再使能串口接收使能,以便下次接收
//从这里也可以看出Freemodbus是专为从机设计
//master主机读写时,才进行回应
eSndState = STATE_TX_IDLE;
}
break;
}

return xNeedPoll;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: