【转载说明】
本文首发于电子技术论坛,原文链接

瑞萨开发板介绍

本次试用的是瑞萨FPB-RA6E2开发板,配备R7FA6E2BB3CFM微控制器,是专门用于各种原型开发的评估板,内置J-Link仿真器电路,极大地方便了开发者编写和调试程序。收到实物如下图所示。

在这里插入图片描述

这款开发板拥有丰富的外设功能和配置,如下图所示。
在这里插入图片描述

zephyr介绍

zephyr是由linux基金会托管的开源实时操作系统,采用Apache 2.0协议。使用zephyr API开发项目,可以在板间非常轻松的实现项目迁移。其主流的开发方式以命令行工具为主,类似于linux开发。

本次开发使用vscode工具,具体环境配置教程参考链接如下:

跟着上述教程可以轻松完成开发环境配置。

配置环境过程中遇到的问题:下载过程不稳定,因为有一些依赖包需要在github下载,可以链接不稳定,下载失败,重新下载,多次尝试。

vscode环境配置问题:我是使用python的虚拟环境进行开发,但是配置好依赖包以后,使用zephyr IDE运行命令,使用的是默认的bash,报没有安装相关库的错误警告,找不到west,直接创建软链接,将其放在默认路径进行跳转解决。(很少使用python和vscode,能跑就不想继续折腾了)。下面将正式上手开发,首先从点灯开始。

zephyr的LED工程

在配置好环境以后,需要验证配置的环境是否正常,生成的代码是否能够在开发板上成功跑通。在零基础的情况下,参考瑞萨RA × Zephyr 系列教程4生成Blinky Sample代码,具体操作步骤详见教程4。

/*
 * Copyright (c) 2016 Intel Corporation
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <stdio.h>
#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>

/* 1000 msec = 1 sec */
#define SLEEP_TIME_MS   1000

/* The devicetree node identifier for the "led0" alias. */
#define LED0_NODE DT_ALIAS(led0)

/*
 * 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(LED0_NODE, gpios);

int main(void)
{
	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");
		k_msleep(SLEEP_TIME_MS);
	}
	return 0;
}

上面是生成的main.c代码的全部内容,可以看到代码的格式与常见的STM32配置程序有所不同,在while(1)中的主要代码逻辑为进行led的电平翻转,并通过串口输出当前led的状态,翻转间隔时间为#define SLEEP_TIME_MS 1000

直接将代码下载到开发板,观察led状态,可以看到led状态按照代码的逻辑进行闪烁,并在串口输出了led的状态,如下图所示:

在这里插入图片描述

成功跑通Blinky Sample例程,说明环境配置成功,并且能够顺利下载到开发板,那么现在就可以愉快地玩耍了。

GPIO功能测评

GPIO做为一个开发板的常用对外接口,通常连接外部LED或按键,瑞萨FPB-RA6E2这款开发板拥有两个用户LED和一个用户按键。具体分布如下图所示:

在这里插入图片描述

本次实验:
功能:LED、按键

效果:按键中断控制LED1的翻转,主循环延时控制LED2的翻转(时间为1s)

实现代码如下所示:

#include <stdio.h>
#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>

/* 1. 获取设备树节点:LED0、LED1 和 SW0 (按键) */
#define LED0_NODE DT_ALIAS(led0)
#define LED1_NODE DT_ALIAS(led1)
#define SW0_NODE  DT_ALIAS(sw0)

/* 2. 定义 GPIO 结构体 */
static const struct gpio_dt_spec led0 = GPIO_DT_SPEC_GET(LED0_NODE, gpios);
static const struct gpio_dt_spec led1 = GPIO_DT_SPEC_GET(LED1_NODE, gpios);
/* 获取按键的 GPIO 信息,注意这里增加了 GPIO_IN 标志的获取 */
static const struct gpio_dt_spec button = GPIO_DT_SPEC_GET(SW0_NODE, gpios);

/* 3. 定义中断回调结构体变量 */
static struct gpio_callback button_cb_data;

/* 4. 中断服务函数 (按键按下后会自动执行这里) */
void button_pressed(const struct device *dev, struct gpio_callback *cb, uint32_t pins)
{
    /* 翻转 LED 状态 */
    gpio_pin_toggle_dt(&led0);
    printf("按键已按下!LED 状态翻转。\n");
}

