《DMG474开发指南_V1.1》第三十六章 USB读卡器(Slave)实验

第三十六章 USB读卡器(Slave)实验


       STM32G474系列芯片自带了USB FS(FS,即全速,12Mbps),所有USB相关例程,均使用USB FS实现。下面,我们将介绍如何使用USB FS在DMG474开发板上实现一个USB读卡器。

       本章分为如下几个小节:

       36.1 USB简介

       36.2 硬件设计

       36.3 程序设计

       36.4 下载验证


        36 . 1 USB简介

       USB,即通用串行总线(Universal Serial Bus),包括USB协议和USB硬件两个方面,支持热插拔功能。现在日常生活的很多方面都离不开USB的应用,如充电和数据传输等方面的应用。

       USB经过多次修改,1996年确定了初始规范版本USB1.0,目前由非盈利组织USB-IF( https://www.usb.org )管理。STM32自带的USB符合USB2.0规范,故2.0版本仍是本文的重点介绍对象。


       36.1.1 USB简介

       USB本身的知识体系非常复杂,本节只能作一点知识点的引入。本书篇幅有限,不可能在这里详细介绍,想更系统地学习USB的知识可以参考《圈圈教你玩USB》、塞普拉斯提供的《USB 101:通用串行总线2.0简介》等文献,下面我们一起来看USB的简单特性:

       l USB的硬件接口

       USB协议有漫长的发展历程,为的不同的场合和硬件功能而发展出不同的接口:Type-A、Type-B、Type-C,Type-C规范碰巧是跟着USB3.1的规范一起发布的。常见的接口类型列出如图36.1.1.1所示:


图36.1.1.1 常见的USB连接器的形状


       USB 发展到现在已经有 USB1.0/1.1/2.0/3.x/4等多个版本。目前用的最多的就是版本USB1.1和USB2.0,USB3.x/USB4目前也在加速推广。从图中可以发现不同的版本的USB接口内的引脚数量是有差异的。USB3.0以后为了提高速度,采用了更多数量的通讯线,比如同样的是Type A接口,USB2.0版本内部只有四根线,采用半双工式广播式通讯,USB3.0版本则将通讯线提高到了9根,并可以支持全双工非广播式的总线,允许两个单向数据管道分别处理一个单向通信。

       USB2.0常使用四根线:VCC(5V)、GND、D+(3.3V)和D-(3.3V) (注:五线模式多了一个DI脚用于支持OTG模式,OTG为USB主机+USB设备双重角色),其中数据线采用差分电压的方式进行数据传输。在USB主机上,D-和D+都是接了15K的电阻到地的,所以在没有设备接入的时候,D+、D-均是低电平。而在USB设备中,如果是高速设备,则会在D+上接一个1.5K的电阻到3.3V,而如果是低速设备,则会在D-上接一个1.5K的电阻到3.3V。这样当设备接入主机的时候,主机就可以判断是否有设备接入,并能判断设备是高速设备还是低速设备。

       关于USB硬件还有更多具体的细节规定,硬件设计时需要严格按照USB的器件的使用描述和USB标准所规定的参数来设计。

       l USB速度

       USB规范已经为USB系统定义了以下四种速度模式:低速(Low-Speed)、全速(Full-Speed)、高速(Hi-Speed)和超高速(SuperSpeedUSB)。接口的速度上限与设备支持的USB协议标准和导线长度、阻抗有关,不同协议版本对硬件的传输线数量、阻抗等要求各不相同,各个版本的能达到的理论速度上限对应如图36.1.1.2所示:


图36.1.1.2 USB协议发展与版本对应的速度


       USB端口和连接器有时会标上颜色,以指示USB规格及其支持的功能。这些颜色不是USB规范所要求的,并且在设备制造商之间不一致。例如,常见的支持USB3.0的U盘和电脑等设备使用蓝色指示,英特尔使用橙色指示充电端口等。

       l USB系统

       USB系统主要包括三个部分:控制器(Host Controller)、集线器(Hub)和USB设备。

       控制器(Host Controller),主机一般可以有一个或多个控制器,主要负责执行由控制器驱动程序发出的命令。控制器驱动程序(Host Controller Driver)在控制器与USB设备之间建立通信信道。

       集线器(Hub)连接到USB主机的根集线器,可用于拓展主机可访问的USB设备的数量。

       USB设备(USB Device)则是我们常用的如U盘,USB鼠标这类受主机控制的设备。

       l USB通讯

       USB针对主机、集线器和设备制定了严格的协议。概括来讲,通过检测、令牌、传输控制、数据传输等多种方式,定义了主机和从机在系统中的不同职能。USB系统通过“管道”进行通讯,有“控制管道”和“数据管道”两种,“控制管道”是双向的,而每个“数据管道”则是单向的,这种关系如图36.1.1.3所示:


图36.1.1.3 USB管道模型


       USB通讯中的检测和断开总是由主机发起。USB主机与设备首 次进行连接时会交换信息,这一过程叫“USB枚举”。枚举是设备和主机间进行的信息交换过程,包含用于识别设备的信息。此外,枚举过程主机需要分配设备地址、读取描述符(作为提供有关设备信息的数据结构),并分配和加载设备驱动程序,而从机需要提供相应的描述符使主机知悉如何操作此设备。整个过程需要数秒时间。完成该过程后设备才可以向主机传输数据。数据传输也有规定的三种类型,分别是:IN/读取/上行数据传输、OUT/写入/下行数据传输、控制数据传输。

       USB通过设备端点寻址,在主机和设备间实现信息交流。枚举发生前有一套专用的端点用于与设备进行通信。这些专用的端点统称为控制端点或端点0,有端点0 IN和端点0 OUT两个不同的端点,但对开发者来说,它们的构建和运行方式是一样的。每一个USB设备都需要支持端点0。因此,端点0不需要使用独立的描述符。除了端点0外,特定设备所支持的端点数量将由各自的设计要求决定。简单的设计(如鼠标)可能仅要一个IN端点。复杂的设计可能需要多个数据端点。

       USB规定的数据4种数据传输方式也是通过管道进行,分别是控制传输(Control Transfer)、中断传输(Interrupt Transfer)、批量传输或叫块传输(Bulk Transfer)、实时传输或叫同步传输(Isochronous Transfer ),每种模式规定了各自通讯时使用的管道类型。

       关于USB还有很多更详细的时序和要求,像USB描述符、VID/PID的规定、USB类设备和调试等,因为USB2.0和之后的版本有差异,这里就不再为大家列举了,ST对USB2.0也有专门的培训资料,这部分我们也放到“A盘→7,STM32参考资料→2,STM32 USB学习资料”中了,感兴趣的朋友自行去查阅更的USB的相关扩展知识,我们对USB的简介就到这里。


       36.1.2 STM32G4的USB特性

       USB发展到现在已经有USB1.0/1.1/2.0/3.0等多个版本。目前用的最多的就是USB1.1和USB2.0,USB3.0目前已经开始普及。STM32G474自带的USB符合USB2.0规范。

       标准USB共四根线组成,除VCC/GND外,另外为D+和D-,这两根数据线采用的是差分电压的方式进行数据传输的。在USB主机上,D-和D+都是接了15K的电阻到地的,所以在没有设备接入的时候,D+、D-均是低电平。而在USB设备中,如果是高速设备,则会在D+上接一个1.5K的电阻到VCC,而如果是低速设备,则会在D-上接一个1.5K的电阻到VCC。这样当设备接入主机的时候,主机就可以判断是否有设备接入,并能判断设备是高速设备还是低速设备。接下来,我们简单介绍一下STM32的USB控制器。

       STM32G474系列芯片自带有1个全速USB(USB FS),支持从机功能,其主要特性如下: 

       经USB-IF认证,符合通用串行总线规范第2.0版;

       可配置的端点数1~8个;

       拥有1024字节的专用数据包缓冲器(SRAM);

       循环冗余校验(CRC)的生成、检查;

       支持同步传输;

       支持双缓冲、同步端点;

       支持USB挂起、恢复;

       STM32G474的USB框图如图36.1.2.1所示:


图36.1.2.1 USB框图


       由上图可知,STM32G474通过APB总线访问USB,除此之外,它还支持USB电源传输控制器(UCPD),感兴趣的朋友可以去了解一下,我们这里不展开介绍。

       要正常使用STM32G474的USB,就得编写USB驱动,而整个USB通信的详细过程是很复杂的,本书篇幅有限,不可能在这里详细介绍,有兴趣的朋友可以去看看电脑圈圈的《圈圈教你玩USB》这本书,该书对USB通信有详细讲解。如果要我们自己编写USB驱动,那是一件相当困难的事情,尤其对于从没了解过USB的人来说,基本上不花个一两年时间学习,是没法搞定的。不过,ST提供了我们一个完整的USB驱动库,通过这个库,我们可以很方便的实现我们所要的功能,而不需要详细了解USB的整个驱动,大大缩短了我们的开发时间和精力。

STM32G4系列的USB例程全部是以HAL库的形式提供,为了简化开发设计,我们直接使用ST提供的HAL库版本USB驱动库来设计相关例程。

       ST提供的USB库和相关参考例程在STM32Cube_FW_G4_V1.5.1.zip里面可以找到,该文件可以在 www.st.com 网站搜索:cubeg4找到。不过,我们已经帮大家下载到开发板A盘:7,STM32参考资料→1,STM32CubeG4固件包 STM32Cube_FW_G4_V1.5.1.zip。解压可以得到STM32G4的Cube固件支持包:STM32Cube_FW_G4_V1.5.1,该文件夹里面包含了STM32G4的USB驱动库,如图36.1.2.2所示,并提供了4个例程供我们参考,如图36.1.2.3所示:


图36.1.2.2 ST提供的USB库(不包括USB PD)


图36.1.2.3 ST提供的USB例程(不包括USB PD)


       整个USB库的使用和例程说明,可以参考ST官方提供的:UM1734这个文档(在光盘:7,STM32参考资料→2,STM32 USB 学习资料 文件夹),这个文档详细介绍了USB库的各个组成部分以及所提供的例程使用方法(注意:仅供参考,某些芯片不一定覆盖所有功能),有兴趣学习USB的朋友,这个文档是必须仔细看的。

       这4个例程,虽然都是基于官方STM32G474E-EVAL板来实现,但它们可以很容易地移植到我们的电机开发板上的。

       在本实验中,我们移植的官方例程是:STM32Cube_FW_G4_V1.5.1\Projects\STM32G474E-EVAL\Applications\USB_Device\MSC_Standalone例程,从而实现USB读卡器功能。


        36.2 硬件设计


       1. 例程功能

       本实验代码,开机的时候先检测SPI FLASH是否存在,如果存在则获取其容量,并显示在LCD上面(如果不存在,则报错)。之后开始USB配置,在配置成功之后就可以在电脑上发现一个可移动磁盘。USB正在读写会在液晶上显示出来。

       LED0闪烁,提示程序运行。USB读写SPI FLASH时,LED1闪烁。


       2. 硬件资源


       1)LED灯

               LED0 – PE0

               LED1 – PE1

       2)串口1(PB6/PB7连接在板载USB转串口芯片CH340上面)

       3)正点原子1.3寸TFTLCD模块(SPI接口)

       4)USB_SLAVE接口(D-/D+连接在PA11/PA12上)

       5)SPI1(PB3/PB4/PB5/PB8)

       6)norflash(本例程使用的是W25Q128,连接在SPI1)


       3. 原理图

       开发板采用的是16PIN的Type-C接口,用来和电脑的USB相连接,Type-C接口与STM32的连接电路图,如下图所示:


