STM32实现HID和u盘复合设备
USB设备可以定义一个复合设备,复合设备分两种,一种是一个设备多个配置,还有一种是一个配置多个接口,在本例中采用一个配置多个接口的方式
首先修改设备描述符,标准设备描述符和报告描述符都不需要修改,只需要修改配置描述符即可
//usb配置描述符 const u8 DinkUsbConfigDescriptor[DINK_USB_SIZ_CONFIG_DESC] = { /***************配置描述符***********************/ USB_CONFIGUARTION_DESC_SIZE, //bLength字段。配置描述符的长度为9字节。 USB_CONFIGURATION_DESCRIPTOR_TYPE, //bDescriptorType字段。配置描述符编号为0x02。 //wTotalLength字段。配置描述符集合的总长度, //包括配置描述符本身、接口描述符、类描述符、端点描述符等。 WBVAL( USB_CONFIGUARTION_DESC_SIZE + //配置描述符 USB_INTERFACE_DESC_SIZE + //接口1描述符 9 + //hid描述符 USB_ENDPOINT_DESC_SIZE + //端点描述符 USB_ENDPOINT_DESC_SIZE + //端点描述符 USB_INTERFACE_DESC_SIZE + //接口描述符2 USB_ENDPOINT_DESC_SIZE + //端点描述符1 USB_ENDPOINT_DESC_SIZE //端点描述符2 ), 0x02, //bNumInterfaces字段。该配置包含的接口数,复合设备,两个接口。 0x01, //bConfiguration字段。该配置的值为1。 0x00, //iConfigurationz字段,该配置的字符串索引。这里没有,为0。 USB_CONFIG_BUS_POWERED , //bmAttributes字段,该设备的属性 USB_CONFIG_POWER_MA(500), //bMaxPower字段,该设备需要的最大电流量 /*********************第一个接口描述符,hid设备**********************/ USB_INTERFACE_DESC_SIZE, //bLength字段。接口描述符的长度为9字节。 USB_INTERFACE_DESCRIPTOR_TYPE, //bDescriptorType字段。接口描述符的编号为0x04。 0x00, //bInterfaceNumber字段。该接口的编号,第一个接口,编号为0。 0x00, //bAlternateSetting字段。该接口的备用编号,为0。 0x02, //bNumEndpoints字段。非0端点的数目。该接口有2个批量端点 USB_DEVICE_CLASS_HUMAN_INTERFACE, //bInterfaceClass字段。该接口所使用的类。大容量存储设备接口类的代码为0x08。, 0x00, //bInterfaceSubClass字段。该接口所使用的子类。在HID1.1协议中, //只规定了一种子类:支持BIOS引导启动的子类。 //USB键盘、鼠标属于该子类,子类代码为0x01。 //但这里我们是自定义的HID设备,所以不使用子类。 0x00, //bInterfaceProtocol字段。如果子类为支持引导启动的子类, //则协议可选择鼠标和键盘。键盘代码为0x01,鼠标代码为0x02。 //自定义的HID设备,也不使用协议。 0x00, //iConfiguration字段。该接口的字符串索引值。这里没有,为0。 /*********************HID报告描述符*************************/ //bLength字段。本HID描述符下只有一个下级描述符。所以长度为9字节。 0x09, //bDescriptorType字段。HID描述符的编号为0x21。 0x21, //bcdHID字段。本协议使用的HID1.1协议。注意低字节在先。 0x10, 0x01, //bCountyCode字段。设备适用的国家代码,这里选择为美国,代码0x21。 0x21, //bNumDescriptors字段。下级描述符的数目。我们只有一个报告描述符。 0x01, //bDescriptorType字段。下级描述符的类型,为报告描述符,编号为0x22。 0x22, //bDescriptorLength字段。下级描述符的长度。下级描述符为报告描述符。 sizeof(HID_ReportDescriptor)&0xFF, (sizeof(HID_ReportDescriptor)>>8)&0xFF, /*********************端点描述符**********************************/ /* 端点描述符 */ USB_ENDPOINT_DESC_SIZE, //bLength字段。端点描述符长度为7字节。 USB_ENDPOINT_DESCRIPTOR_TYPE, //bDescriptorType字段。端点描述符编号为0x05。 USB_ENDPOINT_IN(1), //bEndpointAddress字段。端点的地址。我们使用D12的输入端点1。 USB_ENDPOINT_TYPE_INTERRUPT, //bmAttributes字段。D1~D0为端点传输类型选择。 WBVAL(0x0040), //wMaxPacketSize字段。该端点的最大包长。最大包长为64字节。 0x01, //bInterval字段。端点查询的时间,端点查询的时间,此处无意义。 /***********************端点描述符*******************************************/ USB_ENDPOINT_DESC_SIZE, //bLength字段。端点描述符长度为7字节。 USB_ENDPOINT_DESCRIPTOR_TYPE, //bDescriptorType字段。端点描述符编号为0x05。 USB_ENDPOINT_OUT(1), //bEndpointAddress字段。端点的地址。我们使用D12的输入端点1。 USB_ENDPOINT_TYPE_INTERRUPT, //bmAttributes字段。D1~D0为端点传输类型选择。 WBVAL(0x0040), //wMaxPacketSize字段。该端点的最大包长。最大包长为64字节。 0x01, //bInterval字段。端点查询的时间,端点查询的时间,此处无意义。 /*******************第二个接口描述符 存储设备*********************/ USB_INTERFACE_DESC_SIZE, //bLength字段。接口描述符的长度为9字节。 USB_INTERFACE_DESCRIPTOR_TYPE, //bDescriptorType字段。接口描述符的编号为0x04。 0x01, //bInterfaceNumber字段。该接口的编号,第二个接口,编号为1。 0x00, //bAlternateSetting字段。该接口的备用编号,为0。 0x02, //bNumEndpoints字段。非0端点的数目。该接口有2个批量端点 USB_DEVICE_CLASS_STORAGE, //bInterfaceClass字段。该接口所使用的类。大容量存储设备接口类的代码为0x08。, MSC_SUBCLASS_SCSI, //bInterfaceSubClass字段。SCSI透明命令集的子类代码为0x06。 MSC_PROTOCOL_BULK_ONLY, //bInterfaceProtocol字段。协议为仅批量传输,代码为0x50。 0x04, //iConfiguration字段。该接口的字符串索引值 /************************************* 端点描述符 *********************************************/ USB_ENDPOINT_DESC_SIZE, //bLength字段。端点描述符长度为7字节。 USB_ENDPOINT_DESCRIPTOR_TYPE, //bDescriptorType字段。端点描述符编号为0x05。 USB_ENDPOINT_IN(2), //bEndpointAddress字段。端点的地址。我们使用D12的输入端点1。 USB_ENDPOINT_TYPE_BULK, //bmAttributes字段。D1~D0为端点传输类型选择。 WBVAL(0x0040), //wMaxPacketSize字段。该端点的最大包长。最大包长为64字节。 0x00, //bInterval字段。端点查询的时间,端点查询的时间,此处无意义。 /************************************端点描述符********************************************************/ USB_ENDPOINT_DESC_SIZE, //bLength字段。端点描述符长度为7字节。 USB_ENDPOINT_DESCRIPTOR_TYPE, //bDescriptorType字段。端点描述符编号为0x05。 USB_ENDPOINT_OUT(2), //bEndpointAddress字段。端点的地址。我们使用D12的输入端点1。 USB_ENDPOINT_TYPE_BULK, //bmAttributes字段。D1~D0为端点传输类型选择。 WBVAL(0x0040), //wMaxPacketSize字段。该端点的最大包长。最大包长为64字节。 0x00, //bInterval字段。端点查询的时间,端点查询的时间,此处无意义。 };
修改描述符之后要同时记得修改描述符的长度,然后修改usb_prop文件,主要是两个多出来的命令GET_MAX_LEN用来获取当前存储设备的个数,还有一个用来复位当前存储设备,如下
RESULT DinkUsbData_Setup(u8 RequestNo) { u8 *(*CopyRoutine)(u16); CopyRoutine = NULL; if ((RequestNo == GET_DESCRIPTOR) && (Type_Recipient == (STANDARD_REQUEST | INTERFACE_RECIPIENT)) && (pInformation->USBwIndex0 == 0)) { //获取报告描述符 if (pInformation->USBwValue1 == REPORT_DESCRIPTOR) { CopyRoutine = DinkUsbGetReportDescriptor; } //获取HID描述符 else if (pInformation->USBwValue1 == HID_DESCRIPTOR_TYPE) { CopyRoutine = DinkUsbGetHIDDescriptor; } } /*** GET_PROTOCOL ***/ else if ((Type_Recipient == (CLASS_REQUEST | INTERFACE_RECIPIENT)) && RequestNo == GET_PROTOCOL) { CopyRoutine = DinkUsbGetProtocolValue;//获取协议值 } else if ((Type_Recipient == (CLASS_REQUEST | INTERFACE_RECIPIENT)) && (RequestNo == GET_MAX_LUN) && (pInformation->USBwValue == 0) && (pInformation->USBwIndex == 0) && (pInformation->USBwLength == 0x01)) { CopyRoutine = Get_Max_Lun; } if (CopyRoutine == NULL) { return USB_UNSUPPORT; } pInformation->Ctrl_Info.CopyData = CopyRoutine; pInformation->Ctrl_Info.Usb_wOffset = 0; (*CopyRoutine)(0); return USB_SUCCESS; }
GET_MAX_LEN的函数体为
u8 *Get_Max_Lun(u16 Length) { if (Length == 0) { pInformation->Ctrl_Info.Usb_wLength = LUN_DATA_LENGTH; return 0; } else { return((u8*)(&Max_Lun)); } }
对了,因为这一次使用了端点2作为存储设备使用的端点,所以要在初始化的时候顺便也多初始化两个端点
//设备复位 void DinkUsbReset(void) { Device_Info.Current_Configuration = 0; //选择当前配置为0 pInformation->Current_Feature = DinkUsbConfigDescriptor[7]; //获取配置描述符中当前设备属性 pInformation->Current_Interface = 0;//设置当前设备接口 SetBTABLE(BTABLE_ADDRESS);//设置缓冲区地址 SetEPType(ENDP0, EP_CONTROL);//控制端点 SetEPTxStatus(ENDP0, EP_TX_STALL); SetEPRxAddr(ENDP0, ENDP0_RXADDR);//设置端点缓冲区地址 SetEPTxAddr(ENDP0, ENDP0_TXADDR); Clear_Status_Out(ENDP0); SetEPRxCount(ENDP0, Device_Property.MaxPacketSize);//设置接收最大长度 SetEPRxValid(ENDP0); SetEPType(ENDP1, EP_INTERRUPT);//初始化端点1为中断传输模式,用来报告一些状态 SetEPTxAddr(ENDP1, ENDP1_TXADDR);//设置端点地址 SetEPRxAddr(ENDP1, ENDP1_RXADDR);//设置端点地址 SetEPRxStatus(ENDP1, EP_RX_VALID);//使能接收 SetEPTxStatus(ENDP1, EP_TX_NAK); //不使能发送 SetEPRxCount(ENDP1, 64);//设置接收最大长度 Clear_Status_Out(ENDP1); SetEPType(ENDP2, EP_BULK);//初始化端点1为中断传输模式,用来报告一些状态 SetEPTxAddr(ENDP2, ENDP2_TXADDR);//设置端点地址 SetEPRxAddr(ENDP2, ENDP2_RXADDR);//设置端点地址 SetEPRxStatus(ENDP2, EP_RX_VALID);//使能接收 SetEPTxStatus(ENDP2, EP_TX_NAK); //不使能发送 SetEPRxCount(ENDP2, 64);//设置接收最大长度 Clear_Status_Out(ENDP2); bDeviceState = ATTACHED;//设备插入 SetDeviceAddress(0);//设置当前地址为0 usb_debug_printf("USB Reset\r\n"); }
然后就是端点响应了,端点2的响应文件如下
void EP2_IN_Callback(void) { Mass_Storage_In(); } //USB总线发送过来数据 void EP2_OUT_Callback(void) { Mass_Storage_Out(); }
对应具体的代码就是这样
/******************************************************************************* * Function Name : Mass_Storage_In * Description : Mass Storage IN transfer. * Input : None. * Output : None. * Return : None. //设备->USB *******************************************************************************/ void Mass_Storage_In (void) { USB_STATUS_REG|=0X10;//标记轮询 switch (Bot_State) { case BOT_CSW_Send: case BOT_ERROR: Bot_State = BOT_IDLE; SetEPRxStatus(ENDP2, EP_RX_VALID);/* enable the Endpoint to recive the next cmd*/ break; case BOT_DATA_IN: //USB从设备读数据 switch (CBW.CB[0]) { case SCSI_READ10: USB_STATUS_REG|=0X02;//标记正在读数据 SCSI_Read10_Cmd(CBW.bLUN , SCSI_LBA , SCSI_BlkLen); break; } break; case BOT_DATA_IN_LAST: Set_CSW (CSW_CMD_PASSED, SEND_CSW_ENABLE); SetEPRxStatus(ENDP2, EP_RX_VALID); break; default: break; } } /******************************************************************************* * Function Name : Mass_Storage_Out * Description : Mass Storage OUT transfer. * Input : None. * Output : None. * Return : None. //USB->设备 *******************************************************************************/ void Mass_Storage_Out (void) { u8 CMD; USB_STATUS_REG|=0X10;//标记轮询 CMD = CBW.CB[0]; Data_Len = GetEPRxCount(ENDP2); PMAToUserBufferCopy(Bulk_Data_Buff, ENDP2_RXADDR, Data_Len);//读取端点缓存 switch (Bot_State)//根据状态进行处理 { case BOT_IDLE://最开始的命令阶段 CBW_Decode(); break; case BOT_DATA_OUT://USB发送数据到设备 if (CMD == SCSI_WRITE10) { USB_STATUS_REG|=0X01;//标记正在写数据 SCSI_Write10_Cmd(CBW.bLUN , SCSI_LBA , SCSI_BlkLen); break; } Bot_Abort(DIR_OUT); Set_Scsi_Sense_Data(CBW.bLUN, ILLEGAL_REQUEST, INVALID_FIELED_IN_COMMAND); Set_CSW (CSW_PHASE_ERROR, SEND_CSW_DISABLE); break; default: Bot_Abort(BOTH_DIR); Set_Scsi_Sense_Data(CBW.bLUN, ILLEGAL_REQUEST, INVALID_FIELED_IN_COMMAND); Set_CSW (CSW_PHASE_ERROR, SEND_CSW_DISABLE); break; } } /******************************************************************************* * Function Name : CBW_Decode * Description : Decode the received CBW and call the related SCSI command * routine. * Input : None. * Output : None. * Return : None. *******************************************************************************/ void CBW_Decode(void) { u32 Counter; for (Counter = 0; Counter < Data_Len; Counter++) { *((u8 *)&CBW + Counter) = Bulk_Data_Buff[Counter]; }//将buf数据拷贝入cbw结构体,便于下一次处理 CSW.dTag = CBW.dTag; CSW.dDataResidue = CBW.dDataLength; if (Data_Len != BOT_CBW_PACKET_LENGTH) { Bot_Abort(BOTH_DIR); /* reset the CBW.dSignature to desible the clear feature until receiving a Mass storage reset*/ CBW.dSignature = 0; Set_Scsi_Sense_Data(CBW.bLUN, ILLEGAL_REQUEST, PARAMETER_LIST_LENGTH_ERROR); Set_CSW (CSW_CMD_FAILED, SEND_CSW_DISABLE); return; } if ((CBW.CB[0] == SCSI_READ10 ) || (CBW.CB[0] == SCSI_WRITE10 )) { /* Calculate Logical Block Address */ SCSI_LBA = (CBW.CB[2] << 24) | (CBW.CB[3] << 16) | (CBW.CB[4] << 8) | CBW.CB[5]; /* Calculate the Number of Blocks to transfer */ SCSI_BlkLen = (CBW.CB[7] << 8) | CBW.CB[8]; } if (CBW.dSignature == BOT_CBW_SIGNATURE) { /* Valid CBW */ if ((CBW.bLUN > Max_Lun) || (CBW.bCBLength < 1) || (CBW.bCBLength > 16)) { Bot_Abort(BOTH_DIR); Set_Scsi_Sense_Data(CBW.bLUN, ILLEGAL_REQUEST, INVALID_FIELED_IN_COMMAND); Set_CSW (CSW_CMD_FAILED, SEND_CSW_DISABLE); } else { switch (CBW.CB[0]) { case SCSI_REQUEST_SENSE: SCSI_RequestSense_Cmd (CBW.bLUN); msc_debug_printf("SCSI_REQUEST_SENSE\r\n"); break; case SCSI_INQUIRY: SCSI_Inquiry_Cmd(CBW.bLUN); msc_debug_printf("SCSI_INQUIRY\r\n"); break; case SCSI_START_STOP_UNIT: SCSI_Start_Stop_Unit_Cmd(CBW.bLUN); msc_debug_printf("SCSI_START_STOP_UNIT\r\n"); break; case SCSI_ALLOW_MEDIUM_REMOVAL: SCSI_Start_Stop_Unit_Cmd(CBW.bLUN); msc_debug_printf("SCSI_MEDIA_REMOVAL\r\n"); break; case SCSI_MODE_SENSE6: SCSI_ModeSense6_Cmd (CBW.bLUN); msc_debug_printf("SCSI_MODE_SENSE6\r\n"); break; case SCSI_MODE_SENSE10: SCSI_ModeSense10_Cmd (CBW.bLUN); msc_debug_printf("SCSI_MODE_SENSE10\r\n"); break; case SCSI_READ_FORMAT_CAPACITIES: SCSI_ReadFormatCapacity_Cmd(CBW.bLUN); msc_debug_printf("SCSI_READ_FORMAT_CAPACITIES\r\n"); break; case SCSI_READ_CAPACITY10: SCSI_ReadCapacity10_Cmd(CBW.bLUN); msc_debug_printf("SCSI_READ_CAPACITY10\r\n"); break; case SCSI_TEST_UNIT_READY: SCSI_TestUnitReady_Cmd(CBW.bLUN); msc_debug_printf("SCSI_TEST_UNIT_READY\r\n"); break; case SCSI_READ10: SCSI_Read10_Cmd(CBW.bLUN, SCSI_LBA , SCSI_BlkLen); msc_debug_printf("SCSI_READ10\r\n"); break; case SCSI_WRITE10: SCSI_Write10_Cmd(CBW.bLUN, SCSI_LBA , SCSI_BlkLen); msc_debug_printf("SCSI_WRITE10\r\n"); break; case SCSI_VERIFY10: SCSI_Verify10_Cmd(CBW.bLUN); msc_debug_printf("SCSI_VERIFY10\r\n"); break; case SCSI_FORMAT_UNIT: SCSI_Format_Cmd(CBW.bLUN); msc_debug_printf("SCSI_FORMAT_UNIT\r\n"); break; /*Unsupported command*/ case SCSI_MODE_SELECT10: SCSI_Mode_Select10_Cmd(CBW.bLUN); msc_debug_printf("SCSI_MODE_SELECT10\r\n"); break; case SCSI_MODE_SELECT6: SCSI_Mode_Select6_Cmd(CBW.bLUN); msc_debug_printf("SCSI_MODE_SELECT6\r\n"); break; case SCSI_SEND_DIAGNOSTIC: SCSI_Send_Diagnostic_Cmd(CBW.bLUN); msc_debug_printf("SCSI_SEND_DIAGNOSTIC\r\n"); break; case SCSI_READ6: SCSI_Read6_Cmd(CBW.bLUN); msc_debug_printf("SCSI_READ6\r\n"); break; case SCSI_READ12: SCSI_Read12_Cmd(CBW.bLUN); msc_debug_printf("SCSI_READ12\r\n"); break; case SCSI_READ16: SCSI_Read16_Cmd(CBW.bLUN); msc_debug_printf("SCSI_READ16\r\n"); break; case SCSI_READ_CAPACITY16: SCSI_READ_CAPACITY16_Cmd(CBW.bLUN); msc_debug_printf("SCSI_READ_CAPACITY16\r\n"); break; case SCSI_WRITE6: SCSI_Write6_Cmd(CBW.bLUN); msc_debug_printf("SCSI_WRITE6\r\n"); break; case SCSI_WRITE12: SCSI_Write12_Cmd(CBW.bLUN); msc_debug_printf("SCSI_WRITE12\r\n"); break; case SCSI_WRITE16: SCSI_Write16_Cmd(CBW.bLUN); msc_debug_printf("SCSI_WRITE16\r\n"); break; case SCSI_VERIFY12: SCSI_Verify12_Cmd(CBW.bLUN); msc_debug_printf("SCSI_VERIFY12\r\n"); break; case SCSI_VERIFY16: SCSI_Verify16_Cmd(CBW.bLUN); msc_debug_printf("SCSI_VERIFY16\r\n"); break; default: { Bot_Abort(BOTH_DIR); Set_Scsi_Sense_Data(CBW.bLUN, ILLEGAL_REQUEST, INVALID_COMMAND); Set_CSW (CSW_CMD_FAILED, SEND_CSW_DISABLE); } } } } else { /* Invalid CBW */ Bot_Abort(BOTH_DIR); Set_Scsi_Sense_Data(CBW.bLUN, ILLEGAL_REQUEST, INVALID_COMMAND); Set_CSW (CSW_CMD_FAILED, SEND_CSW_DISABLE); } } /******************************************************************************* * Function Name : Transfer_Data_Request * Description : Send the request response to the PC HOST. * Input : u8* Data_Address : point to the data to transfer. * u16 Data_Length : the nember of Bytes to transfer. * Output : None. * Return : None. *******************************************************************************/ void Transfer_Data_Request(u8* Data_Pointer, u16 Data_Len) { UserToPMABufferCopy(Data_Pointer, ENDP2_TXADDR, Data_Len); SetEPTxCount(ENDP2, Data_Len); SetEPTxStatus(ENDP2, EP_TX_VALID); Bot_State = BOT_DATA_IN_LAST; CSW.dDataResidue -= Data_Len; CSW.bStatus = CSW_CMD_PASSED; } /******************************************************************************* * Function Name : Set_CSW * Description : Set the SCW with the needed fields. * Input : u8 CSW_Status this filed can be CSW_CMD_PASSED,CSW_CMD_FAILED, * or CSW_PHASE_ERROR. * Output : None. * Return : None. *******************************************************************************/ void Set_CSW (u8 CSW_Status, u8 Send_Permission) { CSW.dSignature = BOT_CSW_SIGNATURE; CSW.bStatus = CSW_Status; UserToPMABufferCopy(((u8 *)& CSW), ENDP2_TXADDR, CSW_DATA_LENGTH); SetEPTxCount(ENDP2, CSW_DATA_LENGTH); Bot_State = BOT_ERROR; if (Send_Permission) { Bot_State = BOT_CSW_Send; SetEPTxStatus(ENDP2, EP_TX_VALID); } } /******************************************************************************* * Function Name : Bot_Abort * Description : Stall the needed Endpoint according to the selected direction. * Input : Endpoint direction IN, OUT or both directions * Output : None. * Return : None. *******************************************************************************/ void Bot_Abort(u8 Direction) { switch (Direction) { case DIR_IN : SetEPTxStatus(ENDP2, EP_TX_STALL); break; case DIR_OUT : SetEPRxStatus(ENDP2, EP_RX_STALL); break; case BOTH_DIR : SetEPTxStatus(ENDP2, EP_TX_STALL); SetEPRxStatus(ENDP2, EP_RX_STALL); break; default: break; } }
实质上就是实现usb的scsi存储接口,具体请看工程代码,另外需要注意,因为USB读取SD卡是在中断中,所以我们实际上操作物理介质的时候需要将读写函数做成可重入的,否则会为存储设备带来灾难的,也就是每次读取之前加一个标志位,不让其他资源来读写,类似于互斥信号量吧
工程代码地址
http://download.csdn.net/detail/dengrengong/8542847