18344371880 发表于 2026-7-7 16:20:04

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

<blockquote>// 引入头文件同时也希望大家多指导指导

18344371880 发表于 2026-7-7 16:46:05

// 引入头文件
// #include <STC8H.H>
#include <STC8051U.H>
#include "intrins.h"



// 定义数据格式
typedef unsigned char   BYTE;        // 单字节
typedef unsigned int    WORD;        // 2字节
typedef unsigned long   DWORD;        // 4字节



// 定义USB的SIE的寄存器
#define FADDR       0                        // USB 功能地址寄存器
#define POWER       1         // USB 电源管理寄存器
#define INTRIN1   2         // USB 端点 IN 中断标志位
#define EP5INIF   0x20      // INTRIN1.5:端点 5 的 IN 中断标志位
#define EP4INIF   0x10      // INTRIN1.4:端点 4 的 IN 中断标志位
#define EP3INIF   0x08      // INTRIN1.3:端点 3 的 IN 中断标志位
#define EP2INIF   0x04      // INTRIN1.2:端点 2 的 IN 中断标志位
#define EP1INIF   0x02      // INTRIN1.1:端点 1 的 IN 中断标志位
#define EP0IF       0x01      // INTRIN1.0:端点 0 的 IN/OUT 中断标志位
#define INTROUT1    4         // USB 端点 OUT 中断标志位
#define EP5OUTIF    0x20      // INTROUT1.5:端点 5 的 OUT 中断标志位
#define EP4OUTIF    0x10      // INTROUT1.4:端点 4 的 OUT 中断标志位
#define EP3OUTIF    0x08      // INTROUT1.3:端点 3 的 OUT 中断标志位
#define EP2OUTIF    0x04      // INTROUT1.2:端点 2 的 OUT 中断标志位
#define EP1OUTIF    0x02      // INTROUT1.1:端点 1 的 OUT 中断标志位
#define INTRUSB   6         // USB 电源中断标志位
#define SOFIF       0x08      // INTRUSB.3:USB 帧起始信号中断标志
#define RSTIF       0x04      // INTRUSB.2:USB 复位信号中断标志
#define RSUIF       0x02      // INTRUSB.1:USB 恢复信号中断标志
#define SUSIF       0x01      // INTRUSB.0:USB 挂起信号中断标志
#define INTRIN1E    7         // USB 端点 IN 中断允许寄存器
#define EP5INIE   0x20      // INTRIN1E.5:端点 5 的 IN 中断控制位
#define EP4INIE   0x10      // INTRIN1E.4:端点 4 的 IN 中断控制位
#define EP3INIE   0x08      // INTRIN1E.3:端点 3 的 IN 中断控制位
#define EP2INIE   0x04      // INTRIN1E.2:端点 2 的 IN 中断控制位
#define EP1INIE   0x02      // INTRIN1E.1:端点 1 的 IN 中断控制位
#define EP0IE       0x01      // INTRIN1E.0:端点 0 的 IN/OUT 中断控制位
#define INTROUT1E   9         // USB 端点 OUT 中断允许寄存器
#define EP5OUTIE    0x20      // INTROUT1E.5:端点 5 的 OUT 中断控制位
#define EP4OUTIE    0x10      // INTROUT1E.4:端点 4 的 OUT 中断控制位
#define EP3OUTIE    0x08      // INTROUT1E.3:端点 3 的 OUT 中断控制位
#define EP2OUTIE    0x04      // INTROUT1E.2:端点 2 的 OUT 中断控制位
#define EP1OUTIE    0x02      // INTROUT1E.1:端点 1 的 OUT 中断控制位
#define INTRUSBE    11          // USB 电源中断允许寄存器
#define SOFIE       0x08      // INTRUSBE.3:USB 帧起始信号中断控制位
#define RSTIE       0x04      // INTRUSBE.2:USB 复位信号中断控制位
#define RSUIE       0x02      // INTRUSBE.1:USB 恢复信号中断控制位
#define SUSIE       0x01      // INTRUSBE.0:USB 挂起信号中断控制位
#define FRAME1      12          // USB 数据帧号寄存器
#define FRAME2      13          // USB 数据帧号寄存器
#define INDEX       14          // USB 端点索引寄存器
#define INMAXP      16          // IN 端点的最大数据包大小
#define CSR0      17          // USB 端点 0 控制状态寄存器
#define SSUEND      0x80                // CSR0.7:SETUP 结束事件处理完成标志
#define SOPRDY      0x40                // CSR0.6:OPRDY 事件处理完成标志
#define SDSTL       0x20                // CSR0.5:STALL 握手包回传
#define SUEND       0x10                // CSR0.4:SETUP 安装包结束标志
#define DATEND      0x08                // CSR0.3:数据发送/接收完成标志位
#define STSTL       0x04                // CSR0.2:STALL 信号发送完成标志
#define IPRDY       0x02                // CSR0.1:IN 数据包准备完成标志
#define OPRDY       0x01                // CSR0.0:OUT 数据包准备完成标志
#define INCSR1      17                        // IN 端点控制状态寄存器 1
#define INCLRDT   0x40                // INCSR1.6:复位 IN 数据切换位
#define INSTSTL   0x20                // INCSR1.5:STALL 信号发送完成标志
#define INSDSTL   0x10                // INCSR1.4:STALL 信号发送请求位
#define INFLUSH   0x08                // INCSR1.3:清除 IN 端点的 FIFO 的下一个数据包
#define INUNDRUN    0x04                // INCSR1.2:数据不足
#define INFIFONE    0x02                // INCSR1.1:IN 端点的 FIFO 非空标志
#define INIPRDY   0x01                // INCSR1.0:IN 数据包准备完成标志
#define INCSR2      18                        // IN 端点控制状态寄存器 2
#define INAUTOSET   0x80                // INCSR2.7:自动设置 IPRDY 标志控制位
#define INISO       0x40                // INCSR2.6:同步传输使能
#define INMODE        0x20                // INCSR2.5:端点方向选择位
#define INMODEIN        0x20                // INCSR2.5.1:选择端点方向为IN
#define INMODEOUT        0x00                // INCSR2.5.0:选择端点方向为OUT
#define INENDMA   0x10                // INCSR2.4:IN 端点的 DMA 控制,无效的
#define INFCDT      0x08                // INCSR2.3:强制 DATA0/DATA1 数据切换设置
#define OUTMAXP   19                        // OUT 端点的最大数据包大小
#define OUTCSR1   20                        // OUT 端点控制状态寄存器 1
#define OUTCLRDT    0x80                // OUTCSR1.7:复位 OUT 数据切换位
#define OUTSTSTL    0x40                // OUTCSR1.6:STALL 信号发送完成标志
#define OUTSDSTL    0x20                // OUTCSR1.5:STALL 信号发送请求位
#define OUTFLUSH    0x10                // OUTCSR1.4:清除 OUT 端点的 FIFO 的下一个数据包
#define OUTDATERR   0x08                // OUTCSR1.3:数据错误,该位只在ISO方式有效
#define OUTOVRRUN   0x04                // OUTCSR1.2:数据溢出,该位只在ISO方式有效
#define OUTFIFOFUL0x02                // OUTCSR1.1:OUT 端点的 FIFO 数据满标志
#define OUTOPRDY    0x01                // OUTCSR1.0:OUT 数据包接收完成标志
#define OUTCSR2   21
#define OUTAUTOCLR0x80                // OUTCSR1.7:自动清除OPRDY标志控制位
#define OUTISO      0x40                // OUTCSR1.6:同步传输使能
#define OUTENDMA    0x20                // OUTCSR1.5:OUT端点的DMA控制,无效的
#define OUTDMAMD    0x10                // OUTCSR1.4:设置OUT端点的DMA模式,无效的
#define COUNT0           22                        // USB 端点 0 的 OUT 长度
#define OUTCOUNT1   22                        // USB 端点的 OUT 长度
#define OUTCOUNT2   23                        // USB 端点的 OUT 长度
#define FIFO0       32                        // USB 端点 0 的 FIFO 数据访问寄存器
#define FIFO1       33                        // USB 端点 1 的 FIFO 数据访问寄存器
#define FIFO2       34                        // USB 端点 2 的 FIFO 数据访问寄存器
#define FIFO3       35                        // USB 端点 3 的 FIFO 数据访问寄存器
#define FIFO4       36                        // USB 端点 4 的 FIFO 数据访问寄存器
#define FIFO5       37                        // USB 端点 5 的 FIFO 数据访问寄存器
#define UTRKCTL   48                        // USB 跟踪控制寄存器,用不到的,适用于 USB 底层驱动调试
#define UTRKSTS   49                        // USB 跟踪状态寄存器,用不到的,适用于 USB 底层驱动调试



