看到这个标题我第一反应就是——又是一个做设备更新改造的兄弟。EtherCAT和CANopen这两个词出现在同一个STM32F4主控、同一个Keil工程里,说明你手里的设备要么是给老产线做总线网关,要么是做一个能同时挂在两套总线上的控制节点。这类项目在运动控制和产线升级里太常见了:上层换了EtherCAT主站想提高实时性和带宽,下层却还有一堆用CANopen通信的老伺服、IO模块不能直接扔掉,于是中间就需要这么一个"翻译官"。
这篇文章我就按我实际做过的一个类似工程来拆,把硬件选型、Keil工程怎么组织、EtherCAT从站和CANopen从站各自怎么落地,以及两套总线之间数据怎么映射讲透。适合正在做双总线设备、或者准备把老CANopen设备接入EtherCAT系统的工程师参考。
1. 为什么EtherCAT和CANopen要挤在同一台设备里
先说需求背景。很多产线的控制架构是分层的:PLC或者运动控制器作为主站,下面挂伺服驱动器、变频器、IO模块。老一代设备大量使用CANopen,因为CAN总线布线简单、抗干扰不错、协议成熟,一个CAN网络挂十几二十个节点很常见。但CANopen的缺点也明显:带宽只有1Mbps,报文是非周期的、靠ID仲裁,实时性最好也只能做到毫秒级,而且随着节点增多,总线负载上来之后抖动会越来越大。
EtherCAT不一样,它是以太网物理层,100Mbps,数据帧像"火车"一样从第一个从站传到最后一个从站,每个从站只处理属于自己的那几节"车厢",实时性可以做到百微秒甚至几十微秒级别,而且支持分布式时钟同步,多轴联动场景下优势非常明显。
但问题是,产线上不可能把旧的CANopen伺服全部报废。于是就有了这种设备的生存空间:一边通过EtherCAT从站接口接入新主站,另一边通过CAN总线管理一大堆老CANopen设备。
1.1 这类设备的典型工作模式
实际工程里常见两种角色定位。
第一种是当成协议转换网关:EtherCAT主站把数据写到这个设备的过程数据对象里,设备转成CANopen PDO/SDO报文发给下游伺服;反向把伺服的运行状态、位置反馈读回来,映射成EtherCAT过程数据。
第二种是当成智能IO节点:本身有本地输入输出,同时带有CANopen管理能力,EtherCAT主站透传CANopen命令帧,等于把CANopen主站功能"并入"了EtherCAT网络。
我这边做的是第一种,而且是把它们放在一个MCU里完成。这样做最大的好处是省掉"EtherCAT转通用串口再转CANopen"这种三级桥接的中间环节,端到端延迟更低,硬件成本也更低——一块STM32F4加一颗EtherCAT从站控制器芯片就能搞定。
1.2 MCU选型为什么落在STM32F4上
做这种双总线设备,MCU需要同时满足几个条件:要有一个高速SPI接口去跟EtherCAT从站控制器通信(不是用MCU自带以太网MAC,这个后面细说);要有一个或者两个CAN控制器;要有足够快的运行频率去跑协议栈和应用逻辑。
STM32F4系列正好卡在这个需求点上。我用的芯片是STM32F407VET6,168MHz主频,有SPI、双CAN、足够多的定时器和DMA,片内Flash 512KB,RAM 192KB。Keil MDK工程下,EtherCAT从站协议栈加CANopenNode这种开源协议栈加应用代码,Flash占用大概在200~300KB之间,RAM大部分用在过程数据缓冲区和对象字典上,也够用。
选F4而不是更便宜的F1,最关键的原因是SPI速度和中断处理能力。EtherCAT从站控制器通过SPI跟MCU交换数据,一个周期可能就要传100到200字节的过程数据,还有ESC寄存器读写,SPI时钟至少要跑到10MHz以上,F1的SPI在高时钟下配合DMA也能做,但主频不够的时候CPU占用率会很难看。
2. STM32F4 + LAN9252双总线硬件分配
硬件上最核心的一个决策是:EtherCAT从站控制器用外部芯片,而不是让STM32F4直接走以太网。
EtherCAT从站通信有一个硬性要求:从站必须在一个以太网帧经过的时候实时抽取/插入自己的数据,这个操作是纳秒级响应的,普通MCU要么没有以太网MAC,要么有了MAC也不可能有这样的实时处理能力。所以业界的标准做法都是用一颗专用ESC芯片来干这个活。ESC芯片内部完成数据帧的抽取和插入,外部通过SPI或者并行总线跟MCU交换数据。
2.1 ESC芯片选型与SPI连接
我用的ESC芯片是LAN9252。这颗芯片支持SPI从站模式,外接一个晶振、一个EEPROM,再配以太网变压器和RJ45座子就是最小系统。SPI模式下,MCU作为主机,LAN9252作为从设备,通过读写它的内部寄存器来访问ESC寄存器空间和过程数据RAM。
接线很简单:
- PA5/PA6/PA7作为SPI1时钟、MISO、MOSI
- 片选用PA4或者任意GPIO
- 外部中断引脚接PA0或者任意支持EXTI的引脚
- 复位引脚接一个GPIO控制
SPI时钟我配置在20MHz,开启硬件CRC校验(SPI_CR1的CRCEN位),这样数据传输可靠性更好。中断方面,LAN9252的IRQ引脚接MCU的外部中断,默认用上升沿触发。
2.2 CAN接口的资源分配
STM32F407自带两个CAN控制器,我的设计里CAN1用来接CANopen从站网络,CAN2可以留作备用或者接调试总线。CAN收发器用TJA1050这一类的经典芯片,如果现场环境干扰比较厉害,建议用带隔离的模块。
需要注意的细节是CAN控制器时钟配置。STM32F4的CAN时钟来自APB1,如果APB1设成42MHz,CAN波特率可以做到1Mbps,配置位时序的时候把SyncSeg=1、BitSeg1=13、BitSeg2=2、Prescaler=1,采样点大约在87%,对1Mbps的工业CAN网络来说比较合适。
2.3 中断优先级分配
这是一个容易被忽视但是影响极大的设计点。EtherCAT侧LAN9252中断表示有新的过程数据帧到达,CANopen侧的CAN接收中断表示有PDO或者SDO到了,两边都在催MCU干活,优先级怎么排?
我的做法是:
- LAN9252外部中断优先级设为抢占优先级最低(数值最大),但是放在一个专门的SYNC0同步信号上
- SYNC0信号中断设为最高的中断优先级
- CAN接收中断次之
- 应用主循环里处理对象字典和逻辑
为什么这样排?因为EtherCAT的周期数据讲究"跟得上趟",主站可能在1ms周期内发一次帧,如果你在CAN中断里卡太久,下一帧来了数据没读完,主站就会报丢帧甚至断站。而CAN报文本身有缓冲区,稍微晚几十微秒处理不会丢数据。
2.4 电源和隔离的工程细节
做工业设备,电源和隔离这块不能省。
CAN侧建议用隔离CAN收发器,比如ADM3053这类带DC-DC隔离的模块,避免长距离CAN总线产生的共模干扰把MCU烧了。EtherCAT侧的标准做法是网络变压器内置隔离,RJ45座子选带变压器的HR911105A之类,就不需要额外处理。
供电我做了两路:3.3V给MCU和LAN9252,5V给CAN收发器侧,用DC-DC做隔离电源。LAN9252的IO电平是3.3V,和STM32F4直接相连没问题。注意LAN9252的数字电源和模拟电源引脚要分别加滤波电容,不然容易在网络负载大的时候出现帧错误。
3. Keil工程的组织方式:两套协议栈如何和平共处
很多第一次做EtherCAT从站的人拿到SSC生成的代码之后,第一反应是"这代码怎么长这样",一堆文件不知道怎么跟自己的应用代码集成。再加上还得塞一个CANopen协议栈进去,如果工程文件组织不合理,后面调试会非常痛苦。
3.1 工程目录结构
我的Keil工程目录是这么划分的:
Project/ ├── MDK-ARM/ // Keil工程文件 ├── BSP/ │ ├── lan9252_drv.c // LAN9252底层驱动 │ ├── canopen_lowlevel.c // CAN底层驱动 │ ├── spi.c │ └── sys.c ├── ECat/ │ ├── ecat_ssc_*.c // SSC生成的EtherCAT协议栈 │ ├── esc.c // ESC寄存器访问封装 │ └── ecat_appl.c // 应用层接口 ├── CANopen/ │ ├── CO_driver.c // CANopen硬件适配层 │ ├── CO_OD.c // 对象字典实例 │ ├── CO_PDO.c │ └── ... ├── App/ │ ├── main.c │ ├── bridge.c // 双总线映射核心 │ └── user_config.hEtherCAT协议栈代码和CANopen协议栈代码分开放,禁止任何一方直接调用另一方的源码。它们之间只通过一个bridge层交换数据。这个约束非常重要——协议栈各有自己的状态机和定时器逻辑,互相掺和轻则编译警告,重则运行起来莫名其妙的死锁。
3.2 为什么用Keil MDK而不是其他工具链
标题里写了Keil,实际工程里用MDK也确实是最省事的选择。STM32F4的启动文件、下载算法、调试器支持都是现成的,而且很多给设备写底层驱动的老工程师习惯用MDK,协同起来方便。
需要注意一个版本选择:如果你拿到新的SSC生成代码,建议用AC6编译器,老代码有些是AC5的语法。AC6对C99/C11标准支持更好,Pragma pack这类结构体对齐语法也能处理。但如果你用的是很老的EtherCAT协议栈版本,里面可能有AC5才支持的inline写法,那就在Options -> Target -> Arm Compiler里选AC6兼容模式。
我在工程里统一开了C99标准,结构体对齐用__attribute__((packed))处理报文结构体,保证EtherCAT帧和CANopen对象字典的内存布局跟协议严格对应。
3.3 协议栈的源码集成方式
EtherCAT从站协议栈一般有主文件和可选功能模块。我拿到的SSC生成工程里默认带了一些demo代码,包括LED、Mailbox的demo handler,这些在实际产品里大部分需要删除或者改写成自己的逻辑。新手容易犯的错是保留demo的PDO映射和SM配置,直接跑起来发现主站能扫描到从站,但数据对不上——因为你用的是一份"给参考板用的"配置,不是给你的设备用的。
我的处理方式是:把SSC输出的ecat_def.h、ecat_def.c这类文件作为基础框架,按我的硬件和应用需求重新配置PDO映射表、SM通道、FMMU和EEPROM内容。
CANopen协议栈我用的CANopenNode源码,这个协议栈是模块化的,按协议规范分成CO_SDO、CO_PDO、CO_EM、CO_NMT这些模块,内部通过对象字典和通信状态管理来协调。移植它主要工作是写一个CO_driver层,把CAN收发、定时器、以及硬件访问接口对接好。
3.4 RAM和Flash的分配策略
Keil工程里需要指定好RAM区域。
我的做法是:EtherCAT过程数据缓冲区用一个大数组,单独放一个段,编译的时候锁定地址,避免被其他模块的变量覆盖;CANopen的对象字典本身也是一块大结构体,有些变量需要支持在线修改,把它放到可写RAM段。如果Flash空间紧张,可以把调试打印的字符串常量都放到只读段,协议栈里的log功能关掉,能省出几十KB。
实际编译下来,EtherCAT协议栈加CANopenNode加应用,Flash约280KB,RAM约80KB,F407完全扛得住,还有接近一半余量可以做后续扩展。
4. EtherCAT从站侧:ESC寄存器与PDO映射的落地细节
EtherCAT从站的实现核心就是两件事:一是让ESC正确地在网络上工作,二是在MCU侧及时把过程数据"搬"到内存里。
4.1 ESC初始化流程
LAN9252上电复位后,MCU先通过SPI读取它的ID寄存器,确认SPI链路通了。然后按顺序做这几件事:
- 复位ESC,等待Ready
- 配置EEPROM加载,从EEPROM里读出SII信息,包括厂商ID、产品码、PDO相关配置
- 配置Sync Manager(SM)通道,SM0/SM1通常用于邮箱通信,SM2/SM3用于过程数据
- 配置FMMU,把主站发来的逻辑地址映射到ESC本地物理地址
这一步的要点是:EEPROM里的内容必须和协议栈代码里的PDO配置一致。如果两边不一致,会出现主站扫描后看到的设备描述和实际情况对不上,最常见的现象是主站配置成功但运行时报"Process data mismatch"。
4.2 过程数据映射的本质是一张内存表
很多教程把FMMU和PDO映射讲得很玄,其实就是一张地址映射表。主站往逻辑地址空间写数据,ESC芯片通过FMMU把这些数据放到自己的本地RAM地址;SM通道再通知MCU"有数据来了"。MCU在中断里把这个固定地址的缓冲区内容拷贝到应用变量,或者把应用变量写入这个缓冲区等待主站读。
举个例子,我定义的过程数据输入(主站->设备方向)为:
| 偏移 | 长度 | 含义 |
|---|---|---|
| 0 | 2字节 | 控制字 |
| 2 | 2字节 | 目标位置/速度 |
| 4 | 1字节 | 控制命令 |
过程数据输出(设备->主站方向)为:
| 偏移 | 长度 | 含义 |
|---|---|---|
| 0 | 2字节 | 状态字 |
| 2 | 4字节 | 实际位置 |
| 6 | 1字节 | 报警码 |
这份映射需要同时在SSC工具、EEPROM、以及你写在ecat_appl.c里的处理逻辑中保持一致。任何一个地方对不上,排查起来都非常费劲。
4.3 SYNC0同步中断:让MCU跟EtherCAT周期对齐
EtherCAT主站可以配置分布式时钟(DC),输出同步信号Sync0,让所有从站按同一个时间基准触发。这个设计让多轴联动成为可能,但对MCU侧的代码提出了要求——你必须在Sync0中断里快速完成数据搬移。
我的做法是在Sync0中断里做三件事:
- 把ESC中缓存的输入数据拷贝到应用接收缓冲区
- 把应用发送缓冲区拷回ESC
- 置一个标志位,让主循环知道"这周期EtherCAT数据已经刷新了"
中断服务程序里禁止做耗时操作,比如对象字典的复杂处理、CAN报文的一次性发送多个,这些放到主循环。如果Sync0中断里塞了太多逻辑,下一个周期到了还没处理完,主站那边轻则增加抖动,重则Watchdog超时把从站置为OP之前的SafeOp状态。
4.4 用Wireshark抓EtherCAT帧验证
调试EtherCAT的时候,一台电脑加上Wireshark就够。把电脑网口接到主站和从站之间的网络上,抓到的是EtherCAT over EtherType 0x88A4的报文。重点看以下几类:
- 主站发出的寻址写命令,确认数据写到了期望的SM地址
- 从站返回的报文内容和主站期望的是否一致
- 是否有Working counter(工作计数器)不增加的情况
Working counter这个点很多新手不懂:EtherCAT每个从站处理完数据后会在帧的WKC字段做累加,主站用这个机制判断有没有从站没处理成功。如果WKC一直为0,说明从站根本没参与数据处理,问题基本出在FMMU配置或者SM配置上。
5. CANopen侧:对象字典、PDO/SDO与心跳机制
CANopen这半边整体比EtherCAT要"温和"一些——它有一套成熟的设备模型,通信参数和行为都在标准里写好了,实现起来更像"填表"。
5.1 对象字典规划
CANopen的一切都围绕对象字典(Object Dictionary,OD)。OD是一个16位索引加8位子索引的巨大表格,每一项描述设备的一个参数。通信相关参数占用0x1000~0x1FFF区域,设备特性参数在0x2000~0x5FFF,设备规范相关的在0x6000~0x9FFF。
我做伺服桥接设备时,OD规划大致是这样的:
| 索引 | 子索引 | 内容 |
|---|---|---|
| 0x1000 | 0 | 设备类型 |
| 0x1005 | 0 | SYNC COB-ID |
| 0x1017 | 0 | 心跳时间 |
| 0x1018 | 0-4 | 厂商/产品/版本信息 |
| 0x1400/0x1401 | 0/1/2 | RPDO1/RPDO2通信参数 |
| 0x1600/0x1601 | 0-N | RPDO1/RPDO2映射参数 |
| 0x1800/0x1801 | 0/1/2 | TPDO1/TPDO2通信参数 |
| 0x1A00/0x1A01 | 0-N | TPDO1/TPDO2映射参数 |
| 0x2000 | 0 | 本设备的EtherCAT状态字镜像 |
| 0x2001 | 0 | 目标位置(供CANopen侧读取) |
| 0x2002 | 0 | 实际位置反馈(供EtherCAT写入) |
0x2000这个区域是我自定义的,在标准上没有任何限制,但要注意:如果你后面想通过CANopen配置工具(类似EPM或者各种通用配置软件)去访问这些参数,必须把EDS文件写好。EDS文件里描述了OD每一项的类型、范围、默认值,工具的读写才正常。
5.2 PDO通信参数和映射参数的区别
0x1400~0x14FF是RPDO通信参数,决定了"哪个COB-ID过来的数据会触发这个PDO接收";0x1600~0x16FF是映射参数,决定了"收到数据后放到OD的哪个位置"。
举个例子,我让设备接收伺服驱动器的状态数据,用TPDO1,这个PDO的COB-ID默认是0x180+节点ID。如果节点ID是5,COB-ID就是0x185,映射了两个对象:0x6041(状态字)和0x6064(实际位置)。CAN总线上收到0x185这个ID的8字节数据时,CANopen协议栈会根据映射参数自动把第0~1字节写到OD的0x6041、第2~5字节写到OD的0x6064。
这就是CANopen的"自动"之处——一旦映射表配好,你根本不用写代码去解析报文,协议栈帮你把数据放到位,应用逻辑直接读OD就行。
5.3 SDO是给配置用的,PDO是给运行用的
SDO和PDO的分工说白了就是"管理通道"和"数据通道"。
SDO用于读写OD里任意一项,适合配置参数、读取一些不常变的诊断信息。SDO通信是确认式的,有请求就有响应,实时性差一点,但可靠。我的设备在初始化时会通过SDO读取下游每个伺服的厂商信息和故障记录,把这些信息映射到EtherCAT侧供主站读取。
PDO用于周期性或者事件触发的过程数据,无确认,实时性好。我的设备在运行阶段只用PDO通信,所有运动控制和位置反馈数据都走PDO,SDO只在初始化阶段使用。
SDO的COB-ID也有固定规则:客户端发0x600+节点ID,服务器端返回0x580+节点ID。所以SDO报文解析难度很低,滤波就按这两个ID处理。
5.4 心跳与节点保护
CANopen网络里判断节点是否活着,标准做法是心跳报文。设备每隔一段时间(0x1017里配置的时间)发送一个0x700+节点ID的报文,报文字节内容表示当前NMT状态。主站配置了心跳监控后,如果在设定时间内没收到某个节点的心跳,就认为该节点掉线,进入对应的故障处理流程。
这个机制在双总线设备里特别重要:EtherCAT主站需要知道我下面的CANopen从站是不是还活着。所以我用了一个很简单但有效的方法——把CANopen各节点的"心跳超时"状态映射成一个本地字节变量,再通过EtherCAT过程数据上报给主站,主站就知道CANopen侧哪个节点掉了,不至于盲目发命令导致误动作。
6. 双总线桥接的核心:一张映射表串起EtherCAT和CANopen
设备最核心的逻辑不在协议栈内部,而在"怎么把EtherCAT周期数据翻译成CANopen报文、再反向翻译回来"这个桥接层。这块做得好不好,直接决定设备的实时性和稳定性。
6.1 桥接设计的第一原则:别做协议解析
刚接触这个项目的时候,容易有一个天然冲动:把EtherCAT帧打开,把CANopen报文打开,然后逐字节做转换。这个思路是错的——协议转换开销大,而且两边的数据刷新节奏不同,很容易引入延迟。
正确的做法是:在内存层面做映射。EtherCAT侧的数据到了之后,直接写到对应的OD变量;CANopen侧的PDO接收完成后,数据已经在OD里了;应用层只需要维护一张"哪个OD变量对应哪个EtherCAT过程数据位置"的表,然后用memcpy来回搬运。
6.2 具体映射示例
我的设备连接一个CANopen伺服驱动器,节点ID为3。映射关系这样定义:
EtherCAT输入区(主站写过来的控制字):
- 偏移0~1字节 -> OD 0x6041(伺服状态字,实际上这个是从伺服读回来的,见下面方向说明)
我调整一下,用正确方向来说明:
主站发送的EtherCAT数据(进入设备):
- 偏移0~1:控制字 -> 存到本地变量,然后通过RPDO发给伺服
- 偏移2~5:目标位置 -> 存到本地变量,通过目标位置PDO发给伺服
伺服反馈的数据(设备读出后上报主站):
- 伺服TPDO1(0x183)的8字节数据 -> 协议栈把状态字放到OD 0x6041、实际位置放到OD 0x6064 -> 应用层读取OD再写入EtherCAT输出缓冲区偏移0~1和2~5
这个过程的代码核心就两个循环:
// EtherCAT到CANopen:把主站要求的控制命令搬进发送缓冲区 void bridge_ecat_to_canopen(void) { uint16_t control_word = ecat_rx_buffer[0] | (ecat_rx_buffer[1] << 8); uint32_t target_pos = ecat_rx_buffer[2] | (ecat_rx_buffer[3] << 8) | (ecat_rx_buffer[4] << 16) | (ecat_rx_buffer[5] << 24); // 写入OD OD_write(0x6040, &control_word, sizeof(control_word)); // 控制字 OD_write(0x607A, &target_pos, sizeof(target_pos)); // 目标位置 // 通知CANopen发送RPDO CO_RPDO_trigger(OD_RPDO1); }反过来,CANopen侧收到伺服TPDO后,应用层把OD里的状态字和实际位置拷贝到EtherCAT发送缓冲区,标记SYNC0中断里把它写入ESC。
6.3 数据一致性和刷新时序
这个桥接最容易出问题的点是数据一致性:EtherCAT在1ms周期刷新,CANopen的PDO可能也是1ms或者5ms刷新一次,两边不同步的情况下,会不会出现主站读到的位置值"跳变"?
我的处理策略是:
- 所有桥接数据统一由EtherCAT的SYNC0中断"拉高"一个更新标志
- 标志置位后,主循环里做一轮完整的数据搬移,这个循环里完成"ECAT收->CANopen发"和"CANopen收->ECAT发"两个方向
- 搬移过程用临界区保护,避免CANopen中断突然插入,修改到正在拷贝的变量
这样做的效果是:CANopen侧哪怕刷新慢,EtherCAT侧每次读到的总是上一次完整的"快照",不会出现半个周期数据是新的、半个周期是旧的这种情况。
6.4 周期抖动需要控制在什么水平
对于运动控制,主站一般期望从站的周期抖动在几十微秒以内。Sync0中断本身由ESC硬件引脚触发,抖动几乎可以忽略;真正可能带来抖动的是中断服务程序中处理的时间,还有桥上逻辑在主循环里的调度时间。
实测下来,只要SYNC0中断服务程序控制在10微秒以内(就是两次memcpy加几个标志位操作),主循环里桥接逻辑在1ms周期内通常占用不超过200微秒,整体延迟大概在100~200微秒级别,对大部分伺服应用都够用了。
7. 实测中一定会遇到的三类疑难问题
这个项目调试过程比写代码的时间长得多。下面几个问题都是我实际趟过的坑,列出来供参考。
7.1 问题一:EtherCAT主站一进入OP状态就从站掉线
现象:在配置工具里扫描正常,状态机从INIT到PREOP都正常,一进入SAFEOP或OP马上报从站不响应,WKC不更新。
根因:这类问题十有八九出在SM2/SM3的配置上。SM2和SM3是过程数据通道,主站进入OP前会按照从站的寄存器配置去读写过程数据缓冲区。如果你的缓冲区长度或者FMMU映射位置跟SSC工具里配置的不一致,ESC实际响应的地址和主站期望的逻辑地址对不上,WKC就不会递增。
排查方法:用Wireshark抓包,重点对比进入SAFEOP前后的非周期性寻址写命令,看主站写入的地址是什么,再读回ESC的SM2/3基地址和长度,两边不一致就重写EEPROM并重新上电。
7.2 问题二:CANopen从站能收到PDO但从未发出数据
现象:设备能收到上位机通过CANopen配置工具发给它的RPDO,也能进入OPERATIONAL状态,但TPDO一直没发出来,或者上位机收不到TPDO。
根因:TPDO的触发方式没有配置对。CANopen的TPDO触发方式有事件触发、定时触发和同步触发,如果上位机配置的是同步触发(sync mode),而设备没配置接收SYNC对象(0x1005 COB-ID默认不对),TPDO就永远不会发出。
我在设备里给TPDO配置了两种触发方式:默认是事件触发,同时允许通过SDO改成定时触发。这样做兼容性最好,不管主站那边怎么配都能响应。
7.3 问题三:字节序和数据类型对不上
现象:EtherCAT主站显示的目标位置和CANopen工具里读到的目标位置差得很离谱,比如发送1000显示是某个巨大值。
根因:绝大多数情况下是字节序问题。CANopen的PDO传输默认是小端序,EtherCAT的过程数据也用小端,但有些EtherCAT主站工具或者你写的布局代码用了大端排列,两边一错位,数值自然就乱了。
处理方式:在桥接层入口统一做一次字节序转换,把从EtherCAT缓冲区读取的数据转换成主机字节序之后再去写入OD。别图省事,等发现伺服乱跑再去排查就晚了。
最后再分享一点我的体会
做完这个项目我最大的感受是:双总线设备真正的难度不在于EtherCAT也不在于CANopen,而在于把两个不同时代的总线协议流畅地捏在同一个时间轴里。开发的时候建议先把EtherCAT跑通、把CANopen单独跑通,最后再做桥接层,每步单独验证,这样定位问题会快很多。另外记得把函数接口和数据结构提前设计好,两套协议栈之间尽量少直接访问对方的内部变量,接口之间留一层隔离,后面无论是换芯片还是换协议栈版本,都不会伤筋动骨。