RK3568是瑞芯微电子(Rockchip)推出的四核64位Cortex-A55处理器芯片,由于芯片IP设计原因,RK3568 CAN接口在使用时需要结合自身具体应用,由应用层主动参与总线故障管理,才能确保CAN接口稳定工作。
针对 RK3568 CAN接口勘误说明,目前有两个版本的CAN驱动程序:1、官方SDK提供的驱动(后续简称官方驱动)。2、Linux mainline中的驱动(后续简称mainline驱动)。
本文将对比测试CAN接口官方驱动和mainline驱动的性能,以及应用程序如何检测总线故障,并提供故障自动恢复的参考建议。
测试板卡ESM3568
测试平台为英创公司基于RK3568J设计的工控主板ESM3568。

ESM3568主要特点包括:
- ESM3568直接引出4路网口,板载了网络PHY芯片,触摸屏接口芯片,独立的硬件RTC等。与常规的仅包含CPU,内存,存储的核心板相比,ESM3568的必要外设板载化设计可简化应用底板设计难度,减少设计风险,降低底板的物料和维护成本。
- 基于RK3568J的ESM3568,与英创基于NXP iMX8MP的ESM8400工控主板pin-to-pin兼容。用户可以在同一块应用底板上安装不同的主板,以灵活的满足其客户多样化的需求。
- ESM3568出厂配置了"开箱即用"的完整内核驱动和文件系统,用户只需专注于应用软件开发,可大大节省用户产品研发周期。英创公司提供"客户量产协助"服务可在板卡厂时烧写用户指定配置,预拷贝用户测试程序或正式应用程序数据,用户拿到英创板卡后可直接装机测试,省去诸多系统重写,软件配置等环节。
驱动性能测试
1. 最大发送帧数测试

Mainline CAN驱动每秒能发送更多的CAN数据。
2. 收发测试
- 波特率250 kBits/s时,板子和PCAN各每隔1ms发一帧:板子ID:123h / PCAN ID: 120h,测试10万帧
- 波特率 ≥ 500 kBits/s时,板子和PCAN各每隔1ms发两帧:板子ID:123h, 124h / PCAN ID: 120h, 121h,测试20万帧

官方驱动的一大问题是,当RK3568发送的CAN帧优先级低于总线上的其他节点时,就会存在大量发送错误,在总线负载越高的情况下,发送错误率越高。而mainline驱动完全没有这个问题。
3. 延迟测试
- CAN0发 -> CAN1收
- CAN0每间隔5ms发送一帧,测试5万帧。
- 应用进程CPU绑定(未CPU隔离) + 应用进程高最优先级(本次测试,内核非PREEMPT_RT)

在相同的测试条件下,Mainline CAN驱动的数据收发延迟要小于官方驱动。
已知故障与恢复建议
在多个可靠性测试项目过程中,原始驱动出现过CPU占用100%的情况,同时mainline驱动的性能表现明显优于官方驱动,因此ESM3568缺省使用mainline CAN驱动。
但由于RK3568芯片CAN接口IP设计原因,即使mainline驱动,在某些条件下仍然存在不能发送或收发全停的故障,且无法在驱动中自动恢复。此时就需要应用层结合自身应用,主动参与总线故障管理,才能确保CAN接口正常工作。
下面是应用程序检查故障并尝试自动恢复的核心逻辑,用户需要结合实际应用,设置恰当的故障触发阈值。

