键盘鼠标属于HID,U盘功能属于MSC。至于这些定义,这里不再过多介绍。

网上有很多的例程,但是大多是基于HAL库的,标准库的我也找了不少例子看,但是没有HID+MSC的例程。最后还是看了个官方的复合设备例程才顿悟的,官方的例程,网上也很好找。搜USB Composite examples应该就能找到。

手上的设备是基于stm32f1系列的,目前已经复合了键盘和鼠标,想要新增加一个U盘的功能。由于已经是成熟的产品了,硬件方面不方便修改,所以这里采用单片机内部的flash来模拟U盘功能。要去掉程序存储的空间,我的单片机大小是512k,所以这里给U盘配置400k。

首先修改的就是usb_desc.c文件。这个文件主要存放的是一些描述符。一般来说,设备描述符是不需要修改的,主要修改的是配置描述符。

`//USB配置描述符
const uint8_t HID_ConfigDescriptor[CONFIG_DESC_SIZE] =
{0x09, /* bLength: Configuration Descriptor size */CONFIGURATION_DESCRIPTOR_TYPE, /* bDescriptorType: Configuration */CONFIG_DESC_SIZE,//***********配置描述符长度,定义在.h文件中,移植需修改。0x00,0x03, //*********************接口数量配置,移植需修改。之前鼠标键盘为2,这里增加U盘改成30x01,   //bConfiguration字段0x00, //iConfigurationz字段0xC0,    //bmAttributes字段0x32,   //bMaxPower字段/*******************第一个接口描述符(键盘)*********************/0x09,    //bLength字段0x04,    //bDescriptorType字段0x00,    //bInterfaceNumber字段 接口的编号,第一个接口为000x00, //bAlternateSetting字段0x02,  //bNumEndpoints字段,端点的数量,这里键盘采用输入加输出,所以2个端点0x03,    //bInterfaceClass字段0x01,    //bInterfaceSubClass字段0x01, //bInterfaceProtocol字段0x00, //iConfiguration字段/******************HID描述符************************/0x09,   //bLength字段0x21,    //bDescriptorType字段0x10,    //bcdHID字段0x01,0x21,    //bCountyCode字段0x01,    //bNumDescriptors字段0x22,    //bDescriptorType字段sizeof(HID_ReportDescriptor_KEY)&0xFF,  //HID_ReportDescriptor_KEY为键盘的报告描述符(sizeof(HID_ReportDescriptor_KEY)>>8)&0xFF,/**********************输入端点描述符***********************/0x07,  //bLength字段0x05,    //bDescriptorType字段0x81,    //bEndpointAddress字段,01端点的输入地址0x03,  //bmAttributes字段0x10,   //wMaxPacketSize字段0x00,0x01,    //bInterval字段/**********************输出端点描述符***********************/0x07,    //bLength字段0x05,    //bDescriptorType字段0x01,    //bEndpointAddress字段,01d端点的输出地址0x03, //bmAttributes字段0x10,   //wMaxPacketSize字段0x00,0x01,    //bInterval字段/*******************第二个接口描述符(鼠标)*********************/0x09,    //bLength字段0x04,    //bDescriptorType字段0x01,    //bInterfaceNumber字段,第二个接口,为010x00,   //bAlternateSetting字段0x01,  //bNumEndpoints字段,我的鼠标采用了一个端点,所以为010x03,  //bInterfaceClass字段0x01,    //bInterfaceSubClass字段0x02, //bInterfaceProtocol字段0x00, //iConfiguration字段/******************HID描述符************************/0x09,   //bLength字段0x21,    //bDescriptorType字段0x10,    //bcdHID字段0x01,0x21,    //bCountyCode字段0x01,    //bNumDescriptors字段0x22,    //bDescriptorType字段sizeof(HID_ReportDescriptor_MOUSE)&0xFF,(sizeof(HID_ReportDescriptor_MOUSE)>>8)&0xFF,/**********************输入端点描述符***********************/0x07,   //bLength字段0x05,    //bDescriptorType字段0x83,    //bEndpointAddress字段,03端点的地址。0x03,   //bmAttributes字段。D1~D0为端点传输类型选择0x40,    //wMaxPacketSize字段0x00,0x01, //bInterval字段//新增的U盘接口相关代码
//******************** 第三个接口描述符(U盘) ********************/0x09,   /* bLength: Interface Descriptor size */0x04,   /* bDescriptorType: */0x02,   /* bInterfaceNumber: Number of Interface 第三个接口的编号*/0x00,   /* bAlternateSetting: Alternate setting */0x02,   /* bNumEndpoints,U盘是有输入输出的,所以必须是两个端点。*/0x08,   /* bInterfaceClass: MASS STORAGE Class ,注意要识别成大容量存储设备,这里必须选08*/0x06,   /* bInterfaceSubClass : SCSI transparent*/0x50,   /* nInterfaceProtocol */1,          /* iInterface: *//******************** 输入端点描述符 ******************/0x07,   /*Endpoint descriptor length = 7*/0x05,   /*Endpoint descriptor type */0x82,   /*Endpoint address端点2的地址  */0x02,   /*Bulk endpoint type */0x40,   /*Maximum packet size (64 bytes) */0x00,0x00,   /*Polling interval in milliseconds *//******************** 输出端点描述符 ******************/0x07,   /*Endpoint descriptor length = 7 */0x05,   /*Endpoint descriptor type */0x02,   /*Endpoint address端点2的地址 */0x02,   /*Bulk endpoint type */0x40,   /*Maximum packet size (64 bytes) */0x00,0x00     /*Polling interval in milliseconds*/
};`

上面配置描述符和例程里面的都大同小异,主要是一些需要根据自己设备来修改的地方,但我进行了备注,根据备注理解改起来也很容易。
配置描述符改完,usb_desc.c文件的剩下的内容可以不做修改,要是没有鼠标键盘的报告描述符,可以搜一个,然后根据其长度,修改配置描述符的大小就可以。

然后要修改的是usb_endp文件。这个主要根据上面的端点描述符来修改。

`uint8_t Receive_Buffer[2];
__IO uint8_t PrevXferComplete;
//键盘的
void EP1_OUT_Callback(void)
{USB_SIL_Read(EP1_OUT, Receive_Buffer);SetEPRxStatus(ENDP1, EP_RX_VALID);
}void EP1_IN_Callback(void)
{PrevXferComplete = 1;
}//鼠标
void EP3_IN_Callback(void)
{PrevXferComplete = 1;
}//u盘
void EP2_IN_Callback(void)
{Mass_Storage_In();
}void EP2_OUT_Callback(void)
{Mass_Storage_Out();
}
`