int main(void)
{
    int ret;

    /* A. 初始化 LED */
    if ((!gpio_is_ready_dt(&led0))||(!gpio_is_ready_dt(&led1))) {
        printf("Error: LED device not ready\n");
        return 0;
    }
    /* 配置 LED 为输出,初始状态为灭 (INACTIVE) */
    ret = gpio_pin_configure_dt(&led0, GPIO_OUTPUT_INACTIVE);
    if (ret < 0) return 0;
	ret = gpio_pin_configure_dt(&led1, GPIO_OUTPUT_INACTIVE);
    if (ret < 0) return 0;

    /* B. 初始化 按键 */
    if (!gpio_is_ready_dt(&button)) {
        printf("Error: Button device not ready\n");
        return 0;
    }
    
    /* B1. 配置按键为输入 (Input) */
    /* * GPIO_INPUT: 输入模式
     * GPIO_PULL_UP: 启用上拉电阻 (防止按键悬空乱跳)
     */
    ret = gpio_pin_configure_dt(&button, GPIO_INPUT);
    if (ret < 0) {
        printf("Error: Failed to configure button gpio\n");
        return 0;
    }

    /* B2. 配置中断触发方式 */
    /* GPIO_INT_EDGE_TO_ACTIVE: 当按键被按下那一瞬间(边沿触发)产生中断 */
    ret = gpio_pin_interrupt_configure_dt(&button, GPIO_INT_EDGE_TO_ACTIVE);
    if (ret != 0) {
        printf("Error: Failed to configure interrupt\n");
        return 0;
    }

    /* B3. 注册中断回调函数 */
    gpio_init_callback(&button_cb_data, button_pressed, BIT(button.pin));
    gpio_add_callback(button.port, &button_cb_data);

    printf("按键控制 LED 程序已启动。请按板子上的 S1 键。\n");

    /* 主循环 */
    while (1) {
        k_msleep(1000); 
		gpio_pin_toggle_dt(&led1);
    }
    return 0;
}

在代码中注册自己编写的中断处理函数,进入一次中断就翻转一次LED0的状态,同时使用串口输出信息。在主循环里面定时翻转LED1的状态,表示程序运行正常。

在这里插入图片描述

上图为串口输出内容,随着按键的按下,正确显示了中断函数里面的输出内容。

UART功能测试

前面的试验中已经展示了输出功能,串口不仅可以输出日志信息,还可以接收外部传递的信息。这个试验将展示使用串口的接收功能来控制LED灯的亮灭。

串口接收信息 LED灯状态
ON
OFF
TOGGLE 翻转

同时要在prj.conf文件中启用相关配置

启用控制台子系统
CONFIG_CONSOLE_SUBSYS=y
启用行读取功能
CONFIG_CONSOLE_GETLINE=y

主程序如下所示:

#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/console/console.h>
#include <string.h>

/* --- 1. 定义 LED 硬件资源 --- */
/* 获取设备树中定义的 led0 (对应板子上的 LED1) */
#define LED0_NODE DT_ALIAS(led0)
static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED0_NODE, gpios);

int main(void)
{
    int ret;

    /* --- 2. 初始化 LED --- */
    if (!gpio_is_ready_dt(&led)) {
        printk("Error: LED device not ready\n");
        return 0;
    }
    /* 配置为输出,默认关闭 */
    ret = gpio_pin_configure_dt(&led, GPIO_OUTPUT_INACTIVE);
    if (ret < 0) return 0;

    /* --- 3. 初始化控制台 (行读取模式) --- */
    /* 注意:使用的是 console_getline_init() 而不是 console_init() */
    console_getline_init();

    printk("\n=== Zephyr LED Controller ===\n");
    printk("Type 'ON', 'OFF' or 'TOGGLE' and press Enter.\n");

    while (1) {
        /* 打印提示符 */
        printk("uart:~$ ");

        /* --- 4. 获取一整行输入 (阻塞等待) --- */
        char *s = console_getline();
        /* --- 5. 解析命令 --- */
        /* 这里的 s 就是完整的一行字符串,直接比较即可 */        
        if (strcmp(s, "ON") == 0) {
            gpio_pin_set_dt(&led, 1);
            printk("Result: LED Turned ON\n");
        } 
        else if (strcmp(s, "OFF") == 0) {
            gpio_pin_set_dt(&led, 0);
            printk("Result: LED Turned OFF\n");
        }
        else if (strcmp(s, "TOGGLE") == 0) {
            gpio_pin_toggle_dt(&led);
            printk("Result: LED Toggled\n");
        }
        else {
            /* 只是打印出来,不做操作 */
            printk("Unknown command: '%s'\n", s);
        }
    }
    return 0;
}

通过串口调试助手发送信息,查看开发板上LED灯的状态。

