1.安装编译
主要有最新版本和稳定版本3.0.6。最新版本编译较为简单,但是可能出现错误,实测树莓派4B出现部分错误仍然可以使用。
github地址:
https://github.com/stephane/libmodbus
1.直接命令行下载:
git clone git://github.com/stephane/libmodbus
2.安装编译工具
sudo apt-get install automake autoconf libtool
3.进入解压后的文件,运行./autogen.sh,自动配置./configure环境
./autogen.sh
4.新建安装环境和进行交叉编译,修改install相应目录
mkdir install
./configure --host=arm-none-linux-gnueabi --prefix=/pi/git-proj/libmodbus/install/
make install
5.install安装完成后,将其目录下libmodbus.so、libmodbus.so.5、libmodbus.so.5.1.0 复制到系统/usr/lib/目录下,解决动态库调用的问题。
检验库是否配置成功:
分别在2个命令行下运行modbus/tests/unit-test-server 和unit-test-client tcp,用系统私有地址进行模拟通信,如果能收到报文,说明库配置的没有问题。
./unit-test-server tcp
./unit-test-client tcp
2.SDK相应的说明
https://blog.csdn.net/qq_23670601/article/details/82155378#%E6%BA%90%E6%96%87%E6%A1%A3libmodbus
感谢以上博主,将libmodbus官方手册翻译成中文,可对应参照查看。
3.仿真软件
很多同学可能只有一台电脑,或者没有现场的PLC,可以下载Modbus Poll和Modbus Slave模拟主站和从站的通讯。
https://www.cnblogs.com/hieroly/p/9063710.html
楼主进行的是modbus tcp通讯:
树莓派---Server---从站slave(等待PLC报文后,进行数据采集,返回报文)
西门子PLC---Client---主站Poll(发送报文获取数据)
4.Modbus TCP功能码
ModbusTCP的数据帧可分为两部分:MBAP+PDU。
MBAP为报文头
长度为7字节,组成如下:
帧结构PDU——功能码+数据
Modbus的操作对象有四种:
1.线圈---可读可写
2.离散输入---可读
3.输入寄存器--可读
4.保持寄存器---可读可写
功能码:16进制
0x01: 读线圈寄存器 1
0x02: 读离散输入寄存器 2
0x03: 读保持寄存器 3
0x04: 读输入寄存器 4
0x05: 写单个线圈寄存器 5
0x06: 写单个保持寄存器 6
0x0f: 写多个线圈寄存器 15
0x10: 写多个保持寄存器 16
0x17:读写多个保持寄存器 23
功能码实例:
功能码0x17
5.实际软件程序
unit-test.h
#ifndef _UNIT_TEST_H_
#define _UNIT_TEST_H_
/* Constants defined by configure.ac */
#define HAVE_INTTYPES_H 1
#define HAVE_STDINT_H 1
#ifdef HAVE_INTTYPES_H
#include <inttypes.h>
#endif
#ifdef HAVE_STDINT_H
# ifndef _MSC_VER
# include <stdint.h>
# else
# include "stdint.h"
# endif
#endif
#define SERVER_ID 17
#define INVALID_SERVER_ID 18
//开辟本地Server的线圈和寄存器
//线圈(一个线圈,2个字节0xFF)
const uint16_t UT_BITS_ADDRESS = 0x00; //线圈首地址
const uint16_t UT_BITS_NB = 0x04; //线圈数量
const uint8_t UT_BITS_TAB[] = { 0x01, 0x02, 0x03, 0x04 }; //线圈初始值
//input线圈
const uint16_t UT_INPUT_BITS_ADDRESS = 0x04;
const uint16_t UT_INPUT_BITS_NB = 0x04;
const uint8_t UT_INPUT_BITS_TAB[] = { 0x01, 0x02, 0x03, 0x04};
//寄存器(一个寄存器,4个字节0xFFFF,高位+低位)
const uint16_t UT_REGISTERS_ADDRESS = 0x00;
/* Raise a manual exception when this adress is used for the first byte */
const uint16_t UT_REGISTERS_ADDRESS_SPECIAL = 0x0F;
const uint16_t UT_REGISTERS_NB = 0x04;
const uint16_t UT_REGISTERS_TAB[] = { 0x1111,0x0002, 0x0003, 0x0004 };
/* If the following value is used, a bad response is sent.
It's better to test with a lower value than
UT_REGISTERS_NB_POINTS to try to raise a segfault. */
const uint16_t UT_REGISTERS_NB_SPECIAL = 0x2;
//input寄存器
const uint16_t UT_INPUT_REGISTERS_ADDRESS = 0x00;
const uint16_t UT_INPUT_REGISTERS_NB = 0x01;
const uint16_t UT_INPUT_REGISTERS_TAB[] = { 0x000A };
const float UT_REAL = 916.540649;
const uint32_t UT_IREAL = 0x4465229a;
#endif /* _UNIT_TEST_H_ */
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <modbus.h>
#include <iostream>
#include "unit-test.h"
using namespace std;
int main()
{
int socket;
modbus_t *ctx;
modbus_mapping_t *mb_mapping;
int rc = -1;
uint8_t *query;
int header_length;
//创建Modbus Tcp环境
ctx = modbus_new_tcp("192.168.178.100", 8888);
//创建报文
query = (uint8_t*)malloc(MODBUS_TCP_MAX_ADU_LENGTH);
//分配线圈,寄存器的数组
mb_mapping = modbus_mapping_new(
UT_BITS_ADDRESS + UT_BITS_NB,
UT_INPUT_BITS_ADDRESS + UT_INPUT_BITS_NB,
UT_REGISTERS_ADDRESS + UT_REGISTERS_NB,
UT_INPUT_REGISTERS_ADDRESS + UT_INPUT_REGISTERS_NB);
if (mb_mapping == NULL) {
fprintf(stderr, "Failed to allocate the mapping: %s\n",
modbus_strerror(errno));
modbus_free(ctx);
return -1;
}
//初始化赋值
//线圈
for(int i = 0;i<UT_BITS_NB;i++){
mb_mapping->tab_bits[i] = UT_BITS_TAB[i];
}
//input线圈
for(int i = 0;i<UT_INPUT_BITS_NB;i++){
mb_mapping->tab_input_bits[i] = UT_INPUT_BITS_TAB[i];
}
//寄存器
for(int i = 0;i<UT_REGISTERS_NB;i++){
mb_mapping->tab_registers[i] = UT_REGISTERS_TAB[i];
}
//寄存器
for(int i = 0;i<UT_INPUT_REGISTERS_NB;i++){
mb_mapping->tab_input_registers[i] = UT_INPUT_REGISTERS_TAB[i];
}
//从后端,检索当前表头长度
header_length = modbus_get_header_length(ctx);
//设置从站,从站序列号
modbus_set_slave(ctx, SERVER_ID);
//启动调试模式
modbus_set_debug(ctx, TRUE);
//设置超时相应时间
// struct timeval response_timeout;
// response_timeout.tv_sec = 1;
// response_timeout.tv_usec = 0;
// modbus_set_response_timeout(ctx,&response_timeout);
//开始监听
socket = modbus_tcp_listen(ctx, 5);
//开始阻塞
modbus_tcp_accept(ctx, &socket);
cout<<"accept !!"<<endl;
for (;;) {
//收到指示请求,并返回请求长度
//规定发送功能码0x17报文:格式
rc = modbus_receive(ctx, query);
cout<<"以上为接收报文,报文长:"<<rc<<endl;
if (rc == -1) {
/* Connection closed by the client or error */
modbus_tcp_accept(ctx, &socket);
continue;
}
if (query[header_length] == 0x17) {
cout<<"功能码:"<<"0x17"<<endl;
if (MODBUS_GET_INT16_FROM_INT8(query, header_length + 3)
== UT_REGISTERS_NB_SPECIAL) {
cout<<"0x171"<<endl;
printf("Set an incorrect number of values\n");
MODBUS_SET_INT16_TO_INT8(query, header_length + 3,
UT_REGISTERS_NB_SPECIAL - 1);
} else if (MODBUS_GET_INT16_FROM_INT8(query, header_length + 1)
== UT_REGISTERS_ADDRESS_SPECIAL) {
cout<<"0x172"<<endl;
printf("Reply to this special register address by an exception\n");
modbus_reply_exception(ctx, query,
MODBUS_EXCEPTION_SLAVE_OR_SERVER_BUSY);
continue;
}
int slope = 0;
double high = 0;
int high1 = 0;
int high2 = 0;
//liquid high
cam.grab();
cam.retrieve(frame);
temp =frame(Rect(roiX,roiY,roiW,roiH));
input = otsu(temp);
edgeDetect::AboveProcess(input,slope,begin,end);
edgeDetect::belowProcess(input,templateImg,tubeWidth,high);
high1 = (static_cast<int>(high*10)) /10;
high2 = (static_cast<int>(high*10)) %10;
//低位表示
query[18]= slope;
//前为液面高度的小数点前数值,后为小数点后数值
query[20]= high1;
query[22]= high2;
}
cout<<"回复报文:"<<endl;
rc = modbus_reply(ctx, query, rc, mb_mapping);
cout<<"回复报文长度:"<<rc<<endl;
if (rc == -1) {
break;
}
}
printf("Quit the loop: %s\n", modbus_strerror(errno));
close(socket);
modbus_mapping_free(mb_mapping);
free(query);
//释放Modbus Tcp环境
modbus_free(ctx);
return 0;
}