STC8H开发(九): STC8H8K64U模拟USB HID外设

目录

关于USB

通用串行总线(Universal Serial Bus, 简称USB), 是当前使用最广泛的外设接口. 因为供电简单, 支持热插拔, 扩展端口简单, 传输方式多样化, 兼容性好, 支持的外设类型丰富, 基本成为PC标配.
STC MCU从STC15开始支持USB ISP, 但是仅仅用于下载和调试, 可在代码中配置的USB外设功能直到STC8H8K64U才出现. USB标准规范包括USB1.0, USB1.1, USB2.0, USB3.0 以及2019年9月发布的 USB4TM, STC8H8K64U 支持的是 USB2.0 标准.

USB的物理连接结构

USB系统由一个USB Host, 一个或多个USB Hub, 一个或多个USB Node 组成. USB的物理连接是层次性的星型结构, 在系统中, 有且仅有一个USB主机, USB Hub实际上就是一个具有特殊功能的USB设备. 所有的USB设备都连接在USB Hub上, 同时Hub有责任为每个连接其上的USB Node 提供 +5V 和 500mA 的电源.

USB总线采用的是树形结构、主从工作模式. USB主机根据各个设备的属性, 周期性访问各个设备. 而USB设备则是被动响应USB主机的访问请求, 这样避免了USB设备主动发送数据导致的总线冲突. 当然, 这样就导致没有USB主机的情况下 USB 设备之间无法通讯. USB OTG(USB On-The-Go)技术用于解决此问题, 拓展了USB在嵌入式设备, 如手机、PDA等方面的应用.

USB系统的分层结构

在系统设计和逻辑连接关系上USB总线系统具有明确的分层结构. USB设备与主机的驱动/应用程序通信, 是通过特定的USB端点来实现的.

整个USB系统可以分为USB总线接口层, USB设备层, 功能层

  1. USB总线接口层
    USB总线接口层主要用于实现USB主机和USB设备之间的数据传输, 实际的数据流是在此层运行的. USB规范中 , USB总线接口NRZI编码(反向归零编码)来传输数据. 编码的过程是自动进行的, 由USB系统硬件完成.
  2. USB设备层
    USB设备层主要用于管理USB设备, 包括分配USB地址、读取设备描述报文等. 在这一层中, USB主机可以获取USB设备的各种属性或能力. 这部分的功能, USB主机通过驱动软件来完成, 而USB设备的固件层也需要编写对应的代码进行支持.
  3. 功能层
    功能层主要负责数据传输, 由USB设备的功能单元和对应的USB主机程序实现. 按照双方通信的类型, 可分为
    • 控制传输(Control Transfers). 主要用于传输少了的对时间和速率没有要求的数据, 一般用于USB主机读取或设置USB设备的配置信息, 或者其他的简单操作.
    • 中断传输(Interrupt Transfers). 主要用于传输少了对传输时间具有周期性要求的数据, 在鼠标、键盘等HID设备中经常使用.
    • 批量传输(Bulk Transfers). 用于传输大量的、对传输时间和速率没有严格要求的数据.
    • 实时传输(Isochronous Transfers). 用于传输大量的、且传输时间具有周期性、速率恒定的数据

在USB HID设备的传输中主要使用的是控制传输.

USB的命令(USB Device Request)

USB规范定义了设备请求(USB Device Request), 以更好地完成USB主机对总线上所有USB设备的统一控制. 此设备请求由USB主机发往USB设备. USB命令包括USB标准命令, 类命令, 厂商命令. 这些命令的格式都是相同的, 如下表所示.

偏移 字段 长度 数值 描述
0 bmRequesType 1 位图 D7: 数据的传输方向: 0=主机->设备, 1=设备->主机
D6-D5: 命令的类型, 0=标准命令 1=类命令 2=厂商命令 3=保留
D4-D0: 接收对象 0=设备 1=接口 2=端点 3=其他  4…31=保留
1 bRequest 1 命令的序号
2 wValue 2 根据不同的命令, 含义也不同
4 wIndex 2 索引或偏移 根据不同的命令, 含义不同, 主要用于传送索引或者偏移
6 wLength 2 如果有数据阶段, 此字段为数据的字节数

标准命令是每种USB设备都要支持的, 类命令则与USB设备所述类有关, 比如USB HID设备有HID类特有的命令, 如Get_Report、SetReport等.