// 状态标志位定义
#define EPIDLE            0                // 等待新指令
#define EPSTATUS            1                // 解析新指令
#define EPDATAIN            2                // 正在发数据给主机
#define EPDATAOUT         3                // 正在从主机收数据
#define EPSTALL             -1                // 出错,端点被挂起



// 枚举阶段的各种请求
// 标准请求码(bRequest)
#define GET_STATUS          0x00        // 主机查询设备、接口或端点的状态
#define CLEAR_FEATURE       0x01        // 清除端点挂起的命令
#define SET_FEATURE         0x03        // 让某个端点进入挂起状态
#define SET_ADDRESS         0x05        // 给设备分配地址
#define GET_DESCRIPTOR      0x06        // 主机向设备请求描述符的
#define SET_DESCRIPTOR      0x07        // 主机请求设备修改某描述符,这操作很罕见,一般用不上
#define GET_CONFIG          0x08        // 每个接口是可以有多个备用设置的,比如我设置的同样接口下的鼠标,可以有两个备用设置,设置一是低功耗,设置二是电竞风格,不过大部分使用场景都仅仅只有一套设置的
#define SET_CONFIG          0x09        // 启动端点,代表着枚举阶段已经过了
#define GET_INTERFACE       0x0A        // 查询接口当前使用的是第几个备用设置
#define SET_INTERFACE       0x0B        // 主机命令某个接口切换到指定的备用设置
#define SYNCH_FRAME         0x0C        // 用于等时传输(又称:同步传输)的,获取等时端点的同步帧编号,约定从第几帧开始传数据


// HID 类请求码,基于 bRequest 字节衍生出来的,可以不要,在请求发来的时候再解析
#define GET_REPORT          0x01        // 强行通过端点 0 索要HID类设备(鼠标、键盘、手柄等)的特性报告,可以不理的,就回复STALL
#define GET_IDLE            0x02        // 和描述符里的轮询不一样,这个是主机问设备,如果数据没变化,设备最多可以偷懒多久才复报一次,一般是长按的场景
#define GET_PROTOCOL      0x03        // 获取HID设备的报告上传使用的是什么协议,0:引导协议,电脑的BIOS阶段就可以识别HID设备上传的报告,1:报告协议,就是有携带自定义数据的,这个情况下,有上位机的话,可以让HID设备玩的更花,比如电竞设备啥的
#define SET_REPORT          0x09        // 以键盘为例,在枚举阶段就先设置键盘的大小写指示灯、数字键盘指示灯等的开启与否状态
#define SET_IDLE            0x0A        // 设置空闲率,和GET_IDLE类似,主机告诉设备,如果长期没有按键按下,你希望多久才来上报一次
#define SET_PROTOCOL      0x0B        // 设置协议模式(报告协议或引导协议),一般是先有GET_PROTOCOL,然后主机为了保险起见,可能会发个这个包告诉设备,就使用哪种协议


// 描述符类型码,基于Get_Descriptor 请求时,通过 wValue 的高字节指明要哪一种描述符
#define DESC_DEVICE         0x01        // 请求设备描述符
#define DESC_CONFIG         0x02        // 请求配置描述符
#define DESC_STRING         0x03        // 请求字符串描述符
#define DESC_HIDREPORT      0x22        // 请求HID报告描述符


