2、CANopen协议基础:OSI模型、CAN总线帧格式、CANopen对象字典、PDO/SDO/NMT/心跳等核心概念

CANopen协议是建立在CAN总线物理层之上的应用层协议,它通过标准化的对象字典(OD)和通信对象(PDO、SDO、NMT、心跳等)将CAN总线原始的报文收发能力封装为可管理、可配置、可互操作的工业控制网络。理解CANopen协议,必须从OSI模型的分层视角切入,明确CAN总线只解决了物理层和数据链路层的传输问题,而CANopen则承担了网络层以上的会话、表示和应用功能。本章将围绕OSI模型映射、CAN帧格式、对象字典、四种核心通信对象以及心跳机制展开,并结合STM32实战中的典型配置场景进行拆解。

CANopen协议栈与STM32映射关系 OSI 7层模型 应用层(7) 表示层(6) 会话层(5) 传输层(4) 网络层(3) 数据链路层(2) CAN控制器(内置) 物理层(1) CAN收发器 + 双绞线 CANopen仅定义 应用层及以上 (对象字典/通信对象) CANopen核心机制 对象字典 (OD) 索引0x1000-0x9FFF 存储参数/数据/映射 STM32: 结构体数组 PDO (过程数据) 实时传输, 1-8字节 生产者/消费者模型 STM32: CAN Tx/Rx中断 SDO (服务数据) 客户端/服务器, 分段传输 用于OD读写配置 STM32: 状态机解析 NMT (网络管理) 节点状态控制 启动/停止/预操作 STM32: 状态机切换 心跳 (Heartbeat) 周期发送节点状态 超时检测节点故障 STM32: 定时器触发 紧急 (EMCY) 错误上报 16位错误码+寄存器 STM32: 错误处理回调 CAN 2.0A 标准帧格式 (11位ID) SOF(1) | ID(11) | RTR(1) | IDE(1) | DLC(4) | Data(0-8B) | CRC(15) | ACK(2) | EOF(7) STM32F103/F405 CAN外设 bxCAN: 3个发送邮箱, 2个接收FIFO, 28个滤波器组

上图展示了CANopen协议栈与STM32硬件资源的映射关系。左侧是OSI模型,CAN控制器和收发器分别对应数据链路层和物理层,而CANopen协议栈则运行在应用层。右侧是CANopen的六大核心机制:对象字典(OD)、PDO、SDO、NMT、心跳和紧急报文。STM32的bxCAN外设通过硬件滤波器和FIFO为这些机制提供底层支撑。下面我们逐一深入剖析。

2.1 OSI模型与CANopen分层架构

核心原理

OSI七层模型将网络通信划分为从物理层到应用层的七个层次。CAN总线协议(ISO 11898)只定义了物理层和数据链路层,其中物理层负责信号电平、位时序、同步和总线仲裁,数据链路层负责报文封装、错误检测和帧格式。CANopen协议则填补了从网络层到应用层的空白,它定义了对象字典、通信对象和网络管理机制,使得多个CAN节点能够以标准化的方式交换数据。

在实际的STM32开发中,这种分层体现在:物理层由CAN收发器(如TJA1050)和双绞线组成,数据链路层由STM32内置的bxCAN外设实现,而应用层则需要开发者通过软件实现CANopen协议栈。常见的做法是移植CANopenNode或CanFestival开源协议栈,也可以根据项目需求自行实现精简版。

OSI层CAN/CANopen实现STM32资源
应用层(7)对象字典、PDO/SDO映射、用户应用逻辑用户代码、回调函数
表示层(6)数据编码/解码(小端字节序)字节序转换函数
会话层(5)连接管理(SDO会话)SDO状态机
传输层(4)分段传输(SDO快速/分段)分段重组缓冲区
网络层(3)节点寻址(Node-ID + 功能码)COB-ID计算
数据链路层(2)CAN帧封装、错误检测、仲裁bxCAN外设、硬件滤波器
物理层(1)差分信号、位时序、总线终端CAN收发器、120Ω终端电阻

判断方法

在调试CANopen网络时,首先要判断问题出在哪一层。如果总线无波形,检查物理层(收发器供电、终端电阻、接线);如果波形正常但收不到报文,检查数据链路层(波特率配置、滤波器设置);如果报文正常但节点不响应,检查应用层(对象字典配置、NMT状态)。这种分层排查思路能大幅缩短故障定位时间。

案例拆解

某运动控制项目中,STM32作为CANopen从站连接伺服驱动器。上电后主站发送NMT启动命令,但伺服无响应。排查过程:先用示波器测量CAN_H和CAN_L差分信号,发现波形正常,排除物理层问题;再检查STM32的bxCAN滤波器配置,发现滤波器ID设置错误,导致所有报文被硬件过滤掉。修正滤波器后,节点正常响应。这个案例说明,数据链路层的硬件滤波器是STM32特有的"陷阱",必须与CANopen的COB-ID匹配。

常见误区:很多开发者只关注应用层代码,忽略了bxCAN硬件滤波器的配置。滤波器默认设置为全部接收时,虽然能收到报文,但CPU中断负载会显著增加。正确做法是根据COB-ID范围配置滤波器组,只接收本节点相关的报文。

