// 引入头文件
#include "STC8H.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 数据帧号寄存器[7:0]
#define FRAME2      13          // USB 数据帧号寄存器[10:8]
#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 OUTFIFOFUL  0x02		// OUTCSR1.1：OUT 端点的 FIFO 数据满标志
#define OUTOPRDY    0x01		// OUTCSR1.0：OUT 数据包接收完成标志
#define OUTCSR2     21
#define OUTAUTOCLR  0x80		// 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 长度[7:0]
#define OUTCOUNT2   23			// USB 端点的 OUT 长度[10:8]
#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[18];			// 设备描述符
char code CONFIGDESC[41];			// 配置描述符
char code HIDREPORTDESC[27];		// HID 报告描述符
char code LANGIDDESC[4];			// 语言 ID 字符串描述符
char code MANUFACTDESC[8];			// 厂商字符串描述符
char code PRODUCTDESC[30];			// 产品字符串描述符


// 声明变量	
SETUP Setup;						// 标准请求的结构体变量
EP0STAGE Ep0Stage;					// 端点 0 控制传输状态的结构体变量
BYTE xdata HidFreature[64];			// 特性报告缓冲区
BYTE xdata HidInput[64];			// IN 报告缓冲区
BYTE xdata HidOutput[64];			// 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 为 [10:8] 数据
    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;

    USBCLK = 0xB0;				// PLL 时钟源为 24Mhz，后续通过isp软件直接设置，使能 PLL 倍频和时钟追频
    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)[0] = Setup.wLengthH;	// 将端点 0 状态的 wResidue[15:8] 设置为：SETUP 包的 wLength 域的（所需要传输的字节数）
                ((BYTE *)&Ep0Stage.wResidue)[1]= Setup.wLengthL;	// 将端点 0 状态的 wResidue[7:0] 设置为：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[18] =
{
    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[41] =
{
    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[27] =
{
    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[4] =
{
    0x04,0x03,				// 描述符长度为 4 字节，描述符类型为字符串描述符
    0x09,0x04,
};


// 厂商字符串描述符
char code MANUFACTDESC[8] =
{
    0x08,0x03,				// 描述符长度为 8 字节，描述符类型为字符串描述符
    'S',0,
    'T',0,
    'C',0,
};


// 产品字符串描述符
char code PRODUCTDESC[30] =
{
    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,
};
