细说STM32单片机配置为USBH_FS并用FatFS管理U盘文件系统的方法与实例
目录
(1)RCC、SYS、USART6、CodeGenerator、RTC、GPIO
STM32F4处理器上有两个USB-OTG接口,作为USB Host时,可以连接U盘,并通过FatFS管理U盘上的文件系统。本文介绍如何使用FatFS管理U盘上的文件系统。
继续使用旺宝红龙开发板STM32F407VGT6 KIT V1.0。
一、USB概述
1、USB协议
USB 2.0传输速率分为以下3种。
- USB 2.0 LS,低速USB 2.0,数据传输速率为1.5Mbit/s。
- USB 2.0 FS,全速USB 2.0,数据传输速率为12Mbit/s。
- USB 2.0 HS,高速USB 2.0,数据传输速率为480Mbit/s。
带USB接口的STM32 MCU一般只支持到USB 2.0的协议版本,因为USB 2.0的各种数据传输速率能满足一般嵌入式设备的应用需求,例如,USB接口的鼠标、键盘等并不需要多高的数据传输速率。
2、USB设备类型
USB通信是一种主从结构,USB系统包括USB主机(USB host)、USB外设(USB device)以及USB连接3个部分。其中,USB外设又分为USB功能(USB Function)外设和USB集线器(USB Hub)。
- USB主机:一个USB系统中只有一个USB主机,主机系统的USB接口被称为主机控制器。
- USB外设:实现具体功能,并受主机控制的外部USB设备。
- USB集线器:是扩展USB主机端口个数的设备。主机可以连接USB集线器,扩展USB端口个数,最多可连接127个USB外设。
例如,计算机主板上一般有一个USB主机和一个USB根集线器,主板引出多个USB接口,可以连接多个USB外设,通过USB接口连接到计算机上的U盘、键盘和鼠标都是USB外设。
在USB 2.0的基础上还扩展定义了一种USB-OTG(on-the-go)协议和接口标准。USB-OTG设备通过USB接口上的一个ID信号线的电平决定作为USB主机或USB外设,所以USB-OTG设备不能同时作为USB主机或外设,只是易于进行角色的切换。
STM32Cube为USB接口提供了3种驱动库,分别是USB-Host、USB-Device和USB-OTG驱动库,USB接口作为某个角色就应该使用相应的驱动库。USB通信协议和驱动程序是非常复杂的,别说自己写驱动程序,仅是将驱动程序结构和原理搞明白就很难,本文不会涉及这部分内容。
USB设备(主机和外设)在驱动程序上分为以下几大类。
- 音频设备类(Audio Device Class),如USB接口的蓝牙耳机适配器。
- 通信设备类(Communication Device Class,CDC),如虚拟串口。
- 下载固件升级类(Download Firmware Update Class,DFU)。
- 人机接口设备类(Human Interface Device Class,HID),如键盘和鼠标。
- 大容量存储类(Mass Storage Class,MSC),如U盘和移动硬盘。
3、USB接口类型
USB接口是指USB主机或USB外设的外部硬件接口,随着USB版本的演化,出现了多种USB接口类型。标准USB接口分为Type-A、Type-B和Type-C这3种,具体如下。
- Type-A接口是最早出现的USB接口,例如,计算机上的USB接口一般是Type-A接口,U盘的接口也是Type-A接口,只是计算机上的是Type-A母口,U盘上的是Type-A公口。
- Type-B接口一般用在打印机、扫描仪等较大的设备上。现在已经不用了。
- Type-C接口是在USB 3.1之后才出现的,有很多新增的特性,供电能达到20V/5A。现在是主流应用。
- MiniUSB和MicroUSB接口是为支持硬件小型化出现的USB接口,现在已经不用了。
二、STM32F407的USB-OTG接口
1、USB-OTG概述
STM32F407上有两个USB-OTG接口,都支持USB 2.0规范。一个是USB-OTG HS,其最大数据传输速率为480Mbit/s;另一个是USB-OTG FS,其最大数据传输速率为12Mbit/s。USB-OTG接口可以配置为USB主机、USB外设或双角色设备。
两个USB-OTG都有内置的PHY(端口物理层),无须外接PHY硬件。USB数据线上传输的是差分电压信号,而不是普通的高低电平信号。PHY的主要功能就是进行USB数据线上的差分信号与普通数字电平信号之间的转换。
旺宝红龙STM32F407开发板上不仅直接引出了USB-OTG FS,还通过USB3300物理层(Hi-Speed USB Host,Device or OTG PHY with ULPI Low Pin Interface)将USB-OTG HS引出为一个Type-A母口和另一个USB-OTG。
本文以USB-OTG FS为例,介绍USB-OTG的一些基本原理。
开发板上关于USB部分的原理图。



