OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 2503|回复: 0

《STM32F407 探索者开发指南》第六十三章 USB读卡器(Slave)实验

[复制链接]

1140

主题

1152

帖子

2

精华

超级版主

Rank: 8Rank: 8

积分
4895
金钱
4895
注册时间
2019-5-8
在线时间
1248 小时
发表于 2023-9-21 17:33:25 | 显示全部楼层 |阅读模式
本帖最后由 正点原子运营 于 2023-9-20 16:33 编辑

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

1)实验平台:正点原子探索者STM32F407开发板

2) 章节摘自【正点原子】STM32F407开发指南 V1.1


4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/stm32/zdyz_stm32f407_explorerV3.html

5)正点原子官方B站:https://space.bilibili.com/394620890

6)STM32技术交流QQ群:151941872

155537c2odj87vz1z9vj6l.jpg

155537nfqovl2gg9faaol9.png

STM32F407系列芯片都自带了USB OTG FS和USB OTG HS(HS需要外扩高速PHY芯片实现,速度可达480Mbps),支持USB Host和USB Device,STM32F407电机开发板没有外扩高速PHY芯片,所以仅支持USB OTG FS(FS,即全速,12Mbps),所有USB相关例程,均使用USB OTG FS实现。
下面,我们将介绍如何使用USB OTG FS在探索者STM32F407开发板上实现一个USB读卡器。
本章分为如下几个小节:
63.1 USB简介
63.2 硬件设计
63.3 程序设计
63.4 下载验证