图36.2.1 Type-C接口与STM32的连接电路图


       从上图可以看出,USB Type-C座是直接连接到STM32G474上面的,用户只需要用跳线帽将USB_D+连接到USB_DP,将USB_D-连接到USB_DM即可,硬件上不需要做其他操作。

       需要注意的是:DMG474电机开发板有两个Type-C座,本实验需要将USB线接至USB_SLAVE接口,不要连错!


        36.3 程序设计


       36.3.1 程序流程图


图36.3.1.1 USB读卡器(Slave)实验程序流程图


       36.3.2 程序解析

       这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。

       本实验,我们在SPI实验的基础上进行修改,USB的代码从ST官方例程移植:STM32Cube_FW_G4_V1.5.1\Projects\STM32G474E-EVAL\Applications\USB_Device\

MSC_Standalone。该目录下提供了多种开发环境的工程,我们使用的是MDK,打开MDK工程,然后就可以知道和USB相关的代码有哪些,如图36.3.2.1所示:


图36.3.2.1 ST官方例程USB相关代码


       有了这个官方例程做指引,我们就知道具体需要哪些文件,从而实现本章例程。

       首先,在工程的Middlewares文件夹下面,新建一个USB文件夹,并拷贝官方USB驱动库相关代码到该文件夹下,即拷贝:A盘→7,STM32参考资料 1,STM32CubeG4固件包 STM32Cube_FW_G4_V1.5.1 MiddlewaresàST文件夹下的STM32_USB_Device_Library文件夹。

       接下来,在USB文件夹下,新建一个USB_APP文件夹用于存放MSC实现相关代码,即:STM32Cube_FW_G4_V1.5.1àProjectsàSTM32G474E-EVALàApplicationsàUSB_Deviceà

       MSC_StandaloneàUSB_Device下所有.c和.h文件,最后USB_APP文件夹下的文件如图36.3.2.2所示:


图36.3.2.2 USB_APP代码


       之后,根据ST官方MSC例程,在我们SPI例程的基础上新建分组添加相关代码,具体细节请参考例程,这里就不详细介绍了,添加好之后,如图36.3.2.3所示:


图36.3.2.3 添加USB驱动、malloc等相关代码


       1. USB驱动代码

       这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。接下来我们看看USB_APP里面的几个.c文件:

       usbd_conf.c提供了USB设备库(从机库,下同)的回调及MSP初始化函数,当USB状态机处理完不同事务的时候,会调用这些回调函数,我们通过这些回调函数,就可以知道USB当前状态,比如:是否枚举成功了?是否连接上了?是否断开了?等,根据这些状态,用户应用程序可以执行不同操作,完成特定功能。usbd_conf.c我们重点介绍3个函数,首先是HAL_PCD_MspInit和USB_LP_IRQHandler函数,它们的定义如下:

/**
 * @brief       初始化PCD MSP
 * @param       pcdHandle:PCD结构体指针
 * @retval      无
 */
#if (USE_HAL_PCD_REGISTER_CALLBACKS == 1U)
static void HAL_PCD_MspInit(PCD_HandleTypeDef* pcdHandle)
#else
void HAL_PCD_MspInit(PCD_HandleTypeDef* pcdHandle)
#endif
{
    if(pcdHandle->Instance == USB)
    {
        __HAL_RCC_USB_CLK_ENABLE();
        __HAL_RCC_GPIOA_CLK_ENABLE();                        /* 使能GPIOA时钟 */
 
        HAL_NVIC_SetPriority(USB_LP_IRQn, 6, 0);
        HAL_NVIC_EnableIRQ(USB_LP_IRQn);
    }
}
 
/**
 * @brief       USB 中断服务函数
 * @note        处理USB中断
 * @param       无
 * @retval      无
 */
void USB_LP_IRQHandler(void)
{
    HAL_PCD_IRQHandler(&hpcd_USB_FS);
}

       HAL_PCD_MspInit函数,用于使能USB时钟,设置中断等。该函数在HAL_PCD_Init函数里面被调用。

       USB_LP_IRQHandler函数,是USB的中断服务函数,通过调用HAL_PCD_IRQHandler函数,实现对USB各种事务的处理。

       下面介绍的USBD底层初始化函数,其定义如下:

/**
 * @brief  USBD 底层初始化函数
 * @param  pdev    : USBD句柄指针
 * @retval  USB状态
 *   @arg  USBD_OK(0)   , 正常;
 *   @arg  USBD_BUSY(1) , 忙;
 *   @arg  USBD_FAIL(2) , 失败;
 */
USBD_StatusTypeDef USBD_LL_Init(USBD_HandleTypeDef *pdev)
{
    hpcd_USB_FS.pData = pdev;  /* g_pcd_usb_otg_fs的pData指向pdev */
    pdev->pData = &hpcd_USB_FS;  /* pdev的pData指向g_pcd_usb_otg_fs */
 
    hpcd_USB_FS.Instance = USB;        /* 使用USB FS */
    hpcd_USB_FS.Init.dev_endpoints = 8;      /* 端点数为8 */
    hpcd_USB_FS.Init.speed = PCD_SPEED_FULL;                  /* USB全速(12Mbps) */
    hpcd_USB_FS.Init.phy_itface = PCD_PHY_EMBEDDED;   /* 使用内部PHY */
    hpcd_USB_FS.Init.Sof_enable = DISABLE;     /* 不使能SOF中断 */
    hpcd_USB_FS.Init.low_power_enable = DISABLE;   /* 不使能低功耗模式 */
    hpcd_USB_FS.Init.lpm_enable = DISABLE;     /* 不使能连接电源管理 */
    hpcd_USB_FS.Init.battery_charging_enable = DISABLE;
 
    HAL_PCD_Init(&hpcd_USB_FS);
 
    HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData,0x00,PCD_SNG_BUF,0x10);
    HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData,0x80,PCD_SNG_BUF,0x50);
HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData, 
MSC_EPIN_ADDR, PCD_SNG_BUF, 0x90);
HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData, 
MSC_EPOUT_ADDR, PCD_SNG_BUF, 0xD0);
    return USBD_OK;
}

       USBD_LL_Init函数,用于初始化USB底层各种设置,比如,使用内部PHY,使用全速模式,不使能低功耗模式等,详见以上代码。该函数在USBD_Init函数里面被调用。

       usbd_desc.c提供了USB设备类的描述符,直接决定了USB设备的类型、端点、接口、字符串、制造商等重要信息。这个里面的内容,我们一般不用修改,直接用官方的即可。

       usbd_storage_if.c提供一些磁盘操作函数,包括支持的磁盘个数,以及每个磁盘的初始化和读写等函数。本章我们设置了1个磁盘:SPI FLASH。usb_storage.c我们重点介绍3个函数,首先是初始化存储设备函数,其定义如下:

/**
 * @brief       初始化存储设备
 * @param       lun        : 逻辑单元编号
 * @arg                     0, SPI FLASH
 * @retval      操作结果
 * @arg         0    , 成功
 * @arg         其他 , 错误代码
 */
int8_t STORAGE_Init_FS(uint8_t lun)
{
    uint8_t res = 0;
 
    switch (lun)
    {
        case 0: 
            norflash_init();
            break;
    }
    return (res);
}

       STORAGE_Init函数,用于初始化存储设备,我们这里只需要初始化NOR FLASH。

       下面要介绍的是从存储设备读取数据函数。其定义如下:

/**
 * @brief       从存储设备读取数据
 * @param       lun        : 逻辑单元编号
 * @arg                    0, SPI FLASH
 * @param       buf        : 数据存储区首地址指针
 * @param       blk_addr   : 要读取的地址(扇区地址)
 * @param       blk_len    : 要读取的块数(扇区数)
 * @retval      操作结果
 * @arg         0    , 成功
 * @arg         其他 , 错误代码
 */
int8_t STORAGE_Read_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, 
uint16_t blk_len)
{
    int8_t res = 0;
    g_usb_state_reg |= 0X02;  /* 标记正在读数据 */
    switch (lun)
    {
        case 0:      /* SPI FLASH */
            norflash_read(buf, USB_STORAGE_FLASH_BASE+blk_addr*512, blk_len*512);
            break;
    }
 
    if (res)
    {
        printf("rerr:%d,%d", lun, res);
        g_usb_state_reg |= 0X08;  /* 读错误! */
    }
    return (USBD_OK);
}

       STORAGE_Read_FS函数,用于从存储设备读取数据,同样是根据存储设备(lun)的不同,调用不同的读取函数,完成数据读取。

       下面要介绍的是向存储设备写数据函数。其定义如下:

/**
 * @brief       向存储设备写数据
 * @param       lun        : 逻辑单元编号
 *   @arg                  0, SPI FLASH
 * @param       buf        : 数据存储区首地址指针
 * @param       blk_addr   : 要写入的地址(扇区地址)
 * @param       blk_len    : 要写入的块数(扇区数)
 * @retval      操作结果
 *   @arg       0    , 成功
 *   @arg       其他 , 错误代码
 */
int8_t STORAGE_Write_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, 
uint16_t blk_len)
{
    int8_t res = 0;
    g_usb_state_reg |= 0X01;   /* 标记正在写数据 */
 
    switch (lun)
    {
        case 0:       /* SPI FLASH */
            norflash_write(buf,USB_STORAGE_FLASH_BASE+blk_addr*512,blk_len*512);
            break;
    }
 
    if (res)
    {
        g_usb_state_reg |= 0X04;   /* 写错误! */
        printf("werr:%d,%d", lun, res);
    }
    return res;
}

       STORAGE_Write_FS函数,用于往存储设备写入数据,也是根据存储设备(lun)的不同,调用不同的写入函数(G474电机开发板只有NOR FLASH),完成数据写入。

       以上3个.c文件和对应.h文件的详细代码和修改方法,我们就不详细介绍了,请大家参考光盘本例程源码。

       下面提两个重点地方讲解下:

       1、通过修改usbd_conf.h里面的MSC_MEDIA_PACKET定义值大小,可以一定程度提高USB读写速度(越大越快),本例程我们设置32*1024,也就是32KB大小。另外,我们通过修改:STORAGE_LUN_NBR宏定义的值为1,可以支持2个磁盘(电机开发板只有1个磁盘)。

       2、官方例程在2个或以上磁盘支持的时候,存在bug,我们需要修改usbd_msc.h里面USBD_MSC_BOT_HandleTypeDef结构体的scsi_blk_nbr参数,将其改为数组形式:uint32_t scsi_blk_nbr[STORAGE_LUN_NBR];数组大小由STORAGE_LUN_NBR指定,本实验我们定义的是1,因此可以支持最多2个磁盘,修改STORAGE_LUN_NBR的大小,即可修改支持的最大磁盘个数。修改该参数后,相应的有一些函数要做修改,请大家参考本例程源码。

       以上两点,就是我们移植的时候需要特别注意的,其他我们就不详细介绍了(USB相关源码解释,请参考:UM1734(STM32Cube USB device library).pdf这个文档)。


       2. main.c代码

       下面是main.c的程序,具体如下:

extern volatile uint8_t g_usb_state_reg; /* USB状态 */
extern volatile uint8_t g_device_state; /* USB连接 情况 */
 
int main(void)
{
    uint8_t offline_cnt = 0;
    uint8_t tct = 0;
    uint8_t usb_sta;
    uint8_t device_sta;
    uint16_t id;
    
    HAL_Init();        /* 初始化HAL库 */
    sys_stm32_clock_init(85, 2, 2, 4, 8); /* 设置时钟,170Mhz */
    delay_init(170);       /* 延时初始化 */
    usart_init(115200);      /* 串口初始化为115200 */
    led_init();        /* 初始化LED */
    lcd_init();        /* 初始化LCD */
    key_init();        /* 初始化按键 */
    norflash_init();       /* 初始化nor flash */
    
    my_mem_init(SRAMIN);     /* 初始化内部SRAM内存池 */
    my_mem_init(SRAMCCM);     /* 初始化内部SRAMCCM内存池 */
    /* 显示提示信息 */
    lcd_show_string(10, 10, 140, 32, 32, "STM32", RED);
    lcd_show_string(10, 42, 140, 24, 24, "USB Card Reader TEST", RED);
    lcd_show_string(10, 66, 140, 24, 24, "ATOM@ALIENTEK", RED);
    
    id = norflash_read_id();
    
    if ((id == 0) || (id == 0XFFFF))
{
/* 检测NorFlash错误 */
        lcd_show_string(10, 90, 200, 16, 16, "NorFlash Error!", RED);          
    }
    else   /* SPI FLASH 正常 */
    {
        lcd_show_string(10, 110, 200, 16, 16, "SPI FLASH Size:12MB", RED);
    }
    /* 提示正在建立连接 */
    lcd_show_string(10, 130, 200, 16, 16, "USB Connecting...", RED);            
    MX_USB_Device_Init();     /*  USB初始化 */
    
    while (1)
    {
        delay_ms(1);
 
        if (usb_sta != g_usb_state_reg)  /* 状态改变了 */
        {
            lcd_fill(10, 150, 239, 150 + 16, WHITE);  /* 清除显示 */
 
            if (g_usb_state_reg & 0x01)  /* 正在写 */
            {
                LED1(0);
/* 提示USB正在写入数据 */
                lcd_show_string(10, 150, 200, 16, 16, "USB Writing...", RED); 
            }
 
            if (g_usb_state_reg & 0x02)  /* 正在读 */
            {
                LED1(0);
/* 提示USB正在读出数据 */
                lcd_show_string(10, 150, 200, 16, 16, "USB Reading...", RED); 
            }
 
            if (g_usb_state_reg & 0x04)
            {
/* 提示写入错误 */
                lcd_show_string(10, 170, 200, 16, 16, "USB Write Err ", RED); 
            }
            else
            {
                lcd_fill(10, 170, 239, 170 + 16, WHITE);  /* 清除显示 */
            }
            
            if (g_usb_state_reg & 0x08)
            {
/* 提示读出错误 */
                lcd_show_string(10, 190, 200, 16, 16, "USB Read  Err ", RED); 
            }
            else
            {
                lcd_fill(10, 190, 239, 190 + 16, WHITE); /* 清除显示 */
            }
            
            usb_sta = g_usb_state_reg;     /* 记录最后的状态 */
        }
 
        if (device_sta != g_device_state)
        {
            if (g_device_state == 1)
            {
/* 提示USB连接已经建立 */
                lcd_show_string(10, 130, 200, 16, 16, "USB Connected    ", RED); 
            }
            else
            {
/* 提示USB被拔出了 */
                lcd_show_string(10, 130, 200, 16, 16, "USB DisConnected ", RED); 
            }
            
            device_sta = g_device_state;
        }
 
        tct++;
 
        if (tct == 200)
        {
            tct = 0;
            LED1(1);      /* 关闭 LED1 */
            LED0_TOGGLE();    /* LED0 闪烁 */
 
            if (g_usb_state_reg & 0x10)
            {
                offline_cnt = 0;   /* USB连接了,则清除offline计数器 */
                g_device_state = 1;
            }
            else       /* 没有得到轮询 */
            {
                offline_cnt++;
 
                if (offline_cnt > 10)
                {
                    g_device_state = 0; /* 2s内没收到在线标记,代表USB被拔出了 */
                }
            }
            g_usb_state_reg = 0;
        }
    }
}

       在main函数中,我们先进行一系列的初始化和显示提示信息,然后检查NORFLASH是否正常,并调用MX_USB_Device_Init函数初始化USB,最后在循环里面处理USB的通断和状态处理事务。


        36.4 下载验证

       将程序下载到开发板后,在USB配置成功后(注意:USB数据线,要插在USB_SLAVE口!),如图36.4.1所示:


图36.4.1 USB连接成功显示界面


       此时,电脑提示发现新硬件,并开始自动安装驱动,如图36.4.2所示:


图36.4.2 USB读卡器被电脑找到


       USB配置成功后,LED1熄灭,LED0闪烁,在电脑上可以看到磁盘,如图36.4.3所示:


图36.4.3 电脑找到USB读卡器的盘符


       我们打开设备管理器,在通用串行总线控制器里面可以发现多出了一个USB 大容量存储设备,同时看到磁盘驱动器里面多了一个磁盘,如图36.4.4所示:


图36.4.4 通过设备管理器查看磁盘驱动器


       此时,我们就可以通过电脑读写SPI FLASH里面的内容了。在执行读写操作的时候,就可以看到LED1亮,并且会在液晶上显示当前的读写状态。

       注意,在对SPI FLASH操作的时候,最好不要频繁的往里面写数据,否则很容易将SPI FLASH写爆!


请使用浏览器的分享功能分享到微信等