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(), //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)>>)&0xFF,
/*********************端点描述符**********************************/
/* 端点描述符 */
USB_ENDPOINT_DESC_SIZE, //bLength字段。端点描述符长度为7字节。
USB_ENDPOINT_DESCRIPTOR_TYPE, //bDescriptorType字段。端点描述符编号为0x05。
USB_ENDPOINT_IN(), //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(), //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(), //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(), //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 == ))
{
//获取报告描述符
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 == )
&& (pInformation->USBwIndex == ) && (pInformation->USBwLength == 0x01))
{
CopyRoutine = Get_Max_Lun;
} if (CopyRoutine == NULL)
{
return USB_UNSUPPORT;
} pInformation->Ctrl_Info.CopyData = CopyRoutine;
pInformation->Ctrl_Info.Usb_wOffset = ;
(*CopyRoutine)();
return USB_SUCCESS;
}

GET_MAX_LEN的函数体为

u8 *Get_Max_Lun(u16 Length)
{
if (Length == )
{
pInformation->Ctrl_Info.Usb_wLength = LUN_DATA_LENGTH;
return ;
}
else
{
return((u8*)(&Max_Lun));
}
}

对了,因为这一次使用了端点2作为存储设备使用的端点,所以要在初始化的时候顺便也多初始化两个端点

//设备复位
void DinkUsbReset(void)
{
Device_Info.Current_Configuration = ; //选择当前配置为0
pInformation->Current_Feature = DinkUsbConfigDescriptor[]; //获取配置描述符中当前设备属性
pInformation->Current_Interface = ;//设置当前设备接口
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, );//设置接收最大长度
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, );//设置接收最大长度
Clear_Status_Out(ENDP2); bDeviceState = ATTACHED;//设备插入 SetDeviceAddress();//设置当前地址为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[])
{
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[];
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 = ; 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 = ;
Set_Scsi_Sense_Data(CBW.bLUN, ILLEGAL_REQUEST, PARAMETER_LIST_LENGTH_ERROR);
Set_CSW (CSW_CMD_FAILED, SEND_CSW_DISABLE);
return;
} if ((CBW.CB[] == SCSI_READ10 ) || (CBW.CB[] == SCSI_WRITE10 ))
{
/* Calculate Logical Block Address */
SCSI_LBA = (CBW.CB[] << ) | (CBW.CB[] << ) | (CBW.CB[] << ) | CBW.CB[];
/* Calculate the Number of Blocks to transfer */
SCSI_BlkLen = (CBW.CB[] << ) | CBW.CB[];
} if (CBW.dSignature == BOT_CBW_SIGNATURE)
{
/* Valid CBW */
if ((CBW.bLUN > Max_Lun) || (CBW.bCBLength < ) || (CBW.bCBLength > ))
{
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[])
{
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

STM32实现HID和u盘复合设备的更多相关文章

  1. STM32 IAP 固件升级设计/U盘升级固件

    源:STM32 IAP 固件升级设计/U盘升级固件 固件升级的基本思路是: 将stm32 的flash划分为两个区域: 1.Bootloader区:存放bootloader的代码,bootloader ...

  2. USB Mass Storage学习笔记-STM32+FLASH实现U盘

    一.内容概述  采用STM32内部自带USB控制器外加大页NAND FLASH K9F1G08U0A实现一个128M的U盘. 1.STM32的USB控制器 STM32F103的MCU自带USB从控制器 ...

  3. stm32 学习参考(转)

    源:stm32 学习参考 单片机裸机下写一个自己的shell调试器     LWIP_STM32_ENC28J60_NETCONN_TCP_SERVICER(5)     LWIP_STM32_ENC ...

  4. STM32利用CUBEMX建立自定义HID工程,并且完成64字节的IN,OUT传输功能。

    STM32 Customed HID开发流程 本文介绍的是STM32的cubeMX自定义HID的开发流程 cubeMX配置customed HID模式.更多详细配置壳查看代码CubeMX的配置文件. ...

  5. 史上最全USB HID开发资料

    史上最全USB HID开发资料 史上最全USB HID开发资料,悉心整理一个月,亲自测试. 涉及STM32 C51 8051F例子都有源码,VC上位机例子以及源码,USB协议,HID协议,USB抓包工 ...

  6. 做一个U盘的学习路线

    最近想研究一个U盘,然后顺便熟悉一下USB协议.因为USB协议比较复杂, 常用的复杂外设除了WiFi,Ethernet,SDIO和USB这些就是USB了,学习USB的时候肯定要拿一个东西下手,所以简单 ...

  7. 2--STM32+USB移植+HID 与AUDIO类MIDI设备组成的复合设备(原创)

      前期准备: 一.硬件资源:STM32F103,USB-FS固件库. 链接: STM32 之 标准外设版USB驱动库详解(架构+文件+函数+使用说明+示例程序) https://blog.csdn. ...

  8. STM32 USB开发(三) 基于F105RBT6核心板开发的自定义HID收发(FS)

    硬件设计 该核心板的USB插口有两个,一个是用于USB Slave的,可以用来做HID设备,把模拟STM32模拟为U盘等:另一个是USB Host设备,可以对插上的U盘的数据进行读写. 图中J2是Mi ...

  9. STM32 复合设备编写

    目的 完成一个CDC + MSC的复合USB设备 可以方便在CDC,MSC,复合设备三者间切换 可移植性强 预备知识 cube中USB只有两个入口. main函数中的MX_USB_DEVICE_Ini ...

随机推荐

  1. Swift -> RunTime(动态性) 问题 浅析

    Swift是苹果2014年发布的编程开发语言,可与Objective-C共同运行于Mac OS和iOS平台,用于搭建基于苹果平台的应用程序.Swift已经开源,目前最新版本为2.2.我们知道Objec ...

  2. cmd下载文件

    进入cmd 输入ftp 192.168.1.200 然后按照提示输入用户名和密码 cd 进入要下载的目录 dir 看操作权限 lcd查看本地要装下载文件的目录 prompt关闭交互模式 mget da ...

  3. jz2440 环境搭建遇到的问题

    已解决:

  4. Node.js学习 - CallBack Function

    Node.js异步编程的直接体现就是回调,Node使用了大量的回调函数,其所有的API都支持回调. 阻塞代码实例(同步) var fs = require("fs"); var d ...

  5. 正确使用String,StringBuffer,StringBuilder

    很多时候在实际中,我们很常用的就是字符串String. 对于它,网上已经说的很多了. 我就说一点是特别重要的需要记住的,String赋值之后它就是不能被改变的. 也就是这一点,导致了String在操作 ...

  6. laytpl.js 模板使用记录

    {{# for(var j = 0, len = d.length; j < len; j++){ }} <div class="pure-u-1-5 pure-u-sm-1 p ...

  7. php的header函数之设置content-type

    //定义编码 header( 'Content-Type:text/html;charset=utf-8 '); //Atom header('Content-type: application/at ...

  8. Processes and Threads (转)

    http://www.cnblogs.com/xitang/archive/2011/09/24/2189460.html 原文 http://developer.android.com/guide/ ...

  9. USACO Section 1.2 Name That Number 解题报告

    题目 题目描述 在一个农场里面,每一头牛都有一个数字编号,但是现在这些牛不喜欢这种编号,它们想把这些数字编号转化成为可以接受的字母的形式.数字与字母的转换表如下: 2: A,B,C 5: J,K,L ...

  10. Mysql 技巧

    order by条件: SELECT * FROM tablename WHERE id_one=27 OR id_two=27 ORDER BY CASE WHEN id_one=27 THEN t ...