// 请求类型掩码(bmRequestType)
#define STANDARD_REQUEST    0x00        // 标准请求标志
#define CLASS_REQUEST       0x20        // 类请求标志
#define VENDOR_REQUEST      0x40        // 厂商请求标志
#define REQUEST_MASK      0x60        // 与bmRequestType字节做按位与运算,获得是标准请求、类请求或厂商请求的情况



// SETUP令牌包后携带的的DATA0数据包的8个字节数据的结构体
typedef struct
{
    BYTE    bmRequestType;
    BYTE    bRequest;
    BYTE    wValueL;
    BYTE    wValueH;
    BYTE    wIndexL;
    BYTE    wIndexH;
    BYTE    wLengthL;
    BYTE    wLengthH;
}SETUP;



// 管理端点 0 控制传输状态的结构体
typedef struct
{
    BYTE    bStage;                                        // 当前处于哪个阶段
    WORD    wResidue;                                // 还剩多少字节没传完,目前设备的端点基本都是 64 字节的,比如配置描述符,可能就会大于 64 字节,就需要设置标志位,分批往 FIFO0 传入数据
    BYTE    *pData;                                        // 当前要传输的数据指针,每发完一个包,pData 指针向后移动
}EP0STAGE;



// 函数声明
void UsbInit();
BYTE ReadReg(BYTE addr);
void WriteReg(BYTE addr, BYTE dat);
BYTE ReadFifo(BYTE fifo, BYTE *pdat);
void WriteFifo(BYTE fifo, BYTE *pdat, BYTE cnt);


// 描述符数组声明
char code DEVICEDESC;                        // 设备描述符
char code CONFIGDESC;                        // 配置描述符
char code HIDREPORTDESC;                // HID 报告描述符
char code LANGIDDESC;                        // 语言 ID 字符串描述符
char code MANUFACTDESC;                        // 厂商字符串描述符
char code PRODUCTDESC;                        // 产品字符串描述符


// 声明变量       
SETUP Setup;                                                // 标准请求的结构体变量
EP0STAGE Ep0Stage;                                        // 端点 0 控制传输状态的结构体变量
BYTE xdata HidFreature;                        // 特性报告缓冲区
BYTE xdata HidInput;                        // IN 报告缓冲区
BYTE xdata HidOutput;                        // OUT 报告缓冲区



// 函数定义
// 主函数
void main()
{
        // IO 端口初始化为准双向口
    P0M0 = 0x00;
    P0M1 = 0x00;
    P1M0 = 0x00;
    P1M1 = 0x00;
    P2M0 = 0x00;
    P2M1 = 0x00;
    P3M0 = 0x00;
    P3M1 = 0x00;
    P4M0 = 0x00;
    P4M1 = 0x00;
    P5M0 = 0x00;
    P5M1 = 0x00;

    UsbInit();                // USB 功能初始化

    EA = 1;                        // 打开总中断

    while (1);                // 需要循环执行的写这,目前不需要,但也必须要,避免程序跑飞
}


// 读取 USB 控制寄存器
BYTE ReadReg(BYTE addr)
{
    BYTE dat;
        // 如果 USBDAT 寄存器中的数据有效,则进入下一步,不然就在这反复查询
    while (USBADR & 0x80);
        // 启动 USB 的读操作
    USBADR = addr | 0x80;
        // 如果 USBDAT 寄存器中的数据有效,则进入下一步,不然就在这反复查询
    while (USBADR & 0x80);
        // 取出数据
    dat = USBDAT;
        // 返回数据
    return dat;
}


// 写入 USB 控制寄存器
void WriteReg(BYTE addr, BYTE dat)
{
        // 如果 USBDAT 寄存器中的数据有效,则进入下一步,不然就在这反复查询
    while (USBADR & 0x80);
        // 设置需读取的 USB 地址
    USBADR = addr & 0x7f;
        // 写入数据
    USBDAT = dat;
}


// 读取端点n的 FIFO 数据
BYTE ReadFifo(BYTE fifo, BYTE *pdat)
{
    BYTE cnt;
    BYTE ret;
        // 读取端点n的 OUT 长度,别看是叫 COUNT0 那是因为 COUNT0 和 OUTCOUNT1 都是一个地址,而且,针对于 0、1、2、3 端点,都够用了,如果是 4、5 则需要读取 OUTCOUNT2 为 数据
    ret = cnt = ReadReg(COUNT0);
        // 将 FIFO 里的数据逐个读出到指定地址,*pdat 是指针,指向的是存放的内存地址
    while (cnt--)
    {
      *pdat++ = ReadReg(fifo);
    }
        // 返回端点n的 OUT 长度
    return ret;
}


// 写入端点n的 FIFO 数据
void WriteFifo(BYTE fifo, BYTE *pdat, BYTE cnt)
{
        // 将数据逐个写入到指定地址端点 FIFO ,*pdat 是指针,指向的是存放原始数据的内存地址
        while (cnt--)
    {
      WriteReg(fifo, *pdat++);
    }
}


// USB 初始化
void UsbInit()
{
        // 将 USB 的 D+ 和 D- 引脚设置为高阻输入模式
    P3M0 = 0x00;
    P3M1 = 0x03;
        // 8H
    //USBCLK = 0xB0;                                // PLL 时钟源为 24Mhz,后续通过isp软件直接设置,使能 PLL 倍频和时钟追频
        // 32G
        IRC48MCR = 0x80;                        // 使用专门48Mhz时钟
    while (!(IRC48MCR & 0x01));        // 等待时钟稳定
    USBCLK = 0x00;
       
    USBCON = 0x90;                                // 使能 USB 功能和时钟,并使能上拉电阻,使主机将设备识别成全速设备

    WriteReg(FADDR, 0x00);                // 清空 USB 的设备地址寄存器
    WriteReg(POWER, 0x08);                // 产生异步 USB 复位
    WriteReg(INTRIN1E, 0x3f);        // 允许所有 IN 端点的中断
    WriteReg(INTROUT1E, 0x3f);        // 允许所有 OUT 端点的中断
    WriteReg(INTRUSBE, 0x00);        // 不允许帧起始、复位、恢复、挂起信号的中断
    WriteReg(POWER, 0x01);                // 使能挂起检测,当检测到总线上的挂起信号,USB 将进入挂起方式

    Ep0Stage.bStage = EPIDLE;        // 将端点 0 状态的 bStage 设置为:等待新指令
}


