CANopen 基础复习

一、CANopen 基础详解

CANopen 是基于 CAN 2.0 物理层/数据链路层标准化应用层协议,由 CiA(CAN in Automation)协会制定,是工业机器人、伺服驱动、IO 模块等工业设备的核心通信协议之一。


1. CANopen 与 CAN 的关系
层级 CAN CANopen
应用层 无(需自定义) 标准化(对象字典、PDO、SDO、NMT 等)
网络层 支持多主多从、节点管理
数据链路层 CAN 2.0A/B 复用 CAN 2.0A/B
物理层 CAN 差分总线 复用 CAN 差分总线

2. 核心概念
(1)对象字典(OD,Object Dictionary)

对象字典是 CANopen 的"心脏",是一个16 位索引 + 8 位子索引的二维表格,存储了设备的所有参数、状态、控制命令等:

  • 索引(Index) :16 位,标识对象的类别(如 0x1000=设备类型,0x6000=接收 PDO 映射)。
  • 子索引(Subindex) :8 位,标识对象的具体条目(如 0x1000:00=设备类型值,0x6000:01=第一个输入通道)。
  • 数据类型:支持 UNSIGNED8/16/32、INTEGER8/16/32、BOOLEAN、STRING 等标准化类型。

常用对象字典索引

索引 功能
0x1000 设备类型
0x1001 错误寄存器
0x1017 心跳生产者时间
0x1400~0x1403 RxPDO1~4 参数
0x1600~0x1603 RxPDO1~4 映射
0x1800~0x1803 TxPDO1~4 参数
0x1A00~0x1A03 TxPDO1~4 映射
0x6000~0x60FF 接收 PDO 数据(从站输入)
0x6200~0x62FF 发送 PDO 数据(从站输出)

(2)过程数据对象(PDO,Process Data Object)

PDO 用于传输周期性实时数据(如伺服位置、IO 状态),优先级最高,无需确认:

  • RxPDO:主站 → 从站(控制数据,如 LED 开关、伺服目标位置)。
  • TxPDO:从站 → 主站(反馈数据,如按钮状态、伺服当前位置)。
  • 同步 PDO:由 SYNC 帧触发,保证多从站同步传输。
  • 异步 PDO:由事件触发(如输入状态变化)。
  • PDO 映射:将对象字典中的条目"绑定"到 PDO 的数据域,无需通过 SDO 逐个读写,效率极高。

(3)服务数据对象(SDO,Service Data Object)

SDO 用于传输非周期性配置数据(如设置伺服参数、修改 PDO 映射),优先级低于 PDO,需要确认:

  • 客户端(Client):通常是主站,发起读写请求。
  • 服务器(Server):通常是从站,响应读写请求。
  • 加速传输:数据 ≤4 字节时,一次 CAN 帧完成传输。
  • 分段传输:数据 >4 字节时,分多个 CAN 帧传输。

(4)网络管理(NMT,Network Management)

NMT 用于管理从站的状态,主站通过 NMT 命令控制从站切换状态:

  • 状态机InitialisationPre-operationalOperationalStopped

    • Initialisation:从站上电,初始化对象字典。
    • Pre-operational:SDO 通信可用,PDO 通信不可用(配置阶段)。
    • Operational:SDO 和 PDO 通信均可用(正常工作阶段)。
    • Stopped:仅 NMT 通信可用(停止阶段)。
  • 常用 NMT 命令

    命令码 功能
    0x01 进入操作状态
    0x02 停止
    0x80 进入预操作状态
    0x81 复位节点
    0x82 复位通信

(5)同步对象(SYNC)

SYNC 是主站周期性发送的帧,用于触发同步 PDO 的传输,保证多从站的动作同步(如 6 轴机器人的关节同步):

  • COB-ID:0x080(标准帧)。
  • 周期:通常为 1ms、500μs、250μs。

(6)紧急对象(EMCY,Emergency)

