找回密码
 立即注册
查看: 100|回复: 8

我将数据手册的HID协议注释了一遍,供大家参考

[复制链接]
  • 打卡等级:以坛为家III
  • 打卡总天数:817
  • 最近打卡:2026-07-22 05:36:35

32

主题

182

回帖

3468

积分

论坛元老

积分
3468
发表于 2026-7-7 16:20:04 | 显示全部楼层 |阅读模式
  1. <blockquote>// 引入头文件
复制代码
同时也希望大家多指导指导

STC_HID.c

31.8 KB, 下载次数: 13

回复

使用道具 举报 送花

  • 打卡等级:以坛为家III
  • 打卡总天数:817
  • 最近打卡:2026-07-22 05:36:35

32

主题

182

回帖

3468

积分

论坛元老

积分
3468
发表于 2026-7-7 16:46:05 | 显示全部楼层
  1. // 引入头文件
  2. // #include <STC8H.H>
  3. #include <STC8051U.H>
  4. #include "intrins.h"
  5. // 定义数据格式
  6. typedef unsigned char   BYTE;        // 单字节
  7. typedef unsigned int    WORD;        // 2字节
  8. typedef unsigned long   DWORD;        // 4字节
  9. // 定义USB的SIE的寄存器
  10. #define FADDR       0                        // USB 功能地址寄存器
  11. #define POWER       1           // USB 电源管理寄存器
  12. #define INTRIN1     2           // USB 端点 IN 中断标志位
  13. #define EP5INIF     0x20        // INTRIN1.5:端点 5 的 IN 中断标志位
  14. #define EP4INIF     0x10        // INTRIN1.4:端点 4 的 IN 中断标志位
  15. #define EP3INIF     0x08        // INTRIN1.3:端点 3 的 IN 中断标志位
  16. #define EP2INIF     0x04        // INTRIN1.2:端点 2 的 IN 中断标志位
  17. #define EP1INIF     0x02        // INTRIN1.1:端点 1 的 IN 中断标志位
  18. #define EP0IF       0x01        // INTRIN1.0:端点 0 的 IN/OUT 中断标志位
  19. #define INTROUT1    4           // USB 端点 OUT 中断标志位
  20. #define EP5OUTIF    0x20        // INTROUT1.5:端点 5 的 OUT 中断标志位
  21. #define EP4OUTIF    0x10        // INTROUT1.4:端点 4 的 OUT 中断标志位
  22. #define EP3OUTIF    0x08        // INTROUT1.3:端点 3 的 OUT 中断标志位
  23. #define EP2OUTIF    0x04        // INTROUT1.2:端点 2 的 OUT 中断标志位
  24. #define EP1OUTIF    0x02        // INTROUT1.1:端点 1 的 OUT 中断标志位
  25. #define INTRUSB     6           // USB 电源中断标志位
  26. #define SOFIF       0x08        // INTRUSB.3:USB 帧起始信号中断标志
  27. #define RSTIF       0x04        // INTRUSB.2:USB 复位信号中断标志
  28. #define RSUIF       0x02        // INTRUSB.1:USB 恢复信号中断标志
  29. #define SUSIF       0x01        // INTRUSB.0:USB 挂起信号中断标志
  30. #define INTRIN1E    7           // USB 端点 IN 中断允许寄存器
  31. #define EP5INIE     0x20        // INTRIN1E.5:端点 5 的 IN 中断控制位
  32. #define EP4INIE     0x10        // INTRIN1E.4:端点 4 的 IN 中断控制位
  33. #define EP3INIE     0x08        // INTRIN1E.3:端点 3 的 IN 中断控制位
  34. #define EP2INIE     0x04        // INTRIN1E.2:端点 2 的 IN 中断控制位
  35. #define EP1INIE     0x02        // INTRIN1E.1:端点 1 的 IN 中断控制位
  36. #define EP0IE       0x01        // INTRIN1E.0:端点 0 的 IN/OUT 中断控制位
  37. #define INTROUT1E   9           // USB 端点 OUT 中断允许寄存器
  38. #define EP5OUTIE    0x20        // INTROUT1E.5:端点 5 的 OUT 中断控制位
  39. #define EP4OUTIE    0x10        // INTROUT1E.4:端点 4 的 OUT 中断控制位
  40. #define EP3OUTIE    0x08        // INTROUT1E.3:端点 3 的 OUT 中断控制位
  41. #define EP2OUTIE    0x04        // INTROUT1E.2:端点 2 的 OUT 中断控制位
  42. #define EP1OUTIE    0x02        // INTROUT1E.1:端点 1 的 OUT 中断控制位
  43. #define INTRUSBE    11          // USB 电源中断允许寄存器
  44. #define SOFIE       0x08        // INTRUSBE.3:USB 帧起始信号中断控制位
  45. #define RSTIE       0x04        // INTRUSBE.2:USB 复位信号中断控制位
  46. #define RSUIE       0x02        // INTRUSBE.1:USB 恢复信号中断控制位
  47. #define SUSIE       0x01        // INTRUSBE.0:USB 挂起信号中断控制位
  48. #define FRAME1      12          // USB 数据帧号寄存器[7:0]
  49. #define FRAME2      13          // USB 数据帧号寄存器[10:8]
  50. #define INDEX       14          // USB 端点索引寄存器
  51. #define INMAXP      16          // IN 端点的最大数据包大小
  52. #define CSR0        17          // USB 端点 0 控制状态寄存器
  53. #define SSUEND      0x80                // CSR0.7:SETUP 结束事件处理完成标志
  54. #define SOPRDY      0x40                // CSR0.6:OPRDY 事件处理完成标志
  55. #define SDSTL       0x20                // CSR0.5:STALL 握手包回传
  56. #define SUEND       0x10                // CSR0.4:SETUP 安装包结束标志
  57. #define DATEND      0x08                // CSR0.3:数据发送/接收完成标志位
  58. #define STSTL       0x04                // CSR0.2:STALL 信号发送完成标志
  59. #define IPRDY       0x02                // CSR0.1:IN 数据包准备完成标志
  60. #define OPRDY       0x01                // CSR0.0:OUT 数据包准备完成标志
  61. #define INCSR1      17                        // IN 端点控制状态寄存器 1
  62. #define INCLRDT     0x40                // INCSR1.6:复位 IN 数据切换位
  63. #define INSTSTL     0x20                // INCSR1.5:STALL 信号发送完成标志
  64. #define INSDSTL     0x10                // INCSR1.4:STALL 信号发送请求位
  65. #define INFLUSH     0x08                // INCSR1.3:清除 IN 端点的 FIFO 的下一个数据包
  66. #define INUNDRUN    0x04                // INCSR1.2:数据不足
  67. #define INFIFONE    0x02                // INCSR1.1:IN 端点的 FIFO 非空标志
  68. #define INIPRDY     0x01                // INCSR1.0:IN 数据包准备完成标志
  69. #define INCSR2      18                        // IN 端点控制状态寄存器 2
  70. #define INAUTOSET   0x80                // INCSR2.7:自动设置 IPRDY 标志控制位
  71. #define INISO       0x40                // INCSR2.6:同步传输使能
  72. #define INMODE          0x20                // INCSR2.5:端点方向选择位
  73. #define INMODEIN          0x20                // INCSR2.5.1:选择端点方向为IN
  74. #define INMODEOUT          0x00                // INCSR2.5.0:选择端点方向为OUT
  75. #define INENDMA     0x10                // INCSR2.4:IN 端点的 DMA 控制,无效的
  76. #define INFCDT      0x08                // INCSR2.3:强制 DATA0/DATA1 数据切换设置
  77. #define OUTMAXP     19                        // OUT 端点的最大数据包大小
  78. #define OUTCSR1     20                        // OUT 端点控制状态寄存器 1
  79. #define OUTCLRDT    0x80                // OUTCSR1.7:复位 OUT 数据切换位
  80. #define OUTSTSTL    0x40                // OUTCSR1.6:STALL 信号发送完成标志
  81. #define OUTSDSTL    0x20                // OUTCSR1.5:STALL 信号发送请求位
  82. #define OUTFLUSH    0x10                // OUTCSR1.4:清除 OUT 端点的 FIFO 的下一个数据包
  83. #define OUTDATERR   0x08                // OUTCSR1.3:数据错误,该位只在ISO方式有效
  84. #define OUTOVRRUN   0x04                // OUTCSR1.2:数据溢出,该位只在ISO方式有效
  85. #define OUTFIFOFUL  0x02                // OUTCSR1.1:OUT 端点的 FIFO 数据满标志
  86. #define OUTOPRDY    0x01                // OUTCSR1.0:OUT 数据包接收完成标志
  87. #define OUTCSR2     21
  88. #define OUTAUTOCLR  0x80                // OUTCSR1.7:自动清除OPRDY标志控制位
  89. #define OUTISO      0x40                // OUTCSR1.6:同步传输使能
  90. #define OUTENDMA    0x20                // OUTCSR1.5:OUT端点的DMA控制,无效的
  91. #define OUTDMAMD    0x10                // OUTCSR1.4:设置OUT端点的DMA模式,无效的
  92. #define COUNT0             22                        // USB 端点 0 的 OUT 长度
  93. #define OUTCOUNT1   22                        // USB 端点的 OUT 长度[7:0]
  94. #define OUTCOUNT2   23                        // USB 端点的 OUT 长度[10:8]
  95. #define FIFO0       32                        // USB 端点 0 的 FIFO 数据访问寄存器
  96. #define FIFO1       33                        // USB 端点 1 的 FIFO 数据访问寄存器
  97. #define FIFO2       34                        // USB 端点 2 的 FIFO 数据访问寄存器
  98. #define FIFO3       35                        // USB 端点 3 的 FIFO 数据访问寄存器
  99. #define FIFO4       36                        // USB 端点 4 的 FIFO 数据访问寄存器
  100. #define FIFO5       37                        // USB 端点 5 的 FIFO 数据访问寄存器
  101. #define UTRKCTL     48                        // USB 跟踪控制寄存器,用不到的,适用于 USB 底层驱动调试
  102. #define UTRKSTS     49                        // USB 跟踪状态寄存器,用不到的,适用于 USB 底层驱动调试
  103. // 状态标志位定义
  104. #define EPIDLE              0                // 等待新指令
  105. #define EPSTATUS            1                // 解析新指令
  106. #define EPDATAIN            2                // 正在发数据给主机
  107. #define EPDATAOUT           3                // 正在从主机收数据
  108. #define EPSTALL             -1                // 出错,端点被挂起
  109. // 枚举阶段的各种请求
  110. // 标准请求码(bRequest)
  111. #define GET_STATUS          0x00        // 主机查询设备、接口或端点的状态
  112. #define CLEAR_FEATURE       0x01        // 清除端点挂起的命令
  113. #define SET_FEATURE         0x03        // 让某个端点进入挂起状态
  114. #define SET_ADDRESS         0x05        // 给设备分配地址
  115. #define GET_DESCRIPTOR      0x06        // 主机向设备请求描述符的
  116. #define SET_DESCRIPTOR      0x07        // 主机请求设备修改某描述符,这操作很罕见,一般用不上
  117. #define GET_CONFIG          0x08        // 每个接口是可以有多个备用设置的,比如我设置的同样接口下的鼠标,可以有两个备用设置,设置一是低功耗,设置二是电竞风格,不过大部分使用场景都仅仅只有一套设置的
  118. #define SET_CONFIG          0x09        // 启动端点,代表着枚举阶段已经过了
  119. #define GET_INTERFACE       0x0A        // 查询接口当前使用的是第几个备用设置
  120. #define SET_INTERFACE       0x0B        // 主机命令某个接口切换到指定的备用设置
  121. #define SYNCH_FRAME         0x0C        // 用于等时传输(又称:同步传输)的,获取等时端点的同步帧编号,约定从第几帧开始传数据
  122. // HID 类请求码,基于 bRequest 字节衍生出来的,可以不要,在请求发来的时候再解析
  123. #define GET_REPORT          0x01        // 强行通过端点 0 索要HID类设备(鼠标、键盘、手柄等)的特性报告,可以不理的,就回复STALL
  124. #define GET_IDLE            0x02        // 和描述符里的轮询不一样,这个是主机问设备,如果数据没变化,设备最多可以偷懒多久才复报一次,一般是长按的场景
  125. #define GET_PROTOCOL        0x03        // 获取HID设备的报告上传使用的是什么协议,0:引导协议,电脑的BIOS阶段就可以识别HID设备上传的报告,1:报告协议,就是有携带自定义数据的,这个情况下,有上位机的话,可以让HID设备玩的更花,比如电竞设备啥的
  126. #define SET_REPORT          0x09        // 以键盘为例,在枚举阶段就先设置键盘的大小写指示灯、数字键盘指示灯等的开启与否状态
  127. #define SET_IDLE            0x0A        // 设置空闲率,和GET_IDLE类似,主机告诉设备,如果长期没有按键按下,你希望多久才来上报一次
  128. #define SET_PROTOCOL        0x0B        // 设置协议模式(报告协议或引导协议),一般是先有GET_PROTOCOL,然后主机为了保险起见,可能会发个这个包告诉设备,就使用哪种协议
  129. // 描述符类型码,基于Get_Descriptor 请求时,通过 wValue 的高字节指明要哪一种描述符
  130. #define DESC_DEVICE         0x01        // 请求设备描述符
  131. #define DESC_CONFIG         0x02        // 请求配置描述符
  132. #define DESC_STRING         0x03        // 请求字符串描述符
  133. #define DESC_HIDREPORT      0x22        // 请求HID报告描述符
  134. // 请求类型掩码(bmRequestType)
  135. #define STANDARD_REQUEST    0x00        // 标准请求标志
  136. #define CLASS_REQUEST       0x20        // 类请求标志
  137. #define VENDOR_REQUEST      0x40        // 厂商请求标志
  138. #define REQUEST_MASK        0x60        // 与bmRequestType字节做按位与运算,获得是标准请求、类请求或厂商请求的情况
  139. // SETUP令牌包后携带的的DATA0数据包的8个字节数据的结构体
  140. typedef struct
  141. {
  142.     BYTE    bmRequestType;
  143.     BYTE    bRequest;
  144.     BYTE    wValueL;
  145.     BYTE    wValueH;
  146.     BYTE    wIndexL;
  147.     BYTE    wIndexH;
  148.     BYTE    wLengthL;
  149.     BYTE    wLengthH;
  150. }SETUP;
  151. // 管理端点 0 控制传输状态的结构体
  152. typedef struct
  153. {
  154.     BYTE    bStage;                                        // 当前处于哪个阶段
  155.     WORD    wResidue;                                // 还剩多少字节没传完,目前设备的端点基本都是 64 字节的,比如配置描述符,可能就会大于 64 字节,就需要设置标志位,分批往 FIFO0 传入数据
  156.     BYTE    *pData;                                        // 当前要传输的数据指针,每发完一个包,pData 指针向后移动
  157. }EP0STAGE;
  158. // 函数声明
  159. void UsbInit();
  160. BYTE ReadReg(BYTE addr);
  161. void WriteReg(BYTE addr, BYTE dat);
  162. BYTE ReadFifo(BYTE fifo, BYTE *pdat);
  163. void WriteFifo(BYTE fifo, BYTE *pdat, BYTE cnt);
  164. // 描述符数组声明
  165. char code DEVICEDESC[18];                        // 设备描述符
  166. char code CONFIGDESC[41];                        // 配置描述符
  167. char code HIDREPORTDESC[27];                // HID 报告描述符
  168. char code LANGIDDESC[4];                        // 语言 ID 字符串描述符
  169. char code MANUFACTDESC[8];                        // 厂商字符串描述符
  170. char code PRODUCTDESC[30];                        // 产品字符串描述符
  171. // 声明变量       
  172. SETUP Setup;                                                // 标准请求的结构体变量
  173. EP0STAGE Ep0Stage;                                        // 端点 0 控制传输状态的结构体变量
  174. BYTE xdata HidFreature[64];                        // 特性报告缓冲区
  175. BYTE xdata HidInput[64];                        // IN 报告缓冲区
  176. BYTE xdata HidOutput[64];                        // OUT 报告缓冲区
  177. // 函数定义
  178. // 主函数
  179. void main()
  180. {
  181.         // IO 端口初始化为准双向口
  182.     P0M0 = 0x00;
  183.     P0M1 = 0x00;
  184.     P1M0 = 0x00;
  185.     P1M1 = 0x00;
  186.     P2M0 = 0x00;
  187.     P2M1 = 0x00;
  188.     P3M0 = 0x00;
  189.     P3M1 = 0x00;
  190.     P4M0 = 0x00;
  191.     P4M1 = 0x00;
  192.     P5M0 = 0x00;
  193.     P5M1 = 0x00;
  194.     UsbInit();                // USB 功能初始化
  195.     EA = 1;                        // 打开总中断
  196.     while (1);                // 需要循环执行的写这,目前不需要,但也必须要,避免程序跑飞
  197. }
  198. // 读取 USB 控制寄存器
  199. BYTE ReadReg(BYTE addr)
  200. {
  201.     BYTE dat;
  202.         // 如果 USBDAT 寄存器中的数据有效,则进入下一步,不然就在这反复查询
  203.     while (USBADR & 0x80);
  204.         // 启动 USB 的读操作
  205.     USBADR = addr | 0x80;
  206.         // 如果 USBDAT 寄存器中的数据有效,则进入下一步,不然就在这反复查询
  207.     while (USBADR & 0x80);
  208.         // 取出数据
  209.     dat = USBDAT;
  210.         // 返回数据
  211.     return dat;
  212. }
  213. // 写入 USB 控制寄存器
  214. void WriteReg(BYTE addr, BYTE dat)
  215. {
  216.         // 如果 USBDAT 寄存器中的数据有效,则进入下一步,不然就在这反复查询
  217.     while (USBADR & 0x80);
  218.         // 设置需读取的 USB 地址
  219.     USBADR = addr & 0x7f;
  220.         // 写入数据
  221.     USBDAT = dat;
  222. }
  223. // 读取端点n的 FIFO 数据
  224. BYTE ReadFifo(BYTE fifo, BYTE *pdat)
  225. {
  226.     BYTE cnt;
  227.     BYTE ret;
  228.         // 读取端点n的 OUT 长度,别看是叫 COUNT0 那是因为 COUNT0 和 OUTCOUNT1 都是一个地址,而且,针对于 0、1、2、3 端点,都够用了,如果是 4、5 则需要读取 OUTCOUNT2 为 [10:8] 数据
  229.     ret = cnt = ReadReg(COUNT0);
  230.         // 将 FIFO 里的数据逐个读出到指定地址,*pdat 是指针,指向的是存放的内存地址
  231.     while (cnt--)
  232.     {
  233.         *pdat++ = ReadReg(fifo);
  234.     }
  235.         // 返回端点n的 OUT 长度
  236.     return ret;
  237. }
  238. // 写入端点n的 FIFO 数据
  239. void WriteFifo(BYTE fifo, BYTE *pdat, BYTE cnt)
  240. {
  241.         // 将数据逐个写入到指定地址端点 FIFO ,*pdat 是指针,指向的是存放原始数据的内存地址
  242.         while (cnt--)
  243.     {
  244.         WriteReg(fifo, *pdat++);
  245.     }
  246. }
  247. // USB 初始化
  248. void UsbInit()
  249. {
  250.         // 将 USB 的 D+ 和 D- 引脚设置为高阻输入模式
  251.     P3M0 = 0x00;
  252.     P3M1 = 0x03;
  253.         // 8H
  254.     //USBCLK = 0xB0;                                // PLL 时钟源为 24Mhz,后续通过isp软件直接设置,使能 PLL 倍频和时钟追频
  255.         // 32G
  256.         IRC48MCR = 0x80;                        // 使用专门48Mhz时钟
  257.     while (!(IRC48MCR & 0x01));        // 等待时钟稳定
  258.     USBCLK = 0x00;
  259.        
  260.     USBCON = 0x90;                                // 使能 USB 功能和时钟,并使能上拉电阻,使主机将设备识别成全速设备
  261.     WriteReg(FADDR, 0x00);                // 清空 USB 的设备地址寄存器
  262.     WriteReg(POWER, 0x08);                // 产生异步 USB 复位
  263.     WriteReg(INTRIN1E, 0x3f);        // 允许所有 IN 端点的中断
  264.     WriteReg(INTROUT1E, 0x3f);        // 允许所有 OUT 端点的中断
  265.     WriteReg(INTRUSBE, 0x00);        // 不允许帧起始、复位、恢复、挂起信号的中断
  266.     WriteReg(POWER, 0x01);                // 使能挂起检测,当检测到总线上的挂起信号,USB 将进入挂起方式
  267.     Ep0Stage.bStage = EPIDLE;        // 将端点 0 状态的 bStage 设置为:等待新指令
  268. }
  269. // USB 中断入口函数,USB 的中断号为 25
  270. void usb_isr() interrupt 25
  271. {
  272.     BYTE intrusb;
  273.     BYTE intrin;
  274.     BYTE introut;
  275.     BYTE csr;
  276.     BYTE cnt;
  277.     WORD len;
  278.     intrusb = ReadReg(INTRUSB);                        // 获取 USB 电源中断标志位并赋值
  279.     intrin = ReadReg(INTRIN1);                        // 获取 USB 端点 IN 中断标志位并赋值
  280.     introut = ReadReg(INTROUT1);                // 获取 USB 端点 OUT 中断标志位并赋值
  281.        
  282.     if (intrusb & RSTIF)                                // 如果是 USB 复位信号中断
  283.     {       
  284.                 // 因为只启动了端点 1,所以才复位端点 1 而已,启用几个就复位几个
  285.         WriteReg(INDEX, 1);                                // 选择端点 1
  286.         WriteReg(INCSR1, INCLRDT);                // 将 IN 端点数据切换位复位,即复位到 data0 数据包
  287.         WriteReg(INDEX, 1);                                // 选择端点 1
  288.         WriteReg(OUTCSR1, OUTCLRDT);        // 将 OUT 端点数据切换位复位,即复位到 data0 数据包
  289.         Ep0Stage.bStage = EPIDLE;                // 将端点 0 状态的 bStage 设置为:等待新指令
  290.     }
  291.     if (intrin & EP0IF)                                                // 如果是端点 0 的 IN/OUT 中断
  292.     {       
  293.         WriteReg(INDEX, 0);                                        // 切换回端点 0
  294.         csr = ReadReg(CSR0);                                // 获取 USB 端点 0 控制状态寄存器并赋值
  295.         if (csr & STSTL)                                        // 如果是 STALL 发送完成
  296.         {
  297.             WriteReg(CSR0, csr & ~STSTL);        // 则对该位进行清 0,说明已知道,该 SETUP 包设备不支持,而且已经发送了 STALL,接下来就是等待主机的下一个 SETUP 包了
  298.             Ep0Stage.bStage = EPIDLE;                // 将端点 0 状态设置为:等待新指令
  299.         }
  300.         if (csr & SUEND)                                        // 如果 SETUP 包是无数据阶段的控制请求或者是被新的 SETUP 包强制中止
  301.         {
  302.             WriteReg(CSR0, csr | SSUEND);        // 清除标志位
  303.         }
  304.         switch (Ep0Stage.bStage)                        // 查询端点 0 控制传输当前处于哪个阶段
  305.         {
  306.         case EPIDLE:                                                                                                // 等待新指令阶段
  307.             if (csr & OPRDY)                                                                                // 如果是主机发来 OUT 包
  308.             {
  309.                 Ep0Stage.bStage = EPSTATUS;                                                        // 将端点 0 状态设置为:解析新指令
  310.                 ReadFifo(FIFO0, (BYTE *)&Setup);                                        // 将 SETUP 包的数据阶段的 8 字节数据读取出来并赋值给结构体
  311.                 ((BYTE *)&Ep0Stage.wResidue)[0] = Setup.wLengthH;        // 将端点 0 状态的 wResidue[15:8] 设置为:SETUP 包的 wLength 域的(所需要传输的字节数)
  312.                 ((BYTE *)&Ep0Stage.wResidue)[1]= Setup.wLengthL;        // 将端点 0 状态的 wResidue[7:0] 设置为:SETUP 包的 wLength 域的(所需要传输的字节数)
  313.                 switch (Setup.bmRequestType & REQUEST_MASK)                        // 将 SETUP 包的数据阶段的第一个字节(bmRequestType)数据取出,判断请求特性
  314.                 {
  315.                 case STANDARD_REQUEST:                                                                // 如果是标准请求
  316.                     switch (Setup.bRequest)                                                        // 将 SETUP 包的数据阶段的第二个字节(bRequest)数据取出,判断请求代码
  317.                     {
  318.                     case SET_ADDRESS:                                                                // 如果是给设备分配地址
  319.                         WriteReg(FADDR, Setup.wValueL);                                // 将 SETUP 包的数据阶段的第三个字节(wValue 的低字节)数据取出写入 USB 功能地址寄存器
  320.                         break;
  321.                     case SET_CONFIG:                                                                // 如果是启动端口
  322.                         WriteReg(INDEX, 1);                                                        // 选择端点 1
  323.                         WriteReg(INCSR2, INMODEIN);                                        // 设置端点为方向为 IN
  324.                         WriteReg(INMAXP, 8);                                                // 端点最大数据包大小为 8 * 8 = 64 字节
  325.                         WriteReg(INDEX, 1);                                                        // 选择端点 1
  326.                         WriteReg(INCSR2, INMODEOUT);                                // 设置端点为方向为 OUT
  327.                         WriteReg(OUTMAXP, 8);                                                // 端点最大数据包大小为 8 * 8 = 64 字节
  328.                         WriteReg(INDEX, 0);                                                        // 切换回端点 0
  329.                         break;
  330.                     case GET_DESCRIPTOR:                                                        // 如果是主机向设备请求描述符
  331.                         Ep0Stage.bStage = EPDATAIN;                                        // 将端点 0 状态的 bStage 设置为:正在发数据给主机
  332.                         switch (Setup.wValueH)                                                // 将 SETUP 包的数据阶段的第四个字节(wValue 的高字节)数据取出,判断是什么描述符类型
  333.                         {
  334.                         case DESC_DEVICE:                                                        // 如果是设备描述符
  335.                             Ep0Stage.pData = DEVICEDESC;                        // 将端点 0 状态的 pData 指针设置为设备描述符数组的起始位置
  336.                             len = sizeof(DEVICEDESC);                                // 计算设备描述符数组大小
  337.                             break;
  338.                         case DESC_CONFIG:                                                        // 如果是配置描述符
  339.                             Ep0Stage.pData = CONFIGDESC;            // 将端点 0 状态的 pData 指针设置为配置描述符数组的起始位置
  340.                             len = sizeof(CONFIGDESC);               // 计算配置描述符数组大小
  341.                             break;
  342.                         case DESC_STRING:                                                        // 如果是字符串描述符
  343.                             switch (Setup.wValueL)                  // 将 SETUP 包的数据阶段的第三个字节(wValue 的低字节)数据取出,判断是哪个字符串描述符ID,没有强制要求,不过一般的顺序是:语言 ID、厂商、产品名、序列号。。。,语言 ID固定是 0 ,其他1,2,3是可以在描述符里定义的
  344.                             {                                       // 计算字符串描述符数组大小
  345.                             case 0:                                                                        // 如果是获取语言 ID 字符串描述符
  346.                                 Ep0Stage.pData = LANGIDDESC;                // 将端点 0 状态的 pData 指针设置为语言 ID 字符串描述符数组的起始位置
  347.                                 len = sizeof(LANGIDDESC);           // 计算语言 ID 字符串描述符数组大小
  348.                                 break;
  349.                             case 1:                                                                        // 如果是获取厂商字符串描述符
  350.                                 Ep0Stage.pData = MANUFACTDESC;      // 将端点 0 状态的 pData 指针设置为厂商字符串描述符数组的起始位置
  351.                                 len = sizeof(MANUFACTDESC);         // 计算厂商字符串描述符数组大小
  352.                                 break;
  353.                             case 2:                                                                        // 如果是获取产品名字符串描述符
  354.                                 Ep0Stage.pData = PRODUCTDESC;       // 将端点 0 状态的 pData 指针设置为产品名字符串描述符数组的起始位置
  355.                                 len = sizeof(PRODUCTDESC);          // 计算产品名字符串描述符数组大小
  356.                                 break;
  357.                             default:                                                                // 如果都不是
  358.                                 Ep0Stage.bStage = EPSTALL;                        // 将端点 0 状态的 bStage 设置为:出错,端点挂起
  359.                                 break;
  360.                             }
  361.                             break;
  362.                         case DESC_HIDREPORT:                                                // 如果是获取 HID 报告描述符
  363.                             Ep0Stage.pData = HIDREPORTDESC;                        // 将端点 0 状态的 pData 指针设置为 HID 报告描述符数组的起始位置
  364.                             len = sizeof(HIDREPORTDESC);            // 计算 HID 报告描述符数组大小
  365.                             break;
  366.                         default:                                                                        // 如果都不是
  367.                             Ep0Stage.bStage = EPSTALL;              // 将端点 0 状态的 bStage 设置为:出错,端点挂起
  368.                             break;
  369.                         }
  370.                         if (len < Ep0Stage.wResidue)                                // 对需要发送的描述符数组长度与端点 0 状态的 wResidue 的字节数进行判断,如果所需要传输的字节数小于端点 0 状态的 wResidue 的字节数
  371.                         {
  372.                             Ep0Stage.wResidue = len;                                // 则将端点 0 状态的 wResidue 的字节数设置为所需要传输的字节数
  373.                         }
  374.                         break;
  375.                     default:                                                                                // 如果都不是
  376.                         Ep0Stage.bStage = EPSTALL;                  // 将端点 0 状态的 bStage 设置为:出错,端点挂起
  377.                         break;
  378.                     }
  379.                     break;
  380.                 case CLASS_REQUEST:                                                                        // 如果是类请求
  381.                     switch (Setup.bRequest)                                                        // 将 SETUP 包的数据阶段的第二个字节(bRequest)数据取出,判断请求代码
  382.                     {
  383.                     case GET_REPORT:                                                                // 如果是请求获取设备特性报告
  384.                         Ep0Stage.pData = HidFreature;                                // 将端点 0 状态的 pData 指针设置为特性报告缓冲区数组的起始位置
  385.                         Ep0Stage.bStage = EPDATAIN;                 // 将端点 0 状态的 bStage 设置为:正在发数据给主机
  386.                         break;
  387.                     case SET_REPORT:                                                                // 如果是主机要求设备设置端点的一些特性
  388.                         Ep0Stage.pData = HidFreature;                                // 将端点 0 状态的 pData 指针设置为特性报告缓冲区数组的起始位置
  389.                         Ep0Stage.bStage = EPDATAOUT;                                // 将端点 0 状态的 bStage 设置为:正在从主机收数据
  390.                         break;
  391.                     case SET_IDLE:                                                                        // 如果是设置空闲率,让主机等待超时,不作回应
  392.                         break;
  393.                     case GET_IDLE:                                                                        // 如果是获取空闲率,让主机等待超时,不作回应
  394.                     case GET_PROTOCOL:                                                                // 如果是获取报告上传协议,让主机等待超时,不作回应,不设置默认就是引导协议
  395.                     case SET_PROTOCOL:                                                                // 如果是读取报告上传协议,让主机等待超时,不作回应
  396.                     default:                                                                                // 如果都不是
  397.                         Ep0Stage.bStage = EPSTALL;                                        // 将端点 0 状态的 bStage 设置为:出错,端点挂起
  398.                         break;                                      
  399.                     }
  400.                     break;
  401.                 default:                                                                                        // 如果都不是
  402.                     Ep0Stage.bStage = EPSTALL;                                                // 将端点 0 状态的 bStage 设置为:出错,端点挂起
  403.                     break;                                          
  404.                 }
  405.                 switch (Ep0Stage.bStage)                                                        // 判断端点 0 状态的 bStage 的状态标志位
  406.                 {
  407.                 case EPDATAIN:                                                                                // 如果是正在发数据给主机
  408.                     WriteReg(CSR0, SOPRDY);                                                        // 置位事件完成标志
  409.                     goto L_Ep0SendData;                                                                // 跳转至 L_Ep0SendData 其实位置,其实这是伪代码,目前的篇幅,是指跳转至 477 行的位置
  410.                     break;
  411.                 case EPDATAOUT:                                                                                // 如果是正在从主机收数据
  412.                     WriteReg(CSR0, SOPRDY);                                                        // 置位事件完成标志
  413.                     break;
  414.                 case EPSTATUS:                                                                                // 如果是正在解析新指令
  415.                     WriteReg(CSR0, SOPRDY | DATEND);                                // 置位事件完成标志,数据结束:接收完最后一个数据包
  416.                     Ep0Stage.bStage = EPIDLE;                                                // 将端点 0 状态的 bStage 设置为:等待新指令
  417.                     break;
  418.                 case EPSTALL:                                                                                // 如果是出错,端点被挂起
  419.                     WriteReg(CSR0, SOPRDY | SDSTL);                                        // 置位事件完成标志,结束当前的数据传输
  420.                     Ep0Stage.bStage = EPIDLE;                                                // 将端点 0 状态的 bStage 设置为:等待新指令
  421.                     break;
  422.                 }
  423.             }
  424.             break;
  425.         case EPDATAIN:                                                                                                                // 如果是正在发数据给主机
  426.             if (!(csr & IPRDY))                                                                                                // 且如果 IN 数据包准备完成,且已发送
  427.             {
  428. L_Ep0SendData:
  429.                 cnt = Ep0Stage.wResidue > 64 ? 64 : Ep0Stage.wResidue;                // 判断端点 0 状态的 wResidue 的字节数是否大于 64 字节,如果大于则设置本次发送字节数为 64 字节,否则仍按端点 0 状态的 wResidue 的字节数发送
  430.                 WriteFifo(FIFO0, Ep0Stage.pData, cnt);                                                // 往端点 0 的 FIFO 数据访问寄存器写入数据
  431.                 Ep0Stage.wResidue -= cnt;                                                                        // 完成后对总字节数减去已发送的字节数
  432.                 Ep0Stage.pData += cnt;                                                                                // 然后将端点 0 状态的 pData 指针偏移对应的字节数
  433.                 if (Ep0Stage.wResidue == 0)                                                                        // 如果还剩字节数为 0 了
  434.                 {
  435.                     WriteReg(CSR0, IPRDY | DATEND);                                                        // 置位 IN 数据包准备完成标志,数据结束:发送完最后一个数据包
  436.                     Ep0Stage.bStage = EPIDLE;                                                                // 将端点 0 状态的 bStage 设置为:等待新指令
  437.                 }
  438.                 else                                                                                                                // 否则
  439.                 {
  440.                     WriteReg(CSR0, IPRDY);                                                                        // 置位 IN 数据包准备完成标志
  441.                 }
  442.             }
  443.             break;
  444.         case EPDATAOUT:                                                                                                                // 如果是正在从主机收数据
  445.             if (csr & OPRDY)                                                                                                // 且如果 OUT 数据包准备完成,即收满了
  446.             {
  447.                 cnt = ReadFifo(FIFO0, Ep0Stage.pData);                                                // 将端点 0 的 FIFO 数据访问寄存器的值按端点 0 状态的 pData 指针的起始位置开始写出,并同时获取总字节数
  448.                 Ep0Stage.wResidue -= cnt;                                                                        // 端点 0 状态的 wResidue 的字节数减去已发送的字节数
  449.                 Ep0Stage.pData += cnt;                                                                                // 然后将端点 0 状态的 pData 指针偏移对应的字节数
  450.                 if (Ep0Stage.wResidue == 0)                                                                        // 如果还剩字节数为 0 了
  451.                 {
  452.                     WriteReg(CSR0, SOPRDY | DATEND);                                                // 置位事件完成标志,数据结束:接收完最后一个数据包
  453.                     Ep0Stage.bStage = EPIDLE;                                                                // 将端点 0 状态的 bStage 设置为:等待新指令
  454.                 }
  455.                 else                                                                                                                // 否则
  456.                 {
  457.                     WriteReg(CSR0, SOPRDY);                                                                        // 置位事件完成标志
  458.                 }
  459.             }
  460.             break;
  461.         }
  462.     }
  463.     if (intrin & EP1INIF)                                                // 如果是端点 1 的 IN 中断
  464.     {
  465.         WriteReg(INDEX, 1);                                                // 选择端点 1
  466.         csr = ReadReg(INCSR1);                                        // 读取 IN 端点控制状态寄存器 1
  467.         if (csr & INSTSTL)                                                // 如果是 STALL 信号发送完成标志
  468.         {
  469.             WriteReg(INCSR1, INCLRDT);                        // 将 IN 端点数据切换位复位,即复位到 data0 数据包
  470.         }
  471.         if (csr & INUNDRUN)                                                // 如果是回复了 NAK 信号标志位
  472.         {
  473.             WriteReg(INCSR1, 0);                                // 清零标志位
  474.         }
  475.     }
  476.     if (introut & EP1OUTIF)                                                // 如果是端点 1 的 OUT 中断
  477.     {
  478.         WriteReg(INDEX, 1);                                                // 选择端点 1
  479.         csr = ReadReg(OUTCSR1);                                        // 读取 OUT 端点控制状态寄存器 1
  480.         if (csr & OUTSTSTL)                                                // 如果是 STALL 信号发送完成标志
  481.         {
  482.             WriteReg(OUTCSR1, OUTCLRDT);                // 将 OUT 端点数据切换位复位,即复位到 data0 数据包
  483.         }
  484.         if (csr & OUTOPRDY)                                                // 当有 OUT 数据包到来标志位
  485.         {
  486.             ReadFifo(FIFO1, HidOutput);                        // 将数据包读取到 OUT 报告缓冲区数组
  487.             WriteReg(OUTCSR1, 0);                                // 清零标志位
  488.             WriteReg(INDEX, 1);                                        // 选择端点 1
  489.             WriteFifo(FIFO1, HidOutput, 64);        // 将数据写入 IN 端点,目前这个仅仅就是将接收到的数据又原路返回了回去
  490.             WriteReg(INCSR1, INIPRDY);                        // 置数据写入完成标志位
  491.         }
  492.     }
  493. }
  494. // 设备描述符
  495. char code DEVICEDESC[18] =
  496. {
  497.     0x12,       //bLength(18);                                描述符长度为 18 个字节
  498.     0x01,       //bDescriptorType(Device);        描述符类型为设备描述符
  499.     0x00,0x02,  //bcdUSB(2.00);                                USB 版本为 2.0
  500.     0x00,       //bDeviceClass(0);                        设备类,均不声明,所以为0
  501.     0x00,       //bDeviceSubClass0);                子类,均不声明,所以为0
  502.     0x00,       //bDeviceProtocol(0);                协议,均不声明,所以为0
  503.     0x40,       //bMaxPacketSize0(64);                控制传输,即端点 0 最大字节数为 64 字节
  504.     0x54,0x53,  //idVendor(5354);                        厂商 ID 为 0x5354
  505.     0x80,0x43,  //idProduct(4380);                        产品 ID 为 0x4380
  506.     0x00,0x01,  //bcdDevice(1.00);                        设备固件版本号为1.00,这个无所谓的
  507.     0x01,       //iManufacturer(1);                        厂商名称的字符串索引 ID 是 1
  508.     0x02,       //iProduct(2);                                产品名称的字符串索引 ID 是 2
  509.     0x00,       //iSerialNumber(0);                        没有序列号字符串
  510.     0x01,       //bNumConfigurations(1);        设备只有 1 套功能配置
  511. };
  512. // 配置描述符
  513. char code CONFIGDESC[41] =
  514. {
  515.     0x09,       //bLength(9);                                                        描述符长度为 9 字节
  516.     0x02,       //bDescriptorType(Configuration);                描述符类型为配置描述符
  517.     0x29,0x00,  //wTotalLength(41);                                                配置描述符集合总长度为 41 字节
  518.     0x01,       //bNumInterfaces(1);                                        该配置下只有 1 个接口
  519.     0x01,       //bConfigurationValue(1);                                该配置的值为 1
  520.     0x00,       //iConfiguration(0);                                        该配置没有字符串描述符
  521.     0x80,       //bmAttributes(BUSPower);                                该配置为总线供电方式
  522.     0x32,       //MaxPower(100mA);                                                供电电流 100mA
  523.         // 接口描述符
  524.     0x09,       //bLength(9);
  525.     0x04,       //bDescriptorType(Interface);                        描述符类型为接口描述符
  526.     0x00,       //bInterfaceNumber(0);                                        接口编号为 0
  527.     0x00,       //bAlternateSetting(0);                                        备用设置编号为 0
  528.     0x02,       //bNumEndpoints(2);                                                该接口带 2 个业务端点(不含端点 0)
  529.     0x03,       //bInterfaceClass(HID);                                        接口类别为 HID 人机接口类
  530.     0x01,       //bInterfaceSubClass(1);                                接口子类为支持 BIOS 引导启动的子类
  531.     0x00,       //bInterfaceProtocol(0);                                接口协议不声明(自定义HID),可选,0x01 是键盘, 0x02 是鼠标
  532.     0x00,       //iInterface(0);                                                该接口没有字符串描述符
  533.         // HID 类专属描述符
  534.     0x09,       //bLength(9);                                                        描述符长度为 9 字节
  535.     0x21,       //bDescriptorType(HID);                                        描述符类型为 HID 类描述符
  536.     0x01,0x01,  //bcdHID(1.01);                                                        遵循 HID 1.01 规范
  537.     0x00,       //bCountryCode(0);                                                无国家地区编码
  538.     0x01,       //bNumDescriptors(1);                                        附带 1 个子描述符(报告描述符)
  539.     0x22,       //bDescriptorType(HID Report);                        子描述符类型为 HID 报告描述符
  540.     0x1b,0x00,  //wDescriptorLength(27);                                报告描述符总长度为 27 字节
  541.         // 端点 1 IN 描述符
  542.     0x07,       //bLength(7);                                                        描述符长度为 7 字节
  543.     0x05,       //bDescriptorType(Endpoint);                        描述符类型为端点描述符
  544.     0x81,       //bEndpointAddress(EndPoint1 as IN);        端点号为 1,方向为 IN
  545.     0x03,       //bmAttributes(Interrupt);                                传输类型为中断传输
  546.     0x40,0x00,  //wMaxPacketSize(64);                                        单包最大字节数为 64 字节
  547.     0x01,       //bInterval(1ms);                                                主机轮询间隔为 1ms
  548.         // 端点 1 OUT 描述符,键盘才需要这个描述符,如果是鼠标则不需要,可删除,删除后记得调整配置描述的 wTotalLength 和接口描述符的 bLength 及 bNumEndpoints
  549.     0x07,       //bLength(7);                                                        描述符长度为 7 字节
  550.     0x05,       //bDescriptorType(Endpoint);                        描述符类型为端点描述符
  551.     0x01,       //bEndpointAddress(EndPoint1 as OUT);        端点号为 1,方向为 OUT
  552.     0x03,       //bmAttributes(Interrupt);                                传输类型为中断传输
  553.     0x40,0x00,  //wMaxPacketSize(64);                                        单包最大字节数为 64 字节
  554.     0x01,       //bInterval(1ms);                                                主机轮询间隔为 1ms
  555. };
  556. // HID 报告描述符
  557. char code HIDREPORTDESC[27] =
  558. {
  559.     0x05,0x0c,  //USAGE_PAGE(Consumer);                                        用法页:消费设备类
  560.     0x09,0x01,  //USAGE(Consumer Control);                                用法:消费控制
  561.     0xa1,0x01,  //COLLECTION(Application);                                开启一个应用功能集合
  562.     0x15,0x00,  //  LOGICAL_MINIMUM(0);                                        数据逻辑最小值为 0
  563.     0x25,0xff,  //  LOGICAL_MAXIMUM(255);                                数据逻辑最大值为 255
  564.     0x75,0x08,  //  REPORT_SIZE(8);                                                每个字段占 8 位(1 字节)
  565.     0x95,0x40,  //  REPORT_COUNT(64);                                        单份报告有 64 个字段(共 64 字节)
  566.     0x09,0x01,  //  USAGE(Consumer Control);                        绑定用法:消费控制
  567.     0xb1,0x02,  //  FEATURE(Data,Variable);                                定义 Feature 特性报告(控制通道读写)
  568.     0x09,0x01,  //  USAGE(Consumer Control);                        绑定用法:消费控制
  569.     0x81,0x02,  //  INPUT(Data,Variable);                                定义 Input 输入报告(中断 IN 上报)
  570.     0x09,0x01,  //  USAGE(Consumer Control);                        绑定用法:消费控制
  571.     0x91,0x02,  //  OUTPUT(Data,Variable);                                定义 Output 输出报告(中断 OUT 下发)
  572.     0xc0,       //END_COLLECTION;                                                结束功能集合
  573. };
  574. // 语言 ID 字符串描述符
  575. char code LANGIDDESC[4] =
  576. {
  577.     0x04,0x03,                                // 描述符长度为 4 字节,描述符类型为字符串描述符
  578.     0x09,0x04,                                // 支持语言:英语(美国)
  579. };
  580. // 厂商字符串描述符
  581. char code MANUFACTDESC[8] =
  582. {
  583.     0x08,0x03,                                // 描述符长度为 8 字节,描述符类型为字符串描述符
  584.     'S',0,
  585.     'T',0,
  586.     'C',0,
  587. };
  588. // 产品字符串描述符
  589. char code PRODUCTDESC[30] =
  590. {
  591.     0x1e,0x03,                                // 描述符长度为 30 字节,描述符类型为字符串描述符
  592.     'S',0,
  593.     'T',0,
  594.     'C',0,
  595.     ' ',0,
  596.     'U',0,
  597.     'S',0,
  598.     'B',0,
  599.     ' ',0,
  600.     'D',0,
  601.     'e',0,
  602.     'v',0,
  603.     'i',0,
  604.     'c',0,
  605.     'e',0,
  606. };
