【Linux/C++:modebus通信示例】(带初习配置概括)

        以modbus RTU为例,模拟modbus简单通信原理的代码实现

        首先需要配置串口。

        这里使用的为Configure Virtual Serial Port Driver虚拟串口调试工具

 创建COM1,COM2虚拟端口,或另创建一对串口互作收发信号端。

创建完成后虚拟工具COM后会有已启动端口的状态,这里我们以波特率9600 8N1为基准进行设定。

        接着,我对modbus slave开始设定,这里用slave而不用poll,我们后面会用虚拟机做主机,而slave为从机服务器。一般slave使用COM2端口

 

简单模拟,就以03保存寄存器01线圈为例。

按规格设定好后,进入linux ubuntu,进行串口配置,在开机之前,需先添加串口。

后选择COM1端口,一般约定主机COM1连接。要在VMware上面菜单中找到对应的串口组件并点击连接。

 

//P.S.modbus TCP设定是网络设定,检验连接可用wireshark抓包检测连通,一般slave使用502端口,假如使用虚拟机与PC做连通测试,要注意填写ip为相应虚拟机或PC的ip,而127.0.0.1本地回环测试的本质是本机回环,虚拟机也是独立的。

接着我们进入linux,下载minicom模块。

终端输入:

sudo apt-get install minicom

当连接上虚拟串口后,在终端输入dmesg | grep tty,可以查看到对应的设备文件,其中默认的会有ttyS0文件,剩下的就是虚拟串口对应的设备文件。

虚拟机在终端执行sudo minicom -s

主要使用setup进行波特率等属性设置,并记得保存。

 

配置完成,可先使用 UartAssist串口调试工具通信测试。

调试助手关闭时尽量选关闭而不是最小化,不然频繁使用造成打开一堆在后台,占用资源

接着就可以进行真正的通信了。

这里是基于C++的通信,故要使用对应的类库

 基类.h

#include <stdint.h>             //uint

class AbsPoll   
{
protected:
    //从机地址
    int sid;
    //打开文件需要打文件描述符
    int fd; 
public:
    AbsPoll();
    ~AbsPoll();

    //从C++相应库modbus相关类函数里找到需要的函数

    int setSlave(int id);                             //初始id
    //以下多个纯虚函数,代表这是抽象类,之后派生类使用,不能直接创建对象,所以函数定义写在派生类
    virtual int Connect() = 0;                           //连接,打开文件
    virtual int writeData(uint8_t *data, int len) = 0;     //写数据(发送
    virtual int readData(uint8_t *data, int len) = 0;       //读数据(接受
    virtual int buildRequest(int func, int addr, int nb, uint8_t *req) = 0;     //封装请求码
    int read_registers(int addr, int nb, uint16_t *dest);           //读寄存器数据并存放于指定数组
    int write_bit(int addr, int status);        //如05写线圈状态时可以写一种
};

基类.cpp

#include "AbsPoll.h"

AbsPoll::AbsPoll()
{
}

AbsPoll::~AbsPoll()
{
}

//初始化id(从机地址)
int AbsPoll::setSlave(int id)
{
    sid = id;
}


//读寄存器数据操作
int AbsPoll::read_registers(int addr, int nb, uint16_t *dest)
{
    uint8_t req[32] = "";       
    uint8_t data[32] = "";
    int len;                             
    len = buildRequest(3, addr, nb, req); //请求码的实际长度
    writeData(req, len);//写数据
    readData(data, 32); //1:tcp:7+1+4=12 RTU:1+1+4+2=8      读数据
    //modebus TCP和RTU格式和长度是不同的,读取位置也是不同的
    if(len == 8)//RTU
    {
        for(int i = 0,j = 0; i < data[2]; i+=2,j++)
            dest[j] = data[3+i] << 8 | data[4+i];   
    }
    else //TCP
    {
        for(int i = 0,j = 0; i < data[8]; i+=2,j++)
            dest[j] = data[9+i] << 8 | data[10+i];
    } 
}
int write_bit(int addr, int status)
{

}

