一、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 命令控制从站切换状态:
-
状态机 :
Initialisation→Pre-operational→Operational→Stopped。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)
-
安装 SocketCAN(已学过,略)。
-
安装 CANfestival 依赖 :
bashsudo apt update sudo apt install git gcc make libxml2-dev libncurses5-dev -y -
克隆 CANfestival 源码 (推荐
canfestival-ng分支,更新活跃):bashgit clone https://github.com/CanFestival/canfestival-ng.git cd canfestival-ng -
配置、编译、安装 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:编译与运行
-
准备对象字典文件 :
复制 CANfestival 自带的示例 OD 文件到当前目录:
bashcp canfestival-ng/examples/slave/objdict.h . cp canfestival-ng/examples/slave/objdict.c . -
编译从站:
bashgcc slave.c objdict.c -o slave -lcanfestival -lcanfestival_unix -
编译主站:
bashgcc master.c objdict.c -o master -lcanfestival -lcanfestival_unix -
启动虚拟 CAN 设备:
bashsudo ip link add dev vcan0 type vcan sudo ip link set up vcan0 -
运行从站(终端 1):
bash./slave vcan0 -
运行主站(终端 2):
bash./master vcan0 -
预期输出:
-
从站终端:
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:调试方法
-
用 candump 查看 CAN 帧(终端 3):
bashcandump vcan0可以看到 NMT、Heartbeat、PDO 等帧。
-
用 canopen-tools 调试 :
安装
canopen-tools:bashsudo apt install canopen-tools -y用
cocomm读写从站的对象字典:bashcocomm vcan0 1 > read 0x1000 0 # 读取设备类型 > write 0x6200 1 1 # 写入 LED 状态
三、注意事项与进阶建议
-
对象字典生成 :
实际开发中,用 CANfestival 自带的
objdictedit图形工具生成对象字典(需安装libgtk2.0-dev):bashsudo apt install libgtk2.0-dev -y cd canfestival-ng/objdictedit make ./objdictedit -
动态 PDO 映射 :
示例中用的是静态 PDO 映射,实际开发中可通过 SDO 动态修改从站的 PDO 映射(修改
0x1600/0x1A00索引)。 -
CiA 402 行规 :
控制伺服驱动器时,需遵循 CiA 402 行规 (CANopen 驱动器和运动控制设备的标准),对象字典索引为
0x6000~0x6FFF。 -
实时性优化 :
工业现场使用时,需用 RT-Preempt 实时内核,降低周期抖动。
RK3588* 的 CANopen 驱动层实现
针对 RK3588 的 CANopen 驱动层实现,我们需要先明确一个核心概念:CANopen 是基于 CAN 2.0/FD 物理层/数据链路层的标准化应用层协议,没有专门的"CANopen 硬件驱动",完全复用 RK3588 的 CAN/CAN FD 硬件驱动。
其驱动层架构分为 三层,从上到下依次为:
- CANopen 协议栈(用户态/内核态可选);
- SocketCAN 核心适配层(Linux 内核原生,将 CAN 设备抽象为网络接口);
- 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 需接收 0x000、0x080、0x201、0x581、0x701 等),配置硬件过滤器可避免无关帧触发中断:
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 驱动层核心在于:
- 复用 CAN/CAN FD 硬件驱动:针对 CANopen 优化波特率、过滤器、中断优先级;
- SocketCAN 核心适配层:将 CAN 设备抽象为网络接口,用户态协议栈通过标准 Socket API 通信;
- 协议栈选择 :
- 入门/通用:用户态 CANfestival/SOEM CANopen;
- 工业级实时:内核态 IgH CANopen Master。