【USB】STM32F107VC单片机上完全用寄存器实现的USB OTG Device模式的大容量存储设备

本程序是从STM32F103C8的从USB上移植过来的:
https://blog.csdn.net/ZLK1214/article/details/78972484

本程序的目的是为了演示STM32 USB OTG寄存器的使用方法以及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/1iL3-6eQsejuvTgBdeZ4oXg(提取码:gx5s)

连线和STM32F103上相同。USB接口最左侧的VBUS接+5V,通过AMS1117降压到3.3V给STM32F107VC单片机供电。D-通过22Ω的电阻接PA11,D+通过22Ω的电阻接PA12,D+还通过一个1.5kΩ的电阻接PE1,GND引脚接到单片机的GND上。

Device模式下,USB OTG只需要接PA11和PA12,PA9和10不用接。

单片机晶振为25MHz,所用的串口是USART1,波特率为115200。
注意,程序中要慎用printf函数打印字符串,最好不要打印大量的字符串内容,否则由于设备响应主机不及时,USB将无法正常工作。

由于时间关系,程序目前还有如下问题没有解决:
(1)不能像STM32F103那个程序那样,在我的电脑里面在磁盘上右键菜单中点击弹出,磁盘会消失。目前107的程序会使文件夹窗口直接卡死。
(2)在不复位单片机的情况下,拔出USB线再插上,有时候不能正常显示磁盘。串口输出一个USB reset就没有后续了,或者能出现端点0上的枚举过程,但是就是不出现磁盘,一段时间后USB一直复位。

程序的结构和之前F103的程序基本相同,只是将寄存器操作全部改成了USB OTG的。

【main.c】

#include <stdio.h>
#include <stm32f1xx.h>
#include "common.h"
#include "usb_test.h"

int main(void)
{
  clock_init();
  usart_init(115200);
  
  printf("STM32F107VC USB\n");
  printf("SystemCoreClock=%u\n", SystemCoreClock);
  
  USB_CalcDiskAddr(); // 根据当前芯片的Flash大小, 自动计算磁盘数据存放位置
  USB_Init(); // 初始化USB
  
  while (1)
  {
  }
}

【usb_test.c】

#include <stdio.h>
#include <stm32f1xx.h>
#include <string.h>
#include <wchar.h>
#include "common.h"
#include "USB.h"
#include "usb_def.h"
#include "usb_test.h"

//#define ENDP1_DEBUG // 显示端点1收发的数据量
//#define SCSI_READ_DEBUG // 显示磁盘的读取情况
#define SCSI_WRITE_DEBUG // 显示磁盘的写入情况
//#define USE_SRAM // 将磁盘文件保存在SRAM中, 而不是FLASH中

// 容量千万不要设置的太小, 否则将无法完成格式化!!!! (操作系统根本就不会发送CMD2AH命令)
#ifdef USE_SRAM
#define DISK_BLOCKCOUNT 62
#define DISK_BLOCKSIZE 1024
#else
// 为了防止磁盘覆盖程序代码, 可以在Keil工程属性中Target选项卡下设置IROM1的Size
// 如设置成0x3000, 可以保证编译出来的程序的大小在0x3000以内
// 这样磁盘的开始地址就可以为0x08003000
#define DISK_BLOCKCOUNT 122
#define DISK_BLOCKSIZE 2048
#endif

static uint8_t usb_ejected = 0; // 磁盘是否已弹出
static uint8_t usb_txbuffer[EPCNT][64]; // IN端点发送缓冲区
static uint8_t *usb_disk; // 磁盘的存放位置 (自动计算)
static uint16_t usb_txsent[EPCNT]; // 各端点成功发送的数据量 (由于发送成功后寄存器中的值会被清零, 所以保存在这里)
static USB_CSW usb_csw = {0}; // 存放SCSI命令的CSW结果
static USB_EndpointData usb_endp1 = {0}; // 端点1的剩余数据量

