首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >独立配电柜温湿度监测:RJ45传感器硬件电路设计与服务器数据接收程序

独立配电柜温湿度监测:RJ45传感器硬件电路设计与服务器数据接收程序

原创
作者头像
BJ盛世宏博小程
发布于 2026-09-24 13:46:27
发布于 2026-09-24 13:46:27
200
举报

独立配电柜温湿度监测:RJ45传感器硬件电路设计与服务器数据接收程序

一、从"用产品"到"做产品"的跨越

前面的文章都在讲如何选型、部署、排障现成的工业传感器。但当你面对一个定制场景——比如为特定型号的独立配电柜设计一套嵌入式温湿度监测模块,需要自己画电路、写固件、搭服务端——事情就完全不一样了。

这不是"买一个传感器接上网线"的问题,而是:

  • 硬件端:怎么在强电环境里把温湿度采准、把 RJ45 接口做稳、把以太网通信跑起来?
  • 固件端:怎么在资源受限的 MCU 上实现 TCP/IP 协议栈和应用层协议?
  • 服务端:怎么稳定接收成百上千个终端的数据,不丢、不乱、不崩?

本文从硬件电路设计到服务器数据接收程序,完整拆解一个独立配电柜 RJ45 温湿度监测终端的开发过程。


二、硬件电路总体架构

1. 系统框图

代码语言:javascript
复制
┌──────────────────────────────────────────────────────────────────┐
│                    温湿度监测终端 PCB                            │
│                                                                  │
│  ┌─────────┐   I2C    ┌──────────┐    SPI     ┌──────────────┐ │
│  │ SHT3x   │─────────►│ STM32F4  │──────────►│ W5500        │ │
│  │ 温湿度  │          │ (主控)   │           │ 以太网PHY    │ │
│  └─────────┘           │          │           └──────┬───────┘ │
│                        │          │                  │         │
│  ┌─────────┐   UART   │          │    I2C           │         │
│  │ 调试/   │◄────────►│          │◄─────────┐       │         │
│  │ 配置    │           └──────────┘          │       │         │
│  └─────────┘                                 │  ┌────▼────┐   │
│                                             └─►│ EEPROM  │   │
│  ┌────────────────────────────────────────────┐   └─────────┘   │
│  │ 电源模块                                    │                 │
│  │ 24VDC → 3.3V (DC-DC隔离 + LDO)            │                 │
│  │ TVS + EMI滤波 + 浪涌保护                    │                 │
│  └────────────────────────────────────────────┘                 │
│                                                                  │
│  ┌────────────────────────────────────────────┐                 │
│  │ RJ45 (带集成变压器/LED)                     │                 │
│  │ LINK LED / ACT LED                          │                 │
│  └────────────────────────────────────────────┘                 │
└──────────────────────────────────────────────────────────────────┘
                              │
                        以太网线 (Cat5e SF/UTP)
                              │
                     ┌────────▼────────┐
                     │ 交换机           │
                     │ (工业级/带光口)  │
                     └────────┬────────┘
                              │
                     ┌────────▼────────┐
                     │ 服务器           │
                     │ (数据接收程序)   │
                     └─────────────────┘

2. 核心器件选型

器件

型号

选型理由

MCU

STM32F407VGT6

Cortex-M4, 168MHz, 192KB RAM, 1MB Flash, 丰富外设

温湿度传感器

Sensirion SHT35

±1.5%RH / ±0.1℃, I2C, 工业级

以太网控制器

W5500

硬件 TCP/IP 协议栈, SPI 接口, 8个独立socket

RJ45 插座

HanRun HR911105A

集成变压器, 状态LED, 符合 IEEE 802.3

隔离电源

B0505S-1WR3

5V→5V 隔离 DC-DC, 1500VDC 隔离

非隔离降压

MP2359

24V→5V, 2A, 同步降压

LDO

AMS1117-3.3

5V→3.3V, 给 MCU 和传感器供电

EEPROM

AT24C64

64Kbit, 存储配置和校准参数

TVS

SMAJ24A

24V 系统瞬态抑制

ESD 保护

SRV05-4

RJ45 差分线 ESD 保护


三、关键电路设计

3.1 电源电路

