USB的“Virtual_COM_Port”例程分析--枚举--获取设备描述符
二. GET_DESCRIPTOR:主机获取描述符

- 建立事务阶段
主机进入控制传输的第一阶段:建立事务--发送setup令牌包,发送请求数据包,设备发送ACK包。
主机对地址0、端点0发出SETUP令牌包,首先端点0寄存器的第11位SETUP位置位,表明收到了setup令牌包。由于此时端点0数据接收有效,所以接下来主机的请求数据包被SIE保存到端点0描述符表的RxADDR里面,收到的字节数保存到RxCount里面。
端点0寄存器的CTR_RX被置位为1,ISTR的CTR置位为1,DIR=1,EP_ID=0,表示端点0接收到主机来的请求数据。此时设备已经ACK主机,将触发正确传输完成中断。接下来是建立事务阶段的整体流程图:

1.1 ISTR事件中断服务程序USB_Istr()
#if (IMR_MSK & ISTR_CTR) /* Correct TRansfer (clear-only bit)正确传输中断 */ if (wIstr & ISTR_CTR & wInterrupt_Mask) {//正确传输中断,协议相关的数据处理 ,硬件使端点为 NAK 状态,让程序有足够时间 /* servicing of the endpoint correct transfer interrupt */ /* clear of the CTR flag into the sub */ CTR_LP();//正确传输时的处理函数
1.2 低优先级中断CTR_LP()
接收到主机发送的SETUP令牌包,所以进入Setup0_Process()来处理SETUP包
void CTR_LP(void) { __IO uint16_t wEPVal = 0; /* stay in loop while pending interrupts */ //如果有传输正确的中断将继续处理直到处理完成,该位只读,USB_EpnR 寄存器相关中断挂起位清除时才会清除 while (((wIstr = _GetISTR()) & ISTR_CTR) != 0) { /* extract highest priority endpoint number 提取高优先级的端点*/ EPindex = (uint8_t)(wIstr & ISTR_EP_ID); //读出端点ID if (EPindex == 0) //如果是端点0成功传输 ,控制传输,有三个阶段,建立阶段,数据阶段(可能有),状态阶段 { /* Decode and service control endpoint interrupt */ /* calling related service routine */ /* (Setup0_Process, In0_Process, Out0_Process) */ /* save RX & TX status */ /* and set both to NAK */ /* 保存当前的状态,在后面处理中根据情况选择是否重新设置状态 */ SaveRState = _GetENDPOINT(ENDP0); //获取端点0的状态寄存器值 SaveTState = SaveRState & EPTX_STAT; //保存发送状态位 SaveRState &= EPRX_STAT; //保存接受状态位 /* 硬件自动设置为NAK 注意:硬件只会在相应方向上设置为NAK,另一方向是不会进行设置的,所以之前的的保存是有用的 */ _SetEPRxTxStatus(ENDP0,EP_RX_NAK,EP_TX_NAK);//设定发送和接收状态为NAK,此时设备就会一直以NAK回应主机,使设备有足够的时间处理数据 /* DIR bit = origin of the interrupt */
else { /* DIR = 1 */ /* DIR = 1 & CTR_RX => SETUP or OUT int :DIR = 1时,是输出中断*/ /* DIR = 1 & (CTR_TX | CTR_RX) => 2 int pending */ wEPVal = _GetENDPOINT(ENDP0); //获取端点0的状态 /* 令牌包:SETUP 数据包:DATA0 握手包:ACK (程序运行到这里,说明已经确认数据正确,在确认数据正确时已经由硬件自动发送了) */ /* 如果数据错误,则会丢弃数据且不返回握手包程序不会到这里 */ if ((wEPVal &EP_SETUP) != 0) // 如果接收的是控制阶段数据 { _ClearEP_CTR_RX(ENDP0); /* SETUP bit kept frozen while CTR_RX = 1 */ Setup0_Process(); //处理SETUP事件 //程序会进入到这个函数里面 /* before terminate set Tx & Rx status */ _SetEPRxTxStatus(ENDP0,SaveRState,SaveTState);//将处理后的状态重新进行设置 return; }
1.3 处理主机发来的SETUP包Setup0_Process()
先取得设备端点0接收缓冲区地址,然后从地址中读取主机发送过来的SETUP包,根据SETUP包中wLength数据发现设备需要向主机发送数据,因此进入Data_Setup0()。
uint8_t Setup0_Process(void) {//处理主机发来的Setup包 union { uint8_t* b; uint16_t* w; } pBuf; #if defined STM32F303xE || defined STM32F302x8 uint16_t offset = 0; pBuf.b = (uint8_t *)( PMAAddr + _GetEPRxAddr(ENDP0)); #else uint16_t offset = 1; pBuf.b = PMAAddr + (uint8_t *)(_GetEPRxAddr(ENDP0) * 2); /* *2 for 32 bits addr 这是取得端点0接收缓冲区的起始地址*/ //PMAAddr是包缓冲区起始地址,_GetEPRxAddr(ENDP0)获得端点0描述符表里的接收缓冲区地址。 //乘以2的原因是,描述符里地址项为16位,使用的是相对偏移 #endif if (pInformation->ControlState != PAUSE)//如果不处于暂停状态,那么获取主机发送setup包数据 { pInformation->USBbmRequestType = *pBuf.b++; /* bmRequestType 请求类型,表示数据传输方向 请求类型 请求的接收者 */ pInformation->USBbRequest = *pBuf.b++; /* bRequest 标准请求及代码 */ pBuf.w += offset; /* word not accessed because of 32 bits addressing */ pInformation->USBwValue = ByteSwap(*pBuf.w++); /* wValue */ //regs.c ByteSwap() pBuf.w += offset; /* word not accessed because of 32 bits addressing */ pInformation->USBwIndex = ByteSwap(*pBuf.w++); /* wIndex */ //regs.c ByteSwap() pBuf.w += offset; /* word not accessed because of 32 bits addressing */ pInformation->USBwLength = *pBuf.w; /* wLength */ } pInformation->ControlState = SETTING_UP;//设置控制状态为 SETTING_UP设置 if (pInformation->USBwLength == 0) { /* 不需要向主机发送数据的处理 */ NoData_Setup0(); } else { /* 需要向主机发送数据的处理 */ Data_Setup0(); } return Post0_Process(); }
1.4 需要向主机发送数据Data_Setup0()
根据SETUP包中得到标准请求为获取设备描述符,通过偏移来取出设备描述符,然后判断是否成功取得描述符。接着根据SETUP包来确定传输方向为设备向主机,根据传输的数据大小得出设备向主机发送一个数据包。最后进入描述符的输出准备DataStageIn()中。
void Data_Setup0(void) { uint8_t *(*CopyRoutine)(uint16_t); RESULT Result; uint32_t Request_No = pInformation->USBbRequest; uint32_t Related_Endpoint, Reserved; uint32_t wOffset, Status; CopyRoutine = NULL; //这是一个函数指针,由用户提供 wOffset = 0; /*GET DESCRIPTOR 获得描述符*/ if (Request_No == GET_DESCRIPTOR) { //如果是标准请求并且接收者是设备 if (Type_Recipient == (STANDARD_REQUEST | DEVICE_RECIPIENT)) { uint8_t wValue1 = pInformation->USBwValue1;//得到描述符类型 if (wValue1 == DEVICE_DESCRIPTOR) //如果是获取设备描述符 { CopyRoutine = pProperty->GetDeviceDescriptor;//得到设备描述符函数 }
进入prop.c中
/******************************************************************************* * Function Name : Virtual_Com_Port_GetDeviceDescriptor. * Description : Gets the device descriptor.获取设备描述符 * Input : Length. * Output : None. * Return : The address of the device descriptor. *******************************************************************************/ uint8_t *Virtual_Com_Port_GetDeviceDescriptor(uint16_t Length) { return Standard_GetDescriptorData(Length, &Device_Descriptor); }
//通过偏移来取出描述符号 uint8_t *Standard_GetDescriptorData(uint16_t Length, ONE_DESCRIPTOR *pDesc) { uint32_t wOffset; wOffset = pInformation->Ctrl_Info.Usb_wOffset;//得到偏移 if (Length == 0) { pInformation->Ctrl_Info.Usb_wLength = pDesc->Descriptor_Size - wOffset;//得到需要发送的数据长度 return 0; } return pDesc->Descriptor + wOffset; //通过偏移来取出 }
返回Data_Setup0()
if (CopyRoutine) //判断是否取得描述符 { pInformation->Ctrl_Info.Usb_wOffset = wOffset; pInformation->Ctrl_Info.CopyData = CopyRoutine;//将函数指针传入到结构体变量中 /* sb in the original the cast to word was directly */ /* now the cast is made step by step */ (*CopyRoutine)(0); //得到发送数据大小 Result = USB_SUCCESS; }
if (ValBit(pInformation->USBbmRequestType, 7))//D7表示数据传输方向:设备向主机 { /* Device ==> Host :设备到主机*/ __IO uint32_t wLength = pInformation->USBwLength; //要发送的数据 //这个一般是64
else if (pInformation->Ctrl_Info.Usb_wLength < pInformation->USBwLength)//如果实际的数据长度小于请求的数据长度 { if (pInformation->Ctrl_Info.Usb_wLength < pProperty->MaxPacketSize)//可以一个包发送出去 { Data_Mul_MaxPacketSize = FALSE; }
pInformation->Ctrl_Info.PacketSize = pProperty->MaxPacketSize;//只有这一次确定了控制端点的发送包大小 DataStageIn(); //调用这个函数实现描述符的输出准备
1.5 实现描述符的输出准备DataStageIn()
因为此时后数据传输,对此与最大包长进行比较,得到此时控制状态为LAST_IN_DATA。设备开始确定发送地址,发送送字节的数目,更新后续包的数据长度,数据偏移。最后使能发送端点 只要主机的IN令牌包一来 设备就会将描述符返回给主机。
Length = pEPinfo->PacketSize;//最大发送字节数//得到数据包大小 64字节 //要发送的数据小于等于最大发送大小,则处于 LAST_IN_DATA 18字节<64字节 ControlState = LAST_IN_DATA ControlState = (save_wLength <= Length) ? LAST_IN_DATA : IN_DATA; if (Length > save_wLength)//如果要发送的数据小于最大包长 { Length = save_wLength;//就按照实际大小发送 } //否则以一个包的最大数据量进行发送 //以下是主要执行代码: DataBuffer = (*pEPinfo->CopyData)(Length); //得到要发送的数据地址(前面已经确定了发送数据的大小),这里共18个字节 UserToPMABufferCopy(DataBuffer, GetEPTxAddr(ENDP0), Length); //将要发送的数据放到发送缓冲区中 SetEPTxCount(ENDP0, Length); //设置发送字节的数目:18 pEPinfo->Usb_wLength -= Length;//更新需要的数据长度//Usb_wLength等于0 pEPinfo->Usb_wOffset += Length;//更新最新包数据偏移//偏移到18 vSetEPTxStatus(EP_TX_VALID);//使能发送端点 只要主机的IN令牌包一来 SIE就会将描述符返回给主机 /* 设置端点为接收有效 */ USB_StatusOut();/* Expect the host to abort the data IN stage :使能接收也有效,主机可以取消IN*/ Expect_Status_Out: pInformation->ControlState = ControlState;//保存控制状态 }
1.6 设置端点为接收有效USB_StatusOut()
//这样设置是为了将所有的数据都处理完了才可真正使能发送和接收 #define vSetEPRxStatus(st) (SaveRState = st) #define vSetEPTxStatus(st) (SaveTState = st) /* IN令牌包后发送0长度的数据,表明这次数据传输过程结束 */ #define USB_StatusIn() Send0LengthData() /* OUT令牌包后在数据传输结束后设置端点为接收有效,保持数据接收的持续性 */ #define USB_StatusOut() vSetEPRxStatus(EP_RX_VALID)
2. 输入事务(数据阶段)
获取描述符的控制传输进入第二阶段,主机首先发送一个IN令牌包,由于端点0发送有效,SIE将数据返回主机。 主机方返回一个ACK后,主机发送数据的CTR标志置位,DIR = 0,EP_ID = 0,表明主机正确收到了用户发过去的描述符。固件程序由此进入中断。
此时是由IN引起的。主要是调用In0_Process()完成剩下的工作。

低优先级中断CTR_LP()
if ((wIstr & ISTR_DIR) == 0) //如果是IN令牌包,数据被取走。也可以通过判断端点发送状态位 EP_CTR_TX 进行判断 {/* 这里需要处理数据阶段和状态阶段的数据 ,如果没有数据需要发送(也就是说发送了 IN 却没有数据),那么直接返回 NAK 就行了*/ /* DIR = 0 */ /* DIR = 0 => IN int :DIR = 0时,是输入中断*/ /* DIR = 0 implies that (EP_CTR_TX = 1) always */ _ClearEP_CTR_TX(ENDP0); //清除发送标志,同时 ISTR_CTR 位也会清除 //在这里会设置发送的数据大小和接收发送状态SaveRState\SaveTState In0_Process(); //处理IN事件 /* before terminate set Tx & Rx status */ _SetEPRxTxStatus(ENDP0,SaveRState,SaveTState);//将处理后的状态重新进行设置 return; }
2.1 处理主机发来的IN包In0_Process()
因为此时控制状态为LAST_IN_DATA,所以直接进入DataStageIn() 。
uint8_t In0_Process(void) { uint32_t ControlState = pInformation->ControlState; //有数据发送,继续发送数据,或者已经发送完成了 if ((ControlState == IN_DATA) || (ControlState == LAST_IN_DATA))//进入到这里 { DataStageIn();//第一次取设备描述符只取一次 当前的状态变为WAIT_STATUS_OUT //表明设备等待状态过程 主机输出0字节 /* ControlState may be changed outside the function */ ControlState = pInformation->ControlState; }
2.2 控制传输的发送数据阶段DataStageIn()
因为在1.4中已经得出一个数据包发送,Data_Mul_MaxPacketSize = FALSE。所以控制状态成为WAIT_STATUS_OUT,关闭设备的发送端点。
void DataStageIn(void) {/* 有可能数据已经发送完成了,主机没有发送状态数据,而是继续发送了 IN 令牌包,那么设备的状态设为 WAIT_STATUS_OUT,也就是说端点的反应为 NAK */ /* 此时当主机收到 NAK 握手包时就不会再发 IN 令牌包过来了,而是发送OUT令牌包,同时把 0 字节数据发送过来了 */ ENDPOINT_INFO *pEPinfo = &pInformation->Ctrl_Info; //端点信息保存在指针变量中 uint32_t save_wLength = pEPinfo->Usb_wLength; //得到字符的长度 uint32_t ControlState = pInformation->ControlState; //得到当前的状态 uint8_t *DataBuffer; uint32_t Length; /* 如果要发送的数据长度为0并且是最后一次数据,也就是说数据都发送完了 */ if ((save_wLength == 0) && (ControlState == LAST_IN_DATA)) {//程序运行到这里说明数据都发送完了,这里分两种情况:整包发送和非整包发送,两种处理情况不一样
else //字符的长度比数据包要小 {//数据已经发送完 /* No more data to send so STALL the TX Status*/ ControlState = WAIT_STATUS_OUT;//发送完成了,等待接收状态数据包 vSetEPTxStatus(EP_TX_STALL);//设置为STALL,既然已经发送完了,发送端点就可以停止了 } goto Expect_Status_Out;//因为数据发送完成了,那么直接可以结束了 }
3.输出事务(状态阶段)
主机收到18字节描述符后,进入状态事务过程,此时过程的令牌包为OUT,字节数为0,只需要用户返回一个ACK。所以中断处理程序会进入Out0_Process()。

低优先级中断CTR_LP()
/* 令牌包:OUT 数据包:DATA1/DATA0 */ else if ((wEPVal & EP_CTR_RX) != 0)//接收到的是数据包,有可能是有数据的包,也可能是0字节的数据包 {/* 这里需要处理数据阶段和状态阶段的数据 */ _ClearEP_CTR_RX(ENDP0); //清除正确接收标志位 Out0_Process(); //处理OUT事件 /* before terminate set Tx & Rx status */ _SetEPRxTxStatus(ENDP0,SaveRState,SaveTState); return; }
3.1 处理主机发来的OUT包Out0_Process()
//由于此时状态为WAIT_STATUS_OUT,所以执行下面代码 else if (ControlState == WAIT_STATUS_OUT) //之前在等待状态包 {/* 程序运行到这里,说明状态包已经发送过来 */ (*pProperty->Process_Status_OUT)();//状态包发送过来后的处理函数,也可以不处理,什么都不做 ControlState = STALLED; //状态变成了终止发送和接受 }
返回Post0_Process()
3.2 暂停端点的发送和接收Post0_Process()
uint8_t Post0_Process(void) { SetEPRxCount(ENDP0, Device_Property.MaxPacketSize);//设置最大接收包为64字节 if (pInformation->ControlState == STALLED) { vSetEPRxStatus(EP_RX_STALL); vSetEPTxStatus(EP_TX_STALL);//将端点0的发送和接收都设置为:STALL,这样只接收SETUP令牌包。 } return (pInformation->ControlState == PAUSE); }

浙公网安备 33010602011771号