- // 引入头文件
- // #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 数据帧号寄存器[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;
- // 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)[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,
- };
复制代码
|