【瑞萨RA x Zephyr评测】三、CAN模块测试


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,而且熟悉了线程的创建和新工程的创建
相关推荐
charlie11451419113 小时前
嵌入式的现代C++教程——constexpr与设计技巧
开发语言·c++·笔记·单片机·学习·算法·嵌入式
REDcker18 小时前
RTCP 刀尖点跟随技术详解
c++·机器人·操作系统·嵌入式·c·数控·机床
荆楚闲人19 小时前
GPIO内部结构中的施密特触发器(肖特基触发器)作用及原理
嵌入式·gpio·施密特触发器·肖特基触发器
ベadvance courageouslyミ1 天前
嵌入式硬件基础
嵌入式硬件·51单片机·嵌入式·数码管·二极管
一枝小雨1 天前
【OTA专题】15 实现App后台无感下载固件
stm32·单片机·嵌入式·ota·bootloader
凉、介2 天前
深入 QEMU Guest Agent:虚拟机内外通信的隐形纽带
c语言·笔记·学习·嵌入式·虚拟化
hugerat2 天前
在AI的帮助下,用C++构造微型http server
linux·c++·人工智能·http·嵌入式·嵌入式linux
linweidong2 天前
AUTOSAR配置文件(ARXML)版本不一致时如何管理?
嵌入式·autosar
星源~2 天前
Zephyr - MCU 开发快速入门指南
单片机·嵌入式硬件·物联网·嵌入式开发·zephyr