程序下载地址: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表示写第三个数据页的后半页。