派生.h(RTU样例)


#include "AbsPoll.h"
#include "Crc_Calc.h"
#include <stdint.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include "head.h"
#include <iostream>

using namespace std;

class RTUPoll : public AbsPoll                        //使用抽象类创建派生类
{
private:
    string devname;         //注意string类型

public:
    RTUPoll(string name);           //直接构造初始化文件路径
    ~RTUPoll();
    //下在基类为纯虚函数继承下来要写声明,并定义
    int Connect();                  
    int writeData(uint8_t *data, int len);
    int readData(uint8_t *data, int len);
    int buildRequest(int func, int addr, int nb, uint8_t *req);
};

 

 派生.cpp

 

#include "RTUPoll.h"

//初始化设备路径
RTUPoll::RTUPoll(string name)
{
    devname = name;
}

RTUPoll::~RTUPoll()
{
}

//连接
int RTUPoll::Connect()
{
    fd = open(devname.c_str(), O_RDWR);
    //这里注意devname为string类型,而open参数为char*类型,所以使用c_str()转数据类型char
        if (fd < 0)
    {
        cout << "open err" << endl; //错误信息设置
        return -1;
    }
    //
    uart_init(fd); //这个地方是个初始化串口的函数,代码可以自己查查,我之后也会给一种示例。
    return 0;
}


int RTUPoll::writeData(uint8_t *data, int len)
{
    return write(fd, data, len); //写数据
}


int RTUPoll::readData(uint8_t *data, int len)
{
    return read(fd, data, len); //读数据
}

//封装请求码
int RTUPoll::buildRequest(int func, int addr, int nb, uint8_t *req)
{
    //这里控制的就是外部定义的req数组。下面按RTU的数据格式填充
    req[0] = sid;                  //从机地址
    req[1] = func;                 //功能码
    req[2] = (uint8_t)(addr >> 8); //理论上,有要分大小端存储的数,都可以拆分开。
    req[3] = (uint8_t)(addr & 0xff);

    if (func == 3)
    {
        req[4] = (uint8_t)(nb >> 8); //当功能码是03时完成的任务
        req[5] = (uint8_t)(nb & 0xff);
    }
    unsigned short crc = GetCRC16(req, 6); //这里是一个CSC校验,形成两字节的校验位
    req[6] = (uint8_t)(crc >> 8);          //上返回的unsigned short类型,转为uint8_t
    req[7] = (uint8_t)(crc & 0xff);
    return 8;
}

串口初始化

#include <termios.h>

void uart_init(int fd)
{
    struct termios options;
    //设置串口属性
	//获取串口原有属性
    tcgetattr(fd, &options);
	//激活选项CLOCAL(本地连接)和CREAD(接受使能)
    options.c_cflag |= ( CLOCAL | CREAD );
	//设置字符大小
    options.c_cflag &= ~CSIZE;
    //设置流控
    options.c_cflag &= ~CRTSCTS;
	//设置8位数据位
    options.c_cflag |= CS8;
	//设置停止位
    options.c_cflag &= ~CSTOPB;
    //忽略奇偶错字符
    options.c_iflag |= IGNPAR;
    //将输入的CR转换为NL和停止输出控制流起作用
    options.c_iflag &= ~(ICRNL | IXON);
    options.c_oflag = 0;
    options.c_lflag = 0;
	//设置波特率(输入和输出的波特率)
    cfsetispeed(&options, B9600);
    cfsetospeed(&options, B9600);
	//激活配置
    tcsetattr(fd, TCSANOW, &options);
}

接着就可以尝试互通消息了

modbus收发为一问一答,基于linux特性,可用直接读写串口文件来进行通信。

当这样的通信框架建成,就可融合其他功能了,如接入webserver等。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

夢靈子DMC

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值