【USB】STM32F103C8單片機操作USB寄存器實現USB設備的枚舉過程

本程序實現了主機枚舉USB設備的完整過程,設備狀態由Powered狀態轉變爲最終的Configured狀態,全部由STM32寄存器實現,不涉及複雜的庫函數。

設備的枚舉類型是USB大容量存儲(Bulk Only型)。端點0爲控制端點,端點1爲同時用於數據發送和接收的Bulk型端點(這部分代碼還沒寫)。

配置描述符裏面interface->bNumEndpoints要寫2,因爲EP1_IN和EP1_OUT分別算一個端點,共兩個端點(ENDP0不能寫進配置描述符的端點描述符裏面)。實驗證明兩個不同傳輸方向的Bulk型端點使用同一個端點號是可行的(1+31 1-36 1-13,端點1先收到CBW,再發送數據和CSW)。

Keil工程文件下載地址:https://pan.baidu.com/s/1c2yIKzY

參考文檔:usb_20_081017/usb_20.pdf,請認真閱讀其中的第八章,理清Bulk transfer和Control transfer的完整過程。設備描述符等數據結構請參閱第九章的表格。

電路連接:

USB接口最左側的VBUS接+5V,通過AMS1117降壓到3.3V給STM32F103C8單片機供電。D-通過22Ω的電阻接PA11,D+通過22Ω的電阻接PA12,D+還通過一個1.5kΩ的電阻接PB3,GND引腳接到單片機的GND上。

單片機晶振爲8MHz,所用的串口是USART3,波特率爲115200。

注意,程序中要慎用printf函數打印字符串,最好不要打印大量的字符串內容,否則由於設備響應主機不及時,USB將無法正常工作。

【勘誤】

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 "USB.h"

void USB_Init(void);

void dump_data(const void *data, uint16_t len)
{
  const uint8_t *p = data;
  while (len--)
    printf("%02X", *p++);
  printf("\n");
}

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;
}

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_Init();
  
  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)

typedef __packed struct
{
  uint8_t bmRequestType;
  uint8_t bRequest;
  uint16_t wValue;
  uint16_t wIndex;
  uint16_t wLength;
} USB_DeviceRequest;

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;
  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;
  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;
  uint8_t bEndpointAddress;
  uint8_t bmAttributes;
  uint16_t wMaxPacketSize;
  uint8_t bInterval;
} USB_EndpointDescriptor;

typedef __packed struct
{
  uint8_t bLength;
  uint8_t bDescriptorType;
  uint16_t wData[1];
} USB_StringDescriptor;

【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.c】

 

#include <stdio.h>
#include <stm32f10x.h>
#include <wchar.h>
#include "USB.h"
#include "usb_def.h"

void dump_data(const void *data, uint16_t len);
void USB_ReadPMA(uint16_t offset, void *buffer, uint16_t len);
void USB_WritePMA(uint16_t usbaddr, const void *buffer, uint16_t len);

static void USB_GetDescriptor(const USB_DeviceRequest *devreq);

// 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;
}

void USB_CorrectTransfer(void)
{
  static uint8_t pending_addr = 0;
  uint8_t buffer[64];
  uint8_t ep_id;
  uint16_t len;
  USB_DeviceRequest *devreq = (USB_DeviceRequest *)buffer;
  
  ep_id = USB->ISTR & USB_ISTR_EP_ID; // 端點號
  if (ep_id == 0)
  {
    if (USB->ISTR & USB_ISTR_DIR)
    {
      // 端點0接收成功
      //if (USB->EP0R & USB_EP0R_SETUP)
      //  printf("[SETUP]\n");
      USB->EP0R = USB_EPnR(USB->EP0R) & ~USB_EP0R_CTR_RX; // 清除中斷標誌位
      
      len = USB_BufDesc[0].COUNT_RX & USB_COUNT0_RX_COUNT0_RX; // 收到的字節數
      printf("0+%d\n", len);
      if (len > 0)
      {
        USB_ReadPMA(USB_BufDesc[0].ADDR_RX, buffer, len);
        dump_data(buffer, len);
        
        if ((devreq->bmRequestType & REQUEST_TYPE) == STANDARD_REQUEST && (devreq->bmRequestType & RECIPIENT) == DEVICE_RECIPIENT) // 標準設備請求
        {
          switch (devreq->bRequest)
          {
            case GET_DESCRIPTOR:
              USB_GetDescriptor(devreq);
              break;
            case SET_ADDRESS:
              pending_addr = devreq->wValue; // 先暫存USB設備地址, 必須等到Status stage結束後才能寫入USB->DADDR寄存器中
              USB_BufDesc[0].COUNT_TX = 0;
              USB->EP0R = USB_EPnR(USB->EP0R) | ((USB->EP0R & USB_EP0R_STAT_TX) ^ USB_EP0R_STAT_TX); // TX=VALID
              break;
            case SET_CONFIGURATION:
              printf("CFG%hd\n", devreq->wValue);
              USB_BufDesc[0].COUNT_TX = 0;
              USB->EP0R = USB_EPnR(USB->EP0R) | ((USB->EP0R & USB_EP0R_STAT_TX) ^ USB_EP0R_STAT_TX); // TX=VALID
              break;
            default:
              dump_data(buffer, len);
          }
        }
        else
          dump_data(buffer, len);
      }
      else
      {
        // 收到一個空包
        
        // RX=VALID, TX=NAK, STATUS_OUT=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));
      }
    }
    else
    {
      // 端點0發送成功
      USB->EP0R = USB_EPnR(USB->EP0R) & ~USB_EP0R_CTR_TX;
      
      // 當data stage的最後一個transaction爲IN token時(在本程序中所有的control transfer的data stage都最多隻有一個transaction), 應將STATUS_OUT置位
      // 這樣在接下來的status stage的data phase中如果主機發送的數據長度不爲0, 則設備不會迴應ACK, 而是報告錯誤
      len = USB_BufDesc[0].COUNT_TX;
      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
      
      if (pending_addr != 0)
      {
        // 主機在某個control transfer的data stage期間將分配的設備地址發送給設備
        // 但設備必須在status stage完成後纔將地址寫入寄存器
        USB->DADDR = USB_DADDR_EF | pending_addr;
        printf("DADDR_%02X\n", pending_addr);
        pending_addr = 0;
      }
      
      printf("0-%d\n", len);
    }
  }
  else if (ep_id == 1)
  {
    if (USB->ISTR & USB_ISTR_DIR)
    {
      // 端點1接收成功
      USB->EP1R = USB_EPnR(USB->EP1R) & ~USB_EP1R_CTR_RX;
      
      len = USB_BufDesc[1].COUNT_RX & USB_COUNT1_RX_COUNT1_RX; // 收到的字節數
      printf("1+%d\n", len);
      if (len > 0)
      {
        USB_ReadPMA(USB_BufDesc[1].ADDR_RX, buffer, len);
        dump_data(buffer, len);
      }
      
      USB->EP1R = USB_EPnR(USB->EP1R) | ((USB->EP1R & USB_EP1R_STAT_RX) ^ USB_EP1R_STAT_RX);
    }
    else
    {
      // 端點1發送成功
    }
  }
}