USB标准命令

USB 1.1的规范中, 规定了11种USB标准命令, 用来完成各种目的. 根据不同的命令, 相应的字段含义也有所不同. 下表列出了11个USB标准命令的功能.

命令 请求号 功能描述
Get_Status 0x00 读取USB设备、接口或者端口的状态
Clear_Feature 0x01 清除或禁止USB设备、接口或端点的某些特征
Set_Feature 0x03 设置或使能USB设备、接口或端点的某些特征
Set_Address 0x05 分配USB设备地址
Get_Descriptor 0x06 读取描述报文
Set_Descriptor 0x07 更新已有的描述报文或添加新的描述报文
Get_Configuration 0x08 读取USB设备的当前配置值
Set_Configuration 0x09 为USB设备选择一个合适的配置值
Get_Interface 0x0A 获得设备接口当前工作的选择设置值
Set_Interface 0x0B 激活USB设备的某个接口
Synch_Frame 0x0C 设置并报告端点的同步帧号

在USB规范中, 对于这些标准的USB命令, 所有的USB设备必须都支持, 并能够对命令进行响应. 如果不需要对命令进行操作, 也必须准备一个空的响应. 除了这些标准的USB命令, 对于不同的类, 也有类相关的USB命令. 比如人机接口类设备有Set_Report、Get_Report等命令, 集线器类设备有GetHubStatus、GetBusState等命令. 在开发相应类设备的时候, 也需要熟悉这些类本身特有的USB命令.

下表是Get_Descriptor命令的结构

偏移 字段 内容
0 bmRequesType 值为10000000B, 设备到主机
1 bRequest GET_DESCRIPTOR, 0x06
2 wValue 描述报文的类型和描述报文的索引值
4 wIndex 0或语言标识(LANGID)
6 wLength 描述报文的长度
  • Get_Descriptor命令用来获取USB设备的各种描述报文, 包括设备描述报文、配置描述报文、接口描述报文、端点描述报文和字符串描述报文.
  • 需要获取的描述报文类型, 由wValue字段给出. wValue由两个字节组成, 高字节表示描述报文的类型, 低字节表示描述报文的索引值.
  • wIndex字段除去获取字符串描述报文之外, 其他情况下设置为0. 获得字符串描述报文的过程分为两步: 第一次发送命令后获得语言标识;第二次发送命令时, 将语言标识赋给wIndex字段, 需要获取的字符串描述报文的索引值赋给wValue字段, 即可获得所需要的字符串.
  • Get_Descriptor命令中的字段wLength, 表示描述报文的字节长度, 由USB主机指定. 当指定wLength比实际的描述报文长度小时, USB设备严格按照主机指定的字节长度返回描述报文信息;当wLength比实际的描述报文长度大时, USB设备值返回描述报文长度的信息. 比如在访问配置描述报文时, USB主机不清楚配置信息的总长, 可以将Get_Descriptor命令中的wLength设置为4, 得到配置描述报文中wTotalLength. 然后, 重新发送Get_Descriptor命令, 此时将wLength设置为wTotalLength的值, 从而获得整个配置描述报文信息.

USB HID的类命令

HID设备除了支持标准的USB命令外, 还支持6个HID特定的类命令, 如下表所示.

命令 请求号 功能描述
Get_Report 0x01 USB主机接收HID设备发来的报告
Get_Idle 0x02 用于读取HID设备当前空闲速率
Get_Protocol 0x03 用于读取HID设备的协议值
Set_Report 0x09 USB主机向HID设备发送报告
Set_Idle 0x0A 用于设置HID设备的空闲速率
Set_Protocol 0x0B 用于设置HID设备的协议值

HID的类命令, 其数据结构与USB的标准命令类似, 而且也是采用控制传输发送的. HID示例中主要用到了 Get_Report 和 Set_Report 两个命令

Get_Report命令

用于获取HID设备发送来的报告, 它主要在HID设备初始化和读取HID报告时使用. 此命令是所有HID设备都必须支持的, 其结构如下所示.

偏移 字段 内容
0 bmRequesType 值为10100001B, 设备到主机
1 bRequest GET_REPORT, 0x01
2 wValue 报告类型及报告ID
4 wIndex 用于指明支持此命令的接口号码
6 wLength 报告长度

