1. 从零开始:为什么你需要一个Modbus从机?
如果你正在开发工业自动化设备,比如一个智能传感器、一个本地控制器,或者一个数据采集模块,那么“Modbus从机”这个概念对你来说就至关重要。简单来说,在Modbus这个主从通信的“班级”里,主机(Master)是那个发号施令的“老师”,而从机(Slave)就是那些接收指令并做出响应的“学生”。你的设备,绝大多数时候扮演的就是这个“学生”的角色。
我刚开始接触工业通讯时,也觉得协议栈、寄存器映射这些概念很抽象。但后来我发现,把它想象成一个“带地址的共享内存表格”就简单多了。你的设备内部有一张表格,表格的每个格子(寄存器)都有唯一的地址,里面存放着设备的状态、采集的数据或者可控制的参数。Modbus主机要做的,就是通过特定的“问句”(请求帧)来读取或修改这张表格里的内容。而你的从机程序,核心任务就是维护好这张表格,并准确无误地理解主机的“问句”,然后给出正确的“回答”(响应帧)。
LibModbus这个开源库,就是帮你处理所有这些底层通信细节的“得力助手”。它帮你打包数据、计算校验码、处理串口或网络收发,让你可以专注于业务逻辑——也就是决定表格里每个格子该存什么、怎么变。用上它,你就不用从零开始去研究Modbus协议帧的每一个字节,能省下大把时间,也大大降低了出错的概率。接下来,我就带你一步步用LibModbus,亲手搭建一个稳定可靠的Modbus从机。
2. 环境搭建与第一个从机程序
工欲善其事,必先利其器。在开始写代码之前,我们得先把LibModbus库请到我们的开发环境里来。
2.1 获取与编译LibModbus库
LibModbus的源码托管在GitHub上,获取非常方便。我习惯在Linux环境下开发,以下步骤在Ubuntu或树莓派等系统上通用。
首先,打开终端,用git克隆源码库:
git clone https://github.com/stephane/libmodbus.git
cd libmodbus
接下来是经典的“配置-编译-安装”三步曲。这里我建议使用autogen.sh脚本,它能自动检测你的系统环境并生成合适的编译配置。
./autogen.sh
./configure --prefix=/usr/local
make
sudo make install
--prefix=/usr/local参数指定了库的安装路径。安装完成后,头文件(比如modbus.h)会放在/usr/local/include,编译好的库文件(libmodbus.so)会放在/usr/local/lib。
为了让编译器能找到我们新安装的库,可能需要更新一下动态链接库的缓存:
sudo ldconfig
现在,你就可以在代码里通过#include <modbus.h>来使用LibModbus了。编译你的程序时,记得加上链接选项-lmodbus,例如:
gcc -o my_slave my_slave.c -lmodbus
2.2 一个最简从机:响应主机的“点名”
理论说再多,不如动手跑一遍。我们来创建一个最简单的TCP从机,它只做一件事:当主机询问它某个寄存器的值时,它总是回答一个固定的数字。这个例子虽然简单,但包含了从机模式的全部核心骨架。
#include <stdio.h>
#include <stdlib.h>
#include <modbus.h>
int main() {
modbus_t *ctx = NULL;
modbus_mapping_t *mb_mapping = NULL;
uint8_t query[MODBUS_TCP_MAX_ADU_LENGTH];
int master_socket;
int rc;
// 1. 创建TCP上下文,监听所有IP的502端口
ctx = modbus_new_tcp(NULL, 502); // NULL表示监听0.0.0.0
if (ctx == NULL) {
fprintf(stderr, "创建Modbus上下文失败\n");
return -1;
}
// 2. 设置从机ID(对于TCP,这个ID在网关到串行网络时才需要,但建议设置)
modbus_set_slave(ctx, 1);
// 3. 创建寄存器映射
// 这里我们只创建1个保持寄存器(可读可写),地址从0开始
mb_mapping = modbus_mapping_new(0, 0, 0, 0, 0, 1, 0, 0);
if (mb_mapping == NULL) {
fprintf(stderr, "分配寄存器映射失败\n");
modbus_free(ctx);
return -1;
}
// 给这个唯一的寄存器赋一个初始值,比如 0x1234
mb_mapping->tab_registers[0] = 0x1234;
// 4. 开始监听TCP连接
master_socket = modbus_tcp_listen(ctx, 1);
if (master_socket == -1) {
fprintf(stderr, "监听失败\n");
modbus_mapping_free(mb_mapping);
modbus_free(ctx);
return -1;
}
printf("Modbus TCP从机已启动,正在监听端口502...\n");
// 5. 主循环:接受连接、处理请求
for (;;) {
// 等待并接受一个主机的连接
int client_socket = modbus_tcp_accept(ctx, &master_socket);
if (client_socket == -1) {
// 这里可以处理错误,但为了简单,我们继续循环
continue;
}
// 设置响应超时时间(单位:秒和微秒)
modbus_set_response_timeout(ctx, 1, 0); // 1秒超时
// 循环处理该连接上的请求
do {
// 接收Modbus请求报文
rc = modbus_receive(ctx, query);
if (rc > 0) {
// rc是接收到的请求帧长度,query里是完整的ADU
// 核心处理:根据请求操作寄存器映射,并生成回复
rc = modbus_reply(ctx, query, rc, mb_mapping);
if (rc == -1) {
fprintf(stderr, "回复请求失败: %s\n", modbus_strerror(errno));
break; // 发生错误,跳出内层循环,准备处理下一个连接
}
} else if (rc == -1) {
// 连接错误或超时
fprintf(stderr, "接收请求失败或连接中断\n");
break;
}
// 如果r


1346

被折叠的 条评论
为什么被折叠?



