引言

在工业物联网(IIoT)领域,边缘计算设备扮演着至关重要的角色。RK3506作为一款高性能、低功耗的边缘计算芯片,非常适合用于工业数据采集场景。本文将介绍如何基于RK3506构建一个完整的Modbus-TCP传感器采集系统,包括传感器模拟器、采集器开发和测试验证。

项目架构设计

整体架构

整个系统采用分布式架构,由三个核心组件组成:

模拟传感器数据

PC端 - 开发/测试环境

Modbus-TCP
TCP/IP网络

HTTP REST API

云端 - AI服务

LLM智能诊断服务
mimo-v2.5-pro模型

RK3506开发板 - 边缘采集节点

Modbus采集器
嵌入式C语言程序

Modbus-TCP协议栈

传感器数据解析

异常检测引擎

LLM客户端

传感器模拟器
Python异步TCP服务器

环境传感器
Slave 1
温度/湿度

振动传感器
Slave 2
X/Y/Z/RMS

电力传感器
Slave 3
电压/电流/功率

管道传感器
Slave 4
压力/流量

组件分工

组件 运行平台 职责
传感器模拟器 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    # 串口实现

模块依赖关系

外部服务层

业务逻辑层

数据处理层

Modbus传输层

main.c
主程序入口

modbus_transport.h
抽象接口

modbus_tcp.c
TCP实现

modbus_rtu.c
RTU实现

serial_port.c
串口驱动

sensor_map.h
传感器映射

sensor_map.c
数据解析

anomaly.h
异常检测接口

anomaly.c
检测算法

event_json.c
JSON序列化

llm_client.c
LLM客户端

传输层抽象设计

采用策略模式设计传输层,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通信与传感器数据采集。主要内容包括:

  1. 系统架构设计:分布式架构,PC端模拟器 + RK3506采集器
  2. Modbus协议原理:MBAP头结构、功能码解析
  3. 传感器模拟器:Python实现,支持4类传感器和故障注入
  4. RK3506采集器:C语言开发,传输层抽象设计
  5. 测试验证:编译部署成功,所有传感器数据正常采集

下一篇预告:将介绍异常检测算法、LLM智能诊断集成和异步处理优化。

技术交流:欢迎在评论区留言讨论!


📌 提示:本文是系列文章的第一篇,第二篇将深入讲解异常检测与LLM智能诊断。如果你觉得本文对你有帮助,欢迎点赞、收藏、关注!

更多推荐