其中, wValue用来指明报告的类型. 它由两个字节组成, 低字节表示报告ID. 高字节值为1时, 表示Input报告;值为2时, 表示Output报告;值为3时, 表示Feature报告.

Set_Report命令

用于USB主机向HID设备发送报告数据, 它与Get_Report命令类似, 只是数据传输的方向不同. Set_Report命令并不是所有HID设备都必须支持的, 其结构如下所示.

偏移 字段 内容
0 bmRequesType 值为00100001B, 主机到设备
1 bRequest SET_REPORT, 0x09
2 wValue 报告类型及报告ID
4 wIndex 用于指明支持此命令的接口号码
6 wLength 报告长度

USB 的描述报文

USB 设备响应命令的内容就是描述报文, 为了方便USB主机对USB设备进行管理, USB-IF对USB设备的功能采用了分层结构, 包括设备层、配置层、接口层和端点层. 这四层的作用分别为

  1. 设备层. 说明USB设备的主要类型特征(如设备类别、接口、端点等属性), 保障设备枚举过程的正常进行
  2. 配置层. 选择不同的失败配置满足USB主机对设备功能的选择, 可选择复式的设备接口功能, 如图1展示的选择鼠标、键盘和游戏杆的复合功能
  3. 接口层. 将具体功能分类, 不同的功能对用不同的操作方式
  4. 端点层. 针对特定的设备功能, 选择不同的端点, 提供不同的数据管道, 与USB主机进行数据通讯

为了描述USB设备的这些特征, USB规范定义了相应结构的描述报文, 包括设备描述报文、配置描述报文、接口描述报文、端点描述报文和字符串描述报文等. 下表给出了USB1.1下各种USB描述报文的类型值.

类型 描述报文 描述报文值
标准描述报文 设备描述报文(Device Descriptor) 0x01
配置描述报文(Configuration Descriptor) 0x02
字符串描述报文(String Descriptor) 0x03
接口描述报文(Interface Descriptor) 0x04
端点描述报文(Endpoint Descriptor) 0x05
类描述报文 集线器类描述报文(hub descriptor) 0x29
人机接口类描述报文(HID) 0x21
厂商自定义 0xFF

其他版本的USB规范, 还定义了其他类型的描述报文, 比如USB 2.0中的设备限定描述报文(Device_Qualifier), USB 3.2中的二进制设备对象存储描述报文(Binary Device Object Store, 简称BOS)等. 这些内容在博客中不会涉及, 可在USB-IF的官网下载相关的USB规范文档了解.

USB主机会发送USB命令给USB设备, GET_DESCRIPTOR 是常用的获取描述报文命令. USB设备根据命令所要求的, 给出对应的描述报文. 本篇主要讲述描述报文的结构.

不管哪种USB设备, 都必须提供标准描述报文, 用于告知主机设备本身的属性. 以下介绍的内容, 可以使用Bus Hound或USB逻辑分析仪等工具, 去抓取USB设备的识别和通信包.

设备描述报文

USB的设备描述报文用于表示USB设备的一般信息, 如制造商ID、产品序列号等. 一个USB设备有且只有一个设备描述报文, 它是USB主机所读取的第一个描述报文, 其结构如下所示.

偏移 大小 描述
0 bLength 1 数字 描述报文字节数长度(0x12)
1 bDescriptor 1 常量 描述报文的类型(0x01)
2 bcdUSB 2 BCD码 USB设备支持的协议版本号
4 bDeviceClass 1 设备类代码
5 bDeviceSubClass 1 子类 子类代码, 根据bDeviceClass来定
6 bDevicePortocol 1 协议 协议码
7 bMaxPacketSize0 1 数字 端点0的最大包长度
8 idVendor 2 ID 厂商ID(由USB-IF赋值)
10 idProduct 2 ID 产品ID(由厂商赋值)
12 bcdDevice 2 BCD码 设备发行号(BCD码)
14 iManufacturer 1 索引 厂商信息的字符串描述报文索引值
15 iProduct 1 索引 产品信息的字符串描述报文索引值
16 iSerialNumber 1 索引 设备序列号信息的字符串描述报文索引值
17 bNumConfigurations 1 数字 配置描述报文数目

