FreeModbus应用总结系列之一
- FreeModbus简介
- FreeModbus的获取
- 硬件需求
- 移植
-
- 1. 物理层接口文件的修改
-
- 1.1portserial.c中函数的修改
- porttimer.c中函数的修改
- 2. 应用层回调函数的修改
- 3. 应用层初始化及协议访问
- 初始化及运行
- FreeModbus启动流程分析
- MODBUS主机协议移植
FreeModbus简介
FreeMODBUS是一个奥地利人写的Modbus协议。它是一个针对嵌入式应用的一个免费(自由)的通用MODBUS协议的移植。Modbus是一个工业制造环境中应用的一个通用协议。Modbus通信协议栈包括两层:Modbus应用层协议,该层定义了数据模式和功能;另外一层是网络层。
FreeModbus提供了RTU/ASCII 传输模式及TCP协议支持。
FreeModbus协议对硬件的需求非常少——基本上任何具有串行接口,并且有一些能够容纳modbus数据帧的RAM的微控制器都足够了。
modbus协议链接:
https://modbus.org/docs/Modbus_Application_Protocol_V1_1b3.pdf
现支持如下功能码:
Read Input Register (0x04)
Read Holding Registers (0x03)
Write Single Register (0x06)
Write Multiple Registers (0x10)
Read/Write Multiple Registers (0x17)
Read Coils (0x01)
Write Single Coil (0x05)
Write Multiple Coils (0x0F)
Read Discrete Inputs (0x02)
Report Slave ID (0x11)
FreeModbus的获取
目前,FreeModbus最新版本是V1.6,可以通过官网下载,链接如下:
https://www.embedded-solutions.at/en/freemodbus-downloads/
可以下载压缩包,也可以使用git工具来下载。
https://github.com/cwalter-at/freemodbus
硬件需求
FreeModbus协议对硬件的需求非常少——基本上任何具有串行接口,并且有一些能够容纳modbus数据帧的RAM的微控制器都足够了。
1)一个异步串行接口,能够支持接收缓冲区满和发送缓存区空中断。
2)一个能够产生RTU传输所需要的t3.5字符超时定时器的时钟。
对于软件部分,仅仅需要一个简单的事件队列。在使用操作系统的处理器上,可通过单独定义一个任务完成Modbus时间的查询。小点的微控制器往往不允许使用操作系统,在那种情况下,可以使用一个全局变量来实现该事件队列(Atmel AVR 移植使用这种方式实现)。
实际的存储器需求决定于所使用的Modbus模块的多少。下表列出了所支持的功能编译后所需要的存储器。ARM是使用GNUARM编译器3.4.4使用-O1选项得到的。AVR项数值是使用WinAVR编译器3.4.5使用-Os选项编译得到的。
移植
1. 物理层接口文件的修改
在物理层,用户只需完成串行口及超时定时器的配置即可。具体应修改接口文件portserial.c及porttimer.c。
1.1portserial.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)
接送中断函数。此函数无需修改。只需在用户的接收中断函数中调用此函数即可,同时,用户应在调用此函数后,清除接收中断标志位。
porttimer.c中函数的修改
1) BOOL xMBPortTimersInit( USHORT usTim1Timerout50us )
此函数的功能为初始化超时定时器。参数为:usTim1Timerout50us,50us的个数。用户应根据所使用的硬件初始化超时定时器,使之能产生中断时间为usTim1Timerout50us*50us的中断。函数返回值务必为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处理消息。
资料来源:
https://baike.baidu.com/item/freemodbus/7566841?fr=aladdin
FreeModbus启动流程分析
资料来源:
http://emb.hqyj.com/Column/7504.html
移植资料:
STM32 HAL库移植FreeModbus详细步骤
https://blog.csdn.net/qq153471503/article/details/104840279
STM32 移植FreeModbus详细过程
http://www.mcublog.cn/software/2020_03/stm32-freemodbus-yizhi/
移植FreeModbus到FreeRTOS系统上
https://blog.csdn.net/dmjkun/article/details/85564634?utm_medium=distribute.pc_relevant.none-task-blog-2defaultbaidujs_title~default-5.no_search_link&spm=1001.2101.3001.4242
stm32裸机移植FreeModbus
https://blog.csdn.net/qq_27508477/article/details/89280822?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7Edefault-16.no_search_link&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7Edefault-16.no_search_link
https://www.stmcu.org.cn/module/forum/forum.php?mod=viewthread&page=1&tid=604864
安富莱电子论坛:
http://www.armbbs.cn/
MODBUS主机协议移植
https://blog.csdn.net/weixin_42462202/article/details/81232347
Modbus协议栈应用实例之一:Modbus RTU主站应用
https://www.cnblogs.com/foxclever/p/13251949.html