跳转至

can开发指南

概述

CAN(Controller Area Network,控制器局域网)是一种用于实时通信的串行总线协议,专为在复杂电磁环境下可靠传输数据而设计。支持CAN-FD(CAN with Flexible Data rate),可实现更高数据速率和更大数据长度。

典型应用场景:

  • 汽车电子控制
  • 工业自动化
  • 楼宇自动化
  • 医疗设备

快速跑通 CAN Demo

功能说明

社区固件中的 CAN 内部回环示例 使用 LOOP_BACK_MODE 在 CAN 控制器内部完成标准帧过滤、8 字节发送、接收回调和数据比对,不需要外接 CAN 收发器或第二个节点。

Demo 代码归档路径

samples/native_samples/can/
├── CMakeLists.txt    # 独立组件构建配置
├── README.md         # 项目级运行说明
├── can_demo.c        # CAN 配置、内部回环、数据校验和 AT 指令
└── can_demo.h        # 对外运行接口和 AT 注册函数声明

编译与烧录

完成一站式 CLI 环境配置后执行:

fbb set-target pack_diting_community
fbb build

构建成功后,使用一站式 CLI 烧写固件并打开 UART2 串口监视器。以下为 Windows USB DFU 示例;将 COM3 替换为实际日志串口,其他平台和串口烧写参数参见一站式 CLI 开发环境使用指南

fbb flash -f "$env:FBB_SDK_DIR\output\3322\fwpkg\diting-community.fwpkg" --chip 3322 -d --timeout 180
fbb monitor --port COM3 --baud 750000

烧录生成的 diting-community.fwpkg,重新上电后以 750000 波特率打开 UART2 串口。

使用方式

AT+CANLOOPBACK

预期结果

[DRV][CAN-LOOPBACK] PASS
OK

该结果只验证芯片内部 CAN 控制器。验证正常总线模式时,仍需 CAN 收发器、另一 CAN 节点和总线两端的 120 Ω 终端电阻,并将示例中的 can_mode 改为 NORMAL_OPREATION

文件结构与代码走读

文件职责

文件 职责 关键内容
can_demo.c 配置内部回环、过滤器、发送帧、接收回调和资源释放 diting_can_loopback_demo_run()can_demo_callback()
can_demo.h 公开运行和 AT 注册接口 at_diting_can_example_cmd_register()
CMakeLists.txt 定义独立组件和注册宏 diting_can_sampleAT_DITING_EXAMPLE_CAN_TEST
README.md 说明内部回环与正常总线的验证边界 AT+CANLOOPBACK

核心流程

示例使用 500 kbit/s 仲裁段、2 Mbit/s CAN-FD 数据段和内部回环模式:

can_global_config_t global_config = {
    .std_baud_rate = CAN_BPS_500K,
    .fd_baud_rate = CAN_FD_BPS_2M,
    .point = CAN_SAMPLE_PERCENT_80,
    .can_mode = LOOP_BACK_MODE,
};

标准帧过滤器只接收 ID 0x321,发送数据为字符串字节 HiDiTing V100。回调同时校验帧类型、标准帧 ID、长度和数据:

g_can_data_ok = (message != NULL) && (data != NULL) &&
    (message->ide == 0) && (message->std_id == CAN_DEMO_ID) &&
    (length == CAN_DEMO_DATA_LEN) &&
    (memcmp(data, g_can_expected, CAN_DEMO_DATA_LEN) == 0);
g_can_received = true;

运行函数依次初始化、注册回调、发送和等待接收,结束时注销回调并去初始化:

(void)uapi_can_callback_unregister(CAN_BUS_0);
uapi_can_deinit(CAN_BUS_0);

这保证内部回环指令可以重复执行,不会占用后续 CAN 测试所需资源。

基于 CAN Demo 开发自己的应用

  1. 复制 samples/native_samples/can,修改组件名和构建开关,保留 diting_can_loopback_demo_run() 这类可直接调用的业务入口。
  2. 从应用任务、系统服务或 app_run 启动入口调用运行函数,不依赖串口命令触发。
  3. 根据网络修改波特率、采样点、过滤器、帧 ID 和数据长度。
  4. 正常总线模式将 can_mode 改为 NORMAL_OPREATION,并准备收发器、另一节点及终端电阻。
  5. 在回调中快速复制数据或发送消息,不执行长时间阻塞操作。
  6. 在所有失败路径和正常结束路径注销回调并调用 uapi_can_deinit()

需要保留板端串口回归能力时,再为运行函数增加独立 AT 命令名和命令 ID,并在 AT 适配层完成注册。该步骤是可选调试入口,不是 CAN 业务组件的组成部分。