配电柜内 24VDC 供电,但环境恶劣(前文已分析过瞬态尖峰问题),电源设计必须层层防御:

代码语言:javascript
复制
24VDC 输入
    │
    ├───[保险丝 2A]───[TVS SMAJ24A]───[共模电感]───[π型滤波]──┐
    │                                                        │
    │                                              ┌─────────▼────────┐
    │                                              │ MP2359           │
    │                                              │ 24V → 5V/2A     │
    │                                              └────────┬────────┘
    │                                                       │
    │                                              ┌────────▼────────┐
    │                                              │ B0505S-1WR3     │
    │                                              │ 隔离 DC-DC      │
    │                                              │ 5V → 5V 隔离   │
    │                                              └────────┬────────┘
    │                                                       │
    │                                              ┌────────▼────────┐
    │                                              │ AMS1117-3.3     │
    │                                              │ 5V → 3.3V      │
    │                                              └────────┬────────┘
    │                                                       │
    │                                    ┌──────────────────┼──────────┐
    │                                    │                  │          │
    │                              STM32F4            W5500      SHT35 │
    │                              3.3V              3.3V       3.3V  │

设计要点:

层级

作用

参数

TVS

吸收瞬态高压尖峰

击穿电压 26.7V, 钳位 38.9V

共模电感

抑制共模干扰

100Ω @ 100MHz

π型滤波

滤除高频噪声

10μH + 10μF × 2

隔离 DC-DC

电气隔离,阻断地环路

1500VDC 隔离电压

LDO

低压差稳压,低噪声

压差 1V, 纹波 < 50mV

3.2 温湿度传感电路

SHT35 通过 I2C 接口与 MCU 通信:

代码语言:javascript
复制
SHT35 引脚:
  VDD  ─── 3.3V (10μF + 100nF 去耦)
  GND  ─── GND
  SCL  ─── PB6 (STM32 I2C1_SCL) ─── 4.7kΩ 上拉到 3.3V
  SDA  ─── PB7 (STM32 I2C1_SDA) ─── 4.7kΩ 上拉到 3.3V
  SCL  ─── 串联 100Ω 电阻 (抑制振铃)
  SDA  ─── 串联 100Ω 电阻
  VDD  ─── 串联磁珠 (抑制高频噪声)

PCB 布局要点:

  • 传感器尽量靠近板边,便于通风
  • 传感器下方不要走任何信号线(避免热传导影响测量)
  • 传感器周围开槽,减少 PCB 自身发热影响
  • I2C 走线尽量短,远离高频信号(SPI、时钟)

3.3 以太网接口电路

W5500 + RJ45(集成变压器)方案:

代码语言:javascript
复制
W5500 侧:
  TXP/TXN ─── 差分对 ─── RJ45 (HR911105A)
  RXP/RXN ─── 差分对 ─── RJ45
  LED0    ─── LINK LED (绿色)
  LED1    ─── ACT LED (黄色)

RJ45 侧:
  中心抽头 (CT) ─── 3.3V (通过 1μF + 2.2μH 偏置)
  屏蔽壳 ─── GND (通过 1nF/2kV 电容 + 1MΩ 电阻并联)

关键设计:

项目

要求

差分阻抗

100Ω ± 10%

差分对长度匹配

< 5mil

远离噪声源

与 DC-DC、晶振间距 > 5mm

ESD 保护

RJ45 差分线加 SRV05-4

变压器隔离

RJ45 内置变压器 ≥ 1500V

3.4 MCU 与 W5500 接口

SPI 连接方式:

代码语言:javascript
复制
STM32F4          W5500
PA4 (SPI1_NSS) ── CS
PA5 (SPI1_SCK) ── SCLK
PA6 (SPI1_MISO) ─ MISO
PA7 (SPI1_MOSI) ─ MOSI
PC4             ── INT (中断输出,低有效)
PC5             ── RST (硬件复位)

四、固件设计

4.1 软件架构

