ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于i.MX6ULL的IoT网关设计:Mini-PCIe与CAN接口实战解析

基于i.MX6ULL的IoT网关设计:Mini-PCIe与CAN接口实战解析 搞嵌入式网关这几年我越来越觉得选型这件事比写代码还考验人。客户要的东西其实很简单一个盒子能接现场的总线设备最好是CAN能把数据传到云还要能留出扩展接口给未来的4G、WiFi或者别的通信模块。就是这个能接、能传、能扩展三个词市面上的板子能同时满足的还真不多。直到我用了i.MX6ULL做了一版带Mini-PCIe插槽和CAN接口的IoT网关终于找到了那种配置一次就顺手了的感觉。这篇文章就把这块板子的硬件设计逻辑、Mini-PCIe的实际用法、CAN接口的调试经验以及从CAN帧到云平台这条链路的搭建过程完整写出来给同样在做工业物联网网关的朋友一个参考。i.MX6ULL这个平台在工程师圈子里口碑一直不差但真正把它当成网关主控来用的人可能比我预想的要少。很多人一提到网关就想着上A53、A72甚至直接上x86结果成本压不下来功耗也降不下去。实际上对大多数工业数据采集场景来说i.MX6ULL这颗Cortex-A7单核处理器性能刚好卡在够用且不浪费的位置上再配上Mini-PCIe和CAN确实是一个很务实的组合。1. i.MX6ULL到底凭什么吃这碗网关饭我最早接触i.MX6ULL是在一个手持设备项目里当时只是把它当成一颗能跑Linux的廉价处理器。后来做工业数据采集网关的时候重新把市面上的方案翻了一遍才发现这颗芯片的定位其实非常精准它就是NXP专门为IoT和工业边缘设备准备的一颗中间力量。1.1 从需求倒推网关到底需要什么先别急着看芯片参数我们从应用场景倒推。一个典型的工业IoT网关接入侧通常有CAN总线设备比如PLC、电表、电机驱动器、BMS电池包上行侧需要以太网、4G或者WiFi本地可能还要接个USB调试口有时候要挂一块eMMC或者SD卡存数据。这些需求汇总起来对主控的要求是这样的有足够的外设接口至少一路CAN、一路以太网、一路USB Host、若干UART和GPIO。能流畅跑Linux因为要用TCP/IP协议栈、MQTT客户端、CAN协议栈、甚至Node-RED这类边缘流处理工具。功耗不能太高网关往往是7x24小时通电运行的外壳又小散热条件差一颗动不动就几瓦的处理器会让结构设计很难受。成本要卡得住工业品虽然不像消费电子那么极致压价但批量上千台的时候每颗芯片差10块钱都是大差距。把这几条摆在一起i.MX6ULL几乎是为这个需求量身定的。单核Cortex-A7主频最高800MHz带NEON SIMD扩展跑Yocto或Buildroot编译出来的Linux系统非常流畅。内置的FlexCAN控制器和MAC层以太网控制器直接省掉了外接芯片的成本和布板面积。1.2 和其他方案的对比为什么不是STM32MP1也不是全志我知道肯定有人会说STM32MP1不香吗Cortex-A7双核价格也就那样。全志V3s之类的国产方案更是便宜到离谱。说实话这些我都对比过但最终选i.MX6ULL有几个很实际的理由。STM32MP1的问题在于它的定位更偏带显示和多媒体的应用虽然CAN和以太网都齐全但双核A7的功耗和复杂度对纯网关场景来说有点冗余而且ST的软件生态里裸机Linux混合开发的复杂度比NXP的Yocto高不少。全志V3s则是典型的高性价比Linux方案但它的资料开放度和工业级供货稳定性跟NXP这种老牌车规级半导体厂商比还是有差距。i.MX6ULL能做到工业级温度范围供货周期长参考设计非常成熟对于做产品的人来说稳比性能高一点重要得多。当然不是说完美。i.MX6ULL只有单核如果你要在网关上跑容器、跑复杂的边缘AI推理那确实不够。但这颗芯片的设计哲学本来就是把分内的事做好——数据采集、协议转换、上行转发这些活它干得非常漂亮。1.3 实际跑起来的资源余量我手上这版网关用的是800MHz主频版本512MB DDR3L8GB eMMC。在同时运行一个candump抓包进程、一个MQTT转发守护进程、一个Web配置界面的情况下CPU占用率大约在25%到35%之间浮动。内存占用因为带了Web界面和日志缓冲大概在150MB左右。也就是说这颗芯片在典型的2路CAN 一路4G 一路以太网负载下性能和资源余量都是够的。这个测试结果很重要它说明你不需要为了万一以后负载变大这种模糊的担忧去选一颗贵两三倍的双核A7。硬件选型最怕的就是过度设计i.MX6ULL恰好帮你避开了这个坑。2. 把Mini-PCIe用明白这块插槽怎么接4G和WiFiMini-PCIe是这块网关板上比较吸引眼球的部分。很多第一次拿到板子的人会问i.MX6ULL有PCIe控制器吗怎么接Mini-PCIe这个问题恰恰是最需要澄清的。2.1 信号复用Mini-PCIe物理接口USB逻辑协议严格来说i.MX6ULL这颗芯片并没有PCIe控制器它只有USB Host、SDIO、UART这类接口。所以板子上的Mini-PCIe插槽跑的不是PCIe协议而是通过USB信号复用来实现的。这种设计在嵌入式圈子里非常普遍Mini-PCIe插槽在这里更多是被当作一个机械标准来用而不是电气协议标准。为什么大家愿意这么干因为Mini-PCIe插槽的尺寸和引脚定义是固定统一的市面上现成的4G模块、WiFi模块几乎都有Mini-PCIe封装版本。你把USB信号引到插槽的USB数据引脚上再把电源、SIM卡信号引到对应位置就能直接插上各种通信模块不用每次改板子。这个思路在工业产品里特别实用客户要4G网络就插4G模块客户要WiFi就换WiFi模块客户暂时只有以太网那这个插槽空着也不影响。接线方式上重点是这几个引脚Mini-PCIe引脚36USB_D和引脚38USB_D-连接i.MX6ULL的USB Host数据线通常走OTG1或者USB Host1通道。电源引脚Mini-PCIe标准定义了3.3V和1.5V但绝大多数4G模块需要3.8V/4V供电所以板上要单独做一路DC-DC给模块供电不能直接用标准3.3V。SIM卡信号标准Mini-PCIe插槽旁边通常预留了SIM卡座的位置SIM_RST、SIM_CLK、SIM_DATA、SIM_DET这些信号对应4G模块的USIM接口。2.2 4G模块接入的调试记录我在这块板上调试过移远EC20和SIMCom的SIM7600系列模块整体流程还算顺利但有三个地方花了些时间。第一是模块的上电时序。很多4G模块要求主电源稳定后再拉高PWRKEY引脚而且PWRKEY高电平要保持几百毫秒以上。如果你用的是GPIO控制PWRKEY得在Linux设备树里把该GPIO的默认状态和延时时序配好否则会出现模块插上没反应的怪问题。第二是USB枚举。i.MX6ULL的USB Host驱动对某些4G模块的兼容性需要验证建议先把内核的USB Serial、CDC ACM、QMI WWAN、RNDIS这些驱动选项全部打开再逐步裁剪否则容易出现在别的板子上好好的、到自己板子上却无法识别的情况。第三是网络拨号方式。EC20可以用QMI方式上网也可以用PPP拨号。实测下来QMI的稳定性比PPP好不少断线重连的响应也快。具体操作时用ModemManager或者直接用libqmi命令都能拨通但ModemManager管理多SIM卡和自动重连更方便适合网关这种无人值守场景。2.3 天线和电源两个看不见但很要命的细节Mini-PCIe这块还有一个经常被忽略的地方是天线。有些4G模块是双天线的主天线和分集天线RX Diversity都要接好特别是分集天线虽然不接也能通信但信号差、速率掉得厉害。我测试过同样在弱信号环境下只接主天线时下载速率几乎减半。所以结构设计的时候一定要给两条天线都留出位置馈线不要跟DDR走线或者电源走线贴太近。电源方面4G模块在发射瞬间的电流尖峰可以到2A甚至更高。这个峰值电流如果没处理好会导致模块供电电压跌落进而出现掉网、重启、USB设备断开等各种诡异现象。我的做法是模块供电单独用一路能扛3A的DC-DC输出电容要足够大靠近模块引脚放几颗100uF钽电容或陶瓷电容实测会稳很多。千万不能用主板上的LDO给4G模块供电那种方案在网速上传时大概率会掉链子。3. CAN口不是能通就行物理层、采样点与CAN FD接下来说CAN。很多朋友用单片机做CAN的时候功能很简单配一下波特率发数据、收数据能通就行。但到了网关这个层面CAN接口连接的是各种工业设备而且往往不是一台是一整条总线这时候能通和稳定地通之间的差距就出来了。3.1 物理层布线CAN不是随便拉两根线就能跑i.MX6ULL内部集成了FlexCAN控制器引脚比如CAN0_TX和CAN0_RX出来之后还要接一颗CAN收发器才能把逻辑电平转换成CAN_H和CAN_L差分信号。常用的是NXP的TJA1051或者TJA1044。选收发器的时候要注意供电电压TJA1051支持3.3V供电TJA1044则必须5V供电。i.MX6ULL的IO电平是3.3V如果收发器用5V供电记得在TX/RX引脚上做电平兼容处理或者直接选3.3V版本的收发器省事很多。PCB和接线层面CAN总线要用双绞线120欧的特征阻抗匹配总线两端各接一个120欧终端电阻。这里有个非常常见的错误调试的时候把两个120欧电阻都接到了同一侧或者一端接了两个导致总线等效电阻变成60欧甚至40欧这种情况下总线照样能通但是通信极不稳定错误帧率会很高。测终端电阻的最快方法是在板子断电状态下用万用表量CAN_H和CAN_L之间的电阻正常应该是60欧左右两端各120欧并联。3.2 采样点很多人一辈子没调过但调一次就回不去了CAN通信的位时序里有一个非常关键的参数叫采样点它表示接收方在位周期的哪个时间点去采样电平。采样点如果偏了总线长度一长或者波特率一高就会出现偶发性的错误帧而且这种错误很难排查因为它不是每次都出而是传输大数据量时才冒出来。i.MX6ULL的FlexCAN可以通过设置CAN_CTRL1寄存器的PHASE_SEG1、PHASE_SEG2和PROPSEG来调整采样点。用Linux SocketCAN的话可以直接用ip link set can0 type can bitrate 500000 sample-point 0.875来配置。工业上常用的推荐值是87.5%也就是在位周期的87.5%位置采样这样对总线传播延迟和节点时钟偏差的容忍度最好。实测下来我习惯把500kbps的采样点设成87.5%250kbps设成80%左右整体通信质量非常稳定。如果你用的不是SocketCAN而是裸机SDK可以在FlexCAN初始化函数里手动计算并填充这些段的值公式是采样点 (1 TSEG1 1) / (1 TSEG1 TSEG2 1)。网上有很多CAN波特率计算器直接输入期望波特率和采样点就能算出寄存器值。3.3 CAN FD这板上能不能跑怎么跑i.MX6ULL的FlexCAN控制器版本是3.0支持CAN FD这一点很多人会忽略。CAN FD相比经典CAN最大的区别是数据场可以超过8字节最多64字节而且波特率可以切换到最高8Mbps这样总线的实际吞吐量可以提升好几倍。不过要跑CAN FD光有控制器支持还不够收发器也得支持。TJA1044和TJA1051这类早期收发器只能跑经典CAN如果要跑CAN FD建议换成TJA1057或者TJA1044T/3这类标明CAN FD Ready的型号。另外CAN FD模式下仲裁段Arbitration Phase和数据段Data Phase可以配置不同的波特率比如仲裁段500k数据段2M这样既能兼容总线上其他经典CAN节点又能提升大数据量设备的传输效率。我在实际项目中暂时没有大规模部署CAN FD因为现场老设备基本都是经典CAN但板子上已经把CAN FD的驱动支持和收发器选型都留好了后续升级不用换硬件。3.4 隔离不隔离这是个成本和安全问题工业现场最让人头疼的是设备之间的地电位差。A设备用开关电源、B设备用另一个开关电源两个电源的地之间可能有几十伏甚至上百伏的电位差如果不做隔离这个共模电压会直接施加在CAN收发器上轻则通信异常重则烧毁芯片。解决方法是采用隔离收发器。如果追求简单可以用一颗集成隔离的ISO1042一颗芯片搞定信号隔离和收发功能。如果追求成本也可以光耦隔离 独立DC-DC隔离电源比如B0505S隔离收发器的VCC供电效果也OK但布板面积会大一些。我个人建议在网关这种定位的产品上直接上ISO1042省心、可靠PCB尺寸也小多出来的那点成本在整机售价里根本不值一提。另外一个容易被忽略的点是如果用了隔离收发器隔离电源的输出地ISO GND和CAN总线的屏蔽层要单点接地不能让屏蔽层两端都接地否则会形成地环路反而引入更多干扰。这条经验是我们在现场用示波器一点点抓出来的确实有很多人栽在这个细节上。4. 从CAN帧到云平台网关上的软件链路怎么搭硬件说完了说软件。网关的核心能力不是能收到CAN帧而是能把CAN帧变成有用的数据送到该去的地方。这块的链路搭建我分几层来讲。4.1 SocketCAN内核里先把CAN接口收编进来Linux内核自带的SocketCAN协议栈把CAN设备抽象成了类似以太网的网络接口。只要内核配置了CAN_RAW等选项并且在设备树里正确描述FlexCAN控制器启动后就能看到can0这样的网络接口。实际操作时先检查内核配置zcat /proc/config.gz | grep CAN正常会看到CONFIG_CANy、CONFIG_CAN_RAWy。然后启用接口ip link set can0 type can bitrate 500000 ip link set can0 up用candump can0就能实时看到总线上的原始帧。开发调试阶段用cansend can0 123#DEADBEEF可以手工发送一帧测试数据。这一套工具足够应付前期的通信验证了。如果要对帧做过滤、转发、时间戳标记SocketCAN还支持在应用层用PF_CAN套接字编程。下面这个Python示例从can0读取原始帧提取ID和数据然后推送到MQTT主题import can import paho.mqtt.client as mqtt import json can_bus can.interface.Bus(channelcan0, bustypesocketcan) client mqtt.Client() client.connect(192.168.1.100, 1883, 60) def parse_frame(can_id, data): return { id: hex(can_id), data: data.hex(), length: len(data) } while True: msg can_bus.recv(1.0) if msg is None: continue payload json.dumps(parse_frame(msg.arbitration_id, msg.data)) client.publish(fgateway/can/{can_bus.channel}, payload)这个脚本把CAN帧原样推到了MQTT Broker当然实际项目中你还要做解析、缓存、断线重连、QoS策略等但基础骨架就是这么简单。4.2 协议解析CAN帧不等于业务数据CAN帧本身只有ID和数据场要变成业务数据必须知道协议定义。常见的协议有J1939商用车和工程机械、CANopen工业控制、NMEA 2000船舶、以及各种私有协议。以J1939为例一条报文ID里的PGNParameter Group Number、源地址、目标地址都有特定含义数据场里的不同字节可能代表发动机转速、水温等。网关要做的事情就是把这些原始字节流解析成有业务含义的JSON数据再提交给上层。我在项目里的做法是在网关上写一个协议解析守护进程启动时读取一份JSON格式的协议映射表类似于DBC文件的简化版。每次收到CAN帧通过查表快速判断这条ID对应什么信号然后映射成{名称: 值}的键值对。这样做的好处是现场协议有变动时只需要更新JSON配置不需要重新编译程序。4.3 上行通道MQTT是目前最稳的选型数据从CAN接口出来之后要上行到云平台。当前工业物联网领域事实标准就是MQTT没有之一。它的优点很多基于TCP可靠性好发布订阅模式天然适合多设备多平台之间的解耦QoS机制可以在弱网环境下保证消息不丢而且主流的云平台和网关软件都支持MQTT接入。在i.MX6ULL上跑Mosquitto做本地Broker或者直接把数据推给云端Broker都可以。我比较推荐在网关本地跑一个Mosquitto让本地的Node-RED或ThingsBoard Gateway订阅本地主题然后由它们负责跟云端交互。这样即使网断了本地数据也能先缓存到主题里网络恢复后继续推送。4.4 ThingsBoard Gateway一个能直接复用的开源方案如果你想快速搭一套可视化平台不妨直接上ThingsBoard Gateway。这个开源网关支持从CAN、Modbus、OPC UA等设备采集数据然后通过MQTT协议上报到ThingsBoard服务器。对接步骤不复杂在ThingsBoard服务器上创建一个网关设备拿到访问令牌在i.MX6ULL上安装ThingsBoard Gateway写一个CONNECTOR配置把CAN连接器或MQTT连接器指向本地Mosquitto的主题然后在ThingsBoard里配置设备对应的遥测字段映射。完成后CAN总线上的数据就会实时出现在ThingsBoard的仪表盘上。Node-RED也是一个不错的选择节点库里自带socketcan节点拖拖拽拽就能实现CAN数据的读取和转发。如果你的团队成员更习惯用代码写逻辑那就用Python Python-CAN MQTT的方式灵活度最高。没有标准答案按团队习惯来。5. 实际项目中反复踩的坑和性能边界最后这部分我专门想写一写实测中踩过的坑。这些东西在芯片手册和数据手册里是查不到的但现场出了问题往往就是这几个地方。5.1 坑一地环路导致的CAN总线偶发错误帧某次在客户现场测试网关接一个挺远的BMS电池包设备总线距离大概50米。通信时好时坏错误帧率在0.5%到2%之间波动。用示波器抓CAN_H和CAN_L的波形看起来基本正常但误差累积起来就是会出错误帧。排查到最后发现网关和BMS分别接在不同的开关电源上两个电源的GND之间有明显电位差。总线虽然用了双绞线但信号参考地不一致导致共模电压范围超出收发器容限。后来把CAN收发器换成隔离方案ISO1042 隔离DC-DC问题立刻消失错误帧率降到了0.001%以下。从此我的硬件设计清单里工业级CAN接口默认加隔离不再纠结成本。5.2 坑二Mini-PCIe插座的虚焊这个坑出现在小批量试产阶段。部分板子的4G模块在使用几天后出现掉线现象重新插拔模块又能恢复正常怀疑是接触不良。一开始怀疑模块本身问题后来把所有芯片全部重新过一次回流焊故障率明显下降。进一步分析后确认是Mini-PCIe插座的固定脚和信号脚在回流焊时容易虚焊。特别是插座四周的金属固定脚焊盘很大散热快如果炉温曲线设置不当锡膏没完全熔化就会出现看似焊上、实际虚接的情况。解决方法是生产时把炉温曲线的峰值温度适当调高5到10度并在炉后增加AOI检测重点检查Mini-PCIe插座的引脚。这个经验让后续批次的不良率从千分之几降到了几乎为零。5.3 坑三CAN接收线程和MQTT发送线程之间的队列堆积软件层面的坑也很有代表性。我刚把网关跑起来的时候用candump抓包看数据完全正常但连续运行几天后发现MQTT消息延迟越来越大最后甚至内存溢出了。排查后发现是CAN接收线程和MQTT发送线程之间用的队列太浅而且MQTT断线重连期间CAN数据还在不停地往队列里塞。网络恢复后MQTT线程要花很长时间把积压的数据发送出去期间新数据又源源不断进来形成了一个恶性循环。后来我改成CAN接收线程只做轻量级的校验和入队发送线程按优先级和最大积压量来限流比如队列深度超过5000帧就丢弃最老的帧同时记录告警日志。这样即使网络中断几个小时网关也不会内存溢出恢复后数据延迟能迅速回到正常水平。这个策略在嵌入式网关上比什么数据都不能丢的理念实用得多毕竟对于大部分监控类应用最新的现场状态比历史数据更重要。5.4 性能边界这个平台到底能扛多少数据量以我的实测数据来算一笔账经典CAN 500kbps波特率理论上每秒能传输约6000帧左右的标准帧每帧约111位。实际场景中我们的客户总线大约每秒产生600到1000帧。网关在完成接收、协议解析、MQTT推送的全流程下CPU占用率大概在30%左右内存占用稳定。i.MX6ULL在这个负载下完全是游刃有余的。如果把波特率提到1Mbps或者接入2路CAN再叠加一路4G模块高速上传CPU占用率会上升到50%左右但仍然在安全范围内。如果要做CAN FD且数据段跑到2Mbps以上建议在网关上加一层硬件过滤只转发需要关注的ID不要让所有帧都进入用户空间可以有效降低CPU负载。5.5 看门狗和服务管理无人值守的底线网关通常是放在现场没人管的一旦软件跑飞、进程卡死不能指望有人去按复位键。我处理这些问题的方法是下好三招第一用硬件看门狗比如SP706或独立看门狗IC监控系统如果主程序超过一定时间没有喂狗看门狗直接硬复位第二用systemd管理关键服务配置Restartalways让进程崩溃后自动拉起第三把日志打到独立的日志分区方便事后排查。实际运行中这三个措施配合起来很少有需要现场维护的情况。印象最深的一次客户那边电压波动导致电源模块出了异常系统频繁复位但每次复位后看门狗都能保证系统在几十秒内恢复正常数据也没丢太多——因为CAN采集是周期性数据重启后自动从新帧开始对监控场景来说完全可以接受。从整板设计到量产这套基于i.MX6ULL的IoT网关方案在我手里跑了大半年经过的现场环境有高温车间、户外配电柜、甚至潮湿的地下管廊整体表现都很稳定。回看整个项目我最想强调的是硬件平台不需要最贵但每一处接口都要踩在真实需求上。i.MX6ULL加Mini-PCIe加CAN这个组合恰恰把工业设备接入和上云扩展这两件事同时兼顾了。如果你想做一款真正能落地、能批量部署的物联网网关这个方向值得认真考虑。
返回列表