复制代码
1 喜欢他/她就送朵鲜花吧,赠人玫瑰,手有余香!
回复

使用道具 举报 送花

  • 打卡等级:以坛为家III
  • 打卡总天数:670
  • 最近打卡:2026-07-21 17:54:57

0

主题

81

回帖

2652

积分

金牌会员

积分
2652
发表于 2026-7-7 16:34:01 | 显示全部楼层
赞一个,好详细
回复

使用道具 举报 送花

  • 打卡等级:以坛为家II
  • 打卡总天数:467
  • 最近打卡:2026-07-21 09:31:46

851

主题

1万

回帖

2万

积分

管理员

积分
24394
发表于 2026-7-7 16:41:40 | 显示全部楼层
帮直接贴出来
回复

使用道具 举报 送花

  • 打卡等级:以坛为家III
  • 打卡总天数:817
  • 最近打卡:2026-07-22 05:36:35

32

主题

182

回帖

3468

积分

论坛元老

积分
3468
发表于 2026-7-7 16:46:34 | 显示全部楼层

贴出来了,刚刚不知道为什么,贴上去没反应
回复

使用道具 举报 送花

  • 打卡等级:以坛为家I
  • 打卡总天数:387
  • 最近打卡:2026-07-22 08:58:13
已绑定手机

