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