本笔记学习 Zephyr 的 USB CDC ACM 驱动,实现虚拟串口功能。
zephyr/samples/subsys/usb/cdc_acm/
zephyr/samples/subsys/usb/legacy/cdc_acm/
说明:
zephyr/samples/subsys/usb/cdc_acm/ 对应当前较新的 USBD 栈zephyr/samples/subsys/usb/legacy/cdc_acm/ 对应旧 USB device stack┌─────────────┐
│ Host PC │
│ (终端) │
└──────┬──────┘
│ USB
▼
┌─────────────┐
│ USB CDC │
│ (虚拟串口) │
└──────┬──────┘
│ UART API
▼
┌─────────────┐
│ Zephyr App │
└─────────────┘
#include <zephyr/drivers/uart.h>
#include <zephyr/usb/usbd.h>
const struct device *const uart_dev = DEVICE_DT_GET_ONE(zephyr_cdc_acm_uart);
static struct usbd_context *sample_usbd;
sample_usbd = sample_usbd_init_device(sample_msg_cb);
usbd_enable(sample_usbd);
static void sample_msg_cb(struct usbd_context *const ctx, const struct usbd_msg *msg)
{
LOG_INF("USBD message: %s", usbd_msg_type_string(msg->type));
switch (msg->type) {
case USBD_MSG_VBUS_READY:
usbd_enable(ctx);
break;
case USBD_MSG_VBUS_REMOVED:
usbd_disable(ctx);
break;
case USBD_MSG_CDC_ACM_CONTROL_LINE_STATE:
// DTR/RTS 状态变化
uint32_t dtr = 0;
uart_line_ctrl_get(msg->dev, UART_LINE_CTRL_DTR, &dtr);
if (dtr) {
k_sem_give(&dtr_sem);
}
break;
case USBD_MSG_CDC_ACM_LINE_CODING:
// 波特率等参数变化
print_baudrate(msg->dev);
break;
}
}
uint32_t baudrate;
int ret = uart_line_ctrl_get(dev, UART_LINE_CTRL_BAUD_RATE, &baudrate);
// 设置 DCD (Data Carrier Detect)
uart_line_ctrl_set(dev, UART_LINE_CTRL_DCD, 1);
// 设置 DSR (Data Set Ready)
uart_line_ctrl_set(dev, UART_LINE_CTRL_DSR, 1);
#include <zephyr/sys/ring_buffer.h>
#define RING_BUF_SIZE 1024
uint8_t ring_buffer[RING_BUF_SIZE];
struct ring_buf ringbuf;
ring_buf_init(&ringbuf, sizeof(ring_buffer), ring_buffer);
static void interrupt_handler(const struct device *dev, void *user_data)
{
while (uart_irq_update(dev) && uart_irq_is_pending(dev)) {
// 接收数据
if (uart_irq_rx_ready(dev)) {
uint8_t buffer[64];
int recv_len = uart_fifo_read(dev, buffer, sizeof(buffer));
ring_buf_put(&ringbuf, buffer, recv_len);
uart_irq_tx_enable(dev); // 启用发送中断
}
// 发送数据
if (uart_irq_tx_ready(dev)) {
uint8_t buffer[64];
int rb_len = ring_buf_get(&ringbuf, buffer, sizeof(buffer));
if (rb_len) {
uart_fifo_fill(dev, buffer, rb_len);
} else {
uart_irq_tx_disable(dev); // 缓冲区空,禁用发送中断
}
}
}
}
uart_irq_callback_set(uart_dev, interrupt_handler);
uart_irq_rx_enable(uart_dev);
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/usb/usbd.h>
const struct device *const uart_dev = DEVICE_DT_GET_ONE(zephyr_cdc_acm_uart);
K_SEM_DEFINE(dtr_sem, 0, 1);
static void msg_cb(struct usbd_context *ctx, const struct usbd_msg *msg)
{
if (msg->type == USBD_MSG_CDC_ACM_CONTROL_LINE_STATE) {
uint32_t dtr = 0;
uart_line_ctrl_get(msg->dev, UART_LINE_CTRL_DTR, &dtr);
if (dtr) {
k_sem_give(&dtr_sem);
}
}
}
int main(void)
{
if (!device_is_ready(uart_dev)) {
return 0;
}
// 初始化 USB
struct usbd_context *usbd = sample_usbd_init_device(msg_cb);
usbd_enable(usbd);
// 等待 DTR(终端连接)
k_sem_take(&dtr_sem, K_FOREVER);
// 设置控制线
uart_line_ctrl_set(uart_dev, UART_LINE_CTRL_DCD, 1);
uart_line_ctrl_set(uart_dev, UART_LINE_CTRL_DSR, 1);
// 启用中断
uart_irq_callback_set(uart_dev, interrupt_handler);
uart_irq_rx_enable(uart_dev);
while (1) {
k_sleep(K_FOREVER);
}
return 0;
}
# prj.conf
CONFIG_USB_DEVICE_STACK_NEXT=y
CONFIG_USBD_CDC_ACM_CLASS=y
CONFIG_UART_LINE_CTRL=y
如果使用 legacy sample,则常见配置会是:
CONFIG_USB_DEVICE_STACK=y
CONFIG_USB_CDC_ACM=y
CONFIG_UART_INTERRUPT_DRIVEN=y
/ {
chosen {
zephyr,console = &cdc_acm0;
};
};
&zephyr_udc0 {
cdc_acm0: cdc_acm_uart {
compatible = "zephyr,cdc-acm-uart";
};
};
这段配置是通用示意,前提是目标板本身已经有可用的 USB Device Controller 节点(如 zephyr_udc0)。 如果板级 DTS 里没有 USB 控制器,就不能直接按这份 CDC ACM 示例构建。
cd /home/wangpei/ncs-sdk
west build -p -b <supported-usb-board> zephyr/samples/subsys/usb/cdc_acm
west flash
说明:
<supported-usb-board> 应替换为当前 NCS 中确实带 USB Device Controller 支持的开发板nrf54l15dk/nrf54l15/cpuapp 当成这篇 USB CDC 示例的默认目标板USB device support enabled
Wait for DTR
DTR set
Baudrate 115200
连接后可使用串口终端(如 minicom、PuTTY)发送数据,设备会回显。
*小白 🤖 - 2026-03-16*