跳转至

08 - RK3568 CAN1 控制器修复与验证(2026-08-29)

状态:[BSP-6.1 CAN CONTROLLER RUNTIME VERIFIED]

已验证 GEC V11 的 RK3568 CAN1 硬件控制器由 rockchip_canfd 驱动注册为 SocketCAN 网络接口 can0,可配置为 500 kbit/s,控制器时钟为 200 MHz。尚未取得 loopback 帧或外部节点实际收发证据,因此本页不写成“CAN 物理总线完全跑通”。

结论

本次故障的主因不是 CAN1 节点缺失,而是 BSP 6.1 的专用 defconfig 没有启用匹配 RK3568 的控制器驱动:

compatible = "rockchip,rk3568-can-2.0"
driver     = drivers/net/can/rockchip/rockchip_canfd.c
Kconfig    = CONFIG_CANFD_ROCKCHIP

初始系统只有 CAN 协议栈日志,没有控制器 probe,也没有 SocketCAN 接口:

CAN device driver interface
NET: Registered PF_CAN protocol family

加入 CONFIG_CANFD_ROCKCHIP=y,补齐 CAN1 的 200 MHz assigned clock,并重新编译烧写后,板端出现:

2: can0: <NOARP,ECHO> mtu 16 qdisc noop state DOWN

配置 500 kbit/s 后:

can state ERROR-ACTIVE (berr-counter tx 0 rx 0)
bitrate 500000 sample-point 0.875
rockchip_canfd: tseg1 1..128 tseg2 1..128 sjw 1..128 brp 1..256 brp-inc 2
clock 200000000

这证明控制器、驱动、时钟、pinctrl 和 SocketCAN 注册链已经成立。

官方 EVB 与 GEC 的差异

官方 EVB 没有板级 CAN override

核查 BSP 6.1 当前源码:

rk3568-evb.dtsi                    no board-level CAN override
rk3568-evb1-ddr4-v10.dtsi         no board-level CAN override
rk3568-evb1-ddr4-v10-linux.dts    no board-level CAN override

因此不存在一段“原厂 EVB 已启用 CAN”的节点可以直接复制。rk356x.dtsi 只提供 SoC 控制器资源,默认状态仍是 disabled;具体启用哪个控制器、哪组 pinmux,必须由板级 DTS 决定。

SoC 层 CAN1

BSP 6.1 rk356x.dtsi 中的控制器定义:

can1: can@fe580000 {
    compatible = "rockchip,rk3568-can-2.0";
    reg = <0x0 0xfe580000 0x0 0x1000>;
    interrupts = <GIC_SPI 2 IRQ_TYPE_LEVEL_HIGH>;
    clocks = <&cru CLK_CAN1>, <&cru PCLK_CAN1>;
    clock-names = "baudclk", "apb_pclk";
    resets = <&cru SRST_CAN1>, <&cru SRST_P_CAN1>;
    reset-names = "can", "can-apb";
    tx-fifo-depth = <1>;
    rx-fifo-depth = <6>;
    status = "disabled";
};

GEC 板级层只需覆盖板级差异,不修改共享的 rk356x.dtsi

Pinmux 不能照搬其它板卡

BSP 提供两组 CAN1 引脚:

pinctrl RX TX mux
can1m0_pins GPIO1_A0 GPIO1_A1 function 3
can1m1_pins GPIO4_C2 GPIO4_C3 function 3

LubanCat 某些板型使用 can1m1_pins,GEC 当前板级定义使用 can1m0_pins。这代表不同 PCB 接线,不是 m1 比 m0 更“标准”。GEC 的 m0 选择来自既有板级定义和 5.10 迁移基础,本轮保持不变;除非原理图、PCB 或连通性测量提供相反证据,不应照搬其它板卡改成 m1。

修改前

板级节点已经启用 CAN1,但没有显式指定工作时钟:

&can1 {
    pinctrl-names = "default";
    pinctrl-0 = <&can1m0_pins>;
    status = "okay";
};

更关键的是,当时 .config 为:

CONFIG_CAN_DEV=y
CONFIG_CAN_CALC_BITTIMING=y
# CONFIG_CAN_ROCKCHIP is not set
# CONFIG_CANFD_ROCKCHIP is not set

所以 CAN core、RAW、BCM 和 gateway 可以注册,但没有驱动去匹配 fe580000.can

根因:5.10 与 6.1 的配置符号不同