63.1 USB简介
USB,即通用串行总线(UniversalSerial Bus),包括USB协议和USB硬件两个方面,支持热插拔功能。现在日常生活的很多方面都离不开USB的应用,如充电和数据传输等方面的应用。
USB经过多次修改,1996年确定了初始规范版本USB1.0,目前由非盈利组织USB-IF(https://www.usb.org)管理。STM32自带的USB符合USB2.0规范,故2.0版本仍是本文的重点介绍对象。

63.1.1 USB简介
USB本身的知识体系非常复杂,本节只能作一点知识点的引入。本书篇幅有限,不可能在这里详细介绍,想更系统地学习USB的知识可以参考《圈圈教你玩USB》、塞普拉斯提供的《USB101:通用串行总线 2.0 简介》等文献,下面我们一起来看USB的简单特性:
l  USB的硬件接口
USB协议有漫长的发展历程,为的不同的场合和硬件功能而发展出不同的接口:Type-A、Type-B、Type-C,Type-C规范碰巧是跟着USB3.1的规范一起发布的。常见的接口类型列出如图63.1.1所示。                                 
image001.png
图63.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协议标准和导线长度、阻抗有关,不同协议版本对硬件的传输线数量、阻抗等要求各不相同,各个版本的能达到的理论速度上限对应如图63.1.2。     
image003.png
图63.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系统通过“管道”进行通讯,有“控制管道”和“数据管道”两种,“控制管道”是双向的,而每个“数据管道”则是单向的,这种关系如图39.1.3所示。
image006.png
图63.1.3 USB管道模型

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

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

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

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

63.1.2 STM32F407的USB特性
USB发展到现在已经有USB1.0/1.1/2.0/3.0等多个版本。目前用的最多的就是USB1.1和USB2.0,USB3.0目前已经开始普及。STM32F407自带的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控制器。

STM32F407系列芯片自带有2个USB OTG,高速USB(USB OTG HS);全速USB(USB OTG FS),高速USB(HS)需要外扩高速PHY芯片实现,我们这里不做介绍。

STM32F407的USB OTG FS是一款双角色设备 (DRD) 控制器,同时支持从机功能和主机功能,完全符合USB 2.0规范的On-The-Go补充标准。此外,该控制器也可配置为“仅主机”模式或“仅从机”模式,完全符合USB 2.0规范。在主机模式下,OTG FS支持全速(FS,12 Mb/s)和低速(LS,1.5 Mb/s)收发器,而从机模式下则仅支持全速(FS,12 Mb/s)收发器。OTG FS同时支持HNP和SRP。

STM32F407的USB OTG FS主要特性可分为三类:通用特性、主机模式特性和从机模式特性。

1、通用特性
Ø 经USB-IF认证,符合通用串行总线规范第2.0版

Ø 集成全速PHY,且完全支持定义在标准规范OTG补充第1.3版中的OTG协议
1,支持A-B器件识别(ID线)
2,支持主机协商协议(HNP)和会话请求协议(SRP)
3,允许主机关闭VBUS以在OTG应用中节省电池电量
4,支持通过内部比较器对VBUS电平采取监控
5,支持主机到从机的角色动态切换

Ø 可通过软件配置为以下角色:
1,  具有SRP功能的USB FS从机(B器件)
2,  具有SRP功能的USB FS/LS主机(A器件)
3,  USB On-The-Go全速双角色设备

Ø 支持FS SOF和LS Keep-alive令牌
1,SOF脉冲可通过PAD输出
2,SOF脉冲从内部连接到定时器 2 (TIM2)
3,可配置的帧周期
4,可配置的帧结束中断

Ø 具有省电功能,例如在USB挂起期间停止系统、关闭数字模块时钟、对PHY和DFIFO电源加以管理

Ø 具有采用高级FIFO控制的1.25KB专用RAM
1,可将 RAM 空间划分为不同FIFO,以便灵活有效地使用RAM
2,每个FIFO可存储多个数据包
3,动态分配存储区
4,FIFO大小可配置为非2的幂次方值,以便连续使用存储单元

Ø 一帧之内可以无需要应用程序干预,以达到最大 USB 带宽

2、主机(Host)模式特性
Ø 通过外部电荷泵生成VBUS电压。

Ø 多达8个主机通道(管道):每个通道都可以动态实现重新配置,可支持任何类型的USB 传输。

Ø 内置硬件调度器:
1,在周期性硬件队列中存储多达16个中断加同步传输请求
2,在非周期性硬件队列中存储多达16个控制加批量传输请求

Ø 管理一个共享RX FIFO、一个周期性TX FIFO和一个非周期性TX FIFO,以有效使用USB数据RAM。

3、从机(Slave/Device)模式特性
Ø 1个双向控制端点0

Ø 8个IN 端点 (EP),可配置为支持批量传输、中断传输或同步传输

Ø 8个OUT 端点(EP),可配置为支持批量传输、中断传输或同步传输

Ø 管理一个共享Rx FIFO和一个Tx-OUT FIFO,以高效使用USB数据RAM

Ø 管理多达9个专用Tx-INFIFO(分别用于每个使能的IN EP),降低应用程序负荷支持软断开功能。

STM32F407 USB OTG FS框图如图63.1.1所示:     
image007.png
图63.1.1 USB OTG FS框图

对于USB OTG FS功能模块,STM32F407通过AHB总线访问(AHB频率必须大于14.2Mhz),另外,USB OTG的内核时钟必须是48Mhz,是来自时钟树里面的PLL48CK(和SDIO共用)。

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

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

ST提供的F4 USBOTG库和相关参考例程在en.stm32cubef4_v1-26-0_v1.26.0.zip里面可以找到,该文件可以在www.st.com网站搜索:cubef4找到。不过,我们已经帮大家下载到开发板光盘:8,STM32参考资料à1,STM32CubeF4固件包àen.stm32cubef4_v1-26-0_v1.26.0.zip。解压可以得到STM32F4的Cube固件支持包:STM32Cube_FW_F4_V1.26.0,该文件夹里面包含了F4的USB 主机(Host)和从机(Device)驱动库,如图63.1.2所示,并提供了17个例程供我们参考,如图63.1.3所示:     
image009.png
图63.1.2 ST提供的USB OTG库

image011.png
图63.1.3 ST提供的USB OTG例程

标号1是F4 USB从机驱动库;

标号2是F4 USB主机驱动库;

标号3是F4 USB从机例程(共7个);

标号4是F4 USB主机例程(共10个);

整个USBOTG库的使用和例程说明,可以参考ST官方提供的:UM1734(从机)和UM1720(主机)这两个文档(在光盘:8,STM32参考资料à2,STM32USB 学习资料 文件夹),这两个文档详细介绍了USBOTG库的各个组成部分以及所提供的例程使用方法,有兴趣学习USB的朋友,这个文档是必须仔细看的。

这17个例程,虽然都是基于官方STM324xG_EVAL板,但是很容易移植到我们的电机开发板上。

本实验我们移植:STM32Cube_FW_F4_V1.26.0\Projects\STM324xG_EVAL\Applications\USB_Device\MSC_Standalone这个例程,以实现USB读卡器功能。

63.2 硬件设计
1. 例程功能
本实验代码,开机的时候先检测SD卡、SPI FLASH是否存在,如果存在则获取其容量,并显示在LCD上面(如果不存在,则报错)。之后开始USB配置,在配置成功之后就可以在电脑上发现一个可移动磁盘。USB正在读写会在液晶上显示出来。
LED0闪烁,提示程序运行。USB读写文件时,LED1闪烁。

2. 硬件资源
1)LED灯
       LED0 – PF9
       LED1 – PF10
