FreeModbus学习笔记.docx
《FreeModbus学习笔记.docx》由会员分享,可在线阅读,更多相关《FreeModbus学习笔记.docx(17页珍藏版)》请在冰豆网上搜索。
FreeModbus学习笔记
FreeModbus学习笔记
一、FreeModbus简介
FreeMODBUS一个奥地利人写的Modbus协议。
它是一个针对嵌入式应用的一个免费(自由)的通用MODBUS协议的移植。
Modbus是一个工业制造环境中应用的一个通用协议。
Modbus通信协议栈包括两层:
Modbus应用层协议,该层定义了数据模式和功能;另外一层是网络层。
FreeMODBUS提供了RTU/ASCII传输模式及TCP协议支持。
FreeModbus遵循BSD许可证,这意味着用户可以将FreeModbus应用于商业环境中。
目前版本FreeModbus-V1.5提供如下的功能支持:
表1FreeModbus-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对硬件的需求
FreeModbus协议对硬件的需求非常少——基本上任何具有串行接口,并且有一些能够容纳modbus数据帧的RAM的微控制器都足够了。
◆一个异步串行接口,能够支持接收缓冲区满和发送缓存区空中断。
◆一个能够产生RTU传输所需要的t3.5字符超时定时器的时钟。
对于软件部分,仅仅需要一个简单的事件队列。
在使用操作系统的处理器上,可通过单独定义一个任务完成Modbus时间的查询。
小点的微控制器往往不允许使用操作系统,在那种情况下,可以使用一个全局变量来实现该事件队列(AtmelAVR移植使用这种方式实现)。
实际的存储器需求决定于所使用的Modbus模块的多少。
下表列出了所支持的功能编译后所需要的存储器。
ARM是使用GNUARM编译器3.4.4使用-O1选项得到的。
AVR项数值是使用WinAVR编译器3.4.5使用-Os选项编译得到的。
表2FreeModbus对硬件的需求
Module
ARMCode
ARMRAM
(static)
AVRCode
AVRRAM
(static)
ModbusRTU(Required)
1132Byte
272Byte
1456Byte
266Byte
ModbusASCII(Optional)
1612Byte
28Byte
1222Byte
16Byte
ModbusFunctions[1]
1180Byte
34Byte
1602Byte
34Byte
ModbusCore(Required)
924Byte
180Byte
608Byte
75Byte
PortingLayer(Required[2])
1756Byte
16Byte
704Byte
7Byte
Totals
7304Byte
530Byte
5592Byte
398Byte
[1]实际大小决定于可支持的Modbus功能码的多少。
功能码可以在头文件mbconfig.h中进行配置。
[2]决定于硬件。
三、FreeModbus的移植
1、物理层接口文件的修改
在物理层,用户只需完成串行口及超时定时器的配置即可。
具体应修改接口文件portserial.c及porttimer.c。
◆portserial.c中函数的修改:
1)voidvMBPortSerialEnable(BOOLxRxEnable,BOOLxTxEnable)
此函数的功能为设置串口状态。
有两个参数:
xRxEnable及xTxEnable。
当xRxEnable为真时,应使能串口接收及接收中断。
在RS485通讯系统中,还要注意将RS485接口芯片设为接收使能状态;当xTxEnable为真时,应使能串口发送及发送中断。
在RS485通讯系统中,还要注意将RS485接口芯片设为发送使能状态。
2)voidvMBPortClose(void)
此函数的功能是关闭Modbus通讯端口,具体的,应在此函数中关闭通讯端口的发送使能及接收使能。
3)BOOLxMBPortSerialInit(UCHARucPORT,ULONGulBaudRate,UCHARucDataBits,eMBParityeParity)
此函数的功能是初始化串行通讯端口。
有四个参数:
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)BOOLxMBPortSerialPutByte(CHARucByte)
此函数的功能为通讯端口发送一字节数据。
参数为:
ucByte,待发送的数据。
应在此函数中编写发送一字节数据的函数。
注意,由于使用的是中断发送,故只需将数据放到发送寄存器即可。
函数返回值务必为TRUE。
5)BOOLxMBPortSerialGetByte(CHAR*pucByte)
此函数的功能为通讯端口接收一字节数据。
参数为:
*pucByte,接收到的数据。
应在此函数中编写接收的函数。
注意,由于使用的是中断接收,故只需将接收寄存器的值放到*pucByte即可。
函数返回值务必为TRUE。
6)voidprvvUARTTxReadyISR(void)
发送中断函数。
此函数无需修改。
只需在用户的发送中断函数中调用此函数即可,同时,用户应在调用此函数后,清除发送中断标志位。
7)voidprvvUARTRxISR(void)
发送中断函数。
此函数无需修改。
只需在用户的接收中断函数中调用此函数即可,同时,用户应在调用此函数后,清除接收中断标志位。
◆portserial.c中函数的修改:
1)BOOLxMBPortTimersInit(USHORTusTim1Timerout50us)
此函数的功能为初始化超时定时器。
参数为:
usTim1Timerout50us,50us的个数。
用户应根据所使用的硬件初始化超时定时器,使之能产生中断时间为usTim1Timerout50us*50us的中断。
函数返回值务必为TRUE。
2)voidvMBPortTimersEnable()
此函数的功能为使能超时定时器。
用户需在此函数中清除中断标志位、清零定时器计数值,并重新使能定时器中断。
3)voidvMBPortTimersDisable()
此函数的功能为关闭超时定时器。
用户需在此函数中清零定时器计数值,并关闭定时器中断。
4)voidTIMERExpiredISR(void)
定时器中断函数。
此函数无需修改。
只需在用户的定时器中断中调用此函数即可,同时,用户应在调用此函数后清除中断标志位。
2、应用层回函数的修改
在应用层,用户需要定义所需要使用的寄存器,并修改对应的回函数。
回函数有如下几个:
1)eMBErrorCodeeMBRegInputCB(UCHAR*pucRegBuffer,USHORTusAddress,USHORTusNRegs)
输入寄存器回函数。
*pucRegBuffer为要添加到协议中的数据,usAddress为输入寄存器地址,usNRegs为要读取寄存器的个数。
用户应根据要访问的寄存器地址usAddress将相应输入寄存器的值按顺序添加到pucRegBuffer中。
2)eMBErrorCodeeMBRegHoldingCB(UCHAR*pucRegBuffer,USHORTusAddress,USHORTusNRegs,eMBRegisterModeeMode)
保持寄存器回函数。
*pucRegBuffer为要协议中的数据,usAddress为输入寄存器地址,usNRegs为访问寄存器的个数,eMode为访问类型(MB_REG_READ为读保持寄存器,MB_REG_WRITE为写保持寄存器)。
用户应根据要访问的寄存器地址usAddress将相应输入寄存器的值按顺序添加到pucRegBuffer中,或将协议中的数据根据要访问的寄存器地址usAddress放到相应保持寄存器中。
3)eMBErrorCodeeMBRegCoilsCB(UCHAR*pucRegBuffer,USHORTusAddress,USHORTusNCoils,eMBRegisterModeeMode)
读写线圈回函数。
*pucRegBuffer为要添加到协议中的数据,usAddress为线圈地址,usNCoils为要访问线圈的个数,eMode为访问类型(MB_REG_READ为读线圈状态,MB_REG_WRITE为写线圈)。
用户应根据要访问的线圈地址usAddress将相应线圈的值按顺序添加到pucRegBuffer中,或将协议中的数据根据要访问的线圈地址usAddress放到相应线圈中。
4)eMBErrorCodeeMBRegDiscreteCB(UCHAR*pucRegBuffer,USHORTusAddress,USHORTusNDiscrete)
读离散线圈回函数。
*pucRegBuffer为要添加到协议中的数据,usAddress为线圈地址,usNDiscrete为要访问线圈的个数。
用户应根据要访问的线圈地址usAddress将相应线圈的值按顺序添加到pucRegBuffer中。
3、应用层初始化及协议访问
用户只需在主函数中调用协议初始化代码,及消息处理函数即可。
需用户调用的函数有如下几个:
1)eMBErrorCodeeMBInit(eMBModeeMode,UCHARucSlaveAddress,UCHARucPort,ULONGulBaudRate,eMBParityeParity)
协议初始化函数。
eMode为所要使用的模式,用户可选MB_RTU(RTU模式)、MB_ASCII(ASCII模式)或MB_TCP(TCP模式);ucSlaveAddress为从机地址,用户根据需要,取值为1~247(0为广播地址,248~255协议保留);ulBaudRate为通信波特率,用户根据需要选用,但务必使主机能支持此波特率;eParity为校验方式,用户根据需要选用,但务必使主机能支持此校验方式。
2)eMBErrorCodeeMBSetSlaveID(UCHARucSlaveID,BOOLxIsRunning,UCHARconst*pucAdditional,USHORTusAdditionalLen)
从机ID设置函数。
注意,ID表示的是设备的类型,不同于ucSlaveAddress(从机地址)。
对同一通讯系统中,可以有相同的ucSlaveID,但不可以有相同的ucSlaveAddress。
ucSlaveID为一字节的设备ID号;xIsRunning为设备的运行状态,0xFF为运行,0x00为停止;*pucAdditional为设备的附加描述,根据需要添加;usAdditionalLen为附加描述的长度(按字节计算)。
此函数不是必须调用的。
但当一个Modbus通讯系统中有不同种设备时,应调用此函数添加对应设备的描述。
3)eMBErrorCodeeMBPoll(void)
轮询事件查询处理函数。
用户需在主循环中调用此函数。
对于使用操作系统的程序,应单独创建一个任务,使操作系统能周期调用此函数。
四、FreeModbus初始化及运行流程
FreeModbus是基于消息队列的协议。
协议通过检测相应的消息来完成对应功能。
协议栈的初始化及运行流程如下:
1)首先调用eMBErrorCodeeMBInit(eMBModeeMode,UCHARucSlaveAddress,UCHARucPort,ULONGulBaudRate,eMBParityeParity)完成物理层设备的初始化,主要包括:
BOOLxMBPortSerialInit(UCHARucPORT,ULONGulBaudRate,UCHARucDataBits,eMBParityeParity)串口初始化,设定波特率、数据位数、校验方式;BOOLxMBPortTimersInit(USHORTusTim1Timerout50us)定时器初始化,设定T35定时所需要的定时器常数。
2)调用(此处非必需)eMBErrorCodeeMBSetSlaveID(UCHARucSlaveID,BOOLxIsRunning,UCHARconst*pucAdditional,USHORTusAdditionalLen)指定设备ID。
3)调用eMBErrorCodeeMBEnable(void)使能协议栈,主要包括:
staticpvMBFrameStartpvMBFrameStartCur(函数指针)协议栈开始,将eRcvState设为STATE_RX_INIT状态,调用voidvMBPortSerialEnable(BOOLxRxEnable,BOOLxTxEnable)使能接收,调用voidvMBPortTimersEnable()使能超时定时器。
4)在3中使能了超时定时器,故经过T35时间后,发生第一次超时中断,在中断中,向协议栈发送消息EV_READY(Startupfinished),并调用voidvMBPortTimersDisable()关闭超时定时器,同时将eRcvState设为STATE_RX_IDLE。
此时,协议栈可以接收串口数据。
注意,此处首先启用一次超时定时器是因为初始化完成时,串口有可能已经有数据,因为无法判断第一个数据是请求的开始,故等待T35,接收下一帧请求。
5)此时,主函数调用eMBErrorCodeeMBPoll(void)检测事件。
6)若发生串口接收中断,且eRcvState为STATE_RX_IDLE(4中已将eRcvState设为STATE_RX_IDLE),则向接收缓存中存入接收到的字符,同时将eRcvState设为STATE_RX_RCV状态,并清零超时定时器。
在下一个数据来到时,不断将数据存入接收缓存,并清零超时定时器。
7)如果没有接收完成,则不可能发生超时中断。
发生超时中断,说明T35时间内未收到新的串口数据,根据Modbus协议的规定,这指示着一帧请求数据接收完成。
在中断中,向协议栈发送消息EV_FRAME_RECEIVED(Framereceived),等待协议栈处理此消息。
8)主函数调用eMBErrorCodeeMBPoll(void)检测到事件EV_FRAME_RECEIVED后,调用staticpeMBFrameReceivepeMBFrameReceiveCur简单判断请求帧数据,并向协议栈发送消息EV_EXECUTE(Executefunction)。
9)主函数调用eMBErrorCodeeMBPoll(void)检测到事件EV_EXECUTE后,根据相应的请求代码查找处理该功能的函数指针来处理该功能。
若不是广播消息,则调用staticpeMBFrameSendpeMBFrameSendCur发送回复消息,在此函数中,只把要回复的数据复制到了串口缓存中,同时将eSndState设为STATE_TX_XMIT(Transmitterisintransferstate),并通过调用voidvMBPortSerialEnable(BOOLxRxEnable,BOOLxTxEnable)使能发送中断。
注意,发送中断使能后,由于串口发送寄存器本来就是空的,故在使能后将进入发送中断中。
10)发送中断中,且eSndState为STATE_TX_XMIT(9中已将eSndState设为STATE_TX_XMIT),则将串口缓存中的数据发送出去,同时不断对发送字符个数统计,当发送完成后,向协议栈发送消息EV_FRAME_SENT(Framesent)。
11)主函数调用eMBErrorCodeeMBPoll(void)检测到事件EV_FRAME_SENT后,不处理此消息。
12)当串口接收到数据后,协议栈将重复6-11处理消息。
五、一些理解
1.关于mbrtu.c文件的理解
◆宏定义与变量
mbrtu.c文件中定义了RTU模式下的宏定义、全局变量与功能函数。
所包含的宏定义与全局变量定义如下:
/*-----------------------Defines------------------------------------------*/
#defineMB_SER_PDU_SIZE_MIN4/*!
#defineMB_SER_PDU_SIZE_MAX256/*!
#defineMB_SER_PDU_SIZE_CRC2/*!
#defineMB_SER_PDU_ADDR_OFF0/*!
#defineMB_SER_PDU_PDU_OFF1/*!
/*-----------------------Typedefinitions---------------------------------*/
typedefenum
{
STATE_RX_INIT,/*!
STATE_RX_IDLE,/*!
STATE_RX_RCV,/*!
STATE_RX_ERROR/*!
}eMBRcvState;
typedefenum
{
STATE_TX_IDLE,/*!
STATE_TX_XMIT/*!
}eMBSndState;
/*-----------------------Staticvariables---------------------------------*/
staticvolatileeMBSndStateeSndState;
staticvolatileeMBRcvStateeRcvState;
volatileUCHARucRTUBuf[MB_SER_PDU_SIZE_MAX];
staticvolatileUCHAR*pucSndBufferCur;
staticvolatileUSHORTusSndBufferCount;
staticvolatileUSHORTusRcvBufferPos;
首先在宏定义中,指明了该模式下所支持的最小请求帧长度为4(1字节地址+1字节命令+2字节校验),最大请求帧长度为256,CRC为两字节,地址为第一字节,PDU开始于第二字节。
在全局变量中,只定义了一个串口缓存数组ucRTUBuf[MB_SER_PDU_SIZE_MAX]。
由于发送与接收不是同步的,故可采用该缓存数组实现Modbus协议。
在接收过程中,将所接收到的数据直接存放于缓存ucRTUBuf中,在发送过程中,通过指针*pucSndBufferCur来访问该数组。
◆eMBErrorCodeeMBRTUInit(UCHARucSlaveAddress,UCHARucPort,ULONGulBaudRate,eMBParityeParity)
此函数为RTU模式的初始化函数。
此函数中判断串行口初始化是否成功(通过判断串行口初始化函数的返回值实现。
当然,查看返回值必然先调用该函数,从而完成端口初始化),如果成功,则根据波特率计算T35,初始化超时定时器。
◆voideMBRTUStart(void)
此函数为RTU模式开始函数。
函数主要功能是,将接收状态eRcvState设为STATE_RX_INIT(Receiverisininitialstate),使能接收同时关闭发送,使能超时定时器。
◆voideMBRTUStop(void)
此函数为RTU模式终止函数。
函数主要功能是,关闭接收与发送,关闭超时定时器。
◆eMBErrorCodeeMBRTUReceive(UCHAR*pucRcvAddress,UCHAR**pucFrame,USHORT*pusLength)
此函数为RTU接收数据帧信息提取函数。
函数主要功能是,将接收帧(存放于缓存)的地址指针赋给指针变量pucRcvAddress,将PDU编码首地址赋给指针*pucFrame,将PDU长度地址赋给指针变量pusLength。
使用指针访问缓存数组,而不是额外开辟缓存存放帧信息,大大减少了内存的开支。
◆eMBErrorCodeeMBRTUSend(UCHARucSlaveAddress,constUCHAR*pucFrame,USHORTusLength)
此函数为RTU回复帧信息组织函数。
函数的功能是,此函数