网络高级(学习)2024.9.11

目录

Modbus库函数

1.初始化和释放函数

2.功能函数

3.功能案例

[Modbus RTU](#Modbus RTU)

1.特点

2.协议格式

3.编程思路


Modbus库函数

1.初始化和释放函数

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实例


2.功能函数

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_register(modbus_t *ctx, int addr, int value);

功能: 写入单个寄存器(对应功能码为0x06

参数:

ctx:Modbus实例

addr:寄存器地址

value:寄存器的值

返回值:

成功: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_registers(modbus_t *ctx, int addr, int nb, const uint16_t *src);

功能:写入多个连续寄存器(对应功能码为16

参数:

ctx:Modbus实例

addr:寄存器地址

nb:寄存器的个数

src:多个寄存器的值

返回值:

成功:0

失败:-1


3.功能案例

读取保持寄存器的值03功能:

cpp 复制代码
#include <stdio.h>
#include "modbus-tcp.h"
#include "modbus.h"

int main(int argc, char const *argv[])
{
    // 创建modbus实例
    modbus_t *ctx;
    ctx = modbus_new_tcp(argv[1], 502);
    if (ctx == NULL)
    {
        perror("modbus new tcp失败");
        return -1;
    }

    // 设置从机ID
    modbus_set_slave(ctx, 1);

    // 建立连接
    if (modbus_connect(ctx) < 0)
    {
        printf("modbus connect err\n");
        modbus_free(ctx);
        return -1;
    }
    printf("connect ok\n");
    uint16_t dest[32];
    int addr, nb;
    printf("请输入寄存器起始地址,寄存器个数:");
    scanf("%d %d", &addr, &nb);
    getchar();
    int mrr = modbus_read_registers(ctx, addr, nb, dest);
    if (mrr <= 0)
    {
        perror("读保持寄存器的值失败");
        return -1;
    }
    else
    {
        for (int i = 0; i < mrr; i++)
        {
            printf("寄存器的值为:%d\n", dest[i]);
        }
    }

    // 释放
    modbus_free(ctx);
    modbus_close(ctx);

    return 0;
}

写入单个线圈的状态05功能:

cpp 复制代码
#include <stdio.h>
#include "modbus-tcp.h"
#include "modbus.h"

int main(int argc, char const *argv[])
{
    // 创建modbus实例
    modbus_t *ctx;
    ctx = modbus_new_tcp(argv[1], 502);
    if (ctx == NULL)
    {
        perror("modbus new tcp失败");
        return -1;
    }

    // 设置从机ID
    modbus_set_slave(ctx, 1);

    // 建立连接
    if (modbus_connect(ctx) < 0)
    {
        printf("modbus connect err\n");
        modbus_free(ctx);
        return -1;
    }
    printf("connect ok\n");
    uint16_t dest[32];
    int addr, status;
    printf("请输入线圈地址,线圈状态:");
    scanf("%d %d", &addr, &status);
    getchar();
    int mrr = modbus_write_bit(ctx, addr, status);
    if (mrr <= 0)
    {
        perror("写入单个线圈的状态失败");
        return -1;
    }
    else
    {
        printf("写入单个线圈的状态成功\n");
    }

    // 释放
    modbus_free(ctx);
    modbus_close(ctx);

    return 0;
}

Modbus RTU

1.特点

(1)Modbus RTU和Modbus ASCII协议是基于串口进行通信的协议,在一般工业场景使用modbus RTU的场景还是更多一些

(2)与modbus TCP不同的是RTU没有报文头MBAP字段,但是在尾部增加了两个CRC检验字节(CRC16),因为网络协议中自带校验,所以在TCP协议中不需要使用CRC校验码


2.协议格式

ModbusRTU数据帧包含地址码、功能码、数据、校验码四部分

地址码:1个字节的从机地址码,=0:广播地址,=1-247:从机地址,=248-255:保留

功能码:与Modbus TCP相同

数据区:数据区包含这么几部分:起始地址、数量、数据,这三项是大端模式

CRC校验:两个字节,校验的数据范围为:地址码+功能码+数据区,校验码的产生可以通过函数自动生成。


3.编程思路

com1在虚拟机识别的是一个设备文件: /dev/ttyS1就是对这个文件进行操作--文件IO

(1)打开设备文件 //open

(2)对设备文件进行配置:对com1设置,波特率、数据位、停止位等

(3)写文件

write

(4)读文件

read

4.代码

Crc_Calc.h:

cpp 复制代码
#ifndef _CRC_CALC_H_
#define _CRC_CALC_H_

#include "stdint.h"

#ifdef _CRC_CALC_C_
#define CRC_CALC_EXT
#else
#define CRC_CALC_EXT extern
#endif


unsigned short GetCRC16(unsigned char *ptr,  unsigned char len);

#endif 

Crc_Calc.c:

cpp 复制代码
#define _CRC_CALC_C_
#include "stdint.h"
#include "stdio.h"

unsigned char const TabH[] = {  //CRC高位字节值表
                                0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
                                0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
                                0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
                                0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
                                0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
                                0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
                                0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
                                0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
                                0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
                                0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
                                0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
                                0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
                                0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
                                0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
                                0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
                                0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
                                0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
                                0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
                                0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
                                0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
                                0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
                                0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
                                0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
                                0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
                                0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
                                0x80, 0x41, 0x00, 0xC1, 0x81, 0x40
                             } ;
unsigned char const TabL[] = {  //CRC低位字节值表
                                0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06,
                                0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD,
                                0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09,
                                0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A,
                                0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, 0x14, 0xD4,
                                0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
                                0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3,
                                0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4,
                                0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A,
                                0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29,
                                0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED,
                                0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
                                0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60,
                                0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67,
                                0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F,
                                0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68,
                                0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E,
                                0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
                                0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71,
                                0x70, 0xB0, 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92,
                                0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C,
                                0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B,
                                0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B,
                                0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
                                0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42,
                                0x43, 0x83, 0x41, 0x81, 0x80, 0x40
                             } ;

/*******************************************************************************
** 函数名称: crc
** 功能描述: CRC校验函数
** 参    数: *puchMsg-要校验数据的指针
             usDataLen-要校验数据的长度
** 返 回 值: crc-16位的CRC校验值      
*******************************************************************************/
unsigned short GetCRC16(unsigned char *ptr,  unsigned char len)
{ 
    unsigned int index;
    unsigned char crch = 0xFF;  //高CRC字节
    unsigned char crcl = 0xFF;  //低CRC字节


    while (len--)  //计算指定长度的CRC
    {
        index = crch ^ *ptr++;
        crch = crcl ^ TabH[index];
        crcl = TabL[index];
    }
    
    return ((crch<<8) | crcl);
} 

serial_init.c:

cpp 复制代码
#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_rtu.c:

cpp 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include "stdint.h"
#include <termios.h>
#include "Crc_Calc.h"

void uart_init(int fd);

int main(int argc, char const *argv[])
{
    int fd;
    fd = open("/dev/ttyS1", O_RDWR);
    if (fd < 0)
    {
        perror("open失败");
        return -1;
    }
    // 对串口进行初始化
    uart_init(fd);

    // 写一个线圈的状态
    uint8_t buf[16] = {0x01, 0x05, 0x00, 0x00, 0xFF, 0x00};
    unsigned short crc;
    crc = GetCRC16(buf, 6);
    buf[6] = crc >> 8;
    buf[7] = crc & 0xff;
    write(fd, buf, sizeof(buf));
    printf("发送成功\n");

    uint8_t buf1[16] = {0};

    int ret = read(fd, buf1, sizeof(buf1));

    for (int i = 0; i < ret; i++)
    {
        printf("%#x ", buf1[i]);
    }
    printf("\n");

    close(fd);
    return 0;
}
相关推荐
Cici_ovo17 分钟前
wlan和vlan
网络·智能路由器
飞的肖1 小时前
日志(elk stack)基础语法学习,零基础学习
学习·elk
dal118网工任子仪3 小时前
66,【6】buuctf web [HarekazeCTF2019]Avatar Uploader 1
笔记·学习
02苏_3 小时前
2025/1/21 学习Vue的第四天
学习
羊小猪~~4 小时前
MYSQL学习笔记(四):多表关系、多表查询(交叉连接、内连接、外连接、自连接)、七种JSONS、集合
数据库·笔记·后端·sql·学习·mysql·考研
约定Da于配置4 小时前
uniapp封装websocket
前端·javascript·vue.js·websocket·网络协议·学习·uni-app
hardWork_yulu5 小时前
Android RTMP直播练习实践
网络·安卓
东京老树根5 小时前
Excel 技巧15 - 在Excel中抠图头像,换背景色(★★)
笔记·学习·excel
qq_243050796 小时前
irpas:互联网路由协议攻击套件!全参数详细教程!Kali Linux入门教程!黑客渗透测试!
linux·网络·web安全·网络安全·黑客·渗透测试·系统安全
Ronin-Lotus6 小时前
嵌入式硬件篇---ADC模拟-数字转换
笔记·stm32·单片机·嵌入式硬件·学习·低代码·模块测试