2)串口1(PA9/PPA10连接在板载USB转串口芯片CH340上面)
3)正点原子2.8/3.5/4.3/7寸TFTLCD模块(仅限MCU屏,16位8080并口驱动)
4)USB_SLAVE接口(D-/D+连接在PA11/PA12上)
5)外部SRAM芯片,通过FSMC驱动
6)microSD Card(通过SDIO连接)
7)norflash(本例程使用的是W25Q128,连接在SPI1)

3. 原理图
我们主要介绍USB SLAVE原理图:开发板采用的是16PIN的Type-C接口,用来和电脑的USB相连接,Type-C接口与STM32的连接电路图,如下图所示:     
image013.png
图63.2.1 Type-C接口与STM32的连接电路图

从上图可以看出,USB Type-C座是通过跳线帽连连接到STM32F407上面的,所以硬件需要我们通过跳线帽连接PA11和D-以及PA12和D+,跳线帽连接如下图所示。
image015.png
图63.2.2 USB SLAVE跳线接口

需要注意的是:这个Type-C座和USB母座(USB_HOST)是共用D+和D-的,所以他们不能同时使用。这个在使用的时候,要特别注意!!本实验测试时,USB_HOST不能插入任何USB设备!

63.3 程序设计
63.3.1 程序流程图
QQ截图20230920163240.png
图63.3.1.1 USB读卡器(Slave)实验程序流程图

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

本实验,我们在:实验25 SPI实验的基础上修改,USB的代码从ST官方例程移植:STM32Cube_FW_F4_V1.26.0\Projects\STM324xG_EVAL\Applications\USB_Device\MSC_Standalone。该目录下提供了三种开发环境的工程:IAR、MDK和SW4STM32,我们使用的是MDK,打开MDK工程,然后就可以知道和USB相关的代码有哪些,如图63.3.2.1所示:     
image019.png
图63.3.2.1 ST官方例程USB相关代码

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

首先,在工程的Middlewares文件夹下面,新建一个USB文件夹,并拷贝官方USB驱动库相关代码到该文件夹下,即拷贝:光盘à 8,STM32参考资料à1,STM32CubeF4固件包àSTM32Cube_FW_F4_V1.26.0àMiddlewaresàST文件夹下的: STM32_USB_Device_Library、STM32_USB_HOST_Library两个文件夹及源码拷贝到该文件夹下面。

然后,在USB文件夹下,新建一个USB_APP文件夹用于存放MSC实现相关代码,即:STM32Cube_FW_F4_V1.26.0àProjectsàSTM324xG_EVALàApplicationsàUSB_DeviceàMSC_StandaloneàSrc下的部分代码:usbd_conf.c、usbd_storage.c和usbd_desc.c等3个.c文件,同时拷贝:STM32Cube_FW_F4_V1.26.0à Projects à STM324xG_EVAL à Applicationsà USB_
DeviceàMSC_StandaloneàInc下面的:usbd_conf.h、usbd_storage.h和usbd_desc.h等三个文件到USB_APP文件夹下。最后USB_APP文件夹下的文件如图63.3.2.2所示:     
image021.png
图63.3.2.2 USB_APP代码

之后,根据ST官方MSC例程,在我们本章例程的基础上新建分组添加相关代码,具体细节请参考例程,这里就不详细介绍了,添加好之后,如图63.3.2.3所示:     
image023.png
图63.3.2.3 添加USB驱动、malloc等相关代码

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