当然还需要配置每个端点的地址。这里的地址好像可以随便配置,但是最好是需要相差64k的,也就是0x40.

/* EP0  */
/* rx/tx buffer base address */
#define ENDP0_RXADDR        (0x18)
#define ENDP0_TXADDR        (0x58)/* EP1  */
/* tx buffer base address */
/* tx buffer base address */
#define ENDP1_TXADDR        (0x118)
#define ENDP1_RXADDR        (0x11C)
/* EP3  */
/* tx buffer base address */
#define ENDP3_TXADDR        (0x198)/* EP2  */
/* tx buffer base address */
/* tx buffer base address */
#define ENDP2_TXADDR        (0x98)
#define ENDP2_RXADDR        (0xD8)

接下来就是配置usb_prop.c文件来识别这些设备。
主要修改的是下面几个函数。

/*******************************************************************************
* Function Name  : CustomHID_Reset.
* Description    : Custom HID reset routine.
* Input          : None.
* Output         : None.
* Return         : None.
*******************************************************************************/
void CustomHID_Reset(void)
{/* Set CustomHID_DEVICE as not configured */pInformation->Current_Configuration = 0;pInformation->Current_Interface = 0;/*the default Interface*/pInformation->Current_Feature = HID_ConfigDescriptor[7];SetBTABLE(BTABLE_ADDRESS);/* Initialize Endpoint 0 */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);/* Initialize Endpoint 1 */SetEPType(ENDP1, EP_INTERRUPT);SetEPTxAddr(ENDP1, ENDP1_TXADDR);SetEPRxAddr(ENDP1, ENDP1_RXADDR);SetEPTxCount(ENDP1, 8);SetEPRxCount(ENDP1, 2);SetEPRxStatus(ENDP1, EP_RX_VALID);SetEPTxStatus(ENDP1, EP_TX_NAK);/* Initialize Endpoint In 3 */SetEPType(ENDP3, EP_INTERRUPT); //初始化为中断端点类型SetEPTxAddr(ENDP3, ENDP3_TXADDR); //设置发送数据的地址SetEPTxCount(ENDP3, 5); //设置发送的长度SetEPTxStatus(ENDP3, EP_TX_NAK); //设置端点处于忙状态/* Set this device to response on default address *//* 初始化端点2的输入 */SetEPType(ENDP2, EP_BULK);SetEPTxCount(ENDP2, 64);SetEPTxAddr(ENDP2, ENDP2_TXADDR);SetEPTxStatus(ENDP2, EP_TX_NAK);/* 初始化端点2的输出 */SetEPType(ENDP2, EP_BULK);SetEPRxAddr(ENDP2, ENDP2_RXADDR);SetEPRxCount(ENDP2, 64);SetEPRxStatus(ENDP2, EP_RX_VALID);bDeviceState = ATTACHED;SetDeviceAddress(0);
}

根据前面自己的配置来修改,端点0是启动usb所需要的。端点2是U盘的,所以类型不一样。