EMCY 是从站发生错误时主动发送的帧,优先级很高:

  • COB-ID:0x080 + 节点ID(如节点 1 的 EMCY COB-ID 是 0x081)。
  • 内容:包含错误码、错误寄存器、厂商特定错误信息。

(7)心跳对象(Heartbeat)

Heartbeat 是从站周期性发送的帧,用于监控从站的状态(替代传统的 NMT 节点保护):

  • COB-ID:0x700 + 节点ID(如节点 1 的 Heartbeat COB-ID 是 0x701)。
  • 内容:包含从站的当前 NMT 状态。
  • 周期:由对象字典 0x1017(心跳生产者时间)配置。

3. COB-ID 分配

CANopen 使用 CAN 2.0A 标准帧(11 位 COB-ID),COB-ID 由功能码节点ID组成:

功能码 COB-ID 范围 功能
0x000 0x000 NMT 命令
0x080 0x080 SYNC
0x080+ID 0x081~0x0FF EMCY
0x180+ID 0x181~0x1FF TxPDO1
0x200+ID 0x201~0x27F RxPDO1
0x280+ID 0x281~0x2FF TxPDO2
0x300+ID 0x301~0x37F RxPDO2
0x580+ID 0x581~0x5FF SDO 服务器
0x600+ID 0x601~0x67F SDO 客户端
0x700+ID 0x701~0x77F Heartbeat

二、应用层实现:基于 CANfestival(开源 CANopen 协议栈)

CANfestival 是一个开源、轻量、跨平台的 CANopen 协议栈,支持主站/从站模式,底层可对接 SocketCAN,是入门和实际开发的首选。


步骤 1:环境准备(Linux 系统,如 RK3588 Ubuntu 22.04)
  1. 安装 SocketCAN(已学过,略)。

  2. 安装 CANfestival 依赖

    bash 复制代码
    sudo apt update
    sudo apt install git gcc make libxml2-dev libncurses5-dev -y
  3. 克隆 CANfestival 源码 (推荐 canfestival-ng 分支,更新活跃):

    bash 复制代码
    git clone https://github.com/CanFestival/canfestival-ng.git
    cd canfestival-ng
  4. 配置、编译、安装 CANfestival

    bash 复制代码
    ./configure --prefix=/usr/local --enable-socketcan --enable-master --enable-slave
    make -j$(nproc)
    sudo make install
    sudo ldconfig  # 更新库缓存

步骤 2:创建对象字典(OD)

我们用 CANfestival 自带的简单 IO 从站 OD 作为示例,无需手动创建,直接使用 examples/slave 目录下的 OD。


步骤 3:从站代码实现(简单 IO 从站)

从站功能:

  • 接收主站的 RxPDO(控制 1 个 LED,对象字典 0x6200:01)。
  • 发送主站的 TxPDO(反馈 1 个按钮状态,对象字典 0x6000:01)。
  • 周期性发送 Heartbeat(1 秒周期)。

保存代码为 slave.c

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "canfestival.h"
#include "objdict.h"  // 自动生成的对象字典头文件

// 从站节点 ID
#define NODE_ID 1

// 全局变量:模拟按钮状态(0=松开,1=按下)
static uint8_t button_state = 0;

// 步骤 1:PDO 回调函数(RxPDO 接收时触发)
void myRxPDO_callback(CO_Data *d, UNS32 id) {
    // 读取 RxPDO 映射的 LED 状态(0x6200:01)
    UNS8 led_state = *d->error_register;  // 示例:用错误寄存器模拟 LED 状态
    printf("Received RxPDO: LED state = %d\n", led_state);
}

// 步骤 2:Heartbeat 回调函数(主站心跳丢失时触发)
void myHeartbeat_callback(CO_Data *d, UNS8 nodeId) {
    printf("Heartbeat lost for node %d\n", nodeId);
}