5

主题

26

回帖

403

积分

中级会员

积分
403
发表于 2026-7-7 17:42:20 | 显示全部楼层
好人呐。这下难度降低了
回复

使用道具 举报 送花

  • 打卡等级:以坛为家III
  • 打卡总天数:817
  • 最近打卡:2026-07-22 05:36:35

32

主题

182

回帖

3468

积分

论坛元老

积分
3468
发表于 2026-7-7 18:07:32 来自手机 | 显示全部楼层
奥古斯朱 发表于 2026-7-7 17:42
好人呐。这下难度降低了

这个还不齐的哦,首先报告描述符是不齐的,然后,端点字节也不需要那么大,8字节就可以了,还有一些上报的也还要处理好,不过现在插进去,是能正确通讯了
回复

使用道具 举报 送花

  • 打卡等级:以坛为家II
  • 打卡总天数:467
  • 最近打卡:2026-07-21 09:31:46

851

主题

1万

回帖

2万

积分

管理员

积分
24394
发表于 2026-7-7 21:04:08 | 显示全部楼层
截图202607072104054647.jpg
回复

使用道具 举报 送花

  • 打卡等级:以坛为家II
  • 打卡总天数:467
  • 最近打卡:2026-07-21 09:31:46

851

主题

1万

回帖