// USB 中断入口函数,USB 的中断号为 25
void usb_isr() interrupt 25
{
    BYTE intrusb;
    BYTE intrin;
    BYTE introut;
    BYTE csr;
    BYTE cnt;
    WORD len;

    intrusb = ReadReg(INTRUSB);                        // 获取 USB 电源中断标志位并赋值
    intrin = ReadReg(INTRIN1);                        // 获取 USB 端点 IN 中断标志位并赋值
    introut = ReadReg(INTROUT1);                // 获取 USB 端点 OUT 中断标志位并赋值
       
    if (intrusb & RSTIF)                                // 如果是 USB 复位信号中断
    {       
                // 因为只启动了端点 1,所以才复位端点 1 而已,启用几个就复位几个
      WriteReg(INDEX, 1);                                // 选择端点 1
      WriteReg(INCSR1, INCLRDT);                // 将 IN 端点数据切换位复位,即复位到 data0 数据包
      WriteReg(INDEX, 1);                                // 选择端点 1
      WriteReg(OUTCSR1, OUTCLRDT);        // 将 OUT 端点数据切换位复位,即复位到 data0 数据包
      Ep0Stage.bStage = EPIDLE;                // 将端点 0 状态的 bStage 设置为:等待新指令
    }

    if (intrin & EP0IF)                                                // 如果是端点 0 的 IN/OUT 中断
    {       
      WriteReg(INDEX, 0);                                        // 切换回端点 0
      csr = ReadReg(CSR0);                                // 获取 USB 端点 0 控制状态寄存器并赋值
      if (csr & STSTL)                                        // 如果是 STALL 发送完成
      {
            WriteReg(CSR0, csr & ~STSTL);        // 则对该位进行清 0,说明已知道,该 SETUP 包设备不支持,而且已经发送了 STALL,接下来就是等待主机的下一个 SETUP 包了
            Ep0Stage.bStage = EPIDLE;                // 将端点 0 状态设置为:等待新指令
      }
      if (csr & SUEND)                                        // 如果 SETUP 包是无数据阶段的控制请求或者是被新的 SETUP 包强制中止
      {
            WriteReg(CSR0, csr | SSUEND);        // 清除标志位
      }

      switch (Ep0Stage.bStage)                        // 查询端点 0 控制传输当前处于哪个阶段
      {
      case EPIDLE:                                                                                                // 等待新指令阶段
            if (csr & OPRDY)                                                                                // 如果是主机发来 OUT 包
            {
                Ep0Stage.bStage = EPSTATUS;                                                        // 将端点 0 状态设置为:解析新指令
                ReadFifo(FIFO0, (BYTE *)&Setup);                                        // 将 SETUP 包的数据阶段的 8 字节数据读取出来并赋值给结构体
                ((BYTE *)&Ep0Stage.wResidue) = Setup.wLengthH;        // 将端点 0 状态的 wResidue 设置为:SETUP 包的 wLength 域的(所需要传输的字节数)
                ((BYTE *)&Ep0Stage.wResidue)= Setup.wLengthL;        // 将端点 0 状态的 wResidue 设置为:SETUP 包的 wLength 域的(所需要传输的字节数)
                switch (Setup.bmRequestType & REQUEST_MASK)                        // 将 SETUP 包的数据阶段的第一个字节(bmRequestType)数据取出,判断请求特性
                {
                case STANDARD_REQUEST:                                                                // 如果是标准请求
                  switch (Setup.bRequest)                                                        // 将 SETUP 包的数据阶段的第二个字节(bRequest)数据取出,判断请求代码
                  {
                  case SET_ADDRESS:                                                                // 如果是给设备分配地址
                        WriteReg(FADDR, Setup.wValueL);                                // 将 SETUP 包的数据阶段的第三个字节(wValue 的低字节)数据取出写入 USB 功能地址寄存器
                        break;
                  case SET_CONFIG:                                                                // 如果是启动端口
                        WriteReg(INDEX, 1);                                                        // 选择端点 1
                        WriteReg(INCSR2, INMODEIN);                                        // 设置端点为方向为 IN
                        WriteReg(INMAXP, 8);                                                // 端点最大数据包大小为 8 * 8 = 64 字节
                        WriteReg(INDEX, 1);                                                        // 选择端点 1
                        WriteReg(INCSR2, INMODEOUT);                                // 设置端点为方向为 OUT
                        WriteReg(OUTMAXP, 8);                                                // 端点最大数据包大小为 8 * 8 = 64 字节
                        WriteReg(INDEX, 0);                                                        // 切换回端点 0
                        break;
                  case GET_DESCRIPTOR:                                                        // 如果是主机向设备请求描述符
                        Ep0Stage.bStage = EPDATAIN;                                        // 将端点 0 状态的 bStage 设置为:正在发数据给主机
                        switch (Setup.wValueH)                                                // 将 SETUP 包的数据阶段的第四个字节(wValue 的高字节)数据取出,判断是什么描述符类型
                        {
                        case DESC_DEVICE:                                                        // 如果是设备描述符
                            Ep0Stage.pData = DEVICEDESC;                        // 将端点 0 状态的 pData 指针设置为设备描述符数组的起始位置
                            len = sizeof(DEVICEDESC);                                // 计算设备描述符数组大小
                            break;
                        case DESC_CONFIG:                                                        // 如果是配置描述符
                            Ep0Stage.pData = CONFIGDESC;            // 将端点 0 状态的 pData 指针设置为配置描述符数组的起始位置
                            len = sizeof(CONFIGDESC);               // 计算配置描述符数组大小
                            break;
                        case DESC_STRING:                                                        // 如果是字符串描述符
                            switch (Setup.wValueL)                  // 将 SETUP 包的数据阶段的第三个字节(wValue 的低字节)数据取出,判断是哪个字符串描述符ID,没有强制要求,不过一般的顺序是:语言 ID、厂商、产品名、序列号。。。,语言 ID固定是 0 ,其他1,2,3是可以在描述符里定义的
                            {                                       // 计算字符串描述符数组大小
                            case 0:                                                                        // 如果是获取语言 ID 字符串描述符
                              Ep0Stage.pData = LANGIDDESC;                // 将端点 0 状态的 pData 指针设置为语言 ID 字符串描述符数组的起始位置
                              len = sizeof(LANGIDDESC);         // 计算语言 ID 字符串描述符数组大小
                              break;
                            case 1:                                                                        // 如果是获取厂商字符串描述符
                              Ep0Stage.pData = MANUFACTDESC;      // 将端点 0 状态的 pData 指针设置为厂商字符串描述符数组的起始位置
                              len = sizeof(MANUFACTDESC);         // 计算厂商字符串描述符数组大小
                              break;
                            case 2:                                                                        // 如果是获取产品名字符串描述符
                              Ep0Stage.pData = PRODUCTDESC;       // 将端点 0 状态的 pData 指针设置为产品名字符串描述符数组的起始位置
                              len = sizeof(PRODUCTDESC);          // 计算产品名字符串描述符数组大小
                              break;
                            default:                                                                // 如果都不是
                              Ep0Stage.bStage = EPSTALL;                        // 将端点 0 状态的 bStage 设置为:出错,端点挂起
                              break;
                            }
                            break;
                        case DESC_HIDREPORT:                                                // 如果是获取 HID 报告描述符
                            Ep0Stage.pData = HIDREPORTDESC;                        // 将端点 0 状态的 pData 指针设置为 HID 报告描述符数组的起始位置
                            len = sizeof(HIDREPORTDESC);            // 计算 HID 报告描述符数组大小
                            break;
                        default:                                                                        // 如果都不是
                            Ep0Stage.bStage = EPSTALL;            // 将端点 0 状态的 bStage 设置为:出错,端点挂起
                            break;
                        }
                        if (len < Ep0Stage.wResidue)                                // 对需要发送的描述符数组长度与端点 0 状态的 wResidue 的字节数进行判断,如果所需要传输的字节数小于端点 0 状态的 wResidue 的字节数
                        {
                            Ep0Stage.wResidue = len;                                // 则将端点 0 状态的 wResidue 的字节数设置为所需要传输的字节数
                        }
                        break;
                  default:                                                                                // 如果都不是
                        Ep0Stage.bStage = EPSTALL;                  // 将端点 0 状态的 bStage 设置为:出错,端点挂起
                        break;
                  }
                  break;
                case CLASS_REQUEST:                                                                        // 如果是类请求
                  switch (Setup.bRequest)                                                        // 将 SETUP 包的数据阶段的第二个字节(bRequest)数据取出,判断请求代码
                  {
                  case GET_REPORT:                                                                // 如果是请求获取设备特性报告
                        Ep0Stage.pData = HidFreature;                                // 将端点 0 状态的 pData 指针设置为特性报告缓冲区数组的起始位置
                        Ep0Stage.bStage = EPDATAIN;               // 将端点 0 状态的 bStage 设置为:正在发数据给主机
                        break;
                  case SET_REPORT:                                                                // 如果是主机要求设备设置端点的一些特性
                        Ep0Stage.pData = HidFreature;                                // 将端点 0 状态的 pData 指针设置为特性报告缓冲区数组的起始位置
                        Ep0Stage.bStage = EPDATAOUT;                                // 将端点 0 状态的 bStage 设置为:正在从主机收数据
                        break;
                  case SET_IDLE:                                                                        // 如果是设置空闲率,让主机等待超时,不作回应
                        break;
                  case GET_IDLE:                                                                        // 如果是获取空闲率,让主机等待超时,不作回应
                  case GET_PROTOCOL:                                                                // 如果是获取报告上传协议,让主机等待超时,不作回应,不设置默认就是引导协议
                  case SET_PROTOCOL:                                                                // 如果是读取报告上传协议,让主机等待超时,不作回应
                  default:                                                                                // 如果都不是
                        Ep0Stage.bStage = EPSTALL;                                        // 将端点 0 状态的 bStage 设置为:出错,端点挂起
                        break;                                    
                  }
                  break;
                default:                                                                                        // 如果都不是
                  Ep0Stage.bStage = EPSTALL;                                                // 将端点 0 状态的 bStage 设置为:出错,端点挂起
                  break;                                          
                }

                switch (Ep0Stage.bStage)                                                        // 判断端点 0 状态的 bStage 的状态标志位
                {
                case EPDATAIN:                                                                                // 如果是正在发数据给主机
                  WriteReg(CSR0, SOPRDY);                                                        // 置位事件完成标志
                  goto L_Ep0SendData;                                                                // 跳转至 L_Ep0SendData 其实位置,其实这是伪代码,目前的篇幅,是指跳转至 477 行的位置
                  break;
                case EPDATAOUT:                                                                                // 如果是正在从主机收数据
                  WriteReg(CSR0, SOPRDY);                                                        // 置位事件完成标志
                  break;
                case EPSTATUS:                                                                                // 如果是正在解析新指令
                  WriteReg(CSR0, SOPRDY | DATEND);                                // 置位事件完成标志,数据结束:接收完最后一个数据包
                  Ep0Stage.bStage = EPIDLE;                                                // 将端点 0 状态的 bStage 设置为:等待新指令
                  break;
                case EPSTALL:                                                                                // 如果是出错,端点被挂起
                  WriteReg(CSR0, SOPRDY | SDSTL);                                        // 置位事件完成标志,结束当前的数据传输
                  Ep0Stage.bStage = EPIDLE;                                                // 将端点 0 状态的 bStage 设置为:等待新指令
                  break;
                }
            }
            break;
      case EPDATAIN:                                                                                                                // 如果是正在发数据给主机
            if (!(csr & IPRDY))                                                                                                // 且如果 IN 数据包准备完成,且已发送
            {
L_Ep0SendData:
                cnt = Ep0Stage.wResidue > 64 ? 64 : Ep0Stage.wResidue;                // 判断端点 0 状态的 wResidue 的字节数是否大于 64 字节,如果大于则设置本次发送字节数为 64 字节,否则仍按端点 0 状态的 wResidue 的字节数发送
                WriteFifo(FIFO0, Ep0Stage.pData, cnt);                                                // 往端点 0 的 FIFO 数据访问寄存器写入数据
                Ep0Stage.wResidue -= cnt;                                                                        // 完成后对总字节数减去已发送的字节数
                Ep0Stage.pData += cnt;                                                                                // 然后将端点 0 状态的 pData 指针偏移对应的字节数
                if (Ep0Stage.wResidue == 0)                                                                        // 如果还剩字节数为 0 了
                {
                  WriteReg(CSR0, IPRDY | DATEND);                                                        // 置位 IN 数据包准备完成标志,数据结束:发送完最后一个数据包
                  Ep0Stage.bStage = EPIDLE;                                                                // 将端点 0 状态的 bStage 设置为:等待新指令
                }
                else                                                                                                                // 否则
                {
                  WriteReg(CSR0, IPRDY);                                                                        // 置位 IN 数据包准备完成标志
                }
            }
            break;
      case EPDATAOUT:                                                                                                                // 如果是正在从主机收数据
            if (csr & OPRDY)                                                                                                // 且如果 OUT 数据包准备完成,即收满了
            {
                cnt = ReadFifo(FIFO0, Ep0Stage.pData);                                                // 将端点 0 的 FIFO 数据访问寄存器的值按端点 0 状态的 pData 指针的起始位置开始写出,并同时获取总字节数
                Ep0Stage.wResidue -= cnt;                                                                        // 端点 0 状态的 wResidue 的字节数减去已发送的字节数
                Ep0Stage.pData += cnt;                                                                                // 然后将端点 0 状态的 pData 指针偏移对应的字节数
                if (Ep0Stage.wResidue == 0)                                                                        // 如果还剩字节数为 0 了
                {
                  WriteReg(CSR0, SOPRDY | DATEND);                                                // 置位事件完成标志,数据结束:接收完最后一个数据包
                  Ep0Stage.bStage = EPIDLE;                                                                // 将端点 0 状态的 bStage 设置为:等待新指令
                }
                else                                                                                                                // 否则
                {
                  WriteReg(CSR0, SOPRDY);                                                                        // 置位事件完成标志
                }
            }
            break;
      }
    }

    if (intrin & EP1INIF)                                                // 如果是端点 1 的 IN 中断
    {
      WriteReg(INDEX, 1);                                                // 选择端点 1
      csr = ReadReg(INCSR1);                                        // 读取 IN 端点控制状态寄存器 1
      if (csr & INSTSTL)                                                // 如果是 STALL 信号发送完成标志
      {
            WriteReg(INCSR1, INCLRDT);                        // 将 IN 端点数据切换位复位,即复位到 data0 数据包
      }
      if (csr & INUNDRUN)                                                // 如果是回复了 NAK 信号标志位
      {
            WriteReg(INCSR1, 0);                                // 清零标志位
      }
    }

    if (introut & EP1OUTIF)                                                // 如果是端点 1 的 OUT 中断
    {
      WriteReg(INDEX, 1);                                                // 选择端点 1
      csr = ReadReg(OUTCSR1);                                        // 读取 OUT 端点控制状态寄存器 1
      if (csr & OUTSTSTL)                                                // 如果是 STALL 信号发送完成标志
      {
            WriteReg(OUTCSR1, OUTCLRDT);                // 将 OUT 端点数据切换位复位,即复位到 data0 数据包
      }
      if (csr & OUTOPRDY)                                                // 当有 OUT 数据包到来标志位
      {
            ReadFifo(FIFO1, HidOutput);                        // 将数据包读取到 OUT 报告缓冲区数组
            WriteReg(OUTCSR1, 0);                                // 清零标志位

            WriteReg(INDEX, 1);                                        // 选择端点 1
            WriteFifo(FIFO1, HidOutput, 64);        // 将数据写入 IN 端点,目前这个仅仅就是将接收到的数据又原路返回了回去
            WriteReg(INCSR1, INIPRDY);                        // 置数据写入完成标志位
      }
    }
}



