基于LAT1466的USB HID Standalone设备移植实践与踩坑记录

发布时间:2026/8/29 10:24:13
基于LAT1466的USB HID Standalone设备移植实践与踩坑记录 做 USB HID 设备开发这几年几乎每次换一颗新的 MCU 都要重新趟一遍枚举的坑。这次把基于 LAT1466 的 USB Device HID Standalone 方案完整跑通从底层控制器适配到报告描述符调通整个过程比想象中顺畅但确实也有几个地方差点翻车。这篇东西把移植思路和实操细节都整理出来给要在 LAT1466 或者类似 Cortex-M 平台上做 HID 设备的朋友一个参考。这个项目要解决的事情不复杂让 LAT1466 作为 USB 从设备连接到电脑被系统识别为 HID 设备不装任何自定义驱动插上就能用数据通过中断端点双向收发。适合正在做 USB 键鼠、自定义 HID 透传、或者想把现有 HID 方案换平台的朋友。下面按方案选型、协议细节、移植步骤、问题调试的顺序展开。1. 项目背景与整体方案选型为什么选 HID Standalone1.1 HID 设备的核心优势与使用场景HIDHuman Interface Device类设备在 USB 协议栈中属于最省事的设备类。操作系统自带 HID 驱动Windows、Linux、macOS 全部原生支持不需要额外安装驱动文件。这一点对量产设备来说极其重要——不需要给客户提供驱动安装包不用担心驱动签名认证问题也不用为不同系统版本维护不同的驱动版本。在实际项目里我很多时候会用自定义 HID 来代替虚拟串口CDC做数据透传因为 CDC 驱动在某些精简版系统上偶尔会被剪裁而 HID 驱动是系统最底层的组件之一几乎不可能被移除。Standalone 这个关键词在这次项目里指的是设备端独立运行不依赖上位机的持续控制。设备上电后自己完成 USB 枚举自己管理 HID 报告的收发逻辑跑裸机主循环或者挂 RTOS 都可以。移植的目标就是把整套 HID 设备逻辑完整搬到 LAT1466 上并且保证主机端没有任何配置操作就能直接识别和使用。1.2 平台选型的考量与方案对比LAT1466 是一款集成 USB Device 控制器的国产 MCU整体资源配置属于中等偏上水平适合做工业控制面板、消费电子外设这类 HID 设备。选择这颗芯片的原因很现实项目方要求把原来运行在其他平台上的 HID 方案整体迁移过来降低硬件成本同时保持功能完全不变。在具体技术路线上我对比过三种方案。第一种是使用 LAT1466 厂商自带的 USB 协议栈。优点是针对性最强底层寄存器操作都已经封装好了上手最快。缺点是文档质量参差不齐部分 API 设计比较别扭而且如果后续要换平台代码复用率很低。第二种是使用开源 USB 协议栈比如 CherryUSB 或者 TinyUSB。CherryUSB 是国产开源协议栈对新兴国产 MCU 的适配越来越完善文档是中文的社区也比较活跃遇到问题方便交流。TinyUSB 生态更大但适配新平台的工作量稍高。第三种是自己从零写协议栈。这个只适合学习用途生产项目完全不推荐。USB 枚举状态机虽然不算复杂但细节非常多自己写的协议栈很难覆盖所有边界情况调试周期会拉得很长。综合比较下来我选了 CherryUSB 作为协议栈基础。原因有三点一是 CherryUSB 的架构分层清晰核心层、类驱动层、端口适配层分离得干净移植时只需要关注 port 层二是它内置的 HID 类驱动封装得比较完整标准请求和类请求都处理好了三是它支持 Finsh/Shell 方式的调试打印配合串口能看到协议栈内部的运行状态。注意无论选哪种方案前期都要确认协议栈的许可证是否符合商用要求。CherryUSB 使用的是 Apache-2.0 许可商用没问题。2. USB HID 协议要点移植前必须搞懂的底层逻辑2.1 HID 描述符族的结构与关系在动手写代码之前先把 HID 设备涉及到的描述符结构搞清楚。一个标准的 HID 设备需要向主机提供四类描述符设备描述符、配置描述符、字符串描述符可选以及 HID 特有的类描述符和报告描述符。很多人会把 HID 描述符和报告描述符搞混这两个东西虽然名字像但作用完全不同。HID 描述符是放在配置描述符里的一个类特定描述符它告诉主机我这个接口是 HID 类接口并且指明报告描述符的长度报告描述符则是真正定义数据格式的地方主机通过解析报告描述符才知道哪些字节代表鼠标坐标哪些字节代表按键状态。报告描述符的字节序列看起来像天书每个字节都有明确的语义。比如0x06, 0x00, 0xFF表示用法页Usage Page为 0xFF00也就是厂商自定义用法页0x09, 0x01表示用法为 Vendor Usage 10xA1, 0x01表示开始一个 Application Collection0x75, 0x08表示报告大小为 8 位0x95, 0x20表示报告数量为 32 个。这些字段组合起来就定义了你的 HID 报告格式。2.2 端点类型与传输方式的适配HID 设备使用中断传输Interrupt Transfer这一点和批量传输Bulk Transfer有明显区别。中断传输保证了相对固定的延迟主机在每个轮询周期都会来读取数据适合对实时性有要求的交互型设备但它的带宽是受限的不适合大数据量的突发传输。一个 HID 设备至少需要一个中断 IN 端点用于向主机发送数据。如果需要接收主机下发的指令还要配置一个中断 OUT 端点。端点最大包长在全速模式下通常配置为 64 字节低速键盘设备则只有 8 字节。具体选择多大包长取决于你的报告大小一般建议 64 字节以内的报告长度都直接配 64 字节端点方便扩展。我这次项目里用的是 32 字节因为自定义报告定义了 32 字节的输入和 32 字节的输出。关于轮询间隔bInterval全速中断端点的单位是 1ms 帧周期我配置成 1ms这样上位机读取数据的延迟最小。如果做的是键盘类设备为了省电可以放宽到 10ms但做数据透传还是越快越好。2.3 标准请求与类请求的处理USB 枚举过程涉及大量标准请求比如 GET_DESCRIPTOR、SET_ADDRESS、SET_CONFIGURATION 等等这些协议栈核心层都会处理。HID 设备额外需要处理类请求包括 GET_REPORT、SET_REPORT、GET_IDLE、SET_IDLE、GET_PROTOCOL、SET_PROTOCOL。最容易出问题的是 SET_IDLE。主机尤其是 Windows在设备枚举完成后可能发送 SET_IDLE 请求来设置空闲速率半字节的值表示空闲延迟0 表示设备可以自行决定是否降低报告频率。如果固件没有处理这个请求或者返回错误设备可能表现为枚举正常但数据不刷新或者首次数据发送有异常的延迟。CherryUSB 的 HID 类驱动已经实现了 SET_IDLE 的处理逻辑但如果自己写协议栈这里一定要小心。3. 移植核心流程从零把 HID Standalone 跑在 LAT1466 上3.1 工程搭建与代码目录规划我用的是 Keil MDK 开发环境配合 arm-none-eabi-gcc 工具链交叉验证。先把 CherryUSB 的源码拉下来核心目录结构大概是这样cherryusb/class/hid/HID 类驱动包括 hid.h、hid.c 等cherryusb/core/协议栈核心包括 USB 状态机、端点调度、标准请求处理cherryusb/port/各个平台的 USB 控制器适配代码LAT1466 不在官方的 port 目录里所以这里就是移植工作的核心位置。我需要自己编写usb_dc_lat1466.c和对应的头文件把 LAT1466 的 USB Device Controller 寄存器操作封装成 CherryUSB 规定的接口函数。工程结构上我把 LAT1466 厂商 SDK 中的 USB 相关头文件放在一个独立目录把 CherryUSB 源码放在另一个独立目录通过头文件搜索路径把它们串联起来。这样做的好处是协议栈和底层驱动完全隔离后续升级 CherryUSB 版本或者更换芯片平台改动的文件范围可控。3.2 底层 USB 控制器适配实操这是整个移植过程里最核心、最容易翻车的部分。LAT1466 的 USB 控制器寄存器手册我啃了整整两天重点关注几个模块设备地址寄存器、端点使能寄存器、端点 FIFO 管理、中断状态寄存器。CherryUSB 定义了一组底层接口核心函数包括int usb_dc_init(void); int usb_dc_deinit(void); int usb_dc_ep_open(const struct usb_ep_cfg *ep_cfg); int usb_dc_ep_close(uint8_t ep); int usb_dc_ep_write(uint8_t ep, uint8_t *data, uint32_t len); int usb_dc_ep_read(uint8_t ep, uint8_t *data, uint32_t len); int usb_dc_ep_stall(uint8_t ep); int usb_dc_ep_clear_stall(uint8_t ep);每个函数都要根据 LAT1466 的寄存器手册来实现。以usb_dc_ep_open为例需要做这几件事首先配置端点的类型中断、批量、同步或控制然后设置端点最大包长全速模式下中断端点我设置的是 32 字节。接下来是比较关键的 FIFO 管理。LAT1466 的端点 FIFO 是共享的内存池需要手动给每个端点分配起始地址和大小。我分配给 EP0 的 FIFO 是 64 字节EP1 IN 分配 32 字节EP1 OUT 分配 32 字节分配完成后要把起始地址寄存器写对。这部分有一个容易踩的坑FIFO 的地址是对齐到特定边界的比如 8 字节对齐如果分配不当读写数据会错位表现出来就是数据莫名其妙地乱掉但逻辑分析仪上又看不出问题。我调试的时候发现 EP1 IN 发出来的数据首个字节经常变成 0x00 或者丢字节最后查出来就是 FIFO 起始地址没有按对齐要求配置。再看中断处理。LAT1466 的 USB 控制器中断需要编写中断服务函数ISR在 ISR 里判断中断标志分别处理总线复位、端点传输完成、 SETUP 包到来等事件然后调用 CherryUSB 内核提供的回调函数void USB_IRQHandler(void) { uint32_t int_status usb_get_int_status(); if (int_status USB_INT_RESET) { usb_clear_int_flag(USB_INT_RESET); usbd_core_reset(); // USB 总线复位重置设备状态 } if (int_status USB_INT_EP0_SETUP) { usb_clear_int_flag(USB_INT_EP0_SETUP); usbd_setup_transaction(); // 处理 SETUP 包 } if (int_status USB_INT_EP_IN) { usb_clear_int_flag(USB_INT_EP_IN); usbd_ep_in_transaction(); } if (int_status USB_INT_EP_OUT) { usb_clear_int_flag(USB_INT_EP_OUT); usbd_ep_out_transaction(); } }总线复位处理要特别注意。USB 总线复位时主机要求设备把地址恢复到 0所有端点回到未配置状态。如果这里没有清理干净会导致下一次 SET_ADDRESS 请求处理错乱。我之前用的一个平台移植代码就是在复位中断里忘了清端点 FIFO导致地址设置成功后后面的 GET_DESCRIPTOR 请求总是返回错误。3.3 HID 类与描述符配置底层适配完成后HID 类的配置就相对顺了。在usb_config.h里开启 HID 类支持#define CONFIG_USBDEV_HID 1然后在描述符定义文件里编写各个描述符。设备描述符中bDeviceClass、bDeviceSubClass、bDeviceProtocol三个字段都置为 0表示设备类的定义在接口描述符层。这样主机看到的是一个复合类设备具体的 HID 类定义在接口描述符中。配置描述符里包含了接口描述符、HID 描述符、端点描述符。HID 描述符的定义是这样的const uint8_t config_desc[] { // 配置描述符 0x09, 0x02, /* bLength, bDescriptorType */ (sizeof(config_desc) 0xFF), (sizeof(config_desc) 8), /* wTotalLength */ 0x01, /* bNumInterfaces */ 0x01, /* bConfigurationValue */ 0x00, /* iConfiguration */ 0x80, /* bmAttributes: Bus Powered */ 0x32, /* bMaxPower: 100mA */ // 接口描述符 0x09, 0x04, /* bLength, bDescriptorType */ 0x00, /* bInterfaceNumber */ 0x00, /* bAlternateSetting */ 0x02, /* bNumEndpoints */ 0x03, /* bInterfaceClass: HID */ 0x00, /* bInterfaceSubClass: None */ 0x00, /* bInterfaceProtocol: None */ 0x00, /* iInterface */ // HID 描述符 0x09, 0x21, /* bLength, bDescriptorType(HID) */ 0x01, 0x01, /* bcdHID 1.01 */ 0x00, /* bCountryCode */ 0x01, /* bNumDescriptors */ 0x22, /* bDescriptorType: Report */ (sizeof(hid_report_desc) 0xFF), (sizeof(hid_report_desc) 8), /* wDescriptorLength */ // 中断 IN 端点 0x07, 0x05, /* bLength, bDescriptorType */ 0x81, /* bEndpointAddress: EP1 IN */ 0x03, /* bmAttributes: Interrupt */ 0x20, 0x00, /* wMaxPacketSize: 32 */ 0x01, /* bInterval: 1ms */ // 中断 OUT 端点 0x07, 0x05, 0x01, /* bEndpointAddress: EP1 OUT */ 0x03, 0x20, 0x00, 0x01, };报告描述符我定义了一个自定义 HID 数据透传格式32 字节输入加 32 字节输出const uint8_t hid_report_desc[] { 0x06, 0x00, 0xFF, // Usage Page (Vendor Defined 0xFF00) 0x09, 0x01, // Usage (Vendor Usage 1) 0xA1, 0x01, // Collection (Application) 0x85, 0x01, // Report ID 1 0x09, 0x01, // Usage (Vendor Usage 1) 0x15, 0x00, // Logical Minimum (0) 0x26, 0xFF, 0x00, // Logical Maximum (255) 0x75, 0x08, // Report Size (8 bits) 0x95, 0x20, // Report Count (32) 0x81, 0x02, // Input (Data, Variable, Absolute) 0x09, 0x01, // Usage (Vendor Usage 1) 0x75, 0x08, // Report Size (8 bits) 0x95, 0x20, // Report Count (32) 0x91, 0x02, // Output (Data, Variable, Absolute) 0xC0 // End Collection };注意0x85, 0x01这一行定义了报告 ID 为 1。如果报告描述符里定义了报告 ID那么设备发送数据时第一字节必须是报告 ID后面跟报告数据。这会带来一个非常隐蔽的问题16 字节的报告 ID 1 32 字节数据实际上占用了 33 字节如果你端点最大包长是 32 字节就会溢出。我一开始就掉进这个坑里后来把报告 Count 减到 31或者去掉报告 ID才解决。3.4 应用层数据收发逻辑移植描述符和底层驱动都就绪后HID 数据收发调用就很简单了。CherryUSB 封装了现成的 APIuint16_t usbd_hid_send_report(uint8_t ep, uint8_t *data, uint16_t len); uint16_t usbd_hid_receive_report(uint8_t ep, uint8_t *data, uint16_t len);发送逻辑放在主循环中从传感器采集数据打包成报告后调用发送接口。以这个项目为例设备定时上报 4 字节传感器数据和 4 字节状态标志int main(void) { system_init(); usb_dc_init(); usbd_hid_init(); uint8_t report_buf[32] {0}; while (1) { // 采集数据 uint32_t sensor_val read_sensor(); uint8_t status read_status(); report_buf[0] 0x01; // Report ID report_buf[1] sensor_val 0xFF; report_buf[2] (sensor_val 8) 0xFF; report_buf[3] (sensor_val 16) 0xFF; report_buf[4] (sensor_val 24) 0xFF; report_buf[5] status; usbd_hid_send_report(0, report_buf, 6); delay_ms(10); } }接收逻辑通过注册回调实现。主机下发命令时协议栈自动把数据存入缓冲区然后调用回调函数通知应用层void hid_callback(uint8_t ep, uint8_t *data, uint16_t len) { // 处理主机下发的命令 process_command(data, len); }这里有一个关键点usbd_hid_send_report的第一个参数填 0 代表使用默认的 HID 类 OUT 通道。如果配置了多个 HID 接口需要填对应的接口号。多接口 HID 复合设备在 CherryUSB 里也支持但接口号和报告 ID 的映射要配对好。4. 调试方法论与踩坑记录全是实操真问题4.1 枚举失败最常见的几种原因USB 设备调试最痛苦的是枚举阶段。这段时间设备还没被操作系统正确识别你没法通过上位机软件查看状态只能在固件和硬件之间反复排查。我遇到的现象是 LAT1466 插上电脑后设备管理器完全没有任何反应偶尔出现未知设备。排查步骤一定要系统化。第一步检查 USB 控制器的时钟配置。USB 模块通常需要精确的 48MHz 时钟源。如果锁相环PLL配置不对USB 模块完全无法工作。这个问题的特征是物理层完全静默用示波器量 D、D- 引脚只有直流电平没有任何波形。我这次就吃过一次亏厂商 SDK 里默认时钟配置的是内部 RC 振荡器精度不够 USB 要求改成外部晶体加 PLL 后立刻正常了。第二步确认 D 上拉电阻是否生效。全速设备需要在 D 引脚并联一个 1.5k 上拉到 3.3V告诉主机我是一台全速设备。很多 MCU 内部集成了这个上拉需要单独使能但寄存器操作方式各不相同。如果上拉没生效主机永远检测不到设备插入事件。第三步用 USB 分析工具看包。我用的是 Beagle USB 480 分析仪能看到枚举过程中的每一个握手包。如果完全看不到任何包问题出在物理层如果看到 SETUP 包但设备没响应问题大概率在固件的 USB 中断处理逻辑上。4.2 描述符写错导致系统黄叹号枚举成功后Windows 设备管理器里看到设备但带上黄色感叹号设备无法启动代码 10这种情况十有八九是描述符的问题。用 USBTreeView 这个免费工具可以查看主机解析出来的描述符树对照固件里的原始字节就能定位到具体是哪个描述符字段写错了。我遇到过一次很奇怪的情况设备描述符正确配置描述符正确但 Windows 就是报代码 10。用 USBTreeView 一查报告描述符长度被截断了。原来是 HID 描述符里的wDescriptorLength字段我用了sizeof(hid_report_desc)宏但 hid_report_desc 数组声明在另一个文件里编译器计算的长度是错的。这个问题折腾了我一下午最后把数组长度改成手动计算的常量就解决了。注意HID 描述符中的wDescriptorLength必须和实际的报告描述符长度完全一致。多一个字节、少一个字节都可能导致主机解析失败。另外如果报告描述符里定义了报告 ID但实际发送数据时不带 ID主机解析数据就会错位。这个错位在 HID 调试工具上看数据表现为首字节丢失或者整体位移。我后来统一了规则定义报告 ID 就所有收发都带上 ID不定义就全部不带绝不在中途改。4.3 SET_IDLE 请求与数据延迟问题有次项目联调上位机工程师反馈数据大概 10 秒才刷新一次但代码里明明设置的是 10ms 发送一次。排查了很久发现是 SET_IDLE 请求在捣鬼。Windows 枚举 HID 设备后可能发送 SET_IDLE 请求半字节的超时值含义比较复杂总结一下就是如果设置了非零值设备在空闲时间达到该值之前不得重复发送相同内容的报告。如果设备每次都发送相同数据主机会认为数据没变化就不更新界面实际表现因操作系统而异。在这个问题上CherryUSB 的实现是按协议规范做的但它有一个开关CONFIG_HID_SUPPORT_SET_IDLE默认可能是关闭的。如果你遇到数据刷新周期异常把这个开关打开看看是否有改善。更好的做法是在 HID 类的实现中直接忽略 SET_IDLE 请求返回 STALL 或者 ACK 都可以但返回 ACK 更保守因为某些主机驱动期望得到 ACK。4.4 工具链与编译环境的兼容性问题热词里有一句 device is not supported by toolchain这种报错我在新平台移植时经常遇到。本质上是编译器版本太老不认识芯片内核的新特性。LAT1466 如果是 Cortex-M33 内核那就需要较新版本的 ARM Compiler 支持同时调试器的配置文件比如 OpenOCD 的 target 配置也要匹配。解决方法是升级工具链到官方推荐的版本或者换用厂商自己的 IDE。还有一个很坑的问题旧版的烧录工具可能不认识这颗芯片的 IDCODE报错 not a genuine st device 或者 abort connection。这种问题一般更新烧录工具版本就能解决。我当时用的是 DAP-Link 加上新版 OpenOCD配置上需要手动指定芯片的 flash 算法否则无法写入程序。4.5 调试工具的搭配使用心得这次移植我用了三组工具各有不可替代的作用。第一组是硬件协议分析仪。Beagle USB 480 能看到主机和设备之间所有的 USB 包用的td_analyzer上位机软件能满足大部分需求。枚举阶段问题、类请求是否符合规范这些都能直接看到包内容。第二组是软件分析工具。USBTreeView 查看设备描述符树HID 调试助手比如 YAT 或者仁爱 HID 调试工具用来做数据收发测试。配合使用可以快速判断枚举是否成功、报告描述符是否被正确解析、数据收发是否正常。第三组是逻辑分析仪。不需要太多通道只要同时抓 D、D- 两个引脚用 24MHz 采样率就能还原 USB 全速信号的包结构。虽然比不上专业分析仪但在没有硬件分析仪的情况下能应急。注意解码时需要设置差分模式否则采出来的电平是反的。5. 移植完成后的性能验证与后续扩展方向5.1 功能验证清单与性能指标移植完成后我在 Windows 10 和 Linux 双系统下做了完整的验证。Windows 端设备管理器识别为 HID 兼容设备没有黄叹号HID 调试助手收发数据正常。Linux 端通过dmesg查看枚举日志能够正常识别用hidraw接口读写数据收发正常。性能测试方面全速模式下端点配置为 32 字节包长轮询间隔 1ms实测最大单向吞吐约 32KB/s考虑到这是一台 HID 设备而不是批量传输设备这个速率已经足够满足交互类应用的需求。对于需要更高吞吐的场景可以把端点最大包长调到 64 字节但需要确认报告描述符的报告长度与端点包长匹配。功耗测试也做了。设备在空闲状态下没有挂起时电流约为 15mA主要功耗来自 MCU 内核时钟。如果要做低功耗应用可以在主机进入挂起状态收到 SUSPEND 状态时切到低功耗模式等待唤醒信号。CherryUSB 提供了挂起/恢复的回调接口建议接上不然长时间连接会浪费功耗。5.2 数据完整性与长时间运行测试我用一个脚本做了 8 小时连续收发测试发送端每 10ms 发一包 32 字节递增数据接收端校验是否有丢包或者误码。结果丢包率为 0但测试过程中发现一个问题如果主机端 USB 控制器负载高同时接多个 U 盘传输HID 设备偶尔会出现上报延迟这是 USB 总线调度机制决定的全速 HID 设备的中断传输优先级低于高速设备的批量传输无法根治。解决方案有两条一是使用高速 USB 控制器和设备二是把 HID 设备配置为复合设备用额外的上报通道做冗余。5.3 后续扩展方向复合设备与 Boot Protocol这个基础 HID 移植跑通之后扩展空间很大。我的下一步计划是在同一颗 LAT1466 上做 HID 复合设备即同时虚拟出键盘、鼠标和自定义数据通道三个接口。主要工作是增加接口描述符数量和对应报告描述符还有在协议栈初始化时注册多个 HID 接口。CherryUSB 原生支持多接口 HID接口号与报告 ID 的映射关系要仔细配置。如果做键盘鼠标还要考虑 Boot Protocol 的支持。这个协议是 HID 的一个子集允许在系统没有 HID 驱动时比如 BIOS 环境使用基础键鼠功能。需要在接口描述符中将bInterfaceSubClass设为 1Boot Interface同时支持 GET_PROTOCOL、SET_PROTOCOL 请求。我在移植过程中已经把相关逻辑预留了后续接入键盘功能时验证。另外如果设备需要支持 DFU 升级可以再叠加一个 HID DFU 类实现通过 HID 通道刷写固件。这样既不需要额外的下载器也不需要在主机端装驱动对现场升级来说非常方便。具体的实现思路是在设备描述符中支持两个配置或者在一个配置下支持两个接口一个 HID 数据接口、一个 HID DFU 接口通过厂家特定的请求在两种模式之间切换。5.4 最后的经验沉淀这次 LAT1466 移植我把踩过的坑都整理成了一份 checklist后续其他项目可以直接复用。第一USB 时钟源必须精确到 48MHz这是所有问题的起源。第二枚举阶段一定要先抓包确认物理层通然后再查固件逻辑不要凭感觉改代码。第三报告描述符里的长度字段和数据格式必须和实际收发代码完全对照一个字节的偏差就是一天的时间。第四SET_IDLE、GET_REPORT 等类请求必须处理好否则设备的兼容性会很差。做 USB HID 移植没有太多妙招核心就是把协议规范和底层硬件吃透剩下的就是耐心调试。希望这篇内容能帮你少走一些弯路尤其是在描述符和底层适配这两个最容易出问题的环节上。如果后续你有做复合 HID 设备或者 HID DFU 的计划欢迎回来交流验证结果。

相关新闻