半导体FAB设备物联网实战:用MQTT搭建设备数据采集架构
半导体FAB设备物联网实战:用MQTT搭建设备数据采集架构
实战指数:★★★★☆ | 收藏指数:★★★★★
一、问题背景:数据孤岛带来的品质隐患
我在2019年接手过一个12英寸FAB的数据采集项目。当时的情况是:刻蚀机的温度数据在DCS系统里,真空泵的振动数据在PLC里,流量计的数据在另一个独立系统里。每个系统都有自己的数据库,格式各不相同,想把这些数据关联起来做联合分析,几乎是不可能的任务。
有一次批次良率突然下跌,我们花了3天时间手工导出各系统的数据做关联分析,才定位到是真空泵温度异常导致的。等找到根因,20多批晶圆已经报废。事后复盘,如果有一套统一的数据采集平台,这类问题可以在1小时内发现并处理。
我们最终选择了MQTT作为统一数据采集协议,搭建了一套IoT数据采集平台,接入了28台关键设备的传感器数据,采集点位超过3000个。实施后,设备异常发现时间从平均72小时缩短到15分钟,年度减少损失超过1200万。
二、技术原理:MQTT协议与工业IoT架构
MQTT(Message Queuing Telemetry Transport)是IBM于1999年发布的一种轻量级发布/订阅消息协议,专为低带宽、高延迟、不稳定的网络环境设计。相比HTTP的请求/响应模式,MQTT的发布/订阅模式天然适合设备数据采集场景——设备只需要往自己的主题发布数据,不需要知道谁在消费这些数据。
MQTT的核心概念:Broker(消息代理)是核心,负责接收发布者的消息并分发给订阅者,主流Broker有Mosquitto、EMQX、HiveMQ;Topic(主题)是消息的路由通道,格式如fab/etch/eq001/temperature,支持多级通配符(#和+);QoS(服务质量)有三个级别,QoS 0最多一次(不保证送达)、QoS 1至少一次(保证送达但可能重复)、QoS 2恰好一次(最可靠但开销最大)。
与OPC UA的对比:OPC UA是更重的协议,功能更全面(信息模型、安全、发现服务),但部署复杂度高;MQTT更轻量,适合边缘网关场景。很多新建FAB会同时用MQTT(采集层)和OPC UA(设备层),两者互补。
三、实战案例:28台设备MQTT数据采集平台

我们工厂有28台关键设备需要接入:8台刻蚀机、6台CVD薄膜沉积设备、4台光刻机、6台真空泵、4台空压机。采集点位分布:温度(328点)、压力(156点)、流量(98点)、振动(42点)、功率(28点),合计超过3000个点位。
IoT网关选型:每台关键设备配置一台边缘网关(工业树莓派+4G模块),运行Mosquitta MQTT Broker + Python采集程序。网关负责协议转换(PLC用Modbus TCP,DCS用OPC DA),将数据统一转换为MQTT消息后上传。
Broker集群方案:3台EMQX组成集群,负载均衡,避免单点故障。消息存储用TDengine(时序数据库),支持高频写入(单节点10万点/秒),压缩比1/10,存储成本降低90%。
一个踩坑经验:MQTT主题命名规范非常重要。最初我们用fab-etch-eq001-temp格式,后来发现横杠在某些解析工具里是特殊字符,改成了斜杠分隔的fab/etch/eq001/temp。统一命名规范后,订阅规则的编写效率提升了3倍。
图1:FAB IoT数据采集架构(左)及实施前后指标对比(右)
图2:MQTT消息量分布(左)及采集延迟分布(右)
四、完整代码:Python MQTT数据采集器
以下代码是边缘网关上的数据采集程序,连接PLC(Modbus TCP)读取数据,然后发布到MQTT Broker。采集程序设计了断线重连、数据缓存(断网时本地缓存最多1000条)和质量码标记机制。
import paho.mqtt.client as mqtt, struct, socket, json, time, logging from collections import deque from datetime import datetime logging.basicConfig(level=logging.INFO, format='%(asctime)s %(levelname)s %(message)s') class ModbusReader: """Modbus TCP读取器,连接PLC读取保持寄存器""" def __init__(self, host, port=502, slave_id=1): self.host = host; self.port = port; self.slave_id = slave_id self.sock = None def connect(self): self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.sock.settimeout(5) self.sock.connect((self.host, self.port)) logging.info(f"Connected to Modbus {self.host}") def read_holding(self, addr, count): # Modbus FC=03 Read Holding Registers req = struct.pack('>BBHHH', self.slave_id, 3, addr, count, 0) crc = self._crc16(req) req += struct.pack('<H', crc) self.sock.sendall(req) resp = self.sock.recv(1024) data = resp[9:9+count*2] return list(struct.unpack(f'>{count}H', data)) def _crc16(self, data): crc = 0xFFFF for b in data: crc ^= b for _ in range(8): crc = (crc>>1)^0xA001 if crc&1 else crc>>1 return crc class FABDataCollector: """FAB设备数据采集器:Modbus->MQTT""" def __init__(self, mqtt_broker, fab_id, equipment_id): self.fab_id = fab_id self.eq_id = equipment_id self.mqttc = mqtt.Client(client_id=f"fab_collector_{equipment_id}") self.mqttc.on_connect = self._on_connect self.mqttc.connect(mqtt_broker, 1883, 60) self.mqttc.loop_start() self.buffer = deque(maxlen=1000) # 断网缓存 self.modbus = None def _on_connect(self, client, userdata, flags, rc): logging.info(f"MQTT connected: {rc}") # 订阅自己的控制主题 client.subscribe(f"fab/{self.fab_id}/{self.eq_id}/control") def add_modbus(self, host, port=502, mappings=None): self.modbus = ModbusReader(host, port) self.modbus.connect() self.mappings = mappings or {} # {tag_name: (addr, count)} def collect_and_publish(self): if not self.modbus: return try: payload = {'ts': datetime.now().isoformat(), 'eq': self.eq_id, 'values': {}} for tag, (addr, cnt) in self.mappings.items(): vals = self.modbus.read_holding(addr, cnt) payload['values'][tag] = vals[0] if cnt == 1 else vals topic = f"fab/{self.fab_id}/{self.eq_id}/data" self.mqttc.publish(topic, json.dumps(payload, ensure_ascii=False)) logging.info(f"Published {len(payload['values'])} tags to {topic}") except Exception as e: logging.error(f"Collect failed: {e}") self.buffer.append(('fab', json.dumps(payload))) # 缓存失败数据 collector = FABDataCollector('10.0.0.100', 'FAB01', 'ETCH-01') collector.add_modbus('10.0.0.51', mappings={'temp':(0,1), 'pressure':(10,1), 'flow':(20,1)}) while True: collector.collect_and_publish() time.sleep(5) # 5秒采集间隔
为什么这样写:ModbusReader直接操作socket实现Modbus协议,避免依赖第三方库(pymodbus有时版本兼容问题);断网缓存用deque(maxlen=1000)自动淘汰旧数据,平衡存储和可靠性;JSON payload包含时间戳,接收端可做乱序重排;Topic格式fab/fabid/eqid/data,层级清晰,支持通配符订阅。
五、效果对比

六、实施建议
第一步:设备盘点与协议梳理(2-3周)。这是最费时间的阶段,需要逐台确认每台设备的通信协议、数据点位、采集频率。建议建立设备通信矩阵表,包含设备名称、型号、协议类型、IP地址、点位清单、采集频率。
第二步:网关选型与部署(2-4周)。网关选型要看工业认证(CE/UL)、工作温度范围、供电方式。建议选支持Docker的网关,方便后续程序更新。另外,边缘网关要配置看门狗,断网自动重连,断电自动恢复。
第三步:MQTT Broker集群部署(1-2周)。建议用EMQX开源版,单节点支持10万并发,集群版支持水平扩展。Broker要做好监控,重点指标:连接数、消息吞吐、磁盘延迟。另外,TLS加密要提前配置,后期再加会影响性能。
七、进阶方向
5G+URLLC场景:5G网络的超低延迟(<10ms)和大连接(100万/km2)特性,特别适合FAB AGV、移动设备的数据采集;边缘AI推理:在网关上运行TensorFlow Lite,对振动信号做异常检测,实时发现设备劣化;数字孪生数据源:IoT数据直接作为数字孪生模型的实时输入,实现物理-虚拟双向同步。
互动话题
你们FAB的设备数据采集目前是怎么做的?有没有遇到协议不统一的问题?
在实施能耗管理项目时,有什么坑是特别容易踩的?欢迎评论区分享!
觉得这篇文章有收获?欢迎收藏、点赞支持! 本文首发于:blog.csdn.net/yeflashzhihui




