Modbus

Modbus

Modbus-RTU

1 局域网协议——Modbus

声明:
以下内容均为自己学过Modbus之后自己的认知输出。
 何为协议?
协议(Protocol):
百度解释:协议是一个汉语词汇,读音为xié yì,意思是共同计议,协商;经过谈判、协商而制定的共同承认、共同遵守的文件。《隋书·律历志中》等均有相关记载。
协议更像一种协议、一种契约,而在工程技术领域就是一种专定领域的协议。Modbus协议是一种很常用、很成熟的一种串行总线协议。针对协议最好的分层模型是OSI七层网络模型,每一层都规定了此层进行通信需要在增加的协议头或者协议规约。OSI七层模型是学术研究领域划分比较明确的模型,而在实际工程中或多或少不会完全按照OSI七层模型。
在嵌入式单片机领域,只需要物理层、数据链路层、应用层即可。Modbus和CANOPen都属于串行应用层的协议。
物理层:485转换芯片、CAN控制器
数据链路层:专用芯片实现
应用层:主要工作实现的功能逻辑层。
无论是CAN局域网还是485菊花链网络都可用上面三层抽象描述。
协议的目的:进行双方数据通信。

1.1 直观感受Modbus协议

首先要有一个主设备A,从设备B(从设备可以有多个)。
设备A要与从设备B通过某种特定的规约进行通信(即Modbus协议),那么双方就要共同遵守协议规约才行。
先来直观感受一条协议报文:
主机A发送请求:
01 06 00 01 00 17 98 04
01 06 00 01 00 17 98 04
从机地址 功能码 数据地址 数据 CRC
含义:
主机A把数据0x0017(十进制23)写入1号从机地址0x0001数据地址,这样主机A完成一次对从机B的写操作。
字段说明:
从机地址——Modbus可以有多个从机,用于区分从机的地址。主机必须只有一个,所以Modbus是主从通信机制。
功能码:简单理解为主机进行何种操作。(根据Modbus规定的数据模型可以有多种不同类型的操作功能码)
数据地址:重点。我认为要理解Modbus,一定要对数据地址字段仔细深刻的理解。
数据:要写入的数据内容
CRC:为了保证数据能正确的从机进行接收,加以校验机制,仅仅是为了能正确接收帧报文

数据地址:
数据地址相当于你准备向那个index索引的位置写入数据,我们知道Modbus有四种数据模型,线圈、离散量、输入寄存器、保持寄存器。说白了在芯片里面就是4个全局数组。位变量:位:线圈、离散量;字:保持寄存器、输入寄存器。位变量就是UINT8类型,字类型就是UINT16。如下图
在这里插入图片描述
数据地址就是你需要向具体的那个特定全局数组那个索引位置写入数据,每个索引位置都会被赋予不同类型的物理量的含义,索引可以通过enum枚举实现。例如:
有如下保持寄存器:

typedef enum
{
	BAT_VOLTAGE,			// 电池电压
	BAT_CURRENT,			// 电池电流
	BAT_MOS_OPEN_TEMP,	// MOS开启温度
	BAT_MOS_CLOSE_TEMP,	// MOS关闭温度
	MAX_SIZE
};

UINT16 HoldRegs[MAX_SIZE];	//保持寄存器

数据地址是 0x0000 就是写入修改电池字段;0x0001就是写入修改电池电流。
4中数据模型都有自己对应的操作功能码,有对应的数据属性。
主机写完之后,从机一定要应答即响应主站的请求。例如上面那条报文为:
01 06 02 00 01 00 02 34 5C
具体协议的内容不想再讲,讲一下别的方面的东西。
 四种数据模型
我认为如何应用好Modbus协议两个方面:如何对4种数据模型进行规划划分?如何进行通讯架构设计?

1.2 感触

我一般将4种数据模型当做数据库,可以读写、可以获取修改的数据库,我只需要调用相关的功能码。对应子站的设备就会给我对应的数据。一般将4种寄存器定义为全局的静态变量,对外只提供相应的访问接口即功能码操作。

