• 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
    ");
    }

    然后就是端点响应了,端点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
    ");
              break;
            case SCSI_INQUIRY:
              SCSI_Inquiry_Cmd(CBW.bLUN);
              msc_debug_printf("SCSI_INQUIRY
    ");
              break;
            case SCSI_START_STOP_UNIT:
              SCSI_Start_Stop_Unit_Cmd(CBW.bLUN);
              msc_debug_printf("SCSI_START_STOP_UNIT
    ");
              break;
            case SCSI_ALLOW_MEDIUM_REMOVAL:
              SCSI_Start_Stop_Unit_Cmd(CBW.bLUN);
              msc_debug_printf("SCSI_MEDIA_REMOVAL
    ");
              break;
            case SCSI_MODE_SENSE6:
              SCSI_ModeSense6_Cmd (CBW.bLUN);
              msc_debug_printf("SCSI_MODE_SENSE6
    ");
              break;
            case SCSI_MODE_SENSE10:
              SCSI_ModeSense10_Cmd (CBW.bLUN);
              msc_debug_printf("SCSI_MODE_SENSE10
    ");
              break;
            case SCSI_READ_FORMAT_CAPACITIES:
              SCSI_ReadFormatCapacity_Cmd(CBW.bLUN);
              msc_debug_printf("SCSI_READ_FORMAT_CAPACITIES
    ");
              break;
            case SCSI_READ_CAPACITY10:
              SCSI_ReadCapacity10_Cmd(CBW.bLUN);
              msc_debug_printf("SCSI_READ_CAPACITY10
    ");
              break;
            case SCSI_TEST_UNIT_READY:
              SCSI_TestUnitReady_Cmd(CBW.bLUN);
              msc_debug_printf("SCSI_TEST_UNIT_READY
    ");
              break;
            case SCSI_READ10:
              SCSI_Read10_Cmd(CBW.bLUN, SCSI_LBA , SCSI_BlkLen);
              msc_debug_printf("SCSI_READ10
    ");
              break;
            case SCSI_WRITE10:
              SCSI_Write10_Cmd(CBW.bLUN, SCSI_LBA , SCSI_BlkLen);
              msc_debug_printf("SCSI_WRITE10
    ");
              break;
            case SCSI_VERIFY10:
              SCSI_Verify10_Cmd(CBW.bLUN);
              msc_debug_printf("SCSI_VERIFY10
    ");
              break;
            case SCSI_FORMAT_UNIT:
              SCSI_Format_Cmd(CBW.bLUN);
              msc_debug_printf("SCSI_FORMAT_UNIT
    ");
              break;
              /*Unsupported command*/
    
            case SCSI_MODE_SELECT10:
              SCSI_Mode_Select10_Cmd(CBW.bLUN);
              msc_debug_printf("SCSI_MODE_SELECT10
    ");
              break;
            case SCSI_MODE_SELECT6:
              SCSI_Mode_Select6_Cmd(CBW.bLUN);
              msc_debug_printf("SCSI_MODE_SELECT6
    ");
              break;
    
            case SCSI_SEND_DIAGNOSTIC:
              SCSI_Send_Diagnostic_Cmd(CBW.bLUN);
               msc_debug_printf("SCSI_SEND_DIAGNOSTIC
    ");
              break;
            case SCSI_READ6:
              SCSI_Read6_Cmd(CBW.bLUN);
              msc_debug_printf("SCSI_READ6
    ");
              break;
            case SCSI_READ12:
              SCSI_Read12_Cmd(CBW.bLUN);
              msc_debug_printf("SCSI_READ12
    ");
              break;
            case SCSI_READ16:
              SCSI_Read16_Cmd(CBW.bLUN);
              msc_debug_printf("SCSI_READ16
    ");
              break;
            case SCSI_READ_CAPACITY16:
              SCSI_READ_CAPACITY16_Cmd(CBW.bLUN);
              msc_debug_printf("SCSI_READ_CAPACITY16
    ");
              break;
            case SCSI_WRITE6:
              SCSI_Write6_Cmd(CBW.bLUN);
              msc_debug_printf("SCSI_WRITE6
    ");
              break;
            case SCSI_WRITE12:
              SCSI_Write12_Cmd(CBW.bLUN);
              msc_debug_printf("SCSI_WRITE12
    ");
              break;
            case SCSI_WRITE16:
              SCSI_Write16_Cmd(CBW.bLUN);
              msc_debug_printf("SCSI_WRITE16
    ");
              break;
            case SCSI_VERIFY12:
              SCSI_Verify12_Cmd(CBW.bLUN);
               msc_debug_printf("SCSI_VERIFY12
    ");
              break;
            case SCSI_VERIFY16:
              SCSI_Verify16_Cmd(CBW.bLUN);
               msc_debug_printf("SCSI_VERIFY16
    ");
              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
    
  • 相关阅读:
    用图片来代替字符串
    下载网页时的 有gzip压缩的处理
    位置不固定验证码的识别
    CookieContainer 与 Session
    Thread Pool 备忘
    用 SGMLReader把子HTML 转 XML
    非asp.net控件实现回发 button
    ajax.net ??= 回车
    如何写需求分析
    jsp中地址
  • 原文地址:https://www.cnblogs.com/dengxiaojun/p/4374963.html
Copyright © 2020-2023  润新知