// 步骤 3:初始化从站
int slave_init(const char *ifname) {
    // 3.1 初始化 CAN 接口(SocketCAN)
    if (CanIf_Init(ifname) != 0) {
        fprintf(stderr, "Failed to init CAN interface %s\n", ifname);
        return -1;
    }

    // 3.2 初始化 CANopen 从站
    SetODentry(OD_Data, 0x1017, 0, &(UNS16){1000}, sizeof(UNS16));  // 设置心跳周期 1000ms
    SetNodeId(OD_Data, NODE_ID);  // 设置从站节点 ID
    InitNodes(OD_Data);  // 初始化节点

    // 3.3 注册回调函数
    RegisterSetODentryCallBack(OD_Data, 0x6200, 0x01, myRxPDO_callback);  // RxPDO 回调
    SetHeartbeatErrorCallback(OD_Data, myHeartbeat_callback);  // 心跳错误回调

    // 3.4 启动从站(进入预操作状态)
    setState(OD_Data, NODE_ID, Initialisation);
    setState(OD_Data, NODE_ID, Pre_operational);
    printf("Slave started (Node ID: %d), waiting for master...\n", NODE_ID);

    return 0;
}

// 步骤 4:从站主循环
void slave_loop(void) {
    while (1) {
        // 4.1 处理 CAN 帧
        CanIf_Process();

        // 4.2 模拟按钮状态变化(每 2 秒切换一次)
        static int counter = 0;
        if (++counter >= 2000) {
            counter = 0;
            button_state = !button_state;
            printf("Button state changed: %d\n", button_state);
            // 更新 TxPDO 映射的按钮状态(0x6000:01)
            SetODentry(OD_Data, 0x6000, 0x01, &button_state, sizeof(button_state));
            // 发送 TxPDO
            sendPDOevent(OD_Data, 1);  // 发送 TxPDO1
        }

        // 4.3 等待 1ms
        usleep(1000);
    }
}

int main(int argc, char *argv[]) {
    const char *ifname = (argc > 1) ? argv[1] : "vcan0";

    // 初始化从站
    if (slave_init(ifname) < 0) {
        return EXIT_FAILURE;
    }

    // 进入主循环
    slave_loop();

    return EXIT_SUCCESS;
}

步骤 4:主站代码实现(简单 IO 主站)

主站功能:

  • 扫描从站 Heartbeat。
  • 配置从站进入操作状态。
  • 周期性发送 RxPDO(控制 LED,每 1 秒切换一次)。
  • 接收从站的 TxPDO(读取按钮状态)。

保存代码为 master.c

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "canfestival.h"
#include "objdict.h"

// 主站节点 ID(CANopen 主站通常无节点 ID,此处用 0)
#define MASTER_NODE_ID 0
// 从站节点 ID
#define SLAVE_NODE_ID 1

// 全局变量:模拟 LED 状态
static uint8_t led_state = 0;

// 步骤 1:PDO 回调函数(TxPDO 接收时触发)
void myTxPDO_callback(CO_Data *d, UNS32 id) {
    // 读取 TxPDO 映射的按钮状态(0x6000:01)
    UNS8 button_state = *d->error_register;  // 示例:用错误寄存器模拟按钮状态
    printf("Received TxPDO: Button state = %d\n", button_state);
}

// 步骤 2:Heartbeat 回调函数(从站心跳到达时触发)
void myHeartbeat_callback(CO_Data *d, UNS8 nodeId, UNS8 state) {
    printf("Heartbeat from node %d, state = %d\n", nodeId, state);
    // 如果从站进入预操作状态,配置它进入操作状态
    if (nodeId == SLAVE_NODE_ID && state == Pre_operational) {
        printf("Sending NMT command: Enter Operational\n");
        masterSendNMTstateChange(d, SLAVE_NODE_ID, NMT_ENTER_OPERATIONAL);
    }
}

// 步骤 3:初始化主站
int master_init(const char *ifname) {
    // 3.1 初始化 CAN 接口(SocketCAN)
    if (CanIf_Init(ifname) != 0) {
        fprintf(stderr, "Failed to init CAN interface %s\n", ifname);
        return -1;
    }

    // 3.2 初始化 CANopen 主站
    SetNodeId(OD_Data, MASTER_NODE_ID);
    InitNodes(OD_Data);

    // 3.3 注册回调函数
    RegisterSetODentryCallBack(OD_Data, 0x6000, 0x01, myTxPDO_callback);  // TxPDO 回调
    SetHeartbeatCallback(OD_Data, myHeartbeat_callback);  // 心跳回调

    printf("Master started, waiting for slave...\n");
    return 0;
}