void USB_CalcDiskAddr(void)
{
#ifdef USE_SRAM
  static uint8_t ram_disk[DISK_BLOCKCOUNT][DISK_BLOCKSIZE];
  
  usb_disk = (uint8_t *)ram_disk;
#else
  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");
#endif
}

void USB_ENDP0In(uint16_t len)
{
  printf("0-%d\n", len);
  USB_OTG_FS_OUTEP[0].DOEPCTL |= USB_OTG_DOEPCTL_CNAK; // RX=VALID
}

void USB_ENDP0Out(const uint8_t *data, uint16_t len)
{
  printf("0+%d\n", len);
  if (len == 0)
  {
    // 收到一个空包
    USB_OTG_FS_OUTEP[0].DOEPCTL |= USB_OTG_DOEPCTL_CNAK; // RX=VALID
  }
  else
    dump_data(data, len);
}

void USB_ENDP0Setup(const uint8_t *data, uint16_t len)
{
  int16_t size = USB_UNSUPPORTED; // 发送的数据大小: -2表示未处理, -1表示已处理但出错, 0表示发送空包
  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, usb_txbuffer[0]);
          break;
        case SET_ADDRESS:
          printf("ADDR_%02X\n", req->wValue);
          USB_OTG_FS_DEVICE->DCFG = (USB_OTG_FS_DEVICE->DCFG & ~USB_OTG_DCFG_DAD) | (req->wValue << USB_OTG_DCFG_DAD_Pos);
          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_OTG_FS_INEP[1].DIEPCTL = (USB_OTG_FS_INEP[1].DIEPCTL & ~USB_OTG_DIEPCTL_STALL) | USB_OTG_DIEPCTL_SNAK; // 从STALL恢复为NAK
          size = 0;
          //printf("NAK EP1_IN\n");
        }
      }
    }
  }
  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)
      usb_txbuffer[0][0] = 0;
      size = 1;
    }
  }
  
  if (size >= 0)
  {
    // 先设置要发送的数据量, 然后在DIEPINT_TXFE中断中放入数据
    USB_OTG_FS_INEP[0].DIEPTSIZ = (1 << USB_OTG_DIEPTSIZ_PKTCNT_Pos) | size;
    USB_OTG_FS_INEP[0].DIEPCTL |= USB_OTG_DIEPCTL_EPENA | USB_OTG_DIEPCTL_CNAK; // TX=VALID
    USB_OTG_FS_DEVICE->DIEPMSK |= USB_OTG_DIEPMSK_ITTXFEMSK; // 准备在中断里面写入数据
  }
  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;
  uint32_t block_addr, block_len;
  
  if (usb_endp1.len_rx == 0)
  {
    /* 接收命令 */
    usb_endp1.data_tx = usb_txbuffer[1];
    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(usb_txbuffer[1], 0, 18);
          usb_txbuffer[1][0] = 0x70; // response code
          usb_txbuffer[1][7] = 0x0a; // additional sense length
          if (usb_ejected)
          {
            // 在我的电脑里面的可移动磁盘上选择弹出命令后, 磁盘应该自动消失 (这相当于只取出磁盘, 不取出USB读卡器)
            // 但USB设备仍会保持Configured状态, 并且还会持续收到CMD00H和CMD03H命令, 等待用户在读卡器上插入新的磁盘
            usb_txbuffer[1][2] = 0x02; // sense key: not ready
            usb_txbuffer[1][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(usb_txbuffer[1], 0, 5);
          usb_txbuffer[1][3] = 1; // page length
          usb_endp1.len_tx = 5;
        }
        else
        {
          usb_txbuffer[1][0] = 0x00; // connected & direct access block device
          usb_txbuffer[1][1] = 0x80; // removable
          usb_txbuffer[1][2] = 0x02; // version
          usb_txbuffer[1][3] = 0x02; // NORMACA & HISUP & response data format
          usb_txbuffer[1][4] = 32; // additional length
          usb_txbuffer[1][5] = 0;
          usb_txbuffer[1][6] = 0;
          usb_txbuffer[1][7] = 0;
          strcpy((char *)usb_txbuffer[1] + 8, "HY-Smart"); // 8-byte vendor identification
          strcpy((char *)usb_txbuffer[1] + 16, "SPI Flash Disk  "); // 16-byte product identification
          strcpy((char *)usb_txbuffer[1] + 32, "1.0 "); // 4-byte product revision level
          usb_endp1.len_tx = 36;
        }
        break;
      case SCSI_MODE_SENSE6:
        /* 3.11 MODE SENSE(6) command */
        usb_txbuffer[1][0] = 0x03;
        memset(usb_txbuffer[1] + 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:
        usb_txbuffer[1][0] = 0;
        usb_txbuffer[1][1] = 0;
        usb_txbuffer[1][2] = 0;
        usb_txbuffer[1][3] = 8; // capacity list length
      
        usb_txbuffer[1][4] = (DISK_BLOCKCOUNT >> 24) & 0xff;
        usb_txbuffer[1][5] = (DISK_BLOCKCOUNT >> 16) & 0xff;
        usb_txbuffer[1][6] = (DISK_BLOCKCOUNT >> 8) & 0xff;
        usb_txbuffer[1][7] = DISK_BLOCKCOUNT & 0xff;
      
        usb_txbuffer[1][8] = 0x02; // descriptor code: formatted media
        usb_txbuffer[1][9] = (DISK_BLOCKSIZE >> 16) & 0xff;
        usb_txbuffer[1][10] = (DISK_BLOCKSIZE >> 8) & 0xff;
        usb_txbuffer[1][11] = DISK_BLOCKSIZE & 0xff;
        usb_endp1.len_tx = 12;
        break;
      case SCSI_READ_CAPACITY10:
        /* 3.22 READ CAPACITY (10) command */
        usb_txbuffer[1][0] = ((DISK_BLOCKCOUNT - 1) >> 24) & 0xff; // the logical block address of the LAST logical block
        usb_txbuffer[1][1] = ((DISK_BLOCKCOUNT - 1) >> 16) & 0xff;
        usb_txbuffer[1][2] = ((DISK_BLOCKCOUNT - 1) >> 8) & 0xff;
        usb_txbuffer[1][3] = (DISK_BLOCKCOUNT - 1) & 0xff;
        usb_txbuffer[1][4] = (DISK_BLOCKSIZE >> 24) & 0xff; // block length in bytes
        usb_txbuffer[1][5] = (DISK_BLOCKSIZE >> 16) & 0xff;
        usb_txbuffer[1][6] = (DISK_BLOCKSIZE >> 8) & 0xff;
        usb_txbuffer[1][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_OTG_FS_OUTEP[1].DOEPCTL |= USB_OTG_DOEPCTL_CNAK; // 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页
#ifndef USE_SRAM
  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;
  }
#endif
  
  // 将收到的数据写入Flash中
#ifndef USE_SRAM
  FLASH->CR |= FLASH_CR_PG;
#endif
  while (len)
  {
    *(uint16_t *)usb_endp1.data_rx = *(const uint16_t *)data;
    data += 2;
    usb_endp1.data_rx += 2;
    len -= 2;
#ifndef USE_SRAM
    while (FLASH->SR & FLASH_SR_BSY);
#endif
  }
#ifndef USE_SRAM
  FLASH->CR &= ~FLASH_CR_PG;
#endif
    
  usb_endp1.data_rx += len;
}

/* ENDP1发送大量数据 */
void USB_ENDP1SendData(void)
{
  if (usb_endp1.len_tx > 64)
  {
    // 发送一个数据包
    USB_OTG_FS_INEP[1].DIEPTSIZ = (1 << USB_OTG_DIEPTSIZ_PKTCNT_Pos) | 64;
    memcpy(usb_txbuffer[1], usb_endp1.data_tx, 64);
    usb_endp1.data_tx += 64;
    usb_endp1.len_tx -= 64;
  }
  else if (usb_endp1.len_tx > 0)
  {
    // 发送最后一个数据包
    USB_OTG_FS_INEP[1].DIEPTSIZ = (1 << USB_OTG_DIEPTSIZ_PKTCNT_Pos) | usb_endp1.len_tx;
    memcpy(usb_txbuffer[1], 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_OTG_FS_INEP[1].DIEPTSIZ = (1 << USB_OTG_DIEPTSIZ_PKTCNT_Pos) | sizeof(usb_csw);
    memcpy(usb_txbuffer[1], &usb_csw, sizeof(usb_csw));
    usb_csw.dCSWSignature = 0; // 表示下次不再发送CSW
  }
  else if (usb_endp1.len_tx == USB_ERROR && usb_csw.dCSWSignature == 0)
  {
    // 处理命令时遇到错误, 且已发送CSW
    USB_OTG_FS_INEP[1].DIEPCTL |= USB_OTG_DIEPCTL_STALL; // STALL后续的所有IN token
    // 接下来端点0将收到clear ENDPOINT_HALT feature的请求
    //printf("STALL EP1_IN\n");
    return;
  }
  else
    return; // 结束发送
  
  USB_OTG_FS_INEP[1].DIEPCTL |= USB_OTG_DIEPCTL_EPENA | USB_OTG_DIEPCTL_CNAK; // TX=VALID
  USB_OTG_FS_DEVICE->DIEPMSK |= USB_OTG_DIEPMSK_ITTXFEMSK; // 准备在中断里面写入数据
}

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_OTG_FS_INEP[0].DIEPCTL |= USB_OTG_DIEPCTL_STALL; // EP0_IN设为STALL
      size = USB_ERROR;
      //printf("STALL EP0_IN\n");
  }
  
  // 发送的字节数不能超过主机要求的最大长度
  if (size > req->wLength)
    size = req->wLength; // 只修改发送长度, 内容原封不动, 切记!!!!
  // 比如在请求字符串语言列表时, 待发送的数据量是str->bLength=4
  // 如果主机要求最大只能发送req->wLength=2字节, 则数据内容str->bLength应该仍为4, 不能改成2
  return size;
}

/* 处理IN端点中断 */
void USB_InEndpointProcess(void)
{
  uint8_t ep_id, i;
  uint16_t len;
  
  for (ep_id = 0; ep_id < EPCNT; ep_id++)
  {
    if (USB_OTG_FS_DEVICE->DAINT & _BV(USB_OTG_DAINT_IEPINT_Pos + ep_id))
    {
      // ep_id端点上有事件发生
      if (USB_OTG_FS_INEP[ep_id].DIEPINT & USB_OTG_DIEPINT_XFRC)
      {
        // 发送成功
        USB_OTG_FS_INEP[ep_id].DIEPINT = USB_OTG_DIEPINT_XFRC;
        len = usb_txsent[ep_id];
        usb_txsent[ep_id] = 0;
        for (i = 0; i < EPCNT; i++)
        {
          if (USB_OTG_FS_INEP[i].DIEPTSIZ & USB_OTG_DIEPTSIZ_XFRSIZ)
            break;
        }
        if (i == EPCNT)
          USB_OTG_FS_DEVICE->DIEPMSK &= ~USB_OTG_DIEPMSK_ITTXFEMSK; // 停止往FIFO放数据
        
        if (ep_id == 0)
          USB_ENDP0In(len);
        else if (ep_id == 1)
          USB_ENDP1In(len);
      }
      if (USB_OTG_FS_INEP[ep_id].DIEPINT & USB_OTG_DIEPINT_TXFE)
      {
        // 发送过程中往FIFO放入数据
        len = USB_OTG_FS_INEP[ep_id].DIEPTSIZ & USB_OTG_DIEPTSIZ_XFRSIZ;
        if (len > 0)
        {
          //printf("TXFE! ep_id=%d, len=%d\n", ep_id, len);
          USB_WriteDFIFO(ep_id, usb_txbuffer[ep_id], len);
          usb_txsent[ep_id] = len;
        }
      }
    }
  }
}

void USB_Init(void)
{
  RCC->AHBENR |= RCC_AHBENR_OTGFSEN;
  RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPEEN;
  
  GPIOA->CRH = (GPIOA->CRH & 0xfff00fff) | 0xbb000; // 将PA11和PA12设为复用推挽输出, 速率为最高
  GPIOE->BSRR = GPIO_BSRR_BS1; // PE1设为高电平
  GPIOE->CRL = (GPIOE->CRL & 0xffffff0f) | 0x30; // PE为USB_DP上的上拉电阻, 高电平表明设备插入主机
  // USB_DP上的上拉电阻最好不要直接接高电平, 而是接到某个I/O口上(这里是PB3), 方便查看调试信息, 避免USB线拔来拔去
  // STM32F4单片机有USB_OTG_GCCFG_NOVBUSSENS这一位, 所以不需要外接上拉电阻, 用内部上拉电阻就可以
  
  printf("USB core ID: %#x\n", USB_OTG_FS->CID);
  
  USB_OTG_FS->GRXFSIZ = 128; // 设置接收缓冲区的大小
  USB_OTG_FS->DIEPTXF0_HNPTXFSIZ = (64 << USB_OTG_DIEPTXF_INEPTXFD_Pos) | 128; // 设置0号发送缓冲区的位置和大小
  USB_OTG_FS->DIEPTXF[0] = (128 << USB_OTG_DIEPTXF_INEPTXFD_Pos) | 192; // 设置1号发送缓冲区的位置和大小
  
  USB_OTG_FS->GAHBCFG |= USB_OTG_GAHBCFG_GINT; // 开USB总中断
  USB_OTG_FS->GINTMSK |= USB_OTG_GINTMSK_IEPINT | USB_OTG_GINTMSK_MMISM | USB_OTG_GINTMSK_RXFLVLM | USB_OTG_GINTMSK_USBRST; // 开USB IN端点中断, 模式错误中断, USB接收中断和复位中断
  USB_OTG_FS_DEVICE->DAINTMSK |= (3 << USB_OTG_DAINTMSK_IEPM_Pos); // 设置GINTMSK_IEPINT包含的端点号: EP0_IN, EP1_IN
  USB_OTG_FS_DEVICE->DIEPMSK |= USB_OTG_DIEPMSK_XFRCM; // 设置GINTMSK_IEPINT包含的中断: 发送完成中断
  NVIC_EnableIRQ(OTG_FS_IRQn);
  
  USB_OTG_FS->GUSBCFG |= USB_OTG_GUSBCFG_FDMOD; // 设为Device模式
  //USB_OTG_FS->GCCFG |= USB_OTG_GCCFG_NOVBUSSENS; // F4单片机需要设置这一位, 开启内部上拉电阻
  USB_OTG_FS->GCCFG |= USB_OTG_GCCFG_PWRDWN; // 禁用VBUS引脚; 退出Power Down模式
  
  // 配置Bulk型端点1
  USB_OTG_FS_INEP[1].DIEPCTL |= USB_OTG_DIEPCTL_SD0PID_SEVNFRM | (1 << USB_OTG_DIEPCTL_TXFNUM_Pos) | (2 << USB_OTG_DIEPCTL_EPTYP_Pos) | (64 << USB_OTG_DOEPCTL_MPSIZ_Pos);
  USB_OTG_FS_OUTEP[1].DOEPCTL |= USB_OTG_DOEPCTL_EPENA | USB_OTG_DOEPCTL_SD0PID_SEVNFRM | (2 << USB_OTG_DOEPCTL_EPTYP_Pos) | (64 << USB_OTG_DOEPCTL_MPSIZ_Pos);
  // 注意: DIEPCTL0_MPSIZ和DIPECTL1_MPSIZ以及DOEPCTL0_MPSIZ和DOPECTL1_MPSIZ的定义是不一样的
}

/* 读取收到的数据 */
void USB_ReadDFIFO(void *data, int len)
{
  int curr, i;
  uint32_t value;
  
  for (i = 0; i < len; i += 4)
  {
    value = USB_OTG_FS_DFIFO->DFIFO;
    curr = len - i;
    if (curr > 4)
      curr = 4;
    memcpy((uint8_t *)data + i, &value, curr);
  }
}

/* 处理USB接收中断 */
void USB_RxFIFONonEmpty(void)
{
  uint8_t buffer[64];
  uint8_t ep_id, pkt_status;
  uint16_t len;
  uint32_t rx_status;
  
  rx_status = USB_OTG_FS->GRXSTSP;
  ep_id = (rx_status & USB_OTG_GRXSTSP_EPNUM) >> USB_OTG_GRXSTSP_EPNUM_Pos; // 接收数据成功的端点号
  len = (rx_status & USB_OTG_GRXSTSP_BCNT) >> USB_OTG_GRXSTSP_BCNT_Pos; // 收到的字节数
  pkt_status = (rx_status & USB_OTG_GRXSTSP_PKTSTS) >> USB_OTG_GRXSTSP_PKTSTS_Pos;
  
  if (pkt_status == 2)
  {
    // OUT data packet received
    USB_ReadDFIFO(buffer, len);
    if (ep_id == 0)
      USB_ENDP0Out(buffer, len);
    else if (ep_id == 1)
      USB_ENDP1Out(buffer, len);
  }
  else if (pkt_status == 6)
  {
    // SETUP data packet received 
    USB_ReadDFIFO(buffer, len);
    if (ep_id == 0)
      USB_ENDP0Setup(buffer, len);
  }
}

/* 写入待发送的数据 */
void USB_WriteDFIFO(int num, const void *data, int len)
{
  int i;
  
  for (i = 0; i < len; i += 4)
    USB_OTG_FS_DFIFO[num].DFIFO = *(__packed uint32_t *)((uint8_t *)data + i);
}

void OTG_FS_IRQHandler(void)
{
  if (USB_OTG_FS->GINTSTS & USB_OTG_GINTSTS_IEPINT)
  {
    // 某个IN端点上有事件发生
    USB_InEndpointProcess();
  }
  if (USB_OTG_FS->GINTSTS & USB_OTG_GINTSTS_MMIS)
  {
    // 主从模式错误
    USB_OTG_FS->GINTSTS = USB_OTG_GINTSTS_MMIS;
    printf("USB mode mismatch!\n");
  }
  if (USB_OTG_FS->GINTSTS & USB_OTG_GINTSTS_RXFLVL)
  {
    // 收到数据
    USB_RxFIFONonEmpty();
  }
  if (USB_OTG_FS->GINTSTS & USB_OTG_GINTSTS_USBRST)
  {
    // USB复位
    USB_OTG_FS->GINTSTS = USB_OTG_GINTSTS_USBRST;
    USB_OTG_FS_DEVICE->DCFG &= ~USB_OTG_DCFG_DAD;
    printf("USB reset!\n");
    
    // 打开端口1 (每次复位后会自动关闭)
    USB_OTG_FS_INEP[1].DIEPCTL |= USB_OTG_DIEPCTL_USBAEP;
    USB_OTG_FS_OUTEP[1].DOEPCTL |= USB_OTG_DOEPCTL_USBAEP;
  }
}

【程序运行结果】

STM32F107VC USB
SystemCoreClock=72000000
Flash Size: 256KB, Disk Size: 244.0KB, Disk Addr: 0x08003000
Flash is unlocked!
USB core ID: 0x1000
USB reset!
USB reset!
0S+8
0-18
0+0
USB reset!
0S+8
ADDR_18
0-0
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
0-0
0S+8
0-1
0+0
0S+8
0-9
0+0
0S+8
0-32
0+0

 

如果想要移植到STM32F407VE单片机上运行,则需要:

(1)头文件#include <stm32f1xx.h>改为<stm32f4xx.h>

(2)RCC时钟和USART串口初始化函数改为:

(晶振的大小为8MHz:HSE_VALUE=8000000)

void clock_init(void)
{
  RCC->CR |= RCC_CR_HSEON;
  while ((RCC->CR & RCC_CR_HSERDY) == 0);
  RCC->PLLCFGR = (RCC->PLLCFGR & ~(RCC_PLLCFGR_PLLM | RCC_PLLCFGR_PLLN | RCC_PLLCFGR_PLLP | RCC_PLLCFGR_PLLQ)) | 
    (8 << RCC_PLLCFGR_PLLM_Pos) | (336 << RCC_PLLCFGR_PLLN_Pos) | (7 << RCC_PLLCFGR_PLLQ_Pos) | RCC_PLLCFGR_PLLSRC_HSE;
  RCC->CR |= RCC_CR_PLLON;
  while ((RCC->CR & RCC_CR_PLLRDY) == 0);
  
  FLASH->ACR |= FLASH_ACR_LATENCY_5WS;
  while ((FLASH->ACR & FLASH_ACR_LATENCY) != FLASH_ACR_LATENCY_5WS);
  
  RCC->CFGR |= RCC_CFGR_PPRE1_DIV4 | RCC_CFGR_PPRE2_DIV2 | RCC_CFGR_SW_PLL;
  while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL);
  SystemCoreClockUpdate();
}