内核路线 RK3568 CAN 控制器配置
Rockchip BSP 5.10 CONFIG_CAN_RK3568
Rockchip BSP 6.1 CONFIG_CANFD_ROCKCHIP

BSP 6.1 Kconfig 中没有 CONFIG_CAN_RK3568。把 5.10 的符号原样写进 6.1 defconfig,不会编出 RK3568 CAN 控制器驱动;重新生成 .config 时该无效符号会被静默丢弃。

CONFIG_CAN_ROCKCHIP 也不是本板所需选项:它对应旧的 rockchip,can-1.0。当前 rockchip,rk3568-can-2.0rockchip_canfd.c 匹配,其 Makefile 映射是:

obj-$(CONFIG_CANFD_ROCKCHIP) += rockchip_canfd.o

虽然配置名中包含 CANFD,它正是当前 BSP 6.1 为 RK3568 can-2.0 节点提供的驱动,不能只凭名称判断并删除。

最终配置

GEC 板级 DTS

当前 rk3568-evb1-gec-v11.dtsi

&can1 {
    compatible = "rockchip,rk3568-can-2.0";
    assigned-clocks = <&cru CLK_CAN1>;
    assigned-clock-rates = <200000000>;
    pinctrl-names = "default";
    pinctrl-0 = <&can1m0_pins>;
    status = "okay";
};

compatible 在 SoC 节点中已经存在,板级重复写不是驱动匹配成功的关键;本轮真正关键的是 CONFIG_CANFD_ROCKCHIP=y。assigned clock 让位时序基准明确固定为 200 MHz,板端 ip -details 已验证该频率生效。

GEC 专用 defconfig

维护配置当前至少包含:

CONFIG_CAN=y
CONFIG_CANFD_ROCKCHIP=y

加载 defconfig 后,最终 .config 展开为:

CONFIG_CAN=y
CONFIG_CAN_RAW=y
CONFIG_CAN_BCM=y
CONFIG_CAN_GW=y
CONFIG_CAN_DEV=y
CONFIG_CAN_NETLINK=y
CONFIG_CAN_CALC_BITTIMING=y
CONFIG_CANFD_ROCKCHIP=y

编译和烧写

cd /home/hyl/lubancat-linux-sdk/kernel-6.1

make ARCH=arm64 rockchip_rk3568_gec_linux_defconfig
make ARCH=arm64 olddefconfig
grep -E '^CONFIG_CAN|CANFD_ROCKCHIP|CAN_RK3568' .config

cd /home/hyl/lubancat-linux-sdk
./build.sh kernel

按照当前 SDK 流程烧写新生成的 boot.img。CAN 驱动和 DTS 都包含在内核启动产物中,只更新用户空间文件不会让控制器出现。

专用 defconfig 在 SDK 内核仓库中曾处于未跟踪状态。执行 savedefconfig 或覆盖文件前应先备份并确认产物包含 CONFIG_CANFD_ROCKCHIP=y,否则旧的根目录 defconfig 可能覆盖新配置。

板端验证

1. 控制器是否注册

dmesg | grep -iE 'can|fe580000|rockchip_canfd'
ip link show
ls /sys/class/net/

只看到 PF_CAN、RAW、BCM 等日志,不能证明控制器已注册;必须看到 SocketCAN 网络接口。

2. 配置 500 kbit/s

本板只启用了一个 CAN 控制器,它注册出的第一个网络接口是 can0

ip link set can0 down
ip link set can0 type can bitrate 500000
ip link set can0 up
ip -details -statistics link show can0

本轮板端输出确认:

can state ERROR-ACTIVE
berr-counter tx 0 rx 0
bitrate 500000
sample-point 0.875
clock 200000000

这里的 ERROR-ACTIVE 是 CAN 控制器的正常主动错误状态名称,不等于“当前发生错误”。但在没有实际发送和接收帧时,零错误计数也不能单独证明外部收发器和总线接线正常。

为什么 DTS 叫 can1,系统却出现 can0

两个名字属于不同命名空间:

DTS label:        can1 = RK3568 地址 0xfe580000 的硬件控制器
SocketCAN netdev: can0 = 本次系统第一个成功注册的 CAN 网络接口

当前只启用 DTS 的 can1,它仍会按网络接口注册顺序获得 can0。因此板端命令应使用实际 ip link show 中的 can0,不要根据 DTS label 猜接口名。

当前证据边界

