【飞凌嵌入式FCU1501试用】Modbus-TCP通信与传感器数据采集
·
文章目录
引言
在工业物联网(IIoT)领域,边缘计算设备扮演着至关重要的角色。RK3506作为一款高性能、低功耗的边缘计算芯片,非常适合用于工业数据采集场景。本文将介绍如何基于RK3506构建一个完整的Modbus-TCP传感器采集系统,包括传感器模拟器、采集器开发和测试验证。
项目架构设计
整体架构
整个系统采用分布式架构,由三个核心组件组成:
组件分工
| 组件 | 运行平台 | 职责 |
|---|---|---|
| 传感器模拟器 | PC | 模拟Modbus从机,生成传感器数据,支持故障注入 |
| RK3506采集器 | RK3506开发板 | 定时轮询传感器,数据解析,异常检测 |
| LLM服务 | 云端/本地 | 接收异常事件,生成专业诊断报告 |
Modbus协议原理
RTU vs TCP
Modbus协议有两种主要传输模式:
RTU模式:基于串行通信(RS-485),使用CRC16校验,适合长距离传输。
TCP模式:基于以太网,使用MBAP(Modbus Application Protocol)头,传输速度更快,适合网络环境。
由于项目初期没有USB转485硬件,本文重点介绍Modbus-TCP模式。
MBAP头结构
Modbus-TCP报文由6字节MBAP头和PDU(Protocol Data Unit)组成:
┌──────┬──────┬──────┬──────┬──────┬──────┐
│ 事务ID │ 协议ID │ 长度 │ 从站地址 │ 功能码 │ ...数据... │
│ (2字节) │ (2字节) │ (2字节) │ (1字节) │ (1字节) │ │
└──────┴──────┴──────┴──────┴──────┴──────┘
0-1 2-3 4-5 6 7
- 事务ID:用于匹配请求和响应
- 协议ID:固定为0(Modbus协议)
- 长度:后续字节数(包括从站地址)
- 从站地址:目标设备地址(0-247)
- 功能码:操作类型(0x03=读保持寄存器)
常用功能码
| 功能码 | 名称 | 用途 |
|---|---|---|
| 0x03 | 读保持寄存器 | 读取传感器数据 |
| 0x06 | 写单个寄存器 | 控制设备参数 |
| 0x10 | 写多个寄存器 | 批量控制 |
传感器模拟器实现
设计思路
模拟器使用Python实现,支持异步TCP服务器,模拟4个Modbus从机:
- Slave 1:环境传感器(温度、湿度)
- Slave 2:振动传感器(X/Y/Z轴、RMS)
- Slave 3:电力传感器(电压、电流、功率)
- Slave 4:管道传感器(压力、流量)
核心代码
传感器数据更新逻辑
def update(self, t):
if self.slave_id == 1:
# 环境传感器:模拟正弦波动
temp = 25.0 + math.sin(t / 30) * 1.5
humi = 50.0 + math.sin(t / 45) * 4.0
# 故障注入:高温故障
if self.fault == 'high_temp':
elapsed = time.time() - self.fault_start
ramp = min(elapsed / 300.0, 1.0)
temp = 55.0 + ramp * 30.0 # 从55°C逐渐升至85°C
self.registers[0] = int(temp * 10) # 温度×10
self.registers[1] = int(humi * 10) # 湿度×10
TCP请求处理
def handle_tcp_request(self, request):
if len(request) < 12:
return None
trans_id = (request[0] << 8) | request[1]
proto_id = (request[2] << 8) | request[3]
length = (request[4] << 8) | request[5]
slave = request[6]
func = request[7]
if slave != self.slave_id or proto_id != 0:
return None
addr = (request[8] << 8) | request[9]
count = (request[10] << 8) | request[11]
if func == 0x03: # 读保持寄存器
pdu_len = 3 + count * 2
response = bytearray()
response.append(trans_id >> 8)
response.append(trans_id & 0xFF)
response.append(0x00) # 协议ID高字节
response.append(0x00) # 协议ID低字节
response.append(pdu_len >> 8)
response.append(pdu_len & 0xFF)
response.append(self.slave_id)
response.append(0x03)
response.append(count * 2)
for i in range(count):
val = self.registers[addr + i]
response.append((val >> 8) & 0xFF)
response.append(val & 0xFF)
return bytes(response)
TCP服务器主循环
def run_tcp_simulator(simulators, tcp_port, timeout_rate):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('0.0.0.0', tcp_port))
sock.listen(5)
start_time = time.time()
while True:
t = time.time() - start_time
for sim in simulators.values():
sim.update(t)
conn, addr = sock.accept()
conn.settimeout(2.0)
while True:
header = conn.recv(6) # 读取MBAP头
if len(header) < 6:
break
length = (header[4] << 8) | header[5]
body = conn.recv(length)
if len(body) < length:
break
request = header + body
slave_addr = request[6]
if slave_addr in simulators:
response = simulators[slave_addr].handle_tcp_request(request)
if response:
conn.sendall(response)
运行模拟器
# 启动TCP模拟器,注入高温故障到Slave 1
python3 simulator/modbus_slave_sim.py \
--mode tcp \
--tcp-port 7502 \
--slaves 1,2,3,4 \
--fault high_temp \
--fault-slave 1
RK3506采集器开发
交叉编译环境
RK3506使用ARM架构,需要配置交叉编译工具链:
# 工具链配置文件
source ~/3506-toolchain/host/environment-setup
# 关键环境变量
# CC=arm-buildroot-linux-gnueabihf-gcc
# CFLAGS=-march=armv8-a+crc -mtune=cortex-a55
# ARCH=arm
# CROSS_COMPILE=arm-buildroot-linux-gnueabihf-
项目结构
src/
├── main.c # 主程序入口
├── modbus_transport.h # 传输层抽象接口
├── modbus_tcp.c # TCP传输实现
├── modbus_rtu.c # RTU传输实现
├── sensor_map.h # 传感器映射定义
├── sensor_map.c # 传感器数据处理
├── anomaly.h # 异常检测接口
├── anomaly.c # 异常检测算法
├── event_json.h # 事件序列化接口
├── event_json.c # JSON序列化实现
├── llm_client.h # LLM客户端接口
├── llm_client.c # LLM客户端实现
├── serial_port.h # 串口接口
└── serial_port.c # 串口实现
模块依赖关系
传输层抽象设计
采用策略模式设计传输层,RTU和TCP共用一套接口:
typedef struct modbus_transport {
int (*connect)(modbus_transport_t *t);
int (*read)(modbus_transport_t *t, uint8_t *buf, int len, int timeout_ms);
int (*write)(modbus_transport_t *t, const uint8_t *buf, int len);
void (*close)(modbus_transport_t *t);
} modbus_transport_t;
typedef struct {
modbus_transport_t base;
const char *host;
int port;
int sockfd;
} modbus_tcp_t;
传感器映射配置
定义传感器与Modbus寄存器的映射关系:
sensor_map_t sensor_maps[MAX_SENSORS] = {
{
"env", 1, 0, 2,
{0.1, 0.1},
{"temperature", "humidity"},
{"C", "%RH"}
},
{
"vibration", 2, 0, 4,
{0.01, 0.01, 0.01, 0.01},
{"x", "y", "z", "rms"},
{"mm/s", "mm/s", "mm/s", "mm/s"}
},
{
"power", 3, 0, 3,
{0.1, 0.1, 0.1},
{"voltage", "current", "power"},
{"V", "A", "W"}
},
{
"pipe", 4, 0, 2,
{0.01, 0.01},
{"pressure", "flow"},
{"MPa", "m3/h"}
}
};
主循环设计
while (1) {
// 创建TCP连接(每次轮询新建连接)
fd = socket(AF_INET, SOCK_STREAM, 0);
// ... 设置超时、连接服务器 ...
for (int i = 0; i < sensor_map_count; i++) {
sensor_data_t *data = &sensors[i];
uint16_t raw[MAX_FIELDS];
// 读取保持寄存器
ret = modbus_read_holding_registers_tcp(fd,
data->map->slave_id,
data->map->start_addr,
data->map->count, raw);
if (ret == MODBUS_ERR_OK) {
data->comm_error_count = 0;
sensor_data_update(data, raw);
print_sensor_data(data);
// 异常检测(第二篇介绍)
anomaly_event_t event;
if (anomaly_detect(data, &event)) {
handle_anomaly(&event);
}
} else {
data->comm_error_count++;
printf("[%s] Slave %d: COMM ERROR (%d)\n",
data->map->name, data->map->slave_id, ret);
}
usleep(100000); // 100ms间隔
}
close(fd);
sleep(POLL_INTERVAL_SEC); // 1秒轮询间隔
}
编译脚本
#!/bin/bash
cd "$(dirname "$0")"/..
echo "Building for RK3506..."
source ~/3506-toolchain/host/environment-setup
mkdir -p build
cd build
cmake ..
make -j4
echo "Build completed. Output: build/modbus_collector"
测试验证
编译部署
# 编译
bash scripts/build.sh
# 部署到RK3506
scp build/modbus_collector root@<RK3506_IP>:/root/
# 赋予执行权限
ssh root@<RK3506_IP> "chmod +x /root/modbus_collector"
运行采集器
# 在RK3506上运行
./modbus_collector --mode tcp --host <SIMULATOR_IP> --port <PORT>
测试结果
Starting RK3506 Modbus-TCP Collector
TCP host: <SIMULATOR_IP>:<PORT>
Polling sensors...
--- 13:19:30 ---
[env] Slave 1: temperature=85.00°C, humidity=51.00%RH
[vibration] Slave 2: x=0.71mm/s, y=0.15mm/s, z=0.15mm/s, rms=0.74mm/s
[power] Slave 3: voltage=24.40V, current=1.40A, power=34.50W
[pipe] Slave 4: pressure=0.64MPa, flow=54.72m3/h
所有4个传感器数据均成功采集,数据格式正确。
总结
本文介绍了基于RK3506的工业边缘采集系统的第一部分——Modbus-TCP通信与传感器数据采集。主要内容包括:
- 系统架构设计:分布式架构,PC端模拟器 + RK3506采集器
- Modbus协议原理:MBAP头结构、功能码解析
- 传感器模拟器:Python实现,支持4类传感器和故障注入
- RK3506采集器:C语言开发,传输层抽象设计
- 测试验证:编译部署成功,所有传感器数据正常采集
下一篇预告:将介绍异常检测算法、LLM智能诊断集成和异步处理优化。
技术交流:欢迎在评论区留言讨论!
📌 提示:本文是系列文章的第一篇,第二篇将深入讲解异常检测与LLM智能诊断。如果你觉得本文对你有帮助,欢迎点赞、收藏、关注!
更多推荐



所有评论(0)