CANopen 协议详解

一、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 优势

  1. 标准化:CiA 标准保证互操作性
  2. 实时性:PDO 通信延迟可达微秒级
  3. 灵活性:支持多种拓扑和传输类型
  4. 可靠性:SDO 带确认,Emergency 异常通知
  5. 可扩展:设备配置文件覆盖多种应用
  6. 成本低:基于成熟的 CAN 总线

7.2 限制

  1. 带宽限制:CAN 2.0A 最高 1Mbps
  2. 节点数限制:最多 127 个节点
  3. 数据长度限制:标准帧最多 8 字节
  4. 距离限制:1Mbps 时最大 25m
  5. 实时性受影响:总线负载高时延迟增加

7.3 适用场景

适合

  • 工业自动化(PLC、传感器、执行器)
  • 运动控制(伺服驱动器、步进电机)
  • 医疗设备
  • 能源管理系统
  • 电梯控制

不适合

  • 需要超高带宽(如视频传输)
  • 超大规模网络(>100 节点)
  • 非确定性应用

八、总结

CANopen 协议通过标准化的对象字典通信对象,在 CAN 总线上实现了功能完善的分布式控制系统。其核心特点包括:

  1. 分层架构:清晰的协议分层便于实现和维护
  2. 多种通信方式:SDO 配置 + PDO 实时 + Emergency 异常
  3. 状态管理:NMT 状态机控制设备生命周期
  4. 灵活配置:支持静态配置和 LSS 动态配置
  5. 可靠通信:多重错误检测和恢复机制

CANopenLinux 实现展示了协议在实际系统中的应用,包括事件驱动架构、存储管理、网关接口等,为开发者提供了完整的参考实现。

相关推荐
Francek Chen2 小时前
【大数据基础】实验1:熟悉常用的Linux操作和Hadoop操作
大数据·linux·hadoop·hdfs
南林yan2 小时前
通过lspci和lsusb理解PCI设备和USB设备
linux
虾..2 小时前
Linux 多线程,线程分离
linux·运维·服务器
hampeter2 小时前
【填坑指南】Trae/VS Code 远程连接 Ubuntu,终端总是自动激活特定的 Conda 环境?三招教你彻底解决!
linux·ubuntu·conda·trae
驱动探索者2 小时前
linux genpool 学习
java·linux·学习
历程里程碑2 小时前
21:重谈重定义理解一切皆“文件“及缓存区
linux·c语言·开发语言·数据结构·c++·算法·缓存
AZ996ZA2 小时前
自学linux的二十天【DNS 服务从入门到实战】
linux·运维·服务器
wdfk_prog2 小时前
[Linux]学习笔记系列 -- [drivers][mmc]mmc_sd
linux·笔记·学习
qinyia2 小时前
**使用AI助手在智慧运维中快速定位并修复服务异常:以Nginx配置错误导致502错误为例**
linux·运维·服务器·数据库·mysql·nginx·自动化