2、USB-OTG FS
(1)功能概述
USB-OTG FS主要由OTG FS内核和OTG FS PHY组成。
- OTG FS内核通过AHB外设总线与CPU通信,并产生相应的USB中断信号,以1.25 KB专用RAM作为USB数据FIFO。OTG FS内核需要48MHz时钟信号用于USB通信的PHY。
- OTG FS PHY用于实现物理层信号转换和接口控制功能。物理层信号转换就是实现USB数据总线上的差分电压信号与数字电平之间的转换,接口控制包括ID信号检测、电源VBUS的控制和检测。
USB-OTG FS的硬件接口有4个信号引脚。
- DP和DM是USB的数据信号引脚,传输的是差分信号,而不是普通的电平信号。PHY的功能之一就是进行USB差分信号与数字电平信号之间的转换。
- ID信号用于识别连接在USB-OTG FS接口上的外部USB设备的类型,是一个输入信号引脚。PHY内部为ID信号集成了上拉电阻,只有当USB-OTG FS作为双角色设备时,ID信号才有用。
- VBUS是外部5V电源监测引脚,当USB-OTG FS作为外设或双角色设备时,此引脚才有用。例如,USB-OTG FS仅作为外设时,通过USB连接器上的VBUS获取5V电源,并通过USB-OTG FS的VBUS引脚监测这个5V电源的电压。USB-OTG可以仅作为USB主机,也可以仅作为USB外设,还可以作为OTG双角色设备。USB-OTG FS的设备类型不同,USB-OTG FS接口的连接电路也不同。
(2)仅作为USB主机
可以通过配置将USB-OTG FS仅作为USB主机,
开发板上直接连接MCU的MiniA/B母口连接U盘时(见原理图),可以配置为只用作主机、只用作设备或者用途为双角色。
当MCU作为USB主机时,USB-OTG FS的DM(PA11引脚)和DP(PA12引脚)分别连接USB接口的DM和DP引脚,不需要使用USB-OTGFS的ID和VBUS信号(也就不需要配置这两个管脚)。
USB主机需要通过MiniA/B母口上的VBUS引脚向外设提供5V电源,这个5V电源来自于单独的电源器件,还可以使用电源开关芯片进行通断控制。
USB 2.0接口的VBUS电压是5V,电流不能超过500mA。电源开关芯片AIC1526具有过电流检测功能,可以将过流信号引脚接入MCU的某个EXTI引脚,实现USB设备的过电流保护。
STM32 MCU固件库中的中间件USB_HOST,是USB主机类设备的驱动程序,其模式和参数可以在CubeMX里配置
(3)仅作为USB外设
通过软件配置也可将USB-OTG FS仅作为USB外设。
当MCU作为USB外设时,开发板USB-OTG FS的DM(PA11引脚)和DP(PA12引脚)分别连接MiniA/B母口上的DM和DP引脚,不需要用到ID信号。USB外设从高侧开关芯片AIC1526获取工作电源VBUS,AIC1526的输入电压是SW= 5V。USB接口的VBUS最多只能提供500mA的电流,所以功耗大的USB外设需要自己提供工作电源。
STM32 MCU固件库中的中间件USB_DEVICE,是USB外设类设备的驱动程序,其模式和参数可以在CubeMX里配置。
(4)OTG双角色设备
USB-OTG FS还可以配置为双角色设备(Dual Role Device,DRD),A类设备就是USB主机,B类设备就是USB外设,通过检测USB接口上ID信号的电平,自动决定USB-OTG FS的角色类型(A类或B类)。DRD设备必须使用ID信号,也就必须使用Mini-AB接口或Micro-AB接口,因为标准Type-A接口和Type-B接口没有ID信号线。
在原理图中,MiniA/B母口上的ID线连接MCU的PA10,PA10就是USB-OTG FS的ID信号。在PHY中,ID有内部上拉电阻,所以在MiniA/B母口上未插入USB设备时,MCU的ID输入是高电平。当U盘设备连接到MiniA/B接口时,MCU检测到ID信号为低电平,这时MCU上的USB-OTG FS用作A类设备,也就是作为USB主机。
三、应用案例:配置USB Host并使用FATFS读写U盘
1、示例功能和CubeMX项目设置
本示例展示将USB-OTG FS作为USB主机时的用法。USB-OTG FS作为USB主机时,在开发板的MiniA/B母口上连接一个U盘,通过USB Host驱动程序和FatFS实现U盘文件系统管理。
U盘的本质也是Flash存储器,其底层的数据读写与SD卡类似。ST提供了一个中间件USB_HOST,作为USB主机的驱动程序,直接使用即可。
- 本示例要用串口助手和4个按键。
- 启用RTC的时钟源和日历,随便设置一个初始日期和时间,本示例要用RTC为FatFS提供时间戳数据。在RCC组件中启用LSE,在时钟树上,将RTC的时钟源设置为LSE。
(1)RCC、SYS、USART6、CodeGenerator、RTC、GPIO
- HSE,LSE,均外部晶振。RTC时钟32.768KHz,PCLK1=42MHz,PCLK2=84MHz。除此之外,USB-OTG FS还需要启用48MHz时钟;
- DEBUG,Serial Wire;
- 启用USART6,启用CodeGenerator代码对;
- 启用RTC,用于FATFS实时时钟,48MHz;

- GPIO,用于菜单操作;

(2)USB-OTG FS设置
在Connectivity分组里找到USB_OTG_FS,对USB-OTG FS进行模式和参数设置。在模式选择里,选择USB-OTG FS用于Host、Device、亦或双角色。



特别地,在模式设置部分还有两个复选框,涉及以下两个引脚的设置。
- Activate_SOF,是否启用帧的起始(Start of Frame,SOF)信号引脚。如果勾选此项,会在PA8引脚输出SOF信号。SOF信号是脉冲信号,此功能尤其适用于自适应音频时钟的生成。本示例不使用SOF信号。
- Activate_VBUS,是否启用MCU的VBUS引脚。仅作为USB主机时,MCU不需要使用VBUS引脚。如果MCU作为USB外设,则可以启用MCU的VBUS引脚,其功能是监测USB接口上是否有VBUS电源,以及监测VBUS电源的电压。如果勾选了此项,PA9将作为VBUS引脚。本示例不需要使用VBUS引脚。
- 在Connectivity分组里设置了USB_OTG_FS后,无论模式里如何选择,在Core/Inc和Core/Src下都分别自动生成usb_otg.h和usb_otg.c。但需要注意,当模式选择双角色时,仅自动生成代码框架。
- 在Connectivity分组里设置了USB_OTG_FS后的文件组成:

(3)中间件USB_HOST的设置
接着上一步的设置USB-OTG硬件的模式,继续再设置相应的中间件。在Middleware分组中,有USB_HOST和USB_DEVICE两个中间件,就是USB主机和USB外设的驱动程序。
- Audio Host Class,音频主机类。
- Human Interface Host Class(HID),人机接口主机类。
- Mass Storage Host Class(MSC),大容量存储主机类。
- Communication Host Class(Virtual Port Com),通信主机类(虚拟COM口)。
- Media Transfer Protocol Class(MTP),媒介传输协议类。
- Host Supporting ALL Classes,支持所有类型的主机类。
每一类设备都有相应的驱动程序,也就有不同的参数设置内容。本例将USB-OTG FS作为主机连接U盘,所以是MSC类型的主机。


- 在Middleware分组里配置完USB_HOST后的文件组成:
令人吃惊地发现,因Connectivity分组里设置了USB_OTG_FS后生成的usb_otg.h和usb_otg.c不见了,因为其本质已经被自动移植到了Middleware分组里配置完USB_HOST后自动生成的的文件中。
(4)NVIC
基础时钟中断优先级调整为=0,其余默认。
(5)FatFS设置
在设置了USB-OTG FS硬件接口和USB_HOST驱动库后,在FatFS的模式设置中,选择USB Disk作为存储介质了。
FatFS的参数设置,大部分参数设置保留默认值。只需设置代码页为简体中文、使用长文件名、MAX_SS和MIN_SS都自动设置为512。设置结果与使用SD卡时FatFS的设置相同,因为U盘使用的也是Flash存储器,数据块的大小也是512字节。


需要注意的是,长文件名一定要选择,经过测试发现,当不选择长文件名的时候,编译也不出错,但是输出结果不会如希望的那样,经验不足的时候,还查不出问题在哪里。

进一步地,在Middleware分组里配置完FATFS后的文件组成,进化为:

(6)总结:配置USB工程的操作流程
- 第一步,在Connectivity分组里设置USB-OTG FS(含HS)硬件接口,选择Host、Device、还是双角色,进而选择设置速率、SOF、VBUS、ID等细节,多数情况下,这些细节是默认的;
- 进一步,在Middleware分组里设置USB_HOST或USB_DEVICE,没有双角色的选择项,这一步的目的是生成驱动库。然后选择类,比如MSC,然后,选择和设置参数等选项,多数情况下这些参数的选择都是默认的;
- 进一步,在Middleware分组里设置FATFS,在模式里选择USB disk,这一步的目的是使用文件系统管理U盘操作。然后选择和设置宏参数、高级宏定义等。大多数情况下的选择和设置详见和参考本文。
2、项目文件组成和初始代码
(1)项目文件组成
完成设置后,CubeMX会自动生成代码,在项目中增加USB-OTG FS硬件驱动和USB_HOST中间件的程序文件。这个项目用到了FatFS和USB_HOST两个中间件,项目的Middlewares目录下的目录结构和文件上图所示。
根据文件结构从上到下依次解释为:
- \FATFS
- \FATFS\App\fatfs.c/.h
- \FATFS\Target\usbh_diskio.c/.h,特别重要地,ffconf.h也在这个目录下。
- \Middlewares\STSTM32_USB_Host_Library\
- \Middlewares\STSTM32_USB_Host_Library\Class\MSC目录下是MSC类主机的USB_HOST驱动程序文件,因为在CubeMX中设置了设备类型为MSC,这个目录下的文件的前缀都是“usbh_msc_”。
- \Middlewares\STSTM32_USB_Host_Library\Core目录下是USB_HOST的核心文件,也就是各类USB主机共用的一些驱动文件,这些文件都以“usbh_”为前缀。


