USB Host Video(UVC 摄像头)示例
源码位置: samples/subsys/usb/host/cherryusb_video 查看源码
功能说明
演示如何使用 CherryUSB 在 ARCS 芯片上实现 USB Host Video(UVC)功能,支持同时连接最多 2 台 USB 摄像头,将采集到的视频画面实时分屏显示在 LCD 屏幕上。
本示例基于 CherryUSB 协议栈实现 UVC Host Bulk 传输,每台摄像头独立运行一个 FreeRTOS 采集任务,帧数据经 PSRAM 缓冲后通过 LVGL 异步渲染到 ST7789P3 屏幕。
特性
双摄同时支持: 最多同时连接 2 台 UVC 摄像头,UVC摄像头格式支持 YUV2和MJPEG
格式和分辨率自动协商: 支持自动协商摄像头格式和分辨率(优先选择第一个格式、第一个分辨率)
MJPEG 实时解码: 对于MJPEG格式的摄像头,通过 TJpgDec(LVGL8 内置 Tiny JPEG 解码器)将 MJPEG 帧解码为 RGB565,按比例缩放后写入对应半屏
YUV422 实时解码: 对于YUV2格式的摄像头,通过软件将 YUV422 帧解码为 RGB888,按比例缩放后写入对应半屏
分屏实时显示: 左右分屏——CAM0 显示在左半屏(0~159 列),CAM1 显示在右半屏(160~319 列);支持任意源分辨率等比缩放到半屏
安全热拔插: 支持热插拔UVC摄像头
注意: MJPEG UVC摄像头暂未验证过
硬件连接
开发板
ARCS EVB 开发板(Host 端)× 1
显示屏
ST7789P3 LCD(物理分辨率 240×320,SPI 4-wire 接口)
LVGL 旋转 270° 后逻辑分辨率为 320×240
USB 连接(单摄像头)
将 1 台 UVC 摄像头直接插入 Host 开发板 USB_ARCS 口即可。
USB 连接(双摄像头)
连接 2 台 UVC 摄像头需通过 USB Hub 扩展:
ARCS EVB (Host)
└── USB_ARCS (Type-C)
└── USB Hub
├── Port 1 → UVC 摄像头 0 (CAM0,显示在左半屏)
└── Port 2 → UVC 摄像头 1 (CAM1,显示在右半屏)
USB Hub 要求:
支持 USB 2.0 High-speed(480 Mbps)
自供电(摄像头需要足够电流)或有独立电源输入
USB Host 硬件修改(必须)
开发板 USB_ARCS Type-C 接口默认工作在 Device 模式,作为 Host 使用须完成以下硬件修改:
VBUS:直接对 Type-C 接口供电 5V(不依赖 USB 协商)
CC1 / CC2:分别接 22K 上拉电阻到 VBUS(Host 模式标识)
注意:未做硬件修改时无法识别外接 USB 设备。
测试用 UVC 摄像头(使用 arcs-evb 模拟)
本示例已在以下测试配置下验证:
设备 |
角色 |
固件 |
|---|---|---|
ARCS EVB #1 |
USB Host + 显示 |
本示例( |
ARCS EVB #2 |
UVC Device (CAM0) |
|
ARCS EVB #3(可选) |
UVC Device (CAM1) |
|
UVC Device 固件通过 TinyUSB 模拟标准 UVC 设备,发布 UNCOMPRESSED(YUY2)640×480 @ 15fps,Bulk 模式。两台 Device 开发板通过 USB Hub 连接到 Host 开发板。
示例内容
USB PHY 初始化(配置为 Host 模式,16-bit 数据总线)
初始化 ST7789P3 LCD 和 LVGL 显示框架(逻辑分辨率 320×240)
等待 USB 摄像头热插拔(支持 1~2 台设备同时工作,经由 USB Hub)
设备枚举完成后,解析描述符并打印摄像头支持的格式和分辨率信息
动态选择最佳格式:优先找首选格式(MJPEG)的第一帧;否则退回到任意可用格式/分辨率
通过 UVC Probe/Commit 协议协商格式,确认后开始 Bulk 流传输
格式协商成功后,按实际分辨率从 PSRAM 动态分配帧缓冲区(
width × height × 2 B),启动独立采集任务持续接收视频帧并通过异步 workqueue 更新显示:
UNCOMPRESSED(YUV422):YUYV → RGB565 缩放转换,按比例写入对应半屏
MJPEG:TJpgDec 解码为 RGB888,按比例缩放写入对应半屏(需
CONFIG_LV_USE_SJPG=1)
每 30 帧打印一次帧序号、帧大小和累计接收字节数
摄像头拔出时安全停止视频流,释放 PSRAM 资源,等待下次插入
编译
重要提示:在编译前,请先确认您使用的开发板型号。SDK 目前支持以下开发板:
arcs_evb - ARCS EVB 评估板
arcs_mini - ARCS Mini 开发板
根据您的开发板型号,选择对应的编译命令:
在示例目录下执行编译:
# 使用 arcs_evb 开发板
./build.sh -C -DBOARD=arcs_evb
# 或使用 arcs_mini 开发板
./build.sh -C -DBOARD=arcs_mini
Note
如果在 SDK 根目录执行,需要指定示例路径:
# 使用 arcs_evb 开发板
./build.sh -C -S samples/<示例路径> -DBOARD=arcs_evb
# 或使用 arcs_mini 开发板
./build.sh -C -S samples/<示例路径> -DBOARD=arcs_mini
Note
确保已安装对应的工具链。
烧录
编译完成后,使用 SDK tools 目录下的 cskburn 工具烧录固件:
./tools/burn/cskburn -s /dev/ttyUSB0 -b 3000000 0x0 build/arcs.bin -C arcs
Note
烧录参数说明:
-s /dev/ttyUSB0:串口设备路径,需要根据实际情况修改 - Linux 系统:通常是/dev/ttyUSB0或/dev/ttyACM0- 可通过ls /dev/tty*命令查看可用串口设备 - 不同开发板或 USB 转串口芯片可能使用不同的设备名-b 3000000:烧录波特率(3Mbps)0x0:烧录起始地址build/arcs.bin:编译生成的固件路径-C arcs:芯片类型
注意事项:
确保开发板已正确连接到电脑
如果无法识别串口设备,请检查 USB 连接线是否正常,或尝试其他 USB 端口
如需使用 ARCS EVB 作为 UVC 摄像头,同样烧录 Device 固件:
# 构建 UVC Device 固件
./build.sh -S samples/subsys/usb/device/uvc -DBOARD=arcs_evb
# 烧录到第二台开发板
cskburn -s /dev/ttyACM1 -b 3000000 -C arcs 0x0 ./build/arcs.bin
预期输出
Host 端日志(插入一台 UVC UNCOMPRESSED 摄像头):
========================================
CherryUSB Host Video Example
Preferred format: MJPEG (auto-negotiated from device)
Max devices: 2
========================================
[INFO] Initializing display...
[INFO] Initializing USB Host...
[INFO] USB Host initialized, please connect USB cameras
[I/usbh_hub] New full-speed device on Bus 0, Hub 1, Port 1 connected
[I/usbh_core] New device found,idVendor:xxxx,idProduct:xxxx,bcdDevice:0150
[I/usbh_core] Enumeration success, start loading class driver
[I/usbh_video] ============= Video device information ===================
[I/usbh_video] bNumFormats:1
[I/usbh_video] FormatIndex:1
[I/usbh_video] FormatType:uncompressed
[I/usbh_video] bNumFrames:1
[I/usbh_video] FrameIndex:1
[I/usbh_video] wWidth: 640, wHeight: 480, fps: 15
[VIDEO0] Camera connected: video0
[VIDEO0] Opening device (format=UNCOMPRESSED 640x480)...
[VIDEO0] frame_buf allocated: 614400 B (640x480)
[VIDEO0] chunk_buf allocated: 512 B
[VIDEO0] Starting bulk streaming...
[VIDEO0] Streaming started
[VIDEO0] frame #30 size=614400 B total=17973 KB
[VIDEO0] Running: 30 frames, 17973 KB total
...
插入第二台摄像头(CAM1,需 USB Hub)时追加输出:
[I/usbh_hub] New full-speed device on Bus 0, Hub 1, Port 2 connected
...
[VIDEO1] Camera connected: video1
[VIDEO1] Opening device (format=UNCOMPRESSED 640x480)...
[VIDEO1] Streaming started
...
LCD 显示效果:
UNCOMPRESSED(YUV422)摄像头:对应半屏实时更新视频画面(YUYV→RGB565 缩放渲染,任意源分辨率 → 160×240)
MJPEG 摄像头:TJpgDec 实时解码,对应半屏实时更新(标签显示 “CAM0(MJPEG)”);解码失败时保持上一帧
核心 API
USB Host 初始化
API |
说明 |
|---|---|
|
初始化 USB Host 协议栈 |
|
初始化 USB Host PHY(由 SYS_INIT 自动调用) |
Video Host API
API |
说明 |
|---|---|
|
打印摄像头支持的格式和分辨率列表 |
|
打开视频设备并通过 UVC Probe/Commit 协商格式和分辨率 |
|
关闭视频设备 |
|
启动 Bulk 视频流(注册帧回调,提交 URB) |
|
停止视频流并 kill 所有 URB |
用户回调(弱符号,需覆盖实现)
回调函数 |
说明 |
|---|---|
|
摄像头插入、枚举完成后由框架调用;用于启动采集任务 |
|
摄像头拔出时由框架调用;必须在返回前完成 URB kill |
关键代码
动态格式协商(不再硬编码分辨率)
/*
* 遍历设备格式表,优先找首选格式(MJPEG)的第一帧;
* 若不支持则退回任意可用格式。
* usbh_video_open() 传入从设备描述符读出的实际宽高,而非硬编码值。
*/
static bool find_best_format(struct usbh_video *vc, uint8_t pref_fmt,
uint8_t *out_fmt, uint16_t *out_w, uint16_t *out_h)
{
for (uint8_t i = 0; i < vc->num_of_formats; i++) {
if (vc->format[i].format_type == pref_fmt && vc->format[i].num_of_frames > 0) {
*out_fmt = pref_fmt;
*out_w = vc->format[i].frame[0].wWidth;
*out_h = vc->format[i].frame[0].wHeight;
return true;
}
}
for (uint8_t i = 0; i < vc->num_of_formats; i++) {
if (vc->format[i].num_of_frames > 0) {
*out_fmt = vc->format[i].format_type;
*out_w = vc->format[i].frame[0].wWidth;
*out_h = vc->format[i].frame[0].wHeight;
return true;
}
}
return false;
}
/* 在采集任务中使用 */
uint8_t actual_fmt; uint16_t actual_w, actual_h;
if (!find_best_format(video_class, VIDEO_FORMAT_PREF, &actual_fmt, &actual_w, &actual_h)) {
goto out_no_stream;
}
ret = usbh_video_open(video_class, actual_fmt, actual_w, actual_h, 0);
ctx->width = actual_w; /* 保存协商结果,帧回调中传给显示模块 */
ctx->height = actual_h;
每设备上下文与设备插入回调
struct video_dev_ctx {
struct usbh_video *video_class;
uint8_t *frame_buf; /* PSRAM 帧缓冲区(≈600KB) */
uint8_t *chunk_buf; /* PSRAM DMA chunk 缓冲区 */
volatile bool running;
volatile bool disconnected;
TaskHandle_t task_handle;
uint32_t frame_count;
uint32_t total_bytes;
uint16_t width; /* 实际协商的视频宽度 */
uint16_t height; /* 实际协商的视频高度 */
};
/* 摄像头插入回调:启动采集任务(帧缓冲区将在格式协商后按实际分辨率动态分配) */
void usbh_video_run(struct usbh_video *video_class)
{
uint8_t idx = video_class->minor;
struct video_dev_ctx *ctx = &g_devs[idx];
ctx->video_class = video_class;
ctx->frame_buf = NULL; /* 格式协商后按实际分辨率动态分配 */
ctx->running = true;
ctx->disconnected = false;
char task_name[16];
snprintf(task_name, sizeof(task_name), "video%u", (unsigned)idx);
xTaskCreate(video_stream_task, task_name, 4096, ctx,
CONFIG_USBHOST_PSC_PRIO + 1, NULL);
}
摄像头断开回调
/*
* 调用链:usbh_video_ctrl_disconnect()
* → usbh_video_stop() ← 本函数
* → usbh_video_class_free() ← memset(video_class, 0)
*
* 若返回前未 kill URB,usbh_video_class_free 将 bulkin_urb.complete
* 清零,MUSB 中断触发时访问 NULL 回调 → crash。
*/
void usbh_video_stop(struct usbh_video *video_class)
{
usbh_video_stop_streaming(video_class); /* 关键:先 kill URB */
struct video_dev_ctx *ctx = &g_devs[video_class->minor];
ctx->disconnected = true;
if (ctx->task_handle) {
xTaskNotifyGive(ctx->task_handle);
}
vTaskDelay(pdMS_TO_TICKS(50));
}
配置说明
prj.conf 关键配置项
配置项 |
值 |
说明 |
|---|---|---|
|
|
启用 CherryUSB USB Video Host 驱动 |
|
|
启用 MUSB 控制器(ARCS 平台) |
|
|
启用 PSRAM 动态堆(帧缓冲区来源) |
|
|
PSRAM 堆大小(5MB,支持双摄各 ≈600KB 帧缓冲) |
|
|
启用 ST7789P3 LCD 驱动 |
|
|
LVGL 旋转 270°(物理 240×320 → 逻辑 320×240) |
|
|
启用 DMA2D 硬件加速显示 |
|
|
启用 TJpgDec(MJPEG 实时解码显示) |
|
|
启用 workqueue(显示更新异步派发) |
usb_config.h 关键配置项
配置项 |
值 |
说明 |
|---|---|---|
|
|
最多同时支持 2 台 UVC 摄像头 |
|
|
DMA 缓冲区对齐字节数(dcache 开启时须 32B) |
|
|
Bulk 单次 URB 接收长度(默认值, |
main.c 视频参数
/* 首选视频格式(优先尝试;若不支持则自动选择其他可用格式) */
#define VIDEO_FORMAT_PREF USBH_VIDEO_FORMAT_MJPEG
帧缓冲区大小在格式协商完成后动态计算:frame_bufsize = actual_w * actual_h * 2,无需手动调整。
注意事项
硬件修改必须:开发板 USB 口默认为 Device 模式,作为 Host 使用必须完成 VBUS 5V 供电和 CC1/CC2 上拉电阻修改,否则无法识别外接设备。
双摄需 USB Hub:ARCS EVB 仅有一个 USB 口,同时接入 2 台摄像头必须经过 USB Hub。Hub 建议使用自供电款,以保证摄像头有足够的工作电流。
仅支持 Bulk 传输:ARCS MUSB 控制器当前未实现 ISO 传输,示例会检测
video_class->is_bulk,若摄像头要求 ISO 模式则自动中止并提示。请使用支持 Bulk 传输的 UVC 摄像头。MJPEG 解码显示(未经硬件验证):MJPEG 解码路径(TJpgDec,
CONFIG_LV_USE_SJPG=1)已实现但尚未在实际硬件上验证,可能存在未知问题。若遇到异常,可将VIDEO_FORMAT_PREF改为USBH_VIDEO_FORMAT_UNCOMPRESSED退回经过验证的 YUV422 路径。TJpgDec 每帧从 PSRAM 申请 4KB 工作内存池;解码失败时 LCD 保持上一帧。内存分配:帧缓冲区在格式协商后按实际分辨率动态分配(
width × height × 2 B,如 640×480 ≈ 600KB),chunk 缓冲区同样来自 PSRAM。建议CONFIG_PSRAM_HEAP_SIZE ≥ 5MB(支持双摄各 ≈ 600KB + MJPEG 解码各 4KB 工作内存池)。设备兼容性:已测试 ARCS EVB UVC Device 固件(UNCOMPRESSED YUY2 640×480 Bulk),其他设备理论支持但未实测。