【方法】20腳的STM32F042F6單片機只有32KB的Flash和6KB的SRAM,移植HAL庫裏面的USB大容量存儲設備(MSC)時,如何讀寫頁大小爲4KB的W25Q128存儲器?

程序下載地址: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表示寫第三個數據頁的後半頁。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章