层级 证据 状态
CAN 协议栈 PF_CAN、RAW、BCM、gateway 注册 [VERIFIED]
控制器驱动 rockchip_canfdcan0 出现 [VERIFIED]
时钟与位时序 200 MHz、500 kbit/s、sample point 0.875 [VERIFIED]
控制器 link up can0UP,LOWER_UP [VERIFIED]
内部 loopback 帧 尚无 candump/cansend 输出 [PENDING]
板载 CAN 收发器 尚无示波器或外部节点证据 [PENDING]
物理总线双向收发 尚无双节点帧记录 [PENDING]

LVGL CAN Test 报错定位

板端 LVGL 测试页面进入 CAN Test 时出现:

RTNETLINK answers: Operation not supported

此前只有运行输出,无法确定失败参数。补充应用源码后,根因已经确认在 can_init()

system("ip link set can0 down");
system("ip link set can0 type can bitrate 500000 dbitrate 500000 fd on");
system("ip link set can0 up");

中间命令要求内核同时启用 CAN-FD 和数据段波特率。ip 通过 netlink 把该请求发给 CAN 驱动,驱动发现当前控制器模式不支持 CAN_CTRLMODE_FD,返回 -EOPNOTSUPP,用户空间便打印 Operation not supported

为什么驱动名称有 CANFD,硬件却不能 fd on

当前 DTS:

compatible = rockchip,rk3568-can-2.0

rockchip_canfd.c 的 compatible 映射:

{
    .compatible = "rockchip,rk3568-can-2.0",
    .data = (void *)ROCKCHIP_RK3568_CAN_MODE
},

probe 时不同 mode 声明不同能力。只有 ROCKCHIP_CANFD_MODE 包含:

CAN_CTRLMODE_FD

RK3568 当前进入 ROCKCHIP_RK3568_CAN_MODE 分支,支持的是:

CAN_CTRLMODE_BERR_REPORTING
CAN_CTRLMODE_LISTENONLY
CAN_CTRLMODE_LOOPBACK
CAN_CTRLMODE_3_SAMPLES

不包含 CAN_CTRLMODE_FD。因此:

  • CONFIG_CANFD_ROCKCHIP 是编译这个多模式 Rockchip 驱动的配置名;
  • rockchip_canfd.c 是同时承载经典 CAN 与 CAN-FD 路径的源文件名;
  • 它们都不代表每个 compatible 对应的硬件一定支持 CAN-FD;
  • 当前 RK3568 节点按经典 CAN 使用,单帧数据长度最大 8 字节;
  • dbitrate 是 CAN-FD 数据阶段参数,在经典 CAN 中没有意义。

这也解释了为什么 CAN 控制器仍能以 500 kbit/s UP:失败的是应用额外请求的 FD 模式,不是 CAN1 驱动、DTS、时钟或经典 CAN bit timing。

修正后的 can_init()

static void can_init(void)
{
    int ret;

    ret = system("ip link set can0 down");
    if (ret != 0)
        eprint("failed to set can0 down\n");

    ret = system("ip link set can0 type can bitrate 500000");
    if (ret != 0)
        eprint("failed to configure can0 bitrate\n");

    ret = system("ip link set can0 up");
    if (ret != 0)
        eprint("failed to set can0 up\n");
}

核心修改是删除:

dbitrate 500000 fd on

产品代码还应避免直接依赖 system() 的模糊返回值,或至少使用 <sys/wait.h>WIFEXITED/WEXITSTATUS 解码退出状态。当前片段先保持原应用结构,只让错误可见。

LVGL 接收过滤器问题

应用的接收初始化与 thread_proc() 都包含:

rfilter[0].can_id = -1; // 0x22
rfilter[0].can_mask = CAN_SFF_MASK;
setsockopt(can_fd, SOL_CAN_RAW, CAN_RAW_FILTER,
           &rfilter, sizeof(rfilter));

这段代码与注释不一致。can_id_t 是无符号 CAN ID 类型,赋值 -1 后所有 bit 都为 1,其中包括 CAN_INV_FILTER。Linux 6.1 会先保存反向过滤标志,再按 mask 预处理 ID;接收时使用:

(received_can_id & mask) != filtered_can_id

所以当前配置不是“接收全部”,也不是“只接收 0x22”,而是反向排除低 11 位等于 0x7FF 的帧。大多数普通 ID 看起来仍能收到,这会让错误更隐蔽。

接收全部普通 CAN 帧

