Modbus介绍与函数使用及项目示例

Modbus起源

1. 起源:

Modbus由Modicon公司于1979年开发,是一种工业现场总线协议标准。

Modbus通信协议具有多个变种,其中有支持串口,以太网多个版本,其中最著名的是Modbus RTU、Modbus ASCII和Modbus TCP三种

其中Modbus TCP是在施耐德收购Modicon后1997年发布的。

2. 分类:

1) Modbus RTU:

运行在串口上的协议,采用二进制表现形式以及紧凑型数据结构,通信效率高,应用广泛

2) Modbus ASCII:

运行在串口上的协议,采用ASCII码传输,并且利用特殊字符作为其字节的开始与结束标识,其传输效率要远远低于Modbus RTU协议,一般只有在通信数据量较小的情况下才考虑使用Modbus ASCII通信协议

3) Modbus TCP:

运行在以太网上的协议

3. 优势:

免费、简单、容易使用

4. 应用场景:

Modbus协议是现在国内工业领域应用最多的协议,不只PLC设备,各种终端设备,比如水控机、水表、电表、工业秤、各种采集设备

5. ModbusTCP特点:

1) 采用主从问答方式进行通信

2) Modbus TCP是应用层协议,基于传输层TCP协议实现

3) Modbus TCP端口号默认为502

Modbus TCP协议格式

ModbusTcp协议包含三部分:报文头、功能码、数据

Modbus TCP/IP协议最大数据帧长度为260字节

1. 报文头

包含7个字节,分别是:

2. 寄存器

包含四种:离散量输入、线圈、输入寄存器、保持寄存器

1) 离散量和线圈其实就是位寄存器(每个寄存器数据占1字节),工业上主要用于控制IO设备。

线圈寄存器,类比为开关量,每一个bit都对应一个信号的开关状态。所以一个byte就可以同时控制8路的信号。比如控制外部8路io的高低。 线圈寄存器支持读也支持写,写在功能码里面又分为写单个线圈寄存器和写多个线圈寄存器。

对应上面的功能码也就是:0x01 0x05 0x0f

离散输入寄存器,离散输入寄存器就相当于线圈寄存器的只读模式,他也是每个bit表示一个开关量,而他的开关量只能读取输入的开关信号,是不能够写的。比如我读取外部按键的按下还是松开。

所以功能码也简单就一个读的 0x02

2) 输入和保持寄存器是字寄存器(每个寄存器数据占2个字节),工业上主要用于存储工业设备的值。

保持寄存器,这个寄存器的单位不再是bit而是两个byte,也就是可以存放具体的数据量的,并且是可读写的。比如我我设置时间年月日,不但可以写也可以读出来现在的时间。写也分为单个写和多个写

所以功能码有对应的三个:0x03 0x06 0x10

输入寄存器,这个和保持寄存器类似,但是也是只支持读而不能写。一个寄存器也是占据两个byte的空间。类比我我通过读取输入寄存器获取现在的AD采集值

对应的功能码也就一个 0x04

3. 功能码

设置灯亮:05

读灯状态:01

具体协议分析可参考:

http://www.360doc.com/content/20/0804/12/43769266_928452485.shtml

在库的配置与安装完成的前提下可以使用modbus的函数对外接设备的控制与数据读取

函数接口

modbus_t*   modbus_new_tcp(const char *ip, int port)
功能:以TCP方式创建Modbus实例,并初始化
参数:
    ip   :ip地址
    port:端口号
返回值:成功:Modbus实例
      失败:NULL
int modbus_set_slave(modbus_t *ctx, int slave)
功能:设置从机ID
参数:
    ctx   :Modbus实例
    slave:从机ID
返回值:成功:0
       失败:-1
int   modbus_connect(modbus_t *ctx)
功能:和从机(slave)建立连接
参数:
    ctx:Modbus实例
返回值:成功:0
       失败:-1
void   modbus_free(modbus_t *ctx)
功能:释放Modbus实例
参数:ctx:Modbus实例
void   modbus_close(modbus_t *ctx)
功能:关闭套接字
参数:ctx:Modbus实例
int modbus_read_bits(modbus_t *ctx, int addr, int nb, uint8_t *dest)
功能:读取线圈状态,可读取多个连续线圈的状态(对应功能码为0x01)
参数:
    ctx   :Modbus实例
    addr :寄存器起始地址
    nb    :寄存器个数
    dest :得到的状态值
int  modbus_read_input_bits(modbus_t *ctx, int addr, int nb, uint8_t *dest)
功能:读取输入状态,可读取多个连续输入的状态(对应功能码为0x02)
参数:
    ctx   :Modbus实例
    addr :寄存器起始地址
    nb   :寄存器个数
    dest :得到的状态值
