USB(Universal Serial Bus通用串行总线)是一个外部总线标准,用于规范电脑与外部设备的连接和通讯,是应用在PC领域的接口技术,支持设备的即插即用和热插拔功能。STM32芯片自带有USB功能,带来了更多的可玩性,但也更加复杂,之前在STM32F103上实现了USB库的移植,由于F103的USB功能简单,移植比较顺利,具体的方法见本人的博客:STM32F1移植USB库实现外部FLASH模拟U盘功能。
相较于 STMF1,STM32F4 的功能大为增强,引入了 OTG 功能,最近入手了一块 NUCLEO-64 开发板,板载芯片是 STM32F446RET6,尝试在上面移植 USB 的 MSC 驱动。
在参考了野火和原子的例程后,没能成功移植官网的USB库,其中一个原因在于例程所使用的芯片是 STM32F429,这一芯片的时钟树结构与 STM32F446 存在差异。从 CubeMX 上可以看出,STM32F429 的 USB 时钟只能来自PLL,如图1,无法在运行于推荐的180MHz主频的情况下获得48MHz的USB 时钟,只能采取降频至144MHz,或超频至192MHz(野火和原子的例程采用的方法)。
图1. STM32F429IGTx时钟树
相比于此,STM32F446RE 的 USB 时钟来源可以选择 PLLSAIP,由 PLLSAI 倍频产生,这就同时满足了180MHz的运行频率和 USB 的48MHz时钟的要求。
图2. STM32F446RETx时钟树
由于与 PLLSAI 相关的资料和程序很少,而且 USB 库的移植相对来说确实麻烦,所以采用 CubeMX 来配置HAL库,实现外部FLASH 模拟U盘的功能。
移植过程
1、在 CubeMX 中选择相对应的芯片,配置 RCC,如下图:
2、在 Connectivity 中选择 USB_OTG_FS(全速模式),配置为 Device_Only 模式,如下图:
3、在 Middleware 中选择 USB_DEVICE,在 Class For FS IP 中选择 Mass Storage Class,由于采用的存储介质是 FLASH,所以MSC_MEDIA_PACKET 配置为 4096 bytes,如果是SD卡采用默认的 512 bytes 配置,如下图:
4、配置 USB 的 中断优先级为5,如下图:
5、配置时钟树,如下图:
6、配置堆空间 Minimum Heap Size 为 0x1200,作为 USB 的缓冲,如下图:
7、由于之前已经写好了外部 FLASH 移植 FATFS 的程序,不想重复折腾,这里将与 USB 驱动相关的文件拷贝出来,直接添加到之前的工程中。CubeMX 生成的与USB相关的文件位于:
“…\Middlewares\ST\STM32_USB_Device_Library”
…\Src中的文件
…\Inc中的文件
将上述文件拷贝至之前的移植FATFS文件系统的工程文件下,如下图
并将stm32f4xx_hal_msp.c文件也拷贝到User文件夹,打开工程添加USB库文件代码如下:
8、编译后存在两个错误:
这两个错误都是由于将函数定义为了静态函数,在 main.c 文件中将对应函数的 static 关键字去掉,并在 main.h 中声明 Error_Handler() 函数。
在 main.c 中的时钟初始化函数添加PLLSAI的设置,可以复制CubeMX生成工程中的 main.c 文件中的时钟初始化函数
void SystemClock_Config(void)
{
RCC_ClkInitTypeDef RCC_ClkInitStruct;
RCC_OscInitTypeDef RCC_OscInitStruct;
RCC_PeriphCLKInitTypeDef PeriphClkInitStruct;
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLM = 8;
RCC_OscInitStruct.PLL.PLLN = 360;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
RCC_OscInitStruct.PLL.PLLQ = 7;
RCC_OscInitStruct.PLL.PLLR = 2;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
if (HAL_PWREx_EnableOverDrive() != HAL_OK)
{
Error_Handler();
}
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
{
Error_Handler();
}
PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_CLK48;
PeriphClkInitStruct.PLLSAI.PLLSAIM = 4;
PeriphClkInitStruct.PLLSAI.PLLSAIN = 96;
PeriphClkInitStruct.PLLSAI.PLLSAIQ = 2;
PeriphClkInitStruct.PLLSAI.PLLSAIP = RCC_PLLSAIP_DIV4;
PeriphClkInitStruct.PLLSAIDivQ = 1;
PeriphClkInitStruct.Clk48ClockSelection = RCC_CLK48CLKSOURCE_PLLSAIP;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK)
{
Error_Handler();
}
}
9、在 usbd_storage_if.c 中添加底层读写 FLASH 的代码:
#include "usbd_storage_if.h"
#include "./flash/bsp_spi_flash.h"
#define STORAGE_LUN_NBR 1
#define STORAGE_BLK_NBR 4096//0x10000
#define STORAGE_BLK_SIZ 4096//0x200
const int8_t STORAGE_Inquirydata_FS[] = {
0x00,
0x80,
0x02,
0x02,
(STANDARD_INQUIRY_DATA_LEN - 5),
0x00,
0x00,
0x00,
'S', 'T', 'M', ' ', ' ', ' ', ' ', ' ',
'P', 'r', 'o', 'd', 'u', 'c', 't', ' ',
' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
'0', '.', '0' ,'1'
};
extern USBD_HandleTypeDef hUsbDeviceFS;
static int8_t STORAGE_Init_FS(uint8_t lun);
static int8_t STORAGE_GetCapacity_FS(uint8_t lun, uint32_t *block_num, uint16_t *block_size);
static int8_t STORAGE_IsReady_FS(uint8_t lun);
static int8_t STORAGE_IsWriteProtected_FS(uint8_t lun);
static int8_t STORAGE_Read_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len);
static int8_t STORAGE_Write_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len);
static int8_t STORAGE_GetMaxLun_FS(void);
USBD_StorageTypeDef USBD_Storage_Interface_fops_FS =
{
STORAGE_Init_FS,
STORAGE_GetCapacity_FS,
STORAGE_IsReady_FS,
STORAGE_IsWriteProtected_FS,
STORAGE_Read_FS,
STORAGE_Write_FS,
STORAGE_GetMaxLun_FS,
(int8_t *)STORAGE_Inquirydata_FS
};
int8_t STORAGE_Init_FS(uint8_t lun)
{
SPI_FLASH_Init();
if (SPI_FLASH_ReadID() != sFLASH_ID)
{
//printf("error\r\n");
return (USBD_FAIL);
}
return (USBD_OK);
}
int8_t STORAGE_GetCapacity_FS(uint8_t lun, uint32_t *block_num, uint16_t *block_size)
{
*block_num = STORAGE_BLK_NBR;
*block_size = STORAGE_BLK_SIZ;
return (USBD_OK);
}
int8_t STORAGE_IsReady_FS(uint8_t lun)
{
if (SPI_FLASH_ReadID() == sFLASH_ID)
{
//printf("error\r\n");
return (USBD_OK);
}
else
{
return (USBD_FAIL);
}
//return (USBD_OK);
}
int8_t STORAGE_IsWriteProtected_FS(uint8_t lun)
{
return (USBD_OK);
}
int8_t STORAGE_Read_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
{
SPI_FLASH_BufferRead(buf, blk_addr*STORAGE_BLK_SIZ, blk_len*STORAGE_BLK_SIZ);
return (USBD_OK);
}
int8_t STORAGE_Write_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
{
SPI_FLASH_SectorErase(blk_addr*STORAGE_BLK_SIZ);
SPI_FLASH_BufferWrite(buf, blk_addr*STORAGE_BLK_SIZ, blk_len*STORAGE_BLK_SIZ);
return (USBD_OK);
}
int8_t STORAGE_GetMaxLun_FS(void)
{
return (STORAGE_LUN_NBR - 1);
}
10、修改启动文件中的堆空间大小
11、在 stm32f4xx_it.c 添加 USB 中断服务函数
#include "main.h"
#include "stm32f4xx_it.h"
extern PCD_HandleTypeDef hpcd_USB_OTG_FS;
void NMI_Handler(void)
{
}
void HardFault_Handler(void)
{
while (1)
{
}
}
void MemManage_Handler(void)
{
while (1)
{
}
}
void BusFault_Handler(void)
{
while (1)
{
}
}
void UsageFault_Handler(void)
{
while (1)
{
}
}
void SVC_Handler(void)
{
}
void DebugMon_Handler(void)
{
}
void PendSV_Handler(void)
{
}
void SysTick_Handler(void)
{
HAL_IncTick();
}
void OTG_FS_IRQHandler(void)
{
//printf("111\r\n");
HAL_PCD_IRQHandler(&hpcd_USB_OTG_FS);
}
12、在 main.c 中添加 USB 库相关头文件,并初始化 USB 外设
#include "main.h"
#include "./usart/bsp_usart.h"
#include "./flash/bsp_spi_flash.h"
#include "ff.h"
#include "usb_device.h"
#include "usbd_core.h"
//函数声明
void SystemClock_Config(void);
void Error_Handler(void);
static void MX_GPIO_Init(void);
//全局变量
FATFS fs;
FIL fnew;
FRESULT res_flash;
UINT fnum;
BYTE ReadBuffer[1024]={0};
BYTE WriteBuffer[] = "FatFs文件系统测试数据。\r\n";
BYTE work[FF_MAX_SS];
int main(void)
{
HAL_Init();
SystemClock_Config();
//初始化USB
MX_GPIO_Init();
MX_USB_DEVICE_Init();
//初始化串口
USART_Config();
printf("****** FatFs文件系统实验 ******\r\n");
//在外部SPI Flash挂载文件系统,文件系统挂载时会对SPI设备初始化
//初始化函数调用流程如下
//f_mount()->find_volume()->disk_initialize->SPI_FLASH_Init()
res_flash = f_mount(&fs,"0:",1);
if(res_flash == FR_NO_FILESYSTEM)
{
printf("》FLASH还没有文件系统,即将进行格式化...\r\n");
res_flash = f_mkfs("0:",0,work,sizeof work);
if(res_flash == FR_OK)
{
printf("》FLASH已成功格式化文件系统。\r\n");
res_flash = f_mount(NULL,"0:",1);
res_flash = f_mount(&fs,"0:",1);
}
else
{
printf("《《格式化失败。(%d)》》\r\n",res_flash);
while(1);
}
}
else if(res_flash!=FR_OK)
{
printf("!!外部Flash挂载文件系统失败。(%d)\r\n",res_flash);
printf("!!可能原因:SPI Flash初始化不成功。\r\n");
while(1);
}
else
{
printf("》文件系统挂载成功,可以进行读写测试\r\n");
}
printf("\r\n****** 即将进行文件写入测试... ******\r\n");
res_flash = f_open(&fnew, "0:FatFs读写测试文件.txt",FA_CREATE_ALWAYS | FA_WRITE );
if ( res_flash == FR_OK )
{
printf("》打开/创建FatFs读写测试文件.txt文件成功,向文件写入数据。\r\n");
res_flash=f_write(&fnew,WriteBuffer,sizeof(WriteBuffer),&fnum);
if(res_flash==FR_OK)
{
printf("》文件写入成功,写入字节数据:%d\n",fnum);
printf("》向文件写入的数据为:\r\n%s\r\n",WriteBuffer);
}
else
{
printf("!!文件写入失败:(%d)\n",res_flash);
}
f_close(&fnew);
}
else
{
printf("!!打开/创建文件失败。(%d)\r\n",res_flash);
}
printf("****** 即将进行文件读取测试... ******\r\n");
res_flash = f_open(&fnew, "0:FatFs读写测试文件.txt",FA_OPEN_EXISTING | FA_READ);
if(res_flash == FR_OK)
{
printf("》打开文件成功。\r\n");
res_flash = f_read(&fnew, ReadBuffer, sizeof(ReadBuffer), &fnum);
if(res_flash==FR_OK)
{
printf("》文件读取成功,读到字节数据:%d\r\n",fnum);
printf("》读取得的文件数据为:\r\n%s \r\n", ReadBuffer);
}
else
{
printf("!!文件读取失败:(%d)\n",res_flash);
}
}
else
{
printf("!!打开文件失败。\r\n");
}
f_close(&fnew);
//f_mount(NULL,"0:",1);
while (1)
{
}
}
void SystemClock_Config(void)
{
RCC_ClkInitTypeDef RCC_ClkInitStruct;
RCC_OscInitTypeDef RCC_OscInitStruct;
RCC_PeriphCLKInitTypeDef PeriphClkInitStruct;
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLM = 8;
RCC_OscInitStruct.PLL.PLLN = 360;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
RCC_OscInitStruct.PLL.PLLQ = 7;
RCC_OscInitStruct.PLL.PLLR = 2;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
if (HAL_PWREx_EnableOverDrive() != HAL_OK)
{
Error_Handler();
}
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
{
Error_Handler();
}
PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_CLK48;
PeriphClkInitStruct.PLLSAI.PLLSAIM = 4;
PeriphClkInitStruct.PLLSAI.PLLSAIN = 96;
PeriphClkInitStruct.PLLSAI.PLLSAIQ = 2;
PeriphClkInitStruct.PLLSAI.PLLSAIP = RCC_PLLSAIP_DIV4;
PeriphClkInitStruct.PLLSAIDivQ = 1;
PeriphClkInitStruct.Clk48ClockSelection = RCC_CLK48CLKSOURCE_PLLSAIP;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK)
{
Error_Handler();
}
}
static void MX_GPIO_Init(void)
{
__HAL_RCC_GPIOH_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
}
void Error_Handler(void)
{
while(1)
{
}
}
#ifdef USE_FULL_ASSERT
void assert_failed(uint8_t* file, uint32_t line)
{
while (1)
{
}
}
#endif
编译下载后可以在 PC 上看到模拟出的U盘:
END