最简单的方式是不覆盖 CAN_RAW socket 的默认普通帧过滤行为;若希望显式配置:

struct can_filter rfilter = {
    .can_id = 0,
    .can_mask = 0,
};

if (setsockopt(can_fd, SOL_CAN_RAW, CAN_RAW_FILTER,
               &rfilter, sizeof(rfilter)) < 0) {
    eprint("failed to set CAN_RAW_FILTER\n");
}

这不包含 CAN error message frame;错误帧订阅由 CAN_RAW_ERR_FILTER 单独控制,本测试页面当前不需要混入错误帧。

只接收标准数据帧 ID 0x22

struct can_filter rfilter = {
    .can_id = 0x22,
    .can_mask = CAN_SFF_MASK | CAN_EFF_FLAG | CAN_RTR_FLAG,
};

CAN_EFF_FLAGCAN_RTR_FLAG 纳入 mask,可避免把扩展帧或 RTR 帧误当成标准数据帧 0x22。如果测试目标是接收页面自身发送的 0x11,过滤 ID 应改成 0x11,或直接接收全部。

修正后的 can_recv_init() 示例

下面以“接收全部”为例,并补齐最基本的错误处理:

static s32 can_recv_init(void)
{
    struct timeval timeout = {0, 20000};
    struct sockaddr_can addr;
    struct ifreq ifr;
    struct can_filter rfilter = {
        .can_id = 0,
        .can_mask = 0,
    };

    can_fd = socket(PF_CAN, SOCK_RAW, CAN_RAW);
    if (can_fd < 0) {
        eprint("failed to create CAN_RAW socket\n");
        return -1;
    }

    memset(&ifr, 0, sizeof(ifr));
    strncpy(ifr.ifr_name, "can0", IFNAMSIZ - 1);
    if (ioctl(can_fd, SIOCGIFINDEX, &ifr) < 0)
        goto err;

    memset(&addr, 0, sizeof(addr));
    addr.can_family = AF_CAN;
    addr.can_ifindex = ifr.ifr_ifindex;
    if (bind(can_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
        goto err;

    if (setsockopt(can_fd, SOL_CAN_RAW, CAN_RAW_FILTER,
                   &rfilter, sizeof(rfilter)) < 0)
        goto err;

    if (setsockopt(can_fd, SOL_SOCKET, SO_RCVTIMEO,
                   &timeout, sizeof(timeout)) < 0)
        goto err;

    return 0;

err:
    eprint("failed to initialize can0 receive socket\n");
    close(can_fd);
    can_fd = -1;
    return -1;
}

thread_proc() 中复制了一份相同的 socket/filter 初始化代码,即使当前线程创建被注释,也应同步修正或抽成一个公共函数,避免以后重新启用线程时旧问题复现。

应用层其它观察

  1. 下拉框列出 can0/can1,但发送和接收代码都硬编码 "can0",当前选择不会改变实际接口。
  2. socketioctlbindsetsockopt 和多数 system() 原来没有检查错误,UI 可能在初始化失败后继续运行,造成“页面打开但功能异常”的假象。
  3. 发送结构使用 struct can_frame、DLC=8,符合经典 CAN;若改成超过 8 字节必须同时更换 struct canfd_frame 和具备 FD 能力的硬件,当前 RK3568 路径不支持。
  4. 发送 socket 主动禁用了自己的接收过滤,接收由另一个 socket 完成。要做单机自发自收,应启用控制器 loopback on,或使用真实外部 CAN 节点提供 ACK 与回帧。

LVGL 修复后的验证

重新编译应用后,先确认不再请求 FD:

ip -details link show can0

预期只显示经典 CAN 500 kbit/s,不应显示 FD data bitrate。然后执行:

  1. 打开 CAN Test 页面,确认不再出现 Operation not supported
  2. 点击 Send,使用外部节点或 candump can0 观察 ID 0x11、DLC 8 和数据内容。
  3. 从外部节点发送 ID 0x22,确认 Receive 区显示正确;若配置接收全部,也测试其它 ID。
  4. 记录 ip -details -statistics link show can0 的 packet 与 error counter。
  5. 双向收发成功后,再升级物理总线状态。

当前仓库只找到 docs/development/strings rk356x-demo_good_board_backup.md 中的命令字符串,没有找到这份 LVGL CAN 页面对应的可编辑 C 源文件。因此本轮完成的是根因和修正方案落地,实际应用仍标记 [APP FIX PENDING]

经典 CAN 手工重配复验

板端最初在接口仍为 UP 时执行:

ip link set can0 type can bitrate 500000

返回:

RTNETLINK answers: Device or resource busy

这不是硬件或 500 kbit/s 不受支持,而是 SocketCAN 要求修改 bit timing 前先停止接口。按正确顺序执行后全部成功:

ip link set can0 down
ip link set can0 type can bitrate 500000
ip link set can0 up
ip -details -statistics link show can0

新增实测结果:

can0: <NOARP,UP,LOWER_UP,ECHO>
can state ERROR-ACTIVE (berr-counter tx 0 rx 0)
bitrate 500000 sample-point 0.875
tq 50 prop-seg 17 phase-seg1 17 phase-seg2 5 sjw 1
rockchip_canfd: tseg1 1..128 tseg2 1..128 sjw 1..128 brp 1..256 brp-inc 2
clock 200000000
bus-off 0

判定:经典 CAN 的 down -> configure -> up 流程再次验证通过;500 kbit/s、200 MHz 时钟和当前 ERROR-ACTIVE 状态成立。

同一输出还包含累计统计:

error-warn 1
error-pass 1
TX dropped 1
RX packets 556155
RX bytes 4449240

error-warn/error-pass 是接口运行期间进入过相应状态的累计事件,不等于当前仍处于 warning/passive;当前状态行和 berr-counter 才描述即时状态,且 bus-off 为 0。RX bytes 恰好等于 packets x 8,但尚无 CAN ID 和 payload 输出,不能仅凭计数宣布物理总线帧已验证。

输出中的以下字段明显不符合正常 netdev 规模:

promiscuity 375469
numtxqueues 375665
numrxqueues 375695

它们更像板端 ip 工具对当前内核 netlink 扩展属性的解码或格式兼容问题,不应解释成系统真的创建了数十万条队列。后续应直接读取 sysfs 统计并用 candump 核对真实帧:

for item in rx_packets rx_bytes rx_errors rx_dropped \
            tx_packets tx_bytes tx_errors tx_dropped; do
    printf '%-12s ' "$item"
    cat "/sys/class/net/can0/statistics/$item"
done

candump -L can0

只有 candump 给出 CAN ID、DLC 和 payload,或另一节点明确收到本板发出的帧,才能把这些 RX/TX 计数升级为帧级和物理总线证据。

下一步:内部回环

板端需要 can-utilscandumpcansend

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

candump can0 &
CANDUMP_PID=$!
cansend can0 123#DEADBEEF
sleep 1
kill "$CANDUMP_PID"

预期接收:

can0  123   [4]  DE AD BE EF

该实验通过后只能升级为 [CONTROLLER LOOPBACK VERIFIED],仍不代表板载物理收发器和外部总线已验证。

下一步:双节点物理总线

  1. 准备两个 CAN 节点,双方波特率均设为 500 kbit/s。
  2. 连接 CAN_HCAN_HCAN_LCAN_L,并连接参考地。
  3. 确认板卡是否已带终端电阻;总线两端各保留一个 120 Ω 终端,避免重复叠加。
  4. 接收端运行 candump can0
  5. 发送端运行 cansend can0 123#DEADBEEF
  6. 交换发送和接收方向,再验证一次。
  7. 保存 ip -details -statistics link show can0,确认无持续增长的 error counter 和 BUS-OFF。

只有双向帧收发成功,才能把状态升级为 [CAN PHYSICAL BUS VERIFIED]

常见误判

误判 正确判断
看见 NET: Registered PF_CAN 就认为 CAN 已通 这只代表协议栈;还要检查 ip link 是否出现 CAN netdev
6.1 继续使用 CONFIG_CAN_RK3568 该符号在当前 6.1 Kconfig 中不存在,应使用 CONFIG_CANFD_ROCKCHIP
CONFIG_CANFD_ROCKCHIP 只服务 CAN-FD,传统 CAN 不能用 当前 BSP 的 rk3568-can-2.0 就由该驱动匹配
LubanCat 使用 m1,所以 GEC 也应改 m1 pinmux 由 PCB 接线决定;GEC 当前使用 m0
DTS label 是 can1,接口就必须叫 can1 SocketCAN 接口名由网络设备注册顺序决定
ERROR-ACTIVE 表示故障 它是 CAN 正常运行状态之一;需结合 error counter、收发帧和 BUS-OFF 判断