对技术执着
分类: LINUX
2015-03-14 15:43:46
3.2 固件在USB设备设别阶段的编程思路
一般地,USB设备接口芯片会产生一些中断来通知程序员特定事件的发生。譬如说,EP0(缺省控制端点) SETUP包的到达,EP0 IN或OUT事务的发生等等。控制传输是分三个阶段的:建立阶段,数据阶段,状态阶段。所以对于一次控制传输,设备固件必须要正确控制其执行流程,不能颠倒。当收到EP0 SETUP包到达的信息之后,固件要分析其请求的具体内容,这里假定为读取描述符,然后进入数据阶段向主机发送相应描述符的具体内容,发送完成后,进入状态阶段。状态阶段结束后,一次控制传输就此完成。要注意的是,就算是进入各个阶段,也要等待主机发送事务请求后才能响应具体操作。也就是,假定固件分析了EP0 SETUP包得到主机的请求是读取某个描述符,固件随后应该进入数据阶段,但只是流程逻辑上的进入,具体的操作还要等待主机的控制IN令牌到达后,才能开始数据阶段真正的数据传输,之后进入状态阶段。一般地,状态阶段只需设定一个寄存器通知芯片开始状态阶段即可,无需干预其细节。主机对设备的识别最初是通过控制管道来进行的,一系列控制传输(主机识别设备的请求)完成之后,主机就能识别到USB设备了,在设备管理器中会显示出来(但是不一定能完全正常地使用设备,因为可能还有一些协议并未完成,例如Mass Storage设备还需对SCSI命令正确响应,文档的第3部分会有具体讲述)。下面举一个例子说明固件处理控制传输的思路,当然实际应用中并不限于这样的思路。
这个例子的思路是,在响应USB产生的中断时,会用全局变量记录下中断的发生,然后在主循环里面进行具体处理。
/* USB服务程序伪代码*/
void USB_Service(void)
{
/*处理控制传输的3个阶段*/
switch (EP.EP0.Stage)
{
case C_STAGE_EP0_SETUP: /*处在建立阶段*/
if (!USB_Setup ()) /*如果请求是被支持的*/
EP.EP0.Stage = C_STAGE_EP0_DATA; /*转入数据阶段*/
break;
case C_STAGE_EP0_DATA: /*处在数据阶段*/
if (EP.EP0.Status == C_STATUS_EP0_IN_NACK) /*收到了IN令牌*/
{
USB_WriteEP0FIFO(); /*通过控制端点发送数据给主机*/
EP.EP0.Status = C_STATUS_RESET; /*已处理完毕,所以复位此状态*/
EP.EP0.Stage = C_STAGE_EP0_STATUS; /*转入状态阶段*/
重新使能EP0_IN_NACK中断; /*在ISR中会关掉此中断*/
}
break;
case C_STAGE_EP0_STATUS:
使能EP0_STATUS寄存器;
break;
default:
break;
}
}
/*USB中断服务程序伪代码(部分)*/
void USB_ISR(void)
{
if (SETUP包到达)
{
清中断;
EP.EP0.Stage = C_STAGE_EP0_SETUP;
EP.EP0.Status = C_STATUS_EP0_SETUP_ARRIVAL;
}
else if (EP0 IN令牌到达但是芯片自动回复了NAK)
{
清中断;
关闭此中断;
EP.EP0.Status = C_STATUS_EP0_IN_NACK;
}
}
/*USB控制传输的建立阶段处理程序伪代码*/
int USB_Setup (void)
{
通过各寄存器的值来得到请求的类型和相关数据;
if (请求类型是读取描述符)
{
switch (描述符值)
{
case设备描述符值:
将全局的发送数据的指针指向设备描述符buffer;
break;
......
default: break;
}
}
else if (设备还需处理的其他请求)
{
处理;
}
else /*不支持的请求*/
{
发送STALL信号;
return 1; /*返回错误*/
}
return 0; /*返回正确*/
}
/*通过端点0发送数据*/
void USB_WriteEP0FIFO(void)
{
取得全局的发送数据的指针;
利用指针读取描述符的数据并填充至端点0的FIFO;
通知芯片EP0 IN数据包已准备好;
}