在这里插入图片描述

通过这个串口调试助手,可以看出,开发板能够正确接收到发送的数据并正确解析,执行LED的状态控制程序。

zephyr是一个实时操作系统,意味不像裸机一样只能单线程工作,可以执行多线程任务,在前面的UART程序中,使用的 char *s = console_getline();会使的程序停留在这里等待内容输入,并不执行后面的程序。因此可以单独创建一个线程运行LED1的闪烁任务,标志系统运行正常,然后在主线程里面还是使用UART控制LED0的状态。

具体程序如下所示:

#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/console/console.h>
#include <string.h>

/* --- 1. 定义 LED 硬件资源 --- */
#define LED0_NODE DT_ALIAS(led0)
#define LED1_NODE DT_ALIAS(led1) 

static const struct gpio_dt_spec led0 = GPIO_DT_SPEC_GET(LED0_NODE, gpios);
static const struct gpio_dt_spec led1 = GPIO_DT_SPEC_GET(LED1_NODE, gpios); 

/* --- 2. 定义新线程的参数 --- */
/* 栈大小:1024字节对于简单的点灯足够了 */
#define BLINK_STACK_SIZE 1024
/* 优先级:7 (Zephyr中数字越小优先级越高,7是常用应用优先级) */
#define BLINK_PRIORITY 7

/* --- 3. 编写 LED1 闪烁线程的入口函数 --- */
/* 这是一个独立的死循环,专门负责 LED1 */
void blink_led1_entry(void *p1, void *p2, void *p3)
{
    int ret;

    /* 在线程内部初始化 LED1,确保资源独立 */
    if (!gpio_is_ready_dt(&led1)) {
        return;
    }
    ret = gpio_pin_configure_dt(&led1, GPIO_OUTPUT_ACTIVE);
    if (ret < 0) return;

    while (1) {
        gpio_pin_toggle_dt(&led1);
        /* 这个线程每 1000ms 醒来工作一次 */
        printk("[Thread 1] LED Toggled\n");
        k_msleep(1000); 
    }
}

/* --- 4. 使用宏静态创建并启动线程 --- */
/* * K_THREAD_DEFINE(
 * 线程名, 栈大小, 入口函数, 
 * 参数1, 参数2, 参数3, 
 * 优先级, 选项, 延迟启动时间
 * )
 */
K_THREAD_DEFINE(blink_tid, BLINK_STACK_SIZE, blink_led1_entry, NULL, NULL, NULL,
                BLINK_PRIORITY, 0, 0);


/* --- 5. 主线程 (main) --- */
/* main 函数本身就是一个线程,我们用它来处理串口输入 */
int main(void)
{
    int ret;

    /* 初始化 LED0 */
    if ((!gpio_is_ready_dt(&led0))) {
        printk("Error: LED device not ready\n");
        return 0;
    }
    ret = gpio_pin_configure_dt(&led0, GPIO_OUTPUT_INACTIVE);
    if (ret < 0) return 0;

    /* 初始化控制台 (行读取模式) */
    console_getline_init();

    printk("\n=== Zephyr Multi-thread Demo ===\n");
    printk("Thread 1: Blinking LED1 automatically.\n");
    printk("Thread 2: Waiting for UART commands for LED0.\n");

    while (1) {
        printk("uart:~$ ");

        /* 这里依然会阻塞,但只会阻塞 main 线程 */
        /* blink_led1_entry 线程依然会在后台欢快地运行 */
        char *s = console_getline();

        if (strcmp(s, "ON") == 0) {
            gpio_pin_set_dt(&led0, 1);
            printk("[Thread 2] Result: LED0 Turned ON\n");
        } 
        else if (strcmp(s, "OFF") == 0) {
            gpio_pin_set_dt(&led0, 0);
            printk("[Thread 2] Result: LED0 Turned OFF\n");
        }
        else if (strcmp(s, "TOGGLE") == 0) {
            gpio_pin_toggle_dt(&led0);
            printk("[Thread 2] Result: LED0 Toggled\n");
        }
        else {
            printk("[Thread 2] Unknown command: '%s'\n", s);
        }
        
        /* 不需要 k_msleep,console_getline 的阻塞就是最好的让渡 CPU 的方式 */
    }
    return 0;
}

在这个里面发现线程的创建并不是在main函数里面,而是在外面,点进去一看,使用的是#define,

在这里插入图片描述

在编译的时候就创建了这个子线程。

在这里插入图片描述

可以看出两个线程独立运行, char *s = console_getline();不能影响到LED1的闪烁。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