我将数据手册的HID协议注释了一遍,供大家参考
<blockquote>// 引入头文件同时也希望大家多指导指导 // 引入头文件// #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,
};
赞一个,好详细{:qiang:} 帮直接贴出来 神农鼎 发表于 2026-7-7 16:41
帮直接贴出来
贴出来了,刚刚不知道为什么,贴上去没反应 好人呐。这下难度降低了 奥古斯朱 发表于 2026-7-7 17:42
好人呐。这下难度降低了
这个还不齐的哦,首先报告描述符是不齐的,然后,端点字节也不需要那么大,8字节就可以了,还有一些上报的也还要处理好,不过现在插进去,是能正确通讯了
<p>现在我已经获取了完整的 USB SIE 寄存器手册资料。让我结合手册内容,对原帖中的 HID 协议注释代码进行完善注解。</p>
<hr />
<p>以下是基于 <strong>STC8H / Ai8H 系列数据手册</strong>(章节 <strong>"28 USB 2.0-FS通用串行总线"</strong>)对原帖代码的完善注解版本。原帖作者已做了很好的基础注释,我在此基础上补充了每个寄存器的位域详细说明、SIE 间址访问机制说明以及 HID 协议相关的描述符背景。</p>
<pre><code class="language-c">//==============================================================================
// 文件名: STC_USB_HID_注释参考.h
// 说明: 基于 STC8H/Ai8H 系列数据手册 USB 章节,对 HID 协议相关
// USB SIE 寄存器进行详细注解,供学习和参考。
// 参考手册: STC8H 数据手册 "28 USB 2.0-FS通用串行总线"
// 原作者: 183443*** (STC论坛)
// 完善注解: STC FAE
//==============================================================================
//==============================================================================
// 一、头文件引入
//==============================================================================
// #include <STC8H.H> // STC8H系列头文件(如使用STC8H)
#include <STC8051U.H> // Ai8051U系列头文件(如使用Ai8051U)
#include "intrins.h" // 内置函数库(_nop_()等)
//==============================================================================
// 二、基础数据类型定义
//==============================================================================
typedef unsigned char BYTE; // 单字节(8位),对应USB的1字节数据
typedef unsigned int WORD; // 2字节(16位),对应USB描述符中的长度字段等
typedef unsigned long DWORD;// 4字节(32位),对应USB描述符中的地址/能力字段等
//==============================================================================
// 三、USB SIE(串行接口引擎)寄存器定义
//==============================================================================
// STC 的 USB 模块通过 SIE 寄存器组进行控制,这些寄存器通过 USBADR/USBDAT
// 间址方式访问。SIE 内部寄存器地址空间为 0x00~0x31。
// 访问方法: 将目标SIE寄存器地址写入 USBADR,然后读/写 USBDAT。
//==============================================================================
// ---- 3.1 基础控制寄存器 (SIE地址: 0x00 ~ 0x0F) ----
// 0x00: USB功能地址寄存器 (FADDR)
// 主机在枚举过程中通过 SET_ADDRESS 请求分配的设备地址。
// B7=UPDATE: 地址更新标志,写1加载新地址
// B6~B0=UADDR: 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 数据手册 "28 USB 2.0-FS通用串行总线" 章节中的寄存器表格,对每个寄存器的每一位功能进行了中文注释。</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&mod=forum&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&f=_all&m=yes&syn=yes&s=relevance&fid=0">🔍 搜索 "https://www.stcaimcu.com/thread-24784-1-1.html 帮完善注解"</a></p>
<p><strong>📌 相关论坛讨论</strong></p>
<ol>
<li><a href="https://www.stcaimcu.com/forum.php?mod=viewthread&tid=11626&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&page=3&extra=#pid117980">为了51爱的大国工匠们优雅地点灯,DMA-单总线 优雅的点灯</a></li>
<li><a href="https://www.stcaimcu.com/forum.php?mod=viewthread&tid=6714&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&page=4&extra=#pid58580">字库生成 / 图片取模 新增工具, AiCube-ISP V6.96R 正式版发布,大家帮测试</a></li>
<li><a href="https://www.stcaimcu.com/forum.php?mod=viewthread&tid=297&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&page=1&extra=#pid1592">8H/32G系列的PWM输出任意周期和任意占空比的波形</a></li>
</ol>
<p>⏱️ 查找用时 13.2s | 总回答用时 65.0s</p>
页:
[1]