Ros与下位机can通信问题

TX 一直 busy + frames=0,基本可以断定问题不在 ROS 层,而是 CAN 总线上没有对端应答:帧发出去没人 ACK → 控制器不停重发 → 发送队列塞满 → 写入返回 ENOBUFS(就是你看到的 TX busy),同时下位机的数据一帧也收不进来。odom 恒为 0,后面 controller_server「Failed to make progress」和 spin 超时都是连带结果,先修总线。

最常见三个原因:波特率不匹配、CAN_H/CAN_L 接反或接触不良、两端缺 120Ω 终端电阻(或没共地)。

1)看接口状态和错误计数

ip -details -statistics link show can0

重点看 state:正常是 ERROR-ACTIVE;出现 ERROR-WARNING / ERROR-PASSIVE / BUS-OFF 就说明总线上收不到 ACK,方向就是物理层或波特率。

2)按下位机实际波特率重新初始化,并打开 bus-off 自动恢复

sudo ip link set can0 down
sudo ip link set can0 type can bitrate 500000 restart-ms 100
sudo ip link set can0 up

bitrate 必须和下位机一致(常见 500k / 250k / 1M),对不上就是现在这个现象。顺便确认接口没用错(can0 还是 can1)。

3)抛开 ROS 直接验总线

candump can0 &
cansend can0 123#DEADBEEF

下位机在正常广播的话 candump 应该能直接看到帧;一帧都没有就查物理层——断电用万用表量 CAN_H 与 CAN_L 之间电阻,约 60Ω 表示两端都端接了,120Ω 只有一端,无穷大说明线没通。

想确认板端驱动和 CAN 控制器没问题,可以开自环测一次:

sudo ip link set can0 down
sudo ip link set can0 type can bitrate 500000 loopback on
sudo ip link set can0 up

自环下能收到自己发的帧,就说明 RDK 侧没问题,故障在下位机或线束(测完记得 loopback off 恢复)。

方便的话补两个信息:下位机侧 CAN 波特率是多少,以及第 1 步 state 显示的值,可以进一步收敛。

自研下位机的 CAN 协议对接属于私有硬件 bring-up,如果需要更深入的适配支持,可以报名 DGP 开发者计划:https://developer.d-robotics.cc/dgp

从截图看,底盘节点一直是 frames=0、enabled=0,还在报 SocketCAN Tx busy;后面的导航超时更像连带结果。单凭这张图还不能断定是接线、波特率,还是底盘程序没有使能。

先停导航,让下位机正常上电、发送它原有的状态帧;板端执行 ip -details -statistics link show can0 和 candump can0(接口不是 can0 就换成实际名称)。若收不到状态帧,或出现 BUS-OFF、TX 错误持续增加,先核对两端波特率及 CAN/CAN FD 模式、H/L/GND 和终端电阻;原始帧正常后,再查底盘节点的 enabled 条件。麻烦贴一下板卡型号、接口名、这两条命令的输出和下位机波特率,我们再往下定位。

SocketCAN 参考:
https://docs.kernel.org/networking/can.html

candump can1,可以收到状态帧

总线这层已经通了:can1 是 ERROR-ACTIVE,RX 3.7 万帧、TX 1.1 万帧、无 bus-off,说明波特率(1M)、接线、ACK 都正常,之前的 Tx busy 已经不复现。现在问题收敛到 ROS 节点层——帧在总线上跑,但底盘节点 frames=0、enabled=0,即节点没「认」这些帧。

按可能性排查:

1)节点绑定的接口不对。确认底盘节点的 CAN 接口参数是不是还写着 can0(你实际用的是 can1,gs_usb 适配器)。可以直接查节点进程打开的 socket:

sudo ls -l /proc/$(pgrep -f 底盘节点名)/fd | grep socket
sudo cat /proc/net/can/recv  # 或看节点日志里 bind 的接口

2)帧 ID / 协议不匹配。把 candump 收到的状态帧 ID 和底盘驱动期望的 ID 对一下(看驱动源码或 DBC 里订阅的 can_id / can_mask)。ID 对不上时驱动会把帧全部过滤掉,表现就是 candump 有数据、节点 frames=0。同时确认下位机发的是经典 CAN 还是 CAN FD——你这个 gs_usb 适配器目前跑在经典 CAN 1M,如果下位机协议是 FD,需要适配器支持并加 fd on 重新初始化。

3)enabled=0 的使能条件。多数底盘驱动要收到特定状态帧(或先下发使能/心跳帧)才置 enabled。翻一下该节点文档或源码里 enabled 的置位条件,用 cansend 手动发一帧使能命令试试。

RX dropped 479 是消费端处理不及时丢的,占比很小,等 frames 通了如果还持续增长,可调大接收缓冲:sudo sysctl -w net.core.rmem_max=8388608 并在节点 socket 设置里对应调大。

先确认第 1、2 步,把节点配置的接口名和期望的帧 ID 贴出来,基本就能定位了。

can1 能收到状态帧,说明波特率、接线、终端电阻都没问题,物理层可以排除了。结合之前 TX busy + frames=0,问题大概率收敛到 ROS 节点这一侧:节点用的接口不对(前面截图和命令都是 can0,而底盘实际接在 can1),发到空总线上没人 ACK,自然一直 TX busy。

1)把底盘节点配置里的 CAN 接口名改成 can1(yaml/launch 里的 can_interface 或对应参数),重启节点。

2)改完先抛开节点单独验证 can1 收发:

cansend can1 123#DEADBEEF
ip -details -statistics link show can1

发送不报错、state 为 ERROR-ACTIVE、TX errors 不增长,说明发送方向也通了。