// 步骤 4:主站主循环
void master_loop(void) {
    while (1) {
        // 4.1 处理 CAN 帧
        CanIf_Process();

        // 4.2 周期性发送 RxPDO(每 1 秒切换 LED 状态)
        static int counter = 0;
        if (++counter >= 1000) {
            counter = 0;
            led_state = !led_state;
            printf("Sending RxPDO: LED state = %d\n", led_state);
            // 更新 RxPDO 映射的 LED 状态(0x6200:01)
            SetODentry(OD_Data, 0x6200, 0x01, &led_state, sizeof(led_state));
            // 发送 RxPDO
            sendPDOevent(OD_Data, 1);  // 发送 RxPDO1
        }

        // 4.3 等待 1ms
        usleep(1000);
    }
}

int main(int argc, char *argv[]) {
    const char *ifname = (argc > 1) ? argv[1] : "vcan0";

    // 初始化主站
    if (master_init(ifname) < 0) {
        return EXIT_FAILURE;
    }

    // 进入主循环
    master_loop();

    return EXIT_SUCCESS;
}

步骤 5:编译与运行
  1. 准备对象字典文件

    复制 CANfestival 自带的示例 OD 文件到当前目录:

    bash 复制代码
    cp canfestival-ng/examples/slave/objdict.h .
    cp canfestival-ng/examples/slave/objdict.c .
  2. 编译从站

    bash 复制代码
    gcc slave.c objdict.c -o slave -lcanfestival -lcanfestival_unix
  3. 编译主站

    bash 复制代码
    gcc master.c objdict.c -o master -lcanfestival -lcanfestival_unix
  4. 启动虚拟 CAN 设备

    bash 复制代码
    sudo ip link add dev vcan0 type vcan
    sudo ip link set up vcan0
  5. 运行从站(终端 1):

    bash 复制代码
    ./slave vcan0
  6. 运行主站(终端 2):

    bash 复制代码
    ./master vcan0
  7. 预期输出

    • 从站终端:

      复制代码
      Slave started (Node ID: 1), waiting for master...
      Button state changed: 1
      Received RxPDO: LED state = 1
      Button state changed: 0
      Received RxPDO: LED state = 0
      ...
    • 主站终端:

      复制代码
      Master started, waiting for slave...
      Heartbeat from node 1, state = 4
      Sending NMT command: Enter Operational
      Sending RxPDO: LED state = 1
      Received TxPDO: Button state = 1
      Sending RxPDO: LED state = 0
      Received TxPDO: Button state = 0
      ...

步骤 6:调试方法
  1. 用 candump 查看 CAN 帧(终端 3):

    bash 复制代码
    candump vcan0

    可以看到 NMT、Heartbeat、PDO 等帧。

  2. 用 canopen-tools 调试

    安装 canopen-tools

    bash 复制代码
    sudo apt install canopen-tools -y

    cocomm 读写从站的对象字典:

    bash 复制代码
    cocomm vcan0 1
    > read 0x1000 0  # 读取设备类型
    > write 0x6200 1 1  # 写入 LED 状态

三、注意事项与进阶建议

  1. 对象字典生成

    实际开发中,用 CANfestival 自带的 objdictedit 图形工具生成对象字典(需安装 libgtk2.0-dev):

    bash 复制代码
    sudo apt install libgtk2.0-dev -y
    cd canfestival-ng/objdictedit
    make
    ./objdictedit
  2. 动态 PDO 映射

    示例中用的是静态 PDO 映射,实际开发中可通过 SDO 动态修改从站的 PDO 映射(修改 0x1600/0x1A00 索引)。

  3. CiA 402 行规

    控制伺服驱动器时,需遵循 CiA 402 行规 (CANopen 驱动器和运动控制设备的标准),对象字典索引为 0x6000~0x6FFF

  4. 实时性优化

    工业现场使用时,需用 RT-Preempt 实时内核,降低周期抖动。


