简介这份资源面向嵌入式Linux与Android底层驱动开发者提供基于Rockchip平台实现的UAC与CDC串口复合设备驱动方案解决设备开机后需依赖配置脚本才能枚举为USB音频与串口的问题。驱动采用legacy方式Linux下无需设置usb_gadgetAndroid下无需修改init.rc上电即可直接枚举为USB Audio与UART若需脚本方式只需不编译legacy驱动并在USB配置脚本中启用acm与uac1功能即可理论全平台适用。压缩包共12个文件约41KB包含4个Kconfig、4个Makefile、2个C源文件及2份rk3308_linux_defconfig覆盖内核配置、编译规则、驱动实现与板级默认配置结构紧凑便于移植参考。目前已有1010人学习下载适合需要快速验证UACCDC复合设备枚举、理解legacy与脚本两种加载差异的开发者参考。1. 开机即枚举的 uac1cdc 复合设备这份 legacy 驱动到底省了哪一步做过 RK 平台 USB 外设的兄弟大概率都碰过这个场景板子要同时当 USB 声卡和 USB 串口用插上主机后系统里得同时冒出card和ttyACM两个设备。常规做法是在init.rc或usb_gadget脚本里配一堆 configfs 节点功能是acm加uac1每次开机跑一遍脚本才枚举。这份usb_audiocdc复合设备驱动.rar走的是另一条路——legacy 内核驱动方式把 uac1 和 cdc acm 直接编进 gadget 驱动里开机不做任何脚本配置插上就枚举成 USB Audio 和 UART 两个接口。它基于 RK 平台验证过理论上全平台通用适合那些不想在用户态维护 gadget 脚本、又需要音频加串口同时工作的嵌入式项目。下面按「这是什么、怎么编、怎么改、坑在哪」的顺序拆一遍。2. legacy 与 configfs 两条路uac1cdc 复合设备的驱动选型与内核配置2.1 为什么这份驱动走 legacy 而不是 configfsLinux 的 USB gadget 框架有两条实现路径。一条是老的 legacy 方式gadget 功能在内核里静态注册模块加载或内核启动时就绑定好用户态不用管另一条是 configfs 方式内核只提供libcomposite具体功能由用户态往/sys/kernel/config/usb_gadget/里写目录和属性来拼装。Android 从某个版本开始默认推 configfs因为灵活可以运行时切换功能组合。但灵活是有代价的。configfs 方案要求开机阶段有脚本或init.rc去创建 gadget、写 VID/PID、配字符串、绑 UDC任何一步顺序错了或者节点没建全枚举就失败。而且音频功能在 configfs 下的 uac1 配置项比较绕chmask、c_chmask、p_chmask、c_ssize、p_ssize这些参数得对着 UAC1 描述符规范填填错主机端要么认不出声卡要么采样率不对。legacy 方式把这些都固化在驱动源码里。这份资源的核心就是一份已经写好 uac1 加 cdc acm 复合功能的 gadget 驱动编译进内核后gadget 在 probe 阶段就把两个 function 注册好UDC 一就绪就枚举。省掉的是用户态脚本这一层换来的是「开机即用」的确定性。对于产品形态固定、不需要运行时切换功能的设备legacy 反而更省心。2.2 内核配置项怎么开不管走哪条路内核侧的 USB gadget 基础配置都得先到位。常见做法是在arch/arm64/configs/下对应的 defconfig 里确认这几项# USB gadget 基础框架 CONFIG_USB_GADGETy CONFIG_USB_GADGET_DUALSPEEDy # legacy gadget 驱动入口这份资源依赖它 CONFIG_USB_GADGET_LEGACYy # 音频功能uac1 走这个 CONFIG_USB_CONFIGFS_F_UAC1y CONFIG_SND_USB_AUDIOy # cdc acm 串口功能 CONFIG_USB_CONFIGFS_F_ACMy CONFIG_USB_CONFIGFS_F_CDCy # RK 平台 UDC 控制器按实际 SoC 选 CONFIG_USB_DWC3y CONFIG_USB_DWC3_DUAL_ROLEy这里有个容易翻车的点CONFIG_USB_GADGET_LEGACY和CONFIG_USB_CONFIGFS不是互斥的但如果你同时开了 configfs 又想让 legacy 驱动生效得确认 legacy 驱动的注册优先级。有些内核版本里 legacy gadget 和 configfs 会抢同一个 UDC表现为udc绑定失败或者枚举出零个配置。稳妥做法是产品定型后只留一条路要么纯 legacy要么纯 configfs别混着来。2.3 复合设备的描述符是怎么拼的uac1 加 cdc acm 复合本质是把两组接口描述符塞进同一个 configuration 里。主机枚举时读 configuration descriptor看到两个 interface association一个归 audio class一个归 cdc class。legacy 驱动里这部分通常是一个usb_descriptor数组按device → config → interface → endpoint的顺序排。音频侧要提供 UAC1 的 AC 接口控制和 AS 接口流AS 接口里带一个 isochronous endpoint 用于音频数据。CDC ACM 侧要提供 communication interface 和 data interfacedata interface 上挂 bulk in/out 两个 endpoint。两组的 endpoint 地址不能冲突比如音频用0x81/0x01CDC 就用0x82/0x02。这份资源既然能开机直接枚举出 audio 和 uart说明描述符拼接和 endpoint 分配是验证过的改的时候别乱动地址。注意UAC1 的采样率描述符是固定列表主机只会从你声明的列表里选。如果你只写了 48000主机就不会出 44100 的选项别指望它自适应。3. 把驱动编进内核并验证枚举从源码到 card 和 ttyACM 出现3.1 源码放置与编译接入拿到压缩包后先看目录结构。legacy gadget 驱动一般放在drivers/usb/gadget/legacy/下文件名可能是audio_cdc.c或类似。把它拷进内核树对应位置然后改Makefile和Kconfig# drivers/usb/gadget/legacy/Makefile obj-$(CONFIG_USB_GADGET_LEGACY_AUDIO_CDC) audio_cdc.o# drivers/usb/gadget/legacy/Kconfig config USB_GADGET_LEGACY_AUDIO_CDC tristate Audio CDC ACM composite gadget depends on USB_GADGET_LEGACY select USB_F_UAC1 select USB_F_ACM help UAC1 CDC ACM composite gadget, no userspace config needed.然后在 defconfig 里把CONFIG_USB_GADGET_LEGACY_AUDIO_CDCy打开。编译时如果报undefined reference to usb_get_function之类的错说明USB_F_UAC1或USB_F_ACM没被 select 上回去检查 Kconfig 的 select 链。3.2 开机枚举的验证步骤烧录后开机先看内核日志里 gadget 有没有绑上 UDCdmesg | grep -i -E gadget|udc|dwc3正常应该能看到类似audio_cdc gadget: bound to udc或者dwc3 ... registered的输出。如果只有 UDC 注册没有 gadget 绑定多半是 legacy 驱动没编进去或者 probe 失败。接着在设备端确认音频和串口节点# 音频应该出现一个 usb audio card cat /proc/asound/cards # 串口应该出现 ttyACM0 ls /dev/ttyACM*主机端比如一台 Linux PC插上后# 主机侧看声卡 arecord -l aplay -l # 主机侧看串口 ls /dev/ttyACM*主机侧能看到 card 和 ttyACM说明复合枚举成功。如果只有其中一个回去查描述符里对应 interface 的 endpoint 是不是被另一个功能占用了。3.3 音频通路的参数确认UAC1 枚举出来后别急着测声音先确认采样率和通道数。设备端可以用tinymix或直接看/proc/asound/cardX/stream0cat /proc/asound/card1/stream0输出里会列出Format、Channels、Rates。这份驱动如果声明的是 48kHz 双声道主机侧arecord就得按这个参数录arecord -D hw:1,0 -f S16_LE -r 48000 -c 2 test.wav参数对不上会报cannot set hw params或者录出来全是噪声。CDC 侧相对简单ttyACM出来就能用minicom或picocom收发波特率在 ACM 下是虚拟的设多少都行但两端要一致。4. 改功能组合与参数从 uac1acm 切到纯 acm 或调采样率4.1 不编译 legacy 驱动改用脚本方式这份资源的一个灵活点是如果你哪天想切回 configfs 脚本方式不用改驱动源码只要不编译 legacy 驱动然后在init.rc或启动脚本里配acm和uac1就行。脚本方式的核心是往 configfs 写节点# 创建 gadget mkdir /sys/kernel/config/usb_gadget/g1 cd /sys/kernel/config/usb_gadget/g1 # 写 VID/PID echo 0x2207 idVendor echo 0x0001 idProduct # 创建配置 mkdir configs/c.1 # 加 acm 功能 mkdir functions/acm.gs0 ln -s functions/acm.gs0 configs/c.1/ # 加 uac1 功能 mkdir functions/uac1.gs0 echo 0x3 functions/uac1.gs0/c_chmask echo 48000 functions/uac1.gs0/c_srate echo 0x3 functions/uac1.gs0/p_chmask echo 48000 functions/uac1.gs0/p_srate ln -s functions/uac1.gs0 configs/c.1/ # 绑 UDC echo $(ls /sys/class/udc | head -1) UDCc_chmask和p_chmask是通道掩码0x3表示双声道c_srate/p_srate是采样率。这套脚本方式的好处是可以在运行时改坏处是开机得保证脚本跑完Android 下还要处理init.rc的触发时机。4.2 调整音频采样率和通道数legacy 驱动里改采样率找描述符数组里的srate字段。UAC1 的采样率在 AS interface 的 type I format descriptor 里通常是三个字节的小端表示48000 就是0x80 0xBB 0x00。改完重新编译烧录。通道数改bNrChannels同时 endpoint 的wMaxPacketSize要跟着调48kHz 双声道 16bit 的包大小是48000 * 2 * 2 / 1000 192字节每毫秒单声道减半。注意改完采样率后主机侧缓存可能还留着旧参数拔插一次或者rmmod/insmod主机驱动再测别被缓存骗了。4.3 CDC ACM 的端口号和权限ttyACM的编号是内核按枚举顺序分的如果系统里还有别的 CDC 设备编号可能不是 0。产品里最好用 udev 规则固定名字# /etc/udev/rules.d/99-audio-cdc.rules SUBSYSTEMtty, ATTRS{idVendor}2207, ATTRS{idProduct}0001, SYMLINKttyAudioCdc这样应用层固定用/dev/ttyAudioCdc不用管ttyACM几。权限方面非 root 进程要访问得加dialout组或者改 udev 的MODE。5. 避坑与排查枚举失败、音频不出声、串口丢数据的血泪经验5.1 插上主机只认到一个功能现象主机侧只出现声卡或者只出现ttyACM另一个功能消失。原因通常是两个 interface 的 endpoint 地址撞了或者 configuration 的bNumInterfaces数不对。legacy 驱动里描述符是手写的加功能时容易漏改总数。解决核对config描述符的bNumInterfaces是否等于两组 interface 之和再逐个检查 endpoint 地址bEndpointAddress有没有重复。5.2 声卡枚举出来但录音全是噪声现象arecord能跑但录出来是白噪声或者断断续续。原因多半是 isochronous endpoint 的wMaxPacketSize和实际音频带宽不匹配或者 UDC 的 FIFO 分配不够。RK 平台的 dwc3 对 iso endpoint 的 FIFO 有要求音频这种周期性传输要预留足够深度。解决查dmesg里有没有dwc3 ... no fifo之类的报错有的话调dr_mode或 FIFO 配置没有的话核对包大小和采样率是否自洽。5.3 开机偶发枚举失败重启就好现象冷启动时主机认不到设备热重启正常。原因是 legacy gadget 在 UDC 就绪前就尝试绑定或者电源域还没稳定。RK 平台常见于 USB PHY 初始化时序。解决在 gadget 驱动的 probe 里加 UDC 就绪等待或者用late_initcall替代module_init延后注册。也可以查dmesg里 gadget 绑定和 UDC 注册的先后顺序绑定早于注册就会失败。5.4 串口大数据量下丢包现象ttyACM小数据能通一跑高速传输就丢。原因是 CDC 的 bulk endpoint 包大小设小了或者应用层没做流控。ACM 本身没有硬件流控全靠 USB 的 bulk 重传但主机侧缓冲有限。解决把 bulk endpoint 的wMaxPacketSize设到 512高速或 64全速应用层加发送间隔或者用tcdrain等发送完成。5.5 换内核版本后编译报错现象在新内核上编译 legacy 驱动报 API 不匹配比如usb_ep_autoconfig参数变了。原因是 legacy gadget API 在内核演进中改过。解决对照新内核drivers/usb/gadget/legacy/下现成的驱动比如zero.c或audio.c改函数签名别硬套老代码。这份资源基于 old kernel 验证new kernel 上要留出适配时间。6. 进阶用 configfs 动态切功能组合并做自动化验证legacy 驱动胜在开机即用但产品如果需要在「纯串口」和「音频加串口」之间切换legacy 就力不从心了得回到 configfs。一个实用技巧是把 configfs 配置封装成脚本开机时根据 GPIO 电平或者配置文件决定加载哪套功能。比如检测到某个引脚拉高就只配acm拉低就配acm加uac1#!/bin/sh # /usr/bin/usb_mode.sh GADGET/sys/kernel/config/usb_gadget/g1 UDC$(ls /sys/class/udc | head -1) setup_base() { mkdir -p $GADGET echo 0x2207 $GADGET/idVendor echo 0x0001 $GADGET/idProduct mkdir -p $GADGET/configs/c.1 } add_acm() { mkdir -p $GADGET/functions/acm.gs0 ln -sf $GADGET/functions/acm.gs0 $GADGET/configs/c.1/ } add_uac1() { mkdir -p $GADGET/functions/uac1.gs0 echo 0x3 $GADGET/functions/uac1.gs0/c_chmask echo 48000 $GADGET/functions/uac1.gs0/c_srate echo 0x3 $GADGET/functions/uac1.gs0/p_chmask echo 48000 $GADGET/functions/uac1.gs0/p_srate ln -sf $GADGET/functions/uac1.gs0 $GADGET/configs/c.1/ } setup_base add_acm # 读 GPIO 或配置文件决定是否加音频 if [ $(cat /etc/usb_audio_enable 2/dev/null) 1 ]; then add_uac1 fi echo $UDC $GADGET/UDC这个脚本的关键是ln -sf建符号链接的顺序和UDC写入的时机UDC 必须最后写写早了功能没配全就枚举成空设备。验证方法切模式后拔插一次主机侧lsusb -t看接口树确认 audio 和 cdc 的 interface 都在或者只有 cdc。自动化验证可以写个 udev 规则设备枚举时触发脚本记录lsusb -v输出对比预期描述符。我一般会在产测脚本里加一步插上后等 2 秒arecord录 1 秒静音echo test /dev/ttyACM0回环读回两个都过才算这板子 USB 复合功能正常。从那以后我每次改描述符或者换内核都强制走一遍这个回环省得批量出货了才发现音频通道没通。希望帮到你。本文还有配套的精品资源点击获取