usbd_conf.c提供了USB设备库(从机库,下同)的回调及MSP初始化函数,当USB状态机处理完不同事务的时候,会调用这些回调函数,我们通过这些回调函数,就可以知道USB当前状态,比如:是否枚举成功了?是否连接上了?是否断开了?等,根据这些状态,用户应用程序可以执行不同操作,完成特定功能。usbd_conf.c我们重点介绍3个函数,首先是HAL_PCD_MspInit和OTG_FS_IRQHandler函数,它们的定义如下:
  1. /**
  2. *@brief        初始化PCD MSP
  3. *@param        hpcd : PCD结构体指针
  4. *@retval       无
  5. */
  6. voidHAL_PCD_MspInit(PCD_HandleTypeDef*hpcd)
  7. {
  8.    GPIO_InitTypeDef gpio_init_struct = {0};
  9.     if (hpcd->Instance == USB_OTG_FS)
  10.     {
  11.        __HAL_RCC_GPIOA_CLK_ENABLE();                          /* 使能GPIOA时钟 */
  12.        gpio_init_struct.Pin = GPIO_PIN_11 | GPIO_PIN_12;   /* 初始化GPIO口 */
  13.        gpio_init_struct.Mode = GPIO_MODE_AF_PP;             /* 复用 */
  14.        gpio_init_struct.Pull = GPIO_NOPULL;                   /* 浮空 */
  15.        gpio_init_struct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; /* 高速 */
  16.         gpio_init_struct.Alternate = GPIO_AF10_OTG_FS;       /* 复用为OTG1_FS */
  17.        HAL_GPIO_Init(GPIOA, & gpio_init_struct);    /* 初始化PA11和PA12引脚 */
  18.        __HAL_RCC_USB_OTG_FS_CLK_ENABLE();            /* 使能OTG FS时钟 */
  19.        /* 优先级设置为抢占1,子优先级0 */
  20.        HAL_NVIC_SetPriority(OTG_FS_IRQn, 1, 0);
  21.        HAL_NVIC_EnableIRQ(OTG_FS_IRQn);              /* 使能OTG FS中断 */
  22.     }
  23.     else if (hpcd->Instance == USB_OTG_HS)
  24.     {
  25.        /* USB OTG HS本例程没用到,故不做处理 */
  26.     }
  27. }
  28. /**
  29. *@brief        USB OTG 中断服务函数
  30. *  @note       处理所有USB中断
  31. *@param        无
  32. *@retval       无
  33. */
  34. voidOTG_FS_IRQHandler(void)
  35. {
  36.    HAL_PCD_IRQHandler(&g_pcd_usb_otg_fs);
  37. }
复制代码
HAL_PCD_MspInit函数,用于使能USB时钟,初始化IO口,设置中断等。该函数在HAL_PCD_Init函数里面被调用。

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

