Modbus RTU、Modbus 库函数

Modbus RTU

与 Modbus TCP 的区别

一般在工业场景中,使用 Modbus RTU 的场景更多一些,Modbus RTU 基于串行协议进行收发数据,包括 RS232/485 等工业总线协议。采用主从问答式(master / slave)通信。
与 Modbus TCP 不同的是,RTU 没有报文头 MBAP 字段,但是在尾部增加了两个 CRC 检验字节(CRC16),因为网络协议中自带校验,所以在 TCP 协议中不需要使用 CRC 校验码。
RTU 和 TCP 的总体使用方法基本一致,只是在创建 Modbus 对象时有所不同。TCP 需要传入网络socket 信息;而 RTU 需要传入串口相关信息。

特点

通信

采用主从问答式(master / slave)通信,由主机发起,一问一答。

设置串口参数

波特率:9600
数据位:8
停止位:1
无流控

协议格式(地址码 + 功能码 + 数据 + 校验码)

Modbus RTU 数据帧包含:地址码、功能码、数据、校验码。
地址码: 从机 ID
功能码: 同 Modbus TCP
数据: 起始地址、数量、数据
CRC 校验码: 两个字节,对 地址码、功能码、数据 进行校验,可以通过函数自动生成

报文详解

(👆 链接至另一博主,放心跳转)

以 03 功能码为例:

主机 ——> 从机:

在这里插入图片描述

从机 ——> 主机:

在这里插入图片描述

模拟器的安装、配置、使用

实际硬件产品成本较高,可以使用一系列 Modbus 软件模拟器,进行数据模拟,从而分析 Modbus RTU 协议。

所用工具

Modbus Slave、vspd 虚拟串口、UartAssist 串口调试工具、虚拟机

安装与配置

一)vspd 虚拟串口的安装

1)将压缩包解压后,双击 vspd.exe 文件进行安装;
在这里插入图片描述

2)打开软件,添加 COM1 和 COM2 端口(用完之后记得删除端口);
在这里插入图片描述

3)打开设备管理器,出现如下图所示即可;
在这里插入图片描述

4)可以汉化,将 Cracked 下的文件复制到软件安装目录即可。
在这里插入图片描述

二)虚拟机绑定端口

1)VMware 虚拟机(注意不是 ubuntu)在系统关机(必须是关机状态,挂起不行)状态下,
点击:虚拟机 ——> 设置 ——> 硬件 ——> 添加串行端口,添加 COM1;
在这里插入图片描述

2)添加完成后,第一次使用需要将电脑重启;
3)重启之后,打开虚拟机,点击虚拟机 ——> 可移动设备 ——> 串行端口 ——> 连接;
在这里插入图片描述

4)在终端输入dmesg|grep tty,查看对应的设备文件,其中默认的会有 ttyS0 文件,
其余一个(ttyS1 或 ttyS2)就是虚拟串口对应的设备文件。
在这里插入图片描述

三)测试通信

1)Windows 下打开串口调试工具,选择好串口 COM2 ——> COM1,设置对应的波特率;
在这里插入图片描述

2)以下步骤在虚拟机下完成,在虚拟机安装 minicom 软件;sudo apt-get install minicom
在这里插入图片描述

3)在终端执行 sudo minicom -s ,选择 Serial port setup;
在这里插入图片描述

4)设置设备文件,波特率,关闭流控;(按 Ctrl + 相应字母)
在这里插入图片描述

5)回车,保存修改,选择 Save setup as dfl;
在这里插入图片描述

6)可以在以下界面输入字符,查看串口助手的显示情况;
在这里插入图片描述

7)测试通信(终端输入不可见);
在这里插入图片描述

8)退出:Ctrl + A,然后按 Z,在弹出的界面里输入X,即可退出。
在这里插入图片描述

四)将 Modbus Slave 模拟器作为 RTU 设备的从机

虚拟机绑定 COM1 端口,Modbus Slave 连接 COM2 端口,虚拟机通过编程测试串口通信;
在这里插入图片描述

五)可能遇到的问题

虚拟串口完成主机与 vmware 下虚拟机进行串口通信
VSPD 虚拟串口工具 —— 从此告别硬件串口调试
vmware 虚拟机检测不到 vspd 虚拟串口问题
(👆 链接至其他博主,放心跳转)

Modbus 库

库的安装

安装与配置

1)在 linux 中解压压缩包,tar -xvf libmodbus-3.1.7.tar.gz
2)进入源码目录,创建文件夹(存放头文件、库文件);

	cd libmodbus-3.1.7 
	mkdir install 

