STM32外挂通讯板实现伺服EtherNet/IP接入与SPI固件适配

发布时间:2026/9/28 5:03:24
STM32外挂通讯板实现伺服EtherNet/IP接入与SPI固件适配
最近做了一个伺服驱动器的 EtherNet/IP 通讯方案。驱动器主控用的是一颗 STM32F405本身不带以太网 MAC主板上也没有预留网络电路。客户要求对接罗克韦尔AB等主流 PLC必须支持 EtherNet/IP。最后定下来的路线很直接在伺服主控板上加一块嵌入式通讯小板板上是一颗 STM32F407 LAN8720 PHY 网络变压器EtherNet/IP 协议栈跑在小板上伺服主控通过 SPI 和小板交换数据。我这次的重头工作就是把协议栈厂商提供的 Demo 固件从评估板环境里“搬”到自研板卡上同时把 SPI 通讯的固件部分从零适配出来。这个方案在伺服、变频器、现场总线模块这类产品里非常典型。外挂通讯板的好处很实在主控不用换芯片、不改原有运动控制逻辑协议栈的所有风险都隔离在小板这边哪怕 EtherNet/IP 一致性测试有问题也只动小板固件。本文不聊伺服驱动器的运动控制部分只讲通讯这条链路怎么打通适合正在做工业以太网从站、通讯扩展板或者被“Demo 跑起来但接不上实际项目”这类问题折磨的工程师参考。1. 方案选型为什么必须用“外挂小板 SPI”1.1 伺服接入 EtherNet/IP 的两种常规路线伺服驱动器要支持 EtherNet/IP一般有两条路线。第一条是主控芯片直接集成协议栈。选一颗带以太网 MAC 的 MCU比如 STM32F407、STM32H743、TI 的 AM335x把主控逻辑和协议栈跑在同一颗芯片里。优点是硬件简单、成本最低但问题也很明显伺服的运动控制是强实时任务PWM 输出、编码器采样、电流环都在跑EtherNet/IP 的协议栈又会占用不少 CPU 时间和中断资源两者容易互相干扰。另外老产品的主控芯片已经定型想换芯片等于重新做一版硬件电气性能、EMC、安规全都得重新验证改动量太大。第二条就是外挂通讯小板。主控不变通过 SPI、UART、并口或者双口 RAM 和一块独立的通讯板连接。以太网相关的硬件电路、协议栈、连接管理全部放在小板上主控只负责把运行数据“投递”过去。伺服行业里很多产品用的就是这种结构最典型的是总线伺服系列位置、速度、电流、报警这些数据通过内部接口在主板和通讯板之间周期性交换。这次选择外挂方案还有一个很现实的原因项目时间紧。EtherNet/IP 从站要做 ODVA 一致性测试测试不通过就很麻烦把协议栈和硬件隔离在一块独立板卡上测试失败的修改范围可控不会拖着整个驱动器的代码一起返工。1.2 EtherNet/IP 协议栈的 Demo 程序从哪来EtherNet/IP 是基于 CIPCommon Industrial Protocol的工业以太网协议它不是一个能自己从零手搓的协议一般通过两种途径获取协议栈。一种是用 ODVA 官方推荐的商业协议栈比如 Pyramid Solutions 提供的 EtherNet/IP Stack或者 HMS 的 Anybus CompactCom。这类栈是收费的但代码完整、文档齐全目标平台上跑通后可以直接当黑盒用稳定性有保障。另一种是找芯片厂商的参考例程。很多带以太网的 MCU 厂商会提供 EtherNet/IP 从站的 Demo 工程例如 TI 的 TM4C129、瑞萨的 RZ/N 系列STM32 生态里也有第三方的 CIP 协议栈移植案例。这种 Demo 的优点是免费、容易拿到缺点是协议版本可能偏旧、功能裁剪程度大、维护不及时需要自己做不少补丁。我们这次用的就是商业协议栈厂商提供的 Demo。协议栈以源码形式交付同时配套一块厂商自己的评估板Demo 工程直接跑在评估板上通过厂商提供的 EDS 文件就能被 PLC 扫描到。我拿到的第一版 Demo 是这样一种状态在评估板上一切正常用一张网线连 PLC、导入 EDS、建立连接、周期性交换数据都没问题。但评估板和我们自研的小板硬件差异很大Demo 工程直接烧进去肯定跑不起来接下来要做的就是移植和适配。1.3 系统数据流与控制流整个系统分三层PLC扫描器— 通讯小板EtherNet/IP 从站适配器— 伺服主控。先说数据流。EtherNet/IP 分显式报文和隐式报文两种。显式报文走 TCP 44818 端口处理参数读写、设备诊断这类请求什么时候有请求什么时候发。隐式报文走 UDP在连接建立后周期性地交换 I/O 数据这个过程叫“隐式连接”交换的数据叫 Assembly 数据。通讯小板要做的事情可以简化成两条路径。第一条路径PLC 通过隐式连接周期性地把控制字、目标位置、速度指令等写到小板的 Output Assembly小板把这些数据打包成自定义帧通过 SPI 发给伺服主控。主控运行完一个周期后把状态字、实际位置、反馈电流等填充到 Input Assembly 的数据区再通过 SPI 写回小板小板把它周期性地发给 PLC。第二条路径PLC 发了一个显式报文比如读伺服的温度参数小板收到 TCP 报文后解析出 CIP 请求由于这部分数据和运动控制强相关小板不能自己回答它需要通过 SPI 把这个请求转给主控主控查完寄存器后把结果回给小板小板再封装成 CIP 响应发回 PLC。所以从固件角度看小板的 SPI 部分承担着两个任务周期性批量搬运 I/O 数据和非周期性地转发显式请求。二者的优先级完全不同I/O 数据的实时性要求高必须在 RPIRequested Packet Interval规定的时间内完成交换显式请求可以排队慢慢应答。我后面在做 SPI 中断优先级和 DMA 分配的时候就是按照这个原则来设计的。2. Demo 固件在小板上的首轮适配2.1 自研小板的硬件资源盘点先把硬件交代清楚。通讯小板的 MCU 用了 STM32F407VET6512KB Flash、192KB RAM主频 168MHz带以太网 MAC 但需要外接 PHY。PHY 芯片选的 LAN8720A通过 RMII 接口连接RMII 参考时钟 50MHz 由 MCU 的 MCO 引脚输出LAN8720 工作在从模式这样只需要一颗 50MHz 晶振就能同时给 MCU 和 PHY 提供时钟硬件成本省很多。SPI 接口方面小板作为 SPI 从机主控作为主机。使用的引脚是 SPI1对应 PA4(NSS)、PA5(SCK)、PA6(MISO)、PA7(MOSI)。这个分配不是随便选的SPI1 挂在 APB2 总线上时钟频率 84MHz比 SPI2、SPI3 的 APB142MHz高一倍作为从机虽然主要受主机时钟控制但内部波特率发生器在需要回传数据时还是会有影响。STM32F407 的以太网 MAC 带 DMA 功能以太网帧的收发不需要 CPU 逐字节搬运对实时性有帮助。Demo 工程在评估板上用的 PHY 是另外一个型号所以第二步的工作集中在 PHY 驱动替换。2.2 Demo 工程从评估板迁到自研板的改动清单我拿到 Demo 工程后第一件事不是看代码而是先对照原理图过了一遍硬件差异。评估板上和自研板上需要重点对应的资源包括PHY 型号与接口模式、以太网复位引脚、中断引脚、LED 指示灯引脚、MAC 地址来源EEPROM 还是代码固定。因为 Demo 工程默认的 PHY 驱动接口和我这边的 LAN8720 不一致主要改动在 PHY 驱动层。IEEE 802.3 定义的标准 PHY 寄存器有 32 个前 16 个是各厂商通用的比如寄存器 0 是控制寄存器、寄存器 1 是状态寄存器、寄存器 4/5 是自协商通告能力。但不同厂商在扩展寄存器上差异很大比如 LAN8720 的寄存器 31 是 PHY 特殊控制/状态寄存器里面包含 Duplex 模式和速度指示位这和评估板用的 PHY 完全不同。另外一件很关键的事是 PHY 复位时序。LAN8720 的复位引脚要求拉低至少 1ms之后等待 50MHz 参考时钟稳定再拉高然后还要等一段自协商时间通常几百毫秒到 2 秒PHY 状态寄存器中的自协商完成位才会置 1。如果选择忽略自协商、强制配置 100M 全双工也需要先在 PHY 控制寄存器里设置速度、关闭自协商再执行软复位。这个时序如果不对现象就是 MAC 初始化不报错但网线插上后状态灯不亮链路一直处于 DOWN 状态。2.3 先把 Demo 跑起来再说首轮点亮适配的第一步往往不是加新功能而是把原始 Demo 原封不动地跑起来。我习惯先做最小化验证换好 PHY 驱动修改板级初始化里的引脚定义下载固件然后用协议栈厂商提供的扫描工具在 PC 上广播搜索设备。这一步有个细节特别重要Demo 工程里的 IP 地址、MAC 地址、产品名称默认是评估板的信息如果直接上电工具可能搜到但 MAC 地址如果冲突或不符合厂商 OUI 范围PLC 扫描器可能会过滤掉它。我先把 Demo 里的 MAC 地址改成一个临时值用厂商工具扫描确认设备能被发现然后进一步做 EDS 文件匹配。跑通 Demo 的判断标准很简单用厂商工具能看到设备、能打开 EDS、尝试建立连接能成功。到这里只说明以太网底层通了还不能证明协议栈完整性。接下来我会用 Wireshark 抓包看设备上电后有没有主动宣告自己的 Identity 报文以及是否有正确的 ARP 响应。这里有一个我的习惯每做一次改动只验证一件事。PHY 换完就只验证 PHY代码里暂时屏蔽 SPI 相关的初始化避免多个变量混在一起查问题。很多移植失败都是因为一次性改了太多地方最后连哪个环节出的问题都定位不了。3. SPI 通讯固件设计从零写一个可靠的数据通道3.1 SPI 通讯参数Mode 选择与时钟频率硬件上 SPI 角色是小板做从机、主控做主机。从机端没有时钟控制权SCK 和 NSS 都由主机控制唯一需要提前统一的就是 SPI 模式CPOL/CPHA和帧格式两边不一致直接导致数据错位。我们最开始定的 SPI 参数是这样的CPOL0、CPHA1也就是 SPI Mode 1数据在第二个时钟沿采样8 位数据MSB 先发。时钟频率在调试阶段用 1MHz跑通后慢慢提到 4MHz、8MHz。从机实际能跑多快取决于从机 MCU 的中断响应和 DMA 搬运速度不是配置了 16MHz 就一定跑得动。3.2 数据帧格式设计主控和小板之间需要交换的数据类型很多周期性 I/O、参数读写请求/响应、状态查询、调试命令。为了让 SPI 链路在多样数据下保持结构清晰我设计了一个通用帧格式。字段长度说明magic1 字节固定 0xAA用于帧同步和字节错位检测cmd1 字节命令类型区分 I/O 数据、显式请求、显式响应、状态上报len1 字节data 区有效字节数seq1 字节帧序号接收方用来检测丢帧crc2 字节CRC16对前 4 字节 data 区做校验data0~244 字节数据区帧头加校验一共 6 字节数据区最大 244 字节单帧总长度不超过 250 字节。这个长度受限于 SPI 从机缓冲区设计也考虑到 EtherNet/IP 的 UDP 报文实际载荷一般不会太大经验上够用了。每次通讯固定发一整帧接收方先收满帧头根据 len 字段再收剩余数据最后校验 CRC通过后解析不通过直接丢帧并给对端一个 NAK 状态。首次固件调试我用 1MHz 时钟一帧 250 字节的传输时间是 250 × 8 / 1MHz 2ms这个速度对隐式报文通常 5ms~10ms 的 RPI 来说留出了足够的处理余量。后面把 SPI 时钟提到 4MHz 时同样一帧只要 0.5ms瓶颈就转移到协议栈的等待上去了。3.3 从机端 SPI 收发实现要点STM32F4 做 SPI 从机最忌讳在这种高速周期通讯里用查询方式收发数据。查询模式下CPU 必须一直死等 SPI 状态寄存器一旦进入中断或者被其他更高优先级任务抢占数据就会丢失。我最后用的是“NSS 边沿触发 DMA 传输”的思路。片选信号 NSS 作为外部中断输入是不行的因为 SPI 从机在每字节传输时都需要检测 NSS 状态。我采用的方法是主控在每次发起一帧通讯前先把 NSS 拉低然后在 SCK 时钟驱动下逐字节传输传输结束后拉高 NSS。小板侧把 SPI1 配置成硬件 NSS 模式SPI_NSS_Hard当 NSS 拉低时从机 SPI 自动使能这样既不需要 GPIO 中断又能在片选信号变化时触发状态判断。DMA 配置上我用了两个 DMA 通道一个接收一个发送。接收 DMA 在 SPI 使能期间一直挂在 SPI1 的 RX 上每收到一个字节就自动搬运到接收缓冲区当收到一帧数据后以 NSS 拉高为边界在 DMA 传输完成中断里把数据拷贝到解析缓冲区并置一个标志位。发送端同理主控通过 SPI 请求数据时小板先把响应填到发送缓冲区然后打开 DMA 发送使能由 SCK 时钟驱动逐字节发出。3.4 与 EtherNet/IP 协议栈的数据对接SPI 链路通了以后最难的是把 SPI 数据和协议栈内部的 Assembly 对象绑定起来。协议栈 Demo 内部一般有一个 I/O 数据交换的入口函数周期性调用比如ProcessIO()它的职责就是把 Input Assembly 内容发送出去把收到的 Output Assembly 内容存到缓冲区。我要做的适配很简单也很关键通过 SPI 收到主控发来的 Output 数据后更新到 Output Assembly 缓存在每次ProcessIO()被调用前把 Input Assembly 缓存的最新数据打包通过 SPI 发送给主控。这里的核心是数据一致性问题。协议栈的ProcessIO()在以太网发送中断里触发SPI 接收在 SPI 中断里触发两个中断互相独立如果不加保护就可能出现“SPI 正在写 Input 数据协议栈已经把上一包发出去了”的情况。我用的办法是给每块 Assembly 数据加一个volatile uint32_t序列号SPI 写入时递增序列号ProcessIO()发送前读取序列号如果发现发送缓冲区里的数据和当前序列号不一致就再重新同步一次。虽然会稍微损失一点时效性但换来的是数据永不会错位。显式报文的转发也类似。PLC 发一个显式请求协议栈解析出对象 ID、属性 ID 后会调一个回调函数。我把这个函数重定向到 SPI 发送把请求打包发给主控主控的响应通过 SPI 回来后再喂给协议栈的响应接口。相当于用 SPI 把协议栈的“业务处理函数”外包给了主控。4. 联调过程与典型问题排查实录4.1 联调环境搭建联调从来不是把设备一接就能跑通的我习惯分三层来搭环境。第一层是 PC Wireshark接在 PLC 和通讯小板之间的交换机镜像口上抓所有 EtherNet/IP 报文。第二层是厂商的扫描工具用来搜索设备、修改 IP、测试连接。第三层是伺服主控侧用一个 SPI 回环测试程序模拟主控不断地发假数据验证通讯小板在纯链路层是否正常。Wireshark 抓包看几个关键节点设备上电启动时有没有发出 Identity 广播请求的响应PLC 扫描设备时设备是否正常响应 Forward Open 请求连接建立后是不是有周期性的 UDP 数据包往来。这三步都正常说明 EtherNet/IP 协议栈本身工作正常之后的问题基本都出在 SPI 适配层。4.2 SPI 偶发丢数据问题联调第一个遇到的现象很典型主控周期性发 1000 帧数据小板会漏掉几帧漏帧没有规律用示波器看 SPI 波形时一切都正常但板子跑一段时间就会漏。排查过程是这样先怀疑时钟极性确认 Mode 一致没问题再怀疑线路干扰把 SPI 线缩短、包屏蔽层问题依然存在最后我把焦点放在 NSS 信号上。结果发现由于 SPI 时钟频率提到 4MHz主控在拉低 NSS 后很快就开始发 SCK而小板这边把 NSS 下降沿用作“准备接收”的标志但实际从 NSS 下降沿到 DMA 接收使能之间存在一段代码执行时间如果这期间 SCK 已经来了第一个时钟边沿第一字节就丢了。解决方案是在 SPI 从机初始化后让接收状态机一直处于“等待 NSS 拉低”的空转状态NSS 拉低后立即把 SPI 的 RX 缓冲区清零并重新使能 DMA 接收。同时把 STM32 的 SPI 从机接收中断优先级调到最高因为 NSS 下降沿即使触发不了中断至少 DMA 接收要尽可能早地准备好。4.3 隐式报文周期性超时问题连接建立后PLC 那边每隔大概几百毫秒就报告一次连接超时。Wireshark 看连接建立成功后来自 PLC 的周期性数据包正常到达小板但小板的 Input 数据经常晚到导致 PLC 认为从站无响应。这个问题的根子不在以太网在 SPI 和协议栈处理的配合上。隐式连接的 RPI 配置成了 5ms意味着 PLC 每 5ms 期望收到一次 Input 数据。但我在 SPI 传输层用的是“收到主控数据后才回送 Input”而主控的 SPI 发送周期是 10ms这样 PLC 每 10ms 才能拿到一次 Input远超 RPI 的 5ms 限制。解决办法是实现一个超时反向推送机制如果 SPI 链路在 RPI/2 时间内没有主动把 Input 数据喂给协议栈协议栈就先把上一包有效数据发送出去等 SPI 数据到位后再更新下一次。虽然会增加 1 个周期的延迟但连接稳定性优先。后面又把主控侧的 SPI 周期改成了 2ms彻底消除了超时。4.4 PHY 初始化不稳定这个问题在首轮适配时就出现过。现象是上电后 50% 概率网络不通动一下网线有时候又恢复。后来定位发现LAN8720 的 RESET 引脚被我接到了 MCU 的普通 GPIO 上复位时序不对。我在初始化代码里先拉低复位脚延时 100ms再拉高然后马上读 PHY 寄存器。但数据手册要求 50MHz 参考时钟必须先稳定至少 1ms我这边时序上先复位了 PHY时钟还没起来PHY 内部状态机处于不确定状态。解决方法是调整初始化顺序先启动 MCO 输出 50MHz 时钟等待时钟稳定后再复位 PHY拉高复位脚后至少等 2ms再轮询 PHY 的链路状态位。把这个时序固化后上电 100 次也没有一次失败。4.5 Demo 存储参数的网络适配问题协议栈 Demo 里通常有一个 CIP 对象存储区用来保存 IP 地址、MAC 地址等网络参数许多实现是放在 EEPROM 或者 Flash 的固定扇区里。我的小板硬件上没接 EEPROM就得把存储驱动适配到 STM32F407 的 Flash 模拟 EEPROM 方案上。这里有个小坑协议栈 Demo 获取 MAC 地址的方式往往是读一个固定的偏移地址如果不改驱动就全是从一个地方读到 0xFFMAC 地址就无效导致设备无法被扫描。我直接用 STM32 的 Flash 最后一个扇区模拟把 MAC、IP 地址这些参数统一存到一个结构体里写之前先擦除整个扇区再一次性写入。注意一定要做掉电保护校验否则中途掉电会连 Flash 的启动配置都搞坏下次上电直接进入硬件错误中断。5. 避坑指南与工程经验5.1 关于 SPI 通讯稳定性我的几条硬经验第一不要在 SPI 从机中断里做协议栈的处理SPI 中断只负责搬运数据置个标志位就退出。协议栈函数允许被重入的很少你在中断里调一个复杂函数万一主流程也在调它CIP 对象的状态直接被破坏。第二CRC 校验必须做。SPI 在短距离板内通讯虽然可靠但工业现场会有变频器、伺服驱动的电磁干扰特别是电机动力线靠近通讯线时一个毛刺就能让一个字节翻转。没有校验一个错误字节可能被当成合法数据去改伺服参数后果很严重。第三帧长度尽量固定避免频繁的 DMA 重配置。固定帧长在从机端可以用一个恒定的接收缓冲区和一次 DMA 配置完成收发减少状态切换开销。如果实在要不定长也建议一个老规矩先固定收发 4 字节帧头根据 len 再配置后面的 DMA 接收不要用所谓“字节计数”的流式处理。5.2 EtherNet/IP 适配的一些工程细节首先EDS 文件不是可有可无。PLC 端导入 EDS 后才知道怎么访问你的设备。Demo 工程自带 EDS 文件通常对应评估板的设备信息直接拿到自研设备上用Vendor ID、Product Code 和固件里的对象数据不一致PLC 会拒绝连接。我改完固件里的 Vendor ID 和 Product Code 后同步改了 EDS 里的对应字段。其次MAC 地址管理。量产品牌不能随便用网卡厂商的板卡 MAC也不能每台设备都烧同一个 MAC。EtherNet/IP 从站的 MAC 地址来源建议从伺服主控的序列号派生出来通过 SPI 下发或者出厂时一次性写入 Flash保证每台设备 MAC 唯一。第三IP 地址冲突。默认情况下设备可以用 DHCP 或者 BootP 获取 IP也可以使用固定的静态 IP。工业现场常见方式是 DHCPPLC 通过 DNS 名字来识别不同设备。Demo 工程里 IP 地址是固定的我做了个适配上电后先去 DHCP 服务器请求一次请求失败才回退到固定 IP这样现场不配 DHCP 也能用。5.3 C 语言层面几个容易踩的坑SPI 帧结构体定义务必加__attribute__((packed))或者#pragma pack(1)。我自己就在这上面栽过跟头结构体里同时有uint8_t和uint16_t时编译器默认会对齐在 STM32 上 CRC 计算用的是“结构体地址 偏移”的方式对齐后结构体的内存布局和帧实际的字节流不一致计算出来的校验值怎么都对不上。大小端问题同样要留意。EtherNet/IP 协议栈内部普遍使用大端字节序而 STM32 是小端。协议栈 Demo 里一般用htons、ntohs做了网络字节序转换但 SPI 帧是我们自己定义的我统一采用“小端 memcpy”的方式处理多字节字段避免直接对未对齐地址做强制类型转换防止总线错误。还有一件事共享变量要用volatile修饰。SPI 中断和主循环之间共享的缓冲区、状态标志、序列号如果不加volatile编译器优化后可能永远看不到中断里更新的值。Debug 模式一切正常Release 优化一开就出现丢帧八成是这个原因。一些实际操作中的体会这次项目最耗时间的环节不是编码而是排查 SPI 偶发丢帧和隐式报文超时这两个软硬件边界问题。两个问题表面上都是通讯异常实际上一个是片选时序一个是周期不匹配全都不在报错信息能直接反映的层面只能靠示波器加 Wireshark 一层层剥。如果有朋友要复现或者扩展这个方案我的建议是从最简单的 Demo 跑通开始别急着上完整功能。先把 PHY 调通、确认协议栈能扫描到再做 SPI最后再联调数据交互。每一步都设一个明确的成功标准出了问题很快就能定位。至于后续扩展比如增加 PROFINET、EtherCAT 等更多协议这个外挂小板的思路是一脉相承的换个协议栈、改改 SPI 数据封装就能复用大半固件架构。最后再多说一句带着 Demo 程序做产品始终要记住 Demo 是“最小可行性验证”不是产品级代码。协议栈能跑通只是起点后面的电源、EMC、隔离、可靠性测试才是真正决定设备能不能在现场长期稳定运行的关键。