// 设备描述符
char code DEVICEDESC =
{
    0x12,       //bLength(18);                                描述符长度为 18 个字节
    0x01,       //bDescriptorType(Device);        描述符类型为设备描述符
    0x00,0x02,//bcdUSB(2.00);                                USB 版本为 2.0
    0x00,       //bDeviceClass(0);                        设备类,均不声明,所以为0
    0x00,       //bDeviceSubClass0);                子类,均不声明,所以为0
    0x00,       //bDeviceProtocol(0);                协议,均不声明,所以为0
    0x40,       //bMaxPacketSize0(64);                控制传输,即端点 0 最大字节数为 64 字节
    0x54,0x53,//idVendor(5354);                        厂商 ID 为 0x5354
    0x80,0x43,//idProduct(4380);                        产品 ID 为 0x4380
    0x00,0x01,//bcdDevice(1.00);                        设备固件版本号为1.00,这个无所谓的
    0x01,       //iManufacturer(1);                        厂商名称的字符串索引 ID 是 1
    0x02,       //iProduct(2);                                产品名称的字符串索引 ID 是 2
    0x00,       //iSerialNumber(0);                        没有序列号字符串
    0x01,       //bNumConfigurations(1);        设备只有 1 套功能配置
};


// 配置描述符
char code CONFIGDESC =
{
    0x09,       //bLength(9);                                                        描述符长度为 9 字节
    0x02,       //bDescriptorType(Configuration);                描述符类型为配置描述符
    0x29,0x00,//wTotalLength(41);                                                配置描述符集合总长度为 41 字节
    0x01,       //bNumInterfaces(1);                                        该配置下只有 1 个接口
    0x01,       //bConfigurationValue(1);                                该配置的值为 1
    0x00,       //iConfiguration(0);                                        该配置没有字符串描述符
    0x80,       //bmAttributes(BUSPower);                                该配置为总线供电方式
    0x32,       //MaxPower(100mA);                                                供电电流 100mA
        // 接口描述符
    0x09,       //bLength(9);
    0x04,       //bDescriptorType(Interface);                        描述符类型为接口描述符
    0x00,       //bInterfaceNumber(0);                                        接口编号为 0
    0x00,       //bAlternateSetting(0);                                        备用设置编号为 0
    0x02,       //bNumEndpoints(2);                                                该接口带 2 个业务端点(不含端点 0)
    0x03,       //bInterfaceClass(HID);                                        接口类别为 HID 人机接口类
    0x01,       //bInterfaceSubClass(1);                                接口子类为支持 BIOS 引导启动的子类
    0x00,       //bInterfaceProtocol(0);                                接口协议不声明(自定义HID),可选,0x01 是键盘, 0x02 是鼠标
    0x00,       //iInterface(0);                                                该接口没有字符串描述符
        // HID 类专属描述符
    0x09,       //bLength(9);                                                        描述符长度为 9 字节
    0x21,       //bDescriptorType(HID);                                        描述符类型为 HID 类描述符
    0x01,0x01,//bcdHID(1.01);                                                        遵循 HID 1.01 规范
    0x00,       //bCountryCode(0);                                                无国家地区编码
    0x01,       //bNumDescriptors(1);                                        附带 1 个子描述符(报告描述符)
    0x22,       //bDescriptorType(HID Report);                        子描述符类型为 HID 报告描述符
    0x1b,0x00,//wDescriptorLength(27);                                报告描述符总长度为 27 字节
        // 端点 1 IN 描述符
    0x07,       //bLength(7);                                                        描述符长度为 7 字节
    0x05,       //bDescriptorType(Endpoint);                        描述符类型为端点描述符
    0x81,       //bEndpointAddress(EndPoint1 as IN);        端点号为 1,方向为 IN
    0x03,       //bmAttributes(Interrupt);                                传输类型为中断传输
    0x40,0x00,//wMaxPacketSize(64);                                        单包最大字节数为 64 字节
    0x01,       //bInterval(1ms);                                                主机轮询间隔为 1ms
        // 端点 1 OUT 描述符,键盘才需要这个描述符,如果是鼠标则不需要,可删除,删除后记得调整配置描述的 wTotalLength 和接口描述符的 bLength 及 bNumEndpoints
    0x07,       //bLength(7);                                                        描述符长度为 7 字节
    0x05,       //bDescriptorType(Endpoint);                        描述符类型为端点描述符
    0x01,       //bEndpointAddress(EndPoint1 as OUT);        端点号为 1,方向为 OUT
    0x03,       //bmAttributes(Interrupt);                                传输类型为中断传输
    0x40,0x00,//wMaxPacketSize(64);                                        单包最大字节数为 64 字节
    0x01,       //bInterval(1ms);                                                主机轮询间隔为 1ms
};