2.2 CAN总线帧格式详解

核心原理

CAN总线支持两种帧格式:标准帧(CAN 2.0A,11位ID)和扩展帧(CAN 2.0B,29位ID)。CANopen协议规定使用标准帧,ID分配遵循预定义连接集。一个完整的CAN数据帧包含:帧起始(SOF)、仲裁场(ID + RTR)、控制场(IDE + DLC)、数据场(0-8字节)、CRC场、ACK场和帧结束(EOF)。

CANopen通过COB-ID(通信对象标识符)来区分不同类型的报文。COB-ID由功能码(4位)和节点ID(7位)组成,例如PDO1发送的COB-ID = 0x180 + NodeID,SDO请求的COB-ID = 0x600 + NodeID。这种编码方式使得总线上每个节点最多有16种通信对象,且ID分配具有确定性。

// CANopen预定义连接集 (部分)
// 功能码: 0x00 NMT, 0x01 SYNC, 0x02 TIME, 0x04 EMCY
// 0x05 PDO1-Tx, 0x06 PDO1-Rx, 0x07 PDO2-Tx, 0x08 PDO2-Rx
// 0x09 PDO3-Tx, 0x0A PDO3-Rx, 0x0B PDO4-Tx, 0x0C PDO4-Rx
// 0x0E SDO-Tx, 0x0F SDO-Rx, 0x10 Heartbeat

// 示例: 节点ID=3
// PDO1发送: 0x180 + 3 = 0x183
// SDO请求: 0x600 + 3 = 0x603
// 心跳: 0x700 + 3 = 0x703

操作流程

在STM32中配置CAN帧发送时,需要设置CAN_TxHeaderTypeDef结构体:

CAN_TxHeaderTypeDef TxHeader;
TxHeader.ExtId = 0;              // 不使用扩展ID
TxHeader.IDE = CAN_ID_STD;       // 标准帧
TxHeader.RTR = CAN_RTR_DATA;     // 数据帧
TxHeader.DLC = 8;                // 数据长度
TxHeader.StdId = 0x183;          // COB-ID
// 填充数据后调用 HAL_CAN_AddTxMessage()

常见误区

初学者常犯的错误是混淆标准帧和扩展帧。CANopen强制使用标准帧,如果误配置为扩展帧,即使ID相同也无法通信。另外,DLC最大为8字节,PDO数据超过8字节时必须使用SDO分段传输,不能强行塞入一帧。

2.3 对象字典(OD)

核心原理

对象字典是CANopen的核心数据结构,它是一个16位索引的数组,每个索引下可以包含多个子索引。对象字典存储了设备的所有参数、配置、状态和过程数据。标准对象字典分为几个区域:0x1000-0x1FFF为通信参数区(如设备类型、错误寄存器、PDO参数),0x2000-0x5FFF为制造商特定区,0x6000-0x9FFF为标准设备行规区(如运动控制的轮廓速度模式参数)。

在STM32实现中,对象字典通常定义为一个结构体数组,每个元素包含索引、子索引、数据类型、访问权限和值指针。例如:

typedef struct {
    uint16_t index;
    uint8_t subindex;
    uint8_t data_type;   // 0x01=BOOL, 0x02=INT8, 0x03=INT16...
    uint8_t access;      // 0x00=RO, 0x01=WO, 0x02=RW
    void *data_ptr;
    uint8_t data_len;
} OD_Entry_t;

// 示例: 0x6000 读输入8位 (CiA 401)
OD_Entry_t OD_Table[] = {
    {0x1000, 0x00, 0x07, 0x00, &device_type, 4},  // 设备类型
    {0x1001, 0x00, 0x05, 0x00, &error_reg, 1},    // 错误寄存器
    {0x6000, 0x01, 0x01, 0x00, &input_byte1, 1},  // 数字输入1
    // ... 更多条目
};

判断方法

对象字典的访问权限决定了SDO读写是否允许。如果主站写入参数失败,首先要检查OD条目的access字段是否设置为RW。另外,数据类型必须匹配,例如写入INT16值时,如果OD定义为UINT8,会导致数据截断。

案例拆解

在一个多轴运动控制系统中,主站通过SDO修改从站的轮廓速度(0x6081)。初始配置时,从站OD中0x6081的数据类型定义为UINT32,但主站发送的是INT32类型。虽然数值相同,但符号位被解释为负数,导致电机反向旋转。排查过程:使用CAN分析仪抓取SDO报文,对比请求和响应数据,发现数据类型不匹配。修正OD定义后,问题解决。这个案例说明,对象字典的数据类型定义必须与设备行规严格一致。

经验提醒:在STM32中实现对象字典时,建议使用const修饰只读条目,防止程序意外修改。同时,对于实时性要求高的PDO映射对象,应使用volatile关键字,确保数据同步。

2.4 PDO与SDO通信机制

核心原理

PDO(过程数据对象)用于实时传输小量数据(最多8字节),采用生产者/消费者模型,无需应答,传输延迟低。PDO分为发送PDO(TPDO)和接收PDO(RPDO),每个节点最多支持4个TPDO和4个RPDO。PDO的传输模式包括:事件触发(数据变化时发送)、定时触发(周期发送)、同步触发(收到SYNC后发送)和远程请求。