2万

积分

管理员

积分
24394
发表于 2026-7-7 21:04:37 | 显示全部楼层

现在我已经获取了完整的 USB SIE 寄存器手册资料。让我结合手册内容,对原帖中的 HID 协议注释代码进行完善注解。


以下是基于 STC8H / Ai8H 系列数据手册(章节 "28 USB 2.0-FS通用串行总线")对原帖代码的完善注解版本。原帖作者已做了很好的基础注释,我在此基础上补充了每个寄存器的位域详细说明、SIE 间址访问机制说明以及 HID 协议相关的描述符背景。

//==============================================================================
// 文件名: STC_USB_HID_注释参考.h
// 说明:   基于 STC8H/Ai8H 系列数据手册 USB 章节,对 HID 协议相关
//          USB SIE 寄存器进行详细注解,供学习和参考。
// 参考手册: STC8H 数据手册 "28 USB 2.0-FS通用串行总线"
// 原作者:   183443*** (STC论坛)
// 完善注解: STC FAE
//==============================================================================

//==============================================================================
// 一、头文件引入
//==============================================================================
// #include <STC8H.H>          // STC8H系列头文件(如使用STC8H)
#include <STC8051U.H>          // Ai8051U系列头文件(如使用Ai8051U)
#include "intrins.h"           // 内置函数库(_nop_()等)