// HID 报告描述符
char code HIDREPORTDESC =
{
    0x05,0x0c,//USAGE_PAGE(Consumer);                                        用法页:消费设备类
    0x09,0x01,//USAGE(Consumer Control);                                用法:消费控制
    0xa1,0x01,//COLLECTION(Application);                                开启一个应用功能集合
    0x15,0x00,//LOGICAL_MINIMUM(0);                                        数据逻辑最小值为 0
    0x25,0xff,//LOGICAL_MAXIMUM(255);                                数据逻辑最大值为 255
    0x75,0x08,//REPORT_SIZE(8);                                                每个字段占 8 位(1 字节)
    0x95,0x40,//REPORT_COUNT(64);                                        单份报告有 64 个字段(共 64 字节)
    0x09,0x01,//USAGE(Consumer Control);                        绑定用法:消费控制
    0xb1,0x02,//FEATURE(Data,Variable);                                定义 Feature 特性报告(控制通道读写)
    0x09,0x01,//USAGE(Consumer Control);                        绑定用法:消费控制
    0x81,0x02,//INPUT(Data,Variable);                                定义 Input 输入报告(中断 IN 上报)
    0x09,0x01,//USAGE(Consumer Control);                        绑定用法:消费控制
    0x91,0x02,//OUTPUT(Data,Variable);                                定义 Output 输出报告(中断 OUT 下发)
    0xc0,       //END_COLLECTION;                                                结束功能集合
};