// FreeModbus协议栈4种数据模型定义
//Slave mode:DiscreteInputs variables
USHORT   usSDiscInStart                               = S_DISCRETE_INPUT_START;
#if S_DISCRETE_INPUT_NDISCRETES%8
UCHAR    ucSDiscInBuf[S_DISCRETE_INPUT_NDISCRETES/8+1];
#else
UCHAR    ucSDiscInBuf[S_DISCRETE_INPUT_NDISCRETES/8]  ;
#endif
//Slave mode:Coils variables
USHORT   usSCoilStart                                 = S_COIL_START;
#if S_COIL_NCOILS%8
UCHAR    ucSCoilBuf[S_COIL_NCOILS/8+1]                ;
#else
UCHAR    ucSCoilBuf[S_COIL_NCOILS/8]                  ;
#endif
//Slave mode:InputRegister variables
USHORT   usSRegInStart                                = S_REG_INPUT_START;
USHORT   usSRegInBuf[S_REG_INPUT_NREGS]               ;
//Slave mode:HoldingRegister variables
USHORT   usSRegHoldStart                              = S_REG_HOLDING_START;
USHORT   usSRegHoldBuf[S_REG_HOLDING_NREGS]           ;

在工作中看到一些代码的设计思想,我觉得设计得很不错。对于大多数的子站设备,可以将设备需要和外部设备交互的数据抽象为2类,UINT8、UINT16,字节就是操作位即开关量,2字节就是模拟量,将子站设备的本地数据全部抽象分类为这两种。每类映射到4中数据模型当中,本地设备只需要将数据读写到4中数据模型当中,即本地读写全局静态数组。而对于外部设备应用层协议,只需要和这4种数据模型交互即可(对外部来说就是数据库)。主站控制子站也是通过这些数据库的接口进行数据交互。可以将数据模型抽象封装起来作为一个RTE_DataLib,上层应用协议、算法、任务均只要调用接口进行数据读写操作。不需要关心底层的数据来源。
请仔细思考这种设计的好处
….(思考3分钟)

  1. 屏蔽底层数据变化,如增加、删除带来的上层逻辑的变动
  2. 提高了可维护性、可扩展性、可移植性
  3. 隔离数据的产生者和数据的使用者,将不同层之间区分为数据依赖和行为依赖
    对于数据库底层来说,本地设备(子站)只需要采集、生产所需要的物理量的数据即可,生产完后写入到数据库;对于我上层应用功能来说,我只需要从数据库中读取我逻辑端需要的数据即可。这样以来,可以在各层之间降低数据依赖和行为依赖,说白了,就是一些层只需要数据的,有些层需要调用不同层之间的接口就产生了行为依赖(函数依赖)。一般来说严禁跨层之间产生行为依赖的,层与层之间最好是通过API接口进行访问。
    中间数据库缓存机制的好处:上层应用逻辑可以只关心自己模块的数据依赖,底层的数据可以是真实数据(本地设备自己采集到的),也可以是标定数据(人为输入的测试数据)。这样的系统各个软件方面的非属性功能就会得到提升。
    这个模型最好的应用场景就是:储能系统的电池BMS。)(想听的话后续可以介绍)
     Modbus协议栈实现方案
    阅读了调试了FreeModbus协议栈源码之后,对Modbus协议栈实现的方案有了了解。有一说一,老外写的代码还是有点东西的,虽然这些源码不能在实际项目中使用,但是可以在设计通讯架构之时提供指导方案。比如,当中用到了很多好的机制和策略,分层机制、收发器状态机、功能码抽象封装、超时完帧判断、事件状态转移等。贴上状态机迁移图如下
// 状态机状态定义
typedef enum
{
    EV_READY,                   /*!< Startup finished. */
    EV_FRAME_RECEIVED,          /*!< Frame received. */
    EV_EXECUTE,                 /*!< Execute function. */
    EV_FRAME_SENT               /*!< Frame sent. */
} eMBEventType;
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;

状态迁移
 分层机制
协议栈分层
 功能码操作封装
功能码
功能码回调处理函数
回调函数的具体操作映射到用户层,需用户自己实现。
FreeModbus协议栈的设计原理、通信机制,讲不了,需要单独写一篇博客讲解,很多内容,其实也没多少啦。
 总结
最近了解Modbus协议栈及其应用,结合自己之前看到的代码,提出了几个方面的思考。软件通信架构设计需要深度思考,只是把一些觉得好的地方提出来分享!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值