一、CANopen 协议概述
1.1 协议定义
CANopen 是基于 CAN (Controller Area Network) 总线的高层应用协议,由 CiA (CAN in Automation) 组织标准化(EN 50325-4 / CiA301)。它在 CAN 2.0A/2.0B 物理层和数据链路层之上,定义了设备间的通信机制和行为规范。
1.2 协议分层
┌─────────────────────────────────────────┐
│ 应用层 (Application) │
│ - 设备配置文件(CiA401/402/...) │
│ - 自定义应用逻辑 │
├─────────────────────────────────────────┤
│ CANopen 协议层 │
│ - 对象字典 (Object Dictionary) │
│ - 通信对象 (NMT/SDO/PDO/...) │
│ - 网络管理 │
├─────────────────────────────────────────┤
│ CAN 数据链路层 │
│ - 帧格式、仲裁、确认 │
├─────────────────────────────────────────┤
│ CAN 物理层 │
│ - 电气特性、位时序 │
└─────────────────────────────────────────┘
1.3 核心特性
- 主从架构:支持一个主站(可选)和多个从站(1-127)
- 分布式控制:无需中央控制器,设备可点对点通信
- 实时性:PDO 通信提供微秒级响应
- 标准化:统一的对象字典和通信机制
- 灵活配置:支持静态和动态网络配置
二、对象字典(Object Dictionary)
2.1 对象字典概念
对象字典(OD)是 CANopen 设备的核心,它是一个结构化的参数表,包含设备的所有配置、状态和数据。
索引结构:
对象字典地址 = 索引(Index, 16位)+ 子索引(Sub-index, 8位)
示例:
0x1017, 0 → 心跳生产者时间
0x1400, 1 → RPDO1 通信参数
0x6000, 1 → 应用数据(如温度传感器值)
2.2 对象字典分区
| 索引范围 | 区域 | 说明 |
|---|---|---|
| 0x0000 | 未使用 | 保留 |
| 0x0001-0x0FFF | 数据类型 | 基本数据类型定义 |
| 0x1000-0x1FFF | 通信参数 | 设备类型、标识、SDO、PDO 配置 |
| 0x2000-0x5FFF | 厂商自定义 | 特定设备参数 |
| 0x6000-0x9FFF | 标准化设备配置文件 | CiA401(I/O)、CiA402(运动)等 |
| 0xA000-0xFFFF | 保留 | 未来扩展 |
2.3 常用通信参数对象
c
// 设备标识
0x1000, 0 → Device Type (u32) // 设备类型
0x1001, 0 → Error Register (u8) // 错误寄存器
0x1017, 0 → Producer Heartbeat Time // 心跳周期 (ms)
0x1018 → Identity Object // 设备身份信息
Sub 1: Vendor ID // 厂商ID
Sub 2: Product Code // 产品代码
Sub 3: Revision Number // 版本号
Sub 4: Serial Number // 序列号
// SDO 服务器
0x1200 → SDO Server Parameter
Sub 1: COB-ID Client→Server (RX) // 接收 COB-ID (0x600+NodeID)
Sub 2: COB-ID Server→Client (TX) // 发送 COB-ID (0x580+NodeID)
// RPDO(接收过程数据)
0x1400 → RPDO1 Communication Parameter
Sub 1: COB-ID // CAN 标识符 (0x200+NodeID)
Sub 2: Transmission Type // 传输类型(同步/异步)
Sub 3: Inhibit Time // 抑制时间
Sub 5: Event Timer // 事件定时器
0x1600 → RPDO1 Mapping Parameter
Sub 0: Number of Mapped Objects // 映射对象数量
Sub 1-8: Mapped Objects // 映射的 OD 地址
// TPDO(发送过程数据)
0x1800 → TPDO1 Communication Parameter
0x1A00 → TPDO1 Mapping Parameter
// 参数存储
0x1010 → Store Parameters // 存储命令
Sub 1: Save All Parameters // 保存所有参数(写入 "save")
Sub 2: Save Communication Parameters // 保存通信参数
Sub 3: Save Application Parameters // 保存应用参数
0x1011 → Restore Default Parameters // 恢复默认值
Sub 1: Restore All Defaults // 恢复所有(写入 "load")
2.4 对象字典访问方式
c
// CANopenNode 中访问对象字典的方式
// 1. 直接读取
uint16_t heartbeatTime;
ODR_t ret = OD_get_u16(OD_ENTRY_H1017_producerHTime, 0, &heartbeatTime, true);
// 2. 直接写入
uint16_t newHeartbeat = 1000; // 1000ms
OD_set_u16(OD_ENTRY_H1017_producerHTime, 0, newHeartbeat, true);
// 3. 通过 SDO 访问(网络访问)
CO_SDOclient_t* SDOclient = CO->SDOclient;
CO_SDOclientUploadInitiate(SDOclient, 0x1017, 0, timeout_ms);
// ... 等待完成
// 4. 通过 PDO 自动更新(实时数据)
// PDO 映射后自动读写对应的 OD 地址
三、通信对象(Communication Objects)
3.1 NMT (Network Management) - 网络管理
功能
NMT 负责控制 CANopen 节点的状态转换和网络启停。
NMT 状态机
┌────────────────┐
│ Initialization│
│ (上电复位) │
└────────┬───────┘
│
自动进入 │
↓
┌─────────────────────────────┐
│ Pre-operational │
│ (预操作态,只接受配置) │
└─────────────────────────────┘
↑ │ ↑
NMT │ │ NMT │ NMT
Stop/Error│ │ Start │ Pre-op
│ ↓ │
┌──────┴──────────────────┴───┐
│ Operational │
│ (操作态,正常通信) │
└──────────────────────────────┘
│
NMT Stop │
↓
┌────────────────┐
│ Stopped │
│ (停止态) │
└────────────────┘
NMT 命令
c
// NMT 命令格式:COB-ID = 0x000 (最高优先级)
// 数据格式:[命令字节, 节点ID]
命令代码 (Command Specifier):
0x01 → Start (启动,进入 Operational)
0x02 → Stop (停止,进入 Stopped)
0x80 → Pre-operational (进入预操作态)
0x81 → Reset Node (节点复位)
0x82 → Reset Communication (通信复位)
节点ID:
0x00 → 广播(所有节点)
0x01-0x7F → 单个节点
示例:
[0x01, 0x04] → 启动节点4
[0x81, 0x00] → 复位所有节点
NMT 控制标志
c
// CANopenNode 中的 NMT 控制配置
#define NMT_CONTROL \
CO_NMT_STARTUP_TO_OPERATIONAL \ // 启动后自动进入操作态
| CO_NMT_ERR_ON_ERR_REG \ // 错误时自动进入预操作态
| CO_ERR_REG_GENERIC_ERR \ // 启用通用错误检测
| CO_ERR_REG_COMMUNICATION // 启用通信错误检测
3.2 SDO (Service Data Object) - 服务数据对象
功能
SDO 用于配置和诊断,通过确认机制保证数据可靠传输。支持读写对象字典任意数据。
SDO 特点
- 面向连接:请求-响应机制
- 可靠传输:带确认和错误处理
- 支持分段传输:可传输大数据(>4字节)
- 非实时:典型响应时间数十毫秒
SDO 协议
客户端(Master) 服务器(Slave)
│ │
│ 上传请求 (Upload Request) │
│ COB-ID = 0x600 + NodeID │
├────────────────────────────> │
│ [40 17 10 00 00 00 00 00] │ 读取 0x1017,0
│ │
│ 上传响应 (Upload Response) │
│ COB-ID = 0x580 + NodeID │
│<──────────────────────────── │
│ [4B 17 10 00 E8 03 00 00] │ 返回值:0x03E8 (1000)
│ │
│ 下载请求 (Download Request) │
│ COB-ID = 0x600 + NodeID │
├────────────────────────────> │
│ [2B 17 10 00 D0 07 00 00] │ 写入 2000 到 0x1017,0
│ │
│ 下载响应 (Download Response) │
│ COB-ID = 0x580 + NodeID │
│<──────────────────────────── │
│ [60 17 10 00 00 00 00 00] │ 确认成功
│ │
SDO 命令字节详解
命令字节格式 (CCS/SCS):[X X X X X X X X]
│ │ │ │ └─┴─┴─ 数据长度指示
│ │ │ └─ 加速传输标志
│ │ └─ 数据大小指示
│ └─ 保留
└─ 命令代码
上传请求 (Client→Server):
0x40 → Initiate Upload (开始上传)
0x60 → Upload Segment (分段上传)
上传响应 (Server→Client):
0x41-0x4F → Expedited Upload (快速上传,数据≤4字节)
0x41: 4字节, 0x43: 3字节, 0x47: 2字节, 0x4B: 1字节
0x60 → Upload Segment (分段上传)
下载请求 (Client→Server):
0x20-0x2F → Initiate Download (开始下载)
0x00 → Download Segment (分段下载)
下载响应 (Server→Client):
0x60 → Download OK (确认成功)
SDO 在 CANopenLinux 中的应用
c
// SDO 服务器:响应来自网络的 SDO 请求
// 在 CO_CANopenInit() 中初始化
CO_SDOserver_t* SDOserver = CO->SDOserver;
// 超时时间:1000ms
SDOserver->timeoutTime_us = 1000000;
// SDO 客户端:主动读写其他节点的 OD
CO_SDOclient_t* SDOclient = CO->SDOclient;
// 上传(读取)示例:读取节点4的心跳时间
CO_SDOclientUploadInitiate(SDOclient, 0x1017, 0, 500, 4);
while (SDOclient->state != CO_SDO_ST_IDLE) {
CO_SDOclientUpload(SDOclient, timeDifference_us, ...);
}
uint16_t heartbeat = *(uint16_t*)SDOclient->buf;
// 下载(写入)示例:写入节点4的心跳时间
uint16_t newValue = 2000;
CO_SDOclientDownloadInitiate(SDOclient, 0x1017, 0,
(uint8_t*)&newValue, 2, 500, 4);
while (SDOclient->state != CO_SDO_ST_IDLE) {
CO_SDOclientDownload(SDOclient, timeDifference_us, ...);
}
3.3 PDO (Process Data Object) - 过程数据对象
功能
PDO 用于实时数据交换,无确认机制,传输效率最高。适用于周期性传感器数据、控制命令等。
PDO 分类
- RPDO (Receive PDO):节点接收的 PDO(输入)
- TPDO (Transmit PDO):节点发送的 PDO(输出)
每个节点支持最多 512 个 RPDO 和 512 个 TPDO(标准配置通常为 4 个)。
PDO 通信参数(0x1400/0x1800)
c
// RPDO1 通信参数 (0x1400)
0x1400, 0 → 最大子索引 = 5
0x1400, 1 → COB-ID (u32)
默认:0x200 + NodeID
例如节点4:0x204
最高位=1表示禁用该PDO
0x1400, 2 → Transmission Type (u8)
0x00 → 同步(非循环)
0x01-0xF0 → 同步(循环,N个SYNC后触发)
0xFC-0xFD → RTR(远程请求)
0xFE → 异步(事件驱动)
0xFF → 异步(设备配置文件特定)
0x1400, 3 → Inhibit Time (u16)
抑制时间(100us单位),防止发送过快
例如:10 = 1ms最小间隔
0x1400, 5 → Event Timer (u16)
事件定时器(ms),周期性发送
例如:1000 = 每1秒发送一次
PDO 映射参数(0x1600/0x1A00)
c
// TPDO1 映射参数 (0x1A00)
0x1A00, 0 → 映射对象数量 (u8)
例如:2(映射2个对象)
0x1A00, 1 → 第1个映射对象 (u32)
格式:[索引16位][子索引8位][位长度8位]
例如:0x60000110 表示:
索引=0x6000, 子索引=0x01, 长度=16位
0x1A00, 2 → 第2个映射对象 (u32)
例如:0x60000220 表示:
索引=0x6000, 子索引=0x02, 长度=32位
PDO 数据帧内容:
[对象1数据][对象2数据]...
最多8字节(CAN标准帧)或64字节(CAN FD)
PDO 配置示例
场景:发送温度(16位)和压力(32位)
1. 配置 TPDO1 通信参数 (0x1800)
0x1800, 1 = 0x40000180 + NodeID // COB-ID
0x1800, 2 = 0xFE // 异步传输
0x1800, 3 = 100 // 10ms 抑制时间
0x1800, 5 = 1000 // 1秒周期
2. 配置 TPDO1 映射 (0x1A00)
0x1A00, 0 = 2 // 映射2个对象
0x1A00, 1 = 0x60010110 // 温度 OD[0x6001,1] 16位
0x1A00, 2 = 0x60020120 // 压力 OD[0x6002,1] 32位
3. 应用层更新数据
OD[0x6001, 1] = 250 // 25.0°C
OD[0x6002, 1] = 101325 // 101.325 kPa
4. PDO 自动发送
CAN帧:COB-ID=0x184, 数据=[FA 00 0D 8B 01 00]
│ │ │ │
└─ 0x184 └─ 250 └─ 101325
PDO 在 CANopenLinux 中的处理
c
// 初始化 PDO
CO_CANopenInitPDO(CO, CO->em, OD, CO_activeNodeId, &errInfo);
// 实时线程中处理 PDO
void CO_epoll_processRT(CO_epoll_t* ep, CO_t* co, bool_t syncWas) {
CO_LOCK_OD(co->CANmodule);
// 处理 SYNC
bool_t syncWas = CO_process_SYNC(co, ep->timeDifference_us, NULL);
// 处理接收的 PDO
CO_process_RPDO(co, syncWas, ep->timeDifference_us, NULL);
// 处理发送的 PDO
CO_process_TPDO(co, syncWas, ep->timeDifference_us, NULL);
CO_UNLOCK_OD(co->CANmodule);
}
// 应用层更新 PDO 数据
void app_programRt(CO_t* co, uint32_t timer1msDiff_us) {
CO_LOCK_OD(co->CANmodule);
// 读取传感器
float temperature = read_temperature_sensor();
// 更新对象字典(映射到 TPDO)
OD_set_r32(OD_ENTRY_H6001_temperature, 1, temperature, true);
CO_UNLOCK_OD(co->CANmodule);
// TPDO 会根据配置自动发送
}
3.4 SYNC (Synchronization) - 同步对象
功能
SYNC 对象用于同步多个 CANopen 节点的 PDO 传输,实现确定性实时通信。
SYNC 协议
SYNC 帧:
COB-ID = 0x080 (默认)
数据长度 = 0 或 1
数据内容 = 计数器(可选,0-240循环)
示例:
[0x080, 0字节] → 简单 SYNC
[0x080, 1字节, 05] → 带计数器的 SYNC,计数值=5
SYNC 配置
c
// SYNC 通信参数 (0x1005-0x1006)
0x1005, 0 → COB-ID SYNC (u32)
默认:0x80
最高位=1 表示该节点不是 SYNC 生产者
0x1006, 0 → Communication Cycle Period (u32)
SYNC 周期(us)
例如:1000 = 1ms 周期
SYNC 与 PDO 的关系
时间轴:
─┬──────────┬──────────┬──────────┬──────────┬─
│ │ │ │ │
SYNC1 SYNC2 SYNC3 SYNC4 SYNC5
│ │ │ │ │
├─ Node1 TPDO1 │ │ │
├─ Node2 TPDO1 │ │ │
│ ├─ Node3 TPDO1 │ │
│ │ (配置为每2个SYNC) │ │
│ │ │ ├─ Node4 TPDO1
│ │ │ │ (配置为每4个SYNC)
配置示例:
Node3: 0x1400,2 = 0x02 (每2个SYNC触发)
Node4: 0x1400,2 = 0x04 (每4个SYNC触发)
3.5 Emergency - 紧急对象
功能
Emergency 用于节点异常通知,当检测到错误时立即发送。
Emergency 帧格式
COB-ID = 0x080 + NodeID
数据格式(8字节):
字节0-1:错误码 (Error Code, u16)
字节2: 错误寄存器 (Error Register, u8)
字节3-7:厂商特定错误信息
错误码分类:
0x0000 → 错误复位(无错误)
0x1000 → 通用错误
0x2000-0x2FFF → 电流错误
0x3000-0x3FFF → 电压错误
0x4000-0x4FFF → 温度错误
0x5000-0x5FFF → 通信错误
0x6000-0x6FFF → 设备配置文件特定错误
0x8000-0x8FFF → 监控错误
0x9000-0x9FFF → 外部错误
0xF000-0xFFFF → 厂商特定错误
示例:
错误码 0x4210 = 温度过高(设备配置文件定义)
Emergency 在 CANopenLinux 中的使用
c
// 发送 Emergency
CO_errorReport(co->em,
CO_EM_TEMPERATURE_HIGH, // 错误码
CO_EMC_TEMPERATURE, // 错误分类
(uint32_t)(temperature * 100)); // 厂商特定数据
// 错误码定义(CANopenNode)
#define CO_EM_NO_ERROR 0x0000
#define CO_EM_CAN_BUS_WARNING 0x0010
#define CO_EM_CAN_TX_BUS_OFF 0x0018
#define CO_EM_HEARTBEAT_CONSUMER 0x0082
#define CO_EM_HB_CONSUMER_REMOTE_RESET 0x0083
#define CO_EM_TEMPERATURE_HIGH 0x4210
#define CO_EM_VOLTAGE_HIGH 0x3210
3.6 Heartbeat - 心跳
功能
Heartbeat 用于节点存活检测,节点周期性发送自身状态。
Heartbeat Producer(生产者)
c
// 心跳配置
0x1017, 0 → Producer Heartbeat Time (u16)
心跳周期(ms)
0 = 禁用心跳
例如:1000 = 每秒发送一次
// 心跳帧
COB-ID = 0x700 + NodeID
数据长度 = 1字节
数据内容 = NMT 状态
NMT 状态值:
0x00 → Boot-up(启动,仅发送一次)
0x04 → Stopped
0x05 → Operational
0x7F → Pre-operational
Heartbeat Consumer(消费者)
c
// 心跳消费者配置(监控其他节点)
0x1016, 0 → Consumer Heartbeat Time - 最大子索引
0x1016, 1 → 第1个被监控节点 (u32)
格式:[节点ID 16位][超时时间 16位]
例如:0x00040064 表示:
监控节点4,超时时间100ms
0x1016, 2 → 第2个被监控节点
...
// 超时检测
if (nodeTimeout > configuredTime) {
// 触发 Emergency:节点4心跳超时
CO_errorReport(co->em, CO_EM_HEARTBEAT_CONSUMER, ...);
}
3.7 LSS (Layer Setting Services) - 层设置服务
功能
LSS 用于动态配置节点 ID 和波特率,无需预先配置即可接入网络。
LSS 操作
c
// LSS 地址(唯一标识设备)
typedef struct {
uint32_t vendorID; // 厂商ID
uint32_t productCode; // 产品代码
uint32_t revisionNumber; // 版本号
uint32_t serialNumber; // 序列号
} CO_LSS_address_t;
// LSS 命令(通过特殊 COB-ID 通信)
LSS Master→Slave: COB-ID = 0x7E5
LSS Slave→Master: COB-ID = 0x7E4
常用命令:
Switch State Global → 全局切换 LSS 状态
Configure Node-ID → 配置节点 ID
Configure Bit Timing → 配置波特率
Activate Bit Timing → 激活新波特率
Store Configuration → 存储配置到非易失性存储
Inquire LSS Address → 查询设备 LSS 地址
LSS 配置流程
Master Slave(未配置,NodeID=0xFF)
│ │
│ 1. Switch State Global (Config) │
├──────────────────────────────────────>│ 进入配置模式
│ │
│ 2. LSS Identify (查询设备) │
├──────────────────────────────────────>│
│ VendorID = 0x12345678 │
│ │
│ 3. LSS Response │
│<──────────────────────────────────────┤ 匹配该地址的设备响应
│ │
│ 4. Configure Node-ID = 0x04 │
├──────────────────────────────────────>│
│ │
│ 5. Store Configuration │
├──────────────────────────────────────>│ 保存到 EEPROM
│ │
│ 6. Switch State Global (Waiting) │
├──────────────────────────────────────>│ 退出配置模式
│ │
│ 7. 节点复位,使用新ID │
LSS 在 CANopenLinux 中的实现
c
// 初始化 LSS
CO_LSS_address_t lssAddress = {
.vendorID = 0x12345678,
.productCode = 0x9ABCDEF0,
.revisionNumber = 0x00010001,
.serialNumber = 0x00000042
};
CO_LSSinit(CO, &lssAddress,
&mlStorage.pendingNodeId,
&mlStorage.pendingBitRate);
// 如果 pendingNodeId == 0xFF,则等待 LSS 配置
if (CO->nodeIdUnconfigured) {
// 只能响应 LSS 命令,不能进入 Operational
}
四、节点状态与生命周期
4.1 启动流程
1. 上电
↓
2. Initialization (初始化)
- 加载对象字典
- 读取存储的配置
- 初始化硬件
↓
3. 发送 Boot-up 消息
Heartbeat: [0x700+NodeID, 0x00]
↓
4. Pre-operational (预操作态)
- 可接受 SDO 配置
- 不能发送/接收 PDO
↓
5. NMT Start 命令 或 自动进入
↓
6. Operational (操作态)
- 正常通信(SDO + PDO)
4.2 通信复位循环
c
while (reset != CO_RESET_APP && CO_endProgram == 0) {
// 步骤1: 初始化 LSS
CO_LSSinit(CO, ...);
// 步骤2: 初始化 CANopen 核心
CO_CANopenInit(CO, ...);
// 步骤3: 初始化 PDO
CO_CANopenInitPDO(CO, ...);
// 步骤4: 启动 CAN
CO_CANsetNormalMode(CO->CANmodule);
// 步骤5: 主循环
while (reset == CO_RESET_NOT && CO_endProgram == 0) {
CO_epoll_wait(&epMain);
CO_epoll_processMain(&epMain, CO, GATEWAY_ENABLE, &reset);
// reset 可能变为:
// - CO_RESET_COMM:通信复位(重新初始化)
// - CO_RESET_APP:应用复位(程序退出)
}
}
4.3 错误处理
c
// 错误寄存器(0x1001)
0x1001, 0 → Error Register (u8)
位定义:
Bit 0: 通用错误
Bit 1: 电流错误
Bit 2: 电压错误
Bit 3: 温度错误
Bit 4: 通信错误
Bit 5: 设备配置文件特定
Bit 6: 保留
Bit 7: 厂商特定
// 错误处理流程
if (error_detected) {
// 1. 设置错误寄存器
CO->em->errorRegister |= CO_ERR_REG_TEMPERATURE;
// 2. 发送 Emergency
CO_errorReport(CO->em, CO_EM_TEMPERATURE_HIGH, ...);
// 3. 根据 NMT 配置决定状态转换
if (NMT_CONTROL & CO_NMT_ERR_ON_ERR_REG) {
// 自动进入 Pre-operational
}
}
五、CANopen 协议栈架构
5.1 CANopenNode 架构
┌─────────────────────────────────────────────────────┐
│ 应用层 (Application) │
│ app_programStart() / app_communicationReset() │
│ app_programAsync() / app_programRt() │
├─────────────────────────────────────────────────────┤
│ CANopenNode 协议层 │
│ ┌────────────┬──────────┬──────────┬─────────┐ │
│ │ NMT │ SDO │ PDO │ SYNC │ │
│ │ 0x000/0x700│ 0x580/600│ 0x180-5FF│ 0x080 │ │
│ ├────────────┼──────────┼──────────┼─────────┤ │
│ │ Emergency │ Heartbeat│ LSS │ TIME │ │
│ │ 0x080-0xFF │ 0x700-7FF│ 0x7E4/5 │ 0x100 │ │
│ └────────────┴──────────┴──────────┴─────────┘ │
├─────────────────────────────────────────────────────┤
│ 对象字典 (Object Dictionary) │
│ 0x1000-0x1FFF: 通信参数 │
│ 0x6000-0x9FFF: 应用参数 │
├─────────────────────────────────────────────────────┤
│ CAN 驱动层 (CO_driver) │
│ - 发送/接收 CAN 帧 │
│ - CAN 过滤器 │
│ - 中断/轮询处理 │
├─────────────────────────────────────────────────────┤
│ 硬件抽象层 (Linux SocketCAN) │
│ - socket()、bind()、read()、write() │
└─────────────────────────────────────────────────────┘
5.2 COB-ID 分配表
| COB-ID 范围 | 功能 | 优先级 | 说明 |
|---|---|---|---|
| 0x000 | NMT | 最高 | 网络管理 |
| 0x001 | SYNC | 最高 | 同步对象 |
| 0x080 | Emergency | 最高 | 紧急消息 |
| 0x081-0x0FF | Emergency | 最高 | 节点1-127紧急消息 |
| 0x100 | TIME | 高 | 时间戳 |
| 0x180-0x1FF | TPDO1 | 高 | 节点1-127 TPDO1 |
| 0x200-0x27F | RPDO1 | 高 | 节点1-127 RPDO1 |
| 0x280-0x2FF | TPDO2 | 中 | 节点1-127 TPDO2 |
| 0x300-0x37F | RPDO2 | 中 | 节点1-127 RPDO2 |
| 0x380-0x3FF | TPDO3 | 中 | 节点1-127 TPDO3 |
| 0x400-0x47F | RPDO3 | 中 | 节点1-127 RPDO3 |
| 0x480-0x4FF | TPDO4 | 低 | 节点1-127 TPDO4 |
| 0x500-0x57F | RPDO4 | 低 | 节点1-127 RPDO4 |
| 0x580-0x5FF | SDO Response (TX) | 低 | 节点1-127 SDO响应 |
| 0x600-0x67F | SDO Request (RX) | 低 | 节点1-127 SDO请求 |
| 0x700-0x7FF | Heartbeat | 最低 | 节点1-127心跳/Boot-up |
5.3 数据流示例
温度传感器节点(Node 4)向控制器(Node 1)发送数据
1. 配置阶段(Pre-operational)
控制器 → Node4 SDO请求:配置TPDO1映射
[0x604] 23 00 1A 01 00 10 01 60 (写入0x1A00,1=0x60010110)
Node4 → 控制器 SDO响应:确认
[0x584] 60 00 1A 01 00 00 00 00
2. 运行阶段(Operational)
Node4 实时线程(1ms):
- 读取温度传感器:25.5°C
- 更新 OD[0x6001,1] = 255 (25.5°C * 10)
- TPDO1 自动触发
Node4 → CAN总线:发送 TPDO1
[0x184] FF 00 (COB-ID=0x180+4, 数据=255)
控制器接收:
- CAN 驱动接收帧
- RPDO1 处理器解析
- 自动更新 OD[0x3001,1] = 255
- 应用层读取:温度 = 25.5°C
3. 错误处理
如果温度 > 80°C:
- Node4 发送 Emergency
[0x084] 10 42 04 00 F4 01 00 00
│ │ │ └─ 数据:500 (50.0°C)
│ │ └─ 错误寄存器:0x04(温度错误)
└────└─ 错误码:0x4210(温度过高)
六、实际应用场景
6.1 工业自动化场景
场景:传送带控制系统
网络拓扑:
PLC (Node 1) ←→ CAN Bus ←→ 变频器 (Node 2)
↕
传感器节点 (Node 3-10)
通信配置:
1. SYNC 周期:1ms
2. Node 3-10 TPDO1 (异步):传感器数据
3. Node 2 RPDO1 (SYNC 1):速度命令
4. Heartbeat:所有节点 1000ms
数据流:
- 传感器节点检测物体位置 → TPDO → PLC
- PLC 计算速度 → RPDO → 变频器
- 变频器反馈实际速度 → TPDO → PLC
- 所有节点定期发送 Heartbeat
故障处理:
- 传感器故障:Emergency + Heartbeat 超时
- PLC 自动切换到安全模式
- 变频器停止运行
6.2 运动控制场景
场景:多轴协调运动
网络拓扑:
运动控制器 (Node 1) ←→ CAN Bus ←→ 伺服驱动器 (Node 10-13)
↕
I/O 模块 (Node 20)
通信配置:
1. SYNC 周期:250us(4kHz)
2. 所有驱动器 RPDO1 (SYNC 1):位置/速度命令
3. 所有驱动器 TPDO1 (SYNC 1):实际位置/速度
4. 使用 CiA402 设备配置文件
PDO 映射(驱动器 Node 10):
RPDO1 (0x1600):
- 控制字 (0x6040,0) 16位
- 目标位置 (0x607A,0) 32位
TPDO1 (0x1A00):
- 状态字 (0x6041,0) 16位
- 实际位置 (0x6064,0) 32位
实时循环(250us):
1. SYNC 发送
2. 控制器计算→发送 RPDO1(所有轴)
3. 驱动器接收→执行→发送 TPDO1
4. 控制器接收反馈→下一周期
6.3 诊断与维护
bash
# 使用 cocomm 进行设备诊断
# 1. 查询设备标识
cocomm "4 read 0x1000 0 u32" # 设备类型
cocomm "4 read 0x1018 1 u32" # 厂商ID
cocomm "4 read 0x1018 2 u32" # 产品代码
cocomm "4 read 0x1018 4 u32" # 序列号
# 2. 读取状态信息
cocomm "4 read 0x1001 0 u8" # 错误寄存器
cocomm "4 read 0x1002 0 u32" # 制造商状态寄存器
# 3. 配置参数
cocomm "4 write 0x1017 0 u16 1000" # 设置心跳 1000ms
cocomm "4 write 0x1400 2 u8 254" # RPDO1 异步传输
# 4. 保存配置
cocomm "4 write 0x1010 1 vs save"
# 5. 复位设备
cocomm "4 reset comm" # 通信复位
cocomm "4 reset node" # 节点复位
# 6. NMT 控制
cocomm "4 start" # 进入 Operational
cocomm "4 stop" # 进入 Stopped
cocomm "4 preop" # 进入 Pre-operational
七、协议优势与限制
7.1 优势
- 标准化:CiA 标准保证互操作性
- 实时性:PDO 通信延迟可达微秒级
- 灵活性:支持多种拓扑和传输类型
- 可靠性:SDO 带确认,Emergency 异常通知
- 可扩展:设备配置文件覆盖多种应用
- 成本低:基于成熟的 CAN 总线
7.2 限制
- 带宽限制:CAN 2.0A 最高 1Mbps
- 节点数限制:最多 127 个节点
- 数据长度限制:标准帧最多 8 字节
- 距离限制:1Mbps 时最大 25m
- 实时性受影响:总线负载高时延迟增加
7.3 适用场景
适合:
- 工业自动化(PLC、传感器、执行器)
- 运动控制(伺服驱动器、步进电机)
- 医疗设备
- 能源管理系统
- 电梯控制
不适合:
- 需要超高带宽(如视频传输)
- 超大规模网络(>100 节点)
- 非确定性应用
八、总结
CANopen 协议通过标准化的对象字典 和通信对象,在 CAN 总线上实现了功能完善的分布式控制系统。其核心特点包括:
- 分层架构:清晰的协议分层便于实现和维护
- 多种通信方式:SDO 配置 + PDO 实时 + Emergency 异常
- 状态管理:NMT 状态机控制设备生命周期
- 灵活配置:支持静态配置和 LSS 动态配置
- 可靠通信:多重错误检测和恢复机制
CANopenLinux 实现展示了协议在实际系统中的应用,包括事件驱动架构、存储管理、网关接口等,为开发者提供了完整的参考实现。