3)执行脚本 configure,进行安装配置(指定安装目录);

	./configure--prefix=$PWD/install 

在这里插入图片描述

4)执行 make 和 make install

	make 					// 编译
  	make install 			   // 安装

在这里插入图片描述
在这里插入图片描述

5)执行完成后会在 install 文件夹下产生对应的头文件、库文件。

使用

1、一般操作:

gcc xxx.c -I ./install/include/modbus -L ./install/lib -lmodbus
./a.out

-I : 后需要指定出头文件的路径(大写的i)
-L : 后需要指定库的路径
-l : 后需要指定库名(小写的L)

2、要想编译方便,可以将头文件和库文件放到系统路径下:

sudo  cp install/include/modbus/*.h  /usr/include 
sudo  cp install/lib/*  -r /lib -d 

在这里插入图片描述

后期编译时,就可以直接 gcc xxx.c -lmodbus,
头文件默认搜索路径:/usr/include、/usr/local/include
库文件默认搜索路径:/lib、/usr/lib

函数接口

0x01(modbus_read_bits)

int modbus_read_bits(modbus_t *ctx, int addr, int nb, uint8_t *dest); 

功能:读取线圈状态,可读取多个连续线圈的状态(对应功能码为0x01)
参数:
    ctx   :	Modbus实例
    addr  :	寄存器起始地址
    nb    :	寄存器个数
    dest  :	得到的状态值

0x02(modbus_read_input_bits)

int  modbus_read_input_bits(modbus_t *ctx, int addr, int nb, uint8_t *dest); 

功能:读取输入状态,可读取多个连续输入的状态(对应功能码为0x02)
参数:
    ctx  :	Modbus 实例
    addr :	寄存器起始地址
    nb   :	寄存器个数
    dest :	得到的状态值
返回值:成功:返回nb的值

0x03(modbus_read_registers)

int  modbus_read_registers(modbus_t *ctx, int addr, int nb, uint16_t *dest); 

功能:读取保持寄存器的值,可读取多个连续保持寄存器的值(对应功能码为0x03)
参数:
    ctx  :	Modbus 实例
    addr :	寄存器起始地址
    nb   :	寄存器个数
    dest :	得到的寄存器的值
返回值:成功:读到寄存器的个数
       失败:-1

0x04(modbus_read_input_registers)

int  modbus_read_input_registers(modbus_t *ctx, int addr, int nb, uint16_t *dest);

功能:读输入寄存器的值,可读取多个连续输入寄存器的值(对应功能码为0x04)
参数:
    ctx   :	Modbus 实例
    addr  :	寄存器起始地址
    nb    :	寄存器个数
    dest  :	得到的寄存器的值
返回值:成功:读到寄存器的个数
       失败:-1

0x05(modbus_write_bit)

int  modbus_write_bit(modbus_t *ctx, int addr, int status);

功能:写入单个线圈的状态(对应功能码为0x05)
参数:
    ctx   :	Modbus 实例
    addr  :	线圈地址
    status:	线圈状态
返回值:成功:0
       失败:-1

0x06(modbus_write_register)

int  modbus_write_register(modbus_t *ctx, int addr, int value);

功能:写入单个寄存器(对应功能码为0x06)
参数: 
    ctx   :	Modbus 实例
    addr  :	寄存器地址
    value :	寄存器的值 
返回值:成功:0
       失败:-1

0x0F(modbus_write_bits)

int  modbus_write_bits(modbus_t *ctx, int addr, int nb, const uint8_t *src);

功能:写入多个连续线圈的状态(对应功能码为15)
参数:
    ctx   :	Modbus 实例
    addr  :	线圈地址
    nb    :	线圈个数
    src   :	多个线圈状态
返回值:成功:0
       失败:-1

0x10(modbus_write_registers)

int  modbus_write_registers(modbus_t *ctx, int addr, int nb, const uint16_t *src);

功能:写入多个连续寄存器(对应功能码为16)
参数:
    ctx   :	Modbus 实例
    addr  :	寄存器地址
    nb    :	寄存器的个数
    src   :	多个寄存器的值 
返回值:成功:0
       失败:-1

编程流程

1)创建实例(modbus_new_tcp / modbus_new_rtu)

modbus_t *modbus_new_tcp(const char *ip, int port); 

功能:以 TCP 方式创建 Modbus 实例,并初始化
参数:
    ip  :	ip 地址
    port:	端口号
返回值:成功:Modbus 实例
       失败:NULL
modbus_t *modbus_new_rtu(const char *device, int baud, 
                         		char parity, int data_bit, int stop_bit);

功能:用于创建一个用于 Modbus RTU 通信的 modbus_t 结构体实例
参数:
	device:	要打开的串口设备的路径(例如:"/dev/ttyUSB0")
	baud:		波特率(如 9600、19200 等)
	parity:	校验位(可选值:'N' - 无校验、'E' - 偶校验、'O' - 奇校验)
	data_bit:	数据位(常用值为 8)
	stop_bit:	停止位(常用值为 1)
 返回值:成功:Modbus 实例
        失败:NULL

2)设置从机地址(modbus_set_slave)

int  modbus_set_slave(modbus_t *ctx, int slave); 
功能:设置从机ID
参数:
    ctx  :		Modbus 实例
    slave:		从机 ID
返回值:成功:0
       失败:-1

3)建立连接(modbus_connect)

int  modbus_connect(modbus_t *ctx); 
功能:和从机(slave)建立连接
参数:
    ctx:		Modbus 实例
返回值:成功:0
       失败:-1

4)各种操作(见函数接口)

5)关闭套接字(modbus_close)

void  modbus_close(modbus_t *ctx); 
功能:关闭套接字
参数:ctx:Modbus 实例

6)释放实例(modbus_free)

void   modbus_free(modbus_t *ctx); 
功能:释放 Modbus 实例
参数:ctx:Modbus 实例

练习:

// 和 Slave 通信,读保持寄存器的三个值

#include <stdio.h>
#include <modbus.h>
#include <stdlib.h>
#include <string.h>
#include <modbus-rtu.h>

int main(int argc, char const *argv[])
{   
    if (argc != 3){
        printf("Please input %s <ip> <port>. \n", argv[0]);
        return -1;
    }

    modbus_t *ctx;
    ctx = modbus_new_tcp(argv[1], atoi(argv[2]));
    // ctx = modbus_new_rtu("/dev/ttyS1", 9600, N, 8, 1);
    
    if (ctx == NULL){
        perror("Failed to modbus_new_tcp");			// "Failed to modbus_new_rtu"
        return -1;
    }

    if (modbus_set_slave(ctx, 1) < 0){
        perror("Failed to modbus_set_slave");
        return -1;
    }

    if (modbus_connect(ctx) < 0){
        perror("Failed to modbus_connect");
        return -1;
    }

    uint16_t dest[32] = {};

    if (modbus_read_registers(ctx, 0, 3, dest) < 0){
        perror("Failed to modbus_read_registers");
        return -1;
    }

    for (int i = 0; i < 3; i++)
        printf("%#x ", dest[i]);
    putchar(10);
    for (int i = 0; i < 3; i++)
        printf("%d ", dest[i]);
    putchar(10);

    modbus_close(ctx);
    modbus_free(ctx);
    return 0;
}

运行结果如下:
在这里插入图片描述

注意:

1、使用 Modbus TCP 协议时,将 slave 的 connect 设置为“Modbus TCP/IP”。
2、使用 Modbus RTU 协议时,将 slave 的 connect 设置为“Serial Port”。
在这里插入图片描述

小目标:

编程实现采集传感器数据和控制硬件设备(传感器和硬件通过 slave 模拟)。
传感器:2个,光线传感器、加速度传感器(x \ y \ z);
硬件设备:2个,LED灯、蜂鸣器。
要求:
1、多任务编程:多线程、多进程
2、循环 1s 采集一次数据,并将数据打印至终端
3、同时从终端输入指令控制硬件设备
0 1:LED 灯开
0 0:LED 灯关
1 1:蜂鸣器开
1 0:蜂鸣器关

// 同步实现

#include <stdio.h>
#include <modbus.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#include <semaphore.h>

modbus_t *ctx;
sem_t sem1, sem2;

void *collector(void *arg){

    uint16_t *dest = (uint16_t *)arg;
    while (1){
        sleep(5);
        sem_wait(&sem1);

        if (modbus_read_registers(ctx, 0, 4, dest) < 0){
            perror("Failed to modbus_read_registers");
            return NULL;
        }
        for (int i = 0; i < 4; i++)
            printf("%d ", dest[i]);
        putchar(10);

        sem_post(&sem2);
    }
    pthread_exit(0);
}

void *control(void *arg){
    
    uint8_t writer[2];
    while (1){
        sem_wait(&sem2);

        printf("Please set status of LED or BUZZER: ");
        for (int i = 0; i < 2; i++)
            scanf("%hhu", &writer[i]);
        
        modbus_write_bit(ctx, writer[0], writer[1]);

        sem_post(&sem1);
    }
    pthread_exit(0);
}

int main(int argc, char const *argv[])
{   
    if (argc != 3){
        printf("Please input %s <ip> <port>. \n", argv[0]);
        return -1;
    }

    ctx = modbus_new_tcp(argv[1], atoi(argv[2]));
    if (ctx == NULL){
        perror("Failed to modbus_new_tcp");
        return -1;
    }

    if (modbus_set_slave(ctx, 1) < 0){
        perror("Failed to modbus_set_slave");
        return -1;
    }

    if (modbus_connect(ctx) < 0){
        perror("Failed to modbus_connect");
        return -1;
    }

    uint16_t dest[32] = {};

    pthread_t tid1, tid2;
    sem_init(&sem1, 0, 1);
    sem_init(&sem2, 0, 0);

    if (pthread_create(&tid1, NULL, collector, dest)){
        perror("Failed to create a thread named collector");
        return -1;
    }
    pthread_detach(tid1);

    if (pthread_create(&tid2, NULL, control, NULL)){
        perror("Failed to create a thread named input");
        return -1;
    }
    pthread_detach(tid2);

    while (1);
    

    modbus_close(ctx);
    modbus_free(ctx);
    return 0;
}

实现效果如下:
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

uni-app打包后,打开软件时使其横屏显示

找到page.json文件&#xff0c;在global加入以下代码&#xff1a; 这样就可以横屏显示了。

Django框架之中间件

目录 一、引入 二、Django中间件介绍 【1】什么是Django中间件 【2】Django中间件的作用 【3】示例 三、Django请求生命周期流程图 四、Django中间件是Django的门户 五、Django中间件详解 六、中间件必须要掌握的两个方法 (1) process_request (2) process_respon…

Vivado Modelsim联合进行UVM仿真指南

打开Vivado&#xff0c;打开对应工程&#xff0c;点击左侧Flow Navigator-->PROJECT MANAGER-->Settings&#xff0c;打开设置面板。点击Project Settings-->Simulation选项卡&#xff0c;如下图所示。 将Target simulator设为Modelsim Simulator。 在下方的Compil…

Android修行手册-超出父布局进行显示以及超出父布局实现点击

Unity3D特效百例案例项目实战源码Android-Unity实战问题汇总游戏脚本-辅助自动化Android控件全解手册再战Android系列Scratch编程案例软考全系列Unity3D学习专栏蓝桥系列ChatGPT和AIGC &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff0c;以及各种资源分…

postgresql数据库中update使用的坑

简介 在数据库中进行增删改查比较常见&#xff0c;经常会用到update的使用。但是在近期发现update在oracle和postgresql使用却有一些隐形区别&#xff0c;oracle 在执行update语句的时候set 后面必须跟着1对1的数据关联而postgresql数据库却可以一对多&#xff0c;这就导致数据…

GaussDB技术解读系列:数据实例的连接

GaussDB是华为公司倾力打造的自研企业级分布式关系型数据库&#xff0c;该产品具备企业级复杂事务混合负载能力&#xff0c;同时支持优异的分布式事务&#xff0c;同城跨AZ部署&#xff0c;数据0丢失&#xff0c;支持1000扩展能力&#xff0c;PB级海量存储等企业级数据库特性。…

【实战精选】掌握图像风格迁移:构建基于生成对抗网络的系统

1.研究背景与意义 随着计算机技术的不断发展&#xff0c;图像处理和计算机视觉领域取得了长足的进步。图像风格迁移是其中一个备受关注的研究方向&#xff0c;它可以将一幅图像的风格特征应用到另一幅图像上&#xff0c;从而创造出新的图像。这项技术具有广泛的应用前景&#…

RK3399平台开发系列讲解(内核入门篇)ConfigFS 的核心数据结构

🚀返回专栏总目录 文章目录 一、关键数据结构二、config_item 的结构体三、属性和方法沉淀、分享、成长,让自己和他人都能有所收获!😄 📢虚拟文件系统 ConfigFS 是一个特殊的文件系统,旨在提供一种动态配置 Linux 内核和设备的机制。 一、关键数据结构 ConfigFS 的核…

解决Emmy Lua插件在IDEA或 Reder 没有代码提示的问题(设置文件关联 增加对.lua.txt文件的支持)

目录 Reder版本2019.x Reder版本2021.1.5x Reder版本2019.x 解决Emmy Lua插件在IDEA或 Reder 没有代码提示的问题(设置文件关联 增加对.lua.txt文件的支持) Reder版本2021.1.5x 解决Emmy Lua插件在IDEA或 Reder 没有代码提示的问题(设置文件关联 增加对.lua.txt文件的支持)…

【精选】​​通道热点加持的LW-ResNet:小麦病害智能诊断与防治系统

1.研究背景与意义 小麦是世界上最重要的粮食作物之一&#xff0c;但由于病害的侵袭&#xff0c;小麦产量和质量受到了严重的威胁。因此&#xff0c;开发一种高效准确的小麦病害识别分类防治系统对于保障粮食安全和农业可持续发展具有重要意义。 传统的小麦病害识别分类方法主…

6.1.webrc媒体协商

那今天呢&#xff1f;我们来看一下y8 rtc的媒体协商&#xff0c;那实际上在我们之前的课程中呢&#xff1f;我已经向你介绍过y8 rtc的媒体协商了。只不过呢&#xff0c;角度是不一样的&#xff0c;在之前介绍外边tc媒体协商的时候呢&#xff0c;我们是从应用的角度来看。那web …

C++的new / delete 与 C语言的malloc/realloc/calloc / free 的讲解

在C语言中我们通常会使用malloc/realloc/calloc来动态开辟的空间&#xff0c;malloc是只会开辟你提供的空间大小&#xff0c;并不会初始化内容&#xff1b;calloc不但会开辟空间&#xff0c;还会初始化&#xff1b;realloc是专门来扩容的&#xff0c;当你第一次开辟的空间不够用…

【MATLAB】全网入门快、免费获取、持续更新的科研绘图教程系列1

1 【MATLAB】科研绘图第一期点线图 %% Made by Lwcah %% 公众号&#xff1a;Lwcah %% 知乎、B站、小红书、抖音同名账号:Lwcah&#xff0c;感谢关注~ %% 更多MATLABSCI绘图教程敬请观看~%% 清除变量 clc; clear all; close all;%% 一幅图的时候figureWidth 8.5;figureHeight …

鸿蒙 ark ui 轮播图实现教程

前言&#xff1a; 各位同学有段时间没有见面 因为一直很忙所以就没有去更新博客。最近有在学习这个鸿蒙的ark ui开发 因为鸿蒙不是发布了一个鸿蒙next的测试版本 明年会启动纯血鸿蒙应用 所以我就想提前给大家写一些博客文章 效果图 具体实现 我们在鸿蒙的ark ui 里面列表使…

SPASS-信度分析

信度分析概述 效度 效度指的是量表是否真正反映了我们希望测量的东西。一般来说&#xff0c;有4种类型的效度&#xff1a;内容效度、标准效度、结构效度和区分效度。内容效度是一种基于概念的评价指标&#xff0c;其他三种效度是基于经验的评价指标。如果一个量表实际上是有效…

使用 NVProf 检测 CUDA kernel 的 bank conflict

使用 NVProf 检测 CUDA kernel 的 bank conflict NVProf 指令 使用 NVProf 可以对 bank conflict 进行检测: nvprof --events shared_ld_bank_conflict,shared_st_bank_conflict <app> [args...]其中: --events 选项指定的 shared_ld_bank_conflict,shared_st_bank_c…

PTA-成绩转换

本题要求编写程序将一个百分制成绩转换为五分制成绩。转换规则&#xff1a; 大于等于90分为A&#xff1b;小于90且大于等于80为B&#xff1b;小于80且大于等于70为C&#xff1b;小于70且大于等于60为D&#xff1b;小于60为E。 输入格式: 输入在一行中给出一个整数的百分制成…

WordPress网站如何修复数千个帖子的SEO错误

在本教程中&#xff0c;我们将向您展示如何解决您经常犯的SEO错误。 最好的是您不必花费太多时间&#xff0c;因为您不需要打开并编辑每个帖子。 相反&#xff0c;我们将向您展示如何使用 WordPress 内的电子表格来修复 WordPress 帖子的 SEO。 在这里&#xff0c;我们为您提…

Spring-jdbcTemplate-配置数据库连接池,配置文件方式beans.xml

1、jdbc.properties jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql:///studb jdbc.userroot jdbc.pwd123456 2、beans.xml <?xml version"1.0" encoding"UTF-8"?> <beans xmlns"http://www.springframework.org/schema/beans&…

计算机中了halo勒索病毒怎么清除,halo勒索病毒解密数据恢复

科技的进步加快了企业发展的步伐&#xff0c;网络技术的不断应用为企业的生产运营提供了极大帮助&#xff0c;但随之而来的网络安全威胁也不断增加&#xff0c;近期&#xff0c;云天数据恢复中心接到很多企业的求助&#xff0c;企业的计算机服务器遭到了halo勒索病毒攻击&#…