设备描述报文结构中的

  • bMaxPacketSize0, 它用来告知USB主机设备所支持的最大数据长度.
  • bDeviceClass表示设备所述的类别, 如果此值为0, 则表示每一个配置中的每个接口来指明它所属的类别(即在接口描述报文中给出设备类), 并且各接口独立工作. 如果此值为0xFF, 则由供应商自定义该设备类. 介于两者之间的值0x1-0xFE, 表示USB规范中定义的某个设备类, 比如0x03表示HID设备类. 它和bDeviceSubClass、bDeviceProtocol共同规定了设备的类别和采用的协议, 更具体的分类定义, 可以参考USB-IF官网.
  • iManufacturer、iProduct和iSerialNumber, 使用字符串描述报文的索引值来进行描述, 索引值为0表示没有字符串描述报文对其进行描述. 通过此索引值和USB命令Get_Descriptor, 可以得到对应的字符串描述报文.

配置描述报文

USB规范中, USB设备可以有一个或者多个配置描述报文, 每个配置描述报文提供了设备特定的配置. 在设备描述报文中的bNumConfigurations提供了配置描述报文的个数, 任何时刻只有一种配置处于工作状态.

配置描述报文中提供了在该配置下设备的接口数目, 一个设备的不同配置描述报文可能包含不同数目和特性的设备接口. 图1中的设备配置1, 就包含了鼠标功能接口和键盘功能接口两种接口. 此外, 配置描述报文中还会描述设备的供电方式(自供电/总线供电)、最大耗电量等信息. 其结构如下表所示.

偏移 大小 描述
0 bLength 1 数字 描述报文的字节数长度(0x09)
1 bDescriptorType 1 常量 配置描述报文的类型(0x02)
2 wTotalLength 2 BCD码 配置信息的总长(包括配置、接口、端点和设备类及厂商定义的描述报文)
4 BnumInterfaces 1 该设备所支持的接口数目
5 bConfigurationValue 1 子类 配置值
6 iConfiguration 1 协议 描述该配置的字符串描述报文索引值
7 bmAttributes 1 数字 配置特性: D7:保留 D6:自供电 D5:远程唤醒 D4…0:保留
8 MaxPower 1 数字 该配置下所需最大总线电流(2mA为单位)

接口描述报文

USB设备的接口是端点的集合, 负责完成该设备的特定功能, 比如数据的输入和输出. 接口描述报文用来表示在USB设备中, 各个接口的特性, 包括接口的端点个数、所述的设备类和子类等.

拥有多个接口的USB设备, 如果设备描述报文中描述的bDeviceClass不为0, 则表示接口之间是互斥关系, 否则接口相互独立, 每个接口有自己的类号、子类号和协议号. 类号、子类号和协议号的定义, 与设备描述报文中的定义是一致的. 接口描述报文的结构如下表所示.

偏移 大小 描述
0 bLength 1 数字 描述报文的字节数长度(0x09)
1 bDescriptorType 1 常量 接口描述报文的类型(0x04)
2 bInterfaceNumber 1 数字 接口号(从0开始)
3 bAlternateSetting 1 数字 可选设置的索引值
4 bNumEndpoint 1 数字 此接口的端点数量(不计默认端点0)
5 bInterfaceClass 1 接口所属的类值
6 bInterfaceSubClass 1 子类 接口所属子类的值
7 bInterfaceProtocol 1 协议 协议码, 根据上面的两个值而定
8 iInterface 1 索引 表示此接口的字符串描述报文的索引值

端点描述报文

USB规范中, 端点描述报文用于指出USB设备端点的特性, 包括其所支持的传输类型、传输方向等. 端点0没有端点描述报文, 其他端点必须包含端点描述报文.

端点是设备与主机之间进行数据传输的逻辑接口, 除配置使用的端点0为双向外, 其他均为单向. 端点描述报文描述了数据的传输类型、传输方向、数据包大小, 以及端点地址, 其结构如下表所示.

偏移 大小 描述
0 bLength 1 数字 描述报文的字节数长度(0x07)
1 bDescriptorType 1 常量 端点描述报文的类型(0x05)
2 bEndPointAddress 1 端点 描述了端点的地址、方向 Bit3…0: 端点号 Bit6…4: 保留, 为0 Bit7: 传输方向, 如果是控制端点则忽略 0: 输出端点(主机到设备) 1: 输入端点(设备到主机)
3 bmAttributes 1 位图 端点传输类型 Bit1…0: 传送类型 00B=控制传送 01B=实时传送 10B=批量传送 11B=中断传送
4 wMaxPacketSize 2 数字 接收/发送的最大数据包长度
6 bInterval 1 数字 周期数据传输端点的时间间隙