下面介绍的USBD底层初始化函数,其定义如下:
  1. /**
  2. *@brief        USBD 底层初始化函数
  3. *@param        pdev    : USBD句柄指针
  4. *@retval       USB状态
  5. *  @arg        USBD_OK(0)   , 正常;
  6. *  @arg        USBD_BUSY(1) , 忙;
  7. *  @arg        USBD_FAIL(2) , 失败;
  8. */
  9. USBD_StatusTypeDef USBD_LL_Init(USBD_HandleTypeDef *pdev)
  10. {
  11.     if (pdev->id == DEVICE_FS)
  12.     {
  13.        g_pcd_usb_otg_fs.pData = pdev;   /* g_pcd_usb_otg_fs的pData指向pdev */
  14.        pdev->pData = &g_pcd_usb_otg_fs; /* pdev的pData指向g_pcd_usb_otg_fs*/
  15.        g_pcd_usb_otg_fs.Instance = USB_OTG_FS;           /* 使用USB OTG FS */
  16.        g_pcd_usb_otg_fs.Init.dev_endpoints = 4;         /* 端点数为4 */
  17.        g_pcd_usb_otg_fs.Init.speed = PCD_SPEED_FULL;    /* USB全速(12Mbps) */
  18.        g_pcd_usb_otg_fs.Init.dma_enable = DISABLE;      /* 不使能DMA */
  19.        g_pcd_usb_otg_fs.Init.phy_itface = PCD_PHY_EMBEDDED; /* 使用内部PHY */
  20.        g_pcd_usb_otg_fs.Init.Sof_enable = DISABLE;            /* 不使能SOF中断 */
  21.        g_pcd_usb_otg_fs.Init.low_power_enable = DISABLE;     /* 不使能低功耗模式 */
  22.        g_pcd_usb_otg_fs.Init.lpm_enable = DISABLE;            /* 不使能电源管理 */
  23.        g_pcd_usb_otg_fs.Init.vbus_sensing_enable = DISABLE;  /* 不使能VBUS检测 */
  24.        g_pcd_usb_otg_fs.Init.use_dedicated_ep1 = DISABLE;
  25.                                                 /* 禁止EP1dedicated中断 */
  26.        HAL_PCD_Init(&g_pcd_usb_otg_fs);
  27.        /* 设置接收FIFO大小为0x80(128字节) */
  28.        HAL_PCDEx_SetRxFiFo(&g_pcd_usb_otg_fs, 0x80);
  29.        /* 设置发送FIFO 0的大小为0x40(64字节) */
  30.        HAL_PCDEx_SetTxFiFo(&g_pcd_usb_otg_fs, 0, 0x40);
  31.        /* 设置发送FIFO 1的大小为0x80(128字节) */
  32.        HAL_PCDEx_SetTxFiFo(&g_pcd_usb_otg_fs, 1, 0x80);
  33.     }
  34.     return USBD_OK;
  35. }
复制代码
USBD_LL_Init函数,用于初始化USB底层设置,因为我们定义的是:USE_USB_FS,因此会设置USB OTG使用USB_OTG_FS,然后完成各种设置,比如,使用内部PHY,使用全速模式,不使能VBUS检测等,详见以上代码。该函数在USBD_Init函数里面被调用。

usbd_desc.c提供了USB设备类的描述符,直接决定了USB设备的类型、端点、接口、字符串、制造商等重要信息。这个里面的内容,我们一般不用修改,直接用官方的即可。注意,这里:usbd_desc.c里面的:usbd即device类,同样:usbh即host类,所以通过文件名我们就可以很容易区分该文件是用在device还是host,而只有usb字样的那就是device和host可以共用的。

usbd_storage.c提供一些磁盘操作函数,包括支持的磁盘个数,以及每个磁盘的初始化和读写等函数。本章我们设置了1个磁盘:SPI FLASH。usb_storage.c我们重点介绍3个函数,首先是初始化存储设备函数,其定义如下:
  1. /**
  2. *@brief        初始化存储设备
  3. *@param        lun        : 逻辑单元编号
  4. *  @arg                    0, SPI FLASH
  5. *   @arg                    1, SD 卡
  6. *@retval       操作结果
  7. *  @arg        0    , 成功
  8. *  @arg        其他 , 错误代码
  9. */
  10. int8_t STORAGE_Init_FS(uint8_t lun)
  11. {
  12.     uint8_t res;
  13.     switch(lun)
  14.     {
  15.        case 0:      /* SPI FLASH*/
  16.            norflash_init();
  17.            res = USBD_OK;
  18.            break;
  19.        case 1:       /* SD卡 */
  20.            norflash_init();
  21.            res = USBD_OK;
  22.            break;
  23.        default :
  24.            res = USBD_FAIL;
  25.     }
  26.    
  27.     return res;
  28. }
复制代码
STORAGE_Init_FS函数,用于初始化存储设备,我们定义了两个存储设备:SPI FLASH和SD卡,因此需要根据输入参数(lun),执行不同存储设备的初始化。