- Middlewares\Third_Party\FatFs\src
在Middlewares\Third_Party\FatFs\src\路径下FatFs的通用驱动函数文件,重要的文件有ff.c、ff_gen_drv.c、diskio.c。内部的函数依次分别以f_、FATFS_、disk_为前缀。
- \USB_HOST
- \USB_HOST\App\usb_host.h/.c是中间件USB_HOST的初始化程序文件,包含初始化函数MX_USB_HOST_Init(),在main()函数里初始化外设时会调用这个函数。
- \USB_HOST\Target\usbh_conf.h/.c是中间件USB_HOST的配置程序文件,也就是中间件中的USB_HOST设置参数后而自动生成的程序文件,还包括DP和DM的GPIO引脚初始化程序。
FatFS使用了U盘作为存储介质,IDE生成的代码针对U盘自动完成了Disk IO访问层的移植。与FatFS相关的文件有以下几个。
- fatfs.h/.c,包含FatFS的初始化函数MX_FATFS_Init(),以及一些相关定义。
- ffconf.h,是与FatFS配置相关的头文件。
- usbh_diskio.h/.c,是针对USB Host MSC设备的Disk IO访问层的移植程序文件。
(2)主程序main.c初始化
//main.c
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "fatfs.h"
#include "rtc.h"
#include "usart.h"
#include "usb_host.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "keyled.h"
#include "file_opera.h"
#include <stdio.h>
/* USER CODE END Includes */
/* USER CODE BEGIN PV */
//#define BLOCKSIZE 512 //block size = 512 bytes
extern ApplicationTypeDef Appli_state; //it defined in usb_host.c
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
void MX_USB_HOST_Process(void);
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* Configure the system clock */
SystemClock_Config();
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART6_UART_Init();
MX_RTC_Init();
MX_FATFS_Init();
MX_USB_HOST_Init();
//剩下的代码待续
函数MX_FATFS_Init()用于FatFS初始化,函数MX_USB_HOST_Init()用于USB Host的初始化,这是在文件usb_host.h中定义的一个函数。函数的功能包括底层USB硬件接口的初始化、USB_HOST驱动库的初始化、注册USBHMSC硬件类型、启动USB Host内核程序。注意,在执行完函数MX_USB_HOST_Init()后,还不能调用FatFS的函数操作U盘,例如,执行函数f_mount()时总是返回不能挂载,即使U盘已经被正常格式化过了。
在主程序的while死循环里,循环执行函数MX_USB_HOST_Process(),这个函数也是在文件usb_host.h中定义的,是USB Host的背景任务。在函数MX_USB_HOST_Init()中启动USB Host内核后,需要周期性地运行这个函数,更新USB状态机的状态,才能在插入、拔出U盘时自动更新USB Host的状态。只有当USB Host的状态变为APPLICATION_READY时,才可以用FatFS操作U盘。
(3)FatFS初始化
函数MX_FATFS_Init()用于初始化FatFS,这个函数在文件fatfs.h/.c中定义和实现。文件fatfs.h的代码如下:
/* 文件:fatfs.h--------------- */
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __fatfs_H
#define __fatfs_H
#ifdef __cplusplus
extern "C" {
#endif
#include "ff.h"
#include "ff_gen_drv.h"
#include "usbh_diskio.h" /* defines USBH_Driver as external */
/* USER CODE BEGIN Includes */
/* USER CODE END Includes */
extern uint8_t retUSBH; /* Return value for USBH */
extern char USBHPath[4]; /* USBH logical drive path */
extern FATFS USBHFatFS; /* File system object for USBH logical drive */
extern FIL USBHFile; /* File object for USBH */
void MX_FATFS_Init(void);
/* USER CODE BEGIN Prototypes */
/* USER CODE END Prototypes */
#ifdef __cplusplus
}
#endif
#endif /*__fatfs_H */
在FatFS中,USB Host的MSC设备被称为USBH驱动器。这里声明了4个变量,都是在文件fatfs.c中定义的。文件fatfs.c的完整代码如下,其中函数get_fattime()用于获取RTC时间,作为创建文件或修改文件时的时间戳数据。这里直接调用了文件file_opera.h中定义的函数_GetFatTimeFromRTC()。
/* 文件:fatfs.c--------------- */
/* USER CODE END Header */
#include "fatfs.h"
uint8_t retUSBH; /* Return value for USBH */
char USBHPath[4]; /* USBH logical drive path */
FATFS USBHFatFS; /* File system object for USBH logical drive */
FIL USBHFile; /* File object for USBH */
/* USER CODE BEGIN Variables */
#include "file_opera.h"
/* USER CODE END Variables */
void MX_FATFS_Init(void)
{
/*## FatFS: Link the USBH driver ###########################*/
retUSBH = FATFS_LinkDriver(&USBH_Driver, USBHPath);
}
/**
* @brief Gets Time from RTC
* @param None
* @retval Time in DWORD
*/
DWORD get_fattime(void)
{
/* USER CODE BEGIN get_fattime */
return fat_GetFatTimeFromRTC();
//return 0;
/* USER CODE END get_fattime */
}
函数MX_FATFS_Init()用于FatFS的初始化,只有一行代码,即
/*## FatFS: Link the USBH driver ###########################*/
retUSBH = FATFS_LinkDriver(&USBH_Driver, USBHPath);
USBH_Driver是在文件usbh_diskio.c中定义的一个Diskio_drvTypeDef结构体类型的变量,用于将Disk IO访问的函数指针指向文件usbh_diskio.c中实现的U盘访问的Disk IO函数。执行这行代码的作用就是将USBH_Driver链接到系统的驱动器列表,以及为USBHPath赋值。执行此行代码后,USBHPath被赋值为“0:/”。
(4)USB_HOST初始化函数
在main()函数的外设初始化部分,调用函数MX_USB_HOST_Init()进行USB Host初始化,这个函数是在文件usb_host.h中定义的,这个文件同时还定义了USBH背景任务函数MX_USB_HOST_Process()。头文件usb_host.h的代码如下:
/* 文件:usb_host.h --------------- */
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __USB_HOST__H__
#define __USB_HOST__H__
#ifdef __cplusplus
extern "C" {
#endif
/* Includes ------------------------------------------------------------------*/
#include "stm32f4xx.h"
#include "stm32f4xx_hal.h"
/* USER CODE BEGIN INCLUDE */
#include "usbh_def.h" //USBH_HandleTypeDef defined here.
extern USBH_HandleTypeDef hUsbHostFS; //defined in usb_host.c,publicity here.
/* USER CODE END INCLUDE */
/** Status of the application. */
typedef enum {
APPLICATION_IDLE = 0,
APPLICATION_START,
APPLICATION_READY,
APPLICATION_DISCONNECT
}ApplicationTypeDef;
/* Exported functions -------------------------------------------------------*/
/** @brief USB Host initialization function. */
void MX_USB_HOST_Init(void);
void MX_USB_HOST_Process(void);
#ifdef __cplusplus
}
#endif
#endif /* __USB_HOST__H__ */
用extern声明了变量hUsbHostFS。这是在文件usb_host.c内定义的一个变量,如此声明后,就变为公共变量。因为文件ffconf.h定义的一个替代性宏要用到这个变量,即
#define hUSB_Host husbHostFS
usb_host.h还定义了一个枚举类型ApplicationTypeDef,这是USB Host状态机的状态变化时表示的应用程序状态。只有当应用程序状态为APPLICATION_READY时,才可以用FatFS操作U盘。文件usb_host.c的完整代码如下:
/* 文件:usb_host.c --------------- */
/* Includes ------------------------------------------------------------------*/
#include "usb_host.h"
#include "usbh_core.h"
#include "usbh_msc.h"
/* USER CODE BEGIN Includes */
#include "usart.h" //test
/* USER CODE END Includes */
/* USB Host core handle declaration */
USBH_HandleTypeDef hUsbHostFS;
ApplicationTypeDef Appli_state = APPLICATION_IDLE;
/*
* user callback declaration
*/
static void USBH_UserProcess(USBH_HandleTypeDef *phost, uint8_t id);
/**
* Init USB host library, add supported class and start the library
* @retval None
*/
void MX_USB_HOST_Init(void)
{
/* Init host Library, add supported class and start the library. */
if (USBH_Init(&hUsbHostFS, USBH_UserProcess, HOST_FS) != USBH_OK)
{
Error_Handler();
}
if (USBH_RegisterClass(&hUsbHostFS, USBH_MSC_CLASS) != USBH_OK)
{
Error_Handler();
}
if (USBH_Start(&hUsbHostFS) != USBH_OK)
{
Error_Handler();
}
}
/*
* Background task
*/
void MX_USB_HOST_Process(void)
{
/* USB Host Background task */
USBH_Process(&hUsbHostFS);
}
/*
* user callback definition
*/
static void USBH_UserProcess (USBH_HandleTypeDef *phost, uint8_t id)
{
/* USER CODE BEGIN CALL_BACK_1 */
/*
* all printf() used to test.
* and can be cancelled after tested.
*
* */
switch(id)
{
case HOST_USER_SELECT_CONFIGURATION:
printf("id = HOST_USER_SELECT_CONFIGURATION.\r\n"); //test
break;
case HOST_USER_DISCONNECTION:
Appli_state = APPLICATION_DISCONNECT;
printf("Appli_state = APPLICATION_DISCONNECT.\r\n");
break;
case HOST_USER_CLASS_ACTIVE:
Appli_state = APPLICATION_READY;
printf("Appli_state = APPLICATION_READY.\r\n");
break;
case HOST_USER_CONNECTION:
Appli_state = APPLICATION_START;
printf("Appli_state = APPLICATION_START.\r\n");
break;
default:
printf("id = null.\r\n");
break;
}
/* USER CODE END CALL_BACK_1 */
}
这个文件定义了一个USBH_HandleTypeDef类型的变量hUsbHostFS,这是表示USB-OTG FS的USB Host外设对象变量,USB_HOST驱动库的一些函数需要使用这个变量作为传递参数。
初始化函数MX_USB_HOST_Init()调用了3个函数执行了一些操作。
- 调用函数USBH_Init()进行USB Host的初始化,执行的函数代码如下:
USBH_Init(&hUsbHostFS, USBH_UserProcess, HOST_FS)
第1个参数hUsbHostFS是USB Host对象指针;第2个变量是用户回调函数指针,这里指向函数USBH_UserProcess();第3个变量是USB的ID,宏常数HOST_FS的值为1,表示USB-OTG FS。
成功执行这个函数后,就完成了对USB-OTG FS的USB Host的初始化,并且注册了一个用户回调函数USBH_UserProcess(),这个函数会在USB Host的状态发生变化时被调用。
- 调用USBH_RegisterClass()注册了USB Host设备类别,执行的函数代码如下:
USBH_Registerclass(chUsbHostFs, USBH_MSC_CLASS)
这是向USB Host对象hUsbHostFS注册设备类别USBH_MSC_CLASS,也就是将MSC相关的驱动程序连接到USB Host内核。
- 调用USBH_Start()启动USB Host内核,执行的函数代码如下:
USBH_Start(&hUsbHostFS)
启动USBH内核后,USBH的内核由状态机驱动,必须在程序中不断地执行USB Host的背景任务函数MX_USB_HOST_Process(),监测USB的硬件和软件状态变化,并更新应用程序状态。
函数MX_USB_HOST_Init()调用的这3个函数都是usbh_core.h中定义的函数,属于USBH内核的函数。
(5)USB_HOST背景任务函数和用户回调函数
USB Host的背景任务函数是MX_USB_HOST_Process(),在USBH内核启动后,需要不断地调用这个函数,监测USB的硬件和软件状态变化,当USB Host的状态发生变化时,会执行用户回调函数。在函数MX_USB_HOST_Init()的代码中,将用户回调函数指向函数USBH_UserProcess()。这两个函数的源代码见usb_host.c的源代码。
背景任务函数MX_USB_HOST_Process()的代码只有一行语句,即
USBH_Process(&hUsbHostFS);
函数USBH_Process()是在文件usbh_core.h中定义的函数,其源代码较长,功能就是监测USB-OTG FS的硬件和软件状态,状态发生变化时,调用回调函数USBH_UserProcess()。
从函数USBH_UserProcess()的代码可以看到,它将参数id表示的USB状态机的一些状态转换为应用程序的状态Appli_state。用户代码可以监测Appli_state的变化,从而进行一些操作。
在实际运行中,只有当Appli_state的值变为APPLICATION_READY时,才可以开始用FatFS操作U盘。在main()函数中,执行完函数MX_USB_HOST_Init()后,还不能立刻使用FatFS操作U盘。
(6)U盘的FatFS Disk IO函数移植
CubeMX生成的代码自动完成了FatFS读写U盘的Disk IO函数的移植,程序文件是usbh_diskio.h和usbh_diskio.c。文件usbh_diskio.h的代码如下,只是导出了USBH驱动器对象USBH_Driver的定义:
/* 文件:usbh_diskio.h---------------------------------- */
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __USBH_DISKIO_H
#define __USBH_DISKIO_H
/* Includes ------------------------------------------------------------------*/
#include "usbh_core.h"
#include "usbh_msc.h"
/* Exported functions ------------------------------------------------------- */
extern const Diskio_drvTypeDef USBH_Driver;
#endif /* __USBH_DISKIO_H */
文件usbh_diskio.c所包含的是Disk IO函数的实现,完整代码如下。编译条件_USE_WRITE和_USE_IOCTL,默认情况下,这两个参数都是1。
/* 文件:usbh_diskio.c---------------------------------- */
/* USER CODE BEGIN firstSection */
/* can be used to modify / undefine following code or add new definitions */
#include "main.h"
#include <stdio.h>
#include "usart.h"
//#include "sdio.h"
/* USER CODE END firstSection */
/* Includes ------------------------------------------------------------------*/
#include "ff_gen_drv.h"
#include "usbh_diskio.h"
/* Private define ------------------------------------------------------------*/
#define USB_DEFAULT_BLOCK_SIZE 512
/* Private variables ---------------------------------------------------------*/
extern USBH_HandleTypeDef hUSB_Host;
/* Private function prototypes -----------------------------------------------*/
DSTATUS USBH_initialize (BYTE);
DSTATUS USBH_status (BYTE);
DRESULT USBH_read (BYTE, BYTE*, DWORD, UINT);
#if _USE_WRITE == 1
DRESULT USBH_write (BYTE, const BYTE*, DWORD, UINT);
#endif /* _USE_WRITE == 1 */
#if _USE_IOCTL == 1
DRESULT USBH_ioctl (BYTE, BYTE, void*);
#endif /* _USE_IOCTL == 1 */
const Diskio_drvTypeDef USBH_Driver =
{
USBH_initialize,
USBH_status,
USBH_read,
#if _USE_WRITE == 1
USBH_write,
#endif /* _USE_WRITE == 1 */
#if _USE_IOCTL == 1
USBH_ioctl,
#endif /* _USE_IOCTL == 1 */
};
/* USER CODE BEGIN beforeFunctionSection */
/* can be used to modify/undefine following code or add new code */
uint8_t USBBuf_TX[BLOCKSIZE]; //Data Sending Cache, BLOCKSIZE=512
uint8_t USBBuf_RX[BLOCKSIZE]; //Data Received Cache
/* USER CODE END beforeFunctionSection */
/* Private functions ---------------------------------------------------------*/
/**
* @brief Initializes a Drive
* @param lun : lun id
* @retval DSTATUS: Operation status
*/
DSTATUS USBH_initialize(BYTE lun)
{
/* CAUTION : USB Host library has to be initialized in the application */
return RES_OK;
}
/**
* @brief Gets Disk Status
* @param lun : lun id
* @retval DSTATUS: Operation status
*/
DSTATUS USBH_status(BYTE lun)
{
DRESULT res = RES_ERROR;
if(USBH_MSC_UnitIsReady(&hUSB_Host, lun))
{
res = RES_OK;
}
else
{
res = RES_ERROR;
}
return res;
}
/**
* @brief Reads Sector(s)
* @param lun : lun id
* @param *buff: Data buffer to store read data
* @param sector: Sector address (LBA)
* @param count: Number of sectors to read (1..128)
* @retval DRESULT: Operation result
*/
DRESULT USBH_read(BYTE lun, BYTE *buff, DWORD sector, UINT count)
{
DRESULT res = RES_ERROR;
MSC_LUNTypeDef info;
if(USBH_MSC_Read(&hUSB_Host, lun, sector, buff, count) == USBH_OK)
{
res = RES_OK;
}
else
{
USBH_MSC_GetLUNInfo(&hUSB_Host, lun, &info);
switch (info.sense.asc)
{
case SCSI_ASC_LOGICAL_UNIT_NOT_READY:
case SCSI_ASC_MEDIUM_NOT_PRESENT:
case SCSI_ASC_NOT_READY_TO_READY_CHANGE:
USBH_ErrLog ("USB Disk is not ready!");
res = RES_NOTRDY;
break;
default:
res = RES_ERROR;
break;
}
}
return res;
}
/**
* @brief Writes Sector(s)
* @param lun : lun id
* @param *buff: Data to be written
* @param sector: Sector address (LBA)
* @param count: Number of sectors to write (1..128)
* @retval DRESULT: Operation result
*/
#if _USE_WRITE == 1
DRESULT USBH_write(BYTE lun, const BYTE *buff, DWORD sector, UINT count)
{
DRESULT res = RES_ERROR;
MSC_LUNTypeDef info;
if(USBH_MSC_Write(&hUSB_Host, lun, sector, (BYTE *)buff, count) == USBH_OK)
{
res = RES_OK;
}
else
{
USBH_MSC_GetLUNInfo(&hUSB_Host, lun, &info);
switch (info.sense.asc)
{
case SCSI_ASC_WRITE_PROTECTED:
USBH_ErrLog("USB Disk is Write protected!");
res = RES_WRPRT;
break;
case SCSI_ASC_LOGICAL_UNIT_NOT_READY:
case SCSI_ASC_MEDIUM_NOT_PRESENT:
case SCSI_ASC_NOT_READY_TO_READY_CHANGE:
USBH_ErrLog("USB Disk is not ready!");
res = RES_NOTRDY;
break;
default:
res = RES_ERROR;
break;
}
}
return res;
}
#endif /* _USE_WRITE == 1 */
/**
* @brief I/O control operation
* @param lun : lun id
* @param cmd: Control code
* @param *buff: Buffer to send/receive control data
* @retval DRESULT: Operation result
*/
#if _USE_IOCTL == 1
DRESULT USBH_ioctl(BYTE lun, BYTE cmd, void *buff)
{
DRESULT res = RES_ERROR;
MSC_LUNTypeDef info;
switch (cmd)
{
/* Make sure that no pending write process */
case CTRL_SYNC:
res = RES_OK;
break;
/* Get number of sectors on the disk (DWORD) */
case GET_SECTOR_COUNT :
if(USBH_MSC_GetLUNInfo(&hUSB_Host, lun, &info) == USBH_OK)
{
*(DWORD*)buff = info.capacity.block_nbr;
res = RES_OK;
}
else
{
res = RES_ERROR;
}
break;
/* Get R/W sector size (WORD) */
case GET_SECTOR_SIZE :
if(USBH_MSC_GetLUNInfo(&hUSB_Host, lun, &info) == USBH_OK)
{
*(DWORD*)buff = info.capacity.block_size;
res = RES_OK;
}
else
{
res = RES_ERROR;
}
break;
/* Get erase block size in unit of sector (DWORD) */
case GET_BLOCK_SIZE :
if(USBH_MSC_GetLUNInfo(&hUSB_Host, lun, &info) == USBH_OK)
{
*(DWORD*)buff = info.capacity.block_size / USB_DEFAULT_BLOCK_SIZE;
res = RES_OK;
}
else
{
res = RES_ERROR;
}
break;
default:
res = RES_PARERR;
}
return res;
}
#endif /* _USE_IOCTL == 1 */
/* 自定义的私有函数都在这个沙箱里*/
/* USER CODE BEGIN lastSection */
/* can be used to modify / undefine previous code or add new code */
/* USBH_status(), test USBDisk status */
/**
<pre>
typedef enum {
RES_OK = 0, 0: Successful
RES_ERROR, 1: R/W Error
RES_WRPRT, 2: Write Protected
RES_NOTRDY, 3: Not Ready
RES_PARERR 4: Invalid Parameter
} DRESULT;
</pre>
*/
void USBDisk_TestStatus()
{
printf("\r\n*** Test USBDisk Status *** \r\n\r\n");
BYTE lun={};
switch(USBH_status(lun)){
case 0:
printf("USBDisk Status Successful.\r\n");
break;
case 1:
printf("USBDisk Status R/W Error.\r\n");
break;
case 2:
printf("USBDisk Status Write Protected.\r\n");
break;
case 3:
printf("USBDisk Status Not Ready.\r\n");
break;
case 4:
printf("USBDisk Status Invalid Parameter.\r\n");
break;
default:
break;
}
}
/* USBH_write(), Write USBDisk */
void USBDisk_TestWrite() //test write
{
printf("\r\n*** USBDisk Writing blocks *** \r\n\r\n");
for(uint16_t i=0;i<BLOCKSIZE; i++)
USBBuf_TX[i]=i; //generate data
printf("Writing block 6. \r\n");
printf("Data in [10:15] is: %d ",USBBuf_TX[10]);
for (uint16_t j=11; j<=15;j++)
{
printf(", %d", USBBuf_TX[j]);
}
printf("\r\n USBH_write() is to be called. \r\n");
uint32_t BlockAddr=6; //Block Address
uint32_t BlockCount=1; //Block Count
BYTE lun={};
//DRESULT USBH_write(BYTE lun, const BYTE *buff, DWORD sector, UINT count);
if(USBH_write(lun,USBBuf_TX,BlockAddr,BlockCount) == RES_OK) //can erase block automatically
{
printf("UDisk write complete. \r\n");
}
}
/* USBH_read(), Read USBDisk */
void USBDisk_TestRead() //test read
{
printf("\r\n*** USBDisk Reading blocks *** \r\n\r\n");
printf("\r\nHAL_SD_ReadBlocks_DMA() is to be called. \r\n");
uint32_t BlockAddr=6; //Block Address
uint32_t BlockCount=1; //Block Count
BYTE lun={};
if(USBH_read(lun,USBBuf_RX,BlockAddr,BlockCount) == RES_OK)
{
printf("USBDisk read complete. \r\n");
printf("Data in [10:15] is: %d",USBBuf_RX[10]);
for (uint16_t j=11; j<=15;j++)
{
printf(", %d", USBBuf_RX[j]);
}
printf("\r\n");
}
}
/* USER CODE END lastSection */
U盘的Disk IO函数主要是调用文件usbh_msc.h中的一些函数实现的。usbh_msc.h是MSC类设备的驱动程序,包含MSC类设备访问的一些底层驱动函数。例如,在函数USBH_read()中,调用USBH_MSC_Read()读取U盘数据;在函数USBH_write()中,调用函数USBH_MSC_Write()向U盘写入数据;在函数USBH_ioctl()中,调用函数USBH_MSC_GetLUNInfo()获取U盘的一些原始信息,如数据块个数、数据块大小等。
函数USBH_MSC_GetLUNInfo()也可以在用户代码里直接调用,用于获取U盘的原始容量信息,其原型定义如下:
USBH_StatusTypeDef USBH_MSC_GetLUNInfo(USBH_HandleTypeDef *phost, uint8_t lun,MSC_LUNTypeDef *info);
//同理
USBH_StatusTypeDef USBH_MSC_Read(USBH_HandleTypeDef *phost, uint8_t lun,
uint32_t address, uint8_t *pbuf, uint32_t length);
USBH_StatusTypeDef USBH_MSC_Write(USBH_HandleTypeDef *phost, uint8_t lun,
uint32_t address, uint8_t *pbuf, uint32_t length);
其中,参数phost是USB Host对象指针,lun是驱动器编号,info是返回信息的MSC_LUNTypeDef结构体指针。结构体MSC_LUNTypeDef存储了U盘的一些信息,其原型定义如下:
/* Structure for LUN */
typedef struct
{
MSC_StateTypeDef state;
MSC_ErrorTypeDef error;
USBH_StatusTypeDef prev_ready_state;
SCSI_CapacityTypeDef capacity; //U盘容量信息,结构体
SCSI_SenseTypeDef sense;
SCSI_StdInquiryDataTypeDef inquiry;
uint8_t state_changed;
}MSC_LUNTypeDef;
结构体MSC_LUNTypeDef的成员变量capacity是结构体类型SCSI_CapacityTypeDef,表示U盘的容量信息。结构体类型SCSI_CapacityTypeDef的定义如下,包含数据块个数和数据块大小:
/* Capacity data */
typedef struct
{
uint32_t block_nbr; //数据块个数
uint16_t block_size; //数据块大小,字节数
} SCSI_CapacityTypeDef;
U盘使用的也是Flash存储器,数据块大小一般为512字节。知道了数据块个数和大小,就可以计算出U盘的总容量。
3、USBH状态变化测试
(1)修改usb_host.c
为了搞清楚USBH驱动程序的工作原理以及USB Host状态变化的规律,在自动生成的代码的基础上稍加修改,进行USBH状态变化测试。
在文件usb_host.c中,包含USART6驱动程序的头文件usart.h,将用户回调函数USBH_UserProcess()修改为如下的内容,也就是将函数参数id或变量Appli_state的字符串意义显示在串口助手上。修改后的代码已经贴在usb_host.c中的沙箱里,标记为test,不需要的时候可以注释掉。
除此之外,在main.c的无限循环体里始终执行背景程序:
MX_USB_HOST_Process();
为避免不必要的外部干预,注释掉与测试无关的私有代码。
(2)状态变化测试
构建项目后,下载到开发板上,用一个FAT32格式化过的8G U盘,做如下的一些测试。
不能使用NTFS格式,也不能使用USB 3.0接口的U盘,因为STM32F407只支持USB 2.0。
第1步:在开发板电源关闭的状态下,将U盘插入开发板上的USB接口P15,然后打开电源。串口助手显示:
id =null
Appli_state =APPLICATION_READY
这说明,在main()函数中,执行完函数MX_USB_HOST_Init()后,USBH并不是处于就绪状态,需要多次执行背景任务函数MX_USB_HOST_Process()后,才会变为就绪状态。此外,USBH处于就绪状态后,如果不拔下U盘,状态就不再变化了。
第2步:在上一步的基础上,待USBH状态稳定后拔下U盘,串口助手显示:
id =null
Appli_state =APPLICATION_READY
Appli_state =APPLICATION_DISCONNECT
前2行是第1步的显示信息,第3行是第2步操作的显示信息,显示U盘断开连接了。
第3步:在第2步的基础上,再次插入U盘,会看到新增如下3行显示,USBH的最后状态也是稳定在就绪状态。
Appli_state =APPLICATION_START
id =null
Appli_state =APPLICATION_READY
(3)USBH状态变化规律总结
由以上的测试可以发现USBH背景任务函数、用户回调函数的作用,以及USBH状态变化的规律,具体如下。
- 如果周期性地调用USBH背景任务函数,就可以检测USBH的状态变化,在状态发生变化时,会自动调用用户回调函数,改变变量Appli_state的值,从而检测出U盘插入或拔出的情况。
- 在U盘已经插入的情况下,复位系统,执行函数MX_USB_HOST_Init()完成USB Host的初始化后,USBH并不是处于就绪状态,必须多次运行背景任务函数后,才会最终变为就绪状态。
- 无论U盘是冷插入(即总电源关闭时插入U盘),还是热插入(系统运行时插入U盘),只要连续运行USBH背景任务函数,最后都能处于就绪状态。
- 只有当USBH处于就绪状态之后,才可以用FatFS操作U盘。所以,在main()函数中,不能在执行完函数MX_USB_HOST_Init()后立刻调用函数f_mount()挂载U盘的文件系统,必须连续运行背景任务函数MX_USB_HOST_Process(),等到USBH状态变为就绪状态后才可以用函数f_mount()挂载U盘的文件系统。
在一个嵌入式设备中,如果U盘总是冷插入的,且不用考虑热拔除(即系统还在运行时拔除U盘)问题,那么,当USBH状态达到就绪状态后,就可以不必再运行USBH背景任务函数了。
4、U盘文件管理功能实现
在搞清楚USB Host的驱动程序工作原理和基本流程,以及FatFS在U盘上的移植程序原理后,就可以编写代码,用FatFS对U盘进行文件管理了。建立两个菜单界面,测试U盘文件管理和文件读写。
- 将KEY_LED和FILE_TEST添加到项目搜索路径中,需要用到这两个目录下的驱动程序文件。
- 将文件usb_host.c中的函数USBH_UserProcess()改回其原始状态,也就是注释掉用于test的显示到串口的语句。
- 在文件fatfs.c中,实现函数get_fattime()的代码。
(1)主程序功能
主程序中设计3级菜单,继续前文贴上来的main.c代码如下:
//继续前文未贴完的main.c
/* USER CODE BEGIN 2 */
// Start Menu
printf("Demo15_1: FatFS on USB Disk.\r\n\r\n");
HAL_Delay(500);
//test usage
if (USBH_MSC_IsReady(&hUsbHostFS))
printf("test1 USBH_MSC_IsReady == 1.\r\n\r\n");
else
printf("test1 USBH_MSC_IsReady == 0.\r\n\r\n");
/* USB Host background task,
* must be executed after USBH is initialized
* until it reaches the ready state.
*
* */
while (1)
{
MX_USB_HOST_Process();
if (Appli_state==APPLICATION_READY)
break;
}
//test usage
if (hUsbHostFS.device.is_connected)
printf("USB disk is connected.\r\n");
else
printf("USB disk is not connected.\r\n");
//Load FATFS
FRESULT res=f_mount(&USBHFatFS, "0:", 1); //FATFS can only be mounted after USBH is ready.
// If the mount is not successful, uninstall it first and then mount it,until have it mounted.
while(res != FR_OK)
{
BYTE workBuffer[4*512];
res=f_mkfs("0:",FM_FAT32,0,workBuffer,4*512);
res=f_mount(NULL,"0:",1); //uninstall it first
HAL_Delay(10);
res=f_mount(&USBHFatFS,"0:",1); //and then mount it
}
if (res==FR_OK)
printf("FatFS is mounted, OK.\r\n\r\n");
else
printf("No file system, to format.\r\n");
//test usage
if (USBH_MSC_IsReady(&hUsbHostFS))
printf("test2 USBH_MSC_IsReady == 1.\r\n");
else
printf("test2 USBH_MSC_IsReady == 0.\r\n\r\n");
//Menu 1
printf("[1][S2]KeyUp =Format USB Disk.\r\n");
printf("[2][S4]KeyLeft =FAT disk info.\r\n");
printf("[3][S5]KeyRight=USB disk info.\r\n");
printf("[4][S1]KeyDown =Next menu page.\r\n");
printf("\r\n");
KEYS waitKey;
while(1)
{
waitKey=ScanPressedKey(KEY_WAIT_ALWAYS);
if (waitKey == KEY_UP) //KeyUp = Format USB Disk
{
BYTE workBuffer[4*BLOCKSIZE]; //buffer
DWORD clusterSize=0; //The cluster must be >= 1 sector, and 0 means automatic.
printf("Formating (10secs)...\r\n");
FRESULT res=f_mkfs("0:", FM_FAT32, clusterSize, workBuffer, 4*BLOCKSIZE); //FM_FAT32
if (res ==FR_OK)
printf("Format OK, to reset.\r\n");
else
printf("Format fail, to reset.\r\n");
}
else if(waitKey == KEY_LEFT) //KeyLeft =FAT disk info
fatTest_GetDiskInfo();
else if (waitKey == KEY_RIGHT) //KeyRight=USB disk info
USBDisk_ShowInfo();
else
break;
printf("Reselect menu item or reset.\r\n\r\n");
HAL_Delay(500); //Delay 500 to eliminate key jitter.
MX_USB_HOST_Process(); //If plug-in and unplugs is not used, func is not required too.
}
//Menu 2
//test read/write U_Disk with FATFS universal function
printf("test read/write U_Disk with FATFS universal function.\r\n");
printf("[5][S2]KeyUp =Write files.\r\n");
printf("[6][S4]KeyLeft =Read a TXT file.\r\n");
printf("[7][S5]KeyRight=Read a BIN file.\r\n");
printf("[8][S1]KeyDown =Next menu page.\r\n");
printf("\r\n");
HAL_Delay(500);
/* Menu2,use FATFS function operates files,such as 'f_'.
* For example, These functions with "f_" as prefix,
* are located under the \Middlewares\Third_Party\FatFs\src\.
* These functions exist as long as FATFS is turned on,
* but they have nothing to do with DMA.
* Whether DMA is enabled or not, these functions are implemented the same way.
*
*/
while(2)
{
waitKey=ScanPressedKey(KEY_WAIT_ALWAYS);
/* Long file names need to be allowed,
* otherwise f_open() fails.
* */
if (waitKey==KEY_UP )
{
fatTest_WriteTXTFile("USB_readme.txt",2025,7,29);
fatTest_WriteTXTFile("USB_help.txt",2025,7,29);
fatTest_WriteBinFile("USB_DAQ3000.dat",50,3000);
fatTest_WriteBinFile("USB_DAQ1500.dat",100,1500);
f_mkdir("0:/USB_Data");
f_mkdir("0:/USB_Documents");
}
else if (waitKey==KEY_LEFT )
fatTest_ReadTXTFile("USB_readme.txt");
else if (waitKey==KEY_RIGHT)
fatTest_ReadBinFile("USB_DAQ3000.dat");
else if (waitKey==KEY_DOWN)
break;
printf("Reselect menu item or reset.\r\n\r\n");
HAL_Delay(500);
// MX_USB_HOST_Process();
}
printf("\r\n");
//Menu Item 3
//test erase/U_Disk info/read/write with specialized USBH_Function
printf("test erase/U_Disk info/read/write with specialized USBH_Function.\r\n");
printf("[09][S2]KeyUp =Show U_Disk Status. \r\n"); //ready to modify
printf("[10][S4]KeyLeft =List entries. \r\n");
printf("[11][S5]KeyRight=Write U_Disk. \r\n");
printf("[12][S1]KeyDown =Read U_Disk. \r\n\r\n");
HAL_Delay(500);
/* Menu 3,use FATFS function operates files,such as 'SD_'.
* For example, These functions with "SD_" as prefix,
* are located under the \FATFS\Target\.
* These functions exist as long as FATFS is turned on,
* Whether DMA is enabled or not, these functions are implemented different way.
*
*/
while(3)
{
waitKey=ScanPressedKey(KEY_WAIT_ALWAYS);
if (waitKey==KEY_UP)
{
USBDisk_TestStatus();
}
else if (waitKey== KEY_DOWN)
{
fatTest_ScanDir("0:/");//Scan the files&dir in the root.
}
else if (waitKey== KEY_LEFT)
USBDisk_TestWrite();
else if (waitKey== KEY_RIGHT)
USBDisk_TestRead();
printf("Reselect menu item or reset.\r\n\r\n");
HAL_Delay(500);
}
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
/* In infinite loop, executes the function loop MX_USB_HOST_Process(),
* which is also defined in usb_host.h
* and is the background task of USB Host.
* After starting the USB Host kernel in the MX_USB_HOST_Init(),
* this function need to be run periodically
* to update the status of the USB state machine
* in order to automatically update the status of the USB Host
* when the USB disk is inserted and unplugged.
* Only when the status of USB Host becomes APPLICATION_READY
* can you use FatFS to operate the USB drive.
*
* */
while (1)
{
/* USER CODE END WHILE */
MX_USB_HOST_Process();
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
//以下IDE自动生成的代码,省略
/* USER CODE BEGIN 4 */
/* Show Udisk Info */
void USBDisk_ShowInfo()
{
MSC_LUNTypeDef info;
uint8_t lun=0;
printf("*** USB disk info ***\r\n");
if(USBH_MSC_GetLUNInfo(&hUSB_Host, lun, &info) == USBH_OK)
{
printf("Block count= %ld.\r\n",info.capacity.block_nbr);
printf("Block Size(Bytes)= %d.\r\n",info.capacity.block_size);
uint32_t cap=(info.capacity.block_nbr)>>11; //MB,BlockSize=512
printf("Disk capacity(MB)= %ld.\r\n",cap);
}
else
printf("Get Disk info fail.\r\n\r\n");
}
//printf()
int __io_putchar(int ch)
{
HAL_UART_Transmit(&huart6,(uint8_t*)&ch,1,0xFFFF);
return ch;
}
/* USER CODE END 4 */
//以下IDE自动生成的代码,省略
上述程序定义了一个宏BLOCKSIZE,表示U盘数据块的大小,其值为512;声明了外部变量Appli_state,这个变量在文件usb_host.c中定义,表示应用程序状态。
完成外设初始化后,在一个while循环里执行USB Host背景任务函数MX_USB_HOST_Process(),直到应用程序状态变为就绪状态,也就是变量Appli_state的值变为APPLICATION_READY。因为只有当USB Host MSC处于就绪状态后,才可以开始用FatFS管理U盘。
主程序中,也是先用函数f_mount()挂载U盘的文件系统,然后创建两级菜单进行U盘文件系统管理。第1组菜单内容如下:
[1][S2]KeyUp =Format USB Disk
[2][S4]KeyLeft =FAT disk info
[3][S5]KeyRight=USB disk info
[4][S1]KeyDown =Next menu page
使用开发板上的4个按键进行选择操作,按下KeyDown后,会显示第2组菜单,内容如下:
[5][S2]KeyUp =Write files
[6][S4]KeyLeft =Read a TXT file
[7][S5]KeyRight=Read a BIN file
[8][S1]KeyDown =Next menu page
第2组菜单,调用FATFS通用函数,即f_前缀的函数管理U盘文件。
第3组菜单,内容如下:
[09][S2]KeyUp =Show U_Disk Status.
[10][S4]KeyLeft =List entries.
[11][S5]KeyRight=Write U_Disk.
[12][S1]KeyDown =Read U_Disk.
第3组菜单,调用FATFS专用函数,即USBH_前缀的函数管理U盘文件。
(2)main.h私有函数声明
私有函数的声明在main.h里,调用专用驱动函数的私有函数定义在usbh_diskio.c定义。调用通用驱动函数的私有函数定义在file_opera.c定义。
//main.h新增私有函数声明和宏定义
/* USER CODE BEGIN Private defines */
void USBDisk_ShowInfo(); //Show Udisk information
void USBDisk_TestStatus();
void USBDisk_TestWrite();
void USBDisk_TestRead();
#define BLOCKSIZE 512 //block size = 512 bytes
/* USER CODE END Private defines */
(3)其它文件
/KEYLED/keyled.c/.h和/FILE_TEST/file_opera.c/h请参考本文作者发布的其他文章。
四、运行与调试
下载到开发板里,进行测试。
测试过程及结果通过串口助手存储为.TXT文件。
Demo15_1: FatFS on USB Disk.
test1 USBH_MSC_IsReady == 0.
Appli_state = APPLICATION_START.
id = null.
Appli_state = APPLICATION_READY.
USB disk is connected.
FatFS is mounted, OK.
test2 USBH_MSC_IsReady == 0.
[1][S2]KeyUp =Format USB Disk.
[2][S4]KeyLeft =FAT disk info.
[3][S5]KeyRight=USB disk info.
[4][S1]KeyDown =Next menu page.
Formating (10secs)...
Format OK, to reset.
Reselect menu item or reset.
*** FAT disk info ***.
FAT type= 3
[1=FAT12,2=FAT16,3=FAT32,4=exFAT]
Sector size(bytes)= 512
Cluster size(sectors)= 64
Total cluster count= 245728
Total sector count= 15726592
Total space(MB)= 7679
Free cluster count= 245727
Free sector count= 15726528
Free space(MB)= 7678
Get FAT disk info OK.
Reselect menu item or reset.
*** USB disk info ***
Block count= 15728639.
Block Size(Bytes)= 512.
Disk capacity(MB)= 7679.
Reselect menu item or reset.
test read/write U_Disk with FATFS universal function.
[5][S2]KeyUp =Write files.
[6][S4]KeyLeft =Read a TXT file.
[7][S5]KeyRight=Read a BIN file.
[8][S1]KeyDown =Next menu page.
Write file OK: USB_readme.txt
Write file OK: USB_help.txt
Write file OK: USB_DAQ3000.dat
Write file OK: USB_DAQ1500.dat
Reselect menu item or reset.
Reading TXT file: Line1: Hello, FatFS
Reading TXT file: Line2: UPC, AnHui Hefei
Reading TXT file: Line3: Date: 2025-7-29
Reselect menu item or reset.
Reading BIN file: ADC1-IN5
Sampling freq: 3000
Point count: 50
Reselect menu item or reset.
test erase/U_Disk info/read/write with specialized USBH_Function.
[09][S2]KeyUp =Show U_Disk Status.
[10][S4]KeyLeft =List entries.
[11][S5]KeyRight=Write U_Disk.
[12][S1]KeyDown =Read U_Disk.
*** Test USBDisk Status ***
USBDisk Status Successful.
Reselect menu item or reset.
*** USBDisk Writing blocks ***
Writing block 6.
Data in [10:15] is: 10 , 11, 12, 13, 14, 15
USBH_write() is to be called.
UDisk write complete.
Reselect menu item or reset.
*** USBDisk Reading blocks ***
HAL_SD_ReadBlocks_DMA() is to be called.
USBDisk read complete.
Data in [10:15] is: 10, 11, 12, 13, 14, 15
Reselect menu item or reset.
All entries in dir 0:/.
FILE USB_readme.txt.
FILE USB_help.txt.
FILE USB_DAQ3000.dat.
FILE USB_DAQ1500.dat.
DIR USB_Data.
DIR USB_Documents.
Scan dir OK.
Reselect menu item or reset.
更多推荐

所有评论(0)