//==============================================================================
// 二、基础数据类型定义
//==============================================================================
typedef unsigned char   BYTE;   // 单字节(8位),对应USB的1字节数据
typedef unsigned int    WORD;   // 2字节(16位),对应USB描述符中的长度字段等
typedef unsigned long   DWORD;  // 4字节(32位),对应USB描述符中的地址/能力字段等

//==============================================================================
// 三、USB SIE(串行接口引擎)寄存器定义
//==============================================================================
// STC 的 USB 模块通过 SIE 寄存器组进行控制,这些寄存器通过 USBADR/USBDAT
// 间址方式访问。SIE 内部寄存器地址空间为 0x00~0x31。
// 访问方法: 将目标SIE寄存器地址写入 USBADR,然后读/写 USBDAT。
//==============================================================================

// ---- 3.1 基础控制寄存器 (SIE地址: 0x00 ~ 0x0F) ----

// 0x00: USB功能地址寄存器 (FADDR)
//       主机在枚举过程中通过 SET_ADDRESS 请求分配的设备地址。
//       B7=UPDATE: 地址更新标志,写1加载新地址
//       B6~B0=UADDR[6:0]: USB设备地址(7位,范围0~127)
#define FADDR       0

// 0x01: USB电源管理寄存器 (POWER)
//       B7=ISOUD:   等时模式输出更新模式
//       B3=USBRST:  总线复位标志
//       B2=USBRSU:  恢复信号标志
//       B1=USBSUS:  挂起状态标志
//       B0=ENSUS:   使能挂起检测
#define POWER       1

