【方法】20脚的STM32F042F6单片机只有32KB的Flash和6KB的SRAM,移植HAL库里面的USB大容量存储设备(MSC)时,如何读写页大小为4KB的W25Q128存储器?

程序下载地址:https://pan.baidu.com/s/1n4J0pBUjliev4Pio6gOUNQ(提取码:4gt6)

【方法】20脚的STM32F042F6单片机只有32KB的Flash和6KB的SRAM,移植HAL库里面的USB大容量存储设备(MSC)时,如何读写页大小为4KB的W25Q128存储器?_第1张图片

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可以执行成功。

运行程序,能够显示磁盘盘符并成功读写文件:

【方法】20脚的STM32F042F6单片机只有32KB的Flash和6KB的SRAM,移植HAL库里面的USB大容量存储设备(MSC)时,如何读写页大小为4KB的W25Q128存储器?_第2张图片

【方法】20脚的STM32F042F6单片机只有32KB的Flash和6KB的SRAM,移植HAL库里面的USB大容量存储设备(MSC)时,如何读写页大小为4KB的W25Q128存储器?_第3张图片

W3A表示写第三个数据页的前半页,W3B表示写第三个数据页的后半页。

你可能感兴趣的:(USB,STM32,CubeMX)