安卓设备的USB-HID通讯例程的开发 (4)
- HID设备对三大主要事务的处理(C代码)
本博文系JGB联合商务组的原创作品,引用请标明出处
在HID设备一侧,完整地理解C代码对三大主要事务(SETUP事务, IN事务, OUT事务)的处理是很重要的,本通讯例程的C源码均涉及到对这三个事务的处理。
由MCD应用团队提供的USB函数库有如下5个文件:
- usb基础支持函数: usb.h / usb.c
- usb各种描述符声明: usb_desc.h / usb_desc.c
- usb各种中断入口定义: usb_it.c
其中usb.h的头文件定义十分简练明了,库内的文件个数也只有五个,因此深受USB开发者的喜爱,其中重点代码是完成一次正确传送的回调函数:CTR_CallBack(),
这里的正确传送指的是当前收到或发送的包是正确的,包主要有三种:
令牌包,数据包,握手包。每当完成这三种包之一的正确发送或正确接收都会触发一次这个中断回调。
另外,这里的CTR_CallBack()函数中的正确传送(CT)是针对包来说,不要和前面的写NFC卡某个单元时提到的完整传输混为一谈了。传输 - 事务 - 包的分解及合成关系中,传输处于最顶层,而包处于最底层,另外不同的包用不同包标识符PID区分。
- 令牌包(总是OUT方向)分三种:
- 1. SETUP令牌包(SETUP事务用),端点0处理。
- 2. IN令牌包(非0端点的IN事务用,SIE硬件层面处理, C代码无处理)
- 3. OUT令牌包(非0端点的OUT事务用,SIE硬件层面处理, C代码无处理)
- 数据包按方向细分为两种: DATA_OUT包 , DATA_IN包。
- 握手包按方向也细分为两种 : STATUS_OUT包 , STATUS_IN包。
IN事务和OUT事务中的握手包也在SIE硬件层面处理, C代码无处理。
SETUP事务中的三个阶段的包都是有方向的,对于有数据阶段的SETUP事务可划分为如下两种情形:
(1) 请求设备接收: SETUP令牌 -> DATA OUT ->STATUS IN
(其中的 DATA OUT ->STATUS IN 可以重复进行多次)
( 图4-A )
(2)请求设备发送: SETUP令牌 -> DATA IN ->STATUS OUT
(其中的 DATA IN ->STATUS OUT 可以重复进行多次,例如主机请求某些超过64字节长度的HID报告描述符即Report_Descriptor时就是这样的情形)
( 图4-B )
其中的OUT表示主机发出的包,IN表示设备发出的包,当然令牌包总是由主机发出的。
只有理解了上述的每个阶段的包方向,才能顺着它的流程指向在如下的C代码中分别找到其相应的处理方式。
CTR_CallBack()正确传送回调函数(由MCD应用团队提供),代码很简短,所以我会尽可能给出了每行的注释。
理解此段代码前需先了解如下几个由MCD应用团队声明的全局变量
//这两个变量标记端点0接下来【应该】是处于哪种状态(根据下一个包的流程指向而定,应准确修改它们),
//当前正确传送处理完成后要恢复之。
u16 SaveRxStatus =0 ;
u16 SaveTxStatus=0;
//SETUP事务三个阶段,三种包(都有方向)
//端点0的接收包有三种: SETUP包 , DATA_OUT包 , STATUS_OUT包
//端点0的发送包有两种: DATA_IN包 , STATUS_IN包
//这个变量标记事务【即将】进入哪一个阶段(包加上WAIT_ 前缀即表示将要进入哪一个阶段)
//例如: WAIT_DATA_OUT 表示端点0即将进入等待主机发来数据包的阶段。
u8 ControlStatus=0;
//SETUP令牌包的指针形式
SETUP_DATA *token;
//端点0的临时接收区
u8 EP0_RxBuf[64]={
0};
以下是CTR_CallBack()回调函数:
//完成一次正确的传送,CTR即正确传送寄存器之意
void CTR_CallBack(void)
{
//存放端点号
u16 EpNum;
//存放此端点号对应的端点寄存器值
u16 EpReg;
//临时短整数变量
u16 ix=0 ;
//取得端点号,ISTR中断状态寄存器的低四位是端点号,所以要掩码0x000f
EpNum = (*(u16 *)ISTR & 0x000f);
if(EpNum == 0)
{
//开始处理端点0完成的一次正确传送
SaveRxStatus = GetEPRxStatus(EP0);
SaveTxStatus = GetEPTxStatus(EP0);
//立即设置繁忙标记,处理完后才解除
SetEpTxStatus(EP0,EP_TX_NAK);
SetEpRxStatus(EP0,EP_RX_NAK);
//方向检查: 如果ISTR的bit4位为1,则EP_CTR_RX=1 表示为OUT方向 ,有包从主机流向设备
//例如: SETUP令牌,EP0收到数据(DATA_OUT),收到握手(STATUS_OUT) 这三个包都是OUT方向.
//反之,若ISTR的bit4位为0,则EP_CTR_TX=1 , 表示为IN方向,有包从设备流向主机
//例如: EP0发送数据(DATA_IN),发送握手(STATUS_IN) 这两个包都是IN方向.
if(*(u16 *)ISTR & ISTR_DIR)
{
// 这里开始单片机EP0的OUT方向处理
// 附注: 不要随意在EP0的SETUP包的处理中加入printf(),否则很可能会造成无法识别设备!!!!!!!!
// 先检查是否为SETUP令牌, 若不是那么就是EP0的: DATA_OUT 或STATUS_OUT
if((*(u16 *)EP0REG) & EP_SETUP)
{
//处理SETUP令牌包
//先清除正确接收
Clr_CTR_Rx(EP0);
//EP0接收缓冲区已经存放了SETUP令牌包,这里把它暂存到EP0_RxBuf临时接收区
GetEpRxBuf(EP0_RxBuf,EP0);
//以指针形式取回令牌包
token = (SETUP_DATA *)&EP0_RxBuf[0];
//开始检查此SETUP令牌包的各种特定标准请求 或 特定类/自定义类请求
if(token->bRequest == GET_DESCRIPTOR )
{
//处理有关: 获取描述符的标准请求
Do_GetDescriptor();
} //GET_DESCRIPTOR ... end
else if(token->bRequest == SET_ADDRESS)
{
//设置HID设备地址的标准请求
//接下来应该 : 等待本设备发出握手包
ControlStatus = WAIT_STATUS_IN;
//这句标记设置的方式有典型意义!!!
//同时放进设置地址的标记,也就是说当设备完成本次的握手动作后,将直接完成对本设备的地址设定,
//附注: 设置地址这个标准请求是没有数据阶段的,要设置的地址就放在SETUP令牌的wValue字段里
//参见后面的 IN 方向的 STATUS_IN 判定
ControlStatus |= WAIT_SET_ADDRESS;
//完成本设备发出握手包的标准动作(后面的代码还有很多次这样的动作): 发送的字节长度为0并令发送有效
SetEP0TxCount(0);
SaveTxStatus = EP_TX_VALID;
} //SET_ADDRESS ... end
else if(token->bRequest == GET_CONFIGURATION)
{
//MCD应用团队提供的源码即为如此: 此处为空,有待研讨。
} // GET_CONFIGURATION ... end
else if(token->bRequest == SET_CONFIGURATION)
{
//此阶段很重要:到了这里,整个设备的枚举就完成了,设备已处于配置完成状态,可以进行USB通讯了
//这里EP0只需发出握手包即可
ControlStatus = WAIT_STATUS_IN;
SetEP0TxCount(0);
SaveTxStatus = EP_TX_VALID;
} // SET_CONFIGURATION ... end
else if(token->bRequest == CLEAR_FEATURE)
{
//处理有关: 清除特性的标准请求
Do_ClearFeature();
} // CLEAR_FEATURE ... end
else if(token