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 环境配置后执行:
构建成功后,使用一站式 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 串口。
使用方式
预期结果
该结果只验证芯片内部 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_sample、AT_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;
运行函数依次初始化、注册回调、发送和等待接收,结束时注销回调并去初始化:
这保证内部回环指令可以重复执行,不会占用后续 CAN 测试所需资源。
基于 CAN Demo 开发自己的应用
- 复制 samples/native_samples/can,修改组件名和构建开关,保留
diting_can_loopback_demo_run()这类可直接调用的业务入口。 - 从应用任务、系统服务或
app_run启动入口调用运行函数,不依赖串口命令触发。 - 根据网络修改波特率、采样点、过滤器、帧 ID 和数据长度。
- 正常总线模式将
can_mode改为NORMAL_OPREATION,并准备收发器、另一节点及终端电阻。 - 在回调中快速复制数据或发送消息,不执行长时间阻塞操作。
- 在所有失败路径和正常结束路径注销回调并调用
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总线架构:

开发流程
操作流程

步骤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
典型应用代码
#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);
}
注意事项
-
波特率配置:需要根据实际总线负载和距离选择合适的波特率,长距离通信应降低波特率
-
终端电阻:CAN总线两端需要安装120Ω终端电阻,用于阻抗匹配
-
过滤器配置:合理配置过滤器避免收到无关报文,过滤器配置错误可能导致无法接收报文
-
CAN-FD特性:
- 支持灵活数据率,高速数据段可达5Mbps
- 数据长度可达64字节
- 需要对端设备同样支持CAN-FD
-
中断处理:回调函数中应避免阻塞操作,影响系统实时性
-
错误处理:关注CAN控制器错误状态,及时处理总线错误
CAN与CAN-FD对比:
| 特性 | CAN | CAN-FD |
|---|---|---|
| 最高速率 | 1Mbps | 5Mbps |
| 数据长度 | 8字节 | 64字节 |
| 兼容性 | CAN 2.0 | 兼容CAN 2.0 |
常见问题排查:
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
| 无法发送 | 总线错误、波特率不匹配 | 检查接线、终端电阻、波特率配置 |
| 无法接收 | 过滤器配置错误、ID不匹配 | 检查过滤器配置、发送端ID |
| 通信不稳定 | 干扰、终端电阻缺失 | 添加终端电阻、改善布线 |