代码语言:javascript
复制
┌────────────────────────────────────────────┐
│ 应用层                                      │
│  ├── 传感器采集任务 (5s周期)                │
│  ├── 网络通信任务 (事件驱动 + 周期发送)     │
│  ├── 配置管理任务 (EEPROM读写)              │
│  └── 看门狗任务 (1s喂狗)                   │
├────────────────────────────────────────────┤
│ W5500 驱动层                                │
│  ├── SPI 读写封装                           │
│  ├── Socket 管理 (8个socket)               │
│  └── 中断处理                               │
├────────────────────────────────────────────┤
│ TCP/IP 协议栈 (W5500 硬件栈)                │
│  ├── TCP Server/Client                      │
│  ├── UDP                                    │
│  └── ICMP (Ping)                            │
├────────────────────────────────────────────┤
│ 硬件抽象层 (HAL)                            │
│  ├── GPIO / SPI / I2C / UART               │
│  ├── 时钟 / 定时器                          │
│  └── Flash / EEPROM                         │
└────────────────────────────────────────────┘

4.2 传感器采集代码

代码语言:javascript
复制
#include "sht3x.h"
#include "i2c.h"

#define SHT3x_ADDR  0x44 << 1
#define POLYNOMIAL  0x31

static uint8_t crc8(const uint8_t *data, int len) {
    uint8_t crc = 0xFF;
    for (int i = 0; i < len; i++) {
        crc ^= data[i];
        for (int j = 0; j < 8; j++) {
            if (crc & 0x80)
                crc = (crc << 1) ^ POLYNOMIAL;
            else
                crc <<= 1;
        }
    }
    return crc;
}

int sht3x_read(float *temp, float *humi) {
    uint8_t cmd[2] = {0x2C, 0x06};  // 高精度测量命令
    uint8_t buf[6];

    // 发送测量命令
    if (HAL_I2C_Master_Transmit(&hi2c1, SHT3x_ADDR, cmd, 2, 100) != HAL_OK)
        return -1;

    HAL_Delay(15);  // 等待测量完成 (高精度模式最长 15ms)

    // 读取结果
    if (HAL_I2C_Master_Receive(&hi2c1, SHT3x_ADDR, buf, 6, 100) != HAL_OK)
        return -1;

    // CRC 校验
    if (crc8(buf, 2) != buf[2] || crc8(buf + 3, 2) != buf[5])
        return -2;  // CRC 错误

    // 转换
    int16_t raw_temp = (buf[0] << 8) | buf[1];
    int16_t raw_humi = (buf[3] << 8) | buf[4];

    *temp = -45.0f + 175.0f * raw_temp / 65535.0f;
    *humi = 100.0f * raw_humi / 65535.0f;

    return 0;
}

4.3 W5500 网络通信

代码语言:javascript
复制
#include "w5500.h"
#include "socket.h"

#define SERVER_IP   {10, 20, 30, 100}
#define SERVER_PORT 9000
#define SENSOR_PORT 5000

static uint8_t tx_buf[512];
static uint8_t rx_buf[512];

void network_init(void) {
    uint8_t mac[6] = {0x00, 0x08, 0xDC, 0x01, 0x02, 0x03};
    uint8_t ip[4]  = {10, 20, 30, 41};
    uint8_t sn[4]  = {255, 255, 255, 0};
    uint8_t gw[4]  = {10, 20, 30, 1};
    uint8_t dns[4] = {8, 8, 8, 8};

    w5500_init();  // SPI 初始化 + 硬件复位
    setSHAR(mac);  // 设置 MAC 地址
    setSIPR(ip);   // IP 地址
    setSUBR(sn);   // 子网掩码
    setGAR(gw);    // 网关
}

int send_env_data(float temp, float humi, uint32_t seq) {
    uint8_t sock = 0;  // 使用 socket 0

    // 如果 socket 未打开,先打开
    if (getSn_SR(sock) != SOCK_ESTABLISHED) {
        socket(sock, Sn_MR_TCP, SENSOR_PORT, 0);
        connect(sock, SERVER_IP, SERVER_PORT);
        // 等待连接建立(带超时)
        uint32_t start = HAL_GetTick();
        while (getSn_SR(sock) != SOCK_ESTABLISHED) {
            if (HAL_GetTick() - start > 5000) {
                close(sock);
                return -1;  // 连接超时
            }
        }
    }

    // 构造报文
    uint8_t *p = tx_buf;
    *p++ = 0xAA;                    // 帧头
    *p++ = 0x55;
    *p++ = 0x01;                    // 协议版本
    memcpy(p, &seq, 4);  p += 4;    // 序列号
    memcpy(p, &temp, 4); p += 4;    // 温度 (float)
    memcpy(p, &humi, 4); p += 4;    // 湿度 (float)
    uint16_t crc = calc_crc16(tx_buf, p - tx_buf);
    memcpy(p, &crc, 2);  p += 2;

    int len = p - tx_buf;

    // 发送
    int sent = send(sock, tx_buf, len);
    if (sent < 0) {
        close(sock);
        return -2;  // 发送失败
    }

    return 0;
}

