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

FreeModbus应用系列之一相关推荐

  1. 嵌入式Linux 串口编程系列4——EasyARM287开发板通过freemodbus实现Modbus通信

    前面的文章分析了串口的一些基本知识,在工业应用中,串口通信比较常用的协议就是Modbus RTU,freemodbus是一款微型modbus协议栈,之前对各种单片机.小型处理器支持的比较好,从V1.6 ...

  2. STM32F103/107 移植Freemodbus RTU

    1.简介 FreeMODBUS一个奥地利人写的Modbus协议.它是一个针对嵌入式应用的一个免费(自由)的通用MODBUS协议的移植.Modbus是一个工业制造环境中应用的一个通用协议.Modbus通 ...

  3. Cubemx与HAL库系列教程|系统时钟配置详解及源码分析

    STM32时钟系统简介 STM32种类繁多,时钟系统也不尽相同,但基本的还是大差不差,今日小飞哥就F1系列的MCU简单聊一聊STM32的时钟系统 1.时钟种类介绍: 先来看一看时钟树图,包含了整个系统 ...

  4. 当RS485隔离遇见Freemodbus,你碰到问题了吗?

    摘要: 基于RS485物理接口的Modbus总线在工业控制中广泛应用.Freemodbus是一个免费的实用的协议栈,孔丙火(微信公众号:孔丙火)用它实现了一个从站.串口收发信号和RS485芯片的收发使 ...

  5. RT-Thread freemodbus RS485 RTU主机调试 - STM32F107VCT6

    背景 已经搭建好STM32F107VCT6的最小系统,本篇开始调试freemodbus主机,RS485 RTU模式. 算是笔记吧,只要STM32系列一个平台上调通,整个系列调试起来,都不会困难 fre ...

  6. 手把手教你移植FreeModbus到STM32【看评论区引导,领取全套资料包】

    为什么要移植freemodbus 大家好,近期由于一个小项目的需要,要用到Modbus协议进行通信.相信各位工作的小伙伴们,或多或少都要跟Modbus打交道吧.那么,Modbus协议的重要性我自不必多 ...

  7. 单片机程序中,Modbus功能码的回调函数如何编写--FreeModbus从站设计(10)

    FreeModbus从站设计(10)-Modbus功能码的回调函数如何编写 关键词:FreeModbus CubeMX HAL库 串口 功能码 此系列的前面几篇文章,主要是阐述了用HAL库生成keil ...

  8. 妙用postman系列——postman建组、分享

    妙用postman系列--postman建组.分享 添加新的组和请求. 3.生成分享链接 4.导入分享链接

  9. java 手编线程池_死磕 java线程系列之自己动手写一个线程池

    欢迎关注我的公众号"彤哥读源码",查看更多源码系列文章, 与彤哥一起畅游源码的海洋. (手机横屏看源码更方便) 问题 (1)自己动手写一个线程池需要考虑哪些因素? (2)自己动手写 ...

最新文章

  1. 习题8-5 使用函数实现字符串部分复制 (20 分)
  2. Graph Embedding方案之DeepWalk
  3. AWK 学习手札之一: an AWK tutorial
  4. 云数据库RDS_MySQL购买流程_以及购买步骤的建议
  5. Python 03 学习内容整理
  6. Python内置的字符串处理函数整理
  7. C++ 四种智能指针详解
  8. @RunWith@ContextConfiguration进行Spring上下文测试报错
  9. 在Struts 2中使用JSON Ajax
  10. [c#] 服务器提交了协议冲突. Section=ResponseHeader Detail=CR...的解决方案总结
  11. docker gpu 创建 训练环境_巧用 Docker 快速部署 GPU 环境
  12. Linux下C编程入门
  13. python成绩分析系统_用Python编写学生成绩分析系统
  14. 大数据必备的十大工具
  15. 三校生计算机教学计划,三校生高考英语教学计划.doc
  16. 机器视觉中的像素、分辨率、灰度值等概念
  17. 机器学习之——什么是Onehot编码?
  18. java 验证输入的是小数_java判断用户输入的是否至少含有N位小数的实例
  19. parallels恢复linux密码,Parallels安装Kali2.0遇到的问题及解决办法
  20. 【嵌入式开发教程9】手把手教你做平板电脑-WIFI 驱动实验教程

热门文章

  1. ROS 2 ardent apalone安装和使用说明
  2. 无法定位序数 354 于动态链接库LIB
  3. 二、IDEA设置、快捷键和代码模板
  4. 风靡整个DOS时代的Pctools,现已不再,饱受争议的它,又能走多远
  5. CI 漂亮的分页样式(页码样式)
  6. 如何利用网络推广您的餐饮业务?
  7. ros3.30版本以上的动态域名解析3322与changeip成功
  8. tf.keras.layers.MaxPooling2D
  9. N点主机管理系统密码解密代码程序
  10. Nwafu-OJ-1426 Problem W C语言实习题五——1.用指针实现排序