下面要介绍的是从存储设备读取数据函数。其定义如下:
  1. /**
  2. *@brief        从存储设备读取数据
  3. *@param        lun         : 逻辑单元编号
  4. *  @arg                      0, SD卡
  5. *  @arg                      1, SPI FLASH
  6. *@param        buf         : 数据存储区首地址指针
  7. *@param        blk_addr   : 要读取的地址(扇区地址)
  8. *@param        blk_len    : 要读取的块数(扇区数)
  9. *@retval       操作结果
  10. *  @arg        0    , 成功
  11. *  @arg        其他 , 错误代码
  12. */
  13. int8_t STORAGE_Read_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr,
  14.   uint16_t blk_len)
  15. {
  16.     int8_t res = 0;
  17.    g_usb_state_reg |= 0X02;    /* 标记正在读数据 */
  18.     switch (lun)
  19.     {
  20.        case 0:  /* SPI FLASH */
  21.            norflash_read(buf, USB_STORAGE_FLASH_BASE + blk_addr * 512,
  22.                              blk_len * 512);
  23.            break;
  24.        case 1:  /* SD卡 */
  25.            res = sd_read_disk(buf, blk_addr, blk_len);
  26.            break;
  27.     }
  28.     if (res)
  29.     {
  30.        printf("rerr:%d,%d", lun, res);
  31.        g_usb_state_reg |= 0X08;    /* 读错误! */
  32.     }
  33.     return res;
  34. }
复制代码
STORAGE_Read_FS函数,用于从存储设备读取数据,同样是根据存储设备(lun)的不同,调用不同的读取函数,完成数据读取。

下面要介绍的是向存储设备写数据函数。其定义如下:
  1. /**
  2. *@brief        向存储设备写数据
  3. *@param        lun       : 逻辑单元编号
  4. *  @arg                   0, SD卡
  5. *  @arg                   1, SPI FLASH
  6. *@param        buf       : 数据存储区首地址指针
  7. *@param        blk_addr : 要写入的地址(扇区地址)
  8. *@param        blk_len  : 要写入的块数(扇区数)
  9. *@retval       操作结果
  10. *  @arg        0    , 成功
  11. *   @arg       其他 , 错误代码
  12. */
  13. int8_t STORAGE_Write_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr,
  14.    uint16_t blk_len)
  15. {
  16.     int8_t res = 0;
  17.    g_usb_state_reg |= 0X01;    /* 标记正在写数据 */
  18.     switch (lun)
  19.     {
  20.        case 0:  /* SPI FLASH */
  21.            norflash_write(buf,USB_STORAGE_FLASH_BASE + blk_addr * 512,
  22. blk_len * 512);
  23.            break;
  24.        case 1:  /* SD卡 */
  25.            res =sd_write_disk(buf, blk_addr, blk_len);
  26.            break;
  27.     }
  28.     if (res)
  29.     {
  30.        g_usb_state_reg |= 0X04;    /* 写错误! */
  31.        printf("werr:%d,%d", lun, res);
  32.     }
  33.     return res;
  34. }
复制代码
STORAGE_Write_FS函数,用于往存储设备写入数据,也是根据存储设备(lun)的不同,调用不同的写入函数,完成数据写入。

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

下面提几个重点地方讲解下:
1、要使用USB OTG FS,必须在MDK编译器的全局宏定义里面,添加宏定义:USE_USB_FS,具体添加情况如图39.3.2.4所示:   
image025.png
图39.3.2.4 定义全局宏USE_USB_FS

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

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

4、修改usbd_msc_bot.c里面修改MSC_BOT_CBW_Decode函数,将hmsc->cbw.bLUN > 1改为:hmsc->cbw.bLUN > STORAGE_LUN_NBR,以支持多个磁盘。

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

