FreeMODBUS是一个奥地利人写的Modbus协定。它是一个针对嵌入式套用的一个免费(自由)的通用MODBUS协定的移植。Modbus是一个工业製造环境中套用的一个通用协定。Modbus通信协定栈包括两层Modbus套用层协定,该层定义了数据模式和功能;一层是网路层。
基本介绍
- 中文名freemodbus
- 功能设定串口状态
- 参数xRxEnable及xTxEnable
- 起源地奥地利
协定介绍
FreeMODBUS 提供了RTU/ASCII 传输模式及TCP协定支持。FreeModbus遵循BSD许可证,这意味着用户可以将FreeModbus套用于商业环境中。版本FreeModbus-V1.5提供如下的功能支持
表1 FreeModbus-V1.5功能支持
代码 | 描述 | 是否支持 | 备注 |
Master | 主机 | 否 | |
Slave | 从机 | 是 | |
MB_RTU | RTU模式 | 是 | |
MB_ASCII | ASCII模式 | 是 | |
MB_TCP | TCP模式 | 是 | |
0x01 | 读线圈 | 是 | |
0x02 | 读离散输入 | 是 | |
0x03 | 读保持暂存器 | 是 | |
0x04 | 读输入暂存器 | 是 | |
0x05 | 写单个线圈 | 是 | |
0x06 | 写单个暂存器 | 是 | |
0x07 | 读异常状态 | 否 | |
0x08 | 诊断 | 否 | |
0x0B | 获取事件计数器 | 否 | |
0x0C | 获取事件记录 | 否 | |
0x0F | 写多个线圈 | 是 | |
0x10 | 写多个暂存器 | 是 | |
0x11 | 报告从机ID | 是 | 协定与文档不一致 |
0x14 | 读档案记录 | 否 | |
0x15 | 写档案记录 | 否 | |
0x16 | 禁止写暂存器 | 否 | |
0x17 | 读/写多个暂存器 | 是 | |
0x18 | 写FIFO | 否 | |
0x2B | 封装接口传输 | 否 | |
0x2B/0x0D | CANopen参考请求与应答 | 否 | |
0x2B/0x0E | 读设备身份表示 | 否 |
硬体需求
FreeModbus协定对硬体的需求非常少——基本上任何具有串列接口,并且有一些能够容纳modbus数据帧的RAM的微控制器都足够了。
u 一个异步串列接口,能够支持接收缓冲区满和传送快取区空中断。
u 一个能够产生RTU传输所需要的t3.5字元逾时定时器的时钟。
对于软体部分,仅仅需要一个简单的事件伫列。在使用作业系统的处理器上,可通过单独定义一个任务完成Modbus时间的查询。小点的微控制器往往不允许使用作业系统,在那种情况下,可以使用一个全局变数来实现该事件伫列(Atmel AVR 移植使用这种方式实现)。
实际的存储器需求决定于所使用的Modbus模组的多少。下表列出了所支持的功能编译后所需要的存储器。ARM是使用GNUARM编译器3.4.4使用-O1选项得到的。AVR项数值是使用WinAVR编译器3.4.5使用-Os选项编译得到的。
表2 FreeModbus对硬体的需求
Module | ARM Code | ARM RAM (static) | AVR Code | AVR RAM (static) |
Modbus RTU (Required) | 1132Byte | 272Byte | 1456Byte | 266Byte |
Modbus ASCII (Optional) | 1612Byte | 28Byte | 1222Byte | 16Byte |
Modbus Functions [1] | 1180Byte | 34Byte | 1602Byte | 34Byte |
Modbus Core (Required) | 924Byte | 180Byte | 608Byte | 75Byte |
Porting Layer (Required [2]) | 1756Byte | 16Byte | 704Byte | 7Byte |
Totals | 7304Byte | 530Byte | 5592Byte | 398Byte |
[1] 实际大小决定于可支持的Modbus功能码的多少。功能码可以在头档案mbconfig.h中进行配置。
[2] 决定于硬体。
移植
1、 物理层接口档案的修改
在物理层,用户只需完成串列口及逾时定时器的配置即可。具体应修改接口档案portserial.c及porttimer.c。
u portserial.c中函式的修改
1) void vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
此函式的功能为设定串口状态。有两个参数xRxEnable及xTxEnable。当xRxEnable为真时,应使能串口接收及接收中断。在RS485通讯系统中,还要注意将RS485接口晶片设为接收使能状态;当xTxEnable为真时,应使能串口传送及传送中断。在RS485通讯系统中,还要注意将RS485接口晶片设为传送使能状态。
2) void vMBPortClose( void )
此函式的功能是关闭Modbus通讯连线埠,具体的,应在此函式中关闭通讯连线埠的传送使能及接收使能。
3) BOOL xMBPortSerialInit(UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity)
此函式的功能是初始化串列通讯连线埠。有四个参数ucPORT、ulBaudRate、ucDataBits及eParity。参数ucPORT可以忽略;参数ulBaudRate是通讯连线埠的波特率,应根据此数值设定所使用硬体连线埠的波特率;参数ucDataBits为通讯时所使用的数据位宽,注意,若使用RTU模式,则有ucDataBits=8,若使用ASCII模式,则有ucDataBits=7,应根据此参数设定所使用硬体连线埠的数据位宽;eParity为校验方式,eParity=MB_PAR_NONE为无校验,此时硬体连线埠应设定为无校验方式及两个停止位,eParity=MB_PAR_ODD为奇校验,此时硬体连线埠应设定为奇校验方式及一个停止位,eParity= MB_PAR_EVEN为偶校验,此时硬体连线埠应设定为偶校验方式及一个停止位。函式返回值务必为TRUE。
4) BOOL xMBPortSerialPutByte(CHAR ucByte)
此函式的功能为通讯连线埠传送一位元组数据。参数为ucByte,待传送的数据。应在此函式中编写传送一位元组数据的函式。注意,由于使用的是中断髮送,故只需将数据放到传送暂存器即可。函式返回值务必为TRUE。
5) BOOL xMBPortSerialGetByte( CHAR pucByte )
此函式的功能为通讯连线埠接收一位元组数据。参数为 pucByte,接收到的数据。应在此函式中编写接收的函式。注意,由于使用的是中断接收,故只需将接收暂存器的值放到 pucByte即可。函式返回值务必为TRUE。
6) void prvvUARTTxReadyISR(void)
传送中断函式。此函式无需修改。只需在用户的传送中断函式中调用此函式即可,,用户应在调用此函式后,清除传送中断标誌位。
7) void prvvUARTRxISR(void)
传送中断函式。此函式无需修改。只需在用户的接收中断函式中调用此函式即可,,用户应在调用此函式后,清除接收中断标誌位。
u porttimer.c中函式的修改
1) BOOL xMBPortTimersInit( USHORT usTim1Timerout50us )
此函式的功能为初始化逾时定时器。参数为usTim1Timerout50us,50us的个数。用户应根据所使用的硬体初始化逾时定时器,使之能产生中断时间为usTim1Timerout50us50us的中断。函式返回值务必为TRUE。
2) void vMBPortTimersEnable( )
此函式的功能为使能逾时定时器。用户需在此函式中清除中断标誌位、清零定时器计数值,并重新使能定时器中断。
3) void vMBPortTimersDisable( )
此函式的功能为关闭逾时定时器。用户需在此函式中清零定时器计数值,并关闭定时器中断。
4) void TIMERExpiredISR( void )
定时器中断函式。此函式无需修改。只需在用户的定时器中断中调用此函式即可,,用户应在调用此函式后清除中断标誌位。
2、 套用层回函式的修改
在套用层,用户需要定义所需要使用的暂存器,并修改对应的回函式。回函式有如下几个
1) eMBErrorCode eMBRegInputCB( UCHAR pucRegBuffer, USHORT usAddress, USHORT usNRegs )
输入暂存器回函式。 pucRegBuffer为要添加到协定中的数据,usAddress为输入暂存器地址,usNRegs为要读取暂存器的个数。用户应根据要访问的暂存器地址usAddress将相应输入暂存器的值按顺序添加到pucRegBuffer中。
2) eMBErrorCode eMBRegHoldingCB( UCHAR pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode )
保持暂存器回函式。 pucRegBuffer为要协定中的数据,usAddress为输入暂存器地址,usNRegs为访问暂存器的个数,eMode为访问类型(MB_REG_READ为读保持暂存器,MB_REG_WRITE为写保持暂存器)。用户应根据要访问的暂存器地址usAddress将相应输入暂存器的值按顺序添加到pucRegBuffer中,或将协定中的数据根据要访问的暂存器地址usAddress放到相应保持暂存器中。
3) eMBErrorCode eMBRegCoilsCB( UCHAR pucRegBuffer, USHORT usAddress, USHORT usNCoils, eMBRegisterMode eMode )
读写线圈回函式。 pucRegBuffer为要添加到协定中的数据,usAddress为线圈地址,usNCoils为要访问线圈的个数,eMode为访问类型(MB_REG_READ为读线圈状态,MB_REG_WRITE为写线圈)。用户应根据要访问的线圈地址usAddress将相应线圈的值按顺序添加到pucRegBuffer中,或将协定中的数据根据要访问的线圈地址usAddress放到相应线圈中。
4) eMBErrorCode eMBRegDiscreteCB( UCHAR pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
读离散线圈回函式。 pucRegBuffer为要添加到协定中的数据,usAddress为线圈地址,usNDiscrete为要访问线圈的个数。用户应根据要访问的线圈地址usAddress将相应线圈的值按顺序添加到pucRegBuffer中。
3、 套用层初始化及协定访问
用户只需在主函式中调用协定初始化代码,及讯息处理函式即可。需用户调用的函式有如下几个
1) eMBErrorCode eMBInit( eMBMode eMode, UCHAR ucSlaveAddress, UCHAR ucPort, ULONG ulBaudRate, eMBParity eParity )
协定初始化函式。eMode为所要使用的模式,用户可选MB_RTU(RTU模式)、MB_ASCII(ASCII模式)或MB_TCP(TCP模式);ucSlaveAddress为从机地址,用户根据需要,取值为1~247(0为广播地址,248~255协定保留);ulBaudRate为通信波特率,用户根据需要选用,但务必使主机能支持此波特率;eParity为校验方式,用户根据需要选用,但务必使主机能支持此校验方式。
2) eMBErrorCode eMBSetSlaveID( UCHAR ucSlaveID, BOOL xIsRunning, UCHAR const pucAdditional, USHORT usAdditionalLen )
从机ID设定函式。注意,ID表示的是设备的类型,不同于ucSlaveAddress(从机地址)。对同一通讯系统中,可以有相同的ucSlaveID,但不可以有相同的ucSlaveAddress。ucSlaveID为一位元组的设备ID号;xIsRunning为设备的运行状态,0xFF为运行,0x00为停止; pucAdditional为设备的附加描述,根据需要添加;usAdditionalLen为附加描述的长度(按位元组计算)。此函式不是必须调用的。但当一个Modbus通讯系统中有不同种设备时,应调用此函式添加对应设备的描述。
3) eMBErrorCode eMBPoll( void )
轮询事件查询处理函式。用户需在主循环中调用此函式。对于使用作业系统的程式,应单独创建一个任务,使作业系统能周期调用此函式。
初始化及运行
FreeModbus是基于讯息伫列的协定。协定通过检测相应的讯息来完成对应功能。协定栈的初始化及运行流程如下
1) 调用eMBErrorCode eMBInit( eMBMode eMode, UCHAR ucSlaveAddress, UCHAR ucPort, ULONG ulBaudRate, eMBParity eParity )完成物理层设备的初始化,主要包括
BOOL xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )串口初始化,设定波特率、数据位数、校验方式;BOOL xMBPortTimersInit( USHORT usTim1Timerout50us )定时器初始化,设定T35定时所需要的定时器常数。
2) 调用(此处非必需)eMBErrorCode eMBSetSlaveID( UCHAR ucSlaveID, BOOL xIsRunning,UCHAR const pucAdditional, USHORT usAdditionalLen )指定设备ID。
3) 调用eMBErrorCode eMBEnable(void)使能协定栈,主要包括static pvMBFrameStart pvMBFrameStartCur(函式指针)协定栈开始,将eRcvState设为STATE_RX_INIT状态,调用void vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )使能接收,调用void vMBPortTimersEnable( )使能逾时定时器。
4) 在3中使能了逾时定时器,故经过T35时间后,发生第一次逾时中断,在中断中,向协定栈传送讯息EV_READY(Startup finished),并调用void vMBPortTimersDisable( )关闭逾时定时器,将eRcvState设为STATE_RX_IDLE。此时,协定栈可以接收串口数据。注意,此处启用一次逾时定时器是因为初始化完成时,串口有可能已经有数据,因为无法判断第一个数据是请求的开始,故等待T35,接收下一帧请求。
5) 此时,主函式调用eMBErrorCode eMBPoll( void )检测事件。
6) 若发生串口接收中断,且eRcvState为STATE_RX_IDLE(4中已将eRcvState设为STATE_RX_IDLE),则向接收快取中存入接收到的字元,将eRcvState设为STATE_RX_RCV状态,并清零逾时定时器。在下一个数据来到时,不断将数据存入接收快取,并清零逾时定时器。
7) 如果没有接收完成,则不可能发生逾时中断。发生逾时中断,说明T35时间内未收到新的串口数据,根据Modbus协定的规定,这指示着一帧请求数据接收完成。在中断中,向协定栈传送讯息EV_FRAME_RECEIVED(Frame received),等待协定栈处理此讯息。
8) 主函式调用eMBErrorCode eMBPoll( void )检测到事件EV_FRAME_RECEIVED后,调用static peMBFrameReceive peMBFrameReceiveCur简单判断请求帧数据,并向协定栈传送讯息EV_EXECUTE(Execute function)。
9) 主函式调用eMBErrorCode eMBPoll( void )检测到事件EV_EXECUTE后,根据相应的请求代码查找处理该功能的函式指针来处理该功能。若不是广播讯息,则调用static peMBFrameSend peMBFrameSendCur传送回复讯息,在此函式中,只把要回复的数据複製到了串口快取中,将eSndState设为STATE_TX_XMIT(Transmitter is in transfer state),并通过调用void vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )使能传送中断。注意,传送中断使能后,由于串口传送暂存器本来就是空的,故在使能后将进入传送中断中。
10) 传送中断中,且eSndState为STATE_TX_XMIT(9中已将eSndState设为STATE_TX_XMIT),则将串口快取中的数据传送出去,不断对传送字元个数统计,当传送完成后,向协定栈传送讯息EV_FRAME_SENT(Frame sent)。
11) 主函式调用eMBErrorCode eMBPoll( void )检测到事件EV_FRAME_SENT后,不处理此讯息。
12) 当串口接收到数据后,协定栈将重複6-11处理讯息。
理解
1. 关于mbrtu.c档案的理解
u 宏定义与变数
mbrtu.c档案中定义了RTU模式下的宏定义、全局变数与功能函式。所包含的宏定义与全局变数定义如下
/ ----------------------- 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;
在宏定义中,指明了该模式下所支持的最小请求帧长度为4(1位元组地址+1位元组命令+2位元组校验),最大请求帧长度为256,CRC为两位元组,地址为第一位元组,PDU开始于第二位元组。
在全局变数中,只定义了一个串口快取数组ucRTUBuf[MB_SER_PDU_SIZE_MAX]。由于传送与接收不是同步的,故可採用该快取数组实现Modbus协定。在接收过程中,将所接收到的数据直接存放于快取ucRTUBuf中,在传送过程中,通过指针pucSndBufferCur来访问该数组。
u eMBErrorCode eMBRTUInit( UCHAR ucSlaveAddress, UCHAR ucPort, ULONG ulBaudRate, eMBParity eParity )
此函式为RTU模式的初始化函式。此函式中判断串列口初始化是否成功(通过判断串列口初始化函式的返回值实现。,查看返回值必然先调用该函式,从而完成连线埠初始化),如果成功,则根据波特率计算T35,初始化逾时定时器。
u void eMBRTUStart( void )
此函式为RTU模式开始函式。函式主要功能是,将接收状态eRcvState设为STATE_RX_INIT(Receiver is in initial state),使能接收关闭传送,使能逾时定时器。
u void eMBRTUStop( void )
此函式为RTU模式终止函式。函式主要功能是,关闭接收与传送,关闭逾时定时器。
u eMBErrorCode eMBRTUReceive( UCHAR pucRcvAddress, UCHAR pucFrame, USHORT pusLength )
此函式为RTU接收数据帧信息提取函式。函式主要功能是,将接收帧(存放于快取)的地址指针赋给指针变数pucRcvAddress,将PDU编码首地址赋给指针 pucFrame,将PDU长度地址赋给指针变数pusLength。使用指针访问快取数组,而不是额外开闢快取存放帧信息,大大减少了记忆体的开支。
u eMBErrorCode eMBRTUSend( UCHAR ucSlaveAddress, const UCHAR pucFrame, USHORT usLength )
此函式为RTU回复帧信息组织函式。函式的功能是,此函式使传送内容指针pucSndBufferCur指向pucFrame之前的一个地址,并将该地址内容填充为ucSlaveAddress,并使用直接访问方式向快取数组ucRTUBuf的相应地址记忆体入CRC校验值。注意,此函式中,对ucRTUBuf的访问既有间接方式(指针pucSndBufferCur与pucFrame),又有直接方式(直接向相应地址内写值),比较难理解。
回复帧组织完后,将传送状态eSndState设为STATE_TX_XMIT(Transmitter is in transfer state),并禁止接收使能传送。传送一旦使能,就会进入传送中断,完成相应字元的传送。
u BOOL xMBRTUReceiveFSM( void )
此函式描述了一个接收状态机,供接收中断调用。状态机中,完成串口接收暂存器读取,然后判断相应接收状态eRcvState,实现接收。在STATE_RX_INIT状态,重置逾时定时器,等待逾时中断(逾时中断会把eRcvState设为STATE_RX_IDLE);在STATE_RX_ERROR状态,同样会重置逾时定时器等待逾时中断;在STATE_RX_IDLE状态,会将接收字元个数置零,向快取数组ucRTUBuf中存入接收到的字元,跳入状态STATE_RX_RCV,并使重置逾时定时器;在STATE_RX_RCV状态,不断将接收到的字元存入快取,并统计接收计数,重置逾时定时器,接收计数大于帧最大长度时,会跳入STATE_RX_ERROR状态。
在任何一处发生逾时中断,都会将状态eRcvState置为STATE_RX_IDLE。在接收过程(STATE_RX_RCV)中,发生逾时中断,指示着一帧数据接收完成。
接收状态机如图1所示
图1 接收状态机图
u BOOL xMBRTUTransmitFSM( void )
此函式描述了一个接收状态机,供传送中断调用。状态机中,判断相应传送状态eSndState,实现传送。在STATE_TX_IDLE状态,使能接收关闭传送;在STATE_TX_XMIT状态,调用底层串口传送函式将快取中的字元传送出去,并使传送指针加1,待传送字元数减1,待传送数为0时,将向系统传送事件EV_FRAME_SENT(Frame sent),使能接收关闭传送,并转向STATE_TX_IDLE状态。
传送状态机如图2所示
图2 传送状态机图
u BOOL xMBRTUTimerT35Expired( void )
此函式描述了发生逾时中断时应处理的事务,供逾时中断调用。通过判读接收状态eRcvState来决定要处理的事务,思想上有点像摩尔类型的FSM的输出逻辑。若中断髮生于STATE_RX_INIT,则向系统传送事件EV_READY(Startup finished);若中断髮生于STATE_RX_RCV,则向系统传送事件EV_FRAME_RECEIVED(Frame received);若中断髮生于STATE_RX_ERROR,则跳出,不执行。在每个执行分支结束后,均关闭逾时定时器,并将eRcvState转为STATE_RX_IDLE。,这儿不像FSM的输出逻辑。
2. 关于mb.c档案的理解
u 宏定义与变数
mb.c档案中定义了一系列的宏定义、函式指针及全局变数,并使用优先编译指令预编译一些程式代码。定义与优先编译部分如下
#if MB_RTU_ENABLED == 1
#include "mbrtu.h"
#endif
#if MB_ASCII_ENABLED == 1
#include "mbascii.h"
#endif
#if MB_TCP_ENABLED == 1
#include "mbtcp.h"
#endif
#ifndef MB_PORT_HAS_CLOSE
#define MB_PORT_HAS_CLOSE 0
#endif
/ ----------------------- Static variables ---------------------------------/
static UCHAR ucMBAddress;
static eMBMode eMBCurrentMode;
static enum
{
STATE_ENABLED,
STATE_DISABLED,
STATE_NOT_INITIALIZED
} eMBState = STATE_NOT_INITIALIZED;
/ Functions pointer which are initialized in eMBInit( ). Depending on the
mode (RTU or ASCII) the are set to the correct implementations.
/
static peMBFrameSend peMBFrameSendCur;
static pvMBFrameStart pvMBFrameStartCur;
static pvMBFrameStop pvMBFrameStopCur;
static peMBFrameReceive peMBFrameReceiveCur;
static pvMBFrameClose pvMBFrameCloseCur;
/ Callback functions required by the porting layer. They are called when
an external event has happend which includes a timeout or the reception
or transmission of a character.
/
BOOL( pxMBFrameCBByteReceived ) ( void );
BOOL( pxMBFrameCBTransmitterEmpty ) ( void );
BOOL( pxMBPortCBTimerExpired ) ( void );
BOOL( pxMBFrameCBReceiveFSMCur ) ( void );
BOOL( pxMBFrameCBTransmitFSMCur ) ( void );
/ An array of Modbus functions handlers which associates Modbus function
codes with implementing functions.
/
static xMBFunctionHandler xFuncHandlers[MB_FUNC_HANDLERS_MAX] = {
#if MB_FUNC_OTHER_REP_SLAVEID_ENABLED > 0
{MB_FUNC_OTHER_REPORT_SLAVEID, eMBFuncReportSlaveID},
#endif
#if MB_FUNC_READ_INPUT_ENABLED > 0
{MB_FUNC_READ_INPUT_REGISTER, eMBFuncReadInputRegister},
#endif
#if MB_FUNC_READ_HOLDING_ENABLED > 0
{MB_FUNC_READ_HOLDING_REGISTER, eMBFuncReadHoldingRegister},
#endif
#if MB_FUNC_WRITE_MULTIPLE_HOLDING_ENABLED > 0
{MB_FUNC_WRITE_MULTIPLE_REGISTERS, eMBFuncWriteMultipleHoldingRegister},
#endif
#if MB_FUNC_WRITE_HOLDING_ENABLED > 0
{MB_FUNC_WRITE_REGISTER, eMBFuncWriteHoldingRegister},
#endif
#if MB_FUNC_READWRITE_HOLDING_ENABLED > 0
{MB_FUNC_READWRITE_MULTIPLE_REGISTERS, eMBFuncReadWriteMultipleHoldingRegister},
#endif
#if MB_FUNC_READ_COILS_ENABLED > 0
{MB_FUNC_READ_COILS, eMBFuncReadCoils},
#endif
#if MB_FUNC_WRITE_COIL_ENABLED > 0
{MB_FUNC_WRITE_SINGLE_COIL, eMBFuncWriteCoil},
#endif
#if MB_FUNC_WRITE_MULTIPLE_COILS_ENABLED > 0
{MB_FUNC_WRITE_MULTIPLE_COILS, eMBFuncWriteMultipleCoils},
#endif
#if MB_FUNC_READ_DISCRETE_INPUTS_ENABLED > 0
{MB_FUNC_READ_DISCRETE_INPUTS, eMBFuncReadDiscreteInputs},
#endif
};
头档案使用优先编译指令,根据Modbus的配置档案中的相应宏开关,预编译所需的头档案,从而减小协定代码量。
全局变数ucMBAddress与eMBCurrentMode分别表示从机地址与当前所选用的Modbus模式。
接下来定义了一系列的函式指针。在初始化函式中,会根据当前所选用的Modbus模式使这些函式指针指向相应模式下的功能函式。
关于功能代码与功能函式,写的特别巧妙定义xMBFunctionHandler类型的结构体数组xFuncHandlers,对于数组中的每一个元素,都可看成一个结构体。xMBFunctionHandler结构体类型在档案mbproto.h中定义如下
typedef struct
{
UCHAR ucFunctionCode;
pxMBFunctionHandler pxHandler;
} xMBFunctionHandler;
pxMBFunctionHandler描述的是一种函式指针类型,在mbproto.h中定义如下
typedef eMBException(pxMBFunctionHandler) (UCHAR pucFrame,USHORT pusLength);
故xFuncHandlers中的每一个元素都具有两个成员ucFunctionCode(功能码)与pxHandler(功能函式指针)。通过相应的宏开关,可选择预编译相应的功能函式(宏开关在档案mbconfig.h中定义)。
u eMBErrorCode eMBInit( eMBMode eMode, UCHAR ucSlaveAddress, UCHAR ucPort, ULONG ulBaudRate, eMBParity eParity )
此函式为Modbus协定初始化函式。函式判断从机地址ucSlaveAddress,若为广播地址,或协定保留地址,或配置档案中未规定的地址,均会使该函式返回一个错误MB_EINVAL(illegal argument)。若地址合法,则会将该地址赋给全局变数ucMBAddress,根据所选用的模式eMode(MB_RTU、MB_ASCII或MB_TCP)初始化相应的函式指针。
以RTU模式为例,pvMBFrameStartCur将指向协定开始函式void eMBRTUStart( void );pvMBFrameStopCur将指向协定终止函式eMBErrorCode eMBRTUSend( UCHAR ucSlaveAddress, const UCHAR pucFrame, USHORT usLength );peMBFrameReceiveCur将指向接收帧信息提取函式eMBErrorCode eMBRTUReceive( UCHAR pucRcvAddress, UCHAR pucFrame, USHORT pusLength );pvMBFrameCloseCur将指向连线埠关闭函式void vMBPortClose( void )(portserial.c中定义);pxMBFrameCBByteReceived将指向接收中断状态机函式BOOL xMBRTUReceiveFSM( void );pxMBFrameCBTransmitterEmpty将指向传送中断状态机函式BOOL xMBRTUTransmitFSM( void );pxMBPortCBTimerExpired将指向逾时中断函式BOOL xMBRTUTimerT35Expired( void )。
完成相应函式指针的初始化之后,会调用该模式的初始化函式完成相应从机地址ucMBAddress、从机连线埠ucPort、从机通信速率ulBaudRate、从机校验方式eParity的初始化。
u eMBErrorCode eMBTCPInit( USHORT ucTCPPort )
Modbus TCP模式初始化函式。只有当配置档案使能对应的宏开关MB_TCP_ENABLED时,才会编译该函式。该函式会初始化所使用的TCP/IP连线埠号,并初始化相应的函式指针。
u eMBErrorCode eMBRegisterCB( UCHAR ucFunctionCode, pxMBFunctionHandler pxHandler )
Modbus功能注册函式。通过该函式,可以定义FreeModbus协定外的功能代码,并注册相应的功能函式。具体如何实现还没具体看。
u eMBErrorCode eMBClose( void )
Modbus连线埠关闭函式。该函式通过函式指针pvMBFrameCloseCur(指向通讯连线埠关闭函式)来停止止Modbus连线埠上的通讯。
u eMBErrorCode eMBEnable( void )
Modbus协定开始函式。该函式通过函式指针pvMBFrameStartCur(指向相应模式下的使能函式)来使能Modbus通讯。
u eMBErrorCode eMBDisable( void )
Modbus协定终止函式。该函式通过函式指针pvMBFrameStopCur(指向相应模式下的终止函式)来终止Modbus协定。
u eMBErrorCode eMBPoll( void )
Modbus事件轮询处理函式。该函式通过查询底层返回来的事件eEvent来决定当前该处理的事务。
处理过程为若事件为EV_READY(Startup finished),则跳出,等待下一次查询;若事件为EV_FRAME_RECEIVED(Frame received),则通过函式指针peMBFrameReceiveCur(指向接收帧信息提取函式)来完成帧信息的提取,并向系统传送EV_EXECUTE(Execute function)事件;若事件为EV_EXECUTE,则根据已经从帧信息中提取到的功能码在xFuncHandlers中查询对应的功能函式指针,查找到后通过函式指针调用相应的功能处理函式来完成帧信息的处理(向快取数组中存放回复PDU),完成处理后,通过函式指针peMBFrameSendCur调用帧传送函式完成回复帧的传送;若事件为EV_FRAME_SENT(Frame sent),则跳出,等待下一次查询。
3. 关于mbconfig.h档案的理解
此档案为Modbus协定的配置档案。在移植时,应根据所选用的目标处理器灵活修改此档案,使之满足需要而代码最小。,若果你的处理器处理能力足够强,可以保持默认配置,或是根据需要,增加相应的功能的配置宏。档案内容及相应解释如下
#ifndef _MB_CONFIG_H
#define _MB_CONFIG_H
//外部C编译器宏开关
#ifdef __cplusplus
PR_BEGIN_EXTERN_C
#endif
/ ----------------------- Defines ------------------------------------------/
/! \brief If Modbus ASCII support is enabled. /
#define MB_ASCII_ENABLED ( 1 )
/! \brief If Modbus RTU support is enabled. /
#define MB_RTU_ENABLED ( 1 )
/! \brief If Modbus TCP support is enabled. /
#define MB_TCP_ENABLED ( 0 )
/! \brief The character timeout value for Modbus ASCII. /
#define MB_ASCII_TIMEOUT_SEC ( 1 )
/! \brief Timeout to wait in ASCII prior to enabling transmitter. /
#ifndef MB_ASCII_TIMEOUT_WAIT_BEFORE_SEND_MS
#define MB_ASCII_TIMEOUT_WAIT_BEFORE_SEND_MS ( 0 )
#endif
/! \brief Maximum number of Modbus functions codes the protocol stack /
#define MB_FUNC_HANDLERS_MAX ( 16 )
/! \brief Number of bytes which should be allocated for the <em>Report Slave ID /
#define MB_FUNC_OTHER_REP_SLAVEID_BUF ( 32 )
/! \brief If the <em>Report Slave ID</em> function should be enabled. /
#define MB_FUNC_OTHER_REP_SLAVEID_ENABLED ( 1 )
/! \brief If the <em>Read Input Registers</em> function should be enabled. /
#define MB_FUNC_READ_INPUT_ENABLED ( 1 )
/! \brief If the <em>Read Holding Registers</em> function should be enabled. /
#define MB_FUNC_READ_HOLDING_ENABLED ( 1 )
/! \brief If the <em>Write Single Register</em> function should be enabled. /
#define MB_FUNC_WRITE_HOLDING_ENABLED ( 1 )
/! \brief If the <em>Write Multiple registers</em> function should be enabled. /
#define MB_FUNC_WRITE_MULTIPLE_HOLDING_ENABLED ( 1 )
/! \brief If the <em>Read Coils</em> function should be enabled. /
#define MB_FUNC_READ_COILS_ENABLED ( 1 )
/! \brief If the <em>Write Coils</em> function should be enabled. /
#define MB_FUNC_WRITE_COIL_ENABLED ( 1 )
/! \brief If the <em>Write Multiple Coils</em> function should be enabled. /
#define MB_FUNC_WRITE_MULTIPLE_COILS_ENABLED ( 1 )
/! \brief If the <em>Read Discrete Inputs</em> function should be enabled. /
#define MB_FUNC_READ_DISCRETE_INPUTS_ENABLED ( 1 )
/! \brief If the <em>Read/Write Multiple Registers</em> function should be enabled. /
#define MB_FUNC_READWRITE_HOLDING_ENABLED ( 1 )
/! @} /
#ifdef __cplusplus
PR_END_EXTERN_C
#endif
#endif
技巧
1、 若Buffer的两个位元组为16位CRC校验值,则对整个Buffer校验时,值为0;