/*******************************************************************************
* Function Name  : CustomHID_Data_Setup
* Description    : Handle the data class specific requests.
* Input          : Request Nb.
* Output         : None.
* Return         : USB_UNSUPPORT or USB_SUCCESS.
*******************************************************************************/
RESULT CustomHID_Data_Setup(uint8_t RequestNo)
{u8 *(*CopyRoutine)(u16);CopyRoutine = NULL;if ((RequestNo == GET_DESCRIPTOR)&& (Type_Recipient == (STANDARD_REQUEST | INTERFACE_RECIPIENT))&& (pInformation->USBwIndex0 < 2)){if (pInformation->USBwValue1 == REPORT_DESCRIPTOR){if (pInformation->USBwIndex0 == 0)//进行轮询查询,0为键盘。其他为鼠标,若是复合三个或多个hid可以0123一次增加。CopyRoutine = KP_GetReportDescriptor;elseCopyRoutine = Mouse_GetReportDescriptor;}else if (pInformation->USBwValue1 == HID_DESCRIPTOR_TYPE){if (pInformation->USBwIndex0 == 0)CopyRoutine = KP_GetHIDDescriptor;elseCopyRoutine = Mouse_GetHIDDescriptor;}} /* End of GET_DESCRIPTOR *//*** GET_PROTOCOL ***/else if ((Type_Recipient == (CLASS_REQUEST | INTERFACE_RECIPIENT))&& RequestNo == GET_PROTOCOL){CopyRoutine = CustomHID_GetProtocolValue;}if (CopyRoutine == NULL){return USB_UNSUPPORT;}pInformation->Ctrl_Info.CopyData = CopyRoutine;pInformation->Ctrl_Info.Usb_wOffset = 0;(*CopyRoutine)(0);return USB_SUCCESS;
}

如果你是从只有键盘的工程代码中修改的话,还需要添加鼠标相关的获取函数。这里直接贴出prop文件代码。

#include "hw_config.h"
#include "usb_lib.h"
#include "usb_conf.h"
#include "usb_prop.h"
#include "usb_desc.h"
#include "usb_pwr.h"
#include "usb_bot.h"
#include "memory.h"
#include "mass_mal.h"uint32_t ProtocolValue;
__IO uint8_t EXTI_Enable;
__IO uint8_t Request = 0;
uint8_t Report_Buf[2];DEVICE Device_Table =
{EP_NUM,1
};/*CustomHID_SetReport_Feature function prototypes*/
uint8_t *CustomHID_SetReport_Feature(uint16_t Length);extern unsigned char Bot_State;
extern Bulk_Only_CBW CBW;
uint32_t Max_Lun = 0; DEVICE_PROP Device_Property =
{CustomHID_init,CustomHID_Reset,CustomHID_Status_In,CustomHID_Status_Out,CustomHID_Data_Setup,CustomHID_NoData_Setup,CustomHID_Get_Interface_Setting,CustomHID_GetDeviceDescriptor,CustomHID_GetConfigDescriptor,CustomHID_GetStringDescriptor,0,0x40 /*MAX PACKET SIZE*/
};
USER_STANDARD_REQUESTS User_Standard_Requests =
{CustomHID_GetConfiguration,CustomHID_SetConfiguration,CustomHID_GetInterface,CustomHID_SetInterface,CustomHID_GetStatus,CustomHID_ClearFeature,CustomHID_SetEndPointFeature,CustomHID_SetDeviceFeature,CustomHID_SetDeviceAddress
};ONE_DESCRIPTOR Device_Descriptor =
{(uint8_t*)HID_DeviceDescriptor,DEVICE_DESC_SIZE
};ONE_DESCRIPTOR Config_Descriptor =
{(uint8_t*)HID_ConfigDescriptor,CONFIG_DESC_SIZE
};ONE_DESCRIPTOR KP_Report_Descriptor =                                {                                                                   (u8 *)HID_ReportDescriptor_KEY,                             63                                      };                                                              ONE_DESCRIPTOR KP_Hid_Descriptor =                                 {                                                                   (u8*)HID_ConfigDescriptor + 18,                9                                           };  ONE_DESCRIPTOR Mouse_Report_Descriptor =                           {                                                                   (u8 *)HID_ReportDescriptor_MOUSE,                               54                                      };                                                              ONE_DESCRIPTOR Mouse_Hid_Descriptor =                      {                                                       (u8*)HID_ConfigDescriptor + 50,            9                                           };ONE_DESCRIPTOR String_Descriptor[4] =
{{ (uint8_t*)HID_LangIDString, LANGID_STRING },{ (uint8_t*)HID_VendorString, VENDOR_STRING_SIZE },{ (uint8_t*)HID_ProductString, PRODUCT_STRING_SIZE },{ (uint8_t*)HID_SerialString, SERIAL_STRING_SIZE }
};/* Extern variables ----------------------------------------------------------*/
/* Private function prototypes -----------------------------------------------*//*CustomHID_SetReport_Feature function prototypes*/
uint8_t *CustomHID_SetReport_Feature(uint16_t Length);/* Extern function prototypes ------------------------------------------------*/
/* Private functions ---------------------------------------------------------*//*******************************************************************************
* Function Name  : CustomHID_init.
* Description    : Custom HID init routine.
* Input          : None.
* Output         : None.
* Return         : None.
*******************************************************************************/
void CustomHID_init(void)
{Get_SerialNum();pInformation->Current_Configuration = 0;/* Connect the device */PowerOn();/* Perform basic device initialization operations */USB_SIL_Init();bDeviceState = UNCONNECTED;
}/*******************************************************************************
* Function Name  : CustomHID_Reset.
* Description    : Custom HID reset routine.
* Input          : None.
* Output         : None.
* Return         : None.
*******************************************************************************/
void CustomHID_Reset(void)
{/* Set CustomHID_DEVICE as not configured */pInformation->Current_Configuration = 0;pInformation->Current_Interface = 0;/*the default Interface*/pInformation->Current_Feature = HID_ConfigDescriptor[7];SetBTABLE(BTABLE_ADDRESS);/* Initialize Endpoint 0 */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);/* Initialize Endpoint 1 */SetEPType(ENDP1, EP_INTERRUPT);SetEPTxAddr(ENDP1, ENDP1_TXADDR);SetEPRxAddr(ENDP1, ENDP1_RXADDR);SetEPTxCount(ENDP1, 8);SetEPRxCount(ENDP1, 2);SetEPRxStatus(ENDP1, EP_RX_VALID);SetEPTxStatus(ENDP1, EP_TX_NAK);/* Initialize Endpoint In 3 */SetEPType(ENDP3, EP_INTERRUPT); //初始化为中断端点类型SetEPTxAddr(ENDP3, ENDP3_TXADDR); //设置发送数据的地址SetEPTxCount(ENDP3, 5); //设置发送的长度SetEPTxStatus(ENDP3, EP_TX_NAK); //设置端点处于忙状态/* Set this device to response on default address *//* 初始化端点2的输入 */SetEPType(ENDP2, EP_BULK);SetEPTxCount(ENDP2, 64);SetEPTxAddr(ENDP2, ENDP2_TXADDR);SetEPTxStatus(ENDP2, EP_TX_NAK);/* 初始化端点2的输出 */SetEPType(ENDP2, EP_BULK);SetEPRxAddr(ENDP2, ENDP2_RXADDR);SetEPRxCount(ENDP2, 64);SetEPRxStatus(ENDP2, EP_RX_VALID);bDeviceState = ATTACHED;SetDeviceAddress(0);
}
/*******************************************************************************
* Function Name  : CustomHID_SetConfiguration.
* Description    : Update the device state to configured and command the ADC
*                  conversion.
* Input          : None.
* Output         : None.
* Return         : None.
*******************************************************************************/
void CustomHID_SetConfiguration(void)
{if (pInformation->Current_Configuration != 0)bDeviceState = CONFIGURED;       // Device configured
}
/*******************************************************************************
* Function Name  : CustomHID_SetConfiguration.
* Description    : Update the device state to addressed.
* Input          : None.
* Output         : None.
* Return         : None.
*******************************************************************************/
void CustomHID_SetDeviceAddress(void)
{//  bDeviceState = ADDRESSED;
}
/*******************************************************************************
* Function Name  : CustomHID_Status_In.
* Description    : Joystick status IN routine.
* Input          : None.
* Output         : None.
* Return         : None.
*******************************************************************************/
void CustomHID_Status_In(void)
{if (Report_Buf[1] == 0){//Led_State = Bit_RESET;}else{//Led_State = Bit_SET;}switch (Report_Buf[0]){/*Change LED's status according to the host report*/case 1: /* Led 1 */break;case 2:   /* Led 2 */break;case 3:/* Led 3 */break;case 4:/* Led 4 */break;default:break;}
}/*******************************************************************************
* Function Name  : CustomHID_Status_Out
* Description    : Joystick status OUT routine.
* Input          : None.
* Output         : None.
* Return         : None.
*******************************************************************************/
void CustomHID_Status_Out(void)
{}/*******************************************************************************
* Function Name  : CustomHID_Data_Setup
* Description    : Handle the data class specific requests.
* Input          : Request Nb.
* Output         : None.
* Return         : USB_UNSUPPORT or USB_SUCCESS.
*******************************************************************************/
RESULT CustomHID_Data_Setup(uint8_t RequestNo)
{u8 *(*CopyRoutine)(u16);CopyRoutine = NULL;if ((RequestNo == GET_DESCRIPTOR)&& (Type_Recipient == (STANDARD_REQUEST | INTERFACE_RECIPIENT))&& (pInformation->USBwIndex0 < 2)){if (pInformation->USBwValue1 == REPORT_DESCRIPTOR){if (pInformation->USBwIndex0 == 0)CopyRoutine = KP_GetReportDescriptor;elseCopyRoutine = Mouse_GetReportDescriptor;}else if (pInformation->USBwValue1 == HID_DESCRIPTOR_TYPE){if (pInformation->USBwIndex0 == 0)CopyRoutine = KP_GetHIDDescriptor;elseCopyRoutine = Mouse_GetHIDDescriptor;}} /* End of GET_DESCRIPTOR *//*** GET_PROTOCOL ***/else if ((Type_Recipient == (CLASS_REQUEST | INTERFACE_RECIPIENT))&& RequestNo == GET_PROTOCOL){CopyRoutine = CustomHID_GetProtocolValue;}if (CopyRoutine == NULL){return USB_UNSUPPORT;}pInformation->Ctrl_Info.CopyData = CopyRoutine;pInformation->Ctrl_Info.Usb_wOffset = 0;(*CopyRoutine)(0);return USB_SUCCESS;
}/*******************************************************************************
* Function Name  : CustomHID_SetReport_Feature
* Description    : Set Feature request handling
* Input          : Length.
* Output         : None.
* Return         : Buffer
*******************************************************************************/
uint8_t *CustomHID_SetReport_Feature(uint16_t Length)
{if (Length == 0){pInformation->Ctrl_Info.Usb_wLength = 2;return NULL;}else{return Report_Buf;}
}/*******************************************************************************
* Function Name  : CustomHID_NoData_Setup
* Description    : handle the no data class specific requests
* Input          : Request Nb.
* Output         : None.
* Return         : USB_UNSUPPORT or USB_SUCCESS.
*******************************************************************************/
RESULT CustomHID_NoData_Setup(uint8_t RequestNo)
{if ((Type_Recipient == (CLASS_REQUEST | INTERFACE_RECIPIENT))&& (RequestNo == SET_PROTOCOL)){return CustomHID_SetProtocol();}else{return USB_UNSUPPORT;}
}/*******************************************************************************
* Function Name  : CustomHID_GetDeviceDescriptor.
* Description    : Gets the device descriptor.
* Input          : Length
* Output         : None.
* Return         : The address of the device descriptor.
*******************************************************************************/
uint8_t *CustomHID_GetDeviceDescriptor(uint16_t Length)
{return Standard_GetDescriptorData(Length, &Device_Descriptor);
}/*******************************************************************************
* Function Name  : CustomHID_GetConfigDescriptor.
* Description    : Gets the configuration descriptor.
* Input          : Length
* Output         : None.
* Return         : The address of the configuration descriptor.
*******************************************************************************/
uint8_t *CustomHID_GetConfigDescriptor(uint16_t Length)
{return Standard_GetDescriptorData(Length, &Config_Descriptor);
}/*******************************************************************************
* Function Name  : CustomHID_GetStringDescriptor
* Description    : Gets the string descriptors according to the needed index
* Input          : Length
* Output         : None.
* Return         : The address of the string descriptors.
*******************************************************************************/
uint8_t *CustomHID_GetStringDescriptor(uint16_t Length)
{uint8_t wValue0 = pInformation->USBwValue0;
//  if (wValue0 >= 4)
//  {//      return NULL;
//  }
//  else
//  {return Standard_GetDescriptorData(Length, &String_Descriptor[wValue0]);
//  }
}/*******************************************************************************
* Function Name  : Joystick_GetReportDescriptor.
* Description    : Gets the HID report descriptor.
* Input          : Length
* Output         : None.
* Return         : The address of the configuration descriptor.
*******************************************************************************/
u8 *KP_GetReportDescriptor(u16 Length)
{return Standard_GetDescriptorData(Length, &KP_Report_Descriptor);
}u8 *Mouse_GetReportDescriptor(u16 Length)
{return Standard_GetDescriptorData(Length, &Mouse_Report_Descriptor);
}/*******************************************************************************
* Function Name  : Joystick_GetHIDDescriptor.
* Description    : Gets the HID descriptor.
* Input          : Length
* Output         : None.
* Return         : The address of the configuration descriptor.
*******************************************************************************/
u8 *KP_GetHIDDescriptor(u16 Length)
{return Standard_GetDescriptorData(Length, &KP_Hid_Descriptor);
}
u8 *Mouse_GetHIDDescriptor(u16 Length)
{return Standard_GetDescriptorData(Length, &Mouse_Hid_Descriptor);
}/*******************************************************************************
* Function Name  : CustomHID_Get_Interface_Setting.
* Description    : tests the interface and the alternate setting according to the
*                  supported one.
* Input          : - Interface : interface number.
*                  - AlternateSetting : Alternate Setting number.
* Output         : None.
* Return         : USB_SUCCESS or USB_UNSUPPORT.
*******************************************************************************/
RESULT CustomHID_Get_Interface_Setting(uint8_t Interface, uint8_t AlternateSetting)
{if (AlternateSetting > 0){return USB_UNSUPPORT;}else if (Interface > 0){return USB_UNSUPPORT;}return USB_SUCCESS;
}/*******************************************************************************
* Function Name  : CustomHID_SetProtocol
* Description    : Joystick Set Protocol request routine.
* Input          : None.
* Output         : None.
* Return         : USB SUCCESS.
*******************************************************************************/
RESULT CustomHID_SetProtocol(void)
{uint8_t wValue0 = pInformation->USBwValue0;ProtocolValue = wValue0;return USB_SUCCESS;
}/*******************************************************************************
* Function Name  : CustomHID_GetProtocolValue
* Description    : get the protocol value
* Input          : Length.
* Output         : None.
* Return         : address of the protocol value.
*******************************************************************************/
uint8_t *CustomHID_GetProtocolValue(uint16_t Length)
{if (Length == 0){pInformation->Ctrl_Info.Usb_wLength = 1;return NULL;}else{return (uint8_t *)(&ProtocolValue);}
}//新增函数
/
uint8_t *Get_Max_Lun(uint16_t Length)
{if (Length == 0){pInformation->Ctrl_Info.Usb_wLength = LUN_DATA_LENGTH;return 0;}else{return((uint8_t*)(&Max_Lun));}
}void CustomHID_ClearFeature (void)
{if (CBW.dSignature != BOT_CBW_SIGNATURE)Bot_Abort(BOTH_DIR);
}
///

下面就该修改MSC相关的。
首先是文件mass_mal.c

#include "fatfs_flash_spi.h"
#include "stm32f10x_flash.h"
#include "mass_mal.h"
#include <stdio.h>
/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
uint32_t Mass_Memory_Size[2];
uint32_t Mass_Block_Size[2];
uint32_t Mass_Block_Count[2];
__IO uint32_t Status = 0;
//#define  sFLASH_ID              0xEF3015     //W25X16
//#define  sFLASH_ID              0xEF4015   //W25Q16
//#define  sFLASH_ID              0XEF4017    //W25Q64
//#define  sFLASH_ID              0XEF4018   //W25Q128
/* Private function prototypes -----------------------------------------------*/
/* Private functions ---------------------------------------------------------*/
/*******************************************************************************
* Function Name  : MAL_Init
* Description    : 初始化STM32上的媒体设备
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
uint16_t MAL_Init(uint8_t lun)
{uint16_t status = MAL_OK;switch (lun){case 0:FLASH_Unlock();break;default:return MAL_FAIL;}return status;
}
/*******************************************************************************
* Function Name  : MAL_Write
* Description    : Write sectors
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
uint16_t MAL_Write(uint8_t lun, uint32_t Memory_Offset, uint32_t *Writebuff, uint16_t Transfer_Length)
{ uint16_t i;switch (lun)
{
case 0: for(i=0;i<Transfer_Length;i+=FLASH_PAGE_SIZE) {if(FLASH_WaitForLastOperation(FLASH_WAIT_TIMEOUT)!=FLASH_TIMEOUT){ FLASH_ClearFlag(FLASH_FLAG_EOP|FLASH_FLAG_PGERR|FLASH_FLAG_WRPRTERR);} FLASH_ErasePage(FLASH_START_ADDR + Memory_Offset + i);}for(i=0;i<Transfer_Length;i+=4) {if(FLASH_WaitForLastOperation(FLASH_WAIT_TIMEOUT)!=FLASH_TIMEOUT){ FLASH_ClearFlag(FLASH_FLAG_EOP|FLASH_FLAG_PGERR| FLASH_FLAG_WRPRTERR);} FLASH_ProgramWord(FLASH_START_ADDR + Memory_Offset + i , Writebuff[i>>2]);
}break;
default: return MAL_FAIL;
}
return MAL_OK;
}/*******************************************************************************
* Function Name  : MAL_Read
* Description    : Read sectors
* Input          : None
* Output         : None
* Return         : Buffer pointer
*******************************************************************************/
uint16_t MAL_Read(uint8_t lun, uint32_t Memory_Offset, uint32_t *Readbuff, uint16_t Transfer_Length) { uint16_t i;switch (lun){ case 0:for(i=0;i<Transfer_Length;i+=4){ Readbuff[i>>2] = ((vu32*)(FLASH_START_ADDR + Memory_Offset))[i>>2]; }
break;default: return MAL_FAIL;}
return MAL_OK;}/*******************************************************************************
* Function Name  : MAL_GetStatus
* Description    : Get status
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
uint16_t MAL_GetStatus (uint8_t lun)
{ if (lun == 0){ Mass_Block_Count[0] = FLASH_SIZE/FLASH_PAGE_SIZE; Mass_Block_Size[0] = FLASH_PAGE_SIZE; Mass_Memory_Size[0] = FLASH_SIZE;return MAL_OK;} return MAL_FAIL; }/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

其余的文件只需要修改端点为前面设置的端点就好了。
当然要进行flash的操作还需要stm32f10x_flash文件。
这里还有一个fatfs_flash_spi.c文件

 /********************************************************************************* @file    bsp_xxx.c* @author  STMicroelectronics* @version V1.0* @date    2013-xx-xx* @brief   spi flash 底层应用函数bsp ******************************************************************************* @attention** 实验平台:野火 F103-指南者 STM32 开发板 * 论坛    :http://www.firebbs.cn* 淘宝    :https://fire-stm32.taobao.com********************************************************************************/#include <fatfs_flash_spi.h>
#include "stm32f10x_spi.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_rcc.h"static __IO uint32_t  SPITimeout = SPIT_LONG_TIMEOUT;
static uint16_t SPI_TIMEOUT_UserCallback(uint8_t errorCode);/*** @brief  SPI_FLASH初始化* @param  无* @retval 无*/
uint8_t FLASH_SPI_disk_initialize(void)
{SPI_InitTypeDef  SPI_InitStructure;GPIO_InitTypeDef GPIO_InitStructure;/* 使能SPI时钟 */FLASH_SPI_APBxClock_FUN ( FLASH_SPI_CLK, ENABLE );/* 使能SPI引脚相关的时钟 */FLASH_SPI_CS_APBxClock_FUN ( FLASH_SPI_CS_CLK|FLASH_SPI_SCK_CLK|FLASH_SPI_MISO_PIN|FLASH_SPI_MOSI_PIN, ENABLE );/* 配置SPI的 CS引脚,普通IO即可 */GPIO_InitStructure.GPIO_Pin = FLASH_SPI_CS_PIN;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_Init(FLASH_SPI_CS_PORT, &GPIO_InitStructure);/* 配置SPI的 SCK引脚*/GPIO_InitStructure.GPIO_Pin = FLASH_SPI_SCK_PIN;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_Init(FLASH_SPI_SCK_PORT, &GPIO_InitStructure);/* 配置SPI的 MISO引脚*/GPIO_InitStructure.GPIO_Pin = FLASH_SPI_MISO_PIN;GPIO_Init(FLASH_SPI_MISO_PORT, &GPIO_InitStructure);/* 配置SPI的 MOSI引脚*/GPIO_InitStructure.GPIO_Pin = FLASH_SPI_MOSI_PIN;GPIO_Init(FLASH_SPI_MOSI_PORT, &GPIO_InitStructure);/* 停止信号 FLASH: CS引脚高电平*/SPI_FLASH_CS_HIGH();/* SPI 模式配置 */// FLASH芯片 支持SPI模式0及模式3,据此设置CPOL CPHASPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;SPI_InitStructure.SPI_Mode = SPI_Mode_Master;SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2;SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;SPI_InitStructure.SPI_CRCPolynomial = 7;SPI_Init(FLASH_SPIx , &SPI_InitStructure);/* 使能 SPI  */SPI_Cmd(FLASH_SPIx , ENABLE);if(sFLASH_ID == SPI_FLASH_ReadID())          /*检测FLASH是否正常工作*/{return 0; /* Clear STA_NOINIT flag */}else{return 1;}
}/*** @brief  擦除FLASH扇区* @param  SectorAddr:要擦除的扇区地址* @retval 无*/
void SPI_FLASH_SectorErase(u32 SectorAddr)
{/* 发送FLASH写使能命令 */SPI_FLASH_WriteEnable();SPI_FLASH_WaitForWriteEnd();/* 擦除扇区 *//* 选择FLASH: CS低电平 */SPI_FLASH_CS_LOW();/* 发送扇区擦除指令*/SPI_FLASH_SendByte(W25X_SectorErase);/*发送擦除扇区地址的高位*/SPI_FLASH_SendByte((SectorAddr & 0xFF0000) >> 16);/* 发送擦除扇区地址的中位 */SPI_FLASH_SendByte((SectorAddr & 0xFF00) >> 8);/* 发送擦除扇区地址的低位 */SPI_FLASH_SendByte(SectorAddr & 0xFF);/* 停止信号 FLASH: CS 高电平 */SPI_FLASH_CS_HIGH();/* 等待擦除完毕*/SPI_FLASH_WaitForWriteEnd();
}/*** @brief  擦除FLASH扇区,整片擦除* @param  无* @retval 无*/
void SPI_FLASH_BulkErase(void)
{/* 发送FLASH写使能命令 */SPI_FLASH_WriteEnable();/* 整块 Erase *//* 选择FLASH: CS低电平 */SPI_FLASH_CS_LOW();/* 发送整块擦除指令*/SPI_FLASH_SendByte(W25X_ChipErase);/* 停止信号 FLASH: CS 高电平 */SPI_FLASH_CS_HIGH();/* 等待擦除完毕*/SPI_FLASH_WaitForWriteEnd();
}/*** @brief  对FLASH按页写入数据,调用本函数写入数据前需要先擦除扇区* @param   pBuffer,要写入数据的指针* @param WriteAddr,写入地址* @param  NumByteToWrite,写入数据长度,必须小于等于SPI_FLASH_PerWritePageSize* @retval 无*/
void SPI_FLASH_PageWrite(u8* pBuffer, u32 WriteAddr, u16 NumByteToWrite)
{/* 发送FLASH写使能命令 */SPI_FLASH_WriteEnable();/* 选择FLASH: CS低电平 */SPI_FLASH_CS_LOW();/* 写页写指令*/SPI_FLASH_SendByte(W25X_PageProgram);/*发送写地址的高位*/SPI_FLASH_SendByte((WriteAddr & 0xFF0000) >> 16);/*发送写地址的中位*/SPI_FLASH_SendByte((WriteAddr & 0xFF00) >> 8);/*发送写地址的低位*/SPI_FLASH_SendByte(WriteAddr & 0xFF);if(NumByteToWrite > SPI_FLASH_PerWritePageSize){NumByteToWrite = SPI_FLASH_PerWritePageSize;FLASH_ERROR("SPI_FLASH_PageWrite too large!"); }/* 写入数据*/while (NumByteToWrite--){/* 发送当前要写入的字节数据 */SPI_FLASH_SendByte(*pBuffer);/* 指向下一字节数据 */pBuffer++;}/* 停止信号 FLASH: CS 高电平 */SPI_FLASH_CS_HIGH();/* 等待写入完毕*/SPI_FLASH_WaitForWriteEnd();
}/*** @brief  对FLASH写入数据,调用本函数写入数据前需要先擦除扇区* @param pBuffer,要写入数据的指针* @param  WriteAddr,写入地址* @param  NumByteToWrite,写入数据长度* @retval 无*/
void SPI_FLASH_BufferWrite(u8* pBuffer, u32 WriteAddr, u16 NumByteToWrite)
{u8 NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;/*mod运算求余,若writeAddr是SPI_FLASH_PageSize整数倍,运算结果Addr值为0*/Addr = WriteAddr % SPI_FLASH_PageSize;/*差count个数据值,刚好可以对齐到页地址*/count = SPI_FLASH_PageSize - Addr;/*计算出要写多少整数页*/NumOfPage =  NumByteToWrite / SPI_FLASH_PageSize;/*mod运算求余,计算出剩余不满一页的字节数*/NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;/* Addr=0,则WriteAddr 刚好按页对齐 aligned  */if (Addr == 0){/* NumByteToWrite < SPI_FLASH_PageSize */if (NumOfPage == 0) {SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumByteToWrite);}else /* NumByteToWrite > SPI_FLASH_PageSize */{ /*先把整数页都写了*/while (NumOfPage--){SPI_FLASH_PageWrite(pBuffer, WriteAddr, SPI_FLASH_PageSize);WriteAddr +=  SPI_FLASH_PageSize;pBuffer += SPI_FLASH_PageSize;}/*若有多余的不满一页的数据,把它写完*/SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle);}}/* 若地址与 SPI_FLASH_PageSize 不对齐  */else {/* NumByteToWrite < SPI_FLASH_PageSize */if (NumOfPage == 0){/*当前页剩余的count个位置比NumOfSingle小,一页写不完*/if (NumOfSingle > count) {temp = NumOfSingle - count;/*先写满当前页*/SPI_FLASH_PageWrite(pBuffer, WriteAddr, count);WriteAddr +=  count;pBuffer += count;/*再写剩余的数据*/SPI_FLASH_PageWrite(pBuffer, WriteAddr, temp);}else /*当前页剩余的count个位置能写完NumOfSingle个数据*/{SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumByteToWrite);}}else /* NumByteToWrite > SPI_FLASH_PageSize */{/*地址不对齐多出的count分开处理,不加入这个运算*/NumByteToWrite -= count;NumOfPage =  NumByteToWrite / SPI_FLASH_PageSize;NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;/* 先写完count个数据,为的是让下一次要写的地址对齐 */SPI_FLASH_PageWrite(pBuffer, WriteAddr, count);/* 接下来就重复地址对齐的情况 */WriteAddr +=  count;pBuffer += count;/*把整数页都写了*/while (NumOfPage--){SPI_FLASH_PageWrite(pBuffer, WriteAddr, SPI_FLASH_PageSize);WriteAddr +=  SPI_FLASH_PageSize;pBuffer += SPI_FLASH_PageSize;}/*若有多余的不满一页的数据,把它写完*/if (NumOfSingle != 0){SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle);}}}
}/*** @brief  读取FLASH数据* @param   pBuffer,存储读出数据的指针* @param   ReadAddr,读取地址* @param   NumByteToRead,读取数据长度* @retval 无*/
void SPI_FLASH_BufferRead(u8* pBuffer, u32 ReadAddr, u16 NumByteToRead)
{/* 选择FLASH: CS低电平 */SPI_FLASH_CS_LOW();/* 发送 读 指令 */SPI_FLASH_SendByte(W25X_ReadData);/* 发送 读 地址高位 */SPI_FLASH_SendByte((ReadAddr & 0xFF0000) >> 16);/* 发送 读 地址中位 */SPI_FLASH_SendByte((ReadAddr& 0xFF00) >> 8);/* 发送 读 地址低位 */SPI_FLASH_SendByte(ReadAddr & 0xFF);/* 读取数据 */while (NumByteToRead--) /* while there is data to be read */{/* 读取一个字节*/*pBuffer = SPI_FLASH_SendByte(Dummy_Byte);/* 指向下一个字节缓冲区 */pBuffer++;}/* 停止信号 FLASH: CS 高电平 */SPI_FLASH_CS_HIGH();
}/*** @brief  读取FLASH ID* @param  无* @retval FLASH ID*/
u32 SPI_FLASH_ReadID(void)
{u32 Temp = 0, Temp0 = 0, Temp1 = 0, Temp2 = 0;/* 开始通讯:CS低电平 */SPI_FLASH_CS_LOW();/* 发送JEDEC指令,读取ID */SPI_FLASH_SendByte(W25X_JedecDeviceID);/* 读取一个字节数据 */Temp0 = SPI_FLASH_SendByte(Dummy_Byte);/* 读取一个字节数据 */Temp1 = SPI_FLASH_SendByte(Dummy_Byte);/* 读取一个字节数据 */Temp2 = SPI_FLASH_SendByte(Dummy_Byte);/* 停止通讯:CS高电平 */SPI_FLASH_CS_HIGH();/*把数据组合起来,作为函数的返回值*/Temp = (Temp0 << 16) | (Temp1 << 8) | Temp2;return Temp;
}/*** @brief  读取FLASH Device ID* @param   无* @retval FLASH Device ID*/
u32 SPI_FLASH_ReadDeviceID(void)
{u32 Temp = 0;/* Select the FLASH: Chip Select low */SPI_FLASH_CS_LOW();/* Send "RDID " instruction */SPI_FLASH_SendByte(W25X_DeviceID);SPI_FLASH_SendByte(Dummy_Byte);SPI_FLASH_SendByte(Dummy_Byte);SPI_FLASH_SendByte(Dummy_Byte);/* Read a byte from the FLASH */Temp = SPI_FLASH_SendByte(Dummy_Byte);/* Deselect the FLASH: Chip Select high */SPI_FLASH_CS_HIGH();return Temp;
}
/*******************************************************************************
* Function Name  : SPI_FLASH_StartReadSequence
* Description    : Initiates a read data byte (READ) sequence from the Flash.
*                  This is done by driving the /CS line low to select the device,
*                  then the READ instruction is transmitted followed by 3 bytes
*                  address. This function exit and keep the /CS line low, so the
*                  Flash still being selected. With this technique the whole
*                  content of the Flash is read with a single READ instruction.
* Input          : - ReadAddr : FLASH's internal address to read from.
* Output         : None
* Return         : None
*******************************************************************************/
void SPI_FLASH_StartReadSequence(u32 ReadAddr)
{/* Select the FLASH: Chip Select low */SPI_FLASH_CS_LOW();/* Send "Read from Memory " instruction */SPI_FLASH_SendByte(W25X_ReadData);/* Send the 24-bit address of the address to read from -----------------------*//* Send ReadAddr high nibble address byte */SPI_FLASH_SendByte((ReadAddr & 0xFF0000) >> 16);/* Send ReadAddr medium nibble address byte */SPI_FLASH_SendByte((ReadAddr& 0xFF00) >> 8);/* Send ReadAddr low nibble address byte */SPI_FLASH_SendByte(ReadAddr & 0xFF);
}/*** @brief  使用SPI读取一个字节的数据* @param  无* @retval 返回接收到的数据*/
u8 SPI_FLASH_ReadByte(void)
{return (SPI_FLASH_SendByte(Dummy_Byte));
}/*** @brief  使用SPI发送一个字节的数据* @param  byte:要发送的数据* @retval 返回接收到的数据*/
u8 SPI_FLASH_SendByte(u8 byte)
{SPITimeout = SPIT_FLAG_TIMEOUT;/* 等待发送缓冲区为空,TXE事件 */while (SPI_I2S_GetFlagStatus(FLASH_SPIx , SPI_I2S_FLAG_TXE) == RESET){if((SPITimeout--) == 0) return SPI_TIMEOUT_UserCallback(0);}/* 写入数据寄存器,把要写入的数据写入发送缓冲区 */SPI_I2S_SendData(FLASH_SPIx , byte);SPITimeout = SPIT_FLAG_TIMEOUT;/* 等待接收缓冲区非空,RXNE事件 */while (SPI_I2S_GetFlagStatus(FLASH_SPIx , SPI_I2S_FLAG_RXNE) == RESET){if((SPITimeout--) == 0) return SPI_TIMEOUT_UserCallback(1);}/* 读取数据寄存器,获取接收缓冲区数据 */return SPI_I2S_ReceiveData(FLASH_SPIx );
}/*** @brief  使用SPI发送两个字节的数据* @param  byte:要发送的数据* @retval 返回接收到的数据*/
u16 SPI_FLASH_SendHalfWord(u16 HalfWord)
{SPITimeout = SPIT_FLAG_TIMEOUT;/* 等待发送缓冲区为空,TXE事件 */while (SPI_I2S_GetFlagStatus(FLASH_SPIx , SPI_I2S_FLAG_TXE) == RESET){if((SPITimeout--) == 0) return SPI_TIMEOUT_UserCallback(2);}/* 写入数据寄存器,把要写入的数据写入发送缓冲区 */SPI_I2S_SendData(FLASH_SPIx , HalfWord);SPITimeout = SPIT_FLAG_TIMEOUT;/* 等待接收缓冲区非空,RXNE事件 */while (SPI_I2S_GetFlagStatus(FLASH_SPIx , SPI_I2S_FLAG_RXNE) == RESET){if((SPITimeout--) == 0) return SPI_TIMEOUT_UserCallback(3);}/* 读取数据寄存器,获取接收缓冲区数据 */return SPI_I2S_ReceiveData(FLASH_SPIx );
}/*** @brief  向FLASH发送 写使能 命令* @param  none* @retval none*/
void SPI_FLASH_WriteEnable(void)
{/* 通讯开始:CS低 */SPI_FLASH_CS_LOW();/* 发送写使能命令*/SPI_FLASH_SendByte(W25X_WriteEnable);/*通讯结束:CS高 */SPI_FLASH_CS_HIGH();
}/* WIP(busy)标志,FLASH内部正在写入 */
#define WIP_Flag                  0x01/*** @brief  等待WIP(BUSY)标志被置0,即等待到FLASH内部数据写入完毕* @param  none* @retval none*/
void SPI_FLASH_WaitForWriteEnd(void)
{u8 FLASH_Status = 0;/* 选择 FLASH: CS 低 */SPI_FLASH_CS_LOW();/* 发送 读状态寄存器 命令 */SPI_FLASH_SendByte(W25X_ReadStatusReg);/* 若FLASH忙碌,则等待 */do{/* 读取FLASH芯片的状态寄存器 */FLASH_Status = SPI_FLASH_SendByte(Dummy_Byte);   }while ((FLASH_Status & WIP_Flag) == SET);  /* 正在写入标志 *//* 停止信号  FLASH: CS 高 */SPI_FLASH_CS_HIGH();
}//进入掉电模式
void SPI_Flash_PowerDown(void)
{ /* 通讯开始:CS低 */SPI_FLASH_CS_LOW();/* 发送 掉电 命令 */SPI_FLASH_SendByte(W25X_PowerDown);/*通讯结束:CS高 */SPI_FLASH_CS_HIGH();
}   //唤醒
void SPI_Flash_WAKEUP(void)
{/*选择 FLASH: CS 低 */SPI_FLASH_CS_LOW();/* 发送 上电 命令 */SPI_FLASH_SendByte(W25X_ReleasePowerDown);/* 停止信号 FLASH: CS 高 */SPI_FLASH_CS_HIGH();
}   /*** @brief  等待超时回调函数* @param  None.* @retval None.*/
static  uint16_t SPI_TIMEOUT_UserCallback(uint8_t errorCode)
{/* 等待超时后的处理,输出错误信息 */FLASH_ERROR("SPI 等待超时!errorCode = %d",errorCode);return 0;
}/*********************************************END OF FILE**********************/