RK3588* 的 CANopen 驱动层实现

针对 RK3588 的 CANopen 驱动层实现,我们需要先明确一个核心概念:CANopen 是基于 CAN 2.0/FD 物理层/数据链路层的标准化应用层协议,没有专门的"CANopen 硬件驱动",完全复用 RK3588 的 CAN/CAN FD 硬件驱动

其驱动层架构分为 三层,从上到下依次为:

  1. CANopen 协议栈(用户态/内核态可选);
  2. SocketCAN 核心适配层(Linux 内核原生,将 CAN 设备抽象为网络接口);
  3. RK3588 CAN/CAN FD 硬件驱动rockchip_can.c,之前已深度讲解)。

一、RK3588 CANopen 硬件基础

复用 RK3588 的 2 个 CAN 控制器(CAN0/CAN1),基于 Cadence CAN FD IP 核,完全满足 CANopen 要求:

CANopen 要求 RK3588 硬件支持
标准帧(11 位 COB-ID) ✅ 完全支持
扩展帧(29 位 COB-ID,可选) ✅ 完全支持
工业常用波特率(125kbps/250kbps/500kbps/1Mbps) ✅ 可灵活配置
硬件过滤器(降低 CPU 负载) ✅ 支持 32 个标准帧/16 个扩展帧过滤器
错误检测与处理 ✅ 支持主动错误、被动错误、总线关闭
终端电阻(可选内置) ✅ 部分板卡支持 GPIO 控制内置终端电阻

二、CANopen 驱动层整体架构

我们重点对比 用户态协议栈 (入门/通用首选)和 内核态协议栈(工业级实时首选)的差异:

层级 用户态方案(CANfestival/SOEM CANopen) 内核态方案(IgH CANopen Master)
实时性 中等(受用户态/内核态切换影响,抖动 <1ms) 极高(直接操作硬件,抖动 <10μs)
复杂度 低(开发门槛低,调试方便) 高(需内核开发知识,调试复杂)
兼容性 好(可对接任何 SocketCAN 硬件) 中(需绑定特定 CAN 硬件驱动)
适用场景 中小型项目、协作机器人、教学实验 工业机器人、多轴运动控制、高可靠场景

三、RK3588 CAN/CAN FD 硬件驱动回顾与 CANopen 优化

之前已深度讲解 rockchip_can.c,这里仅回顾 CANopen 专用的关键优化

1. 关闭 CAN FD(仅用 CANopen 2.0 时)

CANopen 2.0 仅支持 8 字节数据帧,关闭 CAN FD 可降低硬件开销和延迟:

c 复制代码
// 在 rockchip_can_probe 中(或通过设备树配置)
priv->can.ctrlmode &= ~CAN_CTRLMODE_FD;  // 禁用 CAN FD

2. 配置工业常用波特率

通过设备树或 ip link 配置,CANopen 工业标准波特率为 500kbps (通用)或 1Mbps(高速):

bash 复制代码
# 设备树配置(推荐,永久生效)
&can0 {
    status = "okay";
    assigned-clocks = <&cru SCLK_CAN0>;
    assigned-clock-rates = <200000000>;  // CAN 时钟源设为 200MHz
    // 波特率 500kbps(由 CAN 核心根据时钟源自动计算)
};

# 临时配置(调试用)
sudo ip link set can0 type can bitrate 500000
sudo ip link set up can0

3. 配置硬件过滤器(降低 CPU 负载)

CANopen 从站仅需接收 本节点的 COB-ID (如节点 1 需接收 0x0000x0800x2010x5810x701 等),配置硬件过滤器可避免无关帧触发中断:

c 复制代码
// 在 rockchip_can_open 中(或通过 SocketCAN 用户态配置)
struct can_filter filter[] = {
    { .can_id = 0x000, .can_mask = CAN_SFF_MASK },  // NMT
    { .can_id = 0x080, .can_mask = CAN_SFF_MASK },  // SYNC
    { .can_id = 0x200 + NODE_ID, .can_mask = CAN_SFF_MASK },  // RxPDO1
    { .can_id = 0x580 + NODE_ID, .can_mask = CAN_SFF_MASK },  // SDO 服务器
    { .can_id = 0x700 + NODE_ID, .can_mask = CAN_SFF_MASK },  // Heartbeat(主站监控从站时需过滤)
};
rockchip_can_set_filter(priv, filter, ARRAY_SIZE(filter));

四、SocketCAN 核心层适配 CANopen

SocketCAN 是 Linux 内核原生的 CAN 适配层,将 CAN 设备抽象为网络接口(如 can0),应用层通过标准 Socket API 即可通信,是 CANopen 用户态协议栈的基础。

1. CANopen 帧格式与 SocketCAN 的映射

SocketCAN 的 struct can_frame 完全兼容 CANopen 帧格式:

CANopen 帧 SocketCAN struct can_frame 字段
COB-ID(11 位) can_id(低 11 位,无需加 CAN_EFF_FLAG
数据长度(DLC,0~8) can_dlc
数据域(0~8 字节) data[0]~data[7]

2. SocketCAN 用户态配置 CANopen 过滤器(推荐,灵活)

无需修改内核驱动,直接在用户态通过 setsockopt 配置:

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <linux/can.h>
#include <linux/can/raw.h>

#define NODE_ID 1
#define NIC_NAME "can0"

int main() {
    int sock;
    struct ifreq ifr;
    struct sockaddr_can addr;
    struct can_filter filter[] = {
        { .can_id = 0x000, .can_mask = CAN_SFF_MASK },
        { .can_id = 0x080, .can_mask = CAN_SFF_MASK },
        { .can_id = 0x200 + NODE_ID, .can_mask = CAN_SFF_MASK },
        { .can_id = 0x580 + NODE_ID, .can_mask = CAN_SFF_MASK },
    };

    // 1. 创建 SocketCAN 套接字
    sock = socket(PF_CAN, SOCK_RAW, CAN_RAW);
    if (sock < 0) { perror("socket"); return EXIT_FAILURE; }

    // 2. 绑定到 can0 接口
    strncpy(ifr.ifr_name, NIC_NAME, IFNAMSIZ - 1);
    ioctl(sock, SIOCGIFINDEX, &ifr);
    memset(&addr, 0, sizeof(addr));
    addr.can_family = AF_CAN;
    addr.can_ifindex = ifr.ifr_ifindex;
    bind(sock, (struct sockaddr *)&addr, sizeof(addr));

    // 3. 配置 CANopen 过滤器
    setsockopt(sock, SOL_CAN_RAW, CAN_RAW_FILTER, filter, sizeof(filter));

    // 4. 配置回环(可选,本地调试用)
    int loopback = 1;
    setsockopt(sock, SOL_CAN_RAW, CAN_RAW_LOOPBACK, &loopback, sizeof(loopback));

    // 5. 通信循环(略,参考之前的 CAN 应用层代码)
    while (1) {
        struct can_frame frame;
        read(sock, &frame, sizeof(frame));
        // 解析 CANopen 帧(NMT/PDO/SDO/Heartbeat)
    }

    close(sock);
    return EXIT_SUCCESS;
}

五、工业级内核态 CANopen 方案:IgH CANopen Master

如果需要 微秒级实时性 ,可以使用 IgH CANopen Master(和之前学的 IgH EtherCAT Master 同属 IgH 协议栈,复用知识)。

1. RK3588 上编译安装 IgH CANopen Master

步骤 1:准备内核源码

需要与 RK3588 运行的内核版本一致的源码(如 Linux 5.10),并配置好 RT-Preempt 实时补丁(之前已讲解)。

步骤 2:克隆 IgH 源码
bash 复制代码
git clone https://gitlab.com/etherlab.org/ethercat.git
cd ethercat
git checkout stable-1.5  # 选择稳定版本
步骤 3:配置 IgH(启用 CANopen Master)
bash 复制代码
./bootstrap
./configure \
  --with-linux-dir=/path/to/rk3588-kernel-source \
  --enable-canopen=yes \  // 启用 CANopen Master
  --enable-generic=no \   // 禁用 EtherCAT 通用网卡驱动
  --enable-socketcan=yes \  // 启用 SocketCAN 绑定(灵活对接 RK3588 CAN)
  --host=aarch64-linux-gnu  // 交叉编译目标
步骤 4:编译安装
bash 复制代码
make -j$(nproc)
sudo make install
sudo depmod -a

2. 绑定 RK3588 CAN 硬件

bash 复制代码
# 1. 加载 IgH 模块
sudo modprobe ec_master
sudo modprobe ec_socketcan

# 2. 绑定 can0 接口
sudo ethercatctl can0 attach

# 3. 启动 CANopen Master
sudo ethercatctl start

# 4. 扫描 CANopen 从站
sudo ethercat slaves

六、RK3588 CANopen 设备树配置

复用之前的 CAN 设备树,仅需添加 CANopen 常用的配置

dts 复制代码
&can0 {
    status = "okay";
    pinctrl-names = "default";
    pinctrl-0 = <&can0m0_pins>;
    assigned-clocks = <&cru SCLK_CAN0>;
    assigned-clock-rates = <200000000>;  // CAN 时钟源设为 200MHz(方便计算 500kbps/1Mbps)
    // 可选:配置内置终端电阻(板卡支持时)
    rockchip,termination-gpios = <&gpio1 RK_PC8 GPIO_ACTIVE_HIGH>;
    rockchip,termination-enable;
};

七、关键调试方法

1. CAN 硬件调试(复用之前的方法)

bash 复制代码
# 查看 CAN 接口状态
ip link show can0
ip -details link show can0

# 监听 CAN 帧
candump can0

# 发送测试 CANopen 帧(如 NMT 命令:节点 1 进入操作状态)
cansend can0 000#01.01

2. CANopen 专用调试

bash 复制代码
# 安装 canopen-tools
sudo apt install canopen-tools -y

# 读写从站对象字典
cocomm can0 1
> read 0x1000 0  # 读取设备类型
> write 0x6200 1 1  # 写入 LED 状态

# 监控 CANopen 总线
canopen-monitor can0

八、总结

RK3588 的 CANopen 驱动层核心在于:

  1. 复用 CAN/CAN FD 硬件驱动:针对 CANopen 优化波特率、过滤器、中断优先级;
  2. SocketCAN 核心适配层:将 CAN 设备抽象为网络接口,用户态协议栈通过标准 Socket API 通信;
  3. 协议栈选择
    • 入门/通用:用户态 CANfestival/SOEM CANopen;
    • 工业级实时:内核态 IgH CANopen Master。
相关推荐
nainaire2 小时前
速通LeetCode hot100——(1~9 哈希,双指针,滑动窗口)
c++·笔记·算法·leetcode
2501_924952692 小时前
分布式缓存一致性
开发语言·c++·算法
normanhere2 小时前
H3C无线调优案例
网络
东芝、铠侠总代136100683932 小时前
从混合存储架构看SSD与HDD的互补性:技术特性决定应用场景
服务器·架构·ssd·hdd
jinanwuhuaguo2 小时前
《OpenClaw v2026.3.24-beta.1 深度技术分析报告》
运维·服务器·人工智能·openclaw
炸膛坦客2 小时前
单片机/C/C++八股:(二十一)include <> 和 include ““ 的区别
c语言·c++
李昊哲小课2 小时前
PyMySQL完整教程
服务器·数据库·python·pymysql
互联网科技看点2 小时前
2025-2026年研发管理软件推荐:产品研发全流程一体化靠谱解决方案评测
服务器·数据库·人工智能
饿了吃洗衣凝珠2 小时前
hcip 作业
网络