// 0x02: USB端点IN中断标志位 (INTRIN1)
//       当端点完成IN传输(数据从设备发往主机)时,对应位置1。
//       B5=EP5INIF: 端点5 IN中断标志
//       B4=EP4INIF: 端点4 IN中断标志
//       B3=EP3INIF: 端点3 IN中断标志
//       B2=EP2INIF: 端点2 IN中断标志
//       B1=EP1INIF: 端点1 IN中断标志
//       B0=EP0IF:   端点0 IN/OUT中断标志(端点0共用)
#define INTRIN1     2
#define EP5INIF     0x20    // INTRIN1.5
#define EP4INIF     0x10    // INTRIN1.4
#define EP3INIF     0x08    // INTRIN1.3
#define EP2INIF     0x04    // INTRIN1.2
#define EP1INIF     0x02    // INTRIN1.1
#define EP0IF       0x01    // INTRIN1.0

// 0x04: USB端点OUT中断标志位 (INTROUT1)
//       当端点完成OUT传输(主机发数据到设备)时,对应位置1。
//       B5=EP5OUTIF: 端点5 OUT中断标志
//       B4=EP4OUTIF: 端点4 OUT中断标志
//       B3=EP3OUTIF: 端点3 OUT中断标志
//       B2=EP2OUTIF: 端点2 OUT中断标志
//       B1=EP1OUTIF: 端点1 OUT中断标志
#define INTROUT1    4
#define EP5OUTIF    0x20    // INTROUT1.5
#define EP4OUTIF    0x10    // INTROUT1.4
#define EP3OUTIF    0x08    // INTROUT1.3
#define EP2OUTIF    0x04    // INTROUT1.2
#define EP1OUTIF    0x02    // INTROUT1.1

