上篇文章分析了mb.c文件,我们知道,mb.c只是实现modbus协议的一个框架,具体的RTU、ASCII和TCP相关的代码再具体的文件夹中。这里我们以使用比较多的RTU为例,来分析一下RTU的实现。
rtu文件夹下有四个文件,其说明如下:
文件名称 | 说明 |
---|---|
mbcrc.c | 这个文件只包含一个函数,就是标准的CRC16校验函数 |
mbcrc.h | 包含CRC校验函数的函数声明 |
mbrtu.c | 实现RTU协议的具体函数,rtu协议相关的实现函数都在这个文件中 |
mbrtu.h | 头文件,包含rtu函数的声明 |
这里我们主要分析mbrtu.c文件,其他文件都比较简单。
先贴上代码,再一一说明。
/*
* FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU.
* Copyright (c) 2006-2018 Christian Walter
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
/* ----------------------- System includes ----------------------------------*/
#include "stdlib.h"
#include "string.h"
/* ----------------------- Platform includes --------------------------------*/
#include "port.h"
/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbrtu.h"
#include "mbframe.h"
#include "mbcrc.h"
#include "mbport.h"
/* ----------------------- Defines ------------------------------------------*/
#define MB_SER_PDU_SIZE_MIN 4 /*!< Minimum size of a Modbus RTU frame. */
#define MB_SER_PDU_SIZE_MAX 256 /*!< Maximum size of a Modbus RTU frame. */
#define MB_SER_PDU_SIZE_CRC 2 /*!< Size of CRC field in PDU. */
#define MB_SER_PDU_ADDR_OFF 0 /*!< Offset of slave address in Ser-PDU. */
#define MB_SER_PDU_PDU_OFF 1 /*!< Offset of Modbus-PDU in Ser-PDU. */
/* ----------------------- Type definitions ---------------------------------*/
typedef enum
{
STATE_RX_INIT, /*!< Receiver is in initial state. */
STATE_RX_IDLE, /*!< Receiver is in idle state. */
STATE_RX_RCV, /*!< Frame is beeing received. */
STATE_RX_ERROR /*!< If the frame is invalid. */
} eMBRcvState;
typedef enum
{
STATE_TX_IDLE, /*!< Transmitter is in idle state. */
STATE_TX_XMIT /*!< Transmitter is in transfer state. */
} eMBSndState;
/* ----------------------- Static variables ---------------------------------*/
static volatile eMBSndState eSndState;
static volatile eMBRcvState eRcvState;
volatile UCHAR ucRTUBuf[MB_SER_PDU_SIZE_MAX];
static volatile UCHAR *pucSndBufferCur;
static volatile USHORT usSndBufferCount;
static volatile USHORT usRcvBufferPos;
/* ----------------------- Start implementation -----------------------------*/
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;
}
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( );
}
void
eMBRTUStop( void )
{
ENTER_CRITICAL_SECTION( );
vMBPortSerialEnable( FALSE, FALSE );
vMBPortTimersDisable( );
EXIT_CRITICAL_SECTION( );
}
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 ) )
{
/* 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];
xFrameReceived = TRUE;
}
else
{
eStatus = MB_EIO;
}
EXIT_CRITICAL_SECTION( );
return eStatus;
}
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 );
ucRTUBuf[usSndBufferCount++] = ( UCHAR )( usCRC16 & 0xFF );
ucRTUBuf[usSndBufferCount++] = ( UCHAR )( usCRC16 >> 8 );
/* Activate the transmitter. */
eSndState = STATE_TX_XMIT;
vMBPortSerialEnable( FALSE, TRUE );
}
else
{
eStatus = MB_EIO;
}
EXIT_CRITICAL_SECTION( );
return eStatus;
}
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.
*/
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;
}
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;
}
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;
}
该文件主要实现了数据的接收和发送的相关函数。
首先,用枚举定义了接收器和发送器的状态,列表说明如下:
接收器状态说明:
接收器状态 | 说明 |
---|---|
STATE_RX_INIT | 接收器已初始化 |
STATE_RX_IDLE | 接收器空闲 |
STATE_RX_RCV | 接收器正在接收 |
STATE_RX_ERROR | 接收器错误 |
发送器状态说明:
发送器状态 | 说明 |
---|---|
STATE_TX_IDLE | 发送器空闲 |
STATE_TX_XMIT | 正在发送 |
接着定义了一个数据缓存区、发送缓存指针、发送缓存长度、数据接收长度。
eMBRTUInit()
函数该函数实现了RTU涉及到的UART和定时器的初始化。这里也只是提供一个函数,并未对函数进行实现。由于不同的处理器的UART和TIMER的初始化配置方式不同,所以这部分代码需要我们在移植的时候进行实现。具体等到移植的时候再来分析,这里我们重点看一下这个定时器时间是怎么配置的。
我们知道Modbus没有一帧结束的标记符,我们一般是通过超时时间来判断一帧是不是结束。超时时间一般设置为3.5个字符传送的时间。由于波特率不同对应的字符传送时间也不同,因此我们需要针对不同的波特率进行配置。详细说明如下:
根据eMBRTUInit()
函数我们可以看出,FreeModbus将波特率大于19200的超时时间固定为1750us,其他的按照3.5个字符的传送时间来设置。这里我们定时器配置为每50us中断一次,因此我们只需要计算不同波特率下的定时器的中断次数即可。
1、baudrate>19200时
此种情况下次数T=1750/50=35;所以我们从代码中可以看到波特率大于19200的时候,次数固定为35。
2、baudrate≤19200时
我们知道串口一般发送的格式为一个起始位、8或者9位数据位、一位停止位、一般无校验或者一位校验位。加起来一帧大概有11个二进制位(不同的配置有所差别,这里取11个比较合适)。所以传送一个字符的时间就是11/baudrate(单位:s),传送3.5个字符的时间就是(7/2)*(11/baudrate)(单位:s),由于我们定时器50us中断一次,1s=20000个50us,所以对应的50us的次数就是(7/2)*(11/baudrate)*20000=(7*220000)/(2*baudrate);
eMBRTUStart()
函数该函数比较简单,将接收器的状态设置为初始化状态,然后使能UART接收中断,禁止发送中断,使能定时器中断。
eMBRTUStop()
函数该函数和eMBRTUStart()
函数功能相反,禁止UART接收和发送中断,禁止定时器中断,不再相应UART发送过来的数据。
eMBRTUReceive()
函数这个函数就是接收函数,从上篇文章中我们知道,当数据接收完成之后,轮询函数会获取一个接收完成事件,然后调用数据接收函数来接收数据,mb.c中的接收函数就是绑定的这个函数。由这个函数来做具体的数据接收的活。
详细看一下这个函数。
函数进来首先判断接收数据的长度有没有超过最大值,CRC校验可不可以通过,只有两者都满足了以后才对数据进行处理,这一步骤主要是确保接收到的数据的正确性。数据接收正确以后将接收到的地址、PDU的数据长度和PDU的数据数据帧传给对应的指针。(这里说明一下,PDU是去掉从机地址和CRC校验后的数据)
eMBRTUSend()
函数这个函数就是发送函数,从上篇文章中我们知道,当数据处理完成之后,轮询函数会根据接收到的地址是不是广播帧来判断是否需要发送回帧,然后调用数据发送函数来发送数据,mb.c中的发送函数就是绑定的这个函数。由这个函数来做具体的数据发送的活。
该函数主要工作就是在发送PDU前面填充从机地址,后面填充CRC校验值,更新发送数据的长度,使能UART发送中断。
xMBRTUReceiveFSM()
函数这个函数是UART串口中断接收处理函数和2.7中的发送函数都是在串口中断函数中调用的。其功能就是将串口接收到的数据放在ucRTUBuf数据缓冲区当中。
xMBRTUTransmitFSM()
函数这个函数是串口发送中断处理函数,函数的主要功能就是将缓冲区中的数据发送出去。
xMBRTUTimerT35Expired()
函数这个是超时处理函数,当接收超时时,说明接收完成,发送一个接收完成事件,然后关闭超时中断。
mbrtu.c函数实现了modbus-rtu协议栈。它在整个协议栈中占据承上启下的作用个,向上,给modbus协议提供数据,向下接收硬件接收到的数据和往硬件发送数据。超时处理等等。其中一些功能还需要平台相关的接口的支持。这些在后面具体的移植中再进行分析。