1. 为什么要把全志V3S做成USB外设全志V3S这颗芯片在嵌入式圈子里算得上是老熟人了1.2GHz的Cortex-A7单核、内置64MB DDR2、带以太网MAC和USB OTG控制器核心板价格常年压在二十块钱上下。大多数人拿它跑Linux做小网关、做串口服务器、做摄像头推流但有一个方向经常被忽略——把它从“主机”变成“设备”让PC把它识别成串口、网卡或者U盘。这个思路的价值在于你手头可能有一台没有串口的笔记本或者一台需要临时扩展网络接口的工控机又或者需要在两台设备之间快速传文件但不想拔U盘插来插去。V3S的USB OTG控制器原生支持Gadget模式配合Linux内核里的USB Gadget框架可以把它模拟成标准USB设备PC端不需要装任何私有驱动插上就能用。我最初做这个事情的动机很实际调试一块工控板时需要同时看串口日志和传文件手边只有一个USB口可用。与其来回插拔CH340串口模块和U盘不如让V3S同时扮演这两个角色。实测下来串口、网卡、U盘三个功能可以共存PC端识别稳定长时间跑也没有掉线问题。这篇文章面向的是有基本Linux操作经验、手头有V3S开发板、想把它改造成多功能USB外设的嵌入式爱好者。我会从内核配置讲到具体实操把踩过的坑和验证过的参数都摊开说。如果你之前没接触过USB Gadget也不用担心我会把每个环节的“为什么”讲清楚。2. USB Gadget框架的核心逻辑与方案选型2.1 USB Gadget到底是什么USB协议里设备端叫Gadget主机端叫Host。平时我们用V3S它作为Host去识别U盘、键盘而Gadget模式是反过来让V3S扮演被PC识别的那个设备。Linux内核的USB Gadget框架提供了一套抽象层底层是UDCUSB Device Controller驱动上层是各种Function驱动比如g_serial、g_ether、g_mass_storage。关键点在于V3S的USB OTG控制器支持Device模式这是硬件基础。内核里的sunxi UDC驱动负责和硬件打交道我们只需要配置好Function并绑定到UDC上PC那边就会枚举出对应的设备。这里有个容易混淆的地方OTG控制器同一时间只能工作在Host或Device一种模式下。你不能一边让V3S读U盘一边又让PC把V3S当U盘读。所以做这个项目时V3S的USB口是专职做Device的不能再接其他USB设备。2.2 三种Function的选型对比串口、网卡、U盘这三个功能内核里对应的驱动模块和配置方式各有不同。我整理了一张对比表方便你根据需求取舍功能内核模块PC端识别结果典型用途是否需要额外配置串口g_serialCOM口或/dev/ttyACMx调试日志、AT指令需要getty或应用占用网卡g_ether新增网络适配器文件传输、SSH需要配IP和路由U盘g_mass_storage可移动磁盘文件交换需要指定 backing 文件选型时要注意g_serial和g_ether可以同时加载但g_mass_storage如果和它们共存需要确认UDC的端点资源够用。V3S的UDC支持多个端点实测三个Function同时跑没有问题但配置顺序有讲究。2.3 为什么不用现成的USB Hub方案有人可能会想直接买个USB Hub插串口模块和U盘不就行了这个方案的问题在于第一Hub方案需要V3S工作在Host模式那它自己就没法被PC识别了第二多一个Hub多一份供电和体积第三Gadget方案是纯软件实现不增加硬件成本。对于集成度要求高的场景Gadget方案明显更优。另一个常见疑问是能不能用configfs动态配置可以而且我推荐用configfs。老式的g_serial模块加载方式虽然简单但灵活性差改配置要重新加载模块。configfs可以在运行时组合Function、修改参数更适合这种多Function共存的场景。3. 内核配置与基础环境搭建3.1 内核选项怎么选V3S的内核源码一般用linux-sunxi或者全志官方BSP。不管哪个版本USB Gadget相关的配置项都在Device Drivers - USB Gadget Support下面。以下是我验证过的必选项CONFIG_USB_GADGETy CONFIG_USB_SUNXI_UDCy CONFIG_USB_CONFIGFSy CONFIG_USB_CONFIGFS_SERIALy CONFIG_USB_CONFIGFS_ETHERy CONFIG_USB_CONFIGFS_MASS_STORAGEy CONFIG_USB_LIBCOMPOSITEy CONFIG_USB_F_SERIALy CONFIG_USB_F_ECMy CONFIG_USB_F_MASS_STORAGEy这里解释几个关键点CONFIG_USB_SUNXI_UDC是V3S的UDC驱动没有它后面全白搭。CONFIG_USB_CONFIGFS是configfs接口的基础必须开。F_SERIAL、F_ECM、F_MASS_STORAGE分别对应三个Function。ECM是网卡的协议标准Windows 10及以上原生支持不需要装驱动如果是Win7可能需要RNDIS但V3S这边配RNDIS稍微麻烦一点建议优先用ECM。注意如果你的内核版本较老可能没有CONFIG_USB_CONFIGFS_ETHER选项这时候需要用g_ether模块的方式但那样就没法和其他Function灵活组合了。建议升级到4.14以上内核。3.2 设备树里的USB节点V3S的设备树里USB OTG节点默认可能是Host模式。需要确认或修改dr_mode属性usb_otg { dr_mode peripheral; status okay; };dr_mode有三个值host、peripheral、otg。做Gadget必须用peripheral或者用otg让驱动自动判断。我建议直接写peripheral避免自动切换带来的不确定性。有些BSP里这个节点叫usb0或者musb名字不同但属性一样。改完设备树重新编译dtb启动后可以用ls /sys/class/udc/确认UDC是否注册成功。正常应该看到类似musb-hdrc.0.auto或sunxi-udc的名字。如果这个目录是空的说明UDC驱动没加载或者设备树没配对。3.3 挂载configfsconfigfs是一个虚拟文件系统需要手动挂载mount -t configfs none /sys/kernel/config挂载后/sys/kernel/config/usb_gadget/目录就出现了。所有Gadget的配置都在这个目录下操作。建议把这行写进启动脚本因为每次重启后configfs不会自动挂载。实操心得有些精简的根文件系统里没有configfs支持需要在内核里开CONFIG_CONFIGFS_FS。另外挂载点必须是/sys/kernel/config不能换地方这是内核规定的。4. 用configfs搭建复合Gadget4.1 创建Gadget实例与描述符先创建一个Gadget目录名字随便取我习惯用v3s_usbcd /sys/kernel/config/usb_gadget mkdir v3s_usb cd v3s_usb然后设置VID和PID。VID是厂商IDPID是产品ID。随便填的话PC可能识别成未知设备但功能不受影响。我一般用0x1d6bLinux基金会和0x0104echo 0x1d6b idVendor echo 0x0104 idProduct接着设置字符串描述符这些会显示在PC的设备管理器里mkdir strings/0x409 echo 0123456789 strings/0x409/serialnumber echo V3S Gadget strings/0x409/manufacturer echo Composite Device strings/0x409/product0x409是英语的语言代码。serialnumber建议填一个唯一值方便PC区分多个相同设备。4.2 配置串口Function创建配置和Function目录mkdir configs/c.1 mkdir configs/c.1/strings/0x409 echo SerialECMMS configs/c.1/strings/0x409/configuration mkdir functions/acm.usb0 ln -s functions/acm.usb0 configs/c.1/acm是Abstract Control Model的缩写对应CDC-ACM协议PC端识别为标准串口。acm.usb0这个名字可以改但建议保持规范。创建后/dev/ttyGS0设备节点会出现V3S这边的应用往这个节点读写数据就会通过USB传到PC。注意ttyGS0默认可能没有权限普通用户访问不了。可以改udev规则或者直接用root。另外如果PC端打开串口后V3S这边没反应检查一下是不是有getty占用了ttyGS0用systemctl stop serial-gettyttyGS0停掉。4.3 配置网卡Function网卡用ECM协议mkdir functions/ecm.usb0 ln -s functions/ecm.usb0 configs/c.1/创建后V3S这边会出现一个usb0网络接口。PC端会多出一个网络适配器Windows下显示为“USB Ethernet/RNDIS Gadget”或类似名称。ECM在Linux和macOS下原生支持Windows 10 1809以后也原生支持ECM了更早的版本可能需要RNDIS。V3S这边的usb0需要手动配IPip addr add 192.168.7.2/24 dev usb0 ip link set usb0 upPC端配同网段IP比如192.168.7.1/24就能互相ping通了。如果想做DHCP可以在V3S上跑dnsmasq给PC分配地址。4.4 配置U盘FunctionU盘Function需要一个backing文件作为存储介质dd if/dev/zero of/tmp/usbdisk.img bs1M count64 mkfs.vfat /tmp/usbdisk.img mkdir functions/mass_storage.usb0 echo /tmp/usbdisk.img functions/mass_storage.usb0/lun.0/file ln -s functions/mass_storage.usb0 configs/c.1/这里创建了一个64MB的镜像文件格式化为FAT32。PC端会把它识别为一个可移动磁盘读写这个磁盘实际上是在读写这个镜像文件。镜像大小可以根据需要调整但要注意V3S的内存和存储空间。实操心得backing文件不要放在tmpfs上否则重启后数据丢失。建议放在SD卡或Flash的持久化分区里。另外如果PC端提示“需要格式化”说明镜像文件没有正确格式化重新mkfs一下。4.5 绑定UDC并验证所有Function配置好后最后一步是绑定UDCls /sys/class/udc/ echo musb-hdrc.0.auto UDC把UDC的名字写入UDC文件Gadget就激活了。这时候把USB线插到PC上PC会依次枚举出串口、网卡、U盘三个设备。用dmesg可以看到枚举日志。如果绑定失败常见原因是UDC名字不对或者端点资源冲突。可以先只绑一个Function测试确认UDC正常后再逐个添加。5. 实操中的常见问题与排查5.1 PC端识别不到设备这是最常见的问题。排查顺序如下先确认V3S的USB口是Device模式设备树dr_mode是否正确然后检查UDC是否绑定成功cat /sys/kernel/config/usb_gadget/v3s_usb/UDC应该有值再看内核日志有没有“gadget: failed to start”之类的报错。如果V3S这边一切正常但PC没反应换一根USB线试试。有些USB线只有供电没有数据线这种线插上后V3S能供电但PC识别不到。另外PC的USB口供电不足也可能导致枚举失败换一个口或者用带供电的Hub。5.2 串口能识别但收不到数据PC端能看到COM口但打开后没有数据。先确认V3S这边有没有往/dev/ttyGS0写数据。可以用echo test /dev/ttyGS0测试。如果写不进去检查权限和getty占用。另一个可能是波特率不匹配。CDC-ACM的波特率是虚拟的实际上不影响数据传输但有些串口助手会严格检查。建议PC端用115200-8-N-1这是最通用的配置。5.3 网卡识别了但ping不通先确认两边的IP在同一网段子网掩码一致。然后检查V3S的usb0接口是否UPip link show usb0看状态。如果PC端是Windows检查防火墙是否阻止了ICMP。还有一个容易忽略的点ECM模式下PC端可能会把USB网卡识别为“未识别的网络”需要手动设置为专用网络或者关闭防火墙。另外如果PC端同时连着WiFi和USB网卡路由表可能优先走WiFi需要手动加路由。5.4 U盘写入速度慢或报错V3S的USB OTG是USB 2.0 Full Speed还是High Speed取决于具体型号和配置。V3S支持High Speed但实际速度受限于CPU和存储。如果backing文件在SD卡上写入速度可能只有几MB/s。如果PC端写入时报错检查镜像文件是否只读挂载或者文件系统是否损坏。可以用fsck.vfat修复。另外mass_storage的lun.0/ro属性如果设为1就是只读模式PC端无法写入。5.5 三个Function同时使用的稳定性实测下来三个Function共存时如果同时进行大量数据传输可能会出现带宽争抢。比如一边通过网卡传大文件一边往U盘写数据串口可能会丢数据。这是因为USB 2.0的总带宽是480Mbps三个Function共享。解决办法是错峰使用或者降低单个Function的负载。如果对串口实时性要求高可以给串口Function更高的优先级但Linux的Gadget框架没有直接的优先级配置只能从应用层控制发送速率。6. 进阶玩法与实用技巧6.1 开机自动配置Gadget每次手动敲命令太麻烦可以写一个systemd服务或者init脚本。我习惯用systemd创建一个/etc/systemd/system/usb-gadget.service[Unit] DescriptionUSB Gadget Setup Afterlocal-fs.target [Service] Typeoneshot ExecStart/usr/local/bin/setup-gadget.sh RemainAfterExityes [Install] WantedBymulti-user.targetsetup-gadget.sh里放前面那些configfs操作。注意脚本要有执行权限并且路径要对。6.2 用RNDIS兼容Windows 7如果目标PC是Windows 7ECM可能不被支持需要改用RNDIS。内核里对应的是g_rndis或者configfs的rndis.usb0。配置方式和ECM类似但Windows 7需要装一个INF驱动文件。这个INF文件在内核源码的Documentation/usb/gadget_rndis_wince.inf或者网上能找到。注意RNDIS在Linux和macOS下支持不如ECM好如果目标环境不固定建议优先用ECMWin7用户单独处理。6.3 串口和网卡共存的端点分配V3S的UDC端点数量有限如果发现某个Function创建失败可能是端点不够。可以用cat /sys/kernel/debug/usb/musb-hdrc.0.auto查看端点使用情况。一般来说acm需要2个端点ecm需要3个mass_storage需要2个总共7个V3S的UDC支持16个端点够用。如果确实遇到端点冲突可以调整Function的创建顺序或者禁用不用的Function。configfs的好处就是可以随时删除和重建Function。6.4 通过网卡做SSH和文件传输USB网卡配好后除了ping还可以跑SSH和NFS。V3S上跑dropbear或者opensshPC端就能ssh登录。传输文件可以用scp或者sftp速度比串口快得多。实测在High Speed模式下scp传输速度能到10MB/s左右取决于V3S的CPU负载。如果要做NFS挂载V3S上需要跑nfs-kernel-serverPC端mount。这个方案适合开发时快速同步文件比反复插拔U盘方便。6.5 U盘镜像的动态切换mass_storage的backing文件可以在运行时切换只需要往lun.0/file写入新路径。这个特性可以用来做多镜像切换比如一个镜像放固件一个镜像放日志。切换时PC端会看到U盘弹出再插入需要注意数据同步。实操心得切换backing文件前先在PC端安全弹出U盘否则可能损坏文件系统。V3S这边也要确保没有应用在读写镜像文件。7. 我个人在实际操作中的几点体会这个项目我从头到尾做了三遍第一遍用模块加载方式第二遍用configfs第三遍才把三个Function稳定共存。最大的感受是configfs虽然配置项多但一旦跑通灵活性和可维护性远超模块方式。特别是调试阶段改一个参数不需要重新加载模块直接echo就行。另一个体会是供电问题。V3S开发板通过USB口从PC取电如果PC的USB口供电不足枚举可能会失败或者不稳定。我后来换了一个带外部供电的USB Hub问题就消失了。如果你遇到莫名其妙的枚举失败先检查供电。最后分享一个小技巧如果PC端识别到的设备名称不对比如串口显示为“未知设备”可以修改strings/0x409里的product和manufacturer。这些字符串会直接显示在设备管理器里改成你容易识别的名字调试时能省不少事。这个方案后续还可以扩展比如加一个HID Function让V3S模拟键盘鼠标或者加一个UAC Function做USB声卡。V3S的性能虽然不强但跑这些Gadget Function绰绰有余。关键是把configfs的框架搭好后面加Function就是mkdir和ln -s的事。