键盘鼠标属于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盘改成3
0x01, //bConfiguration字段
0x00, //iConfigurationz字段
0xC0, //bmAttributes字段
0x32, //bMaxPower字段
/*******************第一个接口描述符(键盘)*********************/
0x09, //bLength字段
0x04, //bDescriptorType字段
0x00, //bInterfaceNumber字段 接口的编号,第一个接口为00
0x00, //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字段,第二个接口,为01
0x00, //bAlternateSetting字段
0x01, //bNumEndpoints字段,我的鼠标采用了一个端点,所以为01
0x03, //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;
else
CopyRoutine = Mouse_GetReportDescriptor;
}
else if (pInformation->USBwValue1 == HID_DESCRIPTOR_TYPE)
{
if (pInformation->USBwIndex0 == 0)
CopyRoutine = KP_GetHIDDescriptor;
else
CopyRoutine = 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;
else
CopyRoutine = Mouse_GetReportDescriptor;
}
else if (pInformation->USBwValue1 == HID_DESCRIPTOR_TYPE)
{
if (pInformation->USBwIndex0 == 0)
CopyRoutine = KP_GetHIDDescriptor;
else
CopyRoutine = 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文件文章来源:https://www.toymoban.com/news/detail-488153.html
/**
******************************************************************************
* @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 CPHA
SPI_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**********************/
结束文章来源地址https://www.toymoban.com/news/detail-488153.html
到了这里,关于USB复合设备(键盘鼠标U盘三合一)基于标准库的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!