3)如果节点起来后还是 frames=0 / enabled=0,那就是节点侧的过滤或使能条件没满足:对照底盘协议核对状态帧/控制帧的 CAN ID 是否在节点的过滤列表里,以及是否需要先发一帧使能命令底盘才会上报。

改完还不行的话,贴一下节点配置里接口和 ID 过滤那几行,以及 candump 收到的一小段帧(带 CAN ID),基本就能定位了。

总线链路本身现在是通的:TX 发了 11062 帧、0 错误(发出去有人 ACK 才会这样计数),RX 也累计收到过 37576 帧,接口 ERROR-ACTIVE、bus-off 为 0——波特率、接线、终端电阻这些都没问题了。candump 没输出,最可能是下位机当前没在主动发数据(不少下位机是命令-应答式,只有收到上位机请求帧才回,不会自己往总线上发)。

可以这样确认:

1)开着 candump 的同时,隔几秒看两次 RX 计数有没有涨:

ip -statistics link show can1 | grep -A1 "RX:"

不涨 → 下位机确实没在发;查下位机程序是否在跑、是否需要先收到指令才回数据。

2)主动发一帧试探(ID 和数据按你们下位机协议来,下面是示例):

cansend can1 123#1122334455667788

发完看 candump 有没有回应帧。有回应说明链路完全正常,问题只剩 ROS 侧协议对接。

3)如果 candump 能看到原始帧但 ROS 里 odom 还是 0,那问题就在 ROS 的 CAN 桥接/解析节点:核对它配置的接口名(can1)、帧 ID 和协议解析是否与下位机实际发出的帧一致。

另外 RX dropped 479 只是之前没有程序在读时缓冲区溢出丢的,error-warn/error-pass 是历史计数,现在状态已恢复,这两个不用管。

can-utils 各命令用法可参考官方文档:CAN 使用 | RDK X3/X5 DOC

image

从截图看,总线本身通过(能收到下位机 0x601 报文),问题出在发送方向:节点 TX busy + frames=0,且后来 RX 计数也不再增长——最可能是 CAN 控制器进入了 error-passive/bus-off 状态,或者下位机停止应答(无 ACK 时 TX 会一直重发、队列堆满,就报 TX busy)。建议按下面排查:

1. 先看控制器状态和错误计数

ip -details -statistics link show can1

重点看 state 是 ERROR-ACTIVE 还是 ERROR-PASSIVE / BUS-OFF,以及 TX errors、bus-off restart 计数。

2. 如果已 BUS-OFF / 错误计数持续增长,重配并开启自动恢复

sudo ip link set can1 down
sudo ip link set can1 type can bitrate 500000 restart-ms 100
sudo ip link set can1 up

波特率按你下位机实际值改。加 restart-ms 后即使再 bus-off 也会自动恢复,不会卡死。

3. 检查下位机和物理层

  • 收到的 0x601 数据全 0、enabled=0,先确认下位机固件是否在正常运行/是否进入保护或复位(比如看门狗复位后不再应答,就会出现"之前能收、后来 TX busy"的现象)。
  • 确认 CANH/CANL 没接反、两边共地;线长超过 1 米或波特率高于 125K 时,两端都要接 120Ω 终端电阻(X5 板上有终端电阻开关,需要时闭合,对端也要使能)。
  • 可以先用 cansend can1 123#1122334455667788 单发一帧测试,如果 cansend 也卡住/报错,基本可确认是控制器状态或总线无 ACK 的问题,而不是 ROS 节点的问题。

CAN 接口配置和测试命令可参考官方文档:CAN 使用 | RDK X3/X5 DOC

排查后可以把 ip -details 的输出贴上来,方便进一步定位。

用 cansend can1 123#1122334455667788 单发一帧测试,cansend 有数据

总线层可以判定已经通了:state ERROR-ACTIVE、TX/RX 计数都在涨、candump 能收到状态帧、cansend 单发也正常 —— 波特率、接线、终端电阻这条线可以排除。问题收敛到 ROS 底盘节点:它没读到 can1,或者收到的帧解析不了(frames=0、enabled=0 是节点层的计数)。

按这两步查:

1)确认节点绑定的接口名

很多底盘驱动默认写死 can0,你现在实际用的是 can1。启动节点前后各看一次计数:

ip -statistics link show can1

节点跑起来后如果 can1 的 RX/TX packets 完全不动,说明它根本没打开 can1,去改 launch/参数文件里的 interface 名。另外 RX dropped 479 也侧面说明有帧到了但没被及时读走。

2)确认 CAN ID / 协议对得上

candump can1

记下状态帧的 ID,和底盘驱动期望解析的 ID 对一下(不少驱动带 ID 过滤或固定协议格式)。ID 对不上时节点即使收到帧也不会计数,frames 恒为 0;enabled=0 通常是节点在等某个特定帧、或需要先向下位机发一帧「底盘使能」命令,查一下所用驱动的使能条件。

方便的话补两个信息:用的是哪个底盘驱动包(ros2_socketcan 还是厂商自带驱动)、launch 里配的接口名,以及 candump 里状态帧的 ID 长什么样,可以进一步收敛。

前面 can1 收到过下位机状态帧,这次 cansend 也有输出,先不用反复改接线和波特率。不过,看到自己发出的帧不等于下位机已按协议处理。

接下来请确认 agv_base_node 实际绑定的是 can1 ,再核对它期望的状态帧 ID、数据格式和 enabled 的置位条件。麻烦贴一下节点的 CAN 接口配置和启动后的 frames/enable 日志,我们再看是接口选错,还是帧到了但节点没识别。

RDK X5 可使用此Patch