
1. 简介📢
在上一篇中我们以FPB-RA6E2上的ADC为例展开,熟悉了Zephyr设备树中的pin脚定义规则,本篇我们继续以FPB-RA6E2上的CAN为例展开,欢迎大家收藏、转发,多多交流哈🤗😃🎉🪅📢
🏳️🌈🏳️🌈🏳️🌈🏳️🌈🏳️🌈🏳️🌈🏳️🌈🏳️🌈🏳️🌈🏳️🌈🏳️🌈🏳️🌈🏳️🌈🏳️🌈🏳️🌈🏳️🌈🏳️🌈🏳️🌈🏳️🌈🏳️🌈🏳️🌈🏳️🌈🏳️🌈🏳️🌈🏳️🌈🏳️🌈🏳️🌈🏳️🌈🏳️🌈🏳️🌈🏳️🌈🏳️🌈🏳️🌈🏳️🌈🏳️🌈🏳️🌈🏳️🌈🏳️🌈🏳️🌈🏳️🌈🏳️🌈🏳️🌈🏳️🌈🏳️🌈
🚀 ------评测系列跳转------🚀
瑞萨FPB-RA6E2开发板快速入门
【瑞萨RA x Zephyr评测】一、点亮FPB-RA6E2开发板
【瑞萨RA x Zephyr评测】二、ADC模块测试
2. 基础知识
对不熟悉CAN总线的小伙伴建议先看下面的几个资料:
开始之前,也需要熟悉一下CANFD在zephyr中的一些配置文件、设备树绑定文件等。
zephyr\dts\bindings\can\renesas,ra-canfd.yaml
zephyr\dts\bindings\can\renesas,ra-canfd-global.yaml
zephyr\dts\bindings\can\can-controller.yaml
zephyr\dts\arm\renesas\ra\ra6\r7fa6e2bx.dtsi
shell
... ...
canfd_global: canfd_global@400b0000 {
compatible = "renesas,ra-canfd-global";
interrupts = <40 1>, <41 1>;
interrupt-names = "rxf", "glerr";
clocks = <&pclkb 0 0>, <&pclka 0 0>;
clock-names = "opclk", "ramclk";
dll-max-freq = <DT_FREQ_M(40)>;
reg = <0x400b0000 0x2000>;
status = "disabled";
canfd0: canfd0 {
compatible = "renesas,ra-canfd";
channel = <0>;
interrupts = <43 12>, <44 12>, <45 12>;
interrupt-names = "err", "tx", "rx";
clocks = <&canfdclk MSTPC 27>;
clock-names = "dllclk";
status = "disabled";
};
};
... ...
3. 测试工程
参考:REN_r20qs0036eg0100_fpb-ra6e2_qsg_QSG_20230621.pdf和REN_r20ut5161eg0100_fpb-ra6e2_user_manual_MAT_20230621.pdf两个开发板相关的文档,都没有CAN pin脚的描述,于是在官网又下载了芯片的Datasheet,首先确认硬件接法:

再查阅板子原理图,找到对应的扩展pin位置:

于是,连接硬件,如下图:

