RK3568是瑞芯微电子(Rockchip)推出的四核64Cortex-A55处理器芯片,由于芯片IP设计原因,RK3568 CAN接口在使用时需要结合自身具体应用,由应用层主动参与总线故障管理,才能确保CAN接口稳定工作。

        针对 RK3568 CAN接口勘误说明,目前有两个版本的CAN驱动程序:1、官方SDK提供的驱动(后续简称官方驱动)2Linux mainline中的驱动(后续简称mainline驱动)

        本文将对比测试CAN接口官方驱动和mainline驱动的性能,以及应用程序如何检测总线故障,并提供故障自动恢复的参考建议。

测试板卡ESM3568

测试平台为英创公司基于RK3568J设计的工控主板ESM3568

ESM3568主要特点包括:

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

驱动性能测试

1. 最大发送帧数测试

 

Mainline CAN驱动每秒能发送更多的CAN数据。

2. 收发测试

  1. 波特率250 kBits/s时,板子和PCAN各每隔1ms发一帧:板子ID:123h / PCAN ID: 120h,测试10万帧
  2. 波特率 ≥ 500 kBits/s时,板子和PCAN各每隔1ms发两帧:板子ID:123h, 124h / PCAN ID: 120h, 121h,测试20万帧

 

        官方驱动的一大问题是,当RK3568发送的CAN帧优先级低于总线上的其他节点时,就会存在大量发送错误,在总线负载越高的情况下,发送错误率越高。而mainline驱动完全没有这个问题。

3. 延迟测试

  1. CAN0发 -> CAN1收
  2. CAN0每间隔5ms发送一帧,测试5万帧。
  3. 应用进程CPU绑定(未CPU隔离) + 应用进程高最优先级(本次测试,内核非PREEMPT_RT)

 

        在相同的测试条件下,Mainline CAN驱动的数据收发延迟要小于官方驱动。

已知故障与恢复建议

        在多个可靠性测试项目过程中,原始驱动出现过CPU占用100%的情况,同时mainline驱动的性能表现明显优于官方驱动,因此ESM3568缺省使用mainline CAN驱动。

        但由于RK3568芯片CAN接口IP设计原因,即使mainline驱动,在某些条件下仍然存在不能发送或收发全停的故障,且无法在驱动中自动恢复。此时就需要应用层结合自身应用,主动参与总线故障管理,才能确保CAN接口正常工作。

        下面是应用程序检查故障并尝试自动恢复的核心逻辑,用户需要结合实际应用,设置恰当的故障触发阈值。

                                                                                                                                                                                                 

下面是部分代码片段:

1. 程序初始化时,开启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

2. 发送函数中,检测到连续发送失败时,触发自动恢复:

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

}

3. 在接收线程中,同时处理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);   /* 更新上次接收时间 */     

    }  

}

4. 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总线芯片。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