功能特性

特性 说明
协议标准 支持CAN 2.0A/B和CAN-FD
数据长度 CAN最高8字节,CAN-FD最高64字节
传输速率 CAN最高1Mbps,CAN-FD数据段最高5Mbps
帧类型 支持标准帧(11位ID)和扩展帧(29位ID)
过滤器 支持多组过滤器配置,区分标准帧和扩展帧
错误检测 自动错误检测和重传机制

CAN总线架构:

**CAN总线架构:**

开发流程

操作流程

操作流程

步骤1:配置CAN参数

CAN初始化前需要配置全局参数和过滤器:

// 定义中断回调函数
static void can_fd_callback(can_int_sts_t status, can_msg_attr_t *received_msg,
                            uint8_t *data, uint8_t length)
{
    osal_printk("CAN status: 0x%x\n", status);
    if (status == CAN_FRAME_RECEIVED_INT) {
        osal_printk("Received data, length: %d\n", length);
        for (uint32_t i = 0; i < length; i++) {
            osal_printk("Byte[%d]: 0x%x\n", i, data[i]);
        }
    }
}

步骤2:初始化CAN模块

static int can_fd_init_config(void)
{
    // 配置全局参数
    can_global_config_t global_config = {
        .std_baud_rate = CAN_BPS_500K,    // 标准CAN 500Kbps
        .fd_baud_rate = CAN_FD_BPS_2M,    // CAN-FD 2Mbps
        .point = CAN_SAMPLE_PERCENT_80,   // 采样点80%
        .can_mode = NORMAL_OPREATION,     // 正常模式
    };
    // 配置标准帧过滤器
    can_filter_t std_filter[2];
    can_filter_t ext_filter[2];

    std_filter[0].fid1 = 0x1;
    std_filter[0].fid2 = 0x100;
    std_filter[0].filter_type = RANGE_FILTER;
    std_filter[0].fifo_id = 0;
    std_filter[1].fid1 = 0x101;
    std_filter[1].fid2 = 0x200;
    std_filter[1].filter_type = RANGE_FILTER;
    std_filter[1].fifo_id = 1;

    ext_filter[0].fid1 = 0x1;
    ext_filter[0].fid2 = 0x100;
    ext_filter[0].filter_type = RANGE_FILTER;
    ext_filter[0].fifo_id = 0;
    ext_filter[1].fid1 = 0x101;
    ext_filter[1].fid2 = 0x200;
    ext_filter[1].filter_type = RANGE_FILTER;
    ext_filter[1].fifo_id = 1;

    can_filter_list_t std_list = {
        .filters = std_filter,
        .filter_num = 2
    };
    can_filter_list_t ext_list = {
        .filters = ext_filter,
        .filter_num = 2
    };
    errcode_t ret = uapi_can_init(CAN_BUS_0, &global_config, &std_list, &ext_list);
    if (ret != ERRCODE_SUCC) {
        osal_printk("CAN init failed. Retval:%x\n", ret);
        return ERRCODE_FAIL;
    }
    osal_printk("CAN init Success.\n");

    ret = uapi_can_callback_register(CAN_BUS_0, can_fd_callback);
    if (ret != ERRCODE_SUCC) {
        osal_printk("CAN register failed. Retval:%x\n", ret);
        return ERRCODE_FAIL;
    }
    osal_printk("CAN register Success.\n");

    return ERRCODE_SUCC;
}

步骤3:发送数据

#define DATA_LENGTH 8

// 构建标准数据帧
#define build_can_standard_data_frame(id) { \
    .std_id = (id), \
    .extend_id = 0, \
    .rtr = 0, \
    .ide = 0, \
    .fdf = 0, \
    .brs = 0, \
}

static uint8_t g_databuffer[DATA_LENGTH] = {0xFF, 0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};

errcode_t ret = ERRCODE_SUCC;
can_msg_attr_t msg_attr = build_can_standard_data_frame(0x101);

ret = uapi_can_send_msg(CAN_BUS_0, &msg_attr, g_databuffer, DATA_LENGTH);
if (ret != ERRCODE_SUCC) {
    osal_printk("CAN-FD send failed. Retval: 0x%x\n", ret);
    return ERRCODE_FAIL;
}
osal_printk("CAN-FD send Success.\n");

步骤4:接收数据

方式一:中断回调接收(推荐)

在回调函数中处理接收到的数据,参考步骤2中的回调函数示例。

方式二:轮询接收