4.4 主循环

代码语言:javascript
复制
int main(void) {
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_SPI1_Init();
    MX_I2C1_Init();
    MX_TIM2_Init();

    network_init();
    load_config_from_eeprom();  // 加载设备配置

    uint32_t last_sample = 0;
    uint32_t seq = 0;
    float temp, humi;

    while (1) {
        uint32_t now = HAL_GetTick();

        // 每 5 秒采集一次
        if (now - last_sample >= 5000) {
            last_sample = now;

            if (sht3x_read(&temp, &humi) == 0) {
                send_env_data(temp, humi, seq++);
            }
        }

        // 喂看门狗
        HAL_IWDG_Refresh(&hiwdg);

        // 处理 W5500 中断
        if (w5500_interrupt_pending()) {
            w5500_process_interrupt();
        }

        HAL_Delay(10);
    }
}

五、服务器数据接收程序

5.1 设计目标

目标

说明

高并发

支持 1000+ 终端同时连接

数据完整

不丢包、不重复

协议解析

解析自定义二进制协议

存储

写入时序数据库

告警

越限实时告警

设备管理

在线状态、设备注册

5.2 架构选择

代码语言:javascript
复制
方案对比:
  Python asyncio  → 开发快,性能中等,适合 500 以内连接
  Go net          → 开发中等,性能高,适合 1000+ 连接  ← 选择
  C epoll         → 开发慢,性能最高,维护成本高
  Node.js         → 不适合 CPU 密集型解析

5.3 Go 服务端实现

代码语言:javascript
复制
package main

import (
    "bytes"
    "encoding/binary"
    "fmt"
    "net"
    "sync"
    "time"

    "github.com/influxdata/influxdb-client-go/v2"
)

// 报文结构
type EnvPacket struct {
    Header  uint16  // 0xAA55
    Version uint8   // 协议版本
    Seq     uint32  // 序列号
    Temp    float32 // 温度
    Humi    float32 // 湿度
    CRC     uint16  // CRC16
}

// 设备状态
type Device struct {
    ID        string
    Addr      net.Addr
    LastSeen  time.Time
    Seq       uint32
    Connected bool
    mu        sync.Mutex
}

var (
    devices   = make(map[string]*Device)
    devicesMu sync.RWMutex
    influxClient influxdb2.Client
)

func handleConnection(conn net.Conn) {
    defer conn.Close()

    remoteAddr := conn.RemoteAddr().String()
    deviceID := extractDeviceID(remoteAddr) // 从 IP 或证书提取

    // 注册设备
    dev := registerDevice(deviceID, conn.RemoteAddr())
    defer dev.setOffline()

    // 设置读取超时
    conn.SetReadDeadline(time.Now().Add(60 * time.Second))

    buf := make([]byte, 1024)
    for {
        n, err := conn.Read(buf)
        if err != nil {
            if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
                // 超时,检查心跳
                if time.Since(dev.LastSeen) > 30*time.Second {
                    fmt.Printf("[%s] Heartbeat timeout\n", deviceID)
                    return
                }
                conn.SetReadDeadline(time.Now().Add(60 * time.Second))
                continue
            }
            fmt.Printf("[%s] Read error: %v\n", deviceID, err)
            return
        }

        // 解析报文
        pkt, err := parsePacket(buf[:n])
        if err != nil {
            fmt.Printf("[%s] Parse error: %v\n", deviceID, err)
            continue
        }

        // 更新设备状态
        dev.mu.Lock()
        dev.Seq = pkt.Seq
        dev.LastSeen = time.Now()
        dev.mu.Unlock()

        // 写入时序数据库
        writeToInflux(deviceID, pkt)

        // 检查告警
        checkAlarm(deviceID, pkt)

        // 发送 ACK
        sendAck(conn, pkt.Seq)
    }
}

