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模型,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匹配。
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定义后,问题解决。这个案例说明,对象字典的数据类型定义必须与设备行规严格一致。
2.4 PDO与SDO通信机制
核心原理
PDO(过程数据对象)用于实时传输小量数据(最多8字节),采用生产者/消费者模型,无需应答,传输延迟低。PDO分为发送PDO(TPDO)和接收PDO(RPDO),每个节点最多支持4个TPDO和4个RPDO。PDO的传输模式包括:事件触发(数据变化时发送)、定时触发(周期发送)、同步触发(收到SYNC后发送)和远程请求。
SDO(服务数据对象)用于传输大块数据或配置参数,采用客户端/服务器模型,有应答机制,可靠性高。SDO支持快速传输(数据≤4字节)和分段传输(数据>4字节)。SDO报文使用8字节数据场,其中第一个字节为命令码,后三个字节为索引和子索引,最后四个字节为数据。
| 特性 | PDO | SDO |
|---|---|---|
| 传输方向 | 单向(生产者→消费者) | 双向(客户端↔服务器) |
| 数据长度 | ≤8字节 | ≤4字节(快速)/ 分段 |
| 应答机制 | 无应答 | 有应答(确认/错误) |
| 实时性 | 高(微秒级) | 低(毫秒级) |
| 典型应用 | 速度指令、位置反馈 | 参数配置、固件升级 |
| COB-ID | 0x180+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传输模式的选择直接影响控制性能。
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倍,否则可能因网络抖动导致误判。