本程序的目的是为了演示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工程文件下载地址:https://pan.baidu.com/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模式的大容量存储设备

【勘误】

2018年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; 单向双缓冲接收端点则都是RX
typedef 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-2017* @brief   Definitions related to USB Core******************************************************************************* @attention** <h2><center>&copy; COPYRIGHT(c) 2017 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_TYPE      0x60  /* Mask to get request type */
#define STANDARD_REQUEST  0x00  /* Standard request */
#define CLASS_REQUEST     0x20  /* 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 */
// https://en.wikipedia.org/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.org/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 USB
USB 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+8
0005130000000000 // 主机给USB设备分配设备地址0x13, 不请求数据 (Setup stage: hOUT+hData+dACK)
DADDR_13
0-0 // 设备确认收到数据, 并修改设备地址为0x13 (Status stage: hIN+dData+hACK), 设备由Default状态转变为Address状态
0+8
8006000100001200 // 主机再次请求设备描述符, 最大数据长度为0x12=18字节
0-18 // 设备通过端点0发送18字节的设备描述符
0+0 // 主机确认收到数据
0+8
800600020000FF00 // 主机请求配置描述符
0-32 // 设备发送32字节的配置描述符,顺带将接口描述符和端点描述符也发送给主机(USB规范要求)
0+0 // 主机确认
0+8
800600030000FF00 // 主机请求字符串的语言列表
0-4 // 设备告诉主机, 设备只支持0x0409 English (United States)这一种语言
0+0
0+8
800603030904FF00 // 主机请求3号字符串用0x0409这个语言(英语)写的内容
0-40 // 设备发送字符串内容
0+0
0+8
8006000600000A00 // 主机请求Device qualifier描述符, 但由于USB规范规定USB全速设备不支持这个描述符, 所以直接STALL, 向主机报告错误
0+8
8006000100001200 // 主机再次请求18字节的设备描述符
0-18
0+0
0+8
8006000200000900 // 主机请求配置描述符, 但这次只允许设备发送9字节的内容
0-9 // 配置描述符共有32字节, 设备只发送前9字节给主机, 发送的内容不作任何修改(wTotalLength=32, 绝对不允许改成9)
0+0 // 主机确认收到数据
0+8
8006000200002000 // 主机再次请求配置描述符, 最大长度改成了0x20=32字节
0-32 // 设备发送了完整的配置描述符
0+0
0+8
8006000300000200 // 主机请求字符串语言列表, 但只允许设备发送2字节的内容 (实际上就是要获取语言列表的长度)
0-2 // 语言列表共有4字节, 设备只发送前两字节, 内容中的bLength=4保持不变
0+0
0+8
8006000300000400 // 主机请求字符串语言列表, 最大长度改成了4字节
0-4 // 设备发送了完整的语言列表
0+0
0+8
8006030309040200 // 主机请求3号字符串的英语内容的长度
0-2
0+0
0+8
8006030309042800 // 主机请求3号字符串的英语内容
0-40
0+0
0+8
0009010000000000 // 应用1号配置, 设备现在由Address状态转变为最终的Configured状态
CFG1
0-0
0+8
A1FE000000000100 // 后面的代码还没写, 因为调用了两次dump_data, 所以数据内容输出了两次
A1FE000000000100 // A1表示: 方向为从设备到主机, 获取大容量存储Class的接口(Interface)信息
0+8
A1FE000000000100
A1FE000000000100
0+8
A1FE000000000100
A1FE000000000100
1+31 // 端点1收到了31字节的数据
5553424310109C112400000080000612000000240000000000000000000000

串口输出结果:

STM32F103C8 USB
Flash Size: 64KB, Disk Size: 48.0KB, Disk Addr: 0x08004000
Flash is unlocked!
USB reset!
USB reset!
0S+8
0-18
0+0
USB reset!
0S+8
0-0
DADDR_09
0S+8
0-18
0+0
0S+8
0-32
0+0
0S+8
0-4
0+0
0S+8
0-30
0+0
0S+8
0-40
0+0
0S+8
0S+8
0-18
0+0
0S+8
0-9
0+0
0S+8
0-32
0+0
0S+8
0-2
0+0
0S+8
0-4
0+0
0S+8
0-2
0+0
0S+8
0-40
0+0
0S+8
CFG1 // 应用1号配置, 设备现在由Address状态转变为最终的Configured状态
0-0
0S+8
0-1
0+0
0S+8
0-9
0+0
0S+8
0-32
0+0
W4,4 // 从第4块开始连续写4块
W4,4
W4,4
W4,4
W2,1
W3,1
W4,4
W45,1 // 写第45块
W4,4
W4,4
W2,1
W3,1
0S+8
CFG0 // 在系统托盘上弹出磁盘时, 设备由Configured状态转变为Address状态
0-0

【2018年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 USB
Flash Size: 64KB, Disk Size: 48.0KB, Disk Addr: 0x08004000
Flash is unlocked!
USB reset!
ESOF!
ESOF!
ESOF!
{}
USB reset!

仅在最开始产生了Suspend/Resume事件,因为USB设备初始化完毕后,系统一直在发送SCSI_TEST_UNIT_READY命令查询设备的状态,USB设备始终处于非空闲状态。

从系统托盘中弹出磁盘(注意不是在“我的电脑”里面弹出!)后,USB设备会长期进入Suspend状态:

0S+8
CFG0
0-0
ESOF!
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");
}

【USB】STM32F103C8单片机上完全用寄存器实现的USB大容量存储设备相关推荐

  1. usb接上计算机没反应怎么办,usb硬盘插上电脑没反应怎么办,小编教你怎么解决...

    前两天,尤为网友给小编留言称他的usb硬盘按照以往那样插上电脑,但是却发现,电脑没有任何反应,试了几次,还是一样,就连电脑也都重启了几遍,都是没反应,那么该怎么办呢?为此,小编就给他整理了解决usb硬 ...

  2. 连不上局域网计算机一连就闪退,手机usb连不上电脑  连上了就闪退??

    一.手机USB连不上电脑可能是因为手机没安装驱动,可以下载豌豆荚等软件进行驱动安装. 二.第二个手机USB连不上电脑的解决方法不用安装任何东西,你把手机连接电脑后,点击手机设置--应用程序--开发者选 ...

  3. 使用USB在PC上操作Android手机

    使用USB在PC上操作Android手机 目录 使用USB在PC上操作Android手机 我的应用场景 环境介绍 基本原理 安装方法 几个常用命令 后记 我的应用场景 想上班的时候,通过笔记本直接阅读 ...

  4. 51单片机上传数据到手机APP

    前言: 最近在指导朋友毕设时发现的一个比较简单实用的功能 实现功能: 将温度数据上传到手机APP 所需材料: 1.51单片机 2.WIFI模块:ESP8266-01S 3.温度传感器:DS18B20 ...

  5. MPU6050开发 -- 在 C52 单片机上测试

    如需转载请注明出处:https://blog.csdn.net/qq_29350001/article/details/78616706 用了三篇文章,从 MPU6050的初识,相关基本概念,到 I2 ...

  6. usb连接不上 艾德克斯电源_STM32F7 电源控制器(PWR)

    这篇文章给大家介绍一下STM32F7的电源控制器. F7的工作电压也就是VDD要求为1.8V~3.6V,通过VDD引脚给整个芯片供电,VDD再通过主电压调节器给I/O口和内核逻辑提供1.2V工作电压. ...

  7. uCOS-II在51单片机上的移植

    uCOS-II在51单片机上的移植 约定:文中所写的硬件堆栈或系统堆栈是指51单片机SP指针所指向的堆栈空间,而用户堆栈或任务堆栈是指用来保存任务状态为每个任务分配的堆栈空间. 前一段时间一直在学习U ...

  8. 两个hc05蓝牙模块在两块单片机上通信(附完整代码)

    目录 第一步:进入AT模式 第二步:同时插在电脑上实现通信 第三步:单片机上通信 代码链接 所需材料:两个hc05蓝牙,两个usb转串口模块,两块单片机 第一步:进入AT模式 接线图如下所示 HC-0 ...

  9. 在龙芯1C单片机上使用ESP8266 wifi透传模块

    龙芯1C既可以运行linux,也可以当作单片机用.当用作linux时,可以通过USB wifi模块RTL8192C,RTL8188ETV等,当作单片机用时,可以像STM32那样使用串口透传wifi模块 ...

最新文章

  1. 对PInvoke函数函数调用导致堆栈不对称。原因可能是托管的 PInvoke 签名与非托管的目标签名不匹配。...
  2. 阿里云服务器ECS Linux系统分析nginx或apache当天访问最多的IP
  3. linux下apache服务器的配置和管理(启动、重启、中断服务)
  4. 使用Visual Studio重构与分析Python
  5. ip对应的区域查询(php版)(转)
  6. SDN你必须知道的十大问题——SDN有哪些开源项目
  7. JSValidation 配置文件
  8. Android UI开发第三十篇——使用Fragment构建灵活的桌面
  9. docker 导入导出镜像
  10. mPaaS 月度小报 | 3月发生的大事件
  11. Function!(计蒜客 - 42386)
  12. 无穷级数求和7个公式_大家看,用反证法判别级数敛散性(送微积分同学)!...
  13. 载波聚合或双连接的方式进行_智能电表常用远程抄表方式,您想知道吗?--老兵聊电之...
  14. d3.js 旋转图形_MATLAB 的图形处理
  15. C#中增量类功能的方式之 继承与扩展
  16. SPRING IN ACTION 第4版笔记-第二章-001-用@Autowired\@ComponentScan、@Configuration、@Component实现自动装载bean...
  17. 《想吃麻花现给你拧》
  18. linux单网卡多拨Adsl,秋明 | 边缘计算-使用多adsl账号做捆绑上网[单网卡多账号多拨]...
  19. 关于获取安卓设备的mac地址
  20. CodeForces round 753 problem B Odd Grasshopper(奇怪的蚱蜢)

热门文章

  1. 微软应用商店点了安装没反应,没法下载和更新
  2. 华为推送没有跳转到指定页面
  3. css 实现image宽度百分百,高度跟宽度一样大小
  4. matlab cov函数详解
  5. 让IE6/IE7/IE8支持CSS3的8种方法
  6. DocuSign沙盒 C#,SDK的使用demo
  7. 手机当电脑音响_高颜值蓝牙音响,这六款值得拥有
  8. python统计42所高校在各省的分布数量_话题| 全国各省高考难度排行揭晓,上大学有多难?...
  9. php小程序开发实例,微信小程序全局配置开发实例
  10. webmagic学习之路-3:采集安居客经纪人详情页