usb

(转)USB复合设备和USB组合设备的区别和实现代码分析

USB Compound Device,USB复合设备
USB Composite Device,USB组合设备


Compound Device内嵌Hub和多个Function,每个Function都相当于一个独立的USB外设,有自己的PID/VID/DID。
Composite Device内只有一个Function,只有一套PID/VID/DID,通过将不同的interface定义为不同的类来实现
多个功能的组合。

Compound Device内嵌Hub和多个Function,每个Function都相当于一个独立的USB外设,有自己的PID/VID/DID。

Composite Device内只有一个Function,只有一套PID/VID/DID,通过将不同的interface定义为不同的类来实现多个功能的组
合。

很多人认为一个USB接口上实现多个设备,就是指复合设备,其实,这是不确切的,虽然USB Compound Device和USB Composite Device
都会被百度翻译为USB复合设备。

在一个USB接口上实现多个设备有2中方法,一种是Compound Device,就是复合设备;另一种是Composite Device,就是组合设备。

在USB2.0的标准协议中,定义如下:

When multiple functions are combined with a hub in a single package, they are referred to as a compound device.

A device that has multiple interfaces controlled independently of each other is referred to as a

composite device.

所以,复合设备其实就是几个设备通过一个USB Hub形成的单一设备;组合设备也就是具有多个接口的设备,每个接口代表一个独立的设备。

显然,如果是想同样的功能的话,组合设备的方法要简单很多(可以去看一下USB2.0协议中,USB2.0 Hub的复杂度)。


STM32F103实现的USB转多路串口,属于USB Composite Device,不内嵌Hub,三个CDC设备的PID和VID都是相同


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描述符
                           +            //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));
  }
}

接二楼

已邀请:

admin

赞同来自:

二楼.

对了,因为这一次使用了端点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

本文引用自:http://www.cnblogs.com/dengxiaojun/p/4374963.html


admin

赞同来自:

目前看了点,觉得貌似复合设备就是两种方式混合,所以我对比了下

blob.png

admin

赞同来自:

在转一篇经验

我想把U盘(SD卡)和串口做成一个复合设备。
参考了一些资料,ConfigDescriptor里用IAD复合描述符,现在的问题是只能枚举出一个设备,DeviceDescriptor里的idProduct如果是mass storage demo里的设置,枚举出的就是mass storage,如果用demo里的虚拟串口里的设置,枚举出的就是虚拟串口。
那么,在复合设备里这个idProduct应该如何设置呢?


在4.0库里的composite例子,把里面的HID部分去掉,换成virtual comport部分。
搞了好几天,遇到一些问题,也算加深了了解。把一些关键点写下来给大家参考:

1:主要修改usb_desc.c里的Composite_ConfigDescriptor,
bNumInterfaces要改成总共的interface数目,我这里串口用了2个,u盘用了1个。然后是具体的描述,因为串口有2个interface,所以需要IAD描述,如果只有一个,就可以像例程那样直接描述。

    // IAD
    0x08,        //描述符大小
    0x0B,        //IAD描述符类型
    0x00,        // bFirstInterface 
    0x02,        // bInterfaceCount
    0x02,        // bFunctionClass: CDC Class
    0x02,        // bFunctionSubClass
    0x01,        // bFunctionProtocol
    0x00,        // iFunction

下面接串口本来的描述符,IAD里很简单
原串口用了IN2,是中断传输,一直没找到和搞清楚具体的服务函数在哪儿?!有知道的说一声吧。
因为u盘也用IN2和OUT2,所以把这里改成IN4。
描述完串口接着描述u盘

                  // IAD
    0x08,        //描述符大小
    0x0B,        //IAD描述符类型
    0x02,        // bFirstInterface
    0x01,        // bInterfaceCount
    0x08,        // bFunctionClass: MASS STORAGE Class
    0x06,        // bFunctionSubClass
    0x50,        // bFunctionProtocol
    0x01,        // iFunction

下来照搬u盘描述即可。
Composite_ConfigDescriptor 修改完了,别忘了修改一下usb_desc.h里的Composite_SIZ_CONFIG_DESC,因为增加了描述内容嘛。
这时候就能枚举出符合设备了。
2:整合,增加了端点,需要修改usb_prop.c里端点初始化部分。usb_endp.c里callback函数要修改。按照callback函数的增减修改usb_conf.h。
这里有很重要的定义,改不好的话设备还是用不了。
EP_NUM 是使用的端点数,我使用到ep4,那这里就是5。
下来是BTABLE_ADDRESS
各端点的缓冲区地址设置。BTABLE_ADDRESS是0,第一个缓冲区地址我这里要设为0x28。因为BTABLE_ADDRESS到第一个缓冲区之间是各缓冲区的偏移地址存放处,我用了5个端点,每个端点8个字节,所以我要把第一个缓冲区放在0x28才行。
这个问题搞了好久才明白,参考http://bbs.21ic.com/icview-208240-1-1.html ,很详细。

至此,修改完成,两种设备共存都正常工作了!

只是驱动不容易装,如果我安装ST的串口驱动(修改过PID),那么就只有这个设备,u盘没有,如果直接自动装驱动,有u盘没串口。这时候手动更新有惊叹号的设备,指定串口的驱动,装好后测试也就正常了。


U盘部分不需要驱动,串口部分不能自动安装驱动(即使系统安装过也不行),变惊叹号设备。如果再安装一次倒是可以用了,但是U盘却没了。对这个惊叹号设备手动选择驱动类型,强制使用以前安装的驱动则可以正常使用。这样搞总是不正常的样子。
其实,要修改驱动包里的INF, %DESCRIPTION%=DriverInstall,USB\VID_0483&PID_5750改成
%DESCRIPTION%=DriverInstall,USB\VID_0483&PID_5750&MI_00就可以一切正常了。
因为我这里设置串口为interface 0 开始的,惊叹号设备里也能看到 VID_0483&PID_5750&MI_00这样的信息,所以这样设置驱动就知道是要匹配0号interface的。
至此,全部正常!

本文转自:http://bbs.21ic.com/icview-554720-1-1.html

jcdzxh

赞同来自:

几号学做复合设备,感谢分享

洋沙

赞同来自:

感谢,刚接触USB,有点懵逼

要回复问题请先登录注册