static int can_fd_receive_data(void)
{
    errcode_t ret = ERRCODE_SUCC;
    can_msg_attr_t can_msg = {0};
    uint8_t data[DATA_LENGTH] = {0};
    uint32_t length = 0;

    ret = uapi_can_receive_msg(CAN_BUS_0, 0, &can_msg, data, &length);
    if (ret != ERRCODE_SUCC) {
        osal_printk("CAN-FD receive failed. Retval: 0x%x\n", ret);
        return ERRCODE_FAIL;
    }
    osal_printk("CAN-FD receive Success, length: %d\n", length);
    return ERRCODE_SUCC;
}

步骤5:配置CAN-FD发送延迟补偿(可选)

errcode_t ret;
uint8_t tdc_offset = 0;  // TDC偏移量,根据实际环境调整

ret = uapi_can_fd_config_tdc(CAN_BUS_0, 1, tdc_offset);
if (ret != ERRCODE_SUCC) {
    osal_printk("CAN-FD TDC config failed.\n");
    return ERRCODE_FAIL;
}

步骤6:去初始化CAN

uapi_can_deinit(CAN_BUS_0);
osal_printk("CAN deinit Success.\n");

典型应用代码

#include <stdio.h>
#include <stdint.h>
#include "osal.h"
#include "can.h"

#define CAN_BUS_0          0
#define DATA_LENGTH        8
#define CAN_STD_ID         0x101

static uint8_t g_tx_buffer[DATA_LENGTH] = {0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0};
static uint8_t g_rx_buffer[DATA_LENGTH] = {0};

static void can_fd_callback(can_int_sts_t status, can_msg_attr_t *received_msg,
                            uint8_t *data, uint8_t length)
{
    osal_printk("CAN interrupt status: 0x%x\n", status);
    if (status == CAN_FRAME_RECEIVED_INT) {
        osal_printk("CAN frame received, ID: 0x%x, Length: %d\n",
                    received_msg->ide ? received_msg->extend_id : received_msg->std_id, length);
    }
}

static int can_fd_init(void)
{
    can_global_config_t global_config = {
        .std_baud_rate = CAN_BPS_500K,
        .fd_baud_rate = CAN_FD_BPS_2M,
        .point = CAN_SAMPLE_PERCENT_80,
        .can_mode = NORMAL_OPREATION,
    };

    can_filter_list_t std_list = {0};
    can_filter_list_t ext_list = {0};

    errcode_t ret = uapi_can_init(CAN_BUS_0, &global_config, &std_list, &ext_list);
    if (ret != ERRCODE_SUCC) {
        osal_printk("CAN init failed: 0x%x\n", ret);
        return ERRCODE_FAIL;
    }

    ret = uapi_can_callback_register(CAN_BUS_0, can_fd_callback);
    if (ret != ERRCODE_SUCC) {
        osal_printk("CAN callback register failed: 0x%x\n", ret);
        return ERRCODE_FAIL;
    }

    osal_printk("CAN initialized successfully\n");
    return ERRCODE_SUCC;
}

static int can_fd_send_message(uint32_t can_id, uint8_t *data, uint8_t length)
{
    can_msg_attr_t msg_attr = {
        .std_id = can_id,
        .extend_id = 0,
        .rtr = 0,
        .ide = 0,
        .fdf = 0,
        .brs = 0,
    };

    return uapi_can_send_msg(CAN_BUS_0, &msg_attr, data, length);
}

static void can_fd_deinit(void)
{
    uapi_can_deinit(CAN_BUS_0);
}

注意事项

  1. 波特率配置:需要根据实际总线负载和距离选择合适的波特率,长距离通信应降低波特率

  2. 终端电阻:CAN总线两端需要安装120Ω终端电阻,用于阻抗匹配

  3. 过滤器配置:合理配置过滤器避免收到无关报文,过滤器配置错误可能导致无法接收报文

  4. CAN-FD特性

    • 支持灵活数据率,高速数据段可达5Mbps
    • 数据长度可达64字节
    • 需要对端设备同样支持CAN-FD
  5. 中断处理:回调函数中应避免阻塞操作,影响系统实时性

  6. 错误处理:关注CAN控制器错误状态,及时处理总线错误

CAN与CAN-FD对比:

特性 CAN CAN-FD
最高速率 1Mbps 5Mbps
数据长度 8字节 64字节
兼容性 CAN 2.0 兼容CAN 2.0

常见问题排查:

问题 可能原因 解决方案
无法发送 总线错误、波特率不匹配 检查接线、终端电阻、波特率配置
无法接收 过滤器配置错误、ID不匹配 检查过滤器配置、发送端ID
通信不稳定 干扰、终端电阻缺失 添加终端电阻、改善布线