字符串描述报文

字符串描述报文是可选的, 它描述了制造商、设备名称或序列号等信息. 它使用的是Unicode编码, 并支持多语言. USB主机要求获得字符串描述报文时, 需要用一个16位的语言标识出语言类别. 比如, 常用的语言标识1033表示美国英语, 而2052表示中文. 其他的语言标识, 可以在微软的网站上找到 .

USB主机请求得到某个字符串描述报文时分为两步, 首先向USB设备发送USB命令 Get_Descriptor, 命令的wIndex字段设置为0, 设备将返回描述语言标识的字符串描述报文, 然后, USB主机根据需要的语言, 向USB设备发送命令 Get_Descriptor, 在命令对应的字段中设置字符串的索引值和语言标识, 得到需要的字符串描述报文. 字符串描述报文有两种格式

第一种用来指明所用的语言标识, 如下表所示.

偏移 大小 描述
0 bLength 1 N+2 描述报文的字节数长度(N+2字节)
1 bDescriptorType 1 常量 字符串描述报文的类型(0x03)
2 wLANGID[0] 2 数字 语言标识(LANGID), 码0
N wLANGID[x] 2 数字 语言标识(LANGID), 码x

第二种为Unicode字符串描述报文, 包含了非NULL结尾的Unicode字符串, 如下表所示.

偏移 大小 描述
0 bLength 1 N+2 描述报文的字节数长度(N+2字节)
1 bDescriptorType 1 常量 字符串描述报文的类型(0x03)
2 bString N 数字 Unicode编码的字符串

在实际编写代码中, 接口描述报文、端点描述报文一般是和配置描述报文在同一数组中的


HID设备的描述报文

示例的USB设备是HID类设备. HID(Human Interface Devices)为人机接口设备, 日常使用的键盘、鼠标等, 都属于HID设备.

USB规范中, HID类设备的规范为Device Definiton for Human Interface Devices, 每个HID设备都必须符合该规范中对描述报文、传输类型等的定义. 另外, USB-IF还提供了HID的用例规范, 名为HID Usage Tables, 定义了HID设备和USB主机之间通信的HID数据. 所有的 HID 传输都是使用默认控制管道或是一个中断管道, HID 设备必须有一个中断输入端点来传送数据到主机, 中断输出端点则不是必需的.

HID设备的标准描述报文同样遵循上一篇所介绍的内容, 也即拥有各种标准描述报文. 对于HID设备, 其设备描述报文的类代码、子类代码和协议代码都需要设置为0;接口描述报文的类代码设为0x03, 子类代码为0或者0x01, 协议码为0、0x01或0x02.

HID设备支持3种类描述报文: HID描述报文、报告描述报文和物理描述报文. 一个USB设备只能包含一个HID描述报文, 可以支持多个报告描述报文, 而物理描述报文是可选的. 在示例中, 只用到了HID描述报文和报告描述报文.

HID描述报文

HID描述报文主要用来识别HID设备通信时所使用的额外信息(如HID版本号、报告描述报文长度等), 如下表所示, 给出了HID描述报文的定义结构.

偏移 大小 描述
0 bLength 1 数字 描述报文的长度
1 bDescriptorType 1 常量 描述报文的类型, 0x21
2 bcdHID 2 BCD码 HID规范版本号(BCD码)
4 bCountryCode 1 数字 国家代码
5 bNumDescriptor 1 数字 支持的其他类描述报文的数量
6 bDescriptorType 1 常量 类别描述报文的类型
7 wDescriptorLength 2 数字 报表描述报文的总长度
9 bDescriptorType 1 常量 识别描述报文类型的常数, 多个描述报文时使用
10 wDescriptorLength 2 数字 描述报文总长度, 多个描述报文时使用

其中

  • bCountryCode 表示的是国家代码, 一般将其设置为0就可以了. 0表示的是HID设备不是本土化的, 其他的值可以查看HID规范.
  • bDescriptorType 识别HID描述报文附属的描述报文的类型, 报表描述报文的类型为0x22, 物理描述报文的类型为0x23. 每一个 HID设备都必须至少支持一个报表描述报文. 一个接口可以支持多个报表描述报文, 以及一个或多个实体描述报文.
  • HID描述报文的偏移量为9和10的bDescriptorType和wDescriptorLength可以重复存在多个