// 语言 ID 字符串描述符
char code LANGIDDESC =
{
    0x04,0x03,                                // 描述符长度为 4 字节,描述符类型为字符串描述符
    0x09,0x04,                                // 支持语言:英语(美国)
};


// 厂商字符串描述符
char code MANUFACTDESC =
{
    0x08,0x03,                                // 描述符长度为 8 字节,描述符类型为字符串描述符
    'S',0,
    'T',0,
    'C',0,
};


// 产品字符串描述符
char code PRODUCTDESC =
{
    0x1e,0x03,                                // 描述符长度为 30 字节,描述符类型为字符串描述符
    'S',0,
    'T',0,
    'C',0,
    ' ',0,
    'U',0,
    'S',0,
    'B',0,
    ' ',0,
    'D',0,
    'e',0,
    'v',0,
    'i',0,
    'c',0,
    'e',0,
};

蜗牛 发表于 2026-7-7 16:34:01

赞一个,好详细{:qiang:}

神农鼎 发表于 2026-7-7 16:41:40

帮直接贴出来

18344371880 发表于 2026-7-7 16:46:34

神农鼎 发表于 2026-7-7 16:41
帮直接贴出来

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

奥古斯朱 发表于 2026-7-7 17:42:20

好人呐。这下难度降低了

18344371880 发表于 2026-7-7 18:07:32

奥古斯朱 发表于 2026-7-7 17:42
好人呐。这下难度降低了

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

神农鼎 发表于 2026-7-7 21:04:08


神农鼎 发表于 2026-7-7 21:04:37

