本程序实现了主机枚举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将无法正常工作。
【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>© 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