func parsePacket(data []byte) (*EnvPacket, error) {
    if len(data) < 15 { // 最小报文长度
        return nil, fmt.Errorf("packet too short: %d bytes", len(data))
    }

    pkt := &EnvPacket{}

    // 解析帧头
    pkt.Header = binary.BigEndian.Uint16(data[0:2])
    if pkt.Header != 0xAA55 {
        return nil, fmt.Errorf("invalid header: 0x%04X", pkt.Header)
    }

    pkt.Version = data[2]
    pkt.Seq = binary.BigEndian.Uint32(data[3:7])
    pkt.Temp = float32frombytes(data[7:11])
    pkt.Humi = float32frombytes(data[11:15])

    // CRC 校验
    if len(data) >= 17 {
        pkt.CRC = binary.BigEndian.Uint16(data[15:17])
        calcCRC := crc16(data[:15])
        if calcCRC != pkt.CRC {
            return nil, fmt.Errorf("CRC mismatch: calc=0x%04X, recv=0x%04X",
                calcCRC, pkt.CRC)
        }
    }

    return pkt, nil
}

func writeToInflux(deviceID string, pkt *EnvPacket) {
    writeAPI := influxClient.WriteAPI("org", "bucket")
    p := influxdb2.NewPoint(
        "env_data",
        map[string]string{"device_id": deviceID},
        map[string]interface{}{
            "temp": pkt.Temp,
            "humi": pkt.Humi,
            "seq":  pkt.Seq,
        },
        time.Now(),
    )
    writeAPI.WritePoint(p)
}

func checkAlarm(deviceID string, pkt *EnvPacket) {
    if pkt.Temp > 40.0 {
        triggerAlarm(deviceID, "HIGH_TEMP", pkt.Temp)
    }
    if pkt.Humi > 80.0 {
        triggerAlarm(deviceID, "HIGH_HUMI", pkt.Humi)
    }
}

func main() {
    // 初始化 InfluxDB
    influxClient = influxdb2.NewClient("http://localhost:8086", "token")

    listener, err := net.Listen("tcp", ":9000")
    if err != nil {
        panic(err)
    }
    defer listener.Close()

    fmt.Println("Server listening on :9000")

    for {
        conn, err := listener.Accept()
        if err != nil {
            fmt.Printf("Accept error: %v\n", err)
            continue
        }
        go handleConnection(conn)
    }
}

5.4 设备注册与心跳管理

代码语言:javascript
复制
func registerDevice(deviceID string, addr net.Addr) *Device {
    devicesMu.Lock()
    defer devicesMu.Unlock()

    if dev, ok := devices[deviceID]; ok {
        dev.mu.Lock()
        dev.Addr = addr
        dev.Connected = true
        dev.LastSeen = time.Now()
        dev.mu.Unlock()
        fmt.Printf("[%s] Reconnected from %s\n", deviceID, addr)
        return dev
    }

    dev := &Device{
        ID:        deviceID,
        Addr:      addr,
        Connected: true,
        LastSeen:  time.Time{},
    }
    devices[deviceID] = dev
    fmt.Printf("[%s] New device registered from %s\n", deviceID, addr)
    return dev
}

func (d *Device) setOffline() {
    d.mu.Lock()
    d.Connected = false
    d.mu.Unlock()
    fmt.Printf("[%s] Device offline\n", d.ID)
}

// 后台心跳检测
func heartbeatMonitor() {
    ticker := time.NewTicker(10 * time.Second)
    defer ticker.Stop()

    for range ticker.C {
        now := time.Now()
        devicesMu.RLock()
        for _, dev := range devices {
            dev.mu.Lock()
            if dev.Connected && now.Sub(dev.LastSeen) > 30*time.Second {
                fmt.Printf("[%s] Heartbeat timeout, marking offline\n", dev.ID)
                dev.Connected = false
            }
            dev.mu.Unlock()
        }
        devicesMu.RUnlock()
    }
}

5.5 数据校验与容错