<p>现在我已经获取了完整的 USB SIE 寄存器手册资料。让我结合手册内容,对原帖中的 HID 协议注释代码进行完善注解。</p>
<hr />
<p>以下是基于 <strong>STC8H / Ai8H 系列数据手册</strong>(章节 <strong>&quot;28 USB 2.0-FS通用串行总线&quot;</strong>)对原帖代码的完善注解版本。原帖作者已做了很好的基础注释,我在此基础上补充了每个寄存器的位域详细说明、SIE 间址访问机制说明以及 HID 协议相关的描述符背景。</p>
<pre><code class="language-c">//==============================================================================
// 文件名: STC_USB_HID_注释参考.h
// 说明:   基于 STC8H/Ai8H 系列数据手册 USB 章节,对 HID 协议相关
//          USB SIE 寄存器进行详细注解,供学习和参考。
// 参考手册: STC8H 数据手册 &quot;28 USB 2.0-FS通用串行总线&quot;
// 原作者:   183443*** (STC论坛)
// 完善注解: STC FAE
//==============================================================================

//==============================================================================
// 一、头文件引入
//==============================================================================
// #include &lt;STC8H.H&gt;          // STC8H系列头文件(如使用STC8H)
#include &lt;STC8051U.H&gt;          // Ai8051U系列头文件(如使用Ai8051U)
#include &quot;intrins.h&quot;         // 内置函数库(_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: 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 + FRAME2 = 11位帧号(0~2047)
//       主机每1ms发送一个SOF包,内含递增的帧号。
#define FRAME1      0x0C
#define FRAME2      0x0D

// 0x0E: USB端点索引寄存器 (INDEX)
//       用于选择当前操作的端点,后续的INMAXP/CSR/FIFO等寄存器
//       均指向INDEX所选的端点。
//       INDEX:
//         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 INCSR10x11    // 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,端点0收到的OUT数据字节数
//       【INDEX≠0时】OUTCOUNT1: OUTCNT,OUT数据长度低8位
#define COUNT0      0x16

// 0x17: OUT端点OUT长度高字节 (OUTCOUNT2)
//       B2~B0 = OUTCNT,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: 滤波模式选择
//       B5~B4=INTV: 间隔时间选择
//       B3=ENST5: 使能端点5跟踪
//       B2~B0=RES: 分辨率选择
//       复位值: 1011,1011
#define UTRKCTL   0x30

// 0x31: USB跟踪状态寄存器 (UTRKSTS)
//       B7~B4=INTVCNT: 间隔计数值
//       B3~B2=STS: 跟踪状态
//       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数据
//==============================================================================
</code></pre>
<p><strong>主要完善内容说明:</strong></p>
<ol>
<li><strong>补充了每个寄存器的位域详细说明</strong> — 基于 STC8H 数据手册 &quot;28 USB 2.0-FS通用串行总线&quot; 章节中的寄存器表格,对每个寄存器的每一位功能进行了中文注释。</li>
<li><strong>修正了地址注释</strong> — 原帖中 <code>#define CSR0 0x10</code> 实际对应的是 INMAXP 地址(0x10),而 CSR0/INCSR1 的 SIE 地址为 0x11,已在注释中说明。</li>
<li><strong>补充了 SIE 间址访问机制说明</strong> — 解释了 STC USB 模块通过 USBADR + USBDAT 访问 SIE 内部寄存器的方式。</li>
<li><strong>增加了 HID 协议背景说明</strong> — 包括端点用途(控制端点/中断IN/中断OUT)、典型报告大小、枚举流程等。</li>
<li><strong>补充了 INDEX 端点选择说明</strong> — 解释了 INDEX 与端点的对应关系,以及 INMAXP/CSR/FIFO 等寄存器受 INDEX 影响。</li>
<li><strong>增加了复位值信息</strong> — 如 INTRIN1E 复位值为 <code>xx11,1111</code>(端点中断默认使能),INCSR2 复位值 <code>0010,0xxx</code>(默认双缓冲模式)等。</li>
</ol>
<hr />
<p><a href="https://www.stcaimcu.com/plugin.php?id=twpx_xunsearch&amp;mod=forum&amp;q=https%3A%2F%2Fwww.stcaimcu.com%2Fthread-24784-1-1.html%20%E5%B8%AE%E5%AE%8C%E5%96%84%E6%B3%A8%E8%A7%A3&amp;f=_all&amp;m=yes&amp;syn=yes&amp;s=relevance&amp;fid=0">🔍 搜索 &quot;https://www.stcaimcu.com/thread-24784-1-1.html 帮完善注解&quot;</a></p>
<p><strong>📌 相关论坛讨论</strong></p>
<ol>
<li><a href="https://www.stcaimcu.com/forum.php?mod=viewthread&amp;tid=11626&amp;highlight=https%3A%2F%2Fwww.stcaimcu.com%2Fthread-24784-1-1.html%20%E5%B8%AE%E5%AE%8C%E5%96%84%E6%B3%A8%E8%A7%A3&amp;page=3&amp;extra=#pid117980">为了51爱的大国工匠们优雅地点灯,DMA-单总线 优雅的点灯</a></li>
<li><a href="https://www.stcaimcu.com/forum.php?mod=viewthread&amp;tid=6714&amp;highlight=https%3A%2F%2Fwww.stcaimcu.com%2Fthread-24784-1-1.html%20%E5%B8%AE%E5%AE%8C%E5%96%84%E6%B3%A8%E8%A7%A3&amp;page=4&amp;extra=#pid58580">字库生成 / 图片取模 新增工具, AiCube-ISP V6.96R 正式版发布,大家帮测试</a></li>
<li><a href="https://www.stcaimcu.com/forum.php?mod=viewthread&amp;tid=297&amp;highlight=https%3A%2F%2Fwww.stcaimcu.com%2Fthread-24784-1-1.html%20%E5%B8%AE%E5%AE%8C%E5%96%84%E6%B3%A8%E8%A7%A3&amp;page=1&amp;extra=#pid1592">8H/32G系列的PWM输出任意周期和任意占空比的波形</a></li>
</ol>
<p>⏱️ 查找用时 13.2s | 总回答用时 65.0s</p>
页: [1]
查看完整版本: 我将数据手册的HID协议注释了一遍,供大家参考