2. main.c代码
下面是main.c的程序,具体如下:
  1. USBD_HandleTypeDef USBD_Device;              /* USB Device处理结构体 */
  2. extern volatile uint8_t g_usb_state_reg;   /* USB状态 */
  3. extern volatile uint8_t g_device_state;    /* USB连接情况 */
  4. int main(void)
  5. {
  6.     uint8_t offline_cnt = 0;
  7.     uint8_t tct = 0;
  8.     uint8_t usb_sta;
  9.     uint8_t device_sta;
  10.     uint16_t id;
  11.    
  12.    HAL_Init();                             /* 初始化HAL库 */
  13.    sys_stm32_clock_init(336, 8, 2, 7); /* 设置时钟,168Mhz */
  14.    delay_init(168);                       /* 延时初始化 */
  15.    usart_init(115200);                   /* 串口初始化为115200 */
  16.    led_init();                             /* 初始化LED */
  17.    lcd_init();                             /* 初始化LCD */
  18.    key_init();                             /* 初始化按键 */
  19.     norflash_init();                       /* 初始化norflash */
  20.    my_mem_init(SRAMIN);                  /* 初始化内部SRAM内存池 */
  21.    my_mem_init(SRAMCCM);                 /* 初始化内部SRAMCCM内存池 */
  22.    my_mem_init(SRAMEXN);                 /* 初始化外部SRAM内存池 */
  23.    lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
  24.    lcd_show_string(30, 70, 200, 16, 16, "USB CardReader TEST", RED);
  25.    lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
  26.     if (sd_init())   /* 初始化SD卡 */
  27.     {   /* 检测SD卡错误 */
  28.        lcd_show_string(30, 130, 200, 16, 16, "SD CardError!", RED);
  29.     }
  30.     else              /* SD 卡正常 */
  31.     {
  32.         /* 计算SD卡容量 */
  33.        lcd_show_string(30, 130, 200, 16, 16, "SD CardSize:     MB", RED);
  34.        card_capacity = (uint64_t)(g_sd_card_info_handle.LogBlockNbr) *
  35.                            (uint64_t)(g_sd_card_info_handle.LogBlockSize);
  36.        lcd_show_num(134, 130, card_capacity >> 20, 5, 16, RED);/* 显示SD卡容量 */
  37.     }
  38.     id = norflash_read_id();
  39.    
  40.     if ((id == 0) || (id == 0xFFFF))
  41. {
  42.        /* 检测NorFlash错误 */
  43.        lcd_show_string(30, 130, 200, 16, 16, "NorFlashError!", RED);
  44.     }
  45.     else   /* SPI FLASH正常 */
  46.     {
  47.        lcd_show_string(30, 150, 200, 16, 16, "SPI FLASHSize:12MB", RED);
  48.     }
  49. /* 提示正在建立连接 */
  50. lcd_show_string(30, 190, 200, 16, 16, "USB Connecting...", RED);
  51.    USBD_Init(&USBD_Device, &FS_Desc, DEVICE_FS);  /* 初始化USB */
  52.    USBD_RegisterClass(&USBD_Device, &USBD_MSC);   /* 添加类 */
  53. /* 为MSC类添加回调函数 */
  54. USBD_MSC_RegisterStorage(&USBD_Device, &USBD_Storage_Interface_fops_FS);
  55.    USBD_Start(&USBD_Device);   /* 开启USB */
  56.    delay_ms(1800);
  57.    
  58.     while (1)
  59.     {
  60.        delay_ms(1);
  61.        if (usb_sta != g_usb_state_reg)      /* 状态改变了 */
  62.        {
  63.            lcd_fill(30, 210, 240, 210 + 16, WHITE);    /* 清除显示 */
  64.            if (g_usb_state_reg & 0x01)      /* 正在写 */
  65.            {
  66.                 LED1(0);
  67.                 /* 提示USB正在写入数据 */
  68.                 lcd_show_string(30, 210, 200, 16, 16, "USB Writing...", RED);
  69.            }
  70.            if (g_usb_state_reg & 0x02)   /* 正在读 */
  71.            {
  72.                 LED1(0);
  73.                 /* 提示USB正在读出数据 */
  74.                 lcd_show_string(30, 210, 200, 16, 16, "USB Reading...", RED);
  75.            }
  76.            if (g_usb_state_reg & 0x04)
  77.            {
  78.                 /* 提示写入错误 */
  79.                 lcd_show_string(30, 230, 200, 16, 16, "USB Write Err ", RED);
  80.            }
  81.            else
  82.            {
  83.                 lcd_fill(30, 230, 240, 230 + 16, WHITE);  /* 清除显示 */
  84.            }
  85.            
  86.            if (g_usb_state_reg & 0x08)
  87.            {
  88.                 /* 提示读出错误 */
  89.                 lcd_show_string(30, 250, 200, 16, 16, "USB Read  Err", RED);
  90.            }
  91.            else
  92.            {
  93.                 lcd_fill(30, 250, 240, 250 + 16, WHITE);  /* 清除显示 */
  94.            }
  95.            
  96.            usb_sta =g_usb_state_reg;                      /* 记录最后的状态 */
  97.        }
  98.        if (device_sta != g_device_state)
  99.        {
  100.            if (g_device_state == 1)
  101.            {
  102.                 /* 提示USB连接已经建立 */
  103.                 lcd_show_string(30, 190, 200, 16, 16, "USB Connected   ", RED);
  104.            }
  105.            else
  106.            {
  107.                 /* 提示USB被拔出了 */
  108.                 lcd_show_string(30, 190, 200, 16, 16, "USB DisConnected ", RED);
  109.            }
  110.            
  111.            device_sta = g_device_state;
  112.        }
  113.        tct++;
  114.        if (tct == 200)
  115.        {
  116.            tct = 0;
  117.            LED1(1);                           /* 关闭 LED1 */
  118.            LED0_TOGGLE();                    /* LED0 闪烁 */
  119.            if (g_usb_state_reg & 0x10)
  120.            {
  121.                 offline_cnt = 0;              /* USB连接了,则清除offline计数器 */
  122.                 g_device_state = 1;
  123.            }
  124.            else    /* 没有得到轮询 */
  125.            {
  126.                 offline_cnt++;
  127.                 if (offline_cnt > 10)
  128.                 {
  129.                     g_device_state = 0;        /* 2s内没收到在线标记,代表USB被拔出了 */
  130.                 }
  131.            }
  132.            g_usb_state_reg = 0;
  133.        }
  134.     }
  135. }
