本程序的目的是为了演示STM32 USB寄存器的使用方法以及SCSI命令的处理流程,程序只实现了基本的磁盘读写功能。
该USB设备使用了3个端点:ENDP0(Control型端点),EP1_IN和EP1_OUT(Bulk型端点)。
由于时间关系, 下面的无关紧要的功能还没做:
SCSI_REQUEST_SENSE命令只实现了返回"磁盘驱动器已弹出"的信息(也就是Table 203 Preferred TEST UNIT READY responses中的CHECK CONDITION - NOT READY - MEDIUM NOT PRESENT这一条信息)
读写磁盘时没有检验数据块的地址和长度是否有效
Verify10和verify12命令没有实现, 所以不能使用Windows的磁盘检查工具检查磁盘
USB的suspend/resume
如果要实现这些功能,保证磁盘使用的可靠性的话,请自行参考SCSI命令集的PDF文档以及STM32 USB标准库里面的大容量存储例程的代码
未实现的命令都会在串口中断中用dump_data函数将命令内容打印出来, 并且有\a字符的响铃声
参考文档:usb_20_081017/usb_20.pdf,请认真阅读其中的第八章,理清Bulk transfer和Control transfer的完整过程。设备描述符等数据结构请参阅第九章的表格。
Keil工程文件下载地址:/s/1c2yIKzY
电路连接:
USB接口最左侧的VBUS接+5V,通过AMS1117降压到3.3V给STM32F103C8单片机供电。D-通过22Ω的电阻接PA11,D+通过22Ω的电阻接PA12,D+还通过一个1.5kΩ的电阻接PB3,GND引脚接到单片机的GND上。
单片机晶振为8MHz,所用的串口是USART3,波特率为115200。
注意,程序中要慎用printf函数打印字符串,最好不要打印大量的字符串内容,否则由于设备响应主机不及时,USB将无法正常工作。
STM32F107、STM32F407等单片机上的USB OTG和STM32F103上的从USB的使用方法是不一样的。USB OTG的使用方法请参阅:STM32F107VC单片机上完全用寄存器实现的USB OTG Device模式的大容量存储设备
【勘误】
9月15日:USB.h中,USB_BufDesc应该定义为:
#define USB_BufDesc ((USB_BufferDescriptor *)(USB_PMAADDR + 2 * USB->BTABLE))
否则当USB->BTABLE不等于0时会出问题。
【main.c】
#include <stdio.h>#include <stm32f10x.h>#include <wchar.h>#include "USB.h"#include "usb_test.h"void dump_data(const void *data, uint16_t len){const uint8_t *p = data;while (len--)printf("%02X", *p++);printf("\a\n"); // \a: 响铃}int fputc(int ch, FILE *fp){if (fp == stdout){if (ch == '\n'){while ((USART3->SR & USART_SR_TXE) == 0);USART3->DR = '\r';}while ((USART3->SR & USART_SR_TXE) == 0);USART3->DR = ch;}return ch;}// Keil MDK使用微库时, 下面两个函数必须自己实现wchar_t *wcscpy(wchar_t *s1, const wchar_t *s2) // 复制UTF-16字符串{wchar_t *r = s1;while ((*r++ = *s2++) != 0);return s1;}size_t wcslen(const wchar_t *s) // 求UTF-16字符串的长度{size_t n = 0;while (*s++)n++;return n;}int main(void){uint8_t data;RCC->APB1ENR = RCC_APB1ENR_USART3EN | RCC_APB1ENR_USBEN;RCC->APB2ENR = RCC_APB2ENR_AFIOEN | RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN;AFIO->MAPR = AFIO_MAPR_SWJ_CFG_JTAGDISABLE; // 使用SWD调试接口, 禁用JTAG// USB引脚PA11~12无需配置GPIOB->BSRR = GPIO_BSRR_BS3; // PB3设为高电平GPIOB->CRH = 0x44444b44; // 串口发送引脚PB10设为复用推挽输出GPIOB->CRL = 0x44483444; // PB3为USB_DP上的上拉电阻, 高电平表明设备插入主机// USB_DP上的上拉电阻最好不要直接接高电平, 而是接到某个I/O口上(这里是PB3), 方便查看调试信息, 避免USB线拔来拔去USART3->BRR = 312; // 波特率: 115200USART3->CR1 = USART_CR1_UE | USART_CR1_TE | USART_CR1_RE;printf("STM32F103C8 USB\n");USB_CalcDiskAddr(); // 根据当前芯片的Flash大小, 自动计算磁盘数据存放位置USB_Init(); // 初始化USBwhile (1){if (USART3->SR & USART_SR_RXNE){data = USART3->DR;if (data == 'u')printf("USB->EP0R=0x%04x, USB->EP1R=0x%04x, USB->ISTR=0x%04x, USB->CNTR=0x%04x\n", USB->EP0R, USB->EP1R, USB->ISTR, USB->CNTR);}}}
【USB.h】
// STM32 CubeMX头文件中复制过来的USB寄存器定义typedef struct{__IO uint16_t EP0R; /*!< USB Endpoint 0 register, Address offset: 0x00 */ __IO uint16_t RESERVED0; /*!< Reserved */__IO uint16_t EP1R; /*!< USB Endpoint 1 register, Address offset: 0x04 */__IO uint16_t RESERVED1; /*!< Reserved */ __IO uint16_t EP2R; /*!< USB Endpoint 2 register, Address offset: 0x08 */__IO uint16_t RESERVED2; /*!< Reserved */ __IO uint16_t EP3R; /*!< USB Endpoint 3 register, Address offset: 0x0C */ __IO uint16_t RESERVED3; /*!< Reserved */ __IO uint16_t EP4R; /*!< USB Endpoint 4 register, Address offset: 0x10 */__IO uint16_t RESERVED4; /*!< Reserved */ __IO uint16_t EP5R; /*!< USB Endpoint 5 register, Address offset: 0x14 */__IO uint16_t RESERVED5; /*!< Reserved */ __IO uint16_t EP6R; /*!< USB Endpoint 6 register, Address offset: 0x18 */__IO uint16_t RESERVED6; /*!< Reserved */ __IO uint16_t EP7R; /*!< USB Endpoint 7 register, Address offset: 0x1C */__IO uint16_t RESERVED7[17]; /*!< Reserved */__IO uint16_t CNTR; /*!< Control register,Address offset: 0x40 */__IO uint16_t RESERVED8; /*!< Reserved */ __IO uint16_t ISTR; /*!< Interrupt status register, Address offset: 0x44 */__IO uint16_t RESERVED9; /*!< Reserved */ __IO uint16_t FNR; /*!< Frame number register, Address offset: 0x48 */__IO uint16_t RESERVEDA; /*!< Reserved */ __IO uint16_t DADDR;/*!< Device address register, Address offset: 0x4C */__IO uint16_t RESERVEDB; /*!< Reserved */ __IO uint16_t BTABLE;/*!< Buffer Table address register, Address offset: 0x50 */__IO uint16_t RESERVEDC; /*!< Reserved */ } USB_TypeDef;/* USB device FS */#define USB_BASE (APB1PERIPH_BASE + 0x00005C00U) /*!< USB_IP Peripheral Registers base address */#define USB_PMAADDR (APB1PERIPH_BASE + 0x00006000U) /*!< USB_IP Packet Memory Area base address */#define USB ((USB_TypeDef *)USB_BASE)// 对于单向双缓冲型的发送端点, 寄存器名称后缀都是TX; 单向双缓冲接收端点则都是RXtypedef struct{__IO uint16_t ADDR_TX;__IO uint16_t RESERVED0;__IO uint16_t COUNT_TX;__IO uint16_t RESERVED1;__IO uint16_t ADDR_RX;__IO uint16_t RESERVED2;__IO uint16_t COUNT_RX;__IO uint16_t RESERVED3;} USB_BufferDescriptor;#define USB_BufDesc ((USB_BufferDescriptor *)(USB_PMAADDR + USB->BTABLE))#define USB_ISTR_MASK 0x7f00#define USB_EPnR_MASK_T 0x8f8f // 防止翻转位发生翻转用的掩码#define USB_EPnR_MASK_CW0 0x8080 // 防止rc_w0型的位被清零#define USB_EPnR(reg) ((reg & USB_EPnR_MASK_T) | USB_EPnR_MASK_CW0)
【usb_def.h】
/********************************************************************************* @file usb_def.h* @author MCD Application Team* @version V4.1.0* @date 26-May-* @brief Definitions related to USB Core******************************************************************************* @attention** <h2><center>© COPYRIGHT(c) STMicroelectronics</center></h2>** Redistribution and use in source and binary forms, with or without modification,* are permitted provided that the following conditions are met:* 1. Redistributions of source code must retain the above copyright notice,*this list of conditions and the following disclaimer.* 2. Redistributions in binary form must reproduce the above copyright notice,*this list of conditions and the following disclaimer in the documentation*and/or other materials provided with the distribution.* 3. Neither the name of STMicroelectronics nor the names of its contributors*may be used to endorse or promote products derived from this software*without specific prior written permission.** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.********************************************************************************//* Define to prevent recursive inclusion -------------------------------------*/#ifndef __USB_DEF_H#define __USB_DEF_H/* Includes ------------------------------------------------------------------*//* Exported types ------------------------------------------------------------*/typedef enum _RECIPIENT_TYPE{DEVICE_RECIPIENT,/* Recipient device */INTERFACE_RECIPIENT, /* Recipient interface */ENDPOINT_RECIPIENT, /* Recipient endpoint */OTHER_RECIPIENT} RECIPIENT_TYPE;typedef enum _STANDARD_REQUESTS{GET_STATUS = 0,CLEAR_FEATURE,RESERVED1,SET_FEATURE,RESERVED2,SET_ADDRESS,GET_DESCRIPTOR,SET_DESCRIPTOR,GET_CONFIGURATION,SET_CONFIGURATION,GET_INTERFACE,SET_INTERFACE,TOTAL_sREQUEST, /* Total number of Standard request */SYNCH_FRAME = 12} STANDARD_REQUESTS;/* Definition of "USBwValue" */typedef enum _DESCRIPTOR_TYPE{DEVICE_DESCRIPTOR = 1,CONFIG_DESCRIPTOR,STRING_DESCRIPTOR,INTERFACE_DESCRIPTOR,ENDPOINT_DESCRIPTOR,DEVICE_BOS_DESCRIPTOR = 0xF} DESCRIPTOR_TYPE;/* Feature selector of a SET_FEATURE or CLEAR_FEATURE */typedef enum _FEATURE_SELECTOR{ENDPOINT_STALL,DEVICE_REMOTE_WAKEUP} FEATURE_SELECTOR;/* Exported constants --------------------------------------------------------*//* Definition of "USBbmRequestType" */#define REQUEST_TYPE0x60 /* Mask to get request type */#define STANDARD_REQUEST 0x00 /* Standard request */#define CLASS_REQUEST0x20 /* Class request */#define VENDOR_REQUEST 0x40 /* Vendor request */#define RECIPIENT 0x1F /* Mask to get recipient *//* Exported macro ------------------------------------------------------------*//* Exported functions ------------------------------------------------------- */#endif /* __USB_DEF_H *//************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
【usb_test.h】
#define ntohlp(p) ((*(p) << 24) | (*((p) + 1) << 16) | (*((p) + 2) << 8) | *((p) + 3)) // 32位大端序转小端序 (p为uint8_t *)#define ntohsp(p) ((*(p) << 8) | *((p) + 1)) // 16位大端序转小端序 (p为uint8_t *)typedef enum{USB_UNSUPPORTED = -2, // 未处理 (不支持的操作), 将会由dump_data打印出来USB_ERROR = -1 // 已处理但遇到错误} USB_Error;typedef struct{uint8_t *data_rx;uint8_t *data_tx;int32_t len_rx;int32_t len_tx;} USB_EndpointData;typedef __packed struct{uint8_t bmRequestType;uint8_t bRequest;uint16_t wValue;uint16_t wIndex;uint16_t wLength;} USB_Request;typedef __packed struct{uint8_t bLength;uint8_t bDescriptorType;uint16_t wTotalLength;uint8_t bNumInterfaces;uint8_t bConfigurationValue;uint8_t iConfiguration;uint8_t bmAttributes;uint8_t bMaxPower;} USB_ConfigurationDescriptor;typedef __packed struct{uint8_t bLength;uint8_t bDescriptorType;uint16_t bcdUSB;uint8_t bDeviceClass;uint8_t bDeviceSubClass;uint8_t bDeviceProtocol;uint8_t bMaxPacketSize0;uint16_t idVendor;uint16_t idProduct;uint16_t bcdDevice;uint8_t iManufacturer;uint8_t iProduct;uint8_t iSerialNumber;uint8_t bNumConfigurations;} USB_DeviceDescriptor;typedef __packed struct{uint8_t bLength;uint8_t bDescriptorType;uint8_t bEndpointAddress;uint8_t bmAttributes;uint16_t wMaxPacketSize;uint8_t bInterval;} USB_EndpointDescriptor;typedef __packed struct{uint8_t bLength;uint8_t bDescriptorType;uint8_t bInterfaceNumber;uint8_t bAlternateSetting;uint8_t bNumEndpoints;uint8_t bInterfaceClass;uint8_t bInterfaceSubClass;uint8_t bInterfaceProtocol;uint8_t iInterface;} USB_InterfaceDescriptor;typedef __packed struct{uint8_t bLength;uint8_t bDescriptorType;uint16_t wData[1];} USB_StringDescriptor;/* Command Block Wrapper */typedef __packed struct{uint32_t dCBWSignature;uint32_t dCBWTag;uint32_t dCBWDataTransferLength;uint8_t bmCBWFlags;uint8_t bCBWLUN;uint8_t bCBWCBLength;uint8_t CBWCB[16];} USB_CBW;/* Command Status Wrapper */typedef __packed struct{uint32_t dCSWSignature;uint32_t dCSWTag;uint32_t dCSWDataResidue;uint8_t bCSWStatus;} USB_CSW;/* List of SCSI commands */// /wiki/SCSI_command (包括External links下面的一个PDF文档)typedef enum{SCSI_TEST_UNIT_READY = 0x00,SCSI_REQUEST_SENSE = 0x03,SCSI_INQUIRY = 0x12,SCSI_MODE_SENSE6 = 0x1a,SCSI_START_STOP_UNIT = 0x1b,SCSI_PREVENT_ALLOW_MEDIUM_REMOVAL = 0x1e,SCSI_READ_FORMAT_CAPACITIES = 0x23,SCSI_READ_CAPACITY10 = 0x25,SCSI_READ10 = 0x28,SCSI_WRITE10 = 0x2a} SCSI_CommandCode;void USB_CalcDiskAddr(void);void USB_CorrectTransfer(void);void USB_ENDP0In(uint16_t len);void USB_ENDP0Out(const uint8_t *data, uint16_t len);void USB_ENDP0Setup(const uint8_t *data, uint16_t len);void USB_ENDP1In(uint16_t len);void USB_ENDP1Out(const uint8_t *data, uint16_t len);void USB_ENDP1ReceiveData(const uint8_t *data, uint16_t len);void USB_ENDP1SendData(void);int16_t USB_GetDescriptor(const USB_Request *req, void *buffer);void USB_Init(void);void USB_ReadPMA(uint16_t usbaddr, void *buffer, uint16_t len);void USB_WritePMA(uint16_t usbaddr, const void *buffer, uint16_t len);
【usb_test.c】
#include <stdio.h>#include <stm32f10x.h>#include <string.h>#include <wchar.h>#include "USB.h"#include "usb_def.h"#include "usb_test.h"//#define ENDP1_DEBUG // 显示端点1收发的数据量//#define SCSI_READ_DEBUG // 显示磁盘的读取情况#define SCSI_WRITE_DEBUG // 显示磁盘的写入情况// 容量千万不要设置的太小, 否则将无法完成格式化!!!! (操作系统根本就不会发送CMD2AH命令)#define DISK_BLOCKCOUNT 48#define DISK_BLOCKSIZE 1024static uint8_t usb_ejected = 0; // 磁盘是否已弹出static uint8_t usb_pending_addr = 0; // 用于临时存放主机分发的设备地址static uint8_t *usb_disk; // 磁盘的存放位置 (自动计算)static USB_CSW usb_csw = {0}; // 存放SCSI命令的CSW结果static USB_EndpointData usb_endp1 = {0}; // 端点1的剩余数据量void dump_data(const void *data, uint16_t len);void USB_CalcDiskAddr(void){uint32_t flash_size = *(uint16_t *)0x1ffff7e0; // flash memory size in Kbytesuint32_t disk_size = DISK_BLOCKCOUNT * DISK_BLOCKSIZE; // disk size in bytesusb_disk = (uint8_t *)0x8000000 + flash_size * 1024 - disk_size;printf("Flash Size: %dKB, Disk Size: %.1fKB, Disk Addr: 0x%p\n", flash_size, disk_size / 1024.0, usb_disk);if (usb_disk < (uint8_t *)0x8000000){printf("Error! Disk size is too large!!!\n");while (1);}// 解锁FlashFLASH->KEYR = 0x45670123;FLASH->KEYR = 0xcdef89ab;if ((FLASH->CR & FLASH_CR_LOCK) == 0)printf("Flash is unlocked!\n");}void USB_CorrectTransfer(void){uint8_t buffer[64];uint8_t ep_id;uint16_t len;ep_id = USB->ISTR & USB_ISTR_EP_ID; // 端点号if (ep_id == 0){if (USB->ISTR & USB_ISTR_DIR){// 端点0接收成功len = USB_BufDesc[0].COUNT_RX & USB_COUNT0_RX_COUNT0_RX; // 收到的字节数if (len > 0)USB_ReadPMA(USB_BufDesc[0].ADDR_RX, buffer, len);if (USB->EP0R & USB_EP0R_SETUP){USB->EP0R = USB_EPnR(USB->EP0R) & ~USB_EP0R_CTR_RX; // 清除中断标志位USB_ENDP0Setup(buffer, len);}else{USB->EP0R = USB_EPnR(USB->EP0R) & ~USB_EP0R_CTR_RX;USB_ENDP0Out(buffer, len);}}else{// 端点0发送成功len = USB_BufDesc[0].COUNT_TX; // 发送的字节数USB->EP0R = USB_EPnR(USB->EP0R) & ~USB_EP0R_CTR_TX;USB_ENDP0In(len);}}else if (ep_id == 1){if (USB->ISTR & USB_ISTR_DIR){// 端点1接收成功len = USB_BufDesc[1].COUNT_RX & USB_COUNT1_RX_COUNT1_RX;if (len > 0)USB_ReadPMA(USB_BufDesc[1].ADDR_RX, buffer, len);USB->EP1R = USB_EPnR(USB->EP1R) & ~USB_EP1R_CTR_RX;USB_ENDP1Out(buffer, len);}else{// 端点1发送成功len = USB_BufDesc[1].COUNT_TX;USB->EP1R = USB_EPnR(USB->EP1R) & ~USB_EP1R_CTR_TX;USB_ENDP1In(len);}}}void USB_ENDP0In(uint16_t len){printf("0-%d\n", len);if (usb_pending_addr != 0){// 主机在某个control transfer的data stage期间将分配的设备地址发送给设备// 但设备必须在status stage完成后才将地址写入寄存器USB->DADDR = USB_DADDR_EF | usb_pending_addr;printf("DADDR_%02X\n", usb_pending_addr);usb_pending_addr = 0;}// 当data stage的最后一个transaction为IN token时(在本程序中所有的control transfer的data stage都最多只有一个transaction), 应将STATUS_OUT置位// 这样在接下来的status stage的data phase中如果主机发送的数据长度不为0, 则设备不会回应ACK, 而是报告错误if (len != 0)USB->EP0R = USB_EPnR(USB->EP0R) | USB_EP0R_EP_KIND; // STATUS_OUT=1USB->EP0R = USB_EPnR(USB->EP0R) | ((USB->EP0R & USB_EP0R_STAT_RX) ^ USB_EP0R_STAT_RX); // RX=VALID}void USB_ENDP0Out(const uint8_t *data, uint16_t len){printf("0+%d\n", len);if (len == 0){// 收到一个空包USB->EP0R = (USB_EPnR(USB->EP0R) & ~USB_EP0R_EP_KIND) | ((USB->EP0R & (USB_EP0R_STAT_RX | USB_EP0R_STAT_TX)) ^ (USB_EP0R_STAT_RX | USB_EP0R_STAT_TX_1)); // RX=VALID, TX=NAK, STATUS_OUT=0}elsedump_data(data, len);}void USB_ENDP0Setup(const uint8_t *data, uint16_t len){int16_t size = USB_UNSUPPORTED; // 发送的数据大小: -2表示未处理, -1表示已处理但出错, 0表示发送空包uint8_t buffer[64];const USB_Request *req = (const USB_Request *)data;printf("0S+%d\n", len);if ((req->bmRequestType & REQUEST_TYPE) == STANDARD_REQUEST){if ((req->bmRequestType & RECIPIENT) == DEVICE_RECIPIENT){// 标准设备请求switch (req->bRequest){case GET_DESCRIPTOR:size = USB_GetDescriptor(req, buffer);break;case SET_ADDRESS:usb_pending_addr = req->wValue; // 先暂存USB设备地址, 必须等到Status stage结束后才能写入USB->DADDR寄存器中size = 0;break;case SET_CONFIGURATION:printf("CFG%hd\n", req->wValue);size = 0;}}else if ((req->bmRequestType & RECIPIENT) == ENDPOINT_RECIPIENT){// 标准端点请求if (req->bRequest == CLEAR_FEATURE && req->wValue == ENDPOINT_STALL) // 主机请求设备撤销某个端点上的STALL状态{if (req->wIndex == 0x81) // END1_IN{USB->EP1R = USB_EPnR(USB->EP1R) | ((USB->EP1R & USB_EP1R_STAT_TX) ^ USB_EP1R_STAT_TX_1); // 从STALL恢复为NAKsize = 0;}}}}else if ((req->bmRequestType & REQUEST_TYPE) == CLASS_REQUEST && (req->bmRequestType & RECIPIENT) == INTERFACE_RECIPIENT){// PDF: Universal Serial Bus Mass Storage Class Bulk-Only Transport// Section: 3 Functional Characteristicsif (req->bRequest == 0xfe){// 3.2 Get Max LUN (class-specific request)buffer[0] = 0;size = 1;}}if (size >= 0){USB_BufDesc[0].COUNT_TX = size;if (size > 0)USB_WritePMA(USB_BufDesc[0].ADDR_TX, buffer, size);USB->EP0R = USB_EPnR(USB->EP0R) | ((USB->EP0R & USB_EP0R_STAT_TX) ^ USB_EP0R_STAT_TX); // TX=VALID}else if (size == USB_UNSUPPORTED)dump_data(req, len); // 打印未处理的请求内容}void USB_ENDP1In(uint16_t len){#ifdef ENDP1_DEBUGstatic uint8_t newline = 0;#endifUSB_ENDP1SendData(); // 发送剩余数据// 显示上次发送成功的数据包大小#ifdef ENDP1_DEBUGif (len == 64){printf("*"); // 用星号代替64字节的数据包, 节约屏幕显示空间newline = 1;}else{if (newline){newline = 0;printf("\n");}printf("1-%d\n", len);}#endif}void USB_ENDP1Out(const uint8_t *data, uint16_t len){const USB_CBW *cbw = (const USB_CBW *)data;uint8_t buffer[64];uint32_t block_addr, block_len;if (usb_endp1.len_rx == 0){/* 接收命令 */usb_endp1.data_tx = buffer;usb_endp1.len_tx = USB_UNSUPPORTED;#ifdef ENDP1_DEBUGprintf("1+%d\n", len);printf("CMD%02XH\n", cbw->CBWCB[0]);#endifswitch (cbw->CBWCB[0]){case SCSI_TEST_UNIT_READY:/* 3.53 TEST UNIT READY command */usb_endp1.len_tx = (usb_ejected) ? USB_ERROR : 0;break;case SCSI_REQUEST_SENSE:/* 3.37 REQUEST SENSE command */// Test ready失败后会收到的命令if (cbw->CBWCB[1] == 0) // DESC=0{/* fixed format sense data (see 2.4.1.2) */memset(buffer, 0, 18);buffer[0] = 0x70; // response codebuffer[7] = 0x0a; // additional sense lengthif (usb_ejected){// 在我的电脑里面的可移动磁盘上选择弹出命令后, 磁盘应该自动消失 (这相当于只取出磁盘, 不取出USB读卡器)// 但USB设备仍会保持Configured状态, 并且还会持续收到CMD00H和CMD03H命令, 等待用户在读卡器上插入新的磁盘buffer[2] = 0x02; // sense key: not readybuffer[12] = 0x3a; // additional sense code: medium not present// 只有在系统托盘里面选择弹出磁盘时, USB设备才会从Configured状态回到Address状态, 串口会输出CFG0}usb_endp1.len_tx = 18;}break;case SCSI_INQUIRY:/* 3.6 INQUIRY command */if (cbw->CBWCB[1] & 0x01) // EVPD=1{/* 5.4 Vital product data *//* 5.4.18Supported Vital Product Data pages (00h) */// 不管请求的page code是什么, 都只发送Page 00Hmemset(buffer, 0, 5);buffer[3] = 1; // page lengthusb_endp1.len_tx = 5;}else{buffer[0] = 0x00; // connected & direct access block devicebuffer[1] = 0x80; // removablebuffer[2] = 0x02; // versionbuffer[3] = 0x02; // NORMACA & HISUP & response data formatbuffer[4] = 32; // additional lengthbuffer[5] = 0;buffer[6] = 0;buffer[7] = 0;strcpy((char *)buffer + 8, "HY-Smart"); // 8-byte vendor identificationstrcpy((char *)buffer + 16, "SPI Flash Disk "); // 16-byte product identificationstrcpy((char *)buffer + 32, "1.0 "); // 4-byte product revision levelusb_endp1.len_tx = 36;}break;case SCSI_MODE_SENSE6:/* 3.11 MODE SENSE(6) command */buffer[0] = 0x03;memset(buffer + 1, 0, 3);usb_endp1.len_tx = 4;break;case SCSI_START_STOP_UNIT:/* 3.49 START STOP UNIT command */// 弹出磁盘的命令usb_ejected = 1;usb_endp1.len_tx = 0;break;case SCSI_PREVENT_ALLOW_MEDIUM_REMOVAL:usb_endp1.len_tx = 0;break;case SCSI_READ_FORMAT_CAPACITIES:buffer[0] = 0;buffer[1] = 0;buffer[2] = 0;buffer[3] = 8; // capacity list lengthbuffer[4] = (DISK_BLOCKCOUNT >> 24) & 0xff;buffer[5] = (DISK_BLOCKCOUNT >> 16) & 0xff;buffer[6] = (DISK_BLOCKCOUNT >> 8) & 0xff;buffer[7] = DISK_BLOCKCOUNT & 0xff;buffer[8] = 0x02; // descriptor code: formatted mediabuffer[9] = (DISK_BLOCKSIZE >> 16) & 0xff;buffer[10] = (DISK_BLOCKSIZE >> 8) & 0xff;buffer[11] = DISK_BLOCKSIZE & 0xff;usb_endp1.len_tx = 12;break;case SCSI_READ_CAPACITY10:/* 3.22 READ CAPACITY (10) command */buffer[0] = ((DISK_BLOCKCOUNT - 1) >> 24) & 0xff; // the logical block address of the LAST logical blockbuffer[1] = ((DISK_BLOCKCOUNT - 1) >> 16) & 0xff;buffer[2] = ((DISK_BLOCKCOUNT - 1) >> 8) & 0xff;buffer[3] = (DISK_BLOCKCOUNT - 1) & 0xff;buffer[4] = (DISK_BLOCKSIZE >> 24) & 0xff; // block length in bytesbuffer[5] = (DISK_BLOCKSIZE >> 16) & 0xff;buffer[6] = (DISK_BLOCKSIZE >> 8) & 0xff;buffer[7] = DISK_BLOCKSIZE & 0xff;usb_endp1.len_tx = 8;break;case SCSI_READ10:/* 3.16 READ (10) command *//*if (address is invalid){// 请求的地址不合法时应返回错误, 并将EP1_IN设为STALL// 然后主机会在端点0上请求清除掉端点1的STALL状态usb_endp1.len_tx = USB_ERROR;break;}*/block_addr = ntohlp(cbw->CBWCB + 2);block_len = ntohsp(cbw->CBWCB + 7);usb_endp1.data_tx = usb_disk + block_addr * DISK_BLOCKSIZE; // logical block addressusb_endp1.len_tx = block_len * DISK_BLOCKSIZE; // transfer length#ifdef SCSI_READ_DEBUGprintf("R%d,%d\n", block_addr, block_len);#endifbreak;case SCSI_WRITE10:/* 3.60 WRITE (10) command */// 当地址不合法时, 应该将EP_OUT设为STALL, 也就是将后续的所有文件数据全部STALL (这个还没实现....)block_addr = ntohlp(cbw->CBWCB + 2);block_len = ntohsp(cbw->CBWCB + 7);usb_endp1.data_rx = usb_disk + block_addr * DISK_BLOCKSIZE; // Flash地址usb_endp1.len_rx = block_len * DISK_BLOCKSIZE;#ifdef SCSI_WRITE_DEBUGprintf("W%d,%d\n", block_addr, block_len);#endifusb_endp1.len_tx = 0;break;}}else{/* 接收数据 */usb_endp1.len_tx = 0;USB_ENDP1ReceiveData(data, len);}if (usb_endp1.len_tx >= 0){if (usb_endp1.len_tx > cbw->dCBWDataTransferLength)usb_endp1.len_tx = cbw->dCBWDataTransferLength;// 准备好数据发送完毕后要发送的CSWif (usb_csw.dCSWSignature == 0){usb_csw.dCSWSignature = 0x53425355;usb_csw.dCSWTag = cbw->dCBWTag;if (usb_endp1.len_rx == 0)usb_csw.dCSWDataResidue = cbw->dCBWDataTransferLength - usb_endp1.len_tx; // 未发送的数据量elseusb_csw.dCSWDataResidue = 0; // 未接收的数据量usb_csw.bCSWStatus = 0; // Command Passed}if (usb_endp1.len_rx == 0)USB_ENDP1SendData();}else if (usb_endp1.len_tx == USB_ERROR){// 遇到错误时, 先发送CSW, 然后将IN端点设为STALLusb_csw.dCSWSignature = 0x53425355;usb_csw.dCSWTag = cbw->dCBWTag;usb_csw.dCSWDataResidue = cbw->dCBWDataTransferLength;usb_csw.bCSWStatus = 1; // Command FailedUSB_ENDP1SendData();}else if (usb_endp1.len_tx == USB_UNSUPPORTED)dump_data(data, len);USB->EP1R = USB_EPnR(USB->EP1R) | ((USB->EP1R & USB_EP1R_STAT_RX) ^ USB_EP1R_STAT_RX); // RX=VALID}/* ENDP1接收大量数据 */void USB_ENDP1ReceiveData(const uint8_t *data, uint16_t len){if (len < usb_endp1.len_rx)usb_endp1.len_rx -= len;elseusb_endp1.len_rx = 0;// 擦除Flash页if ((uint32_t)usb_endp1.data_rx % DISK_BLOCKSIZE == 0){FLASH->CR |= FLASH_CR_PER;FLASH->AR = (uint32_t)usb_endp1.data_rx;FLASH->CR |= FLASH_CR_STRT;while (FLASH->SR & FLASH_SR_BSY);FLASH->CR &= ~FLASH_CR_PER;}// 将收到的数据写入Flash中FLASH->CR |= FLASH_CR_PG;while (len){*(uint16_t *)usb_endp1.data_rx = *(const uint16_t *)data;data += 2;usb_endp1.data_rx += 2;len -= 2;while (FLASH->SR & FLASH_SR_BSY);}FLASH->CR &= ~FLASH_CR_PG;usb_endp1.data_rx += len;}/* ENDP1发送大量数据 */void USB_ENDP1SendData(void){if (usb_endp1.len_tx > 64){// 发送一个数据包USB_BufDesc[1].COUNT_TX = 64;USB_WritePMA(USB_BufDesc[1].ADDR_TX, usb_endp1.data_tx, 64);usb_endp1.data_tx += 64;usb_endp1.len_tx -= 64;}else if (usb_endp1.len_tx > 0){// 发送最后一个数据包USB_BufDesc[1].COUNT_TX = usb_endp1.len_tx;USB_WritePMA(USB_BufDesc[1].ADDR_TX, usb_endp1.data_tx, usb_endp1.len_tx);usb_endp1.len_tx = 0;}else if ((usb_endp1.len_tx == 0 || usb_endp1.len_tx == USB_ERROR) && usb_csw.dCSWSignature != 0){// 发送CSW状态信息USB_BufDesc[1].COUNT_TX = sizeof(usb_csw);USB_WritePMA(USB_BufDesc[1].ADDR_TX, &usb_csw, sizeof(usb_csw));usb_csw.dCSWSignature = 0; // 表示下次不再发送CSW}else if (usb_endp1.len_tx == USB_ERROR && usb_csw.dCSWSignature == 0){// 处理命令时遇到错误, 且已发送CSWUSB->EP1R = USB_EPnR(USB->EP1R) | ((USB->EP1R & USB_EP1R_STAT_TX) ^ USB_EP1R_STAT_TX_0); // STALL后续的所有IN token// 接下来端点0将收到clear ENDPOINT_HALT feature的请求return;}elsereturn; // 结束发送USB->EP1R = USB_EPnR(USB->EP1R) | ((USB->EP1R & USB_EP1R_STAT_TX) ^ USB_EP1R_STAT_TX); // TX=VALID}int16_t USB_GetDescriptor(const USB_Request *req, void *buffer){int16_t size = USB_UNSUPPORTED;uint8_t type = req->wValue >> 8; // 高8位为请求的描述符类型USB_ConfigurationDescriptor *config = buffer;USB_DeviceDescriptor *device = buffer;USB_EndpointDescriptor *endpoints;USB_InterfaceDescriptor *interface;USB_StringDescriptor *str = buffer;switch (type){case DEVICE_DESCRIPTOR:device->bLength = sizeof(USB_DeviceDescriptor);device->bDescriptorType = DEVICE_DESCRIPTOR;device->bcdUSB = 0x200; // USB 2.0device->bDeviceClass = 0;device->bDeviceSubClass = 0;device->bDeviceProtocol = 0;device->bMaxPacketSize0 = 64;device->idVendor = 0x483; // STMicroelectronics (http://www.linux-/usb.ids)device->idProduct = 0x5720; // STM microSD Flash Devicedevice->bcdDevice = 0x200;device->iManufacturer = 1; // 制造商名称字符串序号device->iProduct = 2; // 产品名字符串序号device->iSerialNumber = 3; // 产品序列号字符串序号device->bNumConfigurations = 1; // 配置数size = device->bLength;break;case CONFIG_DESCRIPTOR:config->bLength = sizeof(USB_ConfigurationDescriptor);config->bDescriptorType = CONFIG_DESCRIPTOR;config->wTotalLength = sizeof(USB_ConfigurationDescriptor) + sizeof(USB_InterfaceDescriptor) + 2 * sizeof(USB_EndpointDescriptor);config->bNumInterfaces = 1; // 接口数config->bConfigurationValue = 1; // 此配置的编号config->iConfiguration = 0; // 配置名字符串序号(0表示没有)config->bmAttributes = 0xc0; // self-poweredconfig->bMaxPower = 50; // 最大电流: 100mAinterface = (USB_InterfaceDescriptor *)(config + 1);interface->bLength = sizeof(USB_InterfaceDescriptor);interface->bDescriptorType = INTERFACE_DESCRIPTOR;interface->bInterfaceNumber = 0; // 此接口的编号interface->bAlternateSetting = 0; // 可用的备用接口编号interface->bNumEndpoints = 2; // 除了端点0外, 此接口还需要的端点数 (EP1_IN和EP1_OUT分别算一个端点); 实际上就是endpoints数组的元素个数interface->bInterfaceClass = 0x08; // Mass Storage devicesinterface->bInterfaceSubClass = 0x06; // SCSI transparent command setinterface->bInterfaceProtocol = 0x50; // USB Mass Storage Class Bulk-Only (BBB) Transportinterface->iInterface = 4; // 接口名称字符串序号// 注意: 这里不能出现端点0的描述符endpoints = (USB_EndpointDescriptor *)(interface + 1);endpoints[0].bLength = sizeof(USB_EndpointDescriptor);endpoints[0].bDescriptorType = ENDPOINT_DESCRIPTOR;endpoints[0].bEndpointAddress = 0x81; // IN, address 1endpoints[0].bmAttributes = 0x02; // Bulkendpoints[0].wMaxPacketSize = 64;endpoints[0].bInterval = 0; // Does not apply to Bulk endpointsendpoints[1].bLength = sizeof(USB_EndpointDescriptor);endpoints[1].bDescriptorType = ENDPOINT_DESCRIPTOR;endpoints[1].bEndpointAddress = 0x01; // OUT, address 1endpoints[1].bmAttributes = 0x02; // Bulkendpoints[1].wMaxPacketSize = 64;endpoints[1].bInterval = 0;size = config->wTotalLength;break;case STRING_DESCRIPTOR:str->bDescriptorType = STRING_DESCRIPTOR;if (req->wIndex == 0x409) // 字符串英文内容{// 字符串的编码为UTF-16switch (req->wValue & 0xff) // 低8位为字符串序号{case 1:wcscpy((wchar_t *)str->wData, L"Hello Manufacturer!");break;case 2:wcscpy((wchar_t *)str->wData, L"Hello Product!");break;case 3:wcscpy((wchar_t *)str->wData, L"Hello SerialNumber!");break;case 4:wcscpy((wchar_t *)str->wData, L"Hello Interface!");break;default:printf("STR_%d\n", req->wValue & 0xff);wcscpy((wchar_t *)str->wData, L"???");}str->bLength = 2 + wcslen((wchar_t *)str->wData) * 2;}else if (req->wIndex == 0) // 字符串语言列表{str->bLength = 4;str->wData[0] = 0x0409; // English (United States)}elsebreak;size = str->bLength;break;default:// 包括Device qualifier (full-speed设备不支持)USB->EP0R = USB_EPnR(USB->EP0R) | ((USB->EP0R & USB_EP0R_STAT_TX) ^ USB_EP0R_STAT_TX_0); // STAT_TX设为STALLsize = USB_ERROR;}// 发送的字节数不能超过主机要求的最大长度if (size > req->wLength)size = req->wLength; // 只修改发送长度, 内容原封不动, 切记!!!!// 比如在请求字符串语言列表时, 待发送的数据量是str->bLength=4// 如果主机要求最大只能发送req->wLength=2字节, 则数据内容str->bLength应该仍为4, 不能改成2return size;}void USB_Init(void){USB->CNTR |= USB_CNTR_ERRM; // 打开错误提示中断NVIC_EnableIRQ(USB_LP_CAN1_RX0_IRQn);USB->CNTR &= ~USB_CNTR_PDWN; // 先打开USB外设 (需要一定的启动时间)USB->CNTR &= ~USB_CNTR_FRES; // 撤销USB外设的复位信号// 初始化端点0和端点1的缓冲区USB_BufDesc[0].ADDR_TX = 112;USB_BufDesc[0].COUNT_TX = 0;USB_BufDesc[0].ADDR_RX = 176;USB_BufDesc[0].COUNT_RX = USB_COUNT0_RX_BLSIZE | USB_COUNT0_RX_NUM_BLOCK_0; // 64 bytes (See Table 177. Definition of allocated buffer memory)USB_BufDesc[1].ADDR_TX = 240;USB_BufDesc[1].COUNT_TX = 0;USB_BufDesc[1].ADDR_RX = 304;USB_BufDesc[1].COUNT_RX = USB_COUNT1_RX_BLSIZE | USB_COUNT1_RX_NUM_BLOCK_0;USB->CNTR |= USB_CNTR_RESETM; // 打开复位中断, 开始处理复位请求}void USB_ReadPMA(uint16_t usbaddr, void *buffer, uint16_t len){const uint16_t *ppma;uint16_t *pbuf;// USBPMA地址范围: 0~511, 对应的APB绝对地址范围为0x40006000~0x400063fd// 0对应0x40006000, 1对应0x40006001; 但2对应0x40006004, 3对应0x40006005, 4对应0x40006008, 5对应0x40006009if (usbaddr % 2 == 1){*(uint8_t *)buffer = *(uint8_t *)(USB_PMAADDR + 2 * usbaddr - 1);pbuf = (uint16_t *)((uint8_t *)buffer + 1);usbaddr++;len--;}elsepbuf = (uint16_t *)buffer;ppma = (const uint16_t *)(USB_PMAADDR + usbaddr * 2); // 将USB地址转换为APB绝对地址while (len >= 2){*pbuf = *ppma;pbuf++; // 缓冲区地址前进2个地址ppma += 2; // APB绝对地址前进2个地址len -= 2;}if (len == 1)*(uint8_t *)pbuf = *(uint8_t *)ppma;}void USB_WritePMA(uint16_t usbaddr, const void *buffer, uint16_t len){const uint16_t *pbuf;uint16_t *ppma;if (usbaddr % 2 == 1){*(uint8_t *)(USB_PMAADDR + 2 * usbaddr - 1) = *(uint8_t *)buffer;pbuf = (uint16_t *)((uint8_t *)buffer + 1);usbaddr++;len--;}elsepbuf = (uint16_t *)buffer;ppma = (uint16_t *)(USB_PMAADDR + usbaddr * 2);while (len >= 2){*ppma = *pbuf;pbuf++;ppma += 2;len -= 2;}if (len == 1)*(uint8_t *)ppma = *(uint8_t *)pbuf;}void USB_LP_CAN1_RX0_IRQHandler(void){if (USB->ISTR & USB_ISTR_ERR){USB->ISTR = USB_ISTR_MASK & ~USB_ISTR_ERR;printf("USB error!\n"); // CRC校验错误会产生这个中断, 但系统会自动重传数据, 软件无需理会}if (USB->ISTR & USB_ISTR_RESET){// USB复位会使DADDR和EPnR寄存器清零USB->ISTR = USB_ISTR_MASK & ~USB_ISTR_RESET;USB->DADDR = USB_DADDR_EF; // 启动USB外设的功能USB->EP0R = USB_EP0R_STAT_RX | USB_EP0R_EP_TYPE_0 | USB_EP0R_STAT_TX_1; // STAT_RX=VALID, EP_TYPE=CONTROL, STAT_TX=NAKUSB->EP1R = USB_EP1R_STAT_RX | USB_EP1R_STAT_TX_1 | 1;printf("USB reset!\n");}if (USB->ISTR & USB_ISTR_CTR) // 虽然CTR中断在寄存器中并没有使能, 但是仍能触发, 所以这个中断是关不掉的USB_CorrectTransfer();}
【程序运行结果】
1.插入了USB设备后,在“我的电脑”中显示了新的可移动磁盘
2.可以在系统托盘中弹出USB设备
3.可以在磁盘中存放文件,查看容量
端点0上USB设备的枚举过程详解:
STM32F103C8 USBUSB reset! // STM32 USB外设本身的复位 (先清除PDWN位, 再清除FRES位), 此时设备为Powered状态USB reset! // 主机让USB设备复位, 设备由Powered状态转变为Default状态0+8 // 端点0收到8字节数据 (Setup stage: hostOUT+hostData+deviceACK)8006000100004000 // 主机请求设备描述符, 请求的最大数据长度为0x40=64字节0-18 // 端点0发出18字节的设备描述符数据 (Data stage: hIN+dData+hACK)0+0 // 主机确认收到数据 (Status stage: hOUT+hDATA+dACK)USB reset! // 主机再次让USB设备复位0+80005130000000000 // 主机给USB设备分配设备地址0x13, 不请求数据 (Setup stage: hOUT+hData+dACK)DADDR_130-0 // 设备确认收到数据, 并修改设备地址为0x13 (Status stage: hIN+dData+hACK), 设备由Default状态转变为Address状态0+88006000100001200 // 主机再次请求设备描述符, 最大数据长度为0x12=18字节0-18 // 设备通过端点0发送18字节的设备描述符0+0 // 主机确认收到数据0+8800600020000FF00 // 主机请求配置描述符0-32 // 设备发送32字节的配置描述符,顺带将接口描述符和端点描述符也发送给主机(USB规范要求)0+0 // 主机确认0+8800600030000FF00 // 主机请求字符串的语言列表0-4 // 设备告诉主机, 设备只支持0x0409 English (United States)这一种语言0+00+8800603030904FF00 // 主机请求3号字符串用0x0409这个语言(英语)写的内容0-40 // 设备发送字符串内容0+00+88006000600000A00 // 主机请求Device qualifier描述符, 但由于USB规范规定USB全速设备不支持这个描述符, 所以直接STALL, 向主机报告错误0+88006000100001200 // 主机再次请求18字节的设备描述符0-180+00+88006000200000900 // 主机请求配置描述符, 但这次只允许设备发送9字节的内容0-9 // 配置描述符共有32字节, 设备只发送前9字节给主机, 发送的内容不作任何修改(wTotalLength=32, 绝对不允许改成9)0+0 // 主机确认收到数据0+88006000200002000 // 主机再次请求配置描述符, 最大长度改成了0x20=32字节0-32 // 设备发送了完整的配置描述符0+00+88006000300000200 // 主机请求字符串语言列表, 但只允许设备发送2字节的内容 (实际上就是要获取语言列表的长度)0-2 // 语言列表共有4字节, 设备只发送前两字节, 内容中的bLength=4保持不变0+00+88006000300000400 // 主机请求字符串语言列表, 最大长度改成了4字节0-4 // 设备发送了完整的语言列表0+00+88006030309040200 // 主机请求3号字符串的英语内容的长度0-20+00+88006030309042800 // 主机请求3号字符串的英语内容0-400+00+80009010000000000 // 应用1号配置, 设备现在由Address状态转变为最终的Configured状态CFG10-00+8A1FE000000000100 // 后面的代码还没写, 因为调用了两次dump_data, 所以数据内容输出了两次A1FE000000000100 // A1表示: 方向为从设备到主机, 获取大容量存储Class的接口(Interface)信息0+8A1FE000000000100A1FE0000000001000+8A1FE000000000100A1FE0000000001001+31 // 端点1收到了31字节的数据5553424310109C112400000080000612000000240000000000000000000000
串口输出结果:
STM32F103C8 USBFlash Size: 64KB, Disk Size: 48.0KB, Disk Addr: 0x08004000Flash is unlocked!USB reset!USB reset!0S+80-180+0USB reset!0S+80-0DADDR_090S+80-180+00S+80-320+00S+80-40+00S+80-300+00S+80-400+00S+80S+80-180+00S+80-90+00S+80-320+00S+80-20+00S+80-40+00S+80-20+00S+80-400+00S+8CFG1 // 应用1号配置, 设备现在由Address状态转变为最终的Configured状态0-00S+80-10+00S+80-90+00S+80-320+0W4,4 // 从第4块开始连续写4块W4,4W4,4W4,4W2,1W3,1W4,4W45,1 // 写第45块W4,4W4,4W2,1W3,10S+8CFG0 // 在系统托盘上弹出磁盘时, 设备由Configured状态转变为Address状态0-0
【9月12日补充:Suspend/Resume】
USB_Init函数末尾添加:
USB->CNTR |= USB_CNTR_SUSPM | USB_CNTR_WKUPM;USB->CNTR |= USB_CNTR_ESOFM;
USB_LP_CAN1_RX0_IRQHandler函数末尾添加:
if (USB->ISTR & USB_ISTR_ESOF){// Expected start of frameUSB->ISTR = USB_ISTR_MASK & ~USB_ISTR_ESOF;printf("ESOF!\n");}if (USB->ISTR & USB_ISTR_SUSP){// SuspendUSB->ISTR = USB_ISTR_MASK & ~USB_ISTR_SUSP;USB->CNTR |= USB_CNTR_FSUSP;USB->CNTR |= USB_CNTR_LP_MODE;printf("{");}if (USB->ISTR & USB_ISTR_WKUP){// ResumeUSB->ISTR = USB_ISTR_MASK & ~USB_ISTR_WKUP;// LP_MODE位自动清除USB->CNTR &= ~USB_CNTR_FSUSP;printf("}\n");}
运行结果:
STM32F103C8 USBFlash Size: 64KB, Disk Size: 48.0KB, Disk Addr: 0x08004000Flash is unlocked!USB reset!ESOF!ESOF!ESOF!{}USB reset!
仅在最开始产生了Suspend/Resume事件,因为USB设备初始化完毕后,系统一直在发送SCSI_TEST_UNIT_READY命令查询设备的状态,USB设备始终处于非空闲状态。
从系统托盘中弹出磁盘(注意不是在“我的电脑”里面弹出!)后,USB设备会长期进入Suspend状态:
0S+8CFG00-0ESOF!ESOF!ESOF!{
USBWakeUp_IRQn中断实际上是一个外部中断,所以只需要配置好EXTI,就可以在USB WKUP事件触发时触发该中断。这个中断可以和USB_LP_CAN1_RX0_IRQn中断同时打开。
EXTI->IMR |= EXTI_IMR_MR18; // 打开18号外部中断EXTI->RTSR |= EXTI_RTSR_TR18; // 上升沿触发//EXTI->FTSR |= EXTI_FTSR_TR18; // 下降沿触发NVIC_EnableIRQ(USBWakeUp_IRQn);void USBWakeUp_IRQHandler(void){EXTI->PR = EXTI_PR_PR18;printf("wakeup int!\n");}