程序下載地址:https://pan.baidu.com/s/1n4J0pBUjliev4Pio6gOUNQ(提取碼:4gt6)
STM32F042F6單片機的USB自帶了內部的1.5kΩ上拉電阻,所以電路上只需要接兩個22Ω的電阻就可以。程序運行時使能內部的上拉電阻,主機就能檢測到USB設備。
/* Private variables ---------------------------------------------------------*/
// 本配置文件的主要任務是建立hpcd, 將hpcd->pData和main.c中的husbd關聯
// 並將usbd_core.c裏面用到的USBD_LL_xxx函數與hpcd關聯起來
// 還要將hpcd裏面用到USB中斷處理回調函數HAL_PCD_xxxCallback與usbd_core.c的USBD_LL_xxx函數關聯起來
// 總的來說就是husbd和hpcd之間的相互關聯
PCD_HandleTypeDef hpcd;
/* Private function prototypes -----------------------------------------------*/
/* Private functions ---------------------------------------------------------*/
/**
* @brief Initializes the Low Level portion of the Device driver.
* @param pdev: Device handle
* @retval USBD Status
*/
USBD_StatusTypeDef USBD_LL_Init(USBD_HandleTypeDef *pdev)
{
GPIO_InitTypeDef gpio;
// 配置USB引腳
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_SYSCFG_CLK_ENABLE();
__HAL_REMAP_PIN_ENABLE(HAL_REMAP_PA11_PA12);
__HAL_RCC_USB_CLK_ENABLE();
gpio.Alternate = GPIO_AF2_USB;
gpio.Mode = GPIO_MODE_AF_PP;
gpio.Pin = GPIO_PIN_11 | GPIO_PIN_12;
gpio.Pull = GPIO_NOPULL;
gpio.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &gpio);
// 打開USB中斷
HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
HAL_NVIC_SetPriority(USB_IRQn, 1, 0);
HAL_NVIC_EnableIRQ(USB_IRQn);
// 將husbd和hpcd關聯起來
// 這裏的pdev就是main.c裏面定義的husbd
pdev->pData = &hpcd;
hpcd.pData = pdev;
// 初始化hpcd
hpcd.Instance = USB;
hpcd.Init.dev_endpoints = 2; // 使用的端點個數
hpcd.Init.speed = PCD_SPEED_FULL;
HAL_PCD_Init(&hpcd);
// 配置各端點使用的PMA緩衝區地址
HAL_PCDEx_PMAConfig(&hpcd, 0x00, PCD_SNG_BUF, 0x20); // EP0_OUT
HAL_PCDEx_PMAConfig(&hpcd, 0x80, PCD_SNG_BUF, 0x60); // EP0_IN
HAL_PCDEx_PMAConfig(&hpcd, 0x01, PCD_SNG_BUF, 0xa0); // EP1_OUT
HAL_PCDEx_PMAConfig(&hpcd, 0x81, PCD_SNG_BUF, 0xe0); // EP1_IN
return USBD_OK;
}
/* USB中斷處理函數 */
void USB_IRQHandler(void)
{
HAL_PCD_IRQHandler(&hpcd);
}
Flash大小的問題倒好解決,Keil工程屬性的C/C++選項卡下面開-O3優化,程序大小就能控制在20KB左右。
關鍵是SRAM只有6KB,USBD_HandleTypeDef和PCD_HandleTypeDef這兩個結構體加起來就有1336字節,啓動文件中棧大小Stack_Size爲0x400=1024字節,剩餘內存6144-1336-1024=3784<4096字節,無法保存一頁完整的4KB數據頁的數據,怎麼辦?
實際上,USB device裏面的數據一次性最多隻能收發64字節,也就是說數據實際上是64字節64字節地收發的,根本不需要一下子集齊一整頁的數據後纔讀寫W25Q128。我們可以改成一次只讀寫半頁(2048字節)的數據,這下內存就夠用了。
首先,在usbd_conf.h裏面,我們把MSC_MEDIA_PACKET改成2048,這樣一次最多就只讀寫半頁數據。
/* MSC Class Config */
#define MSC_MEDIA_PACKET 2048U
把usbd_msc_storage.c裏面的STORAGE_Read函數和STORAGE_Write函數的定義改了,最後一個參數改成uint8_t half,指明是讀寫前半頁還是後半頁。
int8_t STORAGE_Read(uint8_t lun, uint8_t *buf, uint32_t blk_addr,
uint8_t half);
int8_t STORAGE_Write(uint8_t lun, uint8_t *buf, uint32_t blk_addr,
uint8_t half);
於是USBD_StorageTypeDef結構體的定義裏面也要修改成half。
typedef struct _USBD_STORAGE
{
int8_t (* Init)(uint8_t lun);
int8_t (* GetCapacity)(uint8_t lun, uint32_t *block_num, uint16_t *block_size);
int8_t (* IsReady)(uint8_t lun);
int8_t (* IsWriteProtected)(uint8_t lun);
int8_t (* Read)(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint8_t half);
int8_t (* Write)(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint8_t half);
int8_t (* GetMaxLun)(void);
int8_t *pInquiry;
} USBD_StorageTypeDef;
數據頁的大小仍然設置爲4096,只不過每次只讀寫半頁了,不再是整頁整頁地讀寫。對於W25Q128來說,4096*4096剛好就是16MB。
#define STORAGE_BLK_NBR 4096
#define STORAGE_BLK_SIZ 4096
磁盤讀寫函數:
/*******************************************************************************
* Function Name : Read_Memory
* Description : Handle the Read operation from the STORAGE card.
* Input : None.
* Output : None.
* Return : None.
*******************************************************************************/
int8_t STORAGE_Read(uint8_t lun, uint8_t *buf,
uint32_t blk_addr, uint8_t half)
{
//printf("R%u%c\n", blk_addr, 'A' + half);
W25Qxx_Read(blk_addr * STORAGE_BLK_SIZ + half * (STORAGE_BLK_SIZ / 2), buf, STORAGE_BLK_SIZ / 2);
return 0;
}
/*******************************************************************************
* Function Name : Write_Memory
* Description : Handle the Write operation to the STORAGE card.
* Input : None.
* Output : None.
* Return : None.
*******************************************************************************/
int8_t STORAGE_Write(uint8_t lun, uint8_t *buf,
uint32_t blk_addr, uint8_t half)
{
int i;
uint32_t addr;
printf("W%u%c\n", blk_addr, 'A' + half);
addr = blk_addr * STORAGE_BLK_SIZ + half * (STORAGE_BLK_SIZ / 2);
if (half == 0)
W25Qxx_EraseSector(blk_addr);
for (i = 0; i < 8; i++)
{
W25Qxx_ProgramPage(addr, buf, 256);
addr += 256;
buf += 256;
}
return (0);
}
USBD_MSC_BOT_HandleTypeDef結構體裏面新增一個uint8_t scsi_blk_half成員。
SCSI_Read10和SCSI_Write10裏面,初始化讀寫操作時,將scsi_blk_half清零:
if (hmsc->bot_state == USBD_BOT_IDLE) /* Idle */
{
// ...
hmsc->scsi_blk_half = 0; // 添加這句話
hmsc->scsi_blk_addr = ((uint32_t)params[2] << 24) |
((uint32_t)params[3] << 16) |
((uint32_t)params[4] << 8) |
(uint32_t)params[5];
hmsc->scsi_blk_len = ((uint32_t)params[7] << 8) |
(uint32_t)params[8];
// ...
}
SCSI_ProcessRead裏面,調用Read函數時,最後一個參數爲hmsc->scsi_blk_half,然後scsi_blk_half取反,當scsi_blk_half取反後等於0時,才增大scsi_blk_addr,減小scsi_blk_len。
if (((USBD_StorageTypeDef *)pdev->pUserData)->Read(lun,
hmsc->bot_data,
hmsc->scsi_blk_addr,
hmsc->scsi_blk_half) < 0)
{
SCSI_SenseCode(pdev, lun, HARDWARE_ERROR, UNRECOVERED_READ_ERROR);
return -1;
}
USBD_LL_Transmit(pdev, MSC_EPIN_ADDR, hmsc->bot_data, len);
hmsc->scsi_blk_half = !hmsc->scsi_blk_half;
if (hmsc->scsi_blk_half == 0)
{
hmsc->scsi_blk_addr++;
hmsc->scsi_blk_len--;
}
SCSI_ProcessWrite函數也作類似的修改。
最後,如果usbd_conf.h裏面USB的內存分配函數使用的是malloc和free,則需要修改啓動文件裏面的堆大小,使內存能夠分配成功。
/* Memory management macros */
#define USBD_malloc malloc
#define USBD_free free
可以設置Heap_Size EQU 0x00000890。
sizeof(USBD_MSC_BOT_HandleTypeDef)=2160=0x870,經測試malloc可以執行成功。
運行程序,能夠顯示磁盤盤符併成功讀寫文件:
W3A表示寫第三個數據頁的前半頁,W3B表示寫第三個數據頁的後半頁。