代码语言:javascript
复制
// CRC16-CCITT
func crc16(data []byte) uint16 {
    crc := uint16(0xFFFF)
    for _, b := range data {
        crc ^= uint16(b) << 8
        for i := 0; i < 8; i++ {
            if crc & 0x8000 != 0 {
                crc = (crc << 1) ^ 0x1021
            } else {
                crc <<= 1
            }
        }
    }
    return crc & 0xFFFF
}

// 序列号去重
type Deduplicator struct {
    seen map[string]map[uint32]time.Time
    mu   sync.Mutex
}

func (d *Deduplicator) isDuplicate(deviceID string, seq uint32) bool {
    d.mu.Lock()
    defer d.mu.Unlock()

    if _, ok := d.seen[deviceID]; !ok {
        d.seen[deviceID] = make(map[uint32]time.Time)
    }

    if _, ok := d.seen[deviceID][seq]; ok {
        return true  // 重复
    }

    d.seen[deviceID][seq] = time.Now()
    return false
}

六、部署与运维

6.1 终端部署清单

代码语言:javascript
复制
□ PCB 焊接完成,功能测试通过
□ 外壳组装,防护等级验证
□ MAC 地址烧录,IP 地址配置
□ 传感器校准(与标准仪器比对)
□ 上电测试,通信正常
□ 安装到配电柜指定位置
□ 走线完成,屏蔽接地
□ 服务器添加设备记录
□ 验收测试

6.2 服务器部署

代码语言:javascript
复制
□ Go 程序编译,交叉编译支持目标平台
□ systemd 服务配置(自动重启)
□ 防火墙开放 9000 端口
□ InfluxDB 数据库初始化
□ Grafana 仪表盘配置
□ 告警规则配置
□ 日志轮转配置

6.3 监控指标

指标

告警阈值

在线设备数

< 预期数量的 95%

数据接收速率

突降 > 50%

报文解析失败率

> 1%

设备心跳超时

连续 3 次

数据库写入延迟

> 1s


七、经验总结

从硬件电路到服务器程序,完整开发一套 RJ45 温湿度监测终端,核心经验:

  1. 硬件设计要"过度防御"。电力环境的干扰远超实验室,电源、信号、通信每一层都要加保护。省掉的防护器件,最终都会变成现场排障的时间。
  2. 固件要"简单可靠"。在资源受限的 MCU 上,不要追求复杂功能。单线程 + 状态机往往比 RTOS + 多任务更稳定。
  3. 通信协议要"自描述"。帧头、版本号、序列号、CRC,这些看似多余的字段,在排障时是救命稻草。
  4. 服务端要"假设客户端会出错"。任何字段都可能是错的,任何连接都可能中断。防御性编程不是可选项。
  5. 日志是排障的唯一依据。现场问题无法复现,只能靠日志还原。关键节点都要有日志,但注意不要在高频路径上打日志影响性能。

关键词:RJ45传感器,硬件电路设计,STM32,W5500,SHT35,以太网,TCP服务器,Go语言,时序数据库,配电柜监测

标签:#RJ45 #硬件设计 #STM32 #W5500 #温湿度传感器 #嵌入式开发 #TCP服务器 #Go语言 #配电柜 #环境监测

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 独立配电柜温湿度监测:RJ45传感器硬件电路设计与服务器数据接收程序
    • 一、从"用产品"到"做产品"的跨越
    • 二、硬件电路总体架构
      • 1. 系统框图
      • 2. 核心器件选型
    • 三、关键电路设计
      • 3.1 电源电路
      • 3.2 温湿度传感电路
      • 3.3 以太网接口电路
      • 3.4 MCU 与 W5500 接口
    • 四、固件设计
      • 4.1 软件架构
      • 4.2 传感器采集代码
      • 4.3 W5500 网络通信
      • 4.4 主循环
    • 五、服务器数据接收程序
      • 5.1 设计目标
      • 5.2 架构选择
      • 5.3 Go 服务端实现
      • 5.4 设备注册与心跳管理
      • 5.5 数据校验与容错
    • 六、部署与运维
      • 6.1 终端部署清单
      • 6.2 服务器部署
      • 6.3 监控指标
    • 七、经验总结
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档