当前位置:首页 > 智能制造 CIM/MES > 正文内容

半导体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

标签: 半导体

相关文章

良率工程实战:从72%到89%的完整爬坡路径

良率工程实战:从72%到89%的完整爬坡路径

良率工程实战:从72%到89%的完整爬坡路径 一、问题背景:良率是晶圆厂的生命线 良率(Yield)是晶圆厂最核心的KPI,直接决定了盈利能力和市场竞争力。我在晶圆厂负责良率工程的这些年,深刻体会到良...

SPC统计过程控制:FAB质量管理的定海神针

SPC统计过程控制:FAB质量管理的定海神针

SPC统计过程控制:FAB质量管理的定海神针 Statistical Process Control — 用数据说话,让异常无处遁形 一、问题背景:FAB里每天产生上百万个数据点,靠什么来管理质量?...

刻蚀工艺深度解析:干法刻蚀vs湿法刻蚀怎么选

刻蚀工艺深度解析:干法刻蚀vs湿法刻蚀怎么选

刻蚀工艺深度解析:干法刻蚀vs湿法刻蚀怎么选 大家好,我是老张。前面讲完了光刻,今天聊聊刻蚀(Etching)。如果说光刻是「画图」,那刻蚀就是「刻字」——把光刻转移到光刻胶上的图形,精确地转移到下面...

半导体产业全景:从沙子到芯片的完整产业链

半导体产业全景:从沙子到芯片的完整产业链

半导体产业全景:从沙子到芯片的完整产业链 大家好,我是老张,在半导体行业摸爬滚打了十五年。从Fab厂的一线工艺工程师,到现在的产业分析师,我有幸见证了这个行业最波澜壮阔的十年。今天,我想用最接地气的方...

晶圆制造全流程:硅片是怎么从沙子变出来的

晶圆制造全流程:硅片是怎么从沙子变出来的

晶圆制造全流程:硅片是怎么从沙子变出来的 大家好,我是老张。上篇讲了半导体产业全景,很多朋友私信说「想深入了解晶圆制造」。今天我就把这部分展开,从一捧沙子到一片光洁如镜的硅晶圆,每一步的参数、原理、设...

CMP化学机械抛光:让晶圆表面平整到原子级

CMP化学机械抛光:让晶圆表面平整到原子级

CMP化学机械抛光:让晶圆表面平整到原子级 Chemical Mechanical Planarization — 半导体制造中最精密的表面平坦化技术 一、问题背景:为什么芯片需要"磨皮&q...