Zephyr串口驱动开发及构建完全指南
引言
串口(UART)是嵌入式系统中最常用的通信接口之一,用于调试、数据传输和设备间通信。Zephyr提供了一套完整的串口驱动框架,支持多种硬件平台和通信模式。
本文将详细介绍Zephyr下串口驱动的开发流程,包括驱动架构、设备树配置、Kconfig配置、API使用以及完整的项目构建过程。
一、Zephyr串口驱动架构
1.1 架构概述
#mermaid-svg-MDa2DujOs5Eyrsrb{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-MDa2DujOs5Eyrsrb .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-MDa2DujOs5Eyrsrb .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-MDa2DujOs5Eyrsrb .error-icon{fill:#552222;}#mermaid-svg-MDa2DujOs5Eyrsrb .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-MDa2DujOs5Eyrsrb .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-MDa2DujOs5Eyrsrb .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-MDa2DujOs5Eyrsrb .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-MDa2DujOs5Eyrsrb .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-MDa2DujOs5Eyrsrb .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-MDa2DujOs5Eyrsrb .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-MDa2DujOs5Eyrsrb .marker{fill:#333333;stroke:#333333;}#mermaid-svg-MDa2DujOs5Eyrsrb .marker.cross{stroke:#333333;}#mermaid-svg-MDa2DujOs5Eyrsrb svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-MDa2DujOs5Eyrsrb p{margin:0;}#mermaid-svg-MDa2DujOs5Eyrsrb .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-MDa2DujOs5Eyrsrb .cluster-label text{fill:#333;}#mermaid-svg-MDa2DujOs5Eyrsrb .cluster-label span{color:#333;}#mermaid-svg-MDa2DujOs5Eyrsrb .cluster-label span p{background-color:transparent;}#mermaid-svg-MDa2DujOs5Eyrsrb .label text,#mermaid-svg-MDa2DujOs5Eyrsrb span{fill:#333;color:#333;}#mermaid-svg-MDa2DujOs5Eyrsrb .node rect,#mermaid-svg-MDa2DujOs5Eyrsrb .node circle,#mermaid-svg-MDa2DujOs5Eyrsrb .node ellipse,#mermaid-svg-MDa2DujOs5Eyrsrb .node polygon,#mermaid-svg-MDa2DujOs5Eyrsrb .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-MDa2DujOs5Eyrsrb .rough-node .label text,#mermaid-svg-MDa2DujOs5Eyrsrb .node .label text,#mermaid-svg-MDa2DujOs5Eyrsrb .image-shape .label,#mermaid-svg-MDa2DujOs5Eyrsrb .icon-shape .label{text-anchor:middle;}#mermaid-svg-MDa2DujOs5Eyrsrb .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-MDa2DujOs5Eyrsrb .rough-node .label,#mermaid-svg-MDa2DujOs5Eyrsrb .node .label,#mermaid-svg-MDa2DujOs5Eyrsrb .image-shape .label,#mermaid-svg-MDa2DujOs5Eyrsrb .icon-shape .label{text-align:center;}#mermaid-svg-MDa2DujOs5Eyrsrb .node.clickable{cursor:pointer;}#mermaid-svg-MDa2DujOs5Eyrsrb .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-MDa2DujOs5Eyrsrb .arrowheadPath{fill:#333333;}#mermaid-svg-MDa2DujOs5Eyrsrb .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-MDa2DujOs5Eyrsrb .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-MDa2DujOs5Eyrsrb .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-MDa2DujOs5Eyrsrb .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-MDa2DujOs5Eyrsrb .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-MDa2DujOs5Eyrsrb .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-MDa2DujOs5Eyrsrb .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-MDa2DujOs5Eyrsrb .cluster text{fill:#333;}#mermaid-svg-MDa2DujOs5Eyrsrb .cluster span{color:#333;}#mermaid-svg-MDa2DujOs5Eyrsrb div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-MDa2DujOs5Eyrsrb .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-MDa2DujOs5Eyrsrb rect.text{fill:none;stroke-width:0;}#mermaid-svg-MDa2DujOs5Eyrsrb .icon-shape,#mermaid-svg-MDa2DujOs5Eyrsrb .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-MDa2DujOs5Eyrsrb .icon-shape p,#mermaid-svg-MDa2DujOs5Eyrsrb .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-MDa2DujOs5Eyrsrb .icon-shape .label rect,#mermaid-svg-MDa2DujOs5Eyrsrb .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-MDa2DujOs5Eyrsrb .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-MDa2DujOs5Eyrsrb .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-MDa2DujOs5Eyrsrb :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 应用层
串口API
串口驱动层
硬件抽象层
硬件寄存器
uart_write
uart_read
uart_irq_callback
nrf-uart
stm32-uart
x86-uart
通用UART接口
1.2 驱动层次
| 层次 | 说明 | 文件位置 |
|---|---|---|
| 应用层 | 用户应用代码 | src/main.c |
| API层 | 通用串口API | include/zephyr/drivers/uart.h |
| 驱动层 | 平台特定驱动 | drivers/uart/ |
| HAL层 | 硬件抽象层 | drivers/uart/*/uart_*.c |
1.3 串口驱动目录结构
zephyr/drivers/uart/
├── uart.c # 通用串口API实现
├── uart.h # 串口API头文件
├── Kconfig # Kconfig配置
├── nrf/ # Nordic UART驱动
│ ├── uart_nrf.c
│ └── uart_nrf.h
├── stm32/ # STM32 UART驱动
│ ├── uart_stm32.c
│ └── uart_stm32.h
├── x86/ # x86 UART驱动
│ ├── uart_pc.c
│ └── uart_pc.h
└── ... # 其他平台驱动
二、设备树配置
2.1 串口节点定义
dts
/* 设备树中的串口节点 */
uart0: uart@40002000 {
compatible = "nordic,nrf-uart";
reg = <0x40002000 0x1000>;
interrupts = <2 1>;
status = "okay";
current-speed = <115200>;
label = "UART_0";
pinctrl-0 = <&uart0_default>;
pinctrl-1 = <&uart0_sleep>;
pinctrl-names = "default", "sleep";
};
2.2 设备树属性说明
| 属性 | 说明 | 示例 |
|---|---|---|
compatible |
兼容的驱动 | "nordic,nrf-uart" |
reg |
寄存器地址和大小 | <0x40002000 0x1000> |
interrupts |
中断号和优先级 | <2 1> |
status |
设备状态 | "okay"或"disabled" |
current-speed |
默认波特率 | <115200> |
label |
设备标签 | "UART_0" |
pinctrl-0 |
默认引脚配置 | <&uart0_default> |
2.3 别名定义
dts
/* 在aliases节点中定义别名 */
aliases {
uart0 = &uart0;
uart1 = &uart1;
console = &uart0;
shell-uart = &uart0;
};
2.4 使用设备树获取串口
c
#include <zephyr/devicetree.h>
#include <zephyr/drivers/uart.h>
/* 使用别名获取串口设备 */
#define UART_NODE DT_ALIAS(uart0)
const struct device *uart_dev = DEVICE_DT_GET(UART_NODE);
/* 使用路径获取串口设备 */
#define UART_NODE DT_PATH(soc, uart_40002000)
/* 使用标签获取串口设备 */
#define UART_NODE DT_NODELABEL(uart0)
三、Kconfig配置
3.1 基本配置
kconfig
# prj.conf - 串口配置
# 启用串口驱动
CONFIG_UART=y
# 启用控制台串口
CONFIG_UART_CONSOLE=y
# 启用串口输入
CONFIG_CONSOLE_INPUT=y
# 启用控制台处理器
CONFIG_CONSOLE_HANDLER=y
# 启用串行驱动
CONFIG_SERIAL=y
3.2 高级配置
kconfig
# 缓冲区配置
CONFIG_UART_RX_BUF_SIZE=256 # 接收缓冲区大小
CONFIG_UART_TX_BUF_SIZE=256 # 发送缓冲区大小
# 流控制配置
CONFIG_UART_HW_FLOW_CONTROL=n # 硬件流控制
CONFIG_UART_SW_FLOW_CONTROL=n # 软件流控制
# 中断配置
CONFIG_UART_INTERRUPT_DRIVEN=y # 中断驱动模式
# 调试配置
CONFIG_UART_DEBUG=n # 串口调试
CONFIG_UART_DEBUG_LOG=n # 串口调试日志
# 驱动特定配置
CONFIG_UART_NRF=y # Nordic UART驱动
CONFIG_UART_STM32=y # STM32 UART驱动
CONFIG_UART_PC=y # x86 UART驱动
3.3 配置项说明
| 配置项 | 说明 | 默认值 |
|---|---|---|
CONFIG_UART |
启用串口驱动 | y |
CONFIG_UART_CONSOLE |
串口作为控制台 | y |
CONFIG_CONSOLE_INPUT |
控制台输入 | n |
CONFIG_UART_INTERRUPT_DRIVEN |
中断驱动模式 | y |
CONFIG_UART_RX_BUF_SIZE |
接收缓冲区大小 | 256 |
CONFIG_UART_TX_BUF_SIZE |
发送缓冲区大小 | 256 |
CONFIG_UART_HW_FLOW_CONTROL |
硬件流控制 | n |
四、串口API详解
4.1 基本API
c
#include <zephyr/drivers/uart.h>
/* 获取设备 */
const struct device *uart_dev = DEVICE_DT_GET(DT_ALIAS(uart0));
/* 检查设备是否就绪 */
if (!device_is_ready(uart_dev)) {
printk("UART device not ready\n");
return -1;
}
/* 写入数据 */
int uart_write(const struct device *dev, const uint8_t *buf, size_t len);
/* 读取数据(阻塞) */
int uart_read(const struct device *dev, uint8_t *buf, size_t len);
/* 读取数据(非阻塞) */
int uart_fifo_read(const struct device *dev, uint8_t *buf, size_t len);
/* 写入数据(非阻塞) */
int uart_fifo_write(const struct device *dev, const uint8_t *buf, size_t len);
/* 获取接收缓冲区中可用字节数 */
int uart_rx_buf_rpos(const struct device *dev, const uint8_t **buf);
/* 获取发送缓冲区中可用空间 */
int uart_tx_buf_nfree(const struct device *dev);
4.2 中断驱动API
c
#include <zephyr/drivers/uart.h>
/* 中断回调函数 */
void uart_callback(const struct device *dev, void *user_data)
{
uint8_t c;
/* 更新中断状态 */
if (uart_irq_update(dev)) {
/* 检查接收中断 */
if (uart_irq_rx_ready(dev)) {
/* 读取一个字节 */
uart_fifo_read(dev, &c, 1);
/* 处理接收到的数据 */
process_byte(c);
}
/* 检查发送中断 */
if (uart_irq_tx_ready(dev)) {
/* 发送更多数据 */
send_next_byte();
}
}
}
/* 设置中断回调 */
uart_irq_callback_set(uart_dev, uart_callback);
/* 启用接收中断 */
uart_irq_rx_enable(uart_dev);
/* 启用发送中断 */
uart_irq_tx_enable(uart_dev);
/* 禁用接收中断 */
uart_irq_rx_disable(uart_dev);
/* 禁用发送中断 */
uart_irq_tx_disable(uart_dev);
4.3 串口配置API
c
#include <zephyr/drivers/uart.h>
/* 串口配置结构体 */
struct uart_config {
uint32_t baudrate; /* 波特率 */
uint8_t parity; /* 校验位 */
uint8_t stop_bits; /* 停止位 */
uint8_t data_bits; /* 数据位 */
uint8_t flow_ctrl; /* 流控制 */
};
/* 配置串口 */
int uart_configure(const struct device *dev, const struct uart_config *cfg);
/* 获取当前配置 */
int uart_config_get(const struct device *dev, struct uart_config *cfg);
/* 设置波特率 */
int uart_set_baudrate(const struct device *dev, uint32_t baudrate);
/* 获取波特率 */
int uart_get_baudrate(const struct device *dev, uint32_t *baudrate);
4.4 配置选项说明
| 选项 | 值 | 说明 |
|---|---|---|
parity |
UART_CFG_PARITY_NONE |
无校验 |
UART_CFG_PARITY_EVEN |
偶校验 | |
UART_CFG_PARITY_ODD |
奇校验 | |
stop_bits |
UART_CFG_STOP_BITS_1 |
1个停止位 |
UART_CFG_STOP_BITS_2 |
2个停止位 | |
data_bits |
UART_CFG_DATA_BITS_7 |
7位数据 |
UART_CFG_DATA_BITS_8 |
8位数据 | |
flow_ctrl |
UART_CFG_FLOW_CTRL_NONE |
无流控制 |
UART_CFG_FLOW_CTRL_RTS_CTS |
RTS/CTS流控制 |
五、完整串口驱动开发示例
5.1 项目结构
uart_example/
├── CMakeLists.txt
├── prj.conf
├── src/
│ └── main.c
└── boards/
└── qemu_x86.overlay
5.2 CMakeLists.txt
cmake
cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(uart_example)
target_sources(app PRIVATE src/main.c)
5.3 prj.conf
kconfig
CONFIG_KERNEL=y
CONFIG_INIT_STACKS=y
CONFIG_CONSOLE=y
CONFIG_SERIAL=y
CONFIG_UART=y
CONFIG_UART_CONSOLE=y
CONFIG_CONSOLE_INPUT=y
CONFIG_DEBUG=y
CONFIG_LOG=y
CONFIG_LOG_LEVEL_DBG=y
5.4 src/main.c - 阻塞模式
c
#include <zephyr/kernel.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/sys/printk.h>
#define UART_NODE DT_ALIAS(uart0)
const struct device *uart_dev = DEVICE_DT_GET(UART_NODE);
void main(void)
{
uint8_t tx_buf[] = "Hello from Zephyr UART!\n";
uint8_t rx_buf[64];
int ret;
if (!device_is_ready(uart_dev)) {
printk("UART device not ready!\n");
return;
}
printk("UART example started\n");
/* 发送数据 */
ret = uart_write(uart_dev, tx_buf, sizeof(tx_buf) - 1);
if (ret < 0) {
printk("UART write failed: %d\n", ret);
return;
}
printk("Data sent, waiting for response...\n");
/* 等待接收数据(阻塞模式) */
ret = uart_read(uart_dev, rx_buf, sizeof(rx_buf) - 1);
if (ret < 0) {
printk("UART read failed: %d\n", ret);
return;
}
/* 确保字符串以NULL结尾 */
rx_buf[ret] = '\0';
printk("Received: %s\n", rx_buf);
}
5.5 src/main.c - 中断驱动模式
c
#include <zephyr/kernel.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/sys/printk.h>
#define UART_NODE DT_ALIAS(uart0)
const struct device *uart_dev = DEVICE_DT_GET(UART_NODE);
#define RX_BUF_SIZE 128
static uint8_t rx_buf[RX_BUF_SIZE];
static size_t rx_buf_pos = 0;
#define TX_BUF_SIZE 128
static uint8_t tx_buf[TX_BUF_SIZE];
static size_t tx_buf_pos = 0;
static size_t tx_buf_len = 0;
/* 串口中断回调函数 */
void uart_callback(const struct device *dev, void *user_data)
{
uint8_t c;
int ret;
if (!uart_irq_update(dev)) {
return;
}
/* 处理接收中断 */
if (uart_irq_rx_ready(dev)) {
while (uart_fifo_read(dev, &c, 1) == 1) {
if (rx_buf_pos < RX_BUF_SIZE - 1) {
rx_buf[rx_buf_pos++] = c;
/* 检查是否收到换行符 */
if (c == '\n' || c == '\r') {
rx_buf[rx_buf_pos] = '\0';
printk("Received: %s", rx_buf);
rx_buf_pos = 0;
/* 回显数据 */
tx_buf_len = rx_buf_pos;
memcpy(tx_buf, rx_buf, tx_buf_len);
tx_buf_pos = 0;
uart_irq_tx_enable(dev);
}
} else {
/* 缓冲区溢出 */
rx_buf_pos = 0;
printk("RX buffer overflow!\n");
}
}
}
/* 处理发送中断 */
if (uart_irq_tx_ready(dev)) {
while (tx_buf_pos < tx_buf_len) {
ret = uart_fifo_write(dev, &tx_buf[tx_buf_pos], 1);
if (ret == 1) {
tx_buf_pos++;
} else {
break;
}
}
/* 发送完成 */
if (tx_buf_pos >= tx_buf_len) {
tx_buf_pos = 0;
tx_buf_len = 0;
uart_irq_tx_disable(dev);
}
}
}
void main(void)
{
uint8_t welcome_msg[] = "Zephyr UART Interrupt Example\n";
int ret;
if (!device_is_ready(uart_dev)) {
printk("UART device not ready!\n");
return;
}
/* 设置中断回调 */
uart_irq_callback_set(uart_dev, uart_callback);
/* 启用接收中断 */
uart_irq_rx_enable(uart_dev);
/* 发送欢迎消息 */
tx_buf_len = sizeof(welcome_msg) - 1;
memcpy(tx_buf, welcome_msg, tx_buf_len);
tx_buf_pos = 0;
uart_irq_tx_enable(uart_dev);
printk("UART interrupt example started\n");
while (1) {
k_msleep(100);
}
}
六、构建过程
6.1 编译命令
bash
# 进入项目目录
cd uart_example
# 编译(QEMU x86平台)
west build -b qemu_x86 .
# 编译并清理旧构建
west build -b qemu_x86 . --clean
# 指定构建目录
west build -b qemu_x86 . -d build_qemu
# 使用多核编译
west build -b qemu_x86 . -j $(nproc)
# 编译到硬件平台
west build -b nrf52840dk_nrf52840 .
6.2 编译流程
编译器 Kconfig CMake West 用户 编译器 Kconfig CMake West 用户 #mermaid-svg-fEIdv8Fr84Gn0f6A{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-fEIdv8Fr84Gn0f6A .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-fEIdv8Fr84Gn0f6A .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-fEIdv8Fr84Gn0f6A .error-icon{fill:#552222;}#mermaid-svg-fEIdv8Fr84Gn0f6A .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-fEIdv8Fr84Gn0f6A .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-fEIdv8Fr84Gn0f6A .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-fEIdv8Fr84Gn0f6A .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-fEIdv8Fr84Gn0f6A .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-fEIdv8Fr84Gn0f6A .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-fEIdv8Fr84Gn0f6A .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-fEIdv8Fr84Gn0f6A .marker{fill:#333333;stroke:#333333;}#mermaid-svg-fEIdv8Fr84Gn0f6A .marker.cross{stroke:#333333;}#mermaid-svg-fEIdv8Fr84Gn0f6A svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-fEIdv8Fr84Gn0f6A p{margin:0;}#mermaid-svg-fEIdv8Fr84Gn0f6A .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-fEIdv8Fr84Gn0f6A text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-fEIdv8Fr84Gn0f6A .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-fEIdv8Fr84Gn0f6A .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-fEIdv8Fr84Gn0f6A .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-fEIdv8Fr84Gn0f6A .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-fEIdv8Fr84Gn0f6A #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-fEIdv8Fr84Gn0f6A .sequenceNumber{fill:white;}#mermaid-svg-fEIdv8Fr84Gn0f6A #sequencenumber{fill:#333;}#mermaid-svg-fEIdv8Fr84Gn0f6A #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-fEIdv8Fr84Gn0f6A .messageText{fill:#333;stroke:none;}#mermaid-svg-fEIdv8Fr84Gn0f6A .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-fEIdv8Fr84Gn0f6A .labelText,#mermaid-svg-fEIdv8Fr84Gn0f6A .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-fEIdv8Fr84Gn0f6A .loopText,#mermaid-svg-fEIdv8Fr84Gn0f6A .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-fEIdv8Fr84Gn0f6A .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-fEIdv8Fr84Gn0f6A .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-fEIdv8Fr84Gn0f6A .noteText,#mermaid-svg-fEIdv8Fr84Gn0f6A .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-fEIdv8Fr84Gn0f6A .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-fEIdv8Fr84Gn0f6A .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-fEIdv8Fr84Gn0f6A .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-fEIdv8Fr84Gn0f6A .actorPopupMenu{position:absolute;}#mermaid-svg-fEIdv8Fr84Gn0f6A .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-fEIdv8Fr84Gn0f6A .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-fEIdv8Fr84Gn0f6A .actor-man circle,#mermaid-svg-fEIdv8Fr84Gn0f6A line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-fEIdv8Fr84Gn0f6A :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} west build -b qemu_x86 . 解析prj.conf 生成autoconf.h 运行CMake 加载Zephyr模块 解析设备树 生成构建文件 编译源代码 编译main.c 编译uart驱动 链接目标文件 生成zephyr.elf 构建完成
6.3 编译输出
bash
$ west build -b qemu_x86 .
-- west build: making build dir /home/user/zephyrproject/uart_example/build
-- west build: sourcing /home/user/zephyrproject/zephyr/zephyr-env.sh
-- Zephyr version: 3.6.0 (/home/user/zephyrproject/zephyr)
-- Board: qemu_x86
-- Found host-tools: zephyr-sdk-0.16.4 (/home/user/zephyr-sdk-0.16.4)
-- Found toolchain: zephyr (/home/user/zephyr-sdk-0.16.4/x86_64-zephyr-elf)
-- Configuring done
-- Generating done
-- Build files have been written to: /home/user/zephyrproject/uart_example/build
[1/234] Building C object zephyr/CMakeFiles/zephyr.dir/drivers/uart/uart.c.obj
[2/234] Building C object zephyr/CMakeFiles/zephyr.dir/drivers/uart/x86/uart_pc.c.obj
...
[234/234] Linking C executable zephyr/zephyr.elf
Memory region Used Size Region Size %age Used
FLASH: 2048 B 256 KB 0.78%
RAM: 256 B 64 KB 0.39%
七、运行与测试
7.1 在QEMU中运行
bash
# 运行
west build -t run
# 输出示例:
# [1/1] To exit from QEMU enter: 'CTRL+a, x'
# QEMU 7.0.0 monitor - type 'help' for more information
# (qemu)
# SeaBIOS (version rel-1.16.0-0-gd239552ce722-prebuilt.qemu.org)
# Booting from ROM..
# UART example started
# Hello from Zephyr UART!
7.2 在硬件开发板上运行
步骤1:连接开发板
bash
# 查看串口设备
ls /dev/ttyACM*
ls /dev/ttyUSB*
步骤2:编译目标平台
bash
west build -b nrf52840dk_nrf52840 .
步骤3:烧录固件
bash
west flash
步骤4:查看串口输出
bash
# 使用minicom
minicom -D /dev/ttyACM0 -b 115200
# 使用picocom
picocom /dev/ttyACM0 -b 115200
7.3 测试串口通信
发送测试:
bash
# 在串口终端中输入数据
Hello Zephyr!
预期输出:
bash
# 回显数据
Hello Zephyr!
八、串口驱动开发进阶
8.1 流控制配置
c
#include <zephyr/drivers/uart.h>
struct uart_config cfg = {
.baudrate = 115200,
.parity = UART_CFG_PARITY_NONE,
.stop_bits = UART_CFG_STOP_BITS_1,
.data_bits = UART_CFG_DATA_BITS_8,
.flow_ctrl = UART_CFG_FLOW_CTRL_RTS_CTS, /* 启用RTS/CTS流控制 */
};
int ret = uart_configure(uart_dev, &cfg);
if (ret < 0) {
printk("UART configure failed: %d\n", ret);
}
8.2 多串口管理
c
#include <zephyr/drivers/uart.h>
/* 定义多个串口 */
const struct device *uart0 = DEVICE_DT_GET(DT_ALIAS(uart0));
const struct device *uart1 = DEVICE_DT_GET(DT_ALIAS(uart1));
/* 配置不同波特率 */
uart_set_baudrate(uart0, 115200);
uart_set_baudrate(uart1, 9600);
/* 发送数据到不同串口 */
uart_write(uart0, "Data to uart0\n", 14);
uart_write(uart1, "Data to uart1\n", 14);
8.3 DMA模式
c
#include <zephyr/drivers/uart.h>
/* DMA配置(如果硬件支持) */
#if defined(CONFIG_UART_DMA)
#include <zephyr/drivers/dma.h>
static struct dma_block_config dma_tx_block = {
.source_address = NULL,
.dest_address = (uint32_t)UART_TX_REG,
.block_size = 0,
.source_addr_adj = DMA_ADDR_ADJ_INCREMENT,
.dest_addr_adj = DMA_ADDR_ADJ_NO_CHANGE,
.source_reload_en = 0,
};
/* 使用DMA发送 */
dma_configure(dma_dev, dma_channel, &dma_tx_block);
dma_start(dma_dev, dma_channel);
#endif
8.4 串口轮询模式
c
#include <zephyr/drivers/uart.h>
/* 轮询发送 */
void uart_poll_send(const struct device *dev, const uint8_t *buf, size_t len)
{
for (size_t i = 0; i < len; i++) {
/* 等待发送缓冲区为空 */
while (!uart_irq_tx_ready(dev)) {
k_msleep(1);
}
/* 发送一个字节 */
uart_fifo_write(dev, &buf[i], 1);
}
}
/* 轮询接收 */
int uart_poll_receive(const struct device *dev, uint8_t *buf, size_t len)
{
size_t received = 0;
while (received < len) {
/* 检查是否有数据可读 */
if (uart_irq_rx_ready(dev)) {
int ret = uart_fifo_read(dev, &buf[received], 1);
if (ret == 1) {
received++;
}
} else {
k_msleep(1);
}
}
return received;
}
九、常见问题与解决方案
9.1 编译错误
问题1:找不到串口设备
bash
# 错误信息:
# error: 'DT_ALIAS_uart0' undeclared
# 解决:检查设备树配置
# 确保设备树中定义了uart0节点
# 在aliases中添加uart0别名
问题2:串口驱动未启用
bash
# 错误信息:
# undefined reference to `uart_write'
# 解决:在prj.conf中添加
CONFIG_UART=y
CONFIG_SERIAL=y
9.2 运行错误
问题1:串口无输出
bash
# 解决:检查配置
# 确保启用了控制台
CONFIG_UART_CONSOLE=y
# 检查设备树
status = "okay"
# 检查波特率配置
current-speed = <115200>
问题2:串口输出乱码
bash
# 解决:确保波特率一致
# 开发板和终端使用相同波特率
minicom -D /dev/ttyACM0 -b 115200
问题3:接收数据丢失
bash
# 解决:增大缓冲区
CONFIG_UART_RX_BUF_SIZE=512
CONFIG_UART_TX_BUF_SIZE=512
# 或使用中断驱动模式
CONFIG_UART_INTERRUPT_DRIVEN=y
9.3 硬件问题
问题1:串口设备权限
bash
# 错误信息:
# Can't open /dev/ttyACM0: Permission denied
# 解决:添加用户到dialout组
sudo usermod -aG dialout $USER
问题2:串口不识别
bash
# 解决:检查硬件连接
lsusb
# 检查驱动是否加载
dmesg | grep tty
十、最佳实践
10.1 代码结构规范
c
/* 推荐的串口代码结构 */
#include <zephyr/kernel.h>
#include <zephyr/drivers/uart.h>
/* 使用设备树获取设备 */
#define UART_NODE DT_ALIAS(uart0)
static const struct device *const uart_dev = DEVICE_DT_GET(UART_NODE);
/* 定义缓冲区 */
#define BUF_SIZE 256
static uint8_t rx_buf[BUF_SIZE];
static uint8_t tx_buf[BUF_SIZE];
/* 串口初始化 */
static int uart_init(void)
{
if (!device_is_ready(uart_dev)) {
printk("UART device not ready\n");
return -1;
}
/* 配置串口 */
struct uart_config cfg = {
.baudrate = 115200,
.parity = UART_CFG_PARITY_NONE,
.stop_bits = UART_CFG_STOP_BITS_1,
.data_bits = UART_CFG_DATA_BITS_8,
.flow_ctrl = UART_CFG_FLOW_CTRL_NONE,
};
return uart_configure(uart_dev, &cfg);
}
/* 发送字符串 */
static int uart_send_string(const char *str)
{
return uart_write(uart_dev, (const uint8_t *)str, strlen(str));
}
/* 主函数 */
void main(void)
{
uart_init();
uart_send_string("Hello World!\n");
}
10.2 中断驱动模式最佳实践
#mermaid-svg-Ih4Gdysb4QP2YWS3{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Ih4Gdysb4QP2YWS3 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Ih4Gdysb4QP2YWS3 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Ih4Gdysb4QP2YWS3 .error-icon{fill:#552222;}#mermaid-svg-Ih4Gdysb4QP2YWS3 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Ih4Gdysb4QP2YWS3 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Ih4Gdysb4QP2YWS3 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Ih4Gdysb4QP2YWS3 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Ih4Gdysb4QP2YWS3 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Ih4Gdysb4QP2YWS3 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Ih4Gdysb4QP2YWS3 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Ih4Gdysb4QP2YWS3 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Ih4Gdysb4QP2YWS3 .marker.cross{stroke:#333333;}#mermaid-svg-Ih4Gdysb4QP2YWS3 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Ih4Gdysb4QP2YWS3 p{margin:0;}#mermaid-svg-Ih4Gdysb4QP2YWS3 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Ih4Gdysb4QP2YWS3 .cluster-label text{fill:#333;}#mermaid-svg-Ih4Gdysb4QP2YWS3 .cluster-label span{color:#333;}#mermaid-svg-Ih4Gdysb4QP2YWS3 .cluster-label span p{background-color:transparent;}#mermaid-svg-Ih4Gdysb4QP2YWS3 .label text,#mermaid-svg-Ih4Gdysb4QP2YWS3 span{fill:#333;color:#333;}#mermaid-svg-Ih4Gdysb4QP2YWS3 .node rect,#mermaid-svg-Ih4Gdysb4QP2YWS3 .node circle,#mermaid-svg-Ih4Gdysb4QP2YWS3 .node ellipse,#mermaid-svg-Ih4Gdysb4QP2YWS3 .node polygon,#mermaid-svg-Ih4Gdysb4QP2YWS3 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Ih4Gdysb4QP2YWS3 .rough-node .label text,#mermaid-svg-Ih4Gdysb4QP2YWS3 .node .label text,#mermaid-svg-Ih4Gdysb4QP2YWS3 .image-shape .label,#mermaid-svg-Ih4Gdysb4QP2YWS3 .icon-shape .label{text-anchor:middle;}#mermaid-svg-Ih4Gdysb4QP2YWS3 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Ih4Gdysb4QP2YWS3 .rough-node .label,#mermaid-svg-Ih4Gdysb4QP2YWS3 .node .label,#mermaid-svg-Ih4Gdysb4QP2YWS3 .image-shape .label,#mermaid-svg-Ih4Gdysb4QP2YWS3 .icon-shape .label{text-align:center;}#mermaid-svg-Ih4Gdysb4QP2YWS3 .node.clickable{cursor:pointer;}#mermaid-svg-Ih4Gdysb4QP2YWS3 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Ih4Gdysb4QP2YWS3 .arrowheadPath{fill:#333333;}#mermaid-svg-Ih4Gdysb4QP2YWS3 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Ih4Gdysb4QP2YWS3 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Ih4Gdysb4QP2YWS3 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Ih4Gdysb4QP2YWS3 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Ih4Gdysb4QP2YWS3 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Ih4Gdysb4QP2YWS3 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Ih4Gdysb4QP2YWS3 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Ih4Gdysb4QP2YWS3 .cluster text{fill:#333;}#mermaid-svg-Ih4Gdysb4QP2YWS3 .cluster span{color:#333;}#mermaid-svg-Ih4Gdysb4QP2YWS3 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Ih4Gdysb4QP2YWS3 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Ih4Gdysb4QP2YWS3 rect.text{fill:none;stroke-width:0;}#mermaid-svg-Ih4Gdysb4QP2YWS3 .icon-shape,#mermaid-svg-Ih4Gdysb4QP2YWS3 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Ih4Gdysb4QP2YWS3 .icon-shape p,#mermaid-svg-Ih4Gdysb4QP2YWS3 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Ih4Gdysb4QP2YWS3 .icon-shape .label rect,#mermaid-svg-Ih4Gdysb4QP2YWS3 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Ih4Gdysb4QP2YWS3 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Ih4Gdysb4QP2YWS3 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Ih4Gdysb4QP2YWS3 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 中断驱动模式
初始化
中断处理
数据处理
获取设备
设置回调
启用中断
接收中断
发送中断
缓冲区管理
数据解析
10.3 性能优化建议
| 优化项 | 建议 |
|---|---|
| 缓冲区大小 | 根据数据量调整,避免频繁中断 |
| 中断优先级 | 高优先级数据使用高优先级中断 |
| DMA | 大量数据传输使用DMA |
| 批量操作 | 合并多次小数据传输 |
结束语
通过本文的详细介绍,相信您已经掌握了Zephyr串口驱动开发的完整流程:
| 知识点 | 内容 |
|---|---|
| 驱动架构 | 应用层→API层→驱动层→HAL层 |
| 设备树 | 定义串口节点、配置属性、别名 |
| Kconfig | 启用串口驱动、配置缓冲区、流控制 |
| API使用 | uart_write、uart_read、中断回调 |
| 构建过程 | west build编译、QEMU运行、硬件烧录 |
串口开发要点:
- 设备树配置:正确定义串口节点和属性
- Kconfig配置:启用必要的配置选项
- API选择:根据场景选择阻塞/中断/DMA模式
- 缓冲区管理:合理设置缓冲区大小
- 错误处理:检查返回值和设备状态
下一步学习:
- 实现串口数据解析协议
- 添加流控制支持
- 使用DMA进行高速数据传输
- 实现多串口通信
参考资料: