本程序的目的是为了演示STM32 USB寄存器的使用方法以及SCSI命令的处理流程,程序只实现了基本的磁盘读写功能。
该USB设备使用了3个端点:ENDP0(Control型端点),EP1_IN和EP1_OUT(Bulk型端点)。
由于时间关系, 下面的无关紧要的功能还没做:
SCSI_REQUEST_SENSE命令只实现了返回"磁盘驱动器已弹出"的信息(也就是Table 203 Preferred TEST UNIT READY responses中的CHECK CONDITION - NOT READY - MEDIUM NOT PRESENT这一条信息)
读写磁盘时没有检验数据块的地址和长度是否有效
Verify10和verify12命令没有实现, 所以不能使用Windows的磁盘检查工具检查磁盘
USB的suspend/resume
如果要实现这些功能,保证磁盘使用的可靠性的话,请自行参考SCSI命令集的PDF文档以及STM32 USB标准库里面的大容量存储例程的代码
未实现的命令都会在串口中断中用dump_data函数将命令内容打印出来, 并且有\a字符的响铃声
参考文档:usb_20_081017/usb_20.pdf,请认真阅读其中的第八章,理清Bulk transfer和Control transfer的完整过程。设备描述符等数据结构请参阅第九章的表格。
Keil工程文件下载地址:https://pan.baidu.com/s/1c2yIKzY
电路连接:
USB接口最左侧的VBUS接+5V,通过AMS1117降压到3.3V给STM32F103C8单片机供电。D-通过22Ω的电阻接PA11,D+通过22Ω的电阻接PA12,D+还通过一个1.5kΩ的电阻接PB3,GND引脚接到单片机的GND上。
单片机晶振为8MHz,所用的串口是USART3,波特率为115200。
注意,程序中要慎用printf函数打印字符串,最好不要打印大量的字符串内容,否则由于设备响应主机不及时,USB将无法正常工作。
【main.c】
#include <stdio.h> #include <stm32f10x.h> #include <wchar.h> #include "USB.h" #include "usb_test.h" void dump_data(const void *data, uint16_t len) { const uint8_t *p = data; while (len--) printf("%02X", *p++); printf("\a\n"); // \a: 响铃 } int fputc(int ch, FILE *fp) { if (fp == stdout) { if (ch == '\n') { while ((USART3->SR & USART_SR_TXE) == 0); USART3->DR = '\r'; } while ((USART3->SR & USART_SR_TXE) == 0); USART3->DR = ch; } return ch; } // Keil MDK使用微库时, 下面两个函数必须自己实现 wchar_t *wcscpy(wchar_t *s1, const wchar_t *s2) // 复制UTF-16字符串 { wchar_t *r = s1; while ((*r++ = *s2++) != 0); return s1; } size_t wcslen(const wchar_t *s) // 求UTF-16字符串的长度 { size_t n = 0; while (*s++) n++; return n; } int main(void) { uint8_t data; RCC->APB1ENR = RCC_APB1ENR_USART3EN | RCC_APB1ENR_USBEN; RCC->APB2ENR = RCC_APB2ENR_AFIOEN | RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN; AFIO->MAPR = AFIO_MAPR_SWJ_CFG_JTAGDISABLE; // 使用SWD调试接口, 禁用JTAG // USB引脚PA11~12无需配置 GPIOB->BSRR = GPIO_BSRR_BS3; // PB3设为高电平 GPIOB->CRH = 0x44444b44; // 串口发送引脚PB10设为复用推挽输出 GPIOB->CRL = 0x44483444; // PB3为USB_DP上的上拉电阻, 高电平表明设备插入主机 // USB_DP上的上拉电阻最好不要直接接高电平, 而是接到某个I/O口上(这里是PB3), 方便查看调试信息, 避免USB线拔来拔去 USART3->BRR = 312; // 波特率: 115200 USART3->CR1 = USART_CR1_UE | USART_CR1_TE | USART_CR1_RE; printf("STM32F103C8 USB\n"); USB_CalcDiskAddr(); // 根据当前芯片的Flash大小, 自动计算磁盘数据存放位置 USB_Init(); // 初始化USB while (1) { if (USART3->SR & USART_SR_RXNE) { data = USART3->DR; if (data == 'u') printf("USB->EP0R=0x%04x, USB->EP1R=0x%04x, USB->ISTR=0x%04x, USB->CNTR=0x%04x\n", USB->EP0R, USB->EP1R, USB->ISTR, USB->CNTR); } } }【USB.h】
// STM32 CubeMX头文件中复制过来的USB寄存器定义 typedef struct { __IO uint16_t EP0R; /*!< USB Endpoint 0 register, Address offset: 0x00 */ __IO uint16_t RESERVED0; /*!< Reserved */ __IO uint16_t EP1R; /*!< USB Endpoint 1 register, Address offset: 0x04 */ __IO uint16_t RESERVED1; /*!< Reserved */ __IO uint16_t EP2R; /*!< USB Endpoint 2 register, Address offset: 0x08 */ __IO uint16_t RESERVED2; /*!< Reserved */ __IO uint16_t EP3R; /*!< USB Endpoint 3 register, Address offset: 0x0C */ __IO uint16_t RESERVED3; /*!< Reserved */ __IO uint16_t EP4R; /*!< USB Endpoint 4 register, Address offset: 0x10 */ __IO uint16_t RESERVED4; /*!< Reserved */ __IO uint16_t EP5R; /*!< USB Endpoint 5 register, Address offset: 0x14 */ __IO uint16_t RESERVED5; /*!< Reserved */ __IO uint16_t EP6R; /*!< USB Endpoint 6 register, Address offset: 0x18 */ __IO uint16_t RESERVED6; /*!< Reserved */ __IO uint16_t EP7R; /*!< USB Endpoint 7 register, Address offset: 0x1C */ __IO uint16_t RESERVED7[17]; /*!< Reserved */ __IO uint16_t CNTR; /*!< Control register, Address offset: 0x40 */ __IO uint16_t RESERVED8; /*!< Reserved */ __IO uint16_t ISTR; /*!< Interrupt status register, Address offset: 0x44 */ __IO uint16_t RESERVED9; /*!< Reserved */ __IO uint16_t FNR; /*!< Frame number register, Address offset: 0x48 */ __IO uint16_t RESERVEDA; /*!< Reserved */ __IO uint16_t DADDR; /*!< Device address register, Address offset: 0x4C */ __IO uint16_t RESERVEDB; /*!< Reserved */ __IO uint16_t BTABLE; /*!< Buffer Table address register, Address offset: 0x50 */ __IO uint16_t RESERVEDC; /*!< Reserved */ } USB_TypeDef; /* USB device FS */ #define USB_BASE (APB1PERIPH_BASE + 0x00005C00U) /*!< USB_IP Peripheral Registers base address */ #define USB_PMAADDR (APB1PERIPH_BASE + 0x00006000U) /*!< USB_IP Packet Memory Area base address */ #define USB ((USB_TypeDef *)USB_BASE) // 对于单向双缓冲型的发送端点, 寄存器名称后缀都是TX; 单向双缓冲接收端点则都是RX typedef struct { __IO uint16_t ADDR_TX; __IO uint16_t RESERVED0; __IO uint16_t COUNT_TX; __IO uint16_t RESERVED1; __IO uint16_t ADDR_RX; __IO uint16_t RESERVED2; __IO uint16_t COUNT_RX; __IO uint16_t RESERVED3; } USB_BufferDescriptor; #define USB_BufDesc ((USB_BufferDescriptor *)(USB_PMAADDR + USB->BTABLE)) #define USB_ISTR_MASK 0x7f00 #define USB_EPnR_MASK_T 0x8f8f // 防止翻转位发生翻转用的掩码 #define USB_EPnR_MASK_CW0 0x8080 // 防止rc_w0型的位被清零 #define USB_EPnR(reg) ((reg & USB_EPnR_MASK_T) | USB_EPnR_MASK_CW0)【usb_def.h】
/** ****************************************************************************** * @file usb_def.h * @author MCD Application Team * @version V4.1.0 * @date 26-May-2017 * @brief Definitions related to USB Core ****************************************************************************** * @attention * * <h2><center>© COPYRIGHT(c) 2017 STMicroelectronics</center></h2> * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * 3. Neither the name of STMicroelectronics nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * ****************************************************************************** */ /* Define to prevent recursive inclusion -------------------------------------*/ #ifndef __USB_DEF_H #define __USB_DEF_H /* Includes ------------------------------------------------------------------*/ /* Exported types ------------------------------------------------------------*/ typedef enum _RECIPIENT_TYPE { DEVICE_RECIPIENT, /* Recipient device */ INTERFACE_RECIPIENT, /* Recipient interface */ ENDPOINT_RECIPIENT, /* Recipient endpoint */ OTHER_RECIPIENT } RECIPIENT_TYPE; typedef enum _STANDARD_REQUESTS { GET_STATUS = 0, CLEAR_FEATURE, RESERVED1, SET_FEATURE, RESERVED2, SET_ADDRESS, GET_DESCRIPTOR, SET_DESCRIPTOR, GET_CONFIGURATION, SET_CONFIGURATION, GET_INTERFACE, SET_INTERFACE, TOTAL_sREQUEST, /* Total number of Standard request */ SYNCH_FRAME = 12 } STANDARD_REQUESTS; /* Definition of "USBwValue" */ typedef enum _DESCRIPTOR_TYPE { DEVICE_DESCRIPTOR = 1, CONFIG_DESCRIPTOR, STRING_DESCRIPTOR, INTERFACE_DESCRIPTOR, ENDPOINT_DESCRIPTOR, DEVICE_BOS_DESCRIPTOR = 0xF } DESCRIPTOR_TYPE; /* Feature selector of a SET_FEATURE or CLEAR_FEATURE */ typedef enum _FEATURE_SELECTOR { ENDPOINT_STALL, DEVICE_REMOTE_WAKEUP } FEATURE_SELECTOR; /* Exported constants --------------------------------------------------------*/ /* Definition of "USBbmRequestType" */ #define REQUEST_TYPE 0x60 /* Mask to get request type */ #define STANDARD_REQUEST 0x00 /* Standard request */ #define CLASS_REQUEST 0x20 /* Class request */ #define VENDOR_REQUEST 0x40 /* Vendor request */ #define RECIPIENT 0x1F /* Mask to get recipient */ /* Exported macro ------------------------------------------------------------*/ /* Exported functions ------------------------------------------------------- */ #endif /* __USB_DEF_H */ /************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/【usb_test.h】
#define ntohlp(p) ((*(p) << 24) | (*((p) + 1) << 16) | (*((p) + 2) << 8) | *((p) + 3)) // 32位大端序转小端序 (p为uint8_t *) #define ntohsp(p) ((*(p) << 8) | *((p) + 1)) // 16位大端序转小端序 (p为uint8_t *) typedef enum { USB_UNSUPPORTED = -2, // 未处理 (不支持的操作), 将会由dump_data打印出来 USB_ERROR = -1 // 已处理但遇到错误 } USB_Error; typedef struct { uint8_t *data_rx; uint8_t *data_tx; int32_t len_rx; int32_t len_tx; } USB_EndpointData; typedef __packed struct { uint8_t bmRequestType; uint8_t bRequest; uint16_t wValue; uint16_t wIndex; uint16_t wLength; } USB_Request; typedef __packed struct { uint8_t bLength; uint8_t bDescriptorType; uint16_t wTotalLength; uint8_t bNumInterfaces; uint8_t bConfigurationValue; uint8_t iConfiguration; uint8_t bmAttributes; uint8_t bMaxPower; } USB_ConfigurationDescriptor; typedef __packed struct { uint8_t bLength; uint8_t bDescriptorType; uint16_t bcdUSB; uint8_t bDeviceClass; uint8_t bDeviceSubClass; uint8_t bDeviceProtocol; uint8_t bMaxPacketSize0; uint16_t idVendor; uint16_t idProduct; uint16_t bcdDevice; uint8_t iManufacturer; uint8_t iProduct; uint8_t iSerialNumber; uint8_t bNumConfigurations; } USB_DeviceDescriptor; typedef __packed struct { uint8_t bLength; uint8_t bDescriptorType; uint8_t bEndpointAddress; uint8_t bmAttributes; uint16_t wMaxPacketSize; uint8_t bInterval; } USB_EndpointDescriptor; typedef __packed struct { uint8_t bLength; uint8_t bDescriptorType; uint8_t bInterfaceNumber; uint8_t bAlternateSetting; uint8_t bNumEndpoints; uint8_t bInterfaceClass; uint8_t bInterfaceSubClass; uint8_t bInterfaceProtocol; uint8_t iInterface; } USB_InterfaceDescriptor; typedef __packed struct { uint8_t bLength; uint8_t bDescriptorType; uint16_t wData[1]; } USB_StringDescriptor; /* Command Block Wrapper */ typedef __packed struct { uint32_t dCBWSignature; uint32_t dCBWTag; uint32_t dCBWDataTransferLength; uint8_t bmCBWFlags; uint8_t bCBWLUN; uint8_t bCBWCBLength; uint8_t CBWCB[16]; } USB_CBW; /* Command Status Wrapper */ typedef __packed struct { uint32_t dCSWSignature; uint32_t dCSWTag; uint32_t dCSWDataResidue; uint8_t bCSWStatus; } USB_CSW; /* List of SCSI commands */ // https://en.wikipedia.org/wiki/SCSI_command (包括External links下面的一个PDF文档) typedef enum { SCSI_TEST_UNIT_READY = 0x00, SCSI_REQUEST_SENSE = 0x03, SCSI_INQUIRY = 0x12, SCSI_MODE_SENSE6 = 0x1a, SCSI_START_STOP_UNIT = 0x1b, SCSI_PREVENT_ALLOW_MEDIUM_REMOVAL = 0x1e, SCSI_READ_FORMAT_CAPACITIES = 0x23, SCSI_READ_CAPACITY10 = 0x25, SCSI_READ10 = 0x28, SCSI_WRITE10 = 0x2a } SCSI_CommandCode; void USB_CalcDiskAddr(void); void USB_CorrectTransfer(void); void USB_ENDP0In(uint16_t len); void USB_ENDP0Out(const uint8_t *data, uint16_t len); void USB_ENDP0Setup(const uint8_t *data, uint16_t len); void USB_ENDP1In(uint16_t len); void USB_ENDP1Out(const uint8_t *data, uint16_t len); void USB_ENDP1ReceiveData(const uint8_t *data, uint16_t len); void USB_ENDP1SendData(void); int16_t USB_GetDescriptor(const USB_Request *req, void *buffer); void USB_Init(void); void USB_ReadPMA(uint16_t usbaddr, void *buffer, uint16_t len); void USB_WritePMA(uint16_t usbaddr, const void *buffer, uint16_t len);【usb_test.c】
#include <stdio.h> #include <stm32f10x.h> #include <string.h> #include <wchar.h> #include "USB.h" #include "usb_def.h" #include "usb_test.h" //#define ENDP1_DEBUG // 显示端点1收发的数据量 //#define SCSI_READ_DEBUG // 显示磁盘的读取情况 #define SCSI_WRITE_DEBUG // 显示磁盘的写入情况 // 容量千万不要设置的太小, 否则将无法完成格式化!!!! (操作系统根本就不会发送CMD2AH命令) #define DISK_BLOCKCOUNT 48 #define DISK_BLOCKSIZE 1024 static uint8_t usb_ejected = 0; // 磁盘是否已弹出 static uint8_t usb_pending_addr = 0; // 用于临时存放主机分发的设备地址 static uint8_t *usb_disk; // 磁盘的存放位置 (自动计算) static USB_CSW usb_csw = {0}; // 存放SCSI命令的CSW结果 static USB_EndpointData usb_endp1 = {0}; // 端点1的剩余数据量 void dump_data(const void *data, uint16_t len); void USB_CalcDiskAddr(void) { uint32_t flash_size = *(uint16_t *)0x1ffff7e0; // flash memory size in Kbytes uint32_t disk_size = DISK_BLOCKCOUNT * DISK_BLOCKSIZE; // disk size in bytes usb_disk = (uint8_t *)0x8000000 + flash_size * 1024 - disk_size; printf("Flash Size: %dKB, Disk Size: %.1fKB, Disk Addr: 0x%p\n", flash_size, disk_size / 1024.0, usb_disk); if (usb_disk < (uint8_t *)0x8000000) { printf("Error! Disk size is too large!!!\n"); while (1); } // 解锁Flash FLASH->KEYR = 0x45670123; FLASH->KEYR = 0xcdef89ab; if ((FLASH->CR & FLASH_CR_LOCK) == 0) printf("Flash is unlocked!\n"); } void USB_CorrectTransfer(void) { uint8_t buffer[64]; uint8_t ep_id; uint16_t len; ep_id = USB->ISTR & USB_ISTR_EP_ID; // 端点号 if (ep_id == 0) { if (USB->ISTR & USB_ISTR_DIR) { // 端点0接收成功 len = USB_BufDesc[0].COUNT_RX & USB_COUNT0_RX_COUNT0_RX; // 收到的字节数 if (len > 0) USB_ReadPMA(USB_BufDesc[0].ADDR_RX, buffer, len); if (USB->EP0R & USB_EP0R_SETUP) { USB->EP0R = USB_EPnR(USB->EP0R) & ~USB_EP0R_CTR_RX; // 清除中断标志位 USB_ENDP0Setup(buffer, len); } else { USB->EP0R = USB_EPnR(USB->EP0R) & ~USB_EP0R_CTR_RX; USB_ENDP0Out(buffer, len); } } else { // 端点0发送成功 len = USB_BufDesc[0].COUNT_TX; // 发送的字节数 USB->EP0R = USB_EPnR(USB->EP0R) & ~USB_EP0R_CTR_TX; USB_ENDP0In(len); } } else if (ep_id == 1) { if (USB->ISTR & USB_ISTR_DIR) { // 端点1接收成功 len = USB_BufDesc[1].COUNT_RX & USB_COUNT1_RX_COUNT1_RX; if (len > 0) USB_ReadPMA(USB_BufDesc[1].ADDR_RX, buffer, len); USB->EP1R = USB_EPnR(USB->EP1R) & ~USB_EP1R_CTR_RX; USB_ENDP1Out(buffer, len); } else { // 端点1发送成功 len = USB_BufDesc[1].COUNT_TX; USB->EP1R = USB_EPnR(USB->EP1R) & ~USB_EP1R_CTR_TX; USB_ENDP1In(len); } } } void USB_ENDP0In(uint16_t len) { printf("0-%d\n", len); if (usb_pending_addr != 0) { // 主机在某个control transfer的data stage期间将分配的设备地址发送给设备 // 但设备必须在status stage完成后才将地址写入寄存器 USB->DADDR = USB_DADDR_EF | usb_pending_addr; printf("DADDR_%02X\n", usb_pending_addr); usb_pending_addr = 0; } // 当data stage的最后一个transaction为IN token时(在本程序中所有的control transfer的data stage都最多只有一个transaction), 应将STATUS_OUT置位 // 这样在接下来的status stage的data phase中如果主机发送的数据长度不为0, 则设备不会回应ACK, 而是报告错误 if (len != 0) USB->EP0R = USB_EPnR(USB->EP0R) | USB_EP0R_EP_KIND; // STATUS_OUT=1 USB->EP0R = USB_EPnR(USB->EP0R) | ((USB->EP0R & USB_EP0R_STAT_RX) ^ USB_EP0R_STAT_RX); // RX=VALID } void USB_ENDP0Out(const uint8_t *data, uint16_t len) { printf("0+%d\n", len); if (len == 0) { // 收到一个空包 USB->EP0R = (USB_EPnR(USB->EP0R) & ~USB_EP0R_EP_KIND) | ((USB->EP0R & (USB_EP0R_STAT_RX | USB_EP0R_STAT_TX)) ^ (USB_EP0R_STAT_RX | USB_EP0R_STAT_TX_1)); // RX=VALID, TX=NAK, STATUS_OUT=0 } else dump_data(data, len); } void USB_ENDP0Setup(const uint8_t *data, uint16_t len) { int16_t size = USB_UNSUPPORTED; // 发送的数据大小: -2表示未处理, -1表示已处理但出错, 0表示发送空包 uint8_t buffer[64]; const USB_Request *req = (const USB_Request *)data; printf("0S+%d\n", len); if ((req->bmRequestType & REQUEST_TYPE) == STANDARD_REQUEST) { if ((req->bmRequestType & RECIPIENT) == DEVICE_RECIPIENT) { // 标准设备请求 switch (req->bRequest) { case GET_DESCRIPTOR: size = USB_GetDescriptor(req, buffer); break; case SET_ADDRESS: usb_pending_addr = req->wValue; // 先暂存USB设备地址, 必须等到Status stage结束后才能写入USB->DADDR寄存器中 size = 0; break; case SET_CONFIGURATION: printf("CFG%hd\n", req->wValue); size = 0; } } else if ((req->bmRequestType & RECIPIENT) == ENDPOINT_RECIPIENT) { // 标准端点请求 if (req->bRequest == CLEAR_FEATURE && req->wValue == ENDPOINT_STALL) // 主机请求设备撤销某个端点上的STALL状态 { if (req->wIndex == 0x81) // END1_IN { USB->EP1R = USB_EPnR(USB->EP1R) | ((USB->EP1R & USB_EP1R_STAT_TX) ^ USB_EP1R_STAT_TX_1); // 从STALL恢复为NAK size = 0; } } } } else if ((req->bmRequestType & REQUEST_TYPE) == CLASS_REQUEST && (req->bmRequestType & RECIPIENT) == INTERFACE_RECIPIENT) { // PDF: Universal Serial Bus Mass Storage Class Bulk-Only Transport // Section: 3 Functional Characteristics if (req->bRequest == 0xfe) { // 3.2 Get Max LUN (class-specific request) buffer[0] = 0; size = 1; } } if (size >= 0) { USB_BufDesc[0].COUNT_TX = size; if (size > 0) USB_WritePMA(USB_BufDesc[0].ADDR_TX, buffer, size); USB->EP0R = USB_EPnR(USB->EP0R) | ((USB->EP0R & USB_EP0R_STAT_TX) ^ USB_EP0R_STAT_TX); // TX=VALID } else if (size == USB_UNSUPPORTED) dump_data(req, len); // 打印未处理的请求内容 } void USB_ENDP1In(uint16_t len) { #ifdef ENDP1_DEBUG static uint8_t newline = 0; #endif USB_ENDP1SendData(); // 发送剩余数据 // 显示上次发送成功的数据包大小 #ifdef ENDP1_DEBUG if (len == 64) { printf("*"); // 用星号代替64字节的数据包, 节约屏幕显示空间 newline = 1; } else { if (newline) { newline = 0; printf("\n"); } printf("1-%d\n", len); } #endif } void USB_ENDP1Out(const uint8_t *data, uint16_t len) { const USB_CBW *cbw = (const USB_CBW *)data; uint8_t buffer[64]; uint32_t block_addr, block_len; if (usb_endp1.len_rx == 0) { /* 接收命令 */ usb_endp1.data_tx = buffer; usb_endp1.len_tx = USB_UNSUPPORTED; #ifdef ENDP1_DEBUG printf("1+%d\n", len); printf("CMD%02XH\n", cbw->CBWCB[0]); #endif switch (cbw->CBWCB[0]) { case SCSI_TEST_UNIT_READY: /* 3.53 TEST UNIT READY command */ usb_endp1.len_tx = (usb_ejected) ? USB_ERROR : 0; break; case SCSI_REQUEST_SENSE: /* 3.37 REQUEST SENSE command */ // Test ready失败后会收到的命令 if (cbw->CBWCB[1] == 0) // DESC=0 { /* fixed format sense data (see 2.4.1.2) */ memset(buffer, 0, 18); buffer[0] = 0x70; // response code buffer[7] = 0x0a; // additional sense length if (usb_ejected) { // 在我的电脑里面的可移动磁盘上选择弹出命令后, 磁盘应该自动消失 (这相当于只取出磁盘, 不取出USB读卡器) // 但USB设备仍会保持Configured状态, 并且还会持续收到CMD00H和CMD03H命令, 等待用户在读卡器上插入新的磁盘 buffer[2] = 0x02; // sense key: not ready buffer[12] = 0x3a; // additional sense code: medium not present // 只有在系统托盘里面选择弹出磁盘时, USB设备才会从Configured状态回到Address状态, 串口会输出CFG0 } usb_endp1.len_tx = 18; } break; case SCSI_INQUIRY: /* 3.6 INQUIRY command */ if (cbw->CBWCB[1] & 0x01) // EVPD=1 { /* 5.4 Vital product data */ /* 5.4.18Supported Vital Product Data pages (00h) */ // 不管请求的page code是什么, 都只发送Page 00H memset(buffer, 0, 5); buffer[3] = 1; // page length usb_endp1.len_tx = 5; } else { buffer[0] = 0x00; // connected & direct access block device buffer[1] = 0x80; // removable buffer[2] = 0x02; // version buffer[3] = 0x02; // NORMACA & HISUP & response data format buffer[4] = 32; // additional length buffer[5] = 0; buffer[6] = 0; buffer[7] = 0; strcpy((char *)buffer + 8, "HY-Smart"); // 8-byte vendor identification strcpy((char *)buffer + 16, "SPI Flash Disk "); // 16-byte product identification strcpy((char *)buffer + 32, "1.0 "); // 4-byte product revision level usb_endp1.len_tx = 36; } break; case SCSI_MODE_SENSE6: /* 3.11 MODE SENSE(6) command */ buffer[0] = 0x03; memset(buffer + 1, 0, 3); usb_endp1.len_tx = 4; break; case SCSI_START_STOP_UNIT: /* 3.49 START STOP UNIT command */ // 弹出磁盘的命令 usb_ejected = 1; usb_endp1.len_tx = 0; break; case SCSI_PREVENT_ALLOW_MEDIUM_REMOVAL: usb_endp1.len_tx = 0; break; case SCSI_READ_FORMAT_CAPACITIES: buffer[0] = 0; buffer[1] = 0; buffer[2] = 0; buffer[3] = 8; // capacity list length buffer[4] = (DISK_BLOCKCOUNT >> 24) & 0xff; buffer[5] = (DISK_BLOCKCOUNT >> 16) & 0xff; buffer[6] = (DISK_BLOCKCOUNT >> 8) & 0xff; buffer[7] = DISK_BLOCKCOUNT & 0xff; buffer[8] = 0x02; // descriptor code: formatted media buffer[9] = (DISK_BLOCKSIZE >> 16) & 0xff; buffer[10] = (DISK_BLOCKSIZE >> 8) & 0xff; buffer[11] = DISK_BLOCKSIZE & 0xff; usb_endp1.len_tx = 12; break; case SCSI_READ_CAPACITY10: /* 3.22 READ CAPACITY (10) command */ buffer[0] = ((DISK_BLOCKCOUNT - 1) >> 24) & 0xff; // the logical block address of the LAST logical block buffer[1] = ((DISK_BLOCKCOUNT - 1) >> 16) & 0xff; buffer[2] = ((DISK_BLOCKCOUNT - 1) >> 8) & 0xff; buffer[3] = (DISK_BLOCKCOUNT - 1) & 0xff; buffer[4] = (DISK_BLOCKSIZE >> 24) & 0xff; // block length in bytes buffer[5] = (DISK_BLOCKSIZE >> 16) & 0xff; buffer[6] = (DISK_BLOCKSIZE >> 8) & 0xff; buffer[7] = DISK_BLOCKSIZE & 0xff; usb_endp1.len_tx = 8; break; case SCSI_READ10: /* 3.16 READ (10) command */ /*if (address is invalid) { // 请求的地址不合法时应返回错误, 并将EP1_IN设为STALL // 然后主机会在端点0上请求清除掉端点1的STALL状态 usb_endp1.len_tx = USB_ERROR; break; }*/ block_addr = ntohlp(cbw->CBWCB + 2); block_len = ntohsp(cbw->CBWCB + 7); usb_endp1.data_tx = usb_disk + block_addr * DISK_BLOCKSIZE; // logical block address usb_endp1.len_tx = block_len * DISK_BLOCKSIZE; // transfer length #ifdef SCSI_READ_DEBUG printf("R%d,%d\n", block_addr, block_len); #endif break; case SCSI_WRITE10: /* 3.60 WRITE (10) command */ // 当地址不合法时, 应该将EP_OUT设为STALL, 也就是将后续的所有文件数据全部STALL (这个还没实现....) block_addr = ntohlp(cbw->CBWCB + 2); block_len = ntohsp(cbw->CBWCB + 7); usb_endp1.data_rx = usb_disk + block_addr * DISK_BLOCKSIZE; // Flash地址 usb_endp1.len_rx = block_len * DISK_BLOCKSIZE; #ifdef SCSI_WRITE_DEBUG printf("W%d,%d\n", block_addr, block_len); #endif usb_endp1.len_tx = 0; break; } } else { /* 接收数据 */ usb_endp1.len_tx = 0; USB_ENDP1ReceiveData(data, len); } if (usb_endp1.len_tx >= 0) { if (usb_endp1.len_tx > cbw->dCBWDataTransferLength) usb_endp1.len_tx = cbw->dCBWDataTransferLength; // 准备好数据发送完毕后要发送的CSW if (usb_csw.dCSWSignature == 0) { usb_csw.dCSWSignature = 0x53425355; usb_csw.dCSWTag = cbw->dCBWTag; if (usb_endp1.len_rx == 0) usb_csw.dCSWDataResidue = cbw->dCBWDataTransferLength - usb_endp1.len_tx; // 未发送的数据量 else usb_csw.dCSWDataResidue = 0; // 未接收的数据量 usb_csw.bCSWStatus = 0; // Command Passed } if (usb_endp1.len_rx == 0) USB_ENDP1SendData(); } else if (usb_endp1.len_tx == USB_ERROR) { // 遇到错误时, 先发送CSW, 然后将IN端点设为STALL usb_csw.dCSWSignature = 0x53425355; usb_csw.dCSWTag = cbw->dCBWTag; usb_csw.dCSWDataResidue = cbw->dCBWDataTransferLength; usb_csw.bCSWStatus = 1; // Command Failed USB_ENDP1SendData(); } else if (usb_endp1.len_tx == USB_UNSUPPORTED) dump_data(data, len); USB->EP1R = USB_EPnR(USB->EP1R) | ((USB->EP1R & USB_EP1R_STAT_RX) ^ USB_EP1R_STAT_RX); // RX=VALID } /* ENDP1接收大量数据 */ void USB_ENDP1ReceiveData(const uint8_t *data, uint16_t len) { if (len < usb_endp1.len_rx) usb_endp1.len_rx -= len; else usb_endp1.len_rx = 0; // 擦除Flash页 if ((uint32_t)usb_endp1.data_rx % DISK_BLOCKSIZE == 0) { FLASH->CR |= FLASH_CR_PER; FLASH->AR = (uint32_t)usb_endp1.data_rx; FLASH->CR |= FLASH_CR_STRT; while (FLASH->SR & FLASH_SR_BSY); FLASH->CR &= ~FLASH_CR_PER; } // 将收到的数据写入Flash中 FLASH->CR |= FLASH_CR_PG; while (len) { *(uint16_t *)usb_endp1.data_rx = *(const uint16_t *)data; data += 2; usb_endp1.data_rx += 2; len -= 2; while (FLASH->SR & FLASH_SR_BSY); } FLASH->CR &= ~FLASH_CR_PG; usb_endp1.data_rx += len; } /* ENDP1发送大量数据 */ void USB_ENDP1SendData(void) { if (usb_endp1.len_tx > 64) { // 发送一个数据包 USB_BufDesc[1].COUNT_TX = 64; USB_WritePMA(USB_BufDesc[1].ADDR_TX, usb_endp1.data_tx, 64); usb_endp1.data_tx += 64; usb_endp1.len_tx -= 64; } else if (usb_endp1.len_tx > 0) { // 发送最后一个数据包 USB_BufDesc[1].COUNT_TX = usb_endp1.len_tx; USB_WritePMA(USB_BufDesc[1].ADDR_TX, usb_endp1.data_tx, usb_endp1.len_tx); usb_endp1.len_tx = 0; } else if ((usb_endp1.len_tx == 0 || usb_endp1.len_tx == USB_ERROR) && usb_csw.dCSWSignature != 0) { // 发送CSW状态信息 USB_BufDesc[1].COUNT_TX = sizeof(usb_csw); USB_WritePMA(USB_BufDesc[1].ADDR_TX, &usb_csw, sizeof(usb_csw)); usb_csw.dCSWSignature = 0; // 表示下次不再发送CSW } else if (usb_endp1.len_tx == USB_ERROR && usb_csw.dCSWSignature == 0) { // 处理命令时遇到错误, 且已发送CSW USB->EP1R = USB_EPnR(USB->EP1R) | ((USB->EP1R & USB_EP1R_STAT_TX) ^ USB_EP1R_STAT_TX_0); // STALL后续的所有IN token // 接下来端点0将收到clear ENDPOINT_HALT feature的请求 return; } else return; // 结束发送 USB->EP1R = USB_EPnR(USB->EP1R) | ((USB->EP1R & USB_EP1R_STAT_TX) ^ USB_EP1R_STAT_TX); // TX=VALID } int16_t USB_GetDescriptor(const USB_Request *req, void *buffer) { int16_t size = USB_UNSUPPORTED; uint8_t type = req->wValue >> 8; // 高8位为请求的描述符类型 USB_ConfigurationDescriptor *config = buffer; USB_DeviceDescriptor *device = buffer; USB_EndpointDescriptor *endpoints; USB_InterfaceDescriptor *interface; USB_StringDescriptor *str = buffer; switch (type) { case DEVICE_DESCRIPTOR: device->bLength = sizeof(USB_DeviceDescriptor); device->bDescriptorType = DEVICE_DESCRIPTOR; device->bcdUSB = 0x200; // USB 2.0 device->bDeviceClass = 0; device->bDeviceSubClass = 0; device->bDeviceProtocol = 0; device->bMaxPacketSize0 = 64; device->idVendor = 0x483; // STMicroelectronics (http://www.linux-usb.org/usb.ids) device->idProduct = 0x5720; // STM microSD Flash Device device->bcdDevice = 0x200; device->iManufacturer = 1; // 制造商名称字符串序号 device->iProduct = 2; // 产品名字符串序号 device->iSerialNumber = 3; // 产品序列号字符串序号 device->bNumConfigurations = 1; // 配置数 size = device->bLength; break; case CONFIG_DESCRIPTOR: config->bLength = sizeof(USB_ConfigurationDescriptor); config->bDescriptorType = CONFIG_DESCRIPTOR; config->wTotalLength = sizeof(USB_ConfigurationDescriptor) + sizeof(USB_InterfaceDescriptor) + 2 * sizeof(USB_EndpointDescriptor); config->bNumInterfaces = 1; // 接口数 config->bConfigurationValue = 1; // 此配置的编号 config->iConfiguration = 0; // 配置名字符串序号(0表示没有) config->bmAttributes = 0xc0; // self-powered config->bMaxPower = 50; // 最大电流: 100mA interface = (USB_InterfaceDescriptor *)(config + 1); interface->bLength = sizeof(USB_InterfaceDescriptor); interface->bDescriptorType = INTERFACE_DESCRIPTOR; interface->bInterfaceNumber = 0; // 此接口的编号 interface->bAlternateSetting = 0; // 可用的备用接口编号 interface->bNumEndpoints = 2; // 除了端点0外, 此接口还需要的端点数 (EP1_IN和EP1_OUT分别算一个端点); 实际上就是endpoints数组的元素个数 interface->bInterfaceClass = 0x08; // Mass Storage devices interface->bInterfaceSubClass = 0x06; // SCSI transparent command set interface->bInterfaceProtocol = 0x50; // USB Mass Storage Class Bulk-Only (BBB) Transport interface->iInterface = 4; // 接口名称字符串序号 // 注意: 这里不能出现端点0的描述符 endpoints = (USB_EndpointDescriptor *)(interface + 1); endpoints[0].bLength = sizeof(USB_EndpointDescriptor); endpoints[0].bDescriptorType = ENDPOINT_DESCRIPTOR; endpoints[0].bEndpointAddress = 0x81; // IN, address 1 endpoints[0].bmAttributes = 0x02; // Bulk endpoints[0].wMaxPacketSize = 64; endpoints[0].bInterval = 0; // Does not apply to Bulk endpoints endpoints[1].bLength = sizeof(USB_EndpointDescriptor); endpoints[1].bDescriptorType = ENDPOINT_DESCRIPTOR; endpoints[1].bEndpointAddress = 0x01; // OUT, address 1 endpoints[1].bmAttributes = 0x02; // Bulk endpoints[1].wMaxPacketSize = 64; endpoints[1].bInterval = 0; size = config->wTotalLength; break; case STRING_DESCRIPTOR: str->bDescriptorType = STRING_DESCRIPTOR; if (req->wIndex == 0x409) // 字符串英文内容 { // 字符串的编码为UTF-16 switch (req->wValue & 0xff) // 低8位为字符串序号 { case 1: wcscpy((wchar_t *)str->wData, L"Hello Manufacturer!"); break; case 2: wcscpy((wchar_t *)str->wData, L"Hello Product!"); break; case 3: wcscpy((wchar_t *)str->wData, L"Hello SerialNumber!"); break; case 4: wcscpy((wchar_t *)str->wData, L"Hello Interface!"); break; default: printf("STR_%d\n", req->wValue & 0xff); wcscpy((wchar_t *)str->wData, L"???"); } str->bLength = 2 + wcslen((wchar_t *)str->wData) * 2; } else if (req->wIndex == 0) // 字符串语言列表 { str->bLength = 4; str->wData[0] = 0x0409; // English (United States) } else break; size = str->bLength; break; default: // 包括Device qualifier (full-speed设备不支持) USB->EP0R = USB_EPnR(USB->EP0R) | ((USB->EP0R & USB_EP0R_STAT_TX) ^ USB_EP0R_STAT_TX_0); // STAT_TX设为STALL size = USB_ERROR; } // 发送的字节数不能超过主机要求的最大长度 if (size > req->wLength) size = req->wLength; // 只修改发送长度, 内容原封不动, 切记!!!! // 比如在请求字符串语言列表时, 待发送的数据量是str->bLength=4 // 如果主机要求最大只能发送req->wLength=2字节, 则数据内容str->bLength应该仍为4, 不能改成2 return size; } void USB_Init(void) { USB->CNTR |= USB_CNTR_ERRM; // 打开错误提示中断 NVIC_EnableIRQ(USB_LP_CAN1_RX0_IRQn); USB->CNTR &= ~USB_CNTR_PDWN; // 先打开USB外设 (需要一定的启动时间) USB->CNTR &= ~USB_CNTR_FRES; // 撤销USB外设的复位信号 // 初始化端点0和端点1的缓冲区 USB_BufDesc[0].ADDR_TX = 112; USB_BufDesc[0].COUNT_TX = 0; USB_BufDesc[0].ADDR_RX = 176; USB_BufDesc[0].COUNT_RX = USB_COUNT0_RX_BLSIZE | USB_COUNT0_RX_NUM_BLOCK_0; // 64 bytes (See Table 177. Definition of allocated buffer memory) USB_BufDesc[1].ADDR_TX = 240; USB_BufDesc[1].COUNT_TX = 0; USB_BufDesc[1].ADDR_RX = 304; USB_BufDesc[1].COUNT_RX = USB_COUNT1_RX_BLSIZE | USB_COUNT1_RX_NUM_BLOCK_0; USB->CNTR |= USB_CNTR_RESETM; // 打开复位中断, 开始处理复位请求 } void USB_ReadPMA(uint16_t usbaddr, void *buffer, uint16_t len) { const uint16_t *ppma; uint16_t *pbuf; // USBPMA地址范围: 0~511, 对应的APB绝对地址范围为0x40006000~0x400063fd // 0对应0x40006000, 1对应0x40006001; 但2对应0x40006004, 3对应0x40006005, 4对应0x40006008, 5对应0x40006009 if (usbaddr % 2 == 1) { *(uint8_t *)buffer = *(uint8_t *)(USB_PMAADDR + 2 * usbaddr - 1); pbuf = (uint16_t *)((uint8_t *)buffer + 1); usbaddr++; len--; } else pbuf = (uint16_t *)buffer; ppma = (const uint16_t *)(USB_PMAADDR + usbaddr * 2); // 将USB地址转换为APB绝对地址 while (len >= 2) { *pbuf = *ppma; pbuf++; // 缓冲区地址前进2个地址 ppma += 2; // APB绝对地址前进2个地址 len -= 2; } if (len == 1) *(uint8_t *)pbuf = *(uint8_t *)ppma; } void USB_WritePMA(uint16_t usbaddr, const void *buffer, uint16_t len) { const uint16_t *pbuf; uint16_t *ppma; if (usbaddr % 2 == 1) { *(uint8_t *)(USB_PMAADDR + 2 * usbaddr - 1) = *(uint8_t *)buffer; pbuf = (uint16_t *)((uint8_t *)buffer + 1); usbaddr++; len--; } else pbuf = (uint16_t *)buffer; ppma = (uint16_t *)(USB_PMAADDR + usbaddr * 2); while (len >= 2) { *ppma = *pbuf; pbuf++; ppma += 2; len -= 2; } if (len == 1) *(uint8_t *)ppma = *(uint8_t *)pbuf; } void USB_LP_CAN1_RX0_IRQHandler(void) { if (USB->ISTR & USB_ISTR_ERR) { USB->ISTR = USB_ISTR_MASK & ~USB_ISTR_ERR; printf("USB error!\n"); // CRC校验错误会产生这个中断, 但系统会自动重传数据, 软件无需理会 } if (USB->ISTR & USB_ISTR_RESET) { // USB复位会使DADDR和EPnR寄存器清零 USB->ISTR = USB_ISTR_MASK & ~USB_ISTR_RESET; USB->DADDR = USB_DADDR_EF; // 启动USB外设的功能 USB->EP0R = USB_EP0R_STAT_RX | USB_EP0R_EP_TYPE_0 | USB_EP0R_STAT_TX_1; // STAT_RX=VALID, EP_TYPE=CONTROL, STAT_TX=NAK USB->EP1R = USB_EP1R_STAT_RX | USB_EP1R_STAT_TX_1 | 1; printf("USB reset!\n"); } if (USB->ISTR & USB_ISTR_CTR) // 虽然CTR中断在寄存器中并没有使能, 但是仍能触发, 所以这个中断是关不掉的 USB_CorrectTransfer(); }【程序运行结果】
1.插入了USB设备后,在“我的电脑”中显示了新的可移动磁盘
2.可以在系统托盘中弹出USB设备
3.可以在磁盘中存放文件,查看容量
端点0上USB设备的枚举过程详解:
STM32F103C8 USB USB reset! // STM32 USB外设本身的复位 (先清除PDWN位, 再清除FRES位), 此时设备为Powered状态 USB reset! // 主机让USB设备复位, 设备由Powered状态转变为Default状态 0+8 // 端点0收到8字节数据 (Setup stage: hostOUT+hostData+deviceACK) 8006000100004000 // 主机请求设备描述符, 请求的最大数据长度为0x40=64字节 0-18 // 端点0发出18字节的设备描述符数据 (Data stage: hIN+dData+hACK) 0+0 // 主机确认收到数据 (Status stage: hOUT+hDATA+dACK) USB reset! // 主机再次让USB设备复位 0+8 0005130000000000 // 主机给USB设备分配设备地址0x13, 不请求数据 (Setup stage: hOUT+hData+dACK) DADDR_13 0-0 // 设备确认收到数据, 并修改设备地址为0x13 (Status stage: hIN+dData+hACK), 设备由Default状态转变为Address状态 0+8 8006000100001200 // 主机再次请求设备描述符, 最大数据长度为0x12=18字节 0-18 // 设备通过端点0发送18字节的设备描述符 0+0 // 主机确认收到数据 0+8 800600020000FF00 // 主机请求配置描述符 0-32 // 设备发送32字节的配置描述符,顺带将接口描述符和端点描述符也发送给主机(USB规范要求) 0+0 // 主机确认 0+8 800600030000FF00 // 主机请求字符串的语言列表 0-4 // 设备告诉主机, 设备只支持0x0409 English (United States)这一种语言 0+0 0+8 800603030904FF00 // 主机请求3号字符串用0x0409这个语言(英语)写的内容 0-40 // 设备发送字符串内容 0+0 0+8 8006000600000A00 // 主机请求Device qualifier描述符, 但由于USB规范规定USB全速设备不支持这个描述符, 所以直接STALL, 向主机报告错误 0+8 8006000100001200 // 主机再次请求18字节的设备描述符 0-18 0+0 0+8 8006000200000900 // 主机请求配置描述符, 但这次只允许设备发送9字节的内容 0-9 // 配置描述符共有32字节, 设备只发送前9字节给主机, 发送的内容不作任何修改(wTotalLength=32, 绝对不允许改成9) 0+0 // 主机确认收到数据 0+8 8006000200002000 // 主机再次请求配置描述符, 最大长度改成了0x20=32字节 0-32 // 设备发送了完整的配置描述符 0+0 0+8 8006000300000200 // 主机请求字符串语言列表, 但只允许设备发送2字节的内容 (实际上就是要获取语言列表的长度) 0-2 // 语言列表共有4字节, 设备只发送前两字节, 内容中的bLength=4保持不变 0+0 0+8 8006000300000400 // 主机请求字符串语言列表, 最大长度改成了4字节 0-4 // 设备发送了完整的语言列表 0+0 0+8 8006030309040200 // 主机请求3号字符串的英语内容的长度 0-2 0+0 0+8 8006030309042800 // 主机请求3号字符串的英语内容 0-40 0+0 0+8 0009010000000000 // 应用1号配置, 设备现在由Address状态转变为最终的Configured状态 CFG1 0-0 0+8 A1FE000000000100 // 后面的代码还没写, 因为调用了两次dump_data, 所以数据内容输出了两次 A1FE000000000100 // A1表示: 方向为从设备到主机, 获取大容量存储Class的接口(Interface)信息 0+8 A1FE000000000100 A1FE000000000100 0+8 A1FE000000000100 A1FE000000000100 1+31 // 端点1收到了31字节的数据 5553424310109C112400000080000612000000240000000000000000000000串口输出结果:
STM32F103C8 USB Flash Size: 64KB, Disk Size: 48.0KB, Disk Addr: 0x08004000 Flash is unlocked! USB reset! USB reset! 0S+8 0-18 0+0 USB reset! 0S+8 0-0 DADDR_09 0S+8 0-18 0+0 0S+8 0-32 0+0 0S+8 0-4 0+0 0S+8 0-30 0+0 0S+8 0-40 0+0 0S+8 0S+8 0-18 0+0 0S+8 0-9 0+0 0S+8 0-32 0+0 0S+8 0-2 0+0 0S+8 0-4 0+0 0S+8 0-2 0+0 0S+8 0-40 0+0 0S+8 CFG1 // 应用1号配置, 设备现在由Address状态转变为最终的Configured状态 0-0 0S+8 0-1 0+0 0S+8 0-9 0+0 0S+8 0-32 0+0 W4,4 // 从第4块开始连续写4块 W4,4 W4,4 W4,4 W2,1 W3,1 W4,4 W45,1 // 写第45块 W4,4 W4,4 W2,1 W3,1 0S+8 CFG0 // 在系统托盘上弹出磁盘时, 设备由Configured状态转变为Address状态 0-0