SDO(服务数据对象)用于传输大块数据或配置参数,采用客户端/服务器模型,有应答机制,可靠性高。SDO支持快速传输(数据≤4字节)和分段传输(数据>4字节)。SDO报文使用8字节数据场,其中第一个字节为命令码,后三个字节为索引和子索引,最后四个字节为数据。

特性PDOSDO
传输方向单向(生产者→消费者)双向(客户端↔服务器)
数据长度≤8字节≤4字节(快速)/ 分段
应答机制无应答有应答(确认/错误)
实时性高(微秒级)低(毫秒级)
典型应用速度指令、位置反馈参数配置、固件升级
COB-ID0x180+NodeID (TPDO1)0x600+NodeID (请求)

操作流程

在STM32中配置PDO映射时,需要修改对象字典中的PDO映射参数(0x1A00-0x1A03为TPDO映射,0x1600-0x1603为RPDO映射)。映射参数定义了哪些OD条目被映射到PDO数据场中。例如,将0x6040(控制字)和0x6042(目标速度)映射到RPDO1:

// RPDO1映射参数 (0x1600)
// 子索引0: 映射条目数量 = 2
// 子索引1: 0x60400010 (索引0x6040, 子索引0, 长度16位)
// 子索引2: 0x60420010 (索引0x6042, 子索引0, 长度16位)
// 接收到的RPDO1数据: [控制字(2B) | 目标速度(2B)]

案例拆解

在一个伺服控制项目中,主站通过PDO1周期发送控制字和目标速度(4字节),从站通过PDO1反馈状态字和实际速度(4字节)。初始配置时,PDO传输类型设置为事件触发,导致主站发送频率不稳定,速度波动明显。将传输类型改为同步触发(传输类型=1),并配置SYNC周期为1ms后,速度波动从±5%降低到±0.5%。这个案例说明,PDO传输模式的选择直接影响控制性能。

风险提示:PDO无应答机制,如果总线负载过高或节点故障,数据可能丢失。在安全关键应用中,应同时启用心跳监控和紧急报文,确保节点故障能被及时检测。

2.5 NMT状态机与心跳机制

核心原理

NMT(网络管理)定义了CANopen节点的状态机,包括初始化(Initialization)、预操作(Pre-Operational)、操作(Operational)和停止(Stopped)四种状态。节点上电后自动进入初始化,然后进入预操作状态。在预操作状态下,节点只能使用SDO通信,不能使用PDO。主站发送NMT命令后,节点切换到操作状态,开始PDO通信。NMT命令使用COB-ID=0x000,数据场第一个字节为命令码,第二个字节为目标节点ID(0表示所有节点)。

心跳机制用于监控节点状态。每个节点周期性地发送心跳报文(COB-ID=0x700+NodeID),数据场为节点状态(0x00=启动, 0x04=停止, 0x05=操作, 0x7F=预操作)。主站通过心跳超时检测节点故障。心跳周期由对象字典0x1017定义,超时时间由0x1016定义。

// NMT命令码
#define NMT_START_REMOTE_NODE    0x01
#define NMT_STOP_REMOTE_NODE     0x02
#define NMT_ENTER_PRE_OPERATIONAL 0x80
#define NMT_RESET_NODE           0x81
#define NMT_RESET_COMMUNICATION  0x82

// 心跳状态值
#define HB_BOOTUP       0x00
#define HB_STOPPED      0x04
#define HB_OPERATIONAL  0x05
#define HB_PRE_OPERATIONAL 0x7F

操作流程

在STM32中实现心跳发送,可以使用定时器中断,周期性地发送心跳报文。例如,配置定时器周期为100ms,在中断回调中调用CAN发送函数:

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
    if (htim == &htim3) {
        uint8_t hb_data = HB_OPERATIONAL;  // 当前节点状态
        CAN_TxHeaderTypeDef hb_header;
        hb_header.StdId = 0x700 + NODE_ID;
        hb_header.IDE = CAN_ID_STD;
        hb_header.RTR = CAN_RTR_DATA;
        hb_header.DLC = 1;
        HAL_CAN_AddTxMessage(&hcan1, &hb_header, &hb_data, &mailbox);
    }
}

常见误区

一个常见误区是节点在预操作状态下尝试发送PDO,这会导致总线错误。正确的流程是:节点上电→初始化→预操作→等待NMT启动命令→进入操作状态→开始PDO通信。另外,心跳超时时间必须大于心跳周期的3倍,否则可能因网络抖动导致误判。

总结复盘:CANopen协议的核心在于对象字典的统一管理和通信对象的灵活配置。在STM32实战中,建议先实现最小系统(NMT + 心跳 + 1个PDO + 1个SDO),验证通信链路后再逐步扩展。使用CAN分析仪(如PCAN、ZLG USBCAN)抓取报文,对比COB-ID和数据内容,是排查协议问题的最有效手段。本章内容为后续的运动控制应用(如轮廓速度模式、位置模式)奠定了协议基础。