在USB主机发送标准命令 Get_ Configuration, 获取配置描述报文的时候, 将按照配置描述报文、接口描述报文、HID描述报文、端点描述报文的顺序, 返回数据. 也即主机此时可用获取到HID描述报文, 可以据其信息再去获取相关的描述报文(如报告描述报文).

报告描述报文

HID设备的报告描述报文是一种数据报表, 主要用来定义HID设备和USB主机之间的数据交换格式. 与标准描述报文不同, 它没有固定的长度, 根据设备不同的用途, 需要准备不同的结构和数据描述.

报告描述报文由多个不同的项目(item)组成, 它有两种编码: 短项目(short item)和长项目(long item). 长项目保留给未来使用, 本节不对它进行介绍. HID短项目的字节格式如下所示.

23       16|15      8|7    4|3     2|1     0|
| -data  - | -data --| bTag | bType | bSize |
  • bSize用来指出项目所需要的数据字节(即data部分的字节长度), 其值可为0(bSize=0)、1(bSize=1)、2(bSize=2)和4(bSize=3). 注意没有3个字节长的数据, 大部分项目只需要1个字节数据.

  • bType表示项目的类型, 用来表示是主(Main)项目、全局(Global)还是局部(Local)项目. 主项目用来定义报告中数据的种类和格式;局部项目表示所定义只能适用于其下的第一个主项目, 不能扩展到其他主项目;全局项目则适用于其下方所有主项目, 除非被另一个相同标签的全局项目替代. bType=0时, 表示项目为主项目;bType=1时, 项目为全局项目;bType=2时, 项目为局部项目.

  • bTag为标签, 用来标记各项目的作用.

  • data部分是可选的, 其长度由bSize决定. 比如集合结束(End Collection)项目, 其值一般为0xC0, 后面不带有任何数据.

主项目中的Input、Output和Feature用来定义报告中的数据通讯字段, 每个项目标签之后是用来描述特性的32位数, 其中前9位有不同的含义, 后23位被保留. 当然, HID项目中的data部分, 不一定是32位数, 其长度仍旧由bSize值决定. 这三个项目的特性描述, 如下表所示.

偏移 描述
0 0=数据, 表示项目内容可改;1=常数, 项目内容不可改
1 0=数组, 表示项目描述每个控制的状态;1=变量, 项目只报告作用中的控制
2 0=绝对, 表示数值以固定值为基准;1=相对, 当前数值以上一数值为基准
3 0=没有折行, 数据不作折返处理;1=有折行, 遇到最大最小界限时折返
4 0=线性, 测量与报告数据为线性关系;1=非线性, 测量与报告数据非线性关系
5 0=优选状态, 无交互时, 回到特定状态;1=非优选状态
6 0=没有空位置;1=空状态
7 0=非挥发;1=挥发, 设备可以自己改变数值. 此位对Input无效
8 0=位字段, 表示每一位或每一字节内的群组位可代表一份数据;1=缓冲字节, 表示信息包含一个或者多个字节, 缓冲字节的报告大小必须为8
9-23 保留

表2中的Usage Page和Usage是最复杂的项目, 为此USB-IF专门为它们准备了参考文档. Usage Page是全局项目, 用来定义数据的用法或功能;Usage是局部项目, 用来描述项目或集合的用途.

Usage Page一般和Usage, 或者和Usage Minimum、Usage Maximum共同决定设备的用途. 如示例1所示, 这是一个常见的Usage Page和Usage的定义项目.

USB示例

示例说明

在demo中提供了两个HID示例, 一个是响应输入返回ADC采样结果, 另一个是模拟键盘输出按键状态.

代码的工作流程都是相似的, 初始化USB, 初始化通道(Endpoint), 在USB的中断响应中, 判断类型和通道分别处理. 在初始化阶段根据不同的命令返回对应的描述报文, 在输入输出阶段响应对应的数据.

USB配置步骤

准备以下的描述报文

__CODE uint8_t  DEVICEDESC[18]; // 设备描述
__CODE uint8_t  CONFIGDESC[41]; // 配置描述
__CODE uint8_t  HIDREPORTDESC[27]; // HID报告
__CODE uint8_t  LANGIDDESC[4];     // 语言描述
__CODE uint8_t  MANUFACTDESC[8];   // 制造商描述
__CODE uint8_t  PRODUCTDESC[30];   // 产品描述