static void USB_GetDescriptor(const USB_DeviceRequest *devreq)
{
  uint8_t buffer[64];
  uint8_t type = devreq->wValue >> 8; // 高8位爲請求的描述符類型
  USB_ConfigurationDescriptor *config = (USB_ConfigurationDescriptor *)buffer;
  USB_DeviceDescriptor *device = (USB_DeviceDescriptor *)buffer;
  USB_EndpointDescriptor *endpoints;
  USB_InterfaceDescriptor *interface;
  USB_StringDescriptor *str = (USB_StringDescriptor *)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; // 配置數
      USB_BufDesc[0].COUNT_TX = 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外, 此接口還需要的端點數
      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; // 接口名稱字符串序號
    
      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;
    
      USB_BufDesc[0].COUNT_TX = config->wTotalLength;
      break;
    case STRING_DESCRIPTOR:
      str->bDescriptorType = STRING_DESCRIPTOR;
      if (devreq->wIndex == 0x409) // 字符串英文內容
      {
        // 字符串的編碼爲UTF-16
        switch (devreq->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", devreq->wValue & 0xff);
            wcscpy((wchar_t *)str->wData, L"???");
        }
        str->bLength = 2 + wcslen((wchar_t *)str->wData) * 2;
      }
      else if (devreq->wIndex == 0) // 字符串語言列表
      {
        str->bLength = 4;
        str->wData[0] = 0x0409; // English (United States)
      }
      else
      {
        printf("IND%hd\n", devreq->wIndex);
        return;
      }
      USB_BufDesc[0].COUNT_TX = 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
      return;
  }
  
  // 發送的字節數不能超過主機要求的最大長度
  if (USB_BufDesc[0].COUNT_TX > devreq->wLength)
    USB_BufDesc[0].COUNT_TX = devreq->wLength; // 只修改發送長度, 內容原封不動, 切記!!!!
  // 比如在請求字符串語言列表時, 待發送的數據量是str->bLength=4
  // 如果主機要求最大隻能發送devreq->wLength=2字節, 則數據內容str->bLength應該仍爲4, 不能改成2
  
  USB_WritePMA(USB_BufDesc[0].ADDR_TX, buffer, USB_BufDesc[0].COUNT_TX);
  USB->EP0R = USB_EPnR(USB->EP0R) | ((USB->EP0R & USB_EP0R_STAT_TX) ^ USB_EP0R_STAT_TX); // STAT_TX設爲VALID, 允許發送; STAT_RX保持NAK
}

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();
}

 

 

 

 

 

【程序運行結果】

USB線插到電腦上後,電腦先給一個名叫“Hello Product!”的設備安裝驅動,設備的狀態最開始是正常的,但最後提示驅動安裝失敗,設備無法啓動。

串口輸出結果分析:

 

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
USB reset! // 端點沒有迴應, 所以主機認爲出錯了, 因此就發送了復位請求
USB reset!
0+8
8006000100004000
0-18
0+0
0+8
0005140000000000
DADDR_14
0-0
0+8
8006000100001200
0-18
0+0
USB reset!
USB reset!
0+8
8006000100004000
0-18
0+0
USB reset!
USB reset!
0+8
8006000100004000
0-18
0+0
0+8
0005160000000000
DADDR_16
0-0
0+8
8006000100001200
0-18
0+0
USB reset!
USB reset!
0+8
8006000100004000
0-18
0+0
0+8
0005170000000000
DADDR_17
0-0
0+8
8006000100001200
0-18
0+0
0+8
800600020000FF00
0-32
0+0
0+8
0009010000000000
CFG1
0-0
1+31
555342431010AF0D2400000080000612000000240000000000000000000000
USB reset!
USB reset!
0+8
8006000100004000
0-18
0+0
0+8
0005180000000000
DADDR_18
0-0
0+8
8006000100001200
0-18
0+0
USB reset!
USB reset!
0+8
8006000100004000
0-18
0+0
USB reset!
USB reset!
0+8
8006000100004000
0-18
0+0
0+8
00051A0000000000
DADDR_1A
0-0
0+8
8006000100001200
0-18
0+0
0+8
800600020000FF00
0-32
0+0

 

 

 

 

 

發佈了78 篇原創文章 · 獲贊 39 · 訪問量 16萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章