1. 项目概述当Pico W遇上工业协议在嵌入式开发和工业数据采集的交叉点上我们常常会遇到一个经典问题如何让一个低成本、小巧的微控制器去和那些“大家伙”——比如工厂里的PLC、电表、温控器——说上话这些工业设备大多遵循着几十年来沉淀下来的通信标准其中Modbus协议尤其是其RTU串行版本几乎成了工业现场的“普通话”。而树莓派Pico W凭借其双核RP2040芯片、丰富的GPIO和极低的功耗正是一个绝佳的、可编程的“翻译官”人选。这个项目的核心价值就是打通这条从现代微控制器到传统工业设备的对话通道。它解决的不仅仅是“能不能读数据”的问题更是“如何以极低的成本和复杂度稳定可靠地读取数据”的问题。想象一下你需要监测一个分布式太阳能电站里几十个逆变器的运行状态或者想给一台老旧的机床加装一个物联网数据看板传统的方案可能需要工控机、专用网关成本动辄上千。而用树莓派Pico W加上一个十几块钱的RS485转换模块配合几十行MicroPython代码就能实现同样的核心功能。这为设备远程监控、小型自动化系统、教学实验乃至产品原型开发打开了一扇极具性价比的大门。本文适合所有对嵌入式系统、物联网数据采集或工业通信感兴趣的开发者、工程师和学生。无论你是想将一个小创意落地为可交互的物理原型还是需要为现有设备添加数据上报功能亦或是单纯想学习Modbus这一经典协议下面的内容都将从硬件连线到软件调试为你提供一份可直接“抄作业”的完整指南。我会结合自己多次在嘈杂工业现场调试Modbus设备的经验不仅告诉你步骤怎么做更会重点解释为什么这么做以及过程中哪些坑可以提前避开。2. 核心思路与方案选型解析2.1 为什么是Modbus RTU RS485在开始动手之前我们得先搞清楚技术选型背后的逻辑。Modbus协议主要有三种变体TCP、RTU和ASCII。在工业串行通信领域RTU格式因其二进制编码、高数据密度和CRC校验成为了绝对的主流。它通常跑在RS485物理层上这是一种差分信号传输方式。选择RS485而非更常见的RS232是基于几个关键考量通信距离和组网能力。RS232通常只能点对点通信有效距离一般不超过15米。而RS485采用差分平衡传输抗共模干扰能力极强通信距离轻松可达1200米以上并且支持总线式拓扑一条总线上可以挂接多达32个甚至更多的设备通过中继器可扩展至256个。这对于工业现场分散的设备布局来说是刚需。树莓派Pico W的UART接口输出的是TTL电平0V/3.3V无法直接驱动RS485总线因此中间必须有一个“翻译官”——TTL转RS485转换器。这个转换器的核心是一个收发器芯片如MAX485、SP3485它负责将TTL电平的UART信号转换为RS485标准的差分信号。2.2 为什么是MicroPython而非C/CPico W官方支持MicroPython和C/C两种开发方式。对于这个项目我强烈推荐MicroPython原因有三开发效率、生态友好和调试便捷性。首先Python语法简洁无需处理复杂的内存管理和指针让开发者能更专注于业务逻辑——也就是Modbus协议本身。其次MicroPython社区有成熟稳定的micropython-modbus库通常以umodbus为名它封装了Modbus RTU/ASCII和TCP的客户端主站和服务器从站功能我们直接调用几个清晰的API就能完成通信避免了从零实现协议解析的繁琐和潜在错误。最后配合Thonny这类IDE我们可以实现交互式REPL读取-求值-打印循环和直接文件系统访问。这意味着你可以一边运行代码一边在串口终端里实时查看变量、发送测试命令出现异常时也能立刻获得错误回溯信息这对于通信协议的调试来说是巨大的福音。当然C/C在极致性能和底层硬件控制上有优势但对于大多数数据采集场景读取周期通常在秒级以上MicroPython的性能完全绰绰有余。这个选择体现了“用合适的工具解决合适的问题”的工程思维。2.3 硬件连接拓扑与信号流整个系统的信号流非常清晰Pico W (UART TX/RX) - TTL转RS485模块 (逻辑电平转换) - RS485总线 (差分信号传输) - 从站设备。这里需要理解一个关键细节RS485是半双工通信。也就是说在同一时刻总线只能处于发送或接收其中一种状态。TTL转RS485模块上通常会有一个“方向控制引脚”如DE/RE或标为DIR。这个引脚需要由Pico W的一个GPIO来控制。当Pico W要发送数据作为主站发起请求时需要将该引脚置为高电平使能发送器发送完毕后立即置为低电平切换为接收状态等待从站的响应。很多简化版的教程或模块内部做了自动方向控制但对于追求稳定性和兼容性的工业应用手动、显式地控制方向引脚是必须遵循的最佳实践。我们后续的代码也会体现这一点。3. 硬件准备与连接实战3.1 物料清单与选型要点开始焊接或插线前请确保你手头有以下物料树莓派Pico W注意是带Wi-Fi的“W”版本虽然本项目暂未用到无线功能但W版本是主流且资源更多。TTL转RS485转换器模块这是关键。市面上常见的有基于MAX485、SP3485等芯片的模块。选购时注意电压匹配确保模块支持3.3V逻辑电平与Pico W一致。5V模块虽然可能也能工作但长期使用有损坏Pico W GPIO的风险。引脚引出理想的模块应明确引出VCC、GND、TX、RX、DIR方向控制以及RS485的A、B端子。防护与隔离对于工业环境可以考虑选择带光电隔离和浪涌防护的型号虽然价格稍高但能极大提升系统在恶劣电气环境下的生存能力。Modbus从站设备可以是一个真实的设备如支持Modbus RTU的温湿度传感器、电力监测仪也可以使用软件模拟器。对于学习和测试强烈推荐使用软件模拟器如“Modbus Slave”Windows或基于Python的pymodbus库创建一个模拟从站。这能让你完全掌控寄存器的地址和值方便调试。USB数据线用于给Pico W供电和编程。杜邦线若干用于连接。RS485总线电缆如果连接真实设备且距离较远需要使用双绞线缆如屏蔽双绞线。3.2 接线图与引脚定义详解接线是物理基础务必仔细。我们以Pico W最常见的引脚布局和一款通用RS485模块为例**Pico W侧 (GPIO编号) **GP0 (UART0 TX): 连接至转换器模块的RX引脚。注意Pico的TX要接模块的RX。GP1 (UART0 RX): 连接至转换器模块的TX引脚。Pico的RX接模块的TX。GP2 (任意GPIO): 连接至转换器模块的DIR方向控制引脚。这个引脚可以自由选择在代码中指定即可。3V3(OUT): 连接至转换器模块的VCC为其供电。GND: 连接至转换器模块的GND共地。RS485转换器模块侧A: 连接至从站设备的RS485A端子或标为D,Data。B: 连接至从站设备的RS485B端子或标为D-,Data-。从站设备侧同样找到A和B端子与转换器对应连接。务必注意极性A对AB对B。接反通常会导致通信失败。如果总线上有多个设备所有设备的A和B分别并联在一起形成总线。并在总线的最远端两个设备上A和B之间需要并联一个120欧姆的终端电阻以消除信号反射。对于只有一主一从的短距离测试可以暂时不接。重要提示在接通电源前请再次核对TX/RX交叉连接以及A/B的极性。错误的接线是导致“毫无反应”的最常见原因。3.3 上电检查与基础测试连接好所有线路后通过USB线将Pico W连接到电脑。此时Pico W的电源指示灯应亮起RS485模块上的电源指示灯如果有也应点亮。一个快速的硬件测试方法是使用串口调试助手。你可以暂时不写代码先用串口工具打开Pico W识别出的串口在设备管理器中查看端口号如COM3波特率设置为9600。然后手动通过串口调试助手发送一条符合Modbus RTU格式的请求报文需要计算CRC。如果硬件连接和从站设置正确你应该能在调试助手中收到从站的回复。这个测试能帮你快速隔离问题是出在硬件层还是软件层。4. 软件环境搭建与库部署4.1 MicroPython固件刷写与IDE选择Pico W出厂时通常是空白状态我们需要先刷入MicroPython解释器。按住Pico W板上的BOOTSEL按钮不放同时将其通过USB线连接到电脑。然后松开按钮。电脑上会出现一个名为RPI-RP2的可移动磁盘。从树莓派官网下载最新的Pico W MicroPython固件文件.uf2格式。将该.uf2文件拖拽到RPI-RP2磁盘中。Pico W会自动重启此时磁盘会消失MicroPython固件即刷写完成。接下来是开发环境。Thonny IDE是MicroPython开发的首选它集成了固件安装、文件管理和REPL交互对新手极其友好。当然你也可以使用VS Code搭配RT-Thread MicroPython插件或者更硬核地直接使用mpremote命令行工具。本文以Thonny为例进行说明。4.2 关键库umodbus的安装与验证MicroPython的强大在于其库生态。我们需要安装umodbus库。在Thonny中操作最为简单打开Thonny确保右下角解释器选择为“MicroPython (Raspberry Pi Pico)”并正确识别了串口。点击顶部菜单“工具” - “管理包”。在搜索框中输入micropython-modbus找到后点击“安装”。Thonny会自动处理下载和安装到Pico W板载存储的过程。安装完成后我们可以写一个简单的测试脚本验证库是否可用# test_umodbus.py import sys try: from umodbus import version print(fuModbus library version: {version.__version__}) print(Library imported successfully!) except ImportError as e: print(Failed to import uModbus:, e)在Thonny中运行这个脚本如果成功打印出版本号说明库环境就绪。实操心得网络安装有时会因为连接问题失败。备用方案是从micropython-modbus的GitHub仓库手动下载umodbus文件夹包含__init__.py,serial.py,client.py等所有文件然后通过Thonny的文件浏览器将它们整体上传到Pico W的根目录或lib目录下。这种方式在离线环境下尤其可靠。5. 代码逐行解析与深度定制理解了硬件和库之后我们来深入剖析核心代码并把它从一个简单的示例扩展成一个健壮、可配置的实用脚本。5.1 基础代码结构与参数详解让我们先回顾并完善原始示例代码# main.py - Modbus RTU Master for Reading Holding Registers import time from machine import Pin, UART from umodbus.serial import Serial as ModbusRTUMaster # 用户配置区域 # 1. 硬件引脚定义 UART_TX_PIN 0 # GP0, 连接到转换器RX UART_RX_PIN 1 # GP1, 连接到转换器TX DIR_PIN_NUM 2 # GP2, 方向控制引脚连接转换器DIR # 2. Modbus通信参数 SLAVE_ADDRESS 1 # 从站设备地址范围1-247 START_ADDRESS 40001 # 起始寄存器地址常用4xxxx表示保持寄存器 REGISTER_QTY 3 # 要读取的连续寄存器数量 BAUD_RATE 9600 # 波特率必须与从站严格一致 PARITY None # 校验位: None(无), 0(偶), 1(奇) STOP_BITS 1 # 停止位 DATA_BITS 8 # 数据位 # 3. 读取间隔秒 READ_INTERVAL 5.0 # # 初始化方向控制引脚 dir_pin Pin(DIR_PIN_NUM, Pin.OUT) dir_pin.value(0) # 初始化为接收模式 # 初始化UART - 注意UART ID 0 对应 GP0/GP1, UART ID 1 对应 GP4/GP5 uart UART(0, baudrateBAUD_RATE, bitsDATA_BITS, parityPARITY, stopSTOP_BITS, txPin(UART_TX_PIN), rxPin(UART_RX_PIN)) # 初始化Modbus RTU主站传入UART对象和方向控制引脚 # 关键ctrl_pin参数告诉库用哪个引脚控制收发方向ctrl_on_tx为True表示发送时置高 host ModbusRTUMaster(uartuart, ctrl_pindir_pin, ctrl_on_txTrue) print(fModbus RTU Master Initialized.) print(fTarget Slave: {SLAVE_ADDRESS}, Reading from address {START_ADDRESS} for {REGISTER_QTY} registers.) print(- * 40) # 主循环 while True: try: # 注意umodbus库的地址参数通常使用“0-based”偏移量。 # 即 Modbus地址 40001 对应 偏移量 040002 对应 1以此类推。 offset_address START_ADDRESS - 40001 # 计算偏移量 print(f[{time.ticks_ms()//1000}s] Reading {REGISTER_QTY} registers from slave {SLAVE_ADDRESS}...) # 核心函数调用读取保持寄存器 values host.read_holding_registers( slave_addrSLAUSE_ADDRESS, starting_addroffset_address, # 传入偏移量 register_qtyREGISTER_QTY, signedFalse # 数据是否为有符号整数。False表示无符号(0-65535) ) # 打印结果 print(f Success! Raw values: {values}) # 格式化输出显示每个寄存器的地址和值 for i, val in enumerate(values): actual_modbus_addr START_ADDRESS i print(f Register {actual_modbus_addr} (0x{actual_modbus_addr-40000:04X}): {val} (0x{val:04X})) except Exception as e: # 捕获所有异常避免程序因单次通信失败而崩溃 print(f Error reading registers: {e}) # 可以根据异常类型做更精细的处理如超时重试、复位等 # 等待下一次读取 time.sleep(READ_INTERVAL)5.2 关键参数与函数深度剖析START_ADDRESS 40001这是Modbus协议中“保持寄存器”的地址表示法。协议定义寄存器范围为40001-49999对应的是功能码0x03读保持寄存器。在底层通信帧中实际传输的是从0x0000开始的偏移地址。因此代码里需要做offset_address START_ADDRESS - 40001这个转换。常见误区有些设备手册直接给出十六进制地址如0x0000这已经是偏移量无需再减40001。signedFalse这个参数决定了如何解析从设备返回的16位寄存器值。如果设为False值范围是0~65535如果设为True则将其解释为有符号短整型范围是-32768~32767。你必须根据从站设备的数据手册来确定这个参数。例如一个温度传感器可能用0-65535表示0.0-6553.5℃无符号而一个表示正负功率的寄存器可能直接使用有符号整数。host.read_holding_registers()函数这是umodbus库封装的同步阻塞函数。调用时Pico W会自动将dir_pin置高切换为发送模式。通过UART发送完整的Modbus RTU请求帧从站地址、功能码、起始地址、数量、CRC。将dir_pin置低切换为接收模式。等待从站响应并设置超时超时时间取决于UART配置和库实现。收到响应后校验CRC解析数据返回一个整数列表。如果任何一步出错超时、CRC错误、异常响应则抛出异常。错误处理代码中的try...except块至关重要。工业现场总线可能受到干扰导致单次通信失败。良好的程序不应因此崩溃而应记录错误并继续尝试。你可以扩展异常处理针对不同的错误类型如OSError可能代表超时ValueError可能代表数据解析错误采取不同策略比如短暂延迟后重试。5.3 功能扩展写入寄存器与读取其他类型一个完整的主站当然不止能读。umodbus库同样提供了写入功能。以下是写入单个保持寄存器功能码0x06和写入多个保持寄存器功能码0x10的示例# 写入单个保持寄存器 (Preset Single Register) def write_single_register(slave_addr, reg_address, value): 写入单个寄存器 offset reg_address - 40001 try: # 注意写入的值应在0-65535无符号或-32768~32767有符号范围内 host.write_single_register(slave_addrslave_addr, starting_addroffset, register_valuevalue) print(fWrite single to {reg_address} succeeded.) except Exception as e: print(fWrite single failed: {e}) # 写入多个保持寄存器 (Preset Multiple Registers) def write_multiple_registers(slave_addr, start_address, values_list): 写入多个连续寄存器 offset start_address - 40001 try: host.write_multiple_registers(slave_addrslave_addr, starting_addroffset, valuesvalues_list) print(fWrite multiple to {start_address} with {len(values_list)} values succeeded.) except Exception as e: print(fWrite multiple failed: {e}) # 读取输入寄存器 (Read Input Registers, 功能码0x04) def read_input_registers(slave_addr, start_address, qty): 读取输入寄存器通常只读如传感器瞬时值 offset start_address - 30001 # 输入寄存器地址范围为30001-39999 try: values host.read_input_registers(slave_addrslave_addr, starting_addroffset, register_qtyqty) return values except Exception as e: print(fRead input registers failed: {e}) return None注意事项写入操作是“写”操作请务必确认你写入的寄存器地址在从站设备上是可写的并且写入的值在允许范围内。错误的写入可能导致设备误动作。在测试时强烈建议先使用模拟器或者针对一个无关紧要的寄存器进行。6. 调试技巧与常见问题排查实录即使代码看起来正确第一次通信就成功的概率也不高。下面是我在多次项目中总结出的排查流程和常见问题它们能帮你快速定位问题。6.1 系统性排查流程当通信失败时不要盲目修改代码请按照以下层级逐一排查物理层供电RS485模块的电源灯亮吗Pico W的3.3V输出是否稳定可以用万用表测量。接线TX/RX是否交叉连接A/B线是否接反所有GND是否共地这是最高频的错误点。终端电阻如果通信距离较长超过50米或速率较高超过19200bps总线两端是否接了120Ω终端电阻参数层波特率/数据位/停止位/校验位主站和从站的这些串口参数必须完全一致。一个字节都不能差。最常见的是校验位设置错误从站是偶校验主站设成了无校验。从站地址确认你代码中的SLAVE_ADDRESS与从站设备上设置的地址一致。Modbus地址范围是1-247。数据链路层使用监听工具这是最强大的调试手段。在PC上使用一个USB转RS485适配器并联接入总线接A、B线。在PC上运行串口监听软件如ModScan、Modbus Poll自带的监听功能或通用的串口数据监听工具。这样你就能看到总线上实际流动的每一个字节的原始数据。对比主站发出的请求帧和从站返回的响应帧一切问题都无所遁形。如何分析一个标准的读保持寄存器请求帧格式为[从站地址][功能码0x03][起始地址高8位][起始地址低8位][数量高8位][数量低8位][CRC低8位][CRC高8位]。如果监听不到任何数据说明主站没发出来检查代码和UART初始化如果收到了请求但没响应说明从站没收到或地址不对如果收到了异常响应功能码最高位置1则根据随后的错误码查找原因。6.2 常见错误码与解决方案速查表下表列出了在Modbus通信中常见的异常响应及其含义和排查方向异常代码含义可能原因排查建议0x01非法功能码从站不支持请求的功能码如对只读寄存器发起写操作检查设备手册确认寄存器类型和对应的功能码。0x02非法数据地址请求的寄存器地址超出从站设备允许的范围核对设备手册中的寄存器地址表确认START_ADDRESS和REGISTER_QTY在有效范围内。0x03非法数据值写入寄存器的数据值超出允许范围如向一个只有0/1状态的寄存器写入100检查写入数据的有效范围。0x04从站设备故障从站在处理请求时发生内部错误重启从站设备检查其状态指示灯和日志。无响应/超时主站未收到任何回复1. 物理连接不通断线、A/B反。2. 从站地址错误。3. 波特率等参数不匹配。4. 从站未上电或故障。1. 用万用表通断档检查线路。2. 使用监听工具确认请求帧中的地址。3. 仔细核对所有串口参数。4. 检查从站电源和状态。CRC错误接收到的数据帧CRC校验失败1. 总线干扰严重数据被篡改。2. 波特率轻微偏差导致数据错位。3. 监听工具自身问题误报。1. 检查布线远离强电使用屏蔽线并接地。2. 尝试降低波特率如从115200降到9600测试。3. 换用其他监听工具或方法验证。6.3 高级调试处理干扰与超时在工业现场电气干扰是通信不稳定的主要元凶。除了使用屏蔽双绞线、正确接地、加终端电阻外在软件层面也可以增强鲁棒性import time from machine import Pin, UART from umodbus.serial import Serial as ModbusRTUMaster class RobustModbusMaster: def __init__(self, uart_id, tx_pin, rx_pin, dir_pin, baud9600, retries3, timeout1000): self.dir_pin Pin(dir_pin, Pin.OUT) self.uart UART(uart_id, baudratebaud, txPin(tx_pin), rxPin(rx_pin), timeouttimeout) self.host ModbusRTUMaster(uartself.uart, ctrl_pinself.dir_pin, ctrl_on_txTrue) self.retries retries self.timeout timeout def read_registers_with_retry(self, slave_addr, start_addr, qty): 带重试机制的读取函数 offset start_addr - 40001 last_exception None for attempt in range(self.retries): try: print(fAttempt {attempt1}/{self.retries}...) values self.host.read_holding_registers( slave_addrslave_addr, starting_addroffset, register_qtyqty, signedFalse ) return values # 成功则直接返回 except Exception as e: last_exception e print(f Attempt failed: {e}) if attempt self.retries - 1: time.sleep(0.5 * (attempt 1)) # 指数退避等待 # 所有重试都失败 print(fAll {self.retries} attempts failed. Last error: {last_exception}) return None # 使用示例 master RobustModbusMaster(uart_id0, tx_pin0, rx_pin1, dir_pin2, baud9600, retries3) values master.read_registers_with_retry(slave_addr1, start_addr40001, qty3) if values is not None: print(fRead successful: {values})这个类封装了重试逻辑和简单的指数退避能在遇到偶发性干扰时自动恢复大大提升了通信的可靠性。7. 项目进阶与扩展思路一个能读取数据的系统只是起点。基于这个稳定的通信基础你可以向多个方向扩展构建更实用的应用。7.1 多从站轮询与数据整合工业现场一条总线上往往有多个设备。只需修改从站地址即可实现轮询slave_devices [ {addr: 1, name: Temperature Sensor}, {addr: 2, name: Power Meter}, {addr: 3, name: Valve Controller}, ] for device in slave_devices: print(f\nPolling {device[name]} (Addr: {device[addr]})...) try: # 读取每个从站的不同寄存器 if device[addr] 1: values host.read_holding_registers(slave_addrdevice[addr], starting_addr0, register_qty2) print(f Temperature: {values[0]/10.0}°C, Humidity: {values[1]/10.0}%) elif device[addr] 2: values host.read_input_registers(slave_addrdevice[addr], starting_addr0, register_qty3) print(f Voltage: {values[0]/10.0}V, Current: {values[1]/100.0}A, Power: {values[2]}W) except Exception as e: print(f Failed to poll {device[name]}: {e}) time.sleep(1) # 设备间查询间隔7.2 连接网络与数据上报Pico W的Wi-Fi功能在此大放异彩。你可以将读取到的数据通过MQTT协议发布到物联网平台如阿里云、ThingsBoard或通过HTTP POST发送到自定义服务器。import network import urequests import ujson # 1. 连接Wi-Fi def connect_wifi(ssid, password): wlan network.WLAN(network.STA_IF) wlan.active(True) if not wlan.isconnected(): print(Connecting to network...) wlan.connect(ssid, password) while not wlan.isconnected(): pass print(Network config:, wlan.ifconfig()) # 2. 读取Modbus数据复用之前的代码 def read_sensor_data(): # ... 调用Modbus读取函数 ... return {temp: temperature, humi: humidity, power: power} # 3. 通过HTTP上报数据 def report_to_server(data, api_url): headers {Content-Type: application/json} try: resp urequests.post(api_url, dataujson.dumps(data), headersheaders) print(fHTTP POST Status: {resp.status_code}) resp.close() except Exception as e: print(fHTTP POST failed: {e}) # 主循环 connect_wifi(Your_SSID, Your_PASSWORD) while True: sensor_data read_sensor_data() if sensor_data: report_to_server(sensor_data, http://your-server.com/api/data) time.sleep(60) # 每分钟上报一次7.3 本地逻辑控制与状态机除了数据采集Pico W还可以根据读取的数据做出本地控制决策。例如实现一个简单的温控器def simple_thermostat(current_temp, setpoint25.0, hysteresis1.0): 简单的双位控制滞回控制 global heater_state if current_temp setpoint - hysteresis: if not heater_state: print(Temperature low, turning HEATER ON.) # 这里可以调用 write_single_register 打开一个继电器 # write_single_register(slave_addr3, reg_address40001, value1) heater_state True elif current_temp setpoint hysteresis: if heater_state: print(Temperature high, turning HEATER OFF.) # write_single_register(slave_addr3, reg_address40001, value0) heater_state False # 如果温度在滞回区间内保持原状态不变 # 在主循环中调用 while True: temp read_temperature() # 从Modbus读取温度 simple_thermostat(temp, setpoint24.0) time.sleep(10)这个例子展示了如何从单纯的数据采集迈向“感知-决策-控制”的完整闭环这也是工业自动化的核心。从硬件连线的细心到软件参数的精确再到调试时的耐心最后到功能扩展的想象力用树莓派Pico W玩转Modbus RTU通信的整个过程其实就是一个典型的嵌入式系统开发缩影。它不追求极致的性能而是在成本、复杂度与功能之间寻找一个优雅的平衡点。当你看到终端上第一次成功打印出从远方设备传来的数据时那种打通物理世界与数字世界的成就感正是驱动我们不断折腾的原动力。希望这份详尽的指南能成为你手中那把可靠的钥匙打开更多工业物联网项目的大门。