返回值:成功:返回nb的值
int  modbus_read_registers(modbus_t *ctx, int addr, int nb, uint16_t *dest)
功能:读取保持寄存器的值,可读取多个连续保持寄存器的值(对应功能码为0x03)
参数:
    ctx   :Modbus实例
    addr :寄存器起始地址
    nb    :寄存器个数
    dest :得到的寄存器的值
返回值:成功:读到寄存器的个数
       失败:-1
int   modbus_read_input_registers(modbus_t *ctx, int addr, int nb, uint16_t *dest)
功能:读输入寄存器的值,可读取多个连续输入寄存器的值(对应功能码为0x04)
参数:
    ctx   :Modbus实例
    addr :寄存器起始地址
    nb    :寄存器个数
    dest :得到的寄存器的值
返回值:成功:读到寄存器的个数
       失败:-1
int  modbus_write_bit(modbus_t *ctx, int addr, int status);
功能:写入单个线圈的状态(对应功能码为0x05)
参数:
    ctx     :Modbus实例
    addr  :线圈地址
    status:线圈状态
返回值:成功:0
      失败:-1
int  modbus_write_bits(modbus_t *ctx, int addr, int nb, const uint8_t *src);
功能:写入多个连续线圈的状态(对应功能码为15)
参数:
    ctx     :Modbus实例
    addr  :线圈地址
    nb     :线圈个数
    src    :多个线圈状态
返回值:成功:0
      失败:-1
int  modbus_write_register(modbus_t *ctx, int addr, int value);
功能:  写入单个寄存器(对应功能码为0x06)
参数: 
    ctx    :Modbus实例
    addr  :寄存器地址
    value :寄存器的值 
返回值:成功:0
       失败:-1
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

2. 设置从机ID

modbus_set_slave

3. 和从机进行连接

modbus_connect

4. 寄存器进行操作

功能码对应函数

5. 关闭套接字

modbus_close

6. 释放实例

modbus_free

示例:编程实现采集传感器数据和控制硬件设备(传感器和硬件通过slave模拟)

传感器:2个,光线传感器、加速度传感器(x\y\z)

硬件设备:2个,led灯、蜂鸣器

要求:

1. 多任务编程:多线程或者多进程

2. 循环1s采集一次数据,并将数据打印至终端

3. 同时从终端输入指令控制硬件设备

0 1 :led灯打开

0 0:led灯关闭

1 1:蜂鸣器开

1 0 : 蜂鸣器关

在slave内设置上线圈与寄存器,连接上主机ip地址,同时在虚拟机内编译通过后执行时也连接主机ip地址就可以循环获取设备的数值和通过终端输入来设置线圈状态

代码如下:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <modbus.h>
#include <pthread.h>
modbus_t *ctx;
void *handler_thread(void *arg)
{
    uint16_t dest[64] = {};
    while (1)
    {
        modbus_read_registers(ctx, 0, 4, dest);
        for (int i = 0; i < 4; i++)
        {
            if (i == 0)
            {
                printf("光传感器数值:");
                printf("%#x ", dest[i]);
                printf("\n");
            }
            else
            {
                printf("加速度传感器 ");
            }
            if (i == 1)
            {
                printf("x:%#x ", dest[i]);
            }
            else if (i == 2)
            {
                printf("y:%#x ", dest[i]);
            }
            else if (i == 3)
            {
                printf("z:%#x ", dest[i]);
            }
        }
        putchar(10);
        sleep(1);
    }
    pthread_exit(NULL); //结束线程
}
int main(int argc, char const *argv[])
{
    ctx = modbus_new_tcp(argv[1], atoi(argv[2]));
    if (ctx == NULL)
    {
        perror("create modbus err");
        return -1;
    }
    int x = modbus_set_slave(ctx, 1);
    if (x != 0)
    {
        perror("set id err");
        return -1;
    }
    x = modbus_connect(ctx);
    if (x != 0)
    {
        perror("connect err");
        return -1;
    }
    pthread_t tid;
    if (pthread_create(&tid, NULL, handler_thread, NULL) != 0)
    {
        perror("create err\n");
        return -1;
    }
    pthread_detach(tid); //分离线程
    int a, b;
    while (1)
    {
        scanf("%d", &a);
        scanf("%d", &b);
        if (a == 0 && b == 1)
        {
            printf("led灯打开\n");
        }
        else if (a == 0 && b == 0)
        {
            printf("led灯关闭\n");
        }
        else if (a == 1 && b == 0)
        {
            printf("蜂鸣器关\n");
        }
        else if (a == 1 && b == 1)
        {
            printf("蜂鸣器开\n");
        }
        modbus_write_bit(ctx, a, b);
    }
    modbus_close(ctx);
    modbus_free(ctx);
    return 0;
}

当然也可以通过Webserver自己做一个网页,在网页内获取值和控制设备,看阅读量考虑是否继续更新相关内容.

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值