void usart_init(int baud_rate)
{
  RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
  RCC->APB2ENR |= RCC_APB2ENR_USART1EN;
  
  GPIOA->AFR[1] |= (7 << GPIO_AFRH_AFSEL9_Pos) | (7 << GPIO_AFRH_AFSEL10_Pos);
  GPIOA->MODER = (GPIOA->MODER & ~(GPIO_MODER_MODE9 | GPIO_MODER_MODE10)) | GPIO_MODER_MODE9_1 | GPIO_MODER_MODE10_1;
  GPIOA->OSPEEDR |= GPIO_OSPEEDR_OSPEED9 | GPIO_OSPEEDR_OSPEED10;
  
  USART1->BRR = SystemCoreClock / 2 / baud_rate;
  USART1->CR1 = USART_CR1_UE | USART_CR1_TE | USART_CR1_RE;
}

(3)修改USB_Init函数:

RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
RCC->AHB2ENR |= RCC_AHB2ENR_OTGFSEN;

// 将PA11和PA12设为复用推挽输出, 速率为最高
GPIOA->AFR[1] |= (10 << GPIO_AFRH_AFSEL11_Pos) | (10 << GPIO_AFRH_AFSEL12_Pos);
GPIOA->MODER = (GPIOA->MODER & ~(GPIO_MODER_MODE11 | GPIO_MODER_MODE12)) | GPIO_MODER_MODE11_1 | GPIO_MODER_MODE12_1;
GPIOA->OSPEEDR |= GPIO_OSPEEDR_OSPEED11 | GPIO_OSPEEDR_OSPEED12;

注意STM32F4单片机的USB OTG有内部上拉电阻,所以不再需要外接上拉电阻。

USB_OTG_FS->GCCFG |= USB_OTG_GCCFG_NOVBUSSENS; // F4单片机需要设置这一位, 开启内部上拉电阻
USB_OTG_FS->GCCFG |= USB_OTG_GCCFG_PWRDWN; // 退出Power Down模式

(4)Flash读写部分要大改,因为F4单片机的Flash页很大,而且大小不统一,有的是16KB,有的是64KB,还有的是128KB。
可以先暂时改成读写SRAM:

#define USE_SRAM // 将磁盘文件保存在SRAM中, 而不是FLASH中

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

巨大八爪鱼

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

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

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

打赏作者

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

抵扣说明:

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

余额充值