Modbus开源库_Libmodbus
1、Libmodbus简介
在单片机上常用的Modbus库有FreeModbus和Libmodbus,这里介绍Libmodbus。
(1)Libmodbus是一个免费的跨平台支持RTU和TCP的Modbus库,遵循LGPL V2.1+协议。
(2)支持Linux、Mac OsX、FreeBSD、QNX和Windows等操作系统。
(3)支持主、从模式。
(4)稍加修改就可以在裸机、FreeRTOS、RT-Thread上使用。
2、Libmodbus的下载和源码介绍
(1)下载地址:https://github.com/stephane/libmodbus
(2)源码浏览
在目录 “ libmodbus-3.1.10\\libmodbus-3.1.10\\src” 下存在如下文件:
- modbus.c:modbus的统一接口层。
- modbus-data.c:一些数据处理的辅助函数。
- modbus-rtu.c:ModbusRtu模式相关程序,一般使用串口。
- modbus-tcp.c:ModbusTcp相关协议,使用以太网。
(3)源码分层
libmodbus通过抽象出这些层次,提供了一个跨平台的API,使得在不同硬件和平台上实现Modbus通信变得更加容易。用户在使用libmodbus时,可以根据需要选择不同的后端(如RTU或TCP),而核心层和应用层的代码可以保持不变,从而提高了代码的可移植性和可维护性。
3、modbus.c文件核心函数介绍
(1)modbus_read_bits函数
- 函数原型:int modbus_read_bits(modbus_t *ctx, int addr, int nb, uint8_t *dest)
- 函数功能:读布尔量(读数字量输出)
modbus_t *ctx
int addr
int nb
uint8_t *dest
(2)modbus_read_input_bits函数
- 函数原型:int modbus_read_input_bits(modbus_t *ctx, int addr, int nb, uint8_t *dest)
- 函数功能:读布尔量(读数字量输入/离散输入状态)
(3)modbus_write_bit函数
(4)modbus_write_bits函数
(5)modbus_read_registers函数
- 函数原型:int modbus_read_registers(modbus_t *ctx, int addr, int nb, uint16_t *dest)
- 函数功能:读模拟量(读输出模拟量AO)
(6)modbus_read_input_registers函数
- 函数原型:int modbus_read_input_registers(modbus_t *ctx, int addr, int nb, uint16_t *dest)
- 函数功能:读模拟量(读输入模拟量/AI)
(7)modbus_write_register函数
- 函数原型:int modbus_write_register(modbus_t *ctx, int addr, const uint16_t value)
- 函数功能:写单个输出模拟量
- 函数调用了write_single,write_single调用了函数send_msg、_modbus_receive_msg、check_confirmation。
- send_msg:发送消息
- _modbus_receive_msg:接收消息
- check_confirmation:检查
(8)modbus_write_registers函数
- 函数原型:int modbus_write_registers(modbus_t *ctx, int addr, int nb, const uint16_t *src)
- 功能:写多个输出模拟量
- modbus_write_registers调用了函数send_msg、_modbus_receive_msg、check_confirmation。
- send_msg:发送消息
- _modbus_receive_msg:接收消息
- check_confirmation:检查
(9)send_msg函数
- 函数原型:static int send_msg(modbus_t *ctx, uint8_t *msg, int msg_length)
- 函数功能:发送消息
- 调用函数ctx->backend->send_msg_pre(msg, msg_length)
- 发送消息的准备函数
- prepare:准备;backend:后端
- 调用函数ctx->backend->send(ctx, msg, msg_length);
- 发送真正的消息
- backend是一个非常重要的结构体,该结构体由modbus-rtu.c或者modbus-tcp构造。
4、modbus-rtu.c核心函数介绍
(1)主要工作就是构造这个结构体。
// clang-format offconst modbus_backend_t _modbus_rtu_backend = { _MODBUS_BACKEND_TYPE_RTU, _MODBUS_RTU_HEADER_LENGTH, _MODBUS_RTU_CHECKSUM_LENGTH, MODBUS_RTU_MAX_ADU_LENGTH, _modbus_set_slave, _modbus_rtu_build_request_basis, _modbus_rtu_build_response_basis, _modbus_rtu_prepare_response_tid, _modbus_rtu_send_msg_pre, /* 发送 */ _modbus_rtu_send, _modbus_rtu_receive, /* 接收 */ _modbus_rtu_recv, _modbus_rtu_check_integrity, _modbus_rtu_pre_check_confirmation, _modbus_rtu_connect, _modbus_rtu_is_connected, _modbus_rtu_close, _modbus_rtu_flush, _modbus_rtu_select, _modbus_rtu_free};
5、mosbus-tcp核心函数介绍
(1)主要工作就是构造这个结构体。
const modbus_backend_t _modbus_tcp_pi_backend = { _MODBUS_BACKEND_TYPE_TCP, _MODBUS_TCP_HEADER_LENGTH, _MODBUS_TCP_CHECKSUM_LENGTH, MODBUS_TCP_MAX_ADU_LENGTH, _modbus_set_slave, _modbus_tcp_build_request_basis, _modbus_tcp_build_response_basis, _modbus_tcp_prepare_response_tid, _modbus_tcp_send_msg_pre, /* 网络发送 */ _modbus_tcp_send, _modbus_tcp_receive, /* 网络接收 */ _modbus_tcp_recv, _modbus_tcp_check_integrity, _modbus_tcp_pre_check_confirmation, _modbus_tcp_pi_connect, _modbus_tcp_is_connected, _modbus_tcp_close, _modbus_tcp_flush, _modbus_tcp_select, _modbus_tcp_pi_free};
6、modbus-data.c文件核心函数介绍
(1)bswap_16函数:实现16位无符号整数的字节交换。
- 将数据从大端序(big-endian)转换为小端序(little-endian),或者从小端序转换为大端序。
#if !defined(bswap_16)/* 如果bswap_16没有被定义,编译器会发出一个警告信息,提示用户正在使用C函数作为字节交换的后备方案。 */# warning \"Fallback on C functions for bswap_16\"static inline uint16_t bswap_16(uint16_t x){ return (x >> 8) | (x << 8);}#endif
(2)bswap_16函数:实现32位无符号整数的字节交换。
- 将数据从大端序(big-endian)转换为小端序(little-endian),或者从小端序转换为大端序。
# warning \"Fallback on C functions for bswap_32\"static inline uint32_t bswap_32(uint32_t x){ return (bswap_16(x & 0xffff) <> 16));}#endif
7、modbus_backend结构体
typedef struct _modbus_backend { unsigned int backend_type; unsigned int header_length; unsigned int checksum_length; unsigned int max_adu_length; int (*set_slave)(modbus_t *ctx, int slave); int (*build_request_basis)( modbus_t *ctx, int function, int addr, int nb, uint8_t *req); int (*build_response_basis)(sft_t *sft, uint8_t *rsp); int (*prepare_response_tid)(const uint8_t *req, int *req_length); /* 发送消息前的准备工作 */ int (*send_msg_pre)(uint8_t *req, int req_length); /* 发送消息 */ ssize_t (*send)(modbus_t *ctx, const uint8_t *req, int req_length); int (*receive)(modbus_t *ctx, uint8_t *req); /* 接收消息 */ ssize_t (*recv)(modbus_t *ctx, uint8_t *rsp, int rsp_length); int (*check_integrity)(modbus_t *ctx, uint8_t *msg, const int msg_length); int (*pre_check_confirmation)(modbus_t *ctx, const uint8_t *req, const uint8_t *rsp, int rsp_length); /* 连接相关,TCP中需要建立连接 */ int (*connect)(modbus_t *ctx); unsigned int (*is_connected)(modbus_t *ctx); void (*close)(modbus_t *ctx); int (*flush)(modbus_t *ctx); int (*select)(modbus_t *ctx, fd_set *rset, struct timeval *tv, int msg_length); void (*free)(modbus_t *ctx);} modbus_backend_t;
(1)涉及硬件操作的函数
- ssize_t (*send)(modbus_t *ctx, const uint8_t *req, int req_length);
- ssize_t (*recv)(modbus_t *ctx, uint8_t *rsp, int rsp_length);
8、unit-test-client.c文件
(1)单元测试客户端程序,客户端主动发送消息,然后接收服务器的应答。
(2)根据use_backend确认modbus的通信方式。
if (use_backend == TCP) { ctx = modbus_new_tcp(ip_or_device, 1502);} else if (use_backend == TCP_PI) { ctx = modbus_new_tcp_pi(ip_or_device, \"1502\");} else { ctx = modbus_new_rtu(ip_or_device, 115200, \'N\', 8, 1);}if (ctx == NULL) { fprintf(stderr, \"Unable to allocate libmodbus context\\n\"); return -1;}
(3)modbus_new_rtu(ip_or_device, 115200, \'N\', 8, 1);
- ip_or_device:串口设备号
- 115200:串口波特率
- \'N\': 校验相关
- 8:数据位
- 1:停止位