拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

CH552入门USB开发:HID、CDC、AOA三大实战全解析

CH552入门USB开发:HID、CDC、AOA三大实战全解析 如果你逛过各大电子商城会发现很多USB小玩意USB转串口、USB键鼠、USB充电检测仪、USB程序烧录器背后用的都是同一颗几块钱的芯片——CH552。这颗基于增强型8051内核的国产芯片内置完整的USB2.0全速控制器价格却不到主流MCU的一半特别适合用来学习USB协议和做USB周边设备。这篇文章会以CH552为载体从HID、CDC、AOA三个常用USB设备类别出发带你走通“枚举—通信—调试”的完整链路。不管你是刚接触USB协议的嵌入式新手还是想快速出一个USB小产品的老手都可以直接参考这里的描述符配置和代码逻辑。1. 一颗几块钱的芯片为什么成了USB入门神器1.1 CH552的身世从“带USB的51”到WCH全家桶CH552是南京沁恒推出的一款增强型E8051内核MCU指令集兼容8051我当年第一次看到数据手册时还愣了一下这不就是一颗带USB的51单片机吗它的性能比传统8051高不少支持单周期指令主频最高24MHz而且片上集成了一整套USB2.0全速收发器。也就是说你不需要外部挂一颗USBN9604之类的PHY芯片直接把D/D-引到USB座子上就能工作。CH552的片上资源包括16KB Flash、1.25KB RAM、2路UART、2路PWM、ADC、SPI、I2C等。用做协议转换小产品时非常够用。更重要的是沁恒官方提供了相当完整的EVT例程里面包含了HID键盘、HID鼠标、CDC虚拟串口、模拟U盘甚至触控芯片方案。对学习USB的人来说这些例程比动辄几百页的USB规范书友好太多你完全可以直接基于EVT工程改而不是从零点寄存器。我个人的看法是玩USB不一定非要上STM32。STM32的USB库功能强但框架复杂初学者很容易被一堆回调函数绕晕。CH552的优势在于USB中断处理逻辑直接、寄存器数量少、代码量小整个工程拿来就能读懂。等你把CH552的USB机制吃透了回头再看其他MCU的USB库基本都能轻松迁移。1.2 USB全速设备到底是怎么“握手”的在写代码之前先花两分钟把USB枚举这件事讲清楚因为后面HID、CDC、AOA的所有操作都建立在这个过程上。USB枚举可以类比成一次入职流程设备插上后主机通过D/D-上的上拉状态判断设备是否是全速设备。CH552是USB2.0全速设备速度是12Mbps不是高速480Mbps这点一定要先分清不然抓包时会拿错判断标准。主机发出USB Reset把D/D-同时拉低一段时间设备回到默认地址0。主机在地址0处发起获取设备描述符请求设备返回18字节基础信息包括VID、PID、bcdUSB版本等。主机给设备分配一个唯一地址发送SetAddress请求之后所有通信都走这个地址。主机再次获取设备描述符和配置描述符确认设备的资源配置情况。主机发送SetConfiguration请求设备进入配置状态枚举结束接下来才轮到HID或CDC等类协议发挥作用。很多人上来就写HID报告描述符却不理解枚举顺序导致设备插上后系统一直提示“未知设备”。我的经验是先把枚举过程用逻辑分析仪或Wireshark抓一遍看到地址从0变成具体数字、描述符完整返回再进入类协议开发。枚举通了整个USB开发就已经成功了一半。1.3 开发环境与烧录方案CH552的开发环境有几个选择。官方主推MounRiver Studio界面基于Eclipse开箱即用如果你习惯老式8051开发也可以用Keil C51来编译然后把生成的HEX烧进去。我个人更倾向MounRiver因为官方例程基本都在这套环境下维护省去自己移植库的麻烦。烧录方面CH552内置了Bootloader第一次烧录时你可以用一个USB转串口模块比如CH340G把芯片拉进Program模式再用WCHISPTool软件下载HEX。这个模式一般要靠短接或上电时序来触发不同封装和开发板的操作不太一样建议直接买一块官方开发板方便很多也就十几块钱。实际项目里量产后可以用烧录夹或出厂烧录但对学习来说开发板足够了。还有一个很多新手容易忽略的点CH552的USB时钟精度要求很高。用外部12MHz晶振时负载电容要按手册选15~22pF用内部RC时钟时USB控制器需要做帧同步校准不然设备可能枚举成功后传输几个包就卡死。这个坑我在第5节还会讲到。2. 把CH552变成USB键鼠HID类实战2.1 HID设备的“简历”描述符与枚举HID全称Human Interface Device人机交互设备。键盘、鼠标、触摸板、游戏手柄都属于这个大类。HID设备最大的好处是免驱Windows、Linux、macOS都内置了标准HID驱动设备插上就能识别。在USB体系里HID设备通过一组描述符向主机“自报家门”设备描述符说明VID/PID配置描述符说明用了几个接口和端点HID描述符说明报告描述符的位置而报告描述符是关键它决定了主机如何解析你上报的数据。比如同一颗CH552把报告描述符定义成键盘格式它插入电脑后就是键盘定义成鼠标格式它就是鼠标。这也是HID设备能随便“变身”的原因。我建议你把HID报告描述符当成一种“数据Schema”来理解它定义了一条报告中每个bit的用途0x00到0xFF都被协议规范赋予了含义。比如按键A在键盘Usage ID里是0x04鼠标X轴移动是一个8位有符号数。主机按这套Schema解析应用层才能在合适的控件上收到正确输入。2.2 键盘鼠标报告描述符逐个啃直接给出一份最常见的6键无冲键盘报告描述符这也是大多数USB键盘在用的格式const UINT8 code HIDReportDesc[] { 0x05, 0x01, // Usage Page (Generic Desktop) 0x09, 0x06, // Usage (Keyboard) 0xA1, 0x01, // Collection (Application) 0x05, 0x07, // Usage Page (Key Codes) 0x19, 0xE0, // Usage Minimum (Left Control) 0x29, 0xE7, // Usage Maximum (Right GUI) 0x15, 0x00, // Logical Minimum (0) 0x25, 0x01, // Logical Maximum (1) 0x75, 0x01, // Report Size (1) 0x95, 0x08, // Report Count (8) 0x81, 0x02, // Input (Data, Variable, Absolute) 0x95, 0x01, // Report Count (1) 0x75, 0x08, // Report Size (8) 0x81, 0x01, // Input (Constant) 0x95, 0x05, // Report Count (5) 0x75, 0x01, // Report Size (1) 0x05, 0x08, // Usage Page (LEDs) 0x19, 0x01, // Usage Minimum (Num Lock) 0x29, 0x05, // Usage Maximum (Kana) 0x91, 0x02, // Output (Data, Variable, Absolute) 0x95, 0x01, // Report Count (1) 0x75, 0x03, // Report Size (3) 0x91, 0x01, // Output (Constant) 0x95, 0x06, // Report Count (6) 0x75, 0x08, // Report Size (8) 0x15, 0x00, // Logical Minimum (0) 0x25, 0x65, // Logical Maximum (101) 0x05, 0x07, // Usage Page (Key Codes) 0x19, 0x00, // Usage Minimum (None) 0x29, 0x65, // Usage Maximum (Apple) 0x81, 0x00, // Input (Data, Array) 0xC0 // End Collection };这份描述符对应了键盘上报的8字节数据字节0是修饰键位图Bit0代表CtrlBit1代表ShiftBit2代表AltBit3代表WinBit4到Bit7分别是右边对应的按键。字节1是保留字段置0。字节2到字节7是普通按键码数组最多同时上报6个键。这里用Array类型所以主机不会给每个bit附加含义而是把值当键码表索引用。鼠标报告描述符也类似但更简单3个按键位图、1个保留字节、然后是8位有符号X轴移动量和Y轴移动量。主流HID设备开发里鼠标X和Y通常用相对量就是报告这个周期相对上个周期移动了多少像素触摸屏才会用绝对量来描述坐标。有的朋友看到网上各种魔改的“HID远程控制”“CTF HID Paster”项目其实本质就是往CH552上报一份伪造键盘按键数据。比如想实现粘贴功能就是先按下Ctrl字节0对应Bit0置1再上报V键码最后全部清零。懂了报告描述符和按键码表你就能自己改出各种玩法。2.3 主循环里的上报逻辑描述符定好之后上报逻辑非常简单。键盘数据准备好后放到发送缓冲区然后触发端点1的IN传输UINT8 keyReport[8] {0}; void SetKey(UINT8 modifier, UINT8 keycode) { keyReport[0] modifier; // 修饰键位图 keyReport[2] keycode; // 普通键码A就是0x04 } void main(void) { CfgFsys(); CH552_USBDeviceInit(); while(1) { if (keyPressed) { SetKey(KEY_CTRL, KEY_V); // CtrlV HID_SendReport(keyReport, 8); delay_ms(5); keyReport[0] 0; keyReport[2] 0; HID_SendReport(keyReport, 8); } } }HID_SendReport里要处理端点缓冲区和中断标志位。CH552端点1如果配置成中断IN传输你可以先检测上一次传输是否完成再写缓冲区、触发发送避免数据覆盖。实际测试时上报周期在1ms到10ms之间基本都顺滑具体看你的应用需求。有个很容易踩的坑HID设备里上报频率太高不一定好。比如模拟鼠标时如果你没有改变坐标也频繁发相同数据包主机端会出现奇怪的“抖动”或“粘连”。我的惯例是只在数据变化时才发送报告这样既省带宽排查问题也更清楚。2.4 别忽略Boot协议与HID调试器写USB键盘时很多教程只讲报告描述符却漏了一个重要功能Boot Protocol。早期计算机BIOS里没有完整的USB HID协议栈只支持固定格式的键盘报告。因此HID规范规定键盘设备必须支持一个Boot模式报告格式固定为8字节不能走自定义格式。只有做到这一点USB键盘才能在BIOS界面、系统安装界面里正常使用。CH552官方例程里对Boot Protocol做了处理当主机发送Set_Protocol(Boot Protocol)请求时设备切换到固定8字节报告当主机发送Set_Protocol(Report Protocol)时设备才使用报告描述符定义的格式。我建议你在自己项目里也保留这个分支成本极低兼容性收益很大。调试HID时Windows下设备管理器里如果出现“HID-compliant device”说明枚举已经成功了。但你看不到具体数据流还是需要抓包工具。用Wireshark加USBPcap可以抓到中断传输内容也能看到控制传输里的GET_REPORT等请求。前面提到的CTF HID配合抓包工具一眼就能看出对方枚举了哪些端点、上报了什么数据做USB取证排查时非常有帮助。3. CDC虚拟串口做一枚“免驱”USB转TTL3.1 CDC类的三个角色通信接口、数据接口、通知端点CDCCommunications Device Class是USB定义的通信设备类最常见的形态就是USB虚拟串口。你在淘宝上买到的USB转TTL模块很多内部就是一个CDC设备插上电脑自动识别成COM口不用装驱动。CDC虚拟串口在描述符层面有点特殊它不是一个接口而是两个接口组成一个功能。第一个接口叫通信接口负责管理状态比如波特率、数据位、停止位、流控信号的设置第二个接口叫数据接口负责真实数据传输。两个接口之间靠一个“联合功能描述符”绑定在一起。另外通信接口通常还会带一个中断端点叫通知端点。主机可以通过它接收DTR/RTS状态变化通知。实际使用中Windows串口工具打开COM口时就会通过控制请求查询和设置串口参数如果你没正确处理这些CDC控制请求即使数据端点已经配置好串口也不一定能正常打开。3.2 描述符和端点配置对照我列一个典型的CDC虚拟串口配置描述符要点表方便你对照自己的代码检查接口接口类端点用途接口0通信接口0x02 通信设备类0x82 中断IN通知端点状态和Line Coding接口1数据接口0x0A CDC数据类0x01 批量OUT主机发给设备的数据接口1数据接口0x0A CDC数据类0x81 批量IN设备发给主机的数据通信接口里需要包含两个功能描述符Header Functional Descriptor和Call Management Functional Descriptor再加上Union Functional Descriptor来捆绑接口0和接口1。很多人做CDC卡在枚举阶段一张配置描述符太长wTotalLength字段没算对主机解析到一半就截断自然识别不了。如果你想做复合设备比如HID加CDC在同一个CH552里可以把HID的接口描述符和CDC的两组接口描述符全部塞进一份配置描述符同时申明一个IADInterface Association Descriptor来划分功能分组。WCH库里有复合设备的例程照着改就行但新手不建议一上来就搞复合设备先把单一CDC跑通再说。3.3 收发缓冲与流量控制实测CDC的数据收发本质上是两条批量传输通道数据接口的批量OUT端点接收主机下发数据批量IN端点把MCU发出的数据送上去。在CH552主循环里常见的处理框架是这样的// UART数据通过USB虚拟串口发给电脑 if (uart_rx_len 0) { CDC_SendData(uart_rx_buf, uart_rx_len); } // 电脑下发的USB数据通过UART发出去 if (usb_ep_out_flag) { UART_SendBuffer(usb_rx_buf, usb_rx_len); usb_ep_out_flag 0; }这里有个容易被忽视的点批量端点一次传输最大64字节但UART上来可能只有几个字节也可能一次是几百字节。如果CDC缓冲区和UART的DMA配合不好数据量大时容易丢包。我在项目里通常给UART加环形缓冲区USB端批量读出数据后先放进环形队列再由UART侧按实际波特率慢慢发出去这样两边速度不同也能平滑过渡。还要在控制传输的处理里对GET_LINE_CODING、SET_LINE_CODING、SET_CONTROL_LINE_STATE这三个请求返回正确响应。Windows系统一打开COM口就会发送这些请求来设置波特率、检查DTR状态你如果不做任何处理直接返回STALL设备管理器可能显示正常但用串口助手打开时会提示COM口被占用或打开失败。3.4 和CP2102/FT232R这些专用桥接芯片的优劣提到USB转串口很多人第一时间想到CH340G、CP2102、FT232R这些现成的USB-UART桥接芯片。那CH552做CDC的优势在哪我对比一下实际选型经验方案价格可编程性驱动成熟度适合场景CH340G/CH340N1~2元不可编程成熟纯USB转串口CP2102N2~4元不可编程很成熟对稳定性要求高的桥接FT232R10元不可编程极成熟工业环境、高抗干扰CH552自制CDC3~5元完全可编程系统内置CDC驱动需要协议处理的定制设备CH552方案最大的价值不是替代CH340而是在串口链路上增加“智能处理”比如USB转CAN、USB转RS485、USB下载器、USB逻辑分析仪、USB-DAPLink之类核心都是CDC通道加MCU逻辑。你用CH552做一块USB-CAN转换器数据进来后先经过CAN控制器再发出去这种功能是专用桥接芯片给不了的。当然它也有短板CH552的GPIO和UART资源有限波特率误差受内部RC和晶振影响尤其在高波特率场景下要校准时钟。如果只是给工控设备做一个稳定串口直接买CP2102N模块更省心不要拿CH552硬秀。4. AOA模式先泼冷水再讲怎么落地4.1 AOA是什么为什么很适合交互外设AOAAndroid Open Accessory是Android系统从3.1开始支持的USB附件协议。它允许外部硬件设备和Android设备建立连接典型应用场景包括车载多媒体底座、外接键盘、人工刷卡器、嵌入式检测仪器等。AOA的通信模式比较特殊外部硬件充当USB主机Android手机充当USB外设。手机被外部硬件“带领”进入Accessory模式后外部硬件可以在Android设备上触发APP并建立数据通道。这种模式的好处是你不需要在手机上做USB Host权限申请很多扩展坞场景里体验反而比OTG方式更直接。4.2 关键事实CH552没有USB Host硬上AOA会卡在哪这里必须说清楚一个硬性限制CH552的USB控制器只有Device模式没有Host模式不能作为USB主机对外发起访问。而AOA协议的第一步要求外部设备发起一系列vendor request让Android手机切换到附件模式这只有USB主机控制器才能做到。所以理论层面的答案很残酷CH552直接跑AOA是不成立的。你会在“如何用CH552做AOA”这条路上卡死在第一步连手机都枚举不了。如果项目需求明确要AOA最便宜的路线是换一颗CH554它是USB Host/Device二合一单片就能把主机端逻辑跑起来。想用STM32也可以选带USB OTG主机功能的型号比如STM32F407或者树莓派Zero这类。如果只是想实现“安卓手机控制键盘鼠标模拟”这种功能其实用HID就够了CH552把自己枚举成USB键盘或鼠标然后通过OTG线接到手机上手机侧没有AOA也能直接用。网上很多“手机鼠标键盘模拟”项目就是这么做的并不需要AOA协议。4.3 正确的AOA落地流程基于CH554或带USB主机的MCU完整AOA流程是USB主机控制器轮询总线检测到Android手机后先发送ACCESSORY_GET_PROTOCOL请求读取手机支持的AOA协议版本。如果版本号大于或等于1主机继续发送六组字符串manufacturer、model、description、version、uri、serialNumber。这些字符串会显示在Android系统的附件信息里。主机发送ACCESSORY_START请求通知手机切换进Accessory模式。手机会重新枚举一次变成一个AOA附件设备。重新枚举后主机通过新的批量端点如果启用音频还有音频端点和手机APP进行数据交互。Android APP侧需要声明USB_ACCESSORY_ATTACHED的Intent过滤才能被系统自动拉起。Vendor Request的具体bRequest值我在项目代码的宏定义里都会写成可读名字例如ACCESSORY_GET_PROTOCOL、ACCESSORY_SEND_STRING、ACCESSORY_START。到了AOA2.x还会增加音频模式切换等更多请求但这个基础流程是不变的。挺多人一开始拿着CH552折腾AOA折腾很久都没反应后来搞明白是芯片没有Host能力之后都很无语所以这个坑我必须写在前面。4.4 CH552在AOA工程里能扮演什么角色既然CH552做不了主机那它能不能在AOA系统里打辅助完全可以。我做过一个车载环境下的AOA检测设备主控是CH554负责USB Host接入Android手机。CH552则在系统里担任两个角色一是通过CDC虚拟串口提供调试日志通道方便我手机端同时开一个串口助手看主机状态二是作为ADC采集板把温度、电压数据通过UART传给CH554再由CH554通过AOA上报给手机APP。如果你已经有AOA主控又想扩大系统接口能力CH552是非常廉价的“边角功能MCU”。它自带UART、SPI、I2C、ADC正好做数据采集和简单执行器控制。用它分担主控的杂活整套硬件成本能压得很低可靠性也不错。5. USB整不明白这里有一套排错流程5.1 抓包工具组合拳USB开发到后期最实用的技能不是背规范而是会抓包。我平时在Windows下基本是USBPcap加Wireshark组合USBPcap装好后以管理员权限打开Wireshark选择USBPcap接口就能看见总线上的所有USB事务。Linux下则用usbmon内核把USB总线流量导出Wireshark直接读取。Bus Hound也是老牌抓包工具界面相对硬核一点对控制传输的细节呈现更直观。很多设备枚举失败、端点返回STALL的问题用Bus Hound一眼就能看到失败事务发生在哪个阶段。如果你手头有逻辑分析仪也可以直接测D/D-信号但抓包软件能覆盖90%的问题。抓包时重点看三样东西设备地址是否从0切换成了主机分配的地址。获取描述符请求是否完整返回有没有返回STALL或者数据长度不对。中断/批量端点里的数据内容是否符合预期。5.2 枚举失败定位四步法当一个USB设备插上电脑后没有任何反应我的排查套路非常固定第一步打开设备管理器看未知设备还是正常设备。如果显示未知设备且VID是0000或FFFF基本可以断定主机根本没收到完整设备描述符。第二步检查D/D-的上拉和走线。全速设备必须在D上做1.5k上拉很多自制板上如果漏了或者错把上拉电阻放在D-上主机就会把它当成低速设备枚举协议完全对不上。第三步用示波器或逻辑分析仪看插入瞬间的波形。正常会看到主机拉低总线产生Reset然后开始包交换如果只有Reset后面毫无反应问题多半在芯片时钟或配置描述符长度上。第四步把抓包数据拉出来看主机在地址0处请求设备描述符后设备有没有回应。如果回应了继续看SetAddress之后的流程。至少有一半的项目问题出在配置描述符的wTotalLength填错或者接口描述符顺序不对导致主机读不完这个描述符。5.3 电源、晶振和布线的几个暗坑玩USB设备还要特别留意硬件层面的坑。USB口供电是5VCH552大部分封装工作电压是3.3V直接接5V容易出事。我的做法是板上放一颗低功耗LDO5V经过LDO转3.3V给MCU供电USB插座的5V和GND之间再加一颗TVS管防止热插拔浪涌打坏芯片。晶振方面CH552的USB模块需要精确时钟。如果你用内部RCCH552支持通过USB帧同步自动校准这样能免掉外部晶振如果你对稳定性要求高就外接12MHz晶振负载电容根据晶振datasheet选择。晶振焊错电容值会导致时钟漂移现象是设备偶尔能枚举成功但一旦传输大数据包就断开。PCB布线上D/D-尽量等长、靠近不要绕圈。信号线上加22Ω串联电阻能吸收振铃USB插座金属外壳要处理好接地。我见过一个板子静电打几次就“设备无法识别”最后发现是USB座外壳没有和主板地连好整条信号链路被干扰得一塌糊涂。6. 源码与一点题外话6.1 怎么跑通示例工程如果你想从零跑通这套东西我的建议是先下载一份WCH官方EVT包网上搜“CH552EVT”就能找到。EVT里已经包含芯片头文件、USB库和HID/CDC的例程。把官方工程编译烧录后先确认HID键盘能打字CDC串口能收发然后再按文章里的报告描述符和配置描述符逻辑去改自己的项目。文章里提到的HID键盘报告描述符、CDC接口配置要点我建议你直接对照官方Example看逐行理解。完整可以直接编译的源码包我也会放在配套资源里需要的朋友自己取。这里说的源码和我文章里粘贴的关键代码完全一致不会让你拿到手后还要对半天。6.2 踩过几次坑之后的心得我自己带新人做USB项目时一直推荐这条路先用CH552跑通HID键盘接着做CDC虚拟串口再回头看USB枚举细节。等你在Wireshark里看到自己设备干净利落地完成枚举那种感觉比抄十份例程都值。AOA好玩但它需要Host能力别拿着一颗Device only的芯片硬钻。最后再分享一个小经验无论做什么USB项目PCB上一定把D/D-、VCC、GND四个点留成测试点后面调试和抓包会感谢这个设计。
返回列表