// 0x06: USB电源中断标志位 (INTRUSB)
//       B3=SOFIF:  帧起始(Start-of-Frame)中断标志,每1ms产生一次
//       B2=RSTIF:  总线复位(Reset)中断标志,主机发送复位信号时置位
//       B1=RSUIF:  恢复(Resume)中断标志,从挂起唤醒时置位
//       B0=SUSIF:  挂起(Suspend)中断标志,总线空闲超过3ms时置位
#define INTRUSB     6
#define SOFIF       0x08    // INTRUSB.3
#define RSTIF       0x04    // INTRUSB.2
#define RSUIF       0x02    // INTRUSB.1
#define SUSIF       0x01    // INTRUSB.0

// 0x07: USB端点IN中断允许寄存器 (INTRIN1E)
//       对应INTRIN1各中断位的使能控制。
//       B5=EP5INIE: 端点5 IN中断使能
//       B4=EP4INIE: 端点4 IN中断使能
//       B3=EP3INIE: 端点3 IN中断使能
//       B2=EP2INIE: 端点2 IN中断使能
//       B1=EP1INIE: 端点1 IN中断使能
//       B0=EP0IE:   端点0中断使能
//       复位值: xx11,1111(端点1~5和端点0中断默认使能)
#define INTRIN1E    7
#define EP5INIE     0x20    // INTRIN1E.5
#define EP4INIE     0x10    // INTRIN1E.4
#define EP3INIE     0x08    // INTRIN1E.3
#define EP2INIE     0x04    // INTRIN1E.2
#define EP1INIE     0x02    // INTRIN1E.1
#define EP0IE       0x01    // INTRIN1E.0

// 0x09: USB端点OUT中断允许寄存器 (INTROUT1E)
//       对应INTROUT1各中断位的使能控制。
//       B5=EP5OUTIE: 端点5 OUT中断使能
//       B4=EP4OUTIE: 端点4 OUT中断使能
//       B3=EP3OUTIE: 端点3 OUT中断使能
//       B2=EP2OUTIE: 端点2 OUT中断使能
//       B1=EP1OUTIE: 端点1 OUT中断使能
//       复位值: xx11,111x(端点1~5 OUT中断默认使能)
#define INTROUT1E   9
#define EP5OUTIE    0x20    // INTROUT1E.5
#define EP4OUTIE    0x10    // INTROUT1E.4
#define EP3OUTIE    0x08    // INTROUT1E.3
#define EP2OUTIE    0x04    // INTROUT1E.2
#define EP1OUTIE    0x02    // INTROUT1E.1

// 0x0B: USB电源中断允许寄存器 (INTRUSBE)
//       对应INTRUSB各中断位的使能控制。
//       B3=SOFIE: 帧起始中断使能
//       B2=RSTIE: 总线复位中断使能
//       B1=RSUIE: 恢复中断使能
//       B0=SUSIE: 挂起中断使能
//       复位值: xxxx,0110(默认使能复位和恢复中断)
#define INTRUSBE    0x0B
#define SOFIE       0x08    // INTRUSBE.3
#define RSTIE       0x04    // INTRUSBE.2
#define RSUIE       0x02    // INTRUSBE.1
#define SUSIE       0x01    // INTRUSBE.0

// 0x0C~0x0D: USB数据帧号寄存器 (FRAME1/FRAME2)
//       FRAME1[7:0] + FRAME2[2:0] = 11位帧号(0~2047)
//       主机每1ms发送一个SOF包,内含递增的帧号。
#define FRAME1      0x0C
#define FRAME2      0x0D

// 0x0E: USB端点索引寄存器 (INDEX)
//       用于选择当前操作的端点,后续的INMAXP/CSR/FIFO等寄存器
//       均指向INDEX所选的端点。
//       INDEX[2:0]:
//         000 = 端点0 (控制端点,双向)
//         001 = 端点1
//         010 = 端点2
//         011 = 端点3
//         100 = 端点4
//         101 = 端点5
#define INDEX       0x0E

// ---- 3.2 端点配置寄存器 (SIE地址: 0x10 ~ 0x17) ----
//       这些寄存器受INDEX影响,操作前需先设置INDEX选择目标端点。

// 0x10: IN端点的最大数据包大小 (INMAXP)
//       定义该端点IN方向(设备→主机)一次传输的最大字节数。
//       对于HID设备,中断IN端点通常设为 8~64 字节。
#define INMAXP      0x10

// 0x11: 端点0控制状态寄存器 (CSR0) / IN端点控制状态寄存器1 (INCSR1)
//       【当INDEX=0时,该地址为CSR0】
//       B7=SSUEND:   设置Setup结束(写1清除SUEND)
//       B6=SOPRDY:   设置OUT包就绪(固件处理完数据后写1通知SIE)
//       B5=SDSTL:    设置STALL(向主机返回STALL握手)
//       B4=SUEND:    Setup结束标志(主机发送了多个Setup包时置位)
//       B3=DATEND:   数据结束标志(数据阶段完成后置位)
//       B2=STSTL:    STALL发送标志(已发送STALL时置位)
//       B1=IPRDY:    IN包就绪(固件将数据写入FIFO后写1,通知SIE发送)
//       B0=OPRDY:    OUT包就绪(SIE收到OUT数据后置位,通知固件读取)
//
//       【当INDEX≠0时,该地址为INCSR1】
//       B6=CLRDT:    清除数据翻转位
//       B5=STSTL:    STALL发送标志
//       B4=SDSTL:    设置STALL
//       B3=FLUSH:    刷新FIFO
//       B2=UNDRUN:   下溢标志(IN数据未及时准备好)
//       B1=FIFONE:   FIFO非空标志
//       B0=IPRDY:    IN包就绪
#define CSR0        0x10    // 注意: 实际地址是0x11,这里宏定义名称需注意
// 注: 原帖中 #define CSR0 0x10 实际对应的是INMAXP地址。
//     根据手册,CSR0/INCSR1的SIE地址为0x11,INMAXP为0x10。
//     建议修正为:
//     #define CSR0    0x11    // 端点0控制状态寄存器 (INDEX=0时)
//     #define INCSR1  0x11    // IN端点控制状态寄存器1 (INDEX≠0时)

// 0x12: IN端点控制状态寄存器2 (INCSR2)
//       B7=AUTOSET: 自动置位IPRDY(数据写入FIFO后自动触发IN传输)
//       B6=ISO:     等时传输模式使能
//       B5=MODE:    模式选择(1=双缓冲, 0=单缓冲)
//       B4=ENDMA:   DMA结束标志
//       B3=FCDT:    强制数据翻转
//       复位值: 0010,0xxx(默认MODE=1,即双缓冲模式)
#define INCSR2      0x12