设置GIPIO

GPIO_P3_SetMode(GPIO_Pin_0|GPIO_Pin_1, GPIO_Mode_Input_HIP);

初始化USB

void USB_Init()
{
    SYS_EnableOscillator48M();               // USB 48MHz晶振
    USB_SetClockSource(USB_ClockSource_6M);  
    USB_SetEnabled(HAL_State_ON);            // 开启USB
    USB_SetDpDmPullUp(HAL_State_ON);         // 设置D-/D+上拉
    EXTI_USB_SetIntPriority(EXTI_IntPriority_Highest); // 设置USB中断优先级

    USB_WriteReg(FADDR, 0x00);
    USB_WriteReg(POWER, 0x08);
    USB_WriteReg(INTRIN1E, 0x3f);
    USB_WriteReg(INTROUT1E, 0x3f);
    USB_WriteReg(INTRUSBE, 0x00);
    USB_WriteReg(POWER, 0x01);
    Ep0Stage.bStage = USB_CtrlState_Idle;

    EXTI_USB_SetIntState(HAL_State_ON);     // 开启全局中断
}

USB中断处理

INTERRUPT(USB_Routine, EXTI_VectUSB)
{
    uint8_t intrusb;
    uint8_t intrin;
    uint8_t introut;
    uint8_t csr;
    uint8_t cnt;
    uint16_t len;
    intrusb = USB_ReadReg(INTRUSB);
    intrin = USB_ReadReg(INTRIN1);
    introut = USB_ReadReg(INTROUT1);

    /**
     * 中断类型:重置
     */
    if (intrusb & RSTIF)
    {
        USB_SelectEndPoint(1);
        USB_WriteReg(INCSR1, INCLRDT);
        USB_SelectEndPoint(1);
        USB_WriteReg(OUTCSR1, OUTCLRDT);
        Ep0Stage.bStage = USB_CtrlState_Idle;
    }

    /**
     * 中断类型: Endpoint-0
     */
    if (intrin & EP0IF)
    {
        USB_SelectEndPoint(0);
        csr = USB_ReadReg(CSR0);
        if (csr & STSTL) // Sent Stall
        {
            USB_WriteReg(CSR0, csr & ~STSTL);
            Ep0Stage.bStage = USB_CtrlState_Idle;
        }

        if (csr & SUEND) // Setup End
        {
            USB_WriteReg(CSR0, csr | SSUEND);
        }

        // 这里根据不同的 stage, 分别处理
        switch (Ep0Stage.bStage)
        {
            case USB_CtrlState_Idle:
                //...略
                break;

            case USB_CtrlState_DataIn:
                //...略
                break;

            case USB_CtrlState_DataOut:
                //...略
                break;
        }
    }

    /**
     * 中断类型: Endpoint-1 In
     */
    if (intrin & EP1INIF)
    {
        USB_SelectEndPoint(1);
        csr = USB_ReadReg(INCSR1);
        if (csr & INSTSTL)
        {
            USB_WriteReg(INCSR1, INCLRDT);
        }
        if (csr & INUNDRUN)
        {
            USB_WriteReg(INCSR1, 0);
        }
    }

    /**
     * 中断类型: Endpoint-1 Out
     */
    if (introut & EP1OUTIF)
    {
        USB_SelectEndPoint(1);
        csr = USB_ReadReg(OUTCSR1);
        if (csr & OUTSTSTL)
        {
            USB_WriteReg(OUTCSR1, OUTCLRDT);
        }
        if (csr & OUTOPRDY)
        {
            USB_ReadFIFO(FIFO1, HidInput);
            USB_WriteReg(OUTCSR1, 0);
            /** Write response */
            if((HidInput[0] == 0xaa) && (HidInput[1] == 0x55) && (HidInput[2] == 0x01))
            {
                USB_SelectEndPoint(1);
                USB_WriteFIFO(FIFO1, HidOutput, 64);
                USB_WriteReg(INCSR1, INIPRDY);
            }
        }
    }
}

示例代码

接线说明

与PC的连线

转接板   STC8H8K64U
3.3V    -> VCC
D-      -> P3.0
D+      -> P3.1
GND     -> GND

转接板仅将5V转为3.3V, 可以参考前面的文章说明

posted on 2022-02-09 15:52  Milton  阅读(3362)  评论(0编辑  收藏  举报

导航