为了测试FPB-RA6E2的CANFD功能,新建一个独立的工程,命名为canfd_demo,目录结构如下:
shell
// zephyr\samples\renesas\fpb_ra6e2\canfd_demo
canfd_demo
|-- boards
-- fpb_ra6e2.overlay
|-- src
-- main.c
-- CMakeLists.txt
-- prj.conf
其中,fpb_ra6e2.overlay文件:
shell
/ {
chosen {
zephyr,canbus = &canfd0;
};
};
&pinctrl {
canfd0_default: canfd0_default {
group1 {
/* CRX1 CTX1 */
psels = <RA_PSEL(RA_PSEL_CANFD, 4, 2)>,
<RA_PSEL(RA_PSEL_CANFD, 4, 1)>;
drive-strength = "high";
};
};
};
&canfd_global {
status = "okay";
/* 关键:clocks属性引用 .dtsi 中已定义的 &pclkb 和 &pclka */
/* 确保这两个父时钟在系统中是存在的且已启用 */
clocks = <&pclkb 0 0>, <&pclka 0 0>;
clock-names = "opclk", "ramclk";
/* 设置DLL最大频率,必须与硬件能力匹配 */
dll-max-freq = <DT_FREQ_M(40)>; /* 40 MHz */
/* 3. 启用并配置通道0 */
canfd0 {
status = "okay";
pinctrl-0 = <&canfd0_default>;
pinctrl-names = "default";
/* 关键:clocks属性必须引用 .dtsi 中定义的 &canfdclk 控制器 */
clocks = <&canfdclk MSTPC 27>;
clock-names = "dllclk";
/* 配置通信参数 */
rx-max-filters = <5>;
bitrate = <500000>;
sample-point = <875>;
bitrate-data = <2000000>; /* CAN FD 数据段速率 */
sample-point-data = <875>;
};
};
&hoco {
status = "okay";
};
&pll {
status = "okay";
};
&canfdclk {
status = "okay";
clocks = <&pll>;
div = <5>;
};
测试程序,新建两个线程,一个线程闪灯,一个线程发送CAN报文,接收报文,在回调函数中打印ID:
c
/*
* Copyright (c) 2016 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/can.h>
/* 1. 定义线程栈大小 */
#define THREAD1_STACK_SIZE 1024
#define THREAD2_STACK_SIZE 1024
/* 2. 定义线程优先级 */
#define THREAD1_PRIORITY 5 // 数字越小优先级越高
#define THREAD2_PRIORITY 6
/* 3. 定义线程栈空间(内存对齐) */
K_THREAD_STACK_DEFINE(thread1_stack, THREAD1_STACK_SIZE);
K_THREAD_STACK_DEFINE(thread2_stack, THREAD2_STACK_SIZE);
/* 4. 定义线程结构体 */
struct k_thread thread1_data, thread2_data;
/* Devicetree */
#define CANBUS_NODE DT_CHOSEN(zephyr_canbus)
#define BUTTON_NODE DT_ALIAS(sw0)
#define BUTTON_NAME DT_PROP_OR(BUTTON_NODE, label, "sw0")
#if DT_NODE_EXISTS(BUTTON_NODE)
struct button_callback_context {
struct gpio_callback callback;
struct k_sem sem;
};
static void button_callback(const struct device *port, struct gpio_callback *cb,
gpio_port_pins_t pins)
{
struct button_callback_context *ctx =
CONTAINER_OF(cb, struct button_callback_context, callback);
k_sem_give(&ctx->sem);
}
#endif /* DT_NODE_EXISTS(BUTTON_NODE) */
static void can_tx_callback(const struct device *dev, int error, void *user_data)
{
struct k_sem *tx_queue_sem = user_data;
k_sem_give(tx_queue_sem);
}
/* 1000 msec = 1 sec */
#define SLEEP_TIME_MS 1000
/* The devicetree node identifier for the "led0" alias. */
#define LED0_NODE DT_ALIAS(led0)
/* The devicetree node identifier for the "led1" alias. */
#define LED1_NODE DT_ALIAS(led1)
/*
* A build error on this line means your board is unsupported.
* See the sample documentation for information on how to fix this.
*/
static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED1_NODE, gpios);
void thread_init(void);
void thread1_function(void *arg1, void *arg2, void *arg3);
void thread2_function(void *arg1, void *arg2, void *arg3);
void canfd_receive_init(void);
int main(void)
{
/* 创建线程1(使用K_THREAD_DEFINE的静态方式) */
k_tid_t thread1_id = k_thread_create(
&thread1_data,
thread1_stack,
K_THREAD_STACK_SIZEOF(thread1_stack),
thread1_function,
NULL, NULL, NULL, // 三个参数都传NULL
THREAD1_PRIORITY,
0, // 线程选项:0表示无特殊选项
K_FOREVER
);
printk("线程1创建成功,ID: %p\n", thread1_id);
/* 8. 创建线程2(传递参数) */
k_tid_t thread2_id = k_thread_create(
&thread2_data,
thread2_stack,
K_THREAD_STACK_SIZEOF(thread2_stack),
thread2_function,
NULL, NULL, NULL,
THREAD2_PRIORITY,
0,
K_FOREVER
);
printk("线程2创建成功,ID: %p\n", thread2_id);
k_thread_start(&thread1_data);
k_thread_start(&thread2_data);
return 0;
}
/* 5. 定义线程函数 */
void thread1_function(void *arg1, void *arg2, void *arg3)
{
int ret;
bool led_state = true;
if (!gpio_is_ready_dt(&led)) {
return 0;
}
ret = gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE);
if (ret < 0) {
return 0;
}
while (1) {
ret = gpio_pin_toggle_dt(&led);
if (ret < 0) {
return 0;
}
led_state = !led_state;
printf("LED state: %s\n", led_state ? "ON" : "OFF");
/* 让出CPU,使其他线程可以运行 */
k_yield();
k_msleep(SLEEP_TIME_MS);
}
}
void thread2_function(void *arg1, void *arg2, void *arg3)
{
const struct device *can_dev = DEVICE_DT_GET(DT_NODELABEL(canfd0));
struct can_frame frame;
int ret;
if (!device_is_ready(can_dev)) {
printk("CAN设备未就绪!\n");
return;
}
ret = can_set_mode(can_dev, CAN_MODE_NORMAL); //CAN_MODE_FD
if (ret != 0) {
printk("设置CAN模式失败: %d\n", ret);
return;
}
ret = can_start(can_dev);
if (ret != 0) {
printk("无法启动CAN控制器!\n");
return;
}
printk("CAN控制器已启动,波特率:500kbps\n");
frame.id = 0x123;
frame.dlc = can_bytes_to_dlc(8);
// 设置帧类型(新API使用枚举)
frame.flags = CAN_FRAME_IDE; // 使用标准标识符(11位ID
// frame.flags |= CAN_FRAME_FDF; // 标识为CAN FD帧[citation:2]
// frame.flags |= CAN_FRAME_BRS; // 启用数据段波特率提升[citation:2]
int data_length = can_dlc_to_bytes(frame.dlc);
// 确保循环不超过数组边界
for (int i = 0; i < data_length && i < sizeof(frame.data); i++) {
frame.data[i] = i;
}
canfd_receive_init();
while(1)
{
ret = can_send(can_dev, &frame, K_FOREVER, NULL, NULL);
if(ret != 0)
{
printk("发送失败: %d\n", ret);
}
k_yield();
k_msleep(SLEEP_TIME_MS);
}
}
void thread_init(void)
{
printk("Zephyr多线程示例开始\n");
/* 传递给线程2的参数 */
char *thread2_msg = "来自主线程的问候";
int thread2_interval = 500;
/* 7. 创建线程1(使用K_THREAD_DEFINE的静态方式) */
k_tid_t thread1_id = k_thread_create(
&thread1_data,
thread1_stack,
K_THREAD_STACK_SIZEOF(thread1_stack),
thread1_function,
NULL, NULL, NULL, // 三个参数都传NULL
THREAD1_PRIORITY,
0, // 线程选项:0表示无特殊选项
K_NO_WAIT // 不等待,立即启动线程
);
printk("线程1创建成功,ID: %p\n", thread1_id);
/* 8. 创建线程2(传递参数) */
k_tid_t thread2_id = k_thread_create(
&thread2_data,
thread2_stack,
K_THREAD_STACK_SIZEOF(thread2_stack),
thread2_function,
thread2_msg, &thread2_interval, NULL, // 传递两个参数
THREAD2_PRIORITY,
0,
K_MSEC(100) // 延迟100ms启动
);
printk("线程2创建成功,ID: %p\n", thread2_id);
}
// CAN FD接收回调函数
static void canfd_rx_callback(const struct device *dev, struct can_frame *frame, void *user_data)
{
// 判断是否为CAN FD帧
if (frame->flags & CAN_FRAME_FDF) {
printk("收到CAN FD帧, ID:0x%x, DLC:%d, 数据长度:%d字节\n",
frame->id, frame->dlc, can_dlc_to_bytes(frame->dlc));
// 注意:can_dlc_to_bytes() 用于将DLC解码为实际字节数[citation:2]
// 处理数据...
} else {
printk("收到经典CAN帧, ID:0x%x\n", frame->id);
}
}
// 初始化接收的函数
void canfd_receive_init(void)
{
const struct device *can_dev = DEVICE_DT_GET(DT_NODELABEL(canfd0));
struct can_filter filter;
int filter_id;
// 配置接收过滤器:例如接收ID为0x120-0x125的标准帧
filter.id = 0x120;
filter.mask = CAN_STD_ID_MASK & 0x7F0; // 匹配前7位(可变部分为后4位)
filter.flags = 0; // 标准帧
// 添加过滤器,绑定回调函数
filter_id = can_add_rx_filter(can_dev, canfd_rx_callback, NULL, &filter);
if (filter_id < 0) {
printk("添加接收过滤器失败: %d\n", filter_id);
} else {
printk("CAN FD接收过滤器已添加, ID:%d\n", filter_id);
}
}
CmakeLists.txt文件比较简单,参考其他例程的:
shell
# SPDX-License-Identifier: Apache-2.0
cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(CANFD_DEMO)
target_sources(app PRIVATE src/main.c)
prj.conf工程配置文件,根据需要添加或者关闭功能:
shell
CONFIG_GPIO=y
CONFIG_CAN=y
CONFIG_SERIAL=y
CONFIG_UART_CONSOLE=y
CONFIG_SHELL=y
CONFIG_CAN_RENESAS_RA_CANFD=y
CONFIG_MULTITHREADING=y
CONFIG_INIT_STACKS=y
CONFIG_STACK_CANARIES=y
CONFIG_STDOUT_CONSOLE=y
# enable to use thread names
CONFIG_THREAD_NAME=y
CONFIG_SCHED_CPU_MASK=y
CONFIG_PRINTK=y
CONFIG_EARLY_CONSOLE=y
CONFIG_ASSERT=y
CONFIG_LOG=y
CONFIG_CAN_LOG_LEVEL_DBG=y
CONFIG_ASSERT=y
4. 测试结果
使用ZCANPro上位机收、发CAN报文:

串口打印如下图:

5. 总结
- CAN模块调了挺长时间,前期主要还是因为时钟的关系没有搞清楚
- CANFD和CANFD加速没有调通,有没有调通的小伙伴,告诉我一下
- 该模块的调试,不仅仅是调通了CAN,而且熟悉了线程的创建和新工程的创建