结束

USB复合设备(键盘鼠标U盘三合一)基于标准库相关推荐

  1. USB硬件 键盘鼠标控制器 V2.0 支持二次开发

    USB硬件 控制键盘鼠标 V2.0 支持二次开发提供开发包 usb键盘鼠标控制器由来: 听说过"按键精灵"这个软件,就不难理解了;就是这个思路,做一个真正的硬件键盘鼠标,然后我们控 ...

  2. USB硬件 键盘鼠标控制器 Lao-UKM V3.1 (增加高级客户功能)

    原理上和V2.0一样,通过串口发送命令,控制另一台电脑的键盘鼠标, 被控制的电脑不用装软件,因为控制器本身就是一个标准的usb键盘鼠标, 可以一台电脑控制多台计算机,也可以在一台电脑上使用. V3.1 ...

  3. STM32外挂FLASH模拟U盘(基于HAL库)

    STM32外挂FLASH模拟U盘(基于HAL库) 1.背景 1.1这篇文章能给你带来什么 1.2根据你要解决的问题,精确快速跳转到相应位置 1.3我在做完这个后还有不明白的地方,希望能有大触解答困惑 ...

  4. STM32F103VE基于标准库下DHT11数据串口打印输出

    STM32F103VE基于标准库下DHT11数据串口打印输出

  5. 手机刷linux插鼠标U盘,用OTG线连接键盘鼠标U盘 手机瞬间变电脑 自制otg线

    OTG是On-The-Go的缩写,是近年来发展起来的技术,主要应用是让不同的USB设备和移动设备之间连接,进行数据交换.目前,绝大多数的安卓手机都支持OTG,它可以连接各种USB设备,如鼠标.键盘.U ...

  6. python监控键盘输入_Python实现监控键盘鼠标操作示例【基于pyHook与pythoncom模块】...

    本文实例讲述了Python实现监控键盘鼠标操作.分享给大家供大家参考,具体如下: # -*- coding: utf-8 -*- import pythoncom import pyHook impo ...

  7. 图文详解STM32F0xx基于标准库新建工程

    之前从来没有接触过F0系列,更没有基于STM32F0标准库函数新建工程的经历,但是新建F1系列那还都是家常便饭,可是没有想到折腾了大半天才成功,最后记录一下吧.保证可以从无到有,详细记录每一个步骤. ...

  8. USB驱动——键盘,U盘

      文章转自 http://my.csdn.net/weiqing1981127 一 . USB 键盘 跟USB鼠标类型,USB键盘也属于HID类型,代码在/dirver/hid/usbhid/usb ...

  9. 关于USB虚拟键盘鼠标,在IOS上面遇FN键的问题实现。

    最近接了一个任务,就是拿USB单片机做一个鼠标和键盘.合成的,其实网上包括芯片生产商都有案例,太多了,但是,都没有谁提起怎么才能支持苹果手机(15以上系统)及苹果电脑的FN键问题.因为FN呢,本身没有 ...

