Zephyr UART 串口收发三种方式指南

发布时间:2026/7/3 4:49:48
Zephyr UART 串口收发三种方式指南 本文通过三个完整的示例程序演示 Zephyr 中 UART 数据收发的三种标准方式轮询 (Polling)、中断驱动 (Interrupt-driven)和异步 (Asynchronous / DMA)。每个示例都包含发送和接收功能并配有详细注释帮助您快速理解和应用。1. 方式对比方式核心 APICPU 占用实时性吞吐量适用场景轮询uart_poll_out,uart_poll_in高忙等好低调试、简单命令、极少量数据中断驱动uart_irq_callback_user_data_set,uart_fifo_read中仅中断时好中通用推荐如 GPS、4G 模块异步 (DMA)uart_tx,uart_rx_enable, 事件回调极低DMA 搬运好高高速流数据音频、文件2. 方式一轮询 (Polling) —— 最简单适合调试/* * Copyright (c) 2025 Zephyr Project * SPDX-License-Identifier: Apache-2.0 * * UART Polling Example * 发送uart_poll_out 逐个字符发送 * 接收uart_poll_in 非阻塞读取无数据时返回 -1 */ #include zephyr/kernel.h #include zephyr/device.h #include zephyr/drivers/uart.h #include string.h /* 使用 shell UART通常为控制台串口 */ #define UART_DEVICE_NODE DT_CHOSEN(zephyr_shell_uart) static const struct device *const uart_dev DEVICE_DT_GET(UART_DEVICE_NODE); /* 发送字符串轮询 */ void uart_poll_send(const char *str) { if (!device_is_ready(uart_dev)) { printk(UART not ready\n); return; } for (int i 0; str[i] ! \0; i) { uart_poll_out(uart_dev, str[i]); } } /* 接收一行数据以换行结尾轮询读取超时返回 */ int uart_poll_receive(char *buf, int buf_len, int timeout_ms) { int pos 0; int64_t start k_uptime_get(); while (pos buf_len - 1) { int c; if (uart_poll_in(uart_dev, c) 0) { if (c \r || c \n) { break; // 遇到换行结束 } buf[pos] (char)c; } /* 超时检测 */ if (k_uptime_get() - start timeout_ms) { break; } k_yield(); // 避免完全占用 CPU } buf[pos] \0; return pos; } void main(void) { char rx_buf[64]; printk(UART Polling Example started\n); uart_poll_send(Type something and press Enter:\r\n); while (1) { int len uart_poll_receive(rx_buf, sizeof(rx_buf), 5000); if (len 0) { uart_poll_send(Echo: ); uart_poll_send(rx_buf); uart_poll_send(\r\n); } k_sleep(K_MSEC(100)); } }关键点uart_poll_out阻塞直到发送完成。uart_poll_in非阻塞无数据立即返回-1需要轮询检查。适合简单交互但 CPU 忙等效率低。3. 方式二中断驱动 (Interrupt-driven) —— 最常用/* * Copyright (c) 2025 Zephyr Project * SPDX-License-Identifier: Apache-2.0 * * UART Interrupt-driven Example * 接收中断回调中读取 FIFO放入消息队列 * 发送使用 uart_poll_out也可改为中断发送 */ #include zephyr/kernel.h #include zephyr/device.h #include zephyr/drivers/uart.h #include string.h #define UART_DEVICE_NODE DT_CHOSEN(zephyr_shell_uart) #define MSG_SIZE 64 static const struct device *const uart_dev DEVICE_DT_GET(UART_DEVICE_NODE); /* 消息队列用于从 ISR 向主线程传递接收到的行 */ K_MSGQ_DEFINE(uart_msgq, MSG_SIZE, 10, 4); /* 接收缓冲区和当前字符位置 */ static char rx_buf[MSG_SIZE]; static int rx_pos; /* UART 中断回调函数在中断上下文执行 */ static void uart_irq_cb(const struct device *dev, void *user_data) { uint8_t c; if (!uart_irq_update(dev)) { return; } /* 只处理接收就绪 */ if (!uart_irq_rx_ready(dev)) { return; } /* 从 FIFO 中读取所有字符 */ while (uart_fifo_read(dev, c, 1) 1) { if (c \n || c \r) { /* 行结束将缓冲区内容放入消息队列 */ if (rx_pos 0) { rx_buf[rx_pos] \0; k_msgq_put(uart_msgq, rx_buf, K_NO_WAIT); rx_pos 0; } } else if (rx_pos MSG_SIZE - 1) { rx_buf[rx_pos] (char)c; } /* 忽略超出缓冲区的字符 */ } } /* 发送字符串轮询方式简单可靠 */ static void uart_send(const char *str) { for (int i 0; str[i]; i) { uart_poll_out(uart_dev, str[i]); } } void main(void) { char tx_buf[MSG_SIZE]; if (!device_is_ready(uart_dev)) { printk(UART not ready\n); return; } /* 设置中断回调并启用接收中断 */ int ret uart_irq_callback_user_data_set(uart_dev, uart_irq_cb, NULL); if (ret 0) { printk(Callback set failed: %d\n, ret); return; } uart_irq_rx_enable(uart_dev); uart_send(UART Interrupt Example\r\n); uart_send(Send a line, I will echo:\r\n); while (1) { /* 等待消息队列中有新行 */ if (k_msgq_get(uart_msgq, tx_buf, K_FOREVER) 0) { uart_send(Echo: ); uart_send(tx_buf); uart_send(\r\n); } } }关键点中断回调uart_irq_cb中只做最轻量工作读 FIFO、存队列避免阻塞。主线程通过消息队列接收数据处理逻辑与中断分离。发送仍用uart_poll_out简化若需高性能可改为中断发送参考异步示例。这是GPS/4G 模块等常见外设的首选方式。特别说明uart_fifo_fill()是 Zephyr中断驱动 UART发送方式中的核心函数。下面详细解读它的作用、用法和注意事项。uart_fifo_fill()函数原型int uart_fifo_fill(const struct device *dev, const uint8_t *tx_data, int len);作用将数据写入 UART 硬件的发送 FIFO 缓冲区。返回值实际写入 FIFO 的字节数可能0到len之间。特点非阻塞不会等待立即返回。如果 FIFO 剩余空间小于len则只写入部分数据返回值 len。通常需要配合发送中断来持续写入否则可能发送不完。典型使用模式中断驱动发送模式 A一次性填充简单场景int ret uart_fifo_fill(uart, data, len); if (ret len) { // FIFO 满了剩余数据需要等待中断再次填充 } uart_irq_tx_enable(uart); // 使能发送中断当 FIFO 有空闲时会触发中断在中断回调中static void uart_isr(const struct device *dev, void *user_data) { if (uart_irq_tx_ready(dev)) { // 继续填充剩余数据 int ret uart_fifo_fill(dev, send_buf pos, remaining); pos ret; if (pos total_len) { uart_irq_tx_disable(dev); // 发送完成关闭中断 } } }4. 方式三异步 (Asynchronous / DMA) —— 高性能/* * Copyright (c) 2025 Zephyr Project * SPDX-License-Identifier: Apache-2.0 * * UART Asynchronous (DMA) Example * 使用事件回调处理发送完成、接收就绪等。 * 发送uart_tx() 启动 DMA完成后触发 UART_TX_DONE。 * 接收uart_rx_enable() 启动数据到来时触发 UART_RX_RDY。 */ #include zephyr/kernel.h #include zephyr/device.h #include zephyr/drivers/uart.h #include zephyr/net_buf.h #include string.h #define UART_DEVICE_NODE DT_CHOSEN(zephyr_shell_uart) #define RX_BUF_LEN 64 #define TX_MAX_LEN 64 static const struct device *const uart_dev DEVICE_DT_GET(UART_DEVICE_NODE); /* 接收双缓冲 */ static uint8_t rx_buf0[RX_BUF_LEN]; static uint8_t rx_buf1[RX_BUF_LEN]; static uint8_t active_rx_buf_index 0; // 0 或 1 /* 发送缓冲区使用 net_buf 管理 */ NET_BUF_POOL_DEFINE(tx_pool, 4, TX_MAX_LEN, 0, NULL); static struct net_buf *tx_pending NULL; static struct k_fifo tx_queue; // 用于缓存待发送的数据包 /* UART 异步事件回调在中断上下文 */ static void uart_async_cb(const struct device *dev, struct uart_event *evt, void *user_data) { int rc; switch (evt-type) { case UART_TX_DONE: /* 发送完成释放 buffer */ if (tx_pending) { net_buf_unref(tx_pending); tx_pending NULL; } /* 从队列中取出下一个待发送包 */ tx_pending k_fifo_get(tx_queue, K_NO_WAIT); if (tx_pending) { rc uart_tx(dev, tx_pending-data, tx_pending-len, 0); if (rc ! 0) { net_buf_unref(tx_pending); tx_pending NULL; } } break; case UART_RX_BUF_REQUEST: /* 接收缓冲区已满请求新的缓冲区 */ if (active_rx_buf_index 0) { rc uart_rx_buf_rsp(dev, rx_buf1, RX_BUF_LEN); active_rx_buf_index 1; } else { rc uart_rx_buf_rsp(dev, rx_buf0, RX_BUF_LEN); active_rx_buf_index 0; } break; case UART_RX_RDY: /* 接收到数据evt-data.rx 包含偏移和长度 */ printk(RX: %.*s\n, evt-data.rx.len, (char *)(evt-data.rx.buf evt-data.rx.offset)); break; case UART_RX_BUF_RELEASED: case UART_RX_DISABLED: /* 可选处理 */ break; default: break; } } /* 异步发送字符串将字符串封装为 net_buf 并排队发送 */ void uart_async_send(const char *str) { struct net_buf *buf net_buf_alloc(tx_pool, K_FOREVER); int len strlen(str); if (len net_buf_tailroom(buf)) { len net_buf_tailroom(buf); } memcpy(buf-data, str, len); net_buf_add(buf, len); if (tx_pending NULL) { /* 当前无发送任务直接启动 */ int rc uart_tx(uart_dev, buf-data, buf-len, 0); if (rc 0) { tx_pending buf; } else { net_buf_unref(buf); } } else { /* 已有任务放入队列等待 */ k_fifo_put(tx_queue, buf); } } void main(void) { if (!device_is_ready(uart_dev)) { printk(UART not ready\n); return; } /* 注册异步回调 */ uart_callback_set(uart_dev, uart_async_cb, NULL); /* 启动接收提供第一个缓冲区 */ active_rx_buf_index 0; uart_rx_enable(uart_dev, rx_buf0, RX_BUF_LEN, 0); // timeout0 表示无限等待 k_sleep(K_MSEC(100)); uart_async_send(UART Async (DMA) Example\r\n); uart_async_send(Type something, Ill echo:\r\n); while (1) { /* 主线程可以执行其他任务收发完全由 DMA 和中断处理 */ k_sleep(K_SECONDS(1)); } }关键点uart_callback_set注册事件回调。uart_tx启动 DMA 发送发送完成触发UART_TX_DONE。uart_rx_enable启动接收硬件自动将数据存入提供的缓冲区满时请求新缓冲区UART_RX_BUF_REQUEST。数据到达时触发UART_RX_RDY可在回调中处理。发送队列管理避免重叠发送因为一次只能有一个 DMA 传输。5. 选择建议学习/调试轮询方式最简单。常规外设GPS、4G、传感器中断驱动是标准选择兼顾效率和复杂度。高速数据流音频、大文件传输异步 DMA 方式必不可少。这三个示例均可直接复制到 Zephyr 工程中修改UART_DEVICE_NODE以匹配您的硬件如DT_NODELABEL(uart1)即可运行。希望这份指南能助您快速掌握 Zephyr UART 编程。