目录

一、USB概述

1、USB协议

2、USB设备类型

3、USB接口类型

二、STM32F407的USB-OTG接口

1、USB-OTG概述

2、USB-OTG FS

(1)功能概述

(2)仅作为USB主机

(3)仅作为USB外设

(4)OTG双角色设备

三、应用案例:配置USB Host并使用FATFS读写U盘

1、示例功能和CubeMX项目设置

(1)RCC、SYS、USART6、CodeGenerator、RTC、GPIO

(2)USB-OTG FS设置

(3)中间件USB_HOST的设置

(4)NVIC

(5)FatFS设置

(6)总结:配置USB工程的操作流程

2、项目文件组成和初始代码

(1)项目文件组成

(2)主程序main.c初始化

(3)FatFS初始化

(4)USB_HOST初始化函数

(5)USB_HOST背景任务函数和用户回调函数

(6)U盘的FatFS Disk IO函数移植

3、USBH状态变化测试

(1)修改usb_host.c

(2)状态变化测试

(3)USBH状态变化规律总结

4、U盘文件管理功能实现

(1)主程序功能

(2)main.h私有函数声明

(3)其它文件

四、运行与调试


        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.
    
    

    Logo

    有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

    更多推荐