Zephyr RTOS 常用 API 详解:从 GPIO、线程到 CAN FD(附 STM32/FreeRTOS 对照)

文章目录

    • 前言
    • [一、Zephyr 工程的基本结构](#一、Zephyr 工程的基本结构)
    • [二、Kernel 基础 API:延时与系统时间](#二、Kernel 基础 API:延时与系统时间)
      • [2.1 常用 API](#2.1 常用 API)
      • [2.2 示例代码](#2.2 示例代码)
    • [三、Device Model:设备管理 API](#三、Device Model:设备管理 API)
      • [3.1 常用 API](#3.1 常用 API)
      • [3.2 示例](#3.2 示例)
    • 四、Devicetree:设备树常用宏
      • [4.1 常用宏](#4.1 常用宏)
      • [4.2 使用示例](#4.2 使用示例)
    • [五、GPIO API:输入、输出和中断](#五、GPIO API:输入、输出和中断)
      • [5.1 常用 API](#5.1 常用 API)
      • [5.2 完整示例:LED 闪烁](#5.2 完整示例:LED 闪烁)
    • [六、Thread API:线程管理](#六、Thread API:线程管理)
      • [6.1 常用 API](#6.1 常用 API)
      • [6.2 示例:创建两个线程](#6.2 示例:创建两个线程)
      • [6.3 线程优先级](#6.3 线程优先级)
    • [七、Semaphore API:信号量](#七、Semaphore API:信号量)
      • [7.1 常用 API](#7.1 常用 API)
      • [7.2 示例代码](#7.2 示例代码)
    • [八、Mutex API:互斥锁](#八、Mutex API:互斥锁)
      • [8.1 常用 API](#8.1 常用 API)
      • [8.2 示例代码](#8.2 示例代码)
    • [九、Message Queue API:消息队列](#九、Message Queue API:消息队列)
      • [9.1 常用 API](#9.1 常用 API)
      • [9.2 完整示例:两个线程通过队列通信](#9.2 完整示例:两个线程通过队列通信)
    • [十、Timer API:软件定时器](#十、Timer API:软件定时器)
      • [10.1 常用 API](#10.1 常用 API)
      • [10.2 示例代码](#10.2 示例代码)
    • [十一、Work Queue API:工作队列](#十一、Work Queue API:工作队列)
      • [11.1 常用 API](#11.1 常用 API)
      • [11.2 示例](#11.2 示例)
    • [十二、UART API:串口通信](#十二、UART API:串口通信)
      • [12.1 常用 API](#12.1 常用 API)
      • [12.2 Polling 发送示例](#12.2 Polling 发送示例)
      • [12.3 配置文件](#12.3 配置文件)
    • [十三、SPI API](#十三、SPI API)
    • [十四、I2C API](#十四、I2C API)
    • [十五、PWM 和 ADC API](#十五、PWM 和 ADC API)
      • [15.1 PWM](#15.1 PWM)
      • [15.2 ADC](#15.2 ADC)
    • [十六、CAN / CAN FD API](#十六、CAN / CAN FD API)
      • [16.1 常用 API](#16.1 常用 API)
      • [16.2 常见工作模式](#16.2 常见工作模式)
      • [16.3 CAN FD 帧标志位](#16.3 CAN FD 帧标志位)
      • [16.4 CAN FD 发送示例](#16.4 CAN FD 发送示例)
    • [十七、Logging API:日志调试](#十七、Logging API:日志调试)
    • [十八、Shell API:命令行调试](#十八、Shell API:命令行调试)
    • [十九、STM32 HAL / FreeRTOS 与 Zephyr API 对照表](#十九、STM32 HAL / FreeRTOS 与 Zephyr API 对照表)
    • [二十、Zephyr 开发中常见的几个坑](#二十、Zephyr 开发中常见的几个坑)
      • [20.1 Devicetree 和 Kconfig 混淆](#20.1 Devicetree 和 Kconfig 混淆)
      • [20.2 在中断中使用阻塞 API](#20.2 在中断中使用阻塞 API)
      • [20.3 不检查返回值](#20.3 不检查返回值)
      • [20.4 混淆逻辑电平和物理电平](#20.4 混淆逻辑电平和物理电平)
      • [20.5 将 RTOS 软件定时器当成精密硬件定时器](#20.5 将 RTOS 软件定时器当成精密硬件定时器)
      • [20.6 不同 Zephyr 版本混用 API](#20.6 不同 Zephyr 版本混用 API)
    • [二十一、推荐的 API 学习顺序](#二十一、推荐的 API 学习顺序)
    • 二十二、官方学习资源
    • 总结

前言

最近开始学习 Zephyr RTOS,发现 Zephyr 与传统的 STM32 HAL 开发方式存在较大的差异。

对于熟悉 STM32 或 FreeRTOS 的开发者来说,Zephyr 最大的学习难点并不完全在于 RTOS 内核,而是需要同时理解:

  • Kernel API:线程、调度、信号量、消息队列等。
  • Device Model:统一的硬件设备管理模型。
  • Devicetree:通过设备树描述硬件资源。
  • Kconfig:配置系统需要启用的功能。
  • Driver API:GPIO、UART、SPI、I2C、CAN 等外设驱动。
  • Logging / Shell:日志系统与命令行调试工具。

实际上,在日常嵌入式开发中,我们并不需要掌握 Zephyr 的全部 API,只需要熟悉常用的接口,就能够完成绝大部分嵌入式应用开发。

本文按照实际开发场景,对 Zephyr 常用 API 进行分类整理,并结合 STM32 HAL 和 FreeRTOS 进行对比,帮助初学者快速建立 Zephyr 开发思维。

官方文档: https://docs.zephyrproject.org/latest/

API 参考: https://docs.zephyrproject.org/latest/doxygen/html/

本文以 Zephyr 4.x 的常用 API 为基础。具体函数、配置项和驱动支持情况应以项目实际使用的 Zephyr 版本为准。


一、Zephyr 工程的基本结构

在学习 API 之前,首先需要认识 Zephyr 的应用工程结构。

一个最简单的 Zephyr 应用通常包含:

text 复制代码
my_zephyr_app/
├── CMakeLists.txt
├── prj.conf
├── app.overlay
└── src/
    └── main.c

各个文件的作用如下:

文件 作用
main.c 编写应用程序
CMakeLists.txt 配置源文件与编译构建
prj.conf 配置 Zephyr 功能
app.overlay 修改或补充开发板设备树配置

其中需要特别理解两个概念。

1. Devicetree 管理硬件描述

例如启用某个 UART 控制器:

dts 复制代码
&usart2 {
    status = "okay";
    current-speed = <115200>;
};

这里的 usart2 仅作为示例,实际节点名称取决于开发板的设备树。

2. Kconfig 管理软件功能

例如在 prj.conf 中:

ini 复制代码
CONFIG_GPIO=y
CONFIG_SERIAL=y
CONFIG_LOG=y

表示启用 GPIO、串口和日志相关功能。

简单来说:

Devicetree 决定有哪些硬件、硬件如何配置;Kconfig 决定编译哪些软件功能。


二、Kernel 基础 API:延时与系统时间

头文件:

c 复制代码
#include <zephyr/kernel.h>

2.1 常用 API

API 功能 说明
k_msleep(ms) 毫秒级休眠 当前线程休眠
k_usleep(us) 微秒级休眠 精度受系统影响
k_sleep(timeout) 通用休眠 支持多种超时参数
k_busy_wait(us) 忙等待 等待时不主动让出 CPU
k_yield() 主动让出 CPU 允许其他就绪线程运行
k_uptime_get() 获取运行时间 返回 64 位毫秒计数
k_uptime_get_32() 获取运行时间 返回低 32 位毫秒计数
k_cycle_get_32() 获取周期计数 可用于短时间测量

常用超时宏:

宏 作用
K_MSEC(x) 毫秒超时
K_USEC(x) 微秒超时
K_SECONDS(x) 秒级超时
K_NO_WAIT 不等待
K_FOREVER 无限等待

2.2 示例代码

c 复制代码
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>

int main(void)
{
    while (1) {
        printk("Hello Zephyr!\n");

        k_msleep(1000);
    }
}

这里的:

c 复制代码
k_msleep(1000);

表示当前线程休眠约 1000 ms。

需要注意的是,k_msleep() 并不是精确的硬件延时函数。对于电机控制等严格实时场景,应根据实际需求使用硬件定时器、PWM 或其他外设机制。


三、Device Model:设备管理 API

Zephyr 使用统一的设备模型管理硬件外设。

头文件:

c 复制代码
#include <zephyr/device.h>

3.1 常用 API

API 功能
DEVICE_DT_GET() 根据设备树节点获取设备对象
DEVICE_DT_GET_ANY() 获取匹配兼容类型的某个设备
DEVICE_DT_GET_ONE() 获取指定兼容类型的设备
device_is_ready() 检查设备是否初始化就绪
device_get_binding() 根据名称查找设备,旧代码中较常见

3.2 示例

c 复制代码
const struct device *uart_dev =
    DEVICE_DT_GET(DT_NODELABEL(usart2));

if (!device_is_ready(uart_dev)) {
    printk("UART device not ready!\n");
}

其中:

c 复制代码
const struct device *uart_dev;

可以理解为 Zephyr 中的设备对象指针。

如果熟悉 STM32 HAL,可以粗略类比:

c 复制代码
UART_HandleTypeDef huart2;

但两者并不完全等价。Zephyr 的 struct device 是统一驱动模型的一部分,而 HAL 的 Handle 通常与具体外设的底层实现紧密关联。

注意:DEVICE_DT_GET() 依赖编译期生成的设备信息。如果设备树未正确启用设备,或缺少相应驱动,可能产生编译或链接错误。


四、Devicetree:设备树常用宏

头文件:

c 复制代码
#include <zephyr/devicetree.h>

4.1 常用宏

宏 功能
DT_ALIAS(name) 根据别名获取设备树节点
DT_NODELABEL(name) 根据节点标签获取节点
DT_PATH(...) 根据路径获取节点
DT_CHOSEN(prop) 获取 chosen 节点
DT_PROP(node, prop) 读取节点属性
DT_NODE_EXISTS(node) 判断节点是否存在
DT_NODE_HAS_STATUS() 检查节点状态

4.2 使用示例

c 复制代码
#define LED_NODE DT_ALIAS(led0)

这段代码表示:

通过设备树中名为 led0 的别名找到对应节点。

再例如:

c 复制代码
#define UART_NODE DT_NODELABEL(usart2)

表示获取节点标签为 usart2 的设备树节点。

需要特别强调:

Zephyr 的 DT_* 通常是编译期宏,而不是运行时查找函数。

设备树经过构建系统处理后,会生成相应的 C 头文件,供程序通过这些宏获取硬件配置信息。


五、GPIO API:输入、输出和中断

GPIO 是学习 Zephyr 外设驱动最合适的起点。

头文件:

c 复制代码
#include <zephyr/drivers/gpio.h>

5.1 常用 API

API 功能
GPIO_DT_SPEC_GET() 获取设备树中的 GPIO 描述信息
gpio_is_ready_dt() 检查 GPIO 控制器是否就绪
gpio_pin_configure_dt() 配置 GPIO 模式
gpio_pin_set_dt() 设置输出逻辑状态
gpio_pin_get_dt() 读取输入逻辑状态
gpio_pin_toggle_dt() 翻转 GPIO
gpio_pin_interrupt_configure_dt() 配置 GPIO 中断
gpio_init_callback() 初始化中断回调
gpio_add_callback() 注册中断回调

常用配置宏:

c 复制代码
GPIO_INPUT
GPIO_OUTPUT
GPIO_OUTPUT_ACTIVE
GPIO_OUTPUT_INACTIVE

GPIO_PULL_UP
GPIO_PULL_DOWN

GPIO_INT_EDGE_RISING
GPIO_INT_EDGE_FALLING
GPIO_INT_EDGE_BOTH

5.2 完整示例:LED 闪烁

c 复制代码
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/gpio.h>

#define LED_NODE DT_ALIAS(led0)

#if !DT_NODE_HAS_STATUS(LED_NODE, okay)
#error "Board does not have an enabled led0 alias"
#endif

static const struct gpio_dt_spec led =
    GPIO_DT_SPEC_GET(LED_NODE, gpios);

int main(void)
{
    int ret;

    /* 检查设备是否就绪 */
    if (!gpio_is_ready_dt(&led)) {
        printk("GPIO not ready!\n");
        return -1;
    }

    /* 配置为输出,初始逻辑状态为非有效 */
    ret = gpio_pin_configure_dt(
        &led, GPIO_OUTPUT_INACTIVE);

    if (ret < 0) {
        printk("GPIO configure failed\n");
        return ret;
    }

    while (1) {
        /* 翻转输出逻辑状态 */
        ret = gpio_pin_toggle_dt(&led);

        if (ret < 0) {
            printk("GPIO toggle failed\n");
            break;
        }

        k_msleep(500);
    }

    return ret;
}

prj.conf:

ini 复制代码
CONFIG_GPIO=y

这里最值得注意的是:

c 复制代码
GPIO_DT_SPEC_GET(LED_NODE, gpios)

该宏从设备树中提取 GPIO 信息,包括:

  • GPIO 控制器
  • 引脚编号
  • 有效电平等标志信息

因此不需要像 STM32 HAL 那样在应用中手动编写大量引脚配置代码。

注意有效电平:

c 复制代码
gpio_pin_set_dt(&led, 1);

这里的 1 表示逻辑有效状态,并不一定是物理高电平。如果设备树配置了 GPIO_ACTIVE_LOW,逻辑 1 可能对应物理低电平。


六、Thread API:线程管理

Zephyr 使用 Thread 作为基本调度单位,类似 FreeRTOS 中的 Task。

头文件:

c 复制代码
#include <zephyr/kernel.h>

6.1 常用 API

API 功能 FreeRTOS 对照
K_THREAD_DEFINE() 静态定义线程 静态任务创建机制
k_thread_create() 创建线程 xTaskCreate()
k_thread_start() 启动延迟线程 无完全等价接口
k_thread_abort() 终止线程 vTaskDelete()
k_thread_suspend() 挂起线程 vTaskSuspend()
k_thread_resume() 恢复线程 vTaskResume()
k_thread_priority_set() 设置优先级 vTaskPrioritySet()
k_current_get() 获取当前线程 xTaskGetCurrentTaskHandle()

6.2 示例:创建两个线程

c 复制代码
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>

void thread1(void *a, void *b, void *c)
{
    while (1) {
        printk("Thread 1 running\n");
        k_msleep(500);
    }
}

void thread2(void *a, void *b, void *c)
{
    while (1) {
        printk("Thread 2 running\n");
        k_msleep(1000);
    }
}

/* 定义线程1 */
K_THREAD_DEFINE(
    thread1_id,
    1024,
    thread1,
    NULL, NULL, NULL,
    5,
    0,
    0
);

/* 定义线程2 */
K_THREAD_DEFINE(
    thread2_id,
    1024,
    thread2,
    NULL, NULL, NULL,
    6,
    0,
    0
);

K_THREAD_DEFINE() 的参数:

c 复制代码
K_THREAD_DEFINE(
    name,
    stack_size,
    entry,
    p1, p2, p3,
    priority,
    options,
    delay
);
参数 含义
name 线程标识
stack_size 线程栈大小
entry 线程入口函数
p1~p3 线程参数
priority 优先级
options 线程选项
delay 初始启动延迟,单位毫秒

6.3 线程优先级

Zephyr 的一个重要特点:

优先级数值越小,优先级越高。

例如:

text 复制代码
优先级 0  >  优先级 5  >  优先级 10

其中,在默认的线程优先级模型中:

  • 负数优先级:协作式线程(Cooperative)
  • 非负数优先级:抢占式线程(Preemptive)

具体可用优先级范围由 Kconfig 配置决定。

这和 FreeRTOS 常见的优先级规则不同,迁移代码时尤其需要注意。


七、Semaphore API:信号量

信号量主要用于线程之间的同步与事件通知。

头文件:

c 复制代码
#include <zephyr/kernel.h>

7.1 常用 API

API 功能
K_SEM_DEFINE() 静态定义信号量
k_sem_init() 初始化信号量
k_sem_take() 获取信号量
k_sem_give() 释放信号量
k_sem_reset() 重置信号量
k_sem_count_get() 获取计数值

7.2 示例代码

c 复制代码
/* 定义二值信号量 */
K_SEM_DEFINE(my_sem, 0, 1);

/* 线程A:等待信号量 */
void thread_a(void)
{
    k_sem_take(&my_sem, K_FOREVER);

    /* 收到通知后执行任务 */
}

/* 线程B或者ISR:发送通知 */
void notify_thread_a(void)
{
    k_sem_give(&my_sem);
}

执行逻辑:

text 复制代码
线程A
  |
  v
k_sem_take()
  |
  v
等待信号量
  |
  | 线程B / ISR 调用 k_sem_give()
  v
线程A恢复执行

常见应用:

  • UART 接收完成通知
  • CAN 接收事件通知
  • ADC 转换完成通知
  • 线程之间的事件同步

中断使用注意:

k_sem_give() 可以在中断中使用;ISR 中调用 k_sem_take() 时只能采用 K_NO_WAIT,不能等待阻塞。


八、Mutex API:互斥锁

Mutex 用于多个线程安全地访问共享资源。

头文件:

c 复制代码
#include <zephyr/kernel.h>

8.1 常用 API

API 功能
K_MUTEX_DEFINE() 静态定义互斥锁
k_mutex_init() 初始化互斥锁
k_mutex_lock() 获取锁
k_mutex_unlock() 释放锁

8.2 示例代码

c 复制代码
K_MUTEX_DEFINE(spi_mutex);

void access_spi_device(void)
{
    if (k_mutex_lock(&spi_mutex, K_FOREVER) == 0) {

        /* 访问共享SPI设备 */

        k_mutex_unlock(&spi_mutex);
    }
}

当两个线程需要访问同一个 SPI 总线或共享数据结构时,可以通过 Mutex 实现互斥访问。

需要注意:

  • 互斥锁应由拥有它的线程释放。
  • 不能在 ISR 中使用 Mutex。
  • 不应长时间持有互斥锁,避免阻塞其他任务。

九、Message Queue API:消息队列

消息队列是嵌入式多任务程序的重要通信方式。

例如机器人控制器中:

text 复制代码
CAN接收
   |
   v
消息队列
   |
   v
电机数据处理线程
   |
   v
更新电机状态

头文件:

c 复制代码
#include <zephyr/kernel.h>

9.1 常用 API

API 功能
K_MSGQ_DEFINE() 静态定义队列
k_msgq_init() 初始化队列
k_msgq_put() 发送消息
k_msgq_get() 接收消息
k_msgq_peek() 查看消息,不移除
k_msgq_purge() 清空队列
k_msgq_num_used_get() 获取已用消息数量
k_msgq_num_free_get() 获取空闲消息数量

9.2 完整示例:两个线程通过队列通信

c 复制代码
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
#include <stdint.h>

/* 定义消息结构体 */
struct motor_msg {
    uint32_t motor_id;
    int32_t speed;
};

/* 定义消息队列,容量8 */
K_MSGQ_DEFINE(
    motor_queue,
    sizeof(struct motor_msg),
    8,
    4
);

/* 生产者线程 */
void producer(void *a, void *b, void *c)
{
    struct motor_msg msg = {
        .motor_id = 1,
        .speed = 0
    };

    while (1) {
        msg.speed += 100;

        int ret = k_msgq_put(
            &motor_queue,
            &msg,
            K_NO_WAIT
        );

        if (ret != 0) {
            printk("Message queue full!\n");
        }

        k_msleep(500);
    }
}

/* 消费者线程 */
void consumer(void *a, void *b, void *c)
{
    struct motor_msg msg;

    while (1) {
        if (k_msgq_get(
                &motor_queue,
                &msg,
                K_FOREVER) == 0) {

            printk(
                "Motor ID: %u, Speed: %d\n",
                msg.motor_id,
                msg.speed
            );
        }
    }
}

/* 创建两个线程 */
K_THREAD_DEFINE(
    producer_id, 1024,
    producer, NULL, NULL, NULL,
    5, 0, 0
);

K_THREAD_DEFINE(
    consumer_id, 1024,
    consumer, NULL, NULL, NULL,
    5, 0, 0
);

int main(void)
{
    printk("Message queue demo started\n");
    return 0;
}

这个例子模拟:

  • 生产者线程每隔 500 ms 生成一条电机速度消息。
  • 生产者调用 k_msgq_put()。
  • 消费者通过 k_msgq_get() 阻塞等待。
  • 收到数据后打印电机 ID 和速度。

消息队列会复制结构体内容,因此生产者修改局部消息对象不会直接改变已经入队的数据。

如果消息队列满了,使用 K_NO_WAIT 的发送操作会立即返回错误,需要根据业务设计丢弃、重试或其他处理策略。


十、Timer API:软件定时器

头文件:

c 复制代码
#include <zephyr/kernel.h>

10.1 常用 API

API 功能
K_TIMER_DEFINE() 静态定义定时器
k_timer_init() 初始化定时器
k_timer_start() 启动定时器
k_timer_stop() 停止定时器
k_timer_status_get() 获取到期次数
k_timer_status_sync() 等待定时器到期
k_timer_remaining_get() 获取剩余时间

10.2 示例代码

c 复制代码
void timer_callback(struct k_timer *timer)
{
    /* 定时器到期处理 */
}

K_TIMER_DEFINE(my_timer, timer_callback, NULL);

void start_my_timer(void)
{
    k_timer_start(
        &my_timer,
        K_SECONDS(1),
        K_MSEC(500)
    );
}

含义:

  • 启动后 1 秒第一次到期。
  • 此后每 500 ms 周期到期。

特别注意:Zephyr 软件定时器的到期回调在系统时钟中断上下文中执行。

因此不要直接在回调中执行阻塞操作或大量耗时运算。

如果需要处理复杂业务,可以让回调通知一个线程,或者提交一个 Work Queue 工作项。


十一、Work Queue API:工作队列

工作队列用于将非紧急处理任务交给指定的线程执行。

头文件:

c 复制代码
#include <zephyr/kernel.h>

11.1 常用 API

API 功能
K_WORK_DEFINE() 定义工作项
k_work_init() 初始化工作项
k_work_submit() 提交到系统工作队列
k_work_submit_to_queue() 提交到自定义队列
K_WORK_DELAYABLE_DEFINE() 定义延迟工作项
k_work_schedule() 延迟提交工作
k_work_reschedule() 重新安排执行时间

11.2 示例

c 复制代码
void work_handler(struct k_work *work)
{
    printk("Work handler running\n");
}

/* 静态定义工作项 */
K_WORK_DEFINE(my_work, work_handler);

/* 提交工作 */
void submit_my_work(void)
{
    k_work_submit(&my_work);
}

例如在 UART 或 CAN 接收中断发生时,可以提交工作项:

text 复制代码
外设产生中断
     |
     v
中断服务程序
     |
     v
k_work_submit()
     |
     v
Work Queue线程
     |
     v
执行后续处理

需要注意,系统工作队列中的任务可能相互影响,因此对时延有严格要求的工作不能仅依靠系统工作队列保证实时性。


十二、UART API:串口通信

头文件:

c 复制代码
#include <zephyr/drivers/uart.h>

Zephyr UART 驱动主要提供三种 API 方式。

模式 特点 适用场景
Polling 简单,轮询收发 调试、低速通信
Interrupt-driven 中断驱动 常规串口通信
Async 异步事件驱动,通常结合 DMA 高吞吐或复杂串口应用

12.1 常用 API

API 功能
uart_poll_out() 轮询发送一个字符
uart_poll_in() 轮询查询并读取一个字符
uart_irq_rx_enable() 使能接收中断
uart_irq_rx_disable() 关闭接收中断
uart_irq_callback_user_data_set() 注册中断回调
uart_fifo_read() 读取 FIFO 数据
uart_fifo_fill() 写入 FIFO 数据
uart_callback_set() 注册异步回调
uart_tx() 异步发送
uart_rx_enable() 启动异步接收
uart_rx_disable() 停止异步接收

12.2 Polling 发送示例

假设 uart_dev 已指向一个初始化完成的 UART 设备:

c 复制代码
const char msg[] = "Hello Zephyr UART!\r\n";

for (int i = 0; msg[i] != '\0'; i++) {
    uart_poll_out(uart_dev, msg[i]);
}

其中:

c 复制代码
uart_poll_out(uart_dev, msg[i]);

每次发送一个字符。

12.3 配置文件

轮询通信通常只需要启用串口功能:

ini 复制代码
CONFIG_SERIAL=y

中断模式:

ini 复制代码
CONFIG_SERIAL=y
CONFIG_UART_INTERRUPT_DRIVEN=y

异步模式:

ini 复制代码
CONFIG_SERIAL=y
CONFIG_UART_ASYNC_API=y

需要注意,具体 MCU 驱动不一定同时支持所有模式。

同一个 UART 外设不能同时使用中断驱动 API 和 Async API 的回调机制,否则可能发生冲突。

官方文档:

https://docs.zephyrproject.org/latest/hardware/peripherals/uart.html


十三、SPI API

头文件:

c 复制代码
#include <zephyr/drivers/spi.h>
API 功能
SPI_DT_SPEC_GET() 获取 SPI 设备配置信息
spi_is_ready_dt() 检查 SPI 设备是否就绪
spi_transceive() 同步全双工收发
spi_transceive_dt() 使用设备树配置收发
spi_write() 同步写入
spi_write_dt() 使用设备树配置写入
spi_read() 同步读取
spi_read_dt() 使用设备树配置读取

SPI 相关操作通常使用:

c 复制代码
struct spi_config
struct spi_buf
struct spi_buf_set

来配置总线模式、频率以及收发缓冲区。

例如:

c 复制代码
spi_transceive_dt(
    &spi_dev,
    &tx_buf_set,
    &rx_buf_set
);

这里假设 spi_dev 为已经正确初始化的 struct spi_dt_spec。

对于机器人应用,SPI 常用于:

  • 绝对值编码器
  • IMU
  • 外部 ADC
  • 高速数据交换

十四、I2C API

头文件:

c 复制代码
#include <zephyr/drivers/i2c.h>
API 功能
I2C_DT_SPEC_GET() 获取 I2C 设备描述
i2c_is_ready_dt() 检查总线设备是否就绪
i2c_write_dt() 写入数据
i2c_read_dt() 读取数据
i2c_write_read_dt() 先写后读
i2c_burst_read_dt() 连续读取寄存器数据

例如:

c 复制代码
uint8_t reg = 0x00;
uint8_t data[2];

i2c_write_read_dt(
    &i2c_dev,
    &reg,
    sizeof(reg),
    data,
    sizeof(data)
);

这里假设 i2c_dev 已正确初始化。

该函数可以实现典型的寄存器读取流程:

text 复制代码
发送设备寄存器地址
        |
        v
重新开始读操作
        |
        v
读取指定字节数

实际使用时需要注意寄存器地址宽度、设备协议、I2C 地址等配置。


十五、PWM 和 ADC API

15.1 PWM

头文件:

c 复制代码
#include <zephyr/drivers/pwm.h>
API 功能
PWM_DT_SPEC_GET() 获取 PWM 配置
pwm_is_ready_dt() 检查 PWM 控制器
pwm_set() 设置 PWM 周期与脉宽
pwm_set_dt() 使用设备树描述设置 PWM
pwm_set_pulse_dt() 按设备树周期设置脉宽
pwm_set_cycles() 以硬件时钟周期为单位配置 PWM

例如:

c 复制代码
pwm_set(
    pwm_dev,
    channel,
    PWM_MSEC(1),
    PWM_USEC(500),
    PWM_POLARITY_NORMAL
);

假设 pwm_dev 和 channel 已正确配置,上述调用设置:

  • PWM 周期为 1 ms。
  • 高有效脉宽为 500 μs。
  • 占空比为 50%。

15.2 ADC

头文件:

c 复制代码
#include <zephyr/drivers/adc.h>
API 功能
ADC_DT_SPEC_GET() 获取 ADC 通道信息
adc_is_ready_dt() 检查 ADC
adc_channel_setup_dt() 配置 ADC 通道
adc_read_dt() 采样 ADC
adc_raw_to_millivolts_dt() 将采样值转换为毫伏

常见应用包括电压、电流和模拟量传感器采样。

对于 FOC 控制这种需要互补 PWM、死区控制和 ADC 精确同步触发的场景,需要进一步评估具体 STM32 驱动是否提供相应功能。Zephyr 通用 PWM/ADC API 并不等同于 STM32 高级定时器的全部能力。


十六、CAN / CAN FD API

CAN 是机器人控制器、电机驱动器和工业设备常用的通信方式。

Zephyr 提供统一的 CAN Controller API。

头文件:

c 复制代码
#include <zephyr/drivers/can.h>

16.1 常用 API

API 功能
can_get_capabilities() 查询控制器支持的功能
can_set_mode() 设置 CAN 工作模式
can_set_bitrate() 设置仲裁阶段波特率
can_set_bitrate_data() 设置 CAN FD 数据阶段波特率
can_start() 启动 CAN 控制器
can_stop() 停止 CAN 控制器
can_send() 发送 CAN 数据帧
can_add_rx_filter() 添加接收过滤器和回调
can_add_rx_filter_msgq() 将收到的帧发送到消息队列
can_remove_rx_filter() 删除接收过滤器
can_get_state() 获取 CAN 总线状态
can_recover() 从 Bus-Off 状态尝试恢复
can_bytes_to_dlc() 字节数转换为 DLC
can_dlc_to_bytes() DLC 转换为字节数

16.2 常见工作模式

c 复制代码
CAN_MODE_NORMAL
CAN_MODE_LOOPBACK
CAN_MODE_LISTENONLY
CAN_MODE_FD

16.3 CAN FD 帧标志位

标志位 含义
CAN_FRAME_IDE 29 位扩展帧 ID
CAN_FRAME_RTR 远程帧
CAN_FRAME_FDF CAN FD 帧
CAN_FRAME_BRS CAN FD 波特率切换

16.4 CAN FD 发送示例

假设 can_dev 已获取,且控制器已经完成 FD 模式配置、速率配置和 can_start():

c 复制代码
struct can_frame frame = {
    .id = 0x123,

    .dlc = can_bytes_to_dlc(12),

    .flags = CAN_FRAME_FDF | CAN_FRAME_BRS,

    .data = {
        0x01, 0x02, 0x03, 0x04,
        0x05, 0x06, 0x07, 0x08,
        0x09, 0x0A, 0x0B, 0x0C
    }
};

int ret = can_send(
    can_dev,
    &frame,
    K_MSEC(100),
    NULL,
    NULL
);

if (ret < 0) {
    printk("CAN FD send failed: %d\n", ret);
}

需要注意:

  1. CAN FD 模式需要控制器及驱动支持。
  2. 通常需要在 prj.conf 中配置 CONFIG_CAN=y 和 CONFIG_CAN_FD_MODE=y。
  3. CAN FD 的仲裁阶段和数据阶段波特率需要正确匹配总线。
  4. can_send() 的设备必须提前完成初始化并启动。
  5. 如果使用 BRS,通信双方都需要支持相应配置。

CAN FD 的 DLC 与数据字节数并不完全相同:

DLC 数据长度
0~8 0~8 字节
9 12 字节
10 16 字节
11 20 字节
12 24 字节
13 32 字节
14 48 字节
15 64 字节

因此不能简单将 64 字节直接写成 .dlc = 64。

在实际机器人控制项目中,推荐使用:

text 复制代码
CAN FD RX
    |
    v
接收过滤器
    |
    v
Message Queue
    |
    v
通信处理线程
    |
    v
电机状态解析
    |
    v
控制逻辑

Zephyr 的 can_add_rx_filter_msgq() 可以直接将匹配的 CAN 帧放入消息队列,从而减少手动编写中断回调转发代码的工作量。

官方文档:

https://docs.zephyrproject.org/latest/hardware/peripherals/can/controller.html


十七、Logging API:日志调试

头文件:

c 复制代码
#include <zephyr/logging/log.h>

常用 API:

API 功能
LOG_MODULE_REGISTER() 注册日志模块
LOG_ERR() 错误日志
LOG_WRN() 警告日志
LOG_INF() 普通信息
LOG_DBG() 调试日志
LOG_HEXDUMP_INF() 输出十六进制数据

示例:

c 复制代码
#include <zephyr/logging/log.h>

LOG_MODULE_REGISTER(motor, LOG_LEVEL_DBG);

void motor_init(void)
{
    LOG_INF("Motor initialized");

    LOG_DBG("Motor debug message");
}

prj.conf:

ini 复制代码
CONFIG_LOG=y
CONFIG_LOG_DEFAULT_LEVEL=4

在正式项目中,更推荐使用 Logging 进行模块化调试,而不是全部使用 printk()。

Logging 支持模块级日志控制、过滤和不同日志输出后端。


十八、Shell API:命令行调试

头文件:

c 复制代码
#include <zephyr/shell/shell.h>

常用 API:

API 功能
SHELL_CMD_REGISTER() 注册命令
SHELL_CMD_ARG_REGISTER() 注册带参数检查的命令
SHELL_STATIC_SUBCMD_SET_CREATE() 创建子命令集合
shell_print() 输出消息
shell_warn() 输出警告
shell_error() 输出错误

示例:

c 复制代码
#include <zephyr/shell/shell.h>

static int cmd_hello(
    const struct shell *sh,
    size_t argc,
    char **argv)
{
    shell_print(sh, "Hello Zephyr!");
    return 0;
}

SHELL_CMD_REGISTER(
    hello,
    NULL,
    "Print hello message",
    cmd_hello
);

配置并启用 Shell 后,可在终端执行:

text 复制代码
uart:~$ hello
Hello Zephyr!

对于机器人控制器,可以通过 Shell 实现:

text 复制代码
motor status
motor enable
can status
system info

这些命令需要开发者进一步编写相应处理函数,并做好权限及安全限制。


十九、STM32 HAL / FreeRTOS 与 Zephyr API 对照表

如果原来熟悉 STM32 HAL 或 FreeRTOS,可以通过以下表格快速找到功能相近的接口。

STM32 HAL / FreeRTOS Zephyr API
HAL_Delay() k_msleep()
HAL_GetTick() k_uptime_get()
HAL_GPIO_WritePin() gpio_pin_set_dt()
HAL_GPIO_ReadPin() gpio_pin_get_dt()
HAL_GPIO_TogglePin() gpio_pin_toggle_dt()
HAL_UART_Transmit() uart_poll_out() / uart_tx()
HAL_UART_Receive_IT() UART Interrupt API
HAL_UART_Receive_DMA() UART Async API(驱动支持时)
HAL_SPI_TransmitReceive() spi_transceive()
HAL_I2C_Mem_Read() i2c_write_read_dt() 等组合接口
HAL_FDCAN_AddMessageToTxFifoQ() can_send()
xTaskCreate() k_thread_create()
vTaskDelay() k_msleep()
xSemaphoreTake() k_sem_take()
xSemaphoreGive() k_sem_give()
xQueueSend() k_msgq_put()
xQueueReceive() k_msgq_get()
xTimerStart() k_timer_start()

需要注意,以上接口只是功能上的近似对应关系,并不能不加修改地直接互换。


二十、Zephyr 开发中常见的几个坑

20.1 Devicetree 和 Kconfig 混淆

例如:

ini 复制代码
CONFIG_CAN=y

只是启用 CAN 软件功能,并不意味着某个 CAN 控制器节点一定已经启用。

还需要确认相关设备树、引脚复用及驱动配置。

20.2 在中断中使用阻塞 API

例如:

c 复制代码
k_msleep(100);

不应该在 ISR 中调用。

中断中应优先完成必要且简短的工作,再通过信号量、消息队列或者 Work Queue 触发后续处理。

20.3 不检查返回值

例如:

c 复制代码
int ret = gpio_pin_configure_dt(&led, GPIO_OUTPUT);

if (ret < 0) {
    printk("GPIO configuration failed: %d\n", ret);
}

在实际项目中,应当检查设备初始化、通信发送、资源申请等操作的返回值。

20.4 混淆逻辑电平和物理电平

Zephyr GPIO API 带有逻辑有效电平的概念。

使用 GPIO_ACTIVE_LOW 时,逻辑 1 不代表物理引脚一定为高电平。

20.5 将 RTOS 软件定时器当成精密硬件定时器

软件定时器适合周期任务、事件触发等场景。

如果需要高精度 PWM、ADC 同步采样或者严格周期控制,应采用合适的 MCU 硬件外设和实时设计方案。

20.6 不同 Zephyr 版本混用 API

Zephyr 持续更新,部分 API、驱动能力及配置项可能发生变化。

建议:

  • 项目固定使用一个明确版本。
  • 阅读与项目版本对应的文档。
  • 编译时以本地 include/zephyr/ 下的头文件为准。
  • 尽量参考对应版本的官方 Samples。

二十一、推荐的 API 学习顺序

对于有一定 STM32 或 FreeRTOS 基础的开发者,推荐按下面的顺序学习。

阶段 学习模块 学习目标
第一阶段 Kernel、GPIO LED、延时、GPIO 中断
第二阶段 Device、Devicetree 看懂硬件配置
第三阶段 UART 串口收发
第四阶段 Thread 创建和管理多线程
第五阶段 Semaphore、Mutex 线程同步和资源互斥
第六阶段 Message Queue 线程间传递消息
第七阶段 Timer、Work Queue 定时任务和异步处理
第八阶段 SPI、I2C、PWM、ADC 外设驱动开发
第九阶段 CAN / CAN FD 通信节点开发
第十阶段 Logging、Shell 工程调试与管理

建议学习方法:

阅读 API → 运行官方 Sample → 修改参数 → 自己编写一个小程序 → 再应用到项目。

不要一开始就深入研究所有 Zephyr 内核实现,否则很容易陷入大量宏定义、构建脚本和驱动源码中。


二十二、官方学习资源

以下是建议收藏的官方文档:

资料 链接
Zephyr 官方文档 https://docs.zephyrproject.org/latest/
Zephyr API Reference https://docs.zephyrproject.org/latest/doxygen/html/
Getting Started https://docs.zephyrproject.org/latest/develop/getting_started/
Kernel Services https://docs.zephyrproject.org/latest/kernel/services/index.html
GPIO 文档 https://docs.zephyrproject.org/latest/hardware/peripherals/gpio.html
UART 文档 https://docs.zephyrproject.org/latest/hardware/peripherals/uart.html
CAN 文档 https://docs.zephyrproject.org/latest/hardware/peripherals/can.html
Devicetree https://docs.zephyrproject.org/latest/build/dts/index.html
Kconfig https://docs.zephyrproject.org/latest/build/kconfig/index.html
官方 Samples https://docs.zephyrproject.org/latest/samples/

总结

Zephyr RTOS 的 API 虽然非常丰富,但对于一般嵌入式应用,真正需要优先掌握的主要集中在:

  1. Kernel API:线程、时间管理和调度。
  2. GPIO / UART API:基础硬件控制与通信。
  3. Semaphore / Mutex / Message Queue:线程同步与数据传递。
  4. Devicetree / Device Model:统一管理硬件资源。
  5. SPI / I2C / CAN FD:外部设备和总线通信。
  6. Logging / Shell:程序调试与维护。

掌握这些内容后,就可以尝试构建更加完整的嵌入式系统,例如:

text 复制代码
                Zephyr RTOS
                     |
        +------------+------------+
        |            |            |
    UART Thread   CAN Thread   Control Thread
        |            |            |
        +------ Message Queue ----+
                     |
                     v
              Application
                     |
          +----------+----------+
          |          |          |
         SPI        PWM        GPIO

对于机器人控制器、电机驱动器以及工业嵌入式设备来说,这些 API 都具有很高的实际使用价值。

后续还可以结合 STM32G474、CAN FD 和 RS485,继续研究基于 Zephyr 的实时通信与多线程控制程序。

Zephyr 的学习重点不是记住所有 API,而是能够理解什么时候使用什么 API,以及如何将这些 API 组合成一个可维护、可扩展的嵌入式应用。

相关推荐
2501_9226395821 小时前
率能半导体 SS6285L直流双向马达驱动芯片:18V/5A,覆盖电子锁、按摩仪、电动玩具、机器人四大应用场景
单片机·嵌入式硬件·硬件工程
专一的咸鱼哥1 天前
预训练 ONNX 模型转换为 .eim
嵌入式硬件·机器人
SuperByteMaster1 天前
三极管 做开关电路 要尽快跳过放大区
嵌入式硬件
铅笔小新z1 天前
【stm32】输入捕获-超声波测距
stm32·单片机·嵌入式硬件
Zw-awa1 天前
PID 的花样:先看懂模糊 PID 和单神经元自适应 PID
stm32
菩提树下的凡夫1 天前
机器视觉LED光源控制器
单片机·嵌入式硬件
铅笔小新z1 天前
【stm32】全彩led灯(PWM)
stm32·单片机·嵌入式硬件
小鸡脚来咯1 天前
【无标题】
单片机·嵌入式硬件
澄纸气1 天前
STM32 外部中断实战:对射式计数器和编码器计数器,两个实验跑通 EXTI
stm32·单片机·嵌入式硬件