下面是部分代码片段:
- 程序初始化时,开启CAN接口错误帧上报功能:
|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| /* 定义自动恢复功能开关(设为0则禁用对应功能) */ #define MAX_CONSECUTIVE_WRITE_ERRORS 20 /* 发送连续错误阈值 */ #define MAX_RX_IDLE_MS 1000 /* 接收空闲超时(毫秒) */ /* 程序启动时,开启错误帧上报功能 */ #if (MAX_CONSECUTIVE_WRITE_ERRORS > 0 || MAX_RX_IDLE_MS > 0) can_err_mask_t err_mask = CAN_ERR_BUSOFF | CAN_ERR_CRTL | CAN_ERR_PROT; if (setsockopt(s, SOL_CAN_RAW, CAN_RAW_ERR_FILTER, &err_mask, sizeof(err_mask)) < 0) { perror("setsockopt error filter"); } #endif |
- 发送函数中,检测到连续发送失败时,触发自动恢复:
|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| int send_can_frame(int s, struct can_frame *frame, struct program_options *opts) { { ...... /*略*/ nbytes = write(s, frame, sizeof(struct can_frame)); ...... /*略*/ #if MAX_CONSECUTIVE_WRITE_ERRORS > 0 /* 连续写错误达到阈值,触发自动恢复 */ if (nbytes != sizeof(struct can_frame) && consecutive_write_errors >= MAX_CONSECUTIVE_WRITE_ERRORS) { printf("\n[AutoRecovery] %d consecutive write errors on %s, reconfiguring...\n", consecutive_write_errors, opts->device); usleep(100000); /* 等待100ms,避免过快重试 */ report.tx_auto_recoveries++; safe_reconfigure_can(opts); consecutive_write_errors = 0; /* 重置计数器,避免重复触发 */ } #endif } |
- 在接收线程中,同时处理bus_off错误帧和接收超时:
|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| /* 接收线程函数 - 使用recv实现 */ void *receive_thread(void *arg) { /* 初始化上次接收时间为当前时间 */ clock_gettime(CLOCK_REALTIME, &last_rx_time); while (running) { FD_ZERO(&rset); FD_SET(s, &rset); /* 设置超时避免阻塞太久 */ tv.tv_sec = 0; tv.tv_usec = 20000; /* 20ms */ int ret = select(s + 1, &rset, NULL, NULL, &tv); if (ret < 0) { continue; } /* 处理超时(无数据) */ if (ret == 0) { /* 空闲超时自动恢复(仅当功能启用) */ #if MAX_RX_IDLE_MS > 0 struct timespec now; clock_gettime(CLOCK_REALTIME, &now); long elapsed_ms = (now.tv_sec - last_rx_time.tv_sec) * 1000 + (now.tv_nsec - last_rx_time.tv_nsec) / 1000000; if (elapsed_ms >= MAX_RX_IDLE_MS) { printf("\n[AutoRecovery] No CAN frame for %ld ms on %s, reconfiguring...\n", elapsed_ms, opts->device); safe_reconfigure_can(opts); report.rx_auto_recoveries++; /* 重置空闲计时,避免立即再次触发 */ clock_gettime(CLOCK_REALTIME, &last_rx_time); } #endif continue; } /* 使用recv接收数据 */ struct timeval recv_time; int nbytes = recv(s, &frame, sizeof(struct can_frame), 0/*MSG_DONTWAIT*/); if (nbytes != sizeof(struct can_frame)) continue; #if (MAX_CONSECUTIVE_WRITE_ERRORS > 0 || MAX_RX_IDLE_MS > 0) if (frame.can_id & CAN_ERR_BUSOFF) { printf("\n[Error] Bus-off detected via error frame!\n"); usleep(100000); /* 等待100ms,避免过快重试 */ report.bus_off_recoveries++; safe_reconfigure_can(opts); /* 重置空闲计时,避免立即再次触发 */ clock_gettime(CLOCK_REALTIME, &last_rx_time); continue; // 错误帧不作为数据帧处理 } #endif gettimeofday(&recv_time, NULL); clock_gettime(CLOCK_REALTIME, &last_rx_time); /* 更新上次接收时间 */ } } |
- can接口配置中,关闭驱动bus off自恢复:
|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| int configure_can_interface(const char *device, uint32_t bitrate) { ...... /*略*/ /* 根据是否启用用户自动恢复功能,决定驱动是否启动bus off自恢复 */ #if (MAX_CONSECUTIVE_WRITE_ERRORS > 0 || MAX_RX_IDLE_MS > 0) /* 用户自动恢复启用:不使用内核的自动重启,避免冲突 */ snprintf(cmd, sizeof(cmd), "ip link set %s up type can bitrate %d restart-ms 0 2>/dev/null", device, bitrate); #else /* 无用户自动恢复:依赖内核的自动重启(100ms后尝试恢复) */ snprintf(cmd, sizeof(cmd), "ip link set %s up type can bitrate %d restart-ms 100 2>/dev/null", device, bitrate); #endif ret = system(cmd); ...... /*略*/ } |
总结
通过增加应用层自恢复机制,可以确保CAN总线从故障中自动恢复,但确实也就存在,在某些情况下发送或接收被暂时中断的现象。对CAN总线可靠性要求极高的车载应用等场合,建议使用与ESM3568完全兼容的NXP i.MX8MP主板ESM8400,或者考虑外扩独立的CAN总线芯片。