复制代码
其中,USBD_HandleTypeDef是一个用于处理USB设备类通信处理的结构体类型,它包含了USB设备类通信的各种变量、结构体参数、传输状态和端点信息等。凡是USB设备类通信,都必须要用定义一个这样的结构体,这里定义成:USBD_Device。

使用ST官方提供的USB库以后,整个USB初始化就变得比较简单了:
1,  调用USBD_Init函数,初始化USB从机内核;
2,  调用USBD_RegisterClass函数,链接MSC设备类驱动程序到设备内核;
3,  调用USBD_MSC_RegisterStorage函数,为MSC设备类驱动添加回调函数;
4,  调用USBD_Start函数,启动USB通信;

经过以上四步处理,USB就启动了,所有USB事务,都是通过USB中断触发,并由USB驱动库自动处理。USB中断服务函数在usbd_conf.c里面:
  1. /**
  2. *@brief        USB OTG 中断服务函数
  3. *  @note       处理所有USB中断
  4. *@param        无
  5. *@retval       无
  6. */
  7. voidOTG_FS_IRQHandler(void)
  8. {
  9.    HAL_PCD_IRQHandler(&g_pcd_usb_otg_fs);
  10. }
复制代码
该函数调用HAL_PCD_IRQHandler函数来处理各种USB中断请求。因此在main函数里面,我们的处理过程就非常简单,main函数里面通过两个全局状态变量(g_usb_state_reg和g_device_state),来判断USB状态,并在LCD上面显示相关提示信息。

g_usb_state_reg在usbd_storage.c里面定义的一个全局变量,不同的位表示不同状态,用来指示当前USB的读写等操作状态。

g_device_state是在usbd_conf.c里面定义的一个全局变量,0表示USB还没有连接;1表示USB已经连接。

63.4 下载验证
将程序下载到开发板后,在USB配置成功后(假设已经插入SD卡,注意:USB数据线,要插在USB_OTG口!而不是USB_UART端口!),如图63.4.1所示:     
image027.png
图63.4.1 USB连接成功显示界面

此时,电脑提示发现新硬件,并开始自动安装驱动,如图63.4.2所示:     
image029.png
图63.4.2 USB读卡器被电脑找到

USB配置成功后,LED1熄灭,LED0闪烁,在电脑上可以看到磁盘,如图63.4.3所示:     
image031.png
图63.4.3 电脑找到USB读卡器的盘符

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

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

注意,在对SPI FLASH操作的时候,最好不要频繁的往里面写数据,否则很容易将SPI FLASH写爆!
正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2025-2-24 05:43

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表