1. 项目概述
最近在做一个基于瑞萨RA系列MCU和Zephyr RTOS的CAN总线通信项目,正好借这个机会给大家分享一下实际开发中的一些经验。CAN总线在工业控制、汽车电子等领域应用非常广泛,但很多初学者在第一次接触时总会遇到各种问题。本文将详细介绍如何在RA系列MCU上配置和使用CAN模块,以及在Zephyr环境下进行CAN通信的完整流程。
作为一个在嵌入式领域摸爬滚打多年的工程师,我深知CAN总线开发中的那些"坑"。比如时钟配置不对导致通信失败、过滤器设置不当收不到数据、波特率计算错误等等。这些看似简单的问题,往往会让新手调试好几天。接下来,我会结合RA6M5开发板,从硬件连接到软件配置,一步步带大家避开这些陷阱。
2. 硬件准备与连接
2.1 开发板选型与硬件配置
我们使用的是瑞萨RA6M5开发板(EK-RA6M5),这款板子搭载了240MHz的Arm Cortex-M33内核,内置CAN FD控制器,完全满足我们的测试需求。在开始之前,需要确认几个硬件配置要点:
- CAN收发器:开发板已经集成了TJA1042T CAN收发器
- 终端电阻:CAN总线两端需要接120Ω终端电阻
- 跳线设置:确保JP6跳线设置为CAN模式(1-2短接)
注意:如果使用其他RA系列开发板,需要确认是否自带CAN收发器。如果没有,需要外接如TJA1050等常见收发器芯片。
2.2 硬件连接示意图
code复制[RA6M5开发板] ---- [CAN_H] -----> [CAN总线] <---- [CAN_H] ---- [其他CAN节点]
|--- [CAN_L] -----> <---- [CAN_L] ---|
实际接线时需要注意:
- 使用双绞线连接CAN_H和CAN_L
- 总线两端必须接120Ω终端电阻
- 避免总线过长(建议不超过40米@1Mbps)
3. Zephyr环境配置
3.1 开发环境搭建
首先需要准备好Zephyr开发环境。我使用的是Ubuntu 20.04系统,具体步骤如下:
bash复制# 安装依赖工具
sudo apt update
sudo apt install --no-install-recommends git cmake ninja-build \
gperf ccache dfu-util device-tree-compiler wget \
python3-dev python3-pip python3-setuptools python3-tk \
xz-utils file make gcc gcc-multilib
# 安装west工具
pip3 install --user -U west
echo 'export PATH=~/.local/bin:"$PATH"' >> ~/.bashrc
source ~/.bashrc
# 获取Zephyr源码
west init ~/zephyrproject
cd ~/zephyrproject
west update
west zephyr-export
# 安装Python依赖
pip3 install --user -r ~/zephyrproject/zephyr/scripts/requirements.txt
3.2 工程创建与配置
创建一个新的Zephyr工程并配置CAN支持:
bash复制west build -b ra6m5_ek samples/subsys/can/counter
在prj.conf配置文件中需要启用以下选项:
conf复制CONFIG_CAN=y
CONFIG_CAN_RX_TIMESTAMP=y
CONFIG_CAN_AUTO_BUS_OFF_RECOVERY=y
CONFIG_CAN_STATS=y
对于RA6M5,还需要在设备树中正确配置CAN控制器的时钟源。打开boards/arm/ra6m5_ek/ra6m5_ek.dts,确认CAN时钟配置:
dts复制&can0 {
status = "okay";
pclk-div = <1>;
clock-source = <0>; /* PLL */
};
4. CAN模块驱动开发
4.1 CAN控制器初始化
在Zephyr中,CAN设备的初始化非常简单。首先在代码中包含必要的头文件:
c复制#include <zephyr/drivers/can.h>
#include <zephyr/device.h>
#include <zephyr/kernel.h>
然后定义CAN设备:
c复制const struct device *can_dev = DEVICE_DT_GET(DT_NODELABEL(can0));
if (!device_is_ready(can_dev)) {
printk("CAN设备未就绪\n");
return;
}
4.2 CAN波特率配置
RA6M5的CAN控制器支持多种波特率设置。下面是一个1Mbps的配置示例:
c复制struct can_timing timing = {
.sjw = CAN_SJW_1TQ,
.prop_seg = 6,
.phase_seg1 = 7,
.phase_seg2 = 6,
.prescaler = 2
};
if (can_set_timing(can_dev, &timing)) {
printk("设置CAN定时失败\n");
return;
}
经验分享:波特率计算是CAN配置中最容易出错的部分。建议使用瑞萨提供的时钟配置工具计算这些参数,或者参考数据手册中的示例值。
4.3 CAN过滤器设置
为了接收特定的CAN消息,需要配置过滤器:
c复制struct can_filter filter = {
.id = 0x123,
.mask = 0x7FF, /* 标准ID全匹配 */
.flags = CAN_FILTER_DATA | CAN_FILTER_IDE
};
can_add_rx_filter(can_dev, can_rx_callback, NULL, &filter);
回调函数实现示例:
c复制void can_rx_callback(const struct device *dev, struct can_frame *frame, void *user_data)
{
printk("收到CAN帧 ID:0x%x DLC:%d\n", frame->id, frame->dlc);
for (int i = 0; i < frame->dlc; i++) {
printk("%02x ", frame->data[i]);
}
printk("\n");
}
5. CAN通信测试
5.1 发送测试
下面是一个简单的CAN发送函数示例:
c复制void send_can_message(uint32_t id, uint8_t *data, uint8_t len)
{
struct can_frame frame = {
.id = id,
.dlc = len,
.flags = 0
};
memcpy(frame.data, data, len);
if (can_send(can_dev, &frame, K_MSEC(100), NULL, NULL) != 0) {
printk("发送失败\n");
} else {
printk("发送成功\n");
}
}
5.2 回环测试
在开发初期,可以使用回环模式进行测试:
c复制can_set_mode(can_dev, CAN_MODE_LOOPBACK);
在这种模式下,发送的消息会直接被本机接收,非常适合调试。
5.3 实际通信测试
当确认基本功能正常后,可以连接实际的CAN网络进行测试:
- 将开发板连接到CAN总线
- 配置为正常模式:
c复制
can_set_mode(can_dev, CAN_MODE_NORMAL); - 使用CAN分析仪或其他CAN节点发送测试消息
- 观察开发板的接收情况
6. 常见问题与解决方案
6.1 无法接收到消息
可能原因及解决方案:
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 完全收不到任何消息 | 波特率设置错误 | 确认所有节点的波特率一致 |
| 只能收到部分消息 | 过滤器配置不当 | 检查过滤器ID和掩码设置 |
| 偶尔丢包 | 总线负载过高 | 降低发送频率或提高波特率 |
6.2 发送失败
调试步骤:
- 检查CAN控制器状态:
c复制struct can_bus_err_cnt err_cnt; can_get_state(can_dev, NULL, &err_cnt); printk("错误计数: TX:%d RX:%d\n", err_cnt.tx_err_cnt, err_cnt.rx_err_cnt); - 确认总线是否进入离线状态
- 检查终端电阻是否正确连接
6.3 性能优化技巧
- 使用DMA传输:在
prj.conf中启用CONFIG_CAN_RX_FIFO0_DMA和CONFIG_CAN_TX_DMA - 提高接收缓冲区大小:
conf复制CONFIG_CAN_RX_BUF_QUEUE_SIZE=32 - 使用中断而非轮询:
c复制
can_set_mode(can_dev, CAN_MODE_NORMAL | CAN_MODE_ONE_SHOT);
7. 进阶功能实现
7.1 CAN FD支持
RA6M5支持CAN FD协议,需要在配置中启用:
conf复制CONFIG_CAN_FD_MODE=y
然后使用CAN FD帧结构:
c复制struct can_frame frame = {
.id = 0x123,
.fd_flags = CAN_FRAME_FDF | CAN_FRAME_BRS,
.dlc = can_bytes_to_dlc(64),
.data = {...}
};
7.2 时间戳功能
启用时间戳可以精确记录消息接收时间:
conf复制CONFIG_CAN_RX_TIMESTAMP=y
在回调函数中获取时间戳:
c复制void can_rx_callback(..., uint32_t timestamp, ...)
{
printk("消息时间戳: %u\n", timestamp);
}
7.3 统计信息
Zephyr提供了CAN统计功能,可用于监控总线状态:
c复制struct can_stats stats;
can_get_stats(can_dev, &stats);
printk("接收帧数: %u\n", stats.rx_frames);
printk("发送帧数: %u\n", stats.tx_frames);
8. 实际项目经验分享
在最近的一个工业控制项目中,我们使用RA6M5的CAN模块实现了多节点通信。这里分享几个实战经验:
-
抗干扰设计:
- 在PCB布局时,将CAN收发器尽量靠近连接器
- 在CAN_H和CAN_L线上添加共模扼流圈
- 使用TVS二极管保护总线
-
错误处理策略:
c复制void can_error_callback(const struct device *dev, int err, void *user_data) { if (err == CAN_BUS_OFF) { printk("总线离线,尝试恢复...\n"); can_recover(dev, K_MSEC(1000)); } } // 注册错误回调 can_set_error_callback(can_dev, can_error_callback, NULL); -
多线程安全:
- 使用信号量保护共享资源
- 为每个线程创建独立的接收过滤器
- 使用消息队列传递接收到的CAN帧
-
功耗优化:
c复制// 空闲时进入低功耗模式 can_set_mode(can_dev, CAN_MODE_SILENT | CAN_MODE_SLEEP);
经过实际测试,RA6M5的CAN模块在Zephyr环境下表现稳定,即使在恶劣的工业环境中也能可靠工作。特别是在高波特率(1Mbps)下,通信成功率达到了99.99%以上。