最新文章

  1. telegraf监控mysql数据库_influxdb+grafana+telegraf 监听性能数据 (完整详细版)
  2. AI领域的人才短缺,原因是什么?该如何解决?
  3. java 建立网站_建立基本的Java Web站点
  4. java类如何enum_java – 如何模拟ENUM类中的方法?
  5. 精品软件 推荐 360 安全卫士
  6. java agent_如何脚踏实地构建Java Agent
  7. 常见的几种内排序算法以及实现(C语言)(转)
  8. 一文带你彻底了解大数据处理引擎Flink内存管理
  9. 程序默认在副屏显示_树莓派使用 OLED 屏显示图片及文字
  10. HDU-3466-Proud Merchants
  11. swift学习之元组
  12. Winform 导航菜单的方法
  13. 百兆以太网口通信速率_千兆以太网的传输速度
  14. IOC注入框架——Unity中的BuildUp与LifetimeManagers
  15. 路由器 telnet配置
  16. 如何提高用户逃离成本
  17. Python程序员难招人?月入30K程序员告诉你答案
  18. Bugzilla 使用教程
  19. 求1!+2!+3!......+20!
  20. unix cat命令

热门文章

  1. HBuilder webApp开发(十五)MUI增加自定义icon图标
  2. Android 实现APP内应用更新功能(支持Android7.0以上)
  3. SQL:根据消费类型不同统计人次
  4. 软件设计师习题笔记-重点习题四
  5. 神经网络和深度学习(5)-- 逻辑回归
  6. 【Golang入门】二、Go语言快速开发
  7. [课堂大数据] 苏格拉底教学分析系统
  8. Promox VE 一款不错的开源虚拟化软件
  9. win7锁定计算机后断网,win7系统自动锁屏断网的解决方法
  10. Doug Vitale技术博客:联网设备的默认密码(来自OEM)