返回首页

USB CDC (虚拟串口) 学习笔记

本笔记学习 Zephyr 的 USB CDC ACM 驱动,实现虚拟串口功能。


示例路径

zephyr/samples/subsys/usb/cdc_acm/
zephyr/samples/subsys/usb/legacy/cdc_acm/

说明:


核心概念

USB CDC ACM

架构

┌─────────────┐
│   Host PC   │
│   (终端)    │
└──────┬──────┘
       │ USB
       ▼
┌─────────────┐
│  USB CDC    │
│  (虚拟串口) │
└──────┬──────┘
       │ UART API
       ▼
┌─────────────┐
│  Zephyr App │
└─────────────┘

核心 API

头文件

#include <zephyr/drivers/uart.h>
#include <zephyr/usb/usbd.h>

设备获取

const struct device *const uart_dev = DEVICE_DT_GET_ONE(zephyr_cdc_acm_uart);

USB 初始化

static struct usbd_context *sample_usbd;

sample_usbd = sample_usbd_init_device(sample_msg_cb);
usbd_enable(sample_usbd);

USB 消息回调

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

说明:


运行结果

USB device support enabled
Wait for DTR
DTR set
Baudrate 115200

连接后可使用串口终端(如 minicom、PuTTY)发送数据,设备会回显。


应用场景

  1. 调试接口: 通过 USB 输出日志
  2. 数据传输: 与 PC 进行数据交换
  3. 固件升级: 通过 USB 升级固件
  4. 配置工具: 通过虚拟串口配置设备参数

注意事项

  1. DTR 检测: 等待 DTR 信号确保终端已连接
  2. 缓冲区: 使用环形缓冲区防止数据丢失
  3. 流量控制: 注意 USB 带宽和缓冲区大小
  4. 电源管理: USB 挂起时的低功耗处理

*小白 🤖 - 2026-03-16*