物联网实战--驱动篇之(二)Modbus协议

目录

一、modbus简介

二、功能码01、02

三、modbus解析

四、功能码03、04

五、功能码05

六、功能码06

七、功能码16


一、modbus简介

        我们在网上查阅modbus的资料发现很多很杂,modbus-RTU   ASCII  TCP等等,还有跟PLC结合的,地址还分1开头的,4开头的,搞得有点懵,那其实是各行业有各自差异化的规则而已,实际上modbus就是  地址码+功能码+数据区+校验码  了,这是核心,具体可以看这篇比较简洁。modbus rtu六种功能码详细解析-电子发烧友网

        对于我们物联网领域而言,就是方便在主机端接入其它厂家的传感器或执行器设备就是了,比如温湿度、PH计等等,厂家在生产的时候产品定位就是配套应用商,所以modbus协议基本是标配,只是不同厂家的传感器modbus的数据地址定义不一样而已;当然,也有少数厂家用自定义协议,非要用的话就得多费时间去了解它的协议了,那modbus协议的传感器我们如果有驱动的话请求或解析就可以直接复用了,下图是TB上随便搜索的485温湿度传感器的通讯协议。

        另外,modbus对于我们来讲,物理层一般都是RS485的,当然了,要用RS232或TTL串口,甚至是4G、LORA也都是可以的。modbus协议使用的时候一般是主机发送请求,从机回复结果的流程,主机可以请求传感器数据,也可以设置内容,比如控制继电器开关,一应一答,每次轮询根据波特率要有一定间隔,一般几十到几百毫秒,像RS485的波特率不要太高,一般是9600,如果距离有几百米建议更低,4800或2400。在实际工程项目中,主机跟从机的485通讯经常会有莫名其妙的问题(通讯不上或者乱码),这个就要靠经验解决了。

        那我们写modbus驱动文件的意义在哪里,要怎么写?就跟之前用过的mqtt协议一样,我们这个驱动程序主要作用就是组合报文和解析报文,这样当你拿到一个厂家的modbus传感器的时候,直接调用这个驱动文件的函数就可以请求数据了,最后再根据厂家对数据的定义进行应用层解析就行了。接下来我们就几个常用的功能码做详细介绍。

二、功能码01、02

        01的作用就是读取开关量输出状态,02的作用是读取IO输入状态,其实都差不多,返回的数据中,正常每个bit位代表各自的开关/输入状态,比如数据如下:

请求:01 02 00 00 00 04 79 C9

返回:01 02 01 0B E0 4F

这里面的含义是请求起始数据地址为0x0000的4个输入状态,返回的是地址码01、功能码02、数据长度01、数据区0B和CRC校验码E0 4F,那对于应用层来讲,有用的数据就是0B了,我们现在把0B换成二进制显示是 0000 1011,那么这个数据正常理解就是1、2、4路输入触发,3路正常,具体的要以厂家提供的资料为准。功能码01也是一个道理的。这里面,如果你请求的寄存器数量小于8个,那从机会返回一个字节数据,每个bit代表一个寄存器状态,多余的是高位无效,一般用0代替。

下面是具体的请求代码:


/*		
================================================================================
描述 :modbus 0x01的报文组合
输入 : 
输出 :  
================================================================================
*/
u16 drv_modbus_send_fun01(u8 slave_addr, u16 reg_start, u16 reg_num, u8 *make_buff, u16 make_size)
{if(make_size<20){return 0;}u16 make_len=0;u16 crcValue;make_buff[make_len++]=slave_addr;make_buff[make_len++]=0x01;make_buff[make_len++]=reg_start>>8;make_buff[make_len++]=reg_start;make_buff[make_len++]=reg_num>>8;make_buff[make_len++]=reg_num;crcValue=drv_crc16(make_buff, make_len);make_buff[make_len++]=crcValue>>8;make_buff[make_len++]=crcValue;return make_len;
}/*		
================================================================================
描述 :modbus 0x02的报文组合
输入 : 
输出 :  
================================================================================
*/
u16 drv_modbus_send_fun02(u8 slave_addr, u16 reg_start, u16 reg_num, u8 *make_buff, u16 make_size)
{if(make_size<20){return 0;}u16 make_len=0;u16 crcValue;make_buff[make_len++]=slave_addr;make_buff[make_len++]=0x02;make_buff[make_len++]=reg_start>>8;make_buff[make_len++]=reg_start;make_buff[make_len++]=reg_num>>8;make_buff[make_len++]=reg_num;crcValue=drv_crc16(make_buff, make_len);make_buff[make_len++]=crcValue>>8;make_buff[make_len++]=crcValue;return make_len;
}

        代码的核心就是根据从机地址、寄存器起始地址、寄存器数量来组合报文,应用层再把这个报文发送出去,至于是使用RS485还是RS232都是可以的,驱动层不关心。

