目录
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功能:
#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功能:
#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:
#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:
#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:
#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:
#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;
}