【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中

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章