三、modbus解析

        对于协议解析就很有讲究了,我看到的大部分解析代码都是数据包丢进解析函数里,然后函数直接就CRC校验,出错就返回了,这样的解析代码其实稳定性不太好,因为实际传输的时候会经常莫名其妙的数据头或尾巴多出个00或FF或者其它数据,但是完整正确的数据包又在里面,这样CRC校验肯定是错的,所以这里我把解析函数升级了下,这样鲁棒性会好点。

/*		
================================================================================
描述 : modbus 基础数据解析
输入 : 
输出 : 
================================================================================
*/
u16 drv_modbus_parse_base(u8 slave_addr, u8 fun_code, u8 *in_buff, u16 in_len, u8 *out_buff, u16 out_size)
{u16 recv_len=in_len, crcValue;u8 *pData=in_buff;u8 data_len=0;if(recv_len<4 || recv_len>250)return 0;for(u8 i=0;i+4<recv_len;i++,pData++){if(pData[0]==slave_addr && pData[1]==fun_code)//比较地址和功能码{data_len=pData[2];crcValue=pData[data_len+3]<<8|pData[data_len+4];if(crcValue==drv_crc16(pData, data_len+3)){if(data_len<out_size){memcpy(out_buff, &pData[3], data_len);}else{data_len=0;}          break;}						}				}return data_len;
}

        首先要声明的是这个代码适合读取数据的功能码,比如01、02、03和04,对于设置功能码05、06成功了直接返回相同报文,可以用字符串匹配的方式进行确认,这里先略过。这段代码的核心是匹配用户需要的从机地址和功能码,这就相当于数据标识头了,有了这个以后,至少可以过滤掉前面无用的干扰数据了,之后的第三字节就是数据长度了,有了这个长度值就能够准确地做CRC校验了, 校验成功之后把数据区的内容复制出来就可以了,剩下的是应用层的事情了。

        对于功能码01和02,对应的解析函数就是调用上面的函数就行了,具体如下。


/*		
================================================================================
描述 : modbus 0x01 数据解析
输入 : 
输出 : 
================================================================================
*/
u16 drv_modbus_parse_fun01(u8 slave_addr, u8 *in_buff, u16 in_len, u8 *out_buff, u16 out_size)
{return drv_modbus_parse_base(slave_addr, 0x01, in_buff, in_len, out_buff, out_size);
}/*		
================================================================================
描述 : modbus 0x02 数据解析
输入 : 
输出 : 
================================================================================
*/
u16 drv_modbus_parse_fun02(u8 slave_addr, u8 *in_buff, u16 in_len, u8 *out_buff, u16 out_size)
{return drv_modbus_parse_base(slave_addr, 0x02, in_buff, in_len, out_buff, out_size);
}

四、功能码03、04

        03和04功能码比较相似,差别在于03是保持寄存器,可读可写,04是输入寄存器,只读。比如热敏温度头的数据一般就用04来读取,主机不能改变;空调的设定温度一般用03来读取,同时可以用06来设置更改。具体组合报文代码如下:


/*		
================================================================================
描述 :modbus 0x03的报文组合
输入 : 
输出 :  
================================================================================
*/
u16 drv_modbus_send_fun03(u8 slave_addr, u16 reg_start, u16 reg_num, u8 *make_buff, u16 make_size)
{if(make_size<20){return 0;}u16 make_len=0;u16 crcValue;make_buff[make_len++]=slave_addr;make_buff[make_len++]=0x03;make_buff[make_len++]=reg_start>>8;make_buff[make_len++]=reg_start;make_buff[make_len++]=reg_num>>8;make_buff[make_len++]=reg_num;crcValue=drv_crc16(make_buff, make_len);make_buff[make_len++]=crcValue>>8;make_buff[make_len++]=crcValue;return make_len;
}/*		
================================================================================
描述 :modbus 0x04的报文组合
输入 : 
输出 :  
================================================================================
*/
u16 drv_modbus_send_fun04(u8 slave_addr, u16 reg_start, u16 reg_num, u8 *make_buff, u16 make_size)
{if(make_size<20){return 0;}u16 make_len=0;u16 crcValue;make_buff[make_len++]=slave_addr;make_buff[make_len++]=0x04;make_buff[make_len++]=reg_start>>8;make_buff[make_len++]=reg_start;make_buff[make_len++]=reg_num>>8;make_buff[make_len++]=reg_num;crcValue=drv_crc16(make_buff, make_len);make_buff[make_len++]=crcValue>>8;make_buff[make_len++]=crcValue;return make_len;
}

        跟01、02功能码是差不多的,解析代码也是类似:

/*		
================================================================================
描述 : modbus 0x03 数据解析
输入 : 
输出 : 
================================================================================
*/
u16 drv_modbus_parse_fun03(u8 slave_addr, u8 *in_buff, u16 in_len, u8 *out_buff, u16 out_size)
{return drv_modbus_parse_base(slave_addr, 0x03, in_buff, in_len, out_buff, out_size);
}/*		
================================================================================
描述 : modbus 0x04 数据解析
输入 : 
输出 : 
================================================================================
*/
u16 drv_modbus_parse_fun04(u8 slave_addr, u8 *in_buff, u16 in_len, u8 *out_buff, u16 out_size)
{return drv_modbus_parse_base(slave_addr, 0x04, in_buff, in_len, out_buff, out_size);
}

        应用层数据解析以文章开头的温湿度为例,调用drv_modbus_parse_fun03函数后,out_buff内的数据就是02 92 FF 9B四个字节,具体的转换如下图所示。

五、功能码05

        其作用是设置单路输出,比如第二路继电器开,开就往寄存器内设置FF 00,关就设置00 00,设置成功后就直接返回原数据。那么,对于05功能码的返回要如何处理呢?两个选择,一个是用我的工程里drv_common.c的memstr函数做匹配,看下返回的数据包里有没有包含刚才设置的数据串;另一个选择是直接忽略,有没有设置成功不要在这里观测,而是用01功能码实时读取输出状态值,如果状态不匹配要怎么处理由应用层自己决定,比如重复执行3次后仍然失败那就向用户发端出故障信息,人工介入等等。

/*		
================================================================================
描述 :modbus 0x05的报文组合
输入 : 
输出 :  
================================================================================
*/
u16 drv_modbus_send_fun05(u8 slave_addr, u16 reg_start, u16 reg_value, u8 *make_buff, u16 make_size)
{if(make_size<20){return 0;}u16 make_len=0;u16 crcValue;make_buff[make_len++]=slave_addr;make_buff[make_len++]=0x05;make_buff[make_len++]=reg_start>>8;make_buff[make_len++]=reg_start;make_buff[make_len++]=reg_value>>8;make_buff[make_len++]=reg_value;	crcValue=drv_crc16(make_buff, make_len);make_buff[make_len++]=crcValue>>8;make_buff[make_len++]=crcValue;return make_len;
}

六、功能码06

        06和03对应的寄存器是一样的,03读06写,比如空调预设温度、净化器预设转速等这些都可以叫保持寄存器。有点区别是 03可以批量读取连续的寄存器,06只能单个设置,06的具体代码如下:


/*		
================================================================================
描述 :modbus 0x06的报文组合
输入 : 
输出 :  
================================================================================
*/
u16 drv_modbus_send_fun06(u8 slave_addr, u16 reg_start, u16 reg_value, u8 *make_buff, u16 make_size)
{if(make_size<20){return 0;}u16 make_len=0;u16 crcValue;make_buff[make_len++]=slave_addr;make_buff[make_len++]=0x06;make_buff[make_len++]=reg_start>>8;make_buff[make_len++]=reg_start;make_buff[make_len++]=reg_value>>8;make_buff[make_len++]=reg_value;	crcValue=drv_crc16(make_buff, make_len);make_buff[make_len++]=crcValue>>8;make_buff[make_len++]=crcValue;return make_len;
}

        06的返回解析跟05类似,正常直接忽略就行了,用03功能码去监测到底有没有设置成功。

七、功能码16

        16是十进制的,也就是16进制的0x10功能码,是06的扩展,它可以批量设置寄存器,稍微复杂点,具体看如下代码:

/*		
================================================================================
描述 : modbus 0x10的报文组合
输入 : 
输出 : 
================================================================================
*/
u16 drv_modbus_send_fun16(u8 slave_addr, u16 reg_start, u16 reg_num, u8 *reg_data, u8 *make_buff, u16 make_size)
{u16 make_len=0;u16 crcValue;u8 	data_len=reg_num*2;if(make_size<10+data_len)return 0;make_buff[make_len++]=slave_addr;make_buff[make_len++]=0x10;make_buff[make_len++]=reg_start>>8;make_buff[make_len++]=reg_start; //寄存器起始地址make_buff[make_len++]=reg_num>>8;make_buff[make_len++]=reg_num;	//寄存器数量make_buff[make_len++]=data_len;//数据区长度memcpy(&make_buff[make_len], reg_data, data_len);//数据区make_len+=data_len;crcValue=drv_crc16(make_buff, make_len);make_buff[make_len++]=crcValue>>8;make_buff[make_len++]=crcValue;return make_len;		
}

        返回也是忽略就行了,用03去读取监测。

modbus的解析大概就是这样了,完整版的内容比较多,但是根据平时项目的使用频率来看,常用的就这些了,其他的要学习只能自己再找找资料了。

具体代码在这里下载https://download.csdn.net/download/ypp240124016/89091325

工程原来上传过了,自己添加驱动程序测试就行了。https://download.csdn.net/download/ypp240124016/89044525

本项目的交流QQ群:701889554

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/804790.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C语言进阶课程学习记录-第29课 - 指针和数组分析(下)

C语言进阶课程学习记录-第29课 - 指针和数组分析&#xff08;下&#xff09; 数组名与指针实验-数组形式转换实验-数组名与指针的差异实验-转化后数组名加一的比较实验-数组名作为函数形参小结 本文学习自狄泰软件学院 唐佐林老师的 C语言进阶课程&#xff0c;图片全部来源于课…

【JavaWeb】Day39.MySQL概述——数据库设计-DQL(二)

数据库设计-DQL 聚合函数 聚合函数查询就是纵向查询&#xff0c;它是对一列的值进行计算&#xff0c;然后返回一个结果值。&#xff08;将一列数据作为一个整体&#xff0c;进行纵向计算&#xff09; 语法&#xff1a; select 聚合函数(字段列表) from 表名 ; 注意 : 聚合…

软件设计—接口安全设计规范

1.token授权机制 2.https传输加密 3.接口调用防滥用 4.日志审计里监控 5.开发测试环境隔离&#xff0c;脱敏处理 6.数据库运维监控审计 软件项目相关全套精华资料包获取方式①&#xff1a;点我获取 获取方式②&#xff1a;本文末个人名片直接获取。

高校人事管理系统业务分析

目标用户 大学人事部门&#xff0c;部门、院系、任务 解决问题 人事部门按业务划分了很多科室、数据分散、工作流程杂乱、工作效率低。 主要功能模块 人事综合管理平台、个人自助服务平台、人才招聘管理系统、薪酬管理子系统、职称评审子系统、绩效考核子系统组成。

泛零售行业大会员经营的业务挑战与应对策略

​泛零售企业发展到成规模阶段一定会沉淀大量会员&#xff0c;在当前的市场竞争下&#xff0c;企业的经营重点在关注增量市场的同时&#xff0c;也会聚焦对存量会员的价值深挖&#xff0c;提升会员忠诚度&#xff0c;实现“以客户体验为中心、以数据驱动运营”。 对于多业态、…

小程序打开空白的问题处理

小程序打开是空白的&#xff0c;如下&#xff1a; 这个问题都是请求域名的问题&#xff1a; 一、检查服务器域名配置了 https没有&#xff0c;如果没有&#xff0c;解决办法是申请个ssl证书&#xff0c;具体看这里 https://doc.crmeb.com/mer/mer2/4257 二、完成第一步后&#…

基于springboot实现墙绘产品展示交易平台管理系统项目【项目源码+论文说明】

基于springboot实现墙绘产品展示交易平台系统演示 摘要 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本墙绘产品展示交易平台就是在这样的大环境下诞生&#xff…

RTSP/Onvif视频安防监控平台EasyNVR调用接口返回匿名用户名和密码的原因排查

视频安防监控平台EasyNVR可支持设备通过RTSP/Onvif协议接入&#xff0c;并能对接入的视频流进行处理与多端分发&#xff0c;包括RTSP、RTMP、HTTP-FLV、WS-FLV、HLS、WebRTC等多种格式。平台拓展性强、支持二次开发与集成&#xff0c;可应用在景区、校园、水利、社区、工地等场…

【小程序】常用方法、知识点汇总1

欢迎来到《小5讲堂》 这是《小程序》系列文章&#xff0c;每篇文章将以博主理解的角度展开讲解&#xff0c; 温馨提示&#xff1a;博主能力有限&#xff0c;理解水平有限&#xff0c;若有不对之处望指正&#xff01; 目录 前言请求超时Markdown解析逐行显示效果文本变动事件转发…

【Linux的进程篇章 - 环境变量的理解】

Linux学习笔记---007 Linux之进程优先级、环境变量以及地址空间的理解1、进程优先级1.1、什么是优先级&#xff1f;1.2、为什么要有优先级&#xff1f;1.3、Linux的优先级特点以及查看方式1.4、进程的几个特性 2、环境变量2.1、概念2.2、命令行参数2.2.1、什么是命令行参数&…

自定义类型—结构体

目录 1 . 结构体类型的声明 1.1 结构的声明 1.2 结构体变量的创建与初始化 1.3 结构体的特殊声明 1.4 结构体的自引用 2. 结构体内存对齐 2.1 对齐规则 2.2 为什么存在内存对齐 2.3 修改默认对齐数 3. 结构体传参 4.结构体实现位段 4.1 位段的内存分配 4.3 位段的…

强化学习MPC——(二)

本篇主要介绍马尔科夫决策&#xff08;MDP&#xff09;过程&#xff0c;在介绍MDP之前&#xff0c;还需要对MP&#xff0c;MRP过程进行分析。 什么是马尔科夫&#xff0c;说白了就是带遗忘性质&#xff0c;下一个状态S_t1仅与当前状态有关&#xff0c;而与之前的状态无关。 为…

【重磅消息】2024年中国质量协会正式发布六西格玛项目报告编制要求及撰写模板

2024年&#xff0c;中国质量协会正式发布六西格玛系列项目报告编制要求及撰写模板&#xff08;以下简称模板&#xff09;&#xff0c;模板针对项目报告的项目简介、项目背景、项目选择、项目管理、项目实施、效果总结等几个部分的内容、格式以及撰写注意事项等方面作了详细要求…

【前端捉鬼记】使用nvm切换node版本后再用node -v查看仍然是原来的版本

今天遇到一个诡异的问题&#xff0c;使用nvm切换node版本&#xff0c;明明提示已经切换成功&#xff0c;可是再次查看node版本还是之前的&#xff01; 尝试了很多办法&#xff0c;比如重新打开一个cmd窗口、切换前执行nvm install version都没成功&#xff0c;直到找到这篇文章…

New Phytologist | 丛枝菌根真菌介导的土壤有机质动态过程的新概念框架

8月2日&#xff0c;中国科学院生态环境研究中心陈保冬团队等合作在著名期刊New Phytologist上发表题为"Soil organic matter dynamics mediated by arbuscular mycorrhizal fungi – an updated conceptual framework"的观点类文章&#xff0c;详述了丛枝菌根真菌介导…

App 测试必备 - 建议所有测试人收藏

移动端App性能测试需要关注多个方面&#xff0c;包括响应时间、稳定性、内存使用、CPU使用率、网络性能、电池消耗以及设备兼容性等。通过综合考虑这些方面&#xff0c;并在不同条件下进行全面的测试&#xff0c;可以确保应用程序在各种情况下都能够提供优质的用户体验&#xf…

QGIS操作:制作速率专题图

1、修改配色色带 双击打开的矢量文件&#xff0c;弹出如下图所示的图层属性界面&#xff0c;如下图所示&#xff1b; 点击左侧 符号化&#xff0c;选择色带的变化方式、符号、颜色渐变等方式&#xff1b; 设置每个色带所表示的数值范围&#xff0c;变化模式等内容&#xff1…

《深入Linux内核架构》第2章 进程管理和调度 (2)

目录 2.4 进程管理相关的系统调用 2.4.1 进程复制 2.4.2 内核线程 2.4.3 启动新程序 2.4.4 退出进程 本专栏文章将有70篇左右&#xff0c;欢迎关注&#xff0c;订阅后续文章。 2.4 进程管理相关的系统调用 2.4.1 进程复制 1. _do_fork函数 fork vfork clone都最终调用_…

逻辑卷和磁盘配额

文章目录 一、逻辑卷二、磁盘配额 一、逻辑卷 为什么会出现技术&#xff1f; 分区的缺点&#xff1a; 没有备份功能无法扩容性能取决于硬盘本身 相关概念 LVM 是 Logical Volume Manager 的简称&#xff0c;译为中文就是逻辑卷管理。它是 Linux 下对硬盘分区的一种管理机制。…

玩转儿童数码摄影,儿童人像摄影指南

一、资料前言 本套儿童人像摄影&#xff0c;大小250.91M&#xff0c;共有8个文件。 二、资料目录 《爱孩子爱摄影》.pdf 《六招拍儿童》.pdf 《数码摄影工坊-儿童摄影》.pdf 《专业儿童人像摄影指南》.pdf 宝贝看镜头.pdf 儿童摄影手册.pdf 儿童摄影艺术.pdf 玩转儿童…