本程序是从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中