// 0x13: OUT端点的最大数据包大小 (OUTMAXP)
//       定义该端点OUT方向(主机→设备)一次传输的最大字节数。
#define OUTMAXP     0x13

// 0x14: OUT端点控制状态寄存器1 (OUTCSR1)
//       B7=CLRDT:   清除数据翻转位
//       B6=STSTL:   STALL发送标志
//       B5=SDSTL:   设置STALL
//       B4=FLUSH:   刷新FIFO
//       B3=DATERR:  数据错误标志
//       B2=OVRRUN:  上溢标志(FIFO已满,主机继续发数据)
//       B1=FIFOFUL: FIFO满标志
//       B0=OPRDY:   OUT包就绪(SIE收到OUT数据后置位,通知固件读取)
#define OUTCSR1     0x14

// 0x15: OUT端点控制状态寄存器2 (OUTCSR2)
//       B7=AUTOCLR: 自动清除OPRDY(固件读取FIFO后自动清除)
//       B6=ISO:     等时传输模式使能
//       B5=ENDMA:   DMA结束标志(已废弃)
//       B4=DMAMD:   DMA模式(已废弃)
#define OUTCSR2     0x15

// 0x16: 端点0的OUT长度 (COUNT0) / OUT端点OUT长度低字节 (OUTCOUNT1)
//       【INDEX=0时】COUNT0: B6~B0 = OUTCNT0[6:0],端点0收到的OUT数据字节数
//       【INDEX≠0时】OUTCOUNT1: OUTCNT[7:0],OUT数据长度低8位
#define COUNT0      0x16

// 0x17: OUT端点OUT长度高字节 (OUTCOUNT2)
//       B2~B0 = OUTCNT[10:8],OUT数据长度高3位
//       与OUTCOUNT1组合为11位长度值(最大2047字节)
#define OUTCOUNT2   0x17

// ---- 3.3 FIFO数据访问寄存器 (SIE地址: 0x20 ~ 0x25) ----
//       通过读写这些寄存器来访问对应端点的FIFO缓冲区。
//       每个端点有独立的FIFO空间,大小由端点配置决定。

// 0x20: 端点0的FIFO访问寄存器 (FIFO0)
//       控制端点(端点0)的数据缓冲区,用于处理Setup包和
//       控制传输的数据阶段。
#define FIFO0       0x20

// 0x21: 端点1的FIFO访问寄存器 (FIFO1)
//       通常用于HID中断IN端点(鼠标/键盘上报数据)
#define FIFO1       0x21

// 0x22: 端点2的FIFO访问寄存器 (FIFO2)
//       可用于HID中断OUT端点(接收主机下发的LED状态等)
#define FIFO2       0x22

// 0x23: 端点3的FIFO访问寄存器 (FIFO3)
#define FIFO3       0x23

// 0x24: 端点4的FIFO访问寄存器 (FIFO4)
#define FIFO4       0x24

// 0x25: 端点5的FIFO访问寄存器 (FIFO5)
#define FIFO5       0x25

// ---- 3.4 USB跟踪控制/状态寄存器 (SIE地址: 0x30 ~ 0x31) ----
//       用于USB时钟恢复跟踪,一般保持默认值即可。

// 0x30: USB跟踪控制寄存器 (UTRKCTL)
//       B7~B6=FTM[1:0]: 滤波模式选择
//       B5~B4=INTV[1:0]: 间隔时间选择
//       B3=ENST5: 使能端点5跟踪
//       B2~B0=RES[2:0]: 分辨率选择
//       复位值: 1011,1011
#define UTRKCTL     0x30

// 0x31: USB跟踪状态寄存器 (UTRKSTS)
//       B7~B4=INTVCNT[3:0]: 间隔计数值
//       B3~B2=STS[1:0]: 跟踪状态
//       B1=TST_UTRK: 测试模式
//       B0=UTRK_RDY: 跟踪就绪标志
#define UTRKSTS     0x31

//==============================================================================
// 四、HID协议相关补充说明
//==============================================================================
// HID (Human Interface Device) 类设备使用中断传输来周期性上报数据。
// 典型的HID设备(如鼠标、键盘)的端点配置如下:
//
//   【端点0 (控制端点)】
//     方向: 双向(IN/OUT共用)
//     传输类型: 控制传输
//     用途: 枚举过程、获取HID报告描述符、设置空闲速率等
//     最大包长: 通常为 8 或 64 字节
//
//   【端点1 (中断IN端点)】
//     方向: IN(设备→主机)
//     传输类型: 中断传输
//     用途: 上报HID数据(鼠标移动/按键、键盘按键等)
//     最大包长: HID设备通常为 8 字节(如键盘8字节报告)
//     轮询间隔: 通常为 1ms~10ms(在端点描述符中指定)
//
//   【端点2 (中断OUT端点,可选)】
//     方向: OUT(主机→设备)
//     传输类型: 中断传输
//     用途: 接收主机下发的输出报告(如键盘LED指示灯状态)
//     最大包长: 通常为 8 字节
//
//   HID报告描述符定义了上报数据的格式和含义,例如:
//   - 键盘报告: 8字节(1字节修饰键 + 1字节保留 + 6字节按键码)
//   - 鼠标报告: 4字节(1字节按键 + 1字节X位移 + 1字节Y位移 + 1字节滚轮)
//==============================================================================

//==============================================================================
// 五、SIE寄存器访问宏(示例)
//==============================================================================
// STC的USB SIE寄存器通过间址方式访问,需要操作 USBADR 和 USBDAT 寄存器。
// 以下为访问示例(具体实现需根据实际代码调整):

// 写SIE寄存器: 将val写入SIE地址为addr的寄存器
// #define SIE_WRITE(addr, val)  do { USBADR = (addr); USBDAT = (val); } while(0)

// 读SIE寄存器: 返回SIE地址为addr的寄存器值
// #define SIE_READ(addr)        (USBADR = (addr), USBDAT)

// 选择端点: 设置INDEX寄存器选择目标端点
// #define SELECT_ENDP(n)        do { USBADR = INDEX; USBDAT = (n); } while(0)
// 其中 n=0~5 对应端点0~5

//==============================================================================
// 六、HID协议枚举流程简要说明
//==============================================================================
// 1. 主机检测到设备插入,复位总线(RSTIF中断)
// 2. 主机发送SETUP包(Get_Descriptor_Device),设备返回设备描述符
// 3. 主机再次复位总线,分配地址(Set_Address)
// 4. 主机获取配置描述符、接口描述符、HID描述符、端点描述符
// 5. 主机获取HID报告描述符(Get_Descriptor_Report)
// 6. 主机设置配置(Set_Configuration),设备进入配置状态
// 7. 主机周期性发送IN令牌,设备通过中断IN端点上报HID数据
//==============================================================================

主要完善内容说明:

  1. 补充了每个寄存器的位域详细说明 — 基于 STC8H 数据手册 "28 USB 2.0-FS通用串行总线" 章节中的寄存器表格,对每个寄存器的每一位功能进行了中文注释。
  2. 修正了地址注释 — 原帖中 #define CSR0 0x10 实际对应的是 INMAXP 地址(0x10),而 CSR0/INCSR1 的 SIE 地址为 0x11,已在注释中说明。
  3. 补充了 SIE 间址访问机制说明 — 解释了 STC USB 模块通过 USBADR + USBDAT 访问 SIE 内部寄存器的方式。
  4. 增加了 HID 协议背景说明 — 包括端点用途(控制端点/中断IN/中断OUT)、典型报告大小、枚举流程等。
  5. 补充了 INDEX 端点选择说明 — 解释了 INDEX[2:0] 与端点的对应关系,以及 INMAXP/CSR/FIFO 等寄存器受 INDEX 影响。
  6. 增加了复位值信息 — 如 INTRIN1E 复位值为 xx11,1111(端点中断默认使能),INCSR2 复位值 0010,0xxx(默认双缓冲模式)等。

🔍 搜索 "https://www.stcaimcu.com/thread-24784-1-1.html 帮完善注解"

📌 相关论坛讨论

  1. 为了51爱的大国工匠们优雅地点灯,DMA-单总线 优雅的点灯
  2. 字库生成 / 图片取模 新增工具, AiCube-ISP V6.96R 正式版发布,大家帮测试
  3. 8H/32G系列的PWM输出任意周期和任意占空比的波形

⏱️ 查找用时 13.2s | 总回答用时 65.0s

回复

使用道具 举报 送花

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|手机版|深圳国芯人工智能有限公司 ( 粤ICP备2022108929号-2 )

GMT+8, 2026-7-22 09:19 , Processed in 0.090244 second(s), 85 queries .

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

快速回复 返回顶部 返回列表