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

本程序的目的是为了演示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; // 波特率: 115200
  USART3->CR1 = USART_CR1_UE | USART_CR1_TE | USART_CR1_RE;
  printf("STM32F103C8 USB\n");
  
  USB_CalcDiskAddr(); // 根据当前芯片的Flash大小, 自动计算磁盘数据存放位置
  USB_Init(); // 初始化USB
  
  while (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 1024

static 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 Kbytes
  uint32_t disk_size = DISK_BLOCKCOUNT * DISK_BLOCKSIZE; // disk size in bytes
  usb_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);
  }
  
  // 解锁Flash
  FLASH->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=1
  
  USB->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
  }
  else
    dump_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恢复为NAK
          size = 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 Characteristics
    if (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_DEBUG
  static uint8_t newline = 0;
#endif
  USB_ENDP1SendData(); // 发送剩余数据
  
  // 显示上次发送成功的数据包大小
#ifdef ENDP1_DEBUG
  if (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_DEBUG
    printf("1+%d\n", len);
    printf("CMD%02XH\n", cbw->CBWCB[0]);
#endif
    switch (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 code
          buffer[7] = 0x0a; // additional sense length
          if (usb_ejected)
          {
            // 在我的电脑里面的可移动磁盘上选择弹出命令后, 磁盘应该自动消失 (这相当于只取出磁盘, 不取出USB读卡器)
            // 但USB设备仍会保持Configured状态, 并且还会持续收到CMD00H和CMD03H命令, 等待用户在读卡器上插入新的磁盘
            buffer[2] = 0x02; // sense key: not ready
            buffer[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 00H
          memset(buffer, 0, 5);
          buffer[3] = 1; // page length
          usb_endp1.len_tx = 5;
        }
        else
        {
          buffer[0] = 0x00; // connected & direct access block device
          buffer[1] = 0x80; // removable
          buffer[2] = 0x02; // version
          buffer[3] = 0x02; // NORMACA & HISUP & response data format
          buffer[4] = 32; // additional length
          buffer[5] = 0;
          buffer[6] = 0;
          buffer[7] = 0;
          strcpy((char *)buffer + 8, "HY-Smart"); // 8-byte vendor identification
          strcpy((char *)buffer + 16, "SPI Flash Disk  "); // 16-byte product identification
          strcpy((char *)buffer + 32, "1.0 "); // 4-byte product revision level
          usb_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 length
      
        buffer[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 media
        buffer[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 block
        buffer[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 bytes
        buffer[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 address
        usb_endp1.len_tx = block_len * DISK_BLOCKSIZE; // transfer length
#ifdef SCSI_READ_DEBUG
        printf("R%d,%d\n", block_addr, block_len);
#endif
        break;
      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_DEBUG
        printf("W%d,%d\n", block_addr, block_len);
#endif
        usb_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;
    
    // 准备好数据发送完毕后要发送的CSW
    if (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; // 未发送的数据量
      else
        usb_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端点设为STALL
    usb_csw.dCSWSignature = 0x53425355;
    usb_csw.dCSWTag = cbw->dCBWTag;
    usb_csw.dCSWDataResidue = cbw->dCBWDataTransferLength;
    usb_csw.bCSWStatus = 1; // Command Failed
    USB_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;
  else
    usb_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)
  {
    // 处理命令时遇到错误, 且已发送CSW
    USB->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;
  }
  else
    return; // 结束发送
  
  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.0
      device->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 Device
      device->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-powered
      config->bMaxPower = 50; // 最大电流: 100mA
    
      interface = (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 devices
      interface->bInterfaceSubClass = 0x06; // SCSI transparent command set
      interface->bInterfaceProtocol = 0x50; // USB Mass Storage Class Bulk-Only (BBB) Transport
      interface->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 1
      endpoints[0].bmAttributes = 0x02; // Bulk
      endpoints[0].wMaxPacketSize = 64;
      endpoints[0].bInterval = 0; // Does not apply to Bulk endpoints
      
      endpoints[1].bLength = sizeof(USB_EndpointDescriptor);
      endpoints[1].bDescriptorType = ENDPOINT_DESCRIPTOR;
      endpoints[1].bEndpointAddress = 0x01; // OUT, address 1
      endpoints[1].bmAttributes = 0x02; // Bulk
      endpoints[1].wMaxPacketSize = 64;
      endpoints[1].bInterval = 0;
    
      size = config->wTotalLength;
      break;
    case STRING_DESCRIPTOR:
      str->bDescriptorType = STRING_DESCRIPTOR;
      if (req->wIndex == 0x409) // 字符串英文内容
      {
        // 字符串的编码为UTF-16
        switch (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)
      }
      else
        break;
      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设为STALL
      size = USB_ERROR;
  }
  
  // 发送的字节数不能超过主机要求的最大长度
  if (size > req->wLength)
    size = req->wLength; // 只修改发送长度, 内容原封不动, 切记!!!!
  // 比如在请求字符串语言列表时, 待发送的数据量是str->bLength=4
  // 如果主机要求最大只能发送req->wLength=2字节, 则数据内容str->bLength应该仍为4, 不能改成2
  return 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对应0x40006009
  if (usbaddr % 2 == 1)
  {
    *(uint8_t *)buffer = *(uint8_t *)(USB_PMAADDR + 2 * usbaddr - 1);
    pbuf = (uint16_t *)((uint8_t *)buffer + 1);
    usbaddr++;
    len--;
  }
  else
    pbuf = (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--;
  }
  else
    pbuf = (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=NAK
    USB->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 frame
  USB->ISTR = USB_ISTR_MASK & ~USB_ISTR_ESOF;
  printf("ESOF!\n");
}
if (USB->ISTR & USB_ISTR_SUSP)
{
  // Suspend
  USB->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)
{
  // Resume
  USB->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");
}

 

  • 2
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 11
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

巨大八爪鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值