【正点原子STM32连载】第五十七章 USB读卡器(Slave)实验 摘自【正点原子】MiniPro STM32H750 开发指南_V1.1

2023-11-14

1)实验平台:正点原子MiniPro H750开发板
2)平台购买地址:https://detail.tmall.com/item.htm?id=677017430560
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-336836-1-1.html
4)对正点原子STM32感兴趣的同学可以加群讨论:879133275

第五十七章 USB读卡器(Slave)实验

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

57.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版本仍是本文的重点介绍对象。
57.1.1 USB简介
USB本身的知识体系非常复杂,本节只能作一点知识点的引入。本书篇幅有限,不可能在这里详细介绍,想更系统地学习USB的知识可以参考《圈圈教你玩 USB》、塞普拉斯提供的《USB 101:通用串行总线 2.0 简介》等文献,下面我们一起来看USB的简单特性:
USB的硬件接口
USB协议有漫长的发展历程,为的不同的场合和硬件功能而发展出不同的接口:Type-A、Type-B、Type-C,Type-C规范碰巧是跟着USB3.1的规范一起发布的。常见的接口类型列出如图57.1.1所示。
在这里插入图片描述

图57.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标准所规定的参数来设计。
USB速度
USB规范已经为USB系统定义了以下四种速度模式:低速(Low-Speed)、全速(Full-Speed)、高速(Hi-Speed)和超高速(SuperSpeedUSB)。接口的速度上限与设备支持的USB协议标准和导线长度、阻抗有关,不同协议版本对硬件的传输线数量、阻抗等要求各不相同,各个版本的能达到的理论速度上限对应如图57.1.2。
在这里插入图片描述

图57.1.2 USB协议发展与版本对应的速度
USB端口和连接器有时会标上颜色,以指示USB规格及其支持的功能。这些颜色不是USB规范所要求的,并且在设备制造商之间不一致。例如,常见的支持USB3.0的U盘和电脑等设备使用蓝色指示,英特尔使用橙色指示充电端口等。
USB系统
USB系统主要包括三个部分:控制器(Host Controller)、集线器 (Hub) 和USB设备。
控制器(Host Controller),主机一般可以有一个或多个控制器,主要负责执行由控制器驱动程序发出的命令。控制器驱动程序(Host Controller Driver)在控制器与USB设备之间建立通信信道。
集线器(Hub)连接到USB主机的根集线器,可用于拓展主机可访问的USB设备的数量。
USB设备(USB Device)则是我们常用的如U盘,USB鼠标这类受主机控制的设备。
USB通讯
USB针对主机、集线器和设备制定了严格的协议。概括来讲,通过检测、令牌、传输控制、数据传输等多种方式,定义了主机和从机在系统中的不同职能。USB系统通过“管道”进行通讯,有“控制管道”和“数据管道”两种,“控制管道”是双向的,而每个“数据管道”则是单向的,这种关系如图57.1.3所示。
在这里插入图片描述

图57.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盘→8,STM32参考资料→2,STM32 USB学习资料”中了,感兴趣的朋友自行去查阅更的USB的相关扩展知识,我们对USB的简介就到这里。
57.1.2 STM32H7的USB特性
USB发展到现在已经有USB1.0/1.1/2.0/3.0等多个版本。目前用的最多的就是USB1.1和USB2.0,USB3.0目前已经开始普及。STM32H750自带的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控制器。
STM32H750系列芯片自带有2个USB OTG,其中USB1是高速USB(USB1 OTG HS);USB2是全速USB(USB2 OTG FS),高速USB(HS)需要外扩高速PHY芯片实现,我们这里不做介绍。
STM32H750的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。
STM32H750的USB OTG FS主要特性可分为三类:通用特性、主机模式特性和从机模式特性。
1、通用特性
经USB-IF认证,符合通用串行总线规范第2.0版
集成全速PHY,且完全支持定义在标准规范OTG补充第2.0版中的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,可配置的帧周期
3,可配置的帧结束中断
具有省电功能,例如在USB挂起期间停止系统、关闭数字模块时钟、对PHY和DFIFO电源加以管理
具有采用高级FIFO控制的4KB专用RAM
1,可将 RAM 空间划分为不同FIFO,以便灵活有效地使用RAM
2,每个FIFO可存储多个数据包
3,动态分配存储区
4,FIFO大小可配置为非2的幂次方值,以便连续使用存储单元
一帧之内可以无需要应用程序干预,以达到最大 USB 带宽
2、主机(Host)模式特性
通过外部电荷泵生成VBUS电压。
多达16个主机通道(管道):每个通道都可以动态实现重新配置,可支持任何类型的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-IN FIFO(分别用于每个使能的IN EP),降低应用程序负荷支持软断开功能。
STM32H750 USB2 OTG FS框图如图57.1.1所示:
在这里插入图片描述

图57.1.1 USB2 OTG FS框图
对于USB OTG FS功能模块,STM32H750通过AHB总线访问(AHB频率必须大于30Mhz),
另外,USB OTG的内核时钟必须是48Mhz,由RCC_D2CCIP2R寄存器的USBSEL[1:0]位选择:00,禁止USB内核时钟;01,USB内核时钟来自pll1_q_ck;10,USB内核时钟来自pll3_q_ck;11,USB内核时钟来自hsi48_ck;因为pll1_q_ck和pll3_q_ck很有可能被其他外设用作时钟,不方便设置为48Mhz,因此我们一般使用hsi48_ck作为USB OTG内核时钟(USBSEL[1:0]=11),这样就不会受到其他外设的影响。
要正常使用STM32H750的USB,就得编写USB驱动,而整个USB通信的详细过程是很复杂的,本书篇幅有限,不可能在这里详细介绍,有兴趣的朋友可以去看看电脑圈圈的《圈圈教你玩USB》这本书,该书对USB通信有详细讲解。如果要我们自己编写USB驱动,那是一件相当困难的事情,尤其对于从没了解过USB的人来说,基本上不花个一两年时间学习,是没法搞定的。不过,ST提供了我们一个完整的USB OTG 驱动库(包括主机和设备),通过这个库,我们可以很方便的实现我们所要的功能,而不需要详细了解USB的整个驱动,大大缩短了我们的开发时间和精力。
STM32H7的USB例程全部是以HAL库的形式提供,为了简化开发设计,我们直接使用ST提供的HAL库版本USB驱动库来设计相关例程。
ST提供的H7 USB OTG库和相关参考例程在en.stm32cubeh7.zip里面可以找到,该文件可以在www.st.com网站搜索:cubeh7找到。不过,我们已经帮大家下载到开发板光盘:8,STM32参考资料 1,STM32CubeH7固件包 en.stm32cubeh7.zip。解压可以得到STM32H7的Cube固件支持包:STM32Cube_FW_H7_V1.6.0,该文件夹里面包含了H7的USB 主机(Host)和从机(Device)驱动库,并提供了17个例程供我们参考,如图57.1.2所示:
在这里插入图片描述

图57.1.2 ST提供的USB OTG例程
标号1是H7 USB从机驱动库;
标号2是H7 USB主机驱动库;
标号3是H7 USB从机例程(共8个);
标号4是H7 USB主机例程(共9个);
整个USB OTG库的使用和例程说明,可以参考ST官方提供的:UM1734(从机)和UM1720(主机)这两个文档(在光盘:8,STM32参考资料2,STM32 USB 学习资料 文件夹),这两个文档详细介绍了USB OTG库的各个组成部分以及所提供的例程使用方法,有兴趣学习USB的朋友,这个文档是必须仔细看的。
这17个例程,虽然都是基于官方STM32H743IEVAL板,但是很容易移植到我们的MiniPRO STM32H750开发板上。
虽然H750也有一个驱动库,但是并没有提供很多实例参考,而且H750与H743极为相近,所以可以直接使用H743的参考例子。
在这里插入图片描述

图57.1.3 ST提供的H750USB实例参考
本实验我们移植:STM32Cube_FW_H7_V1.6.0\Projects\STM32H743I_EVAL\Applications \USB_Device\MSC_Standalone这个例程,以实现USB读卡器功能。
57.2 硬件设计

  1. 例程功能
    本实验代码,开机的时候先检测SD卡和SPI FLASH是否存在,如果存在则获取其容量,并显示在LCD上面(如果不存在,则报错)。之后开始USB配置,在配置成功之后就可以在电脑上发现两个可移动磁盘。USB正在读写会在液晶上显示出来。
    LED0闪烁,提示程序运行。USB和电脑连接成功后,LED1常亮。
  2. 硬件资源
    1)RGB灯
    RED :LED0 - PB4
    GREEN :LED1 - PE6
    2)串口1(PA9/PA10连接在板载USB转串口芯片CH340上面)
    3)正点原子2.8/3.5/4.3/7/10寸TFTLCD模块(仅限MCU屏,16位8080并口驱动)
    4)USB_SLAVE接口(D-/D+连接在PA11/PA12上)
    5)QSPI(PB2/PB6/PD11/PD12/PD13/PE2)
    6)norflash(QSPI FLASH芯片,连接在QSPI上)
    7)SD卡,通过SDMMC1(SDMMC_D0D4(PC8PC11),SDMMC_SCK(PC12),
    SDMMC_CMD(PD2))连接
  3. 原理图
    开发板采用的是5PIN的MiniUSB接头,用来和电脑的USB相连接,MiniUSB接口与STM32的连接电路图,如下图所示:
    在这里插入图片描述

图57.2.1 MiniUSB接口与STM32的连接电路图
从上图可以看出,USB座是直接连接到STM32H750上面的,所以硬件上不需要我们做什么操作,可直接使用。
需要注意的是:这个MiniUSB座和USB母座(USB_HOST)是共用D+和D-的,所以他们不能同时使用。这个在使用的时候,要特别注意!!本实验测试时,USB_HOST不能插入任何USB设备!
另外,MicroUSB数据线是通过下图框里的USB_SLAVE接口连接到电脑的,不要连错。
在这里插入图片描述

图57.2.2 MiniUSB接口与电脑连接示意图
57.3 程序设计
57.3.1 程序流程图
在这里插入图片描述

图57.3.1.1 USB读卡器(Slave)实验程序流程图
57.3.2 程序解析
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。
本实验,我们在:实验33 SD卡实验的基础上修改,代码移植自ST官方例程:STM32Cube_FW_H7_V1.6.0\Projects\STM32H743I_EVAL\Applications\USB_Device\MSC_Standalone。该目录下提供了三种开发环境的工程:IAR、MDK和SW4STM32,我们使用的是MDK,打开MDK工程,然后就可以知道和USB相关的代码有哪些,如图57.3.2.1所示:
在这里插入图片描述

图57.3.2.1 ST官方例程USB相关代码
有了这个官方例程做指引,我们就知道具体需要哪些文件,从而实现本章例程。
首先,在本例程(即实验33 SD卡实验)工程的Middlewares文件夹下面,新建一个USB文件夹,并拷贝官方USB驱动库相关代码到该文件夹下,即拷贝:光盘 8,STM32参考资料1,STM32CubeH7固件包STM32Cube_FW_H7_V1.6.0MiddlewaresST文件夹下的: STM32_USB_Device_Library、STM32_USB_HOST_Library两个文件夹及源码拷贝到该文件夹下面。
然后,在USB文件夹下,新建一个USB_APP文件夹用于存放MSC实现相关代码,即:STM32Cube_FW_H7_V1.6.0ProjectsSTM32H743I_EVALApplicationsUSB_DeviceMSC_StandaloneSrc下的部分代码:usbd_conf.c、usbd_storage.c和usbd_desc.c等3个.c文件,同时拷贝:STM32Cube_FW_H7_V1.6.0ProjectsSTM32H743I_EVALApplicationsUSB_ DeviceMSC_StandaloneInc下面的:usbd_conf.h、usbd_storage.h和usbd_desc.h等三个文件到USB_APP文件夹下。最后USB_APP文件夹下的文件如图57.3.2.2所示:
在这里插入图片描述

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

图57.3.2.3 添加USB驱动等相关代码

  1. USB驱动代码
    这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。接下来我们看看USB_APP里面的几个.c文件:
    usbd_conf.c提供了USB设备库(从机库,下同)的回调及MSP初始化函数,当USB状态机处理完不同事务的时候,会调用这些回调函数,我们通过这些回调函数,就可以知道USB当前状态,比如:是否枚举成功了?是否连接上了?是否断开了?等,根据这些状态,用户应用程序可以执行不同操作,完成特定功能。usbd_conf.c我们重点介绍3个函数,首先是HAL_PCD_MspInit和OTG_FS_IRQHandler函数,它们的定义如下:
/**
 * @brief       初始化PCD MSP
 * @param       hpcd:PCD结构体指针
 * @retval      无
 */
void HAL_PCD_MspInit(PCD_HandleTypeDef * hpcd)
{
    GPIO_InitTypeDef gpio_init_struct;

    /* USB时钟设置,使用HSI48MHz,具体配置请看sys_stm32_clock_init函数 */
    if (hpcd->Instance == USB2_OTG_FS)
    {
        __HAL_RCC_GPIOA_CLK_ENABLE();                          	/* 使能GPIOA时钟 */

        gpio_init_struct.Pin = GPIO_PIN_11 | GPIO_PIN_12; 
        gpio_init_struct.Mode = GPIO_MODE_AF_PP;             	/* 复用 */
        gpio_init_struct.Pull = GPIO_NOPULL;                  	/* 浮空 */
        gpio_init_struct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;	/* 高速 */
        gpio_init_struct.Alternate = GPIO_AF10_OTG1_FS;     	/* 复用为OTG1_FS */
        HAL_GPIO_Init(GPIOA, &gpio_init_struct);   	/* 初始化PA11和PA12引脚 */

        __HAL_RCC_USB2_OTG_FS_CLK_ENABLE();         	/* 使能OTG FS时钟 */
        HAL_NVIC_SetPriority(OTG_FS_IRQn, 1, 0);	/* 优先级设置为抢占1,子优先级0*/
        HAL_NVIC_EnableIRQ(OTG_FS_IRQn);            	/* 使能OTG FS中断 */
    }
    else if (hpcd->Instance == USB1_OTG_HS)
    {
        /* USB1 OTG本例程没用到,故不做处理 */
    }
}

/**
 * @brief       USB OTG 中断服务函数
 *   @note      处理所有USB中断
 * @param       无
 * @retval      无
 */
void OTG_FS_IRQHandler(void)
{
    HAL_PCD_IRQHandler(&hpcd);
}
HAL_PCD_MspInit函数,用于使能USB时钟,初始化IO口,设置中断等。该函数在HAL_PCD_Init函数里面被调用。这里我们需要注意的是:USB时钟源,我们在sys.c文件的sys_stm32_clock_init函数中已经设置,所以这里只是需要使能OTG FS时钟即可。
	OTG_FS_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)
{
#ifdef USE_USB_FS   /* 针对USB FS,执行FS的初始化 */
    
    /* 设置LL驱动相关参数 */
    hpcd.Instance = USB2_OTG_FS;            /* 使用USB2 OTG */
    hpcd.Init.dev_endpoints = 8;            /* 端点数为8 */
    hpcd.Init.use_dedicated_ep1 = 0;       /* 禁止EP1 dedicated中断 */
    hpcd.Init.ep0_mps = 0x40;               /* 设置端点0的最大包大小为0X40(64字节) */
    hpcd.Init.low_power_enable = 0;        /* 不使能低功耗模式 */
    hpcd.Init.phy_itface = PCD_PHY_EMBEDDED;/* 使用内部PHY */
    hpcd.Init.Sof_enable = 0;               	/* 使能SOF中断 */
    hpcd.Init.speed = PCD_SPEED_FULL;     	/* USB全速(12Mbps) */
    hpcd.Init.vbus_sensing_enable = 0;    	/* 不使能VBUS检测 */
    hpcd.Init.lpm_enable = 0;              	/* 使能连接电源管理 */

    hpcd.pData = pdev;                      	/* hpcd的pData指向pdev */
    pdev->pData = &hpcd;                   	/* pdev的pData指向hpcd */

    HAL_PCD_Init(&hpcd);                   	/* 初始化LL驱动 */

    HAL_PCDEx_SetRxFiFo(&hpcd, 0x80);   		/* 设置接收FIFO大小为0X80(128字节) */
    HAL_PCDEx_SetTxFiFo(&hpcd, 0, 0x40);/* 设置发送FIFO 0的大小为0X40(64字节) */
    HAL_PCDEx_SetTxFiFo(&hpcd, 1, 0x80);/* 设置发送FIFO 1的大小为0X80(128字节) */
#endif

#ifdef USE_USB_HS   /* 针对USB HS,执行HS的初始化 */
    /* 未实现 */
#endif
    return USBD_OK;
}

USBD_LL_Init函数,用于初始化USB底层设置,因为我们定义的是:USE_USB_FS,因此会设置USB OTG使用USB2_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提供一些磁盘操作函数,包括支持的磁盘个数,以及每个磁盘的初始化和读写等函数。本章我们设置了2个磁盘:SD卡和SPI FLASH等。usb_storage.c我们重点介绍3个函数,首先是初始化存储设备函数,其定义如下:

/**
 * @brief       初始化存储设备
 * @param       lun    : 逻辑单元编号
 *   @arg                  0, SD卡
 *   @arg                  1, SPI FLASH
 * @retval      操作结果
 *   @arg       0    , 成功
 *   @arg       其他 , 错误代码
 */
int8_t STORAGE_Init (uint8_t lun)
{
    uint8_t res = 0;
    switch (lun)
    {
        case 0: /* SPI FLASH */
            /* 没有初始化函数!不能调用:NORFLASH_Init!调用该函数将导致QSPI内存映射模式失
             * 效,程序运行可能异常因为在sys.c里面,开启内存映射的时候,已经初始化QSPI接口了 
             */
            break;
        case 1: /* SD卡 */
            res = sd_init();
            break;
    }
    return res;
}
STORAGE_Init函数,用于初始化存储设备,我们定义了两个存储设备:SPI FLASH和SD卡,因此需要根据输入参数(lun),执行不同存储设备的初始化。

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

/**
 * @brief       从存储设备读取数据
 * @param       lun         : 逻辑单元编号
 *   @arg                     0, SD卡
 *   @arg                     1, SPI FLASH
 * @param       buf         : 数据存储区首地址指针
 * @param       blk_addr   : 要读取的地址(扇区地址)
 * @param       blk_len    : 要读取的块数(扇区数)
 * @retval      操作结果
 *   @arg       0    , 成功
 *   @arg       其他 , 错误代码
 */
int8_t STORAGE_Read (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_ex_read(buf, USB_STORAGE_FLASH_BASE + blk_addr * 512, 
blk_len * 512);
            break;

        case 1: /* SD卡 */
            res = sd_read_disk(buf, blk_addr, blk_len);
            break;
    }

    if (res)
    {
        printf("rerr:%d,%d", lun, res);
        g_usb_state_reg |= 0X08;    /* 读错误! */
    }
    return res;
}
STORAGE_Read函数,用于从存储设备读取数据,同样是根据存储设备(lun)的不同,调用不同的读取函数,完成数据读取。

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

/**
 * @brief       向存储设备写数据
 * @param       lun       : 逻辑单元编号
 *   @arg                  0, SD卡
 *   @arg                  1, SPI FLASH
 * @param       buf        : 数据存储区首地址指针
 * @param       blk_addr  : 要写入的地址(扇区地址)
 * @param       blk_len   : 要写入的块数(扇区数)
 * @retval      操作结果
 *   @arg       0    , 成功
 *   @arg       其他 , 错误代码
 */
int8_t STORAGE_Write (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_ex_write(buf, USB_STORAGE_FLASH_BASE + blk_addr * 512, 
blk_len * 512);
            break;

        case 1: /* SD卡 */
            res = sd_write_disk(buf, blk_addr, blk_len);
            break;
    }

    if (res)
    {
        g_usb_state_reg |= 0X04;    /* 写错误! */
        printf("werr:%d,%d", lun, res);
    }
    return res;
}
STORAGE_Write函数,用于往存储设备写入数据,也是根据存储设备(lun)的不同,调用不同的写入函数,完成数据写入。

以上3个.c文件和对应.h文件的详细代码和修改方法,我们就不详细介绍了,请大家参考光盘本例程源码。
下面提几个重点地方讲解下:
1、要使用USB OTG FS,必须在MDK编译器的全局宏定义里面,添加宏定义:USE_USB_FS,具体添加情况如图57.3.2.4所示:
在这里插入图片描述

图57.3.2.4 定义全局宏USE_USB_FS
2、通过修改usbd_conf.h里面的MSC_MEDIA_PACKET定义值大小,可以一定程度提高USB读写速度(越大越快),本例程我们设置32*1024,也就是32KB大小。另外,我们通过修改:STORAGE_LUN_NBR宏定义的值为2,可以支持3个磁盘。就本开发板来说是支持2个磁盘的。
3、官方例程在2个或以上磁盘支持的时候,存在bug,我们需要修改usbd_msc.h里面USBD_MSC_BOT_HandleTypeDef结构体的scsi_blk_nbr参数,将其改为数组形式:uint32_t scsi_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(STM32Cube USB device library).pdf这个文档)。
2. main.c代码
下面是main.c的程序,具体如下:

USBD_HandleTypeDef USBD_Device;               	/* USB Device处理结构体 */
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;
    uint64_t card_capacity;               	/* SD卡容量 */

    sys_cache_enable();                    /* 打开L1-Cache */
    HAL_Init();                             	/* 初始化HAL库 */
    sys_stm32_clock_init(240, 2, 2, 4);	/* 设置时钟, 480Mhz */
    delay_init(480);                       	/* 延时初始化 */
    usart_init(115200);                   	/* 串口初始化为115200 */
    mpu_memory_protection();             	/* 保护相关存储区域 */
    led_init();                            	/* 初始化LED */
    lcd_init();                            	/* 初始化LCD */
    key_init();                            	/* 初始化按键 */
    my_mem_init(SRAMIN);                 	/* 初始化内部内存池(AXI) */
    my_mem_init(SRAM12);                 	/* 初始化SRAM12内存池(SRAM1+SRAM2) */
    my_mem_init(SRAM4);                  	/* 初始化SRAM4内存池(SRAM4) */
    my_mem_init(SRAMDTCM);              	/* 初始化DTCM内存池(DTCM) */
    my_mem_init(SRAMITCM);              	/* 初始化ITCM内存池(ITCM) */

    lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
    lcd_show_string(30, 70, 200, 16, 16, "USB Card Reader TEST", RED);
    lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);

    if (sd_init())  /* 初始化SD卡 */
{
/* 检测SD卡错误 */
        lcd_show_string(30, 130, 200, 16, 16, "SD Card Error!", RED);   
    }
    else     /* SD 卡正常 */
    {
        lcd_show_string(30, 130, 200, 16, 16, "SD Card Size:     MB", RED);
        card_capacity = (uint64_t)(g_sd_card_info_handle.LogBlockNbr) 
* (uint64_t)(g_sd_card_info_handle.LogBlockSize); 	/* 计算SD卡容量 */
        lcd_show_num(134, 130, card_capacity >> 20, 5, 16, RED);/* 显示SD卡容量 */
    }

    id = norflash_ex_read_id();
    if ((id == 0) || (id == 0XFFFF))
{
/* 检测W25Q128错误 */
        lcd_show_string(30, 130, 200, 16, 16, "BY25Q128 Error!", RED);  
    }
    else   /* SPI FLASH 正常 */
    {
        lcd_show_string(30, 150, 200, 16, 16, "SPI FLASH Size:7.25MB", RED);
    }
/* 提示正在建立连接 */
    lcd_show_string(30, 190, 200, 16, 16, "USB Connecting...", RED);    
    USBD_Init(&USBD_Device, &MSC_Desc, 0);                    	/* 初始化USB */
    USBD_RegisterClass(&USBD_Device, USBD_MSC_CLASS);       /* 添加类 */
    USBD_MSC_RegisterStorage(&USBD_Device,&USBD_DISK_fops);/*为MSC类添加回调函数*/
    USBD_Start(&USBD_Device);                                    /* 开启USB */
    delay_ms(1800);
    while (1)
    {
        delay_ms(1);
        if (usb_sta != g_usb_state_reg)   /* 状态改变了 */
        {
            lcd_fill(30, 210, 240, 210 + 16, WHITE); /* 清除显示 */
            if (g_usb_state_reg & 0x01)   /* 正在写 */
            {
                LED1(0);
/* 提示USB正在写入数据 */
                lcd_show_string(30, 210, 200, 16, 16, "USB Writing...", RED); 
            }
            if (g_usb_state_reg & 0x02)   /* 正在读 */
            {
                LED1(0);
/* 提示USB正在读出数据 */
                lcd_show_string(30, 210, 200, 16, 16, "USB Reading...", RED); 
            }
            if (g_usb_state_reg & 0x04)
            {
/* 提示写入错误 */
                lcd_show_string(30, 230, 200, 16, 16, "USB Write Err ", RED); 
            }
            else
            {
                lcd_fill(30, 230, 240, 230 + 16, WHITE); /* 清除显示 */
            }
            
            if (g_usb_state_reg & 0x08)
            {
/* 提示读出错误 */
                lcd_show_string(30, 250, 200, 16, 16, "USB Read  Err ", RED); 
            }
            else
            {
                lcd_fill(30, 250, 240, 250 + 16, WHITE); /* 清除显示 */
            }
            usb_sta = g_usb_state_reg; /* 记录最后的状态 */
        }
        if (device_sta != g_device_state)
        {
            if (g_device_state == 1)
            {
/* 提示USB连接已经建立 */
                lcd_show_string(30, 190, 200, 16, 16, "USB Connected    ", RED);    
            }
            else
            {
/* 提示USB被拔出了 */
                lcd_show_string(30, 190, 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;
        }
    }
}

其中,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里面:

/**
 * @brief       USB OTG 中断服务函数
 *   @note      处理所有USB中断
 * @param       无
 * @retval      无
 */
void OTG_FS_IRQHandler(void)
{
    HAL_PCD_IRQHandler(&hpcd);
}

该函数调用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已经连接。
57.4 下载验证
将程序下载到开发板后,在USB配置成功后(假设已经插入SD卡,注意:USB数据线,要插在USB_SLAVE口!而不是USB_UART端口!),如图57.4.1所示:
在这里插入图片描述

图57.4.1 USB连接成功显示界面
此时,电脑提示发现新硬件,并开始自动安装驱动,如图57.4.2所示:
在这里插入图片描述

图57.4.2 USB读卡器被电脑找到
USB配置成功后,LED1熄灭,LED0闪烁,在电脑上可以看到磁盘,如图57.4.3所示:
在这里插入图片描述

图57.4.3 电脑找到USB读卡器的两个盘符
我们打开设备管理器,在通用串行总线控制器里面可以发现多出了一个USB 大容量存储设备,同时看到磁盘驱动器里面多了两个磁盘,如图57.4.4所示:
在这里插入图片描述

图57.4.4 通过设备管理器查看磁盘驱动器
此时,我们就可以通过电脑读写SD卡和SPI FLASH里面的内容了。在执行读写操作的时候,就可以看到LED1亮,并且会在液晶上显示当前的读写状态。
注意,在对SPI FLASH操作的时候,最好不要频繁的往里面写数据,否则很容易将SPI FLASH写爆!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【正点原子STM32连载】第五十七章 USB读卡器(Slave)实验 摘自【正点原子】MiniPro STM32H750 开发指南_V1.1 的相关文章

  • 我们可以优化代码来降低功耗吗?

    有没有什么技术可以优化代码以确保更低的功耗 架构是ARM 语言是C 来自 ARM 技术参考网站 ARM11 MPCore 的特性 提高能源效率的处理器 效率包括 准确的分支和子程序返回预测 减少数量 错误的指令获取和 解码操作 使用物理寻址
  • ARM NEON:如何实现 256 字节查找表

    我正在使用内联汇编将我编写的一些代码移植到 NEON 我需要的一件事是将范围 0 128 的字节值转换为表中采用完整范围 0 255 的其他字节值 该表很短 但其背后的数学并不容易 因此我认为不值得每次 即时 计算它 所以我想尝试查找表 我
  • 如何在 ARM 架构上从 RAM 运行代码

    我正在对 ARM Cortex R4 进行编程 并且有一些二进制文件 我想从 TCRAM 执行它们 只是为了看看性能的提升是否足够好 我知道我必须编写一个函数来将二进制文件复制到 RAM 这可以通过链接器脚本来完成 并且知道二进制文件的大小
  • STM32F0、ST-link v2、OpenOCD 0.9.0:打开失败

    我在用着发射台 http www ti com ww en launchpad about htmlgcc arm none eabi 4 9 2015q2 为 STM32F0 进行编译 现在我想使用该集合中的 arm none eabi
  • 适用于arm(cortex-m3)的位置独立可执行文件(-pie)

    我正在使用codesourcery g lite 基于gcc4 7 2版本 为stm32 Cortex m3 编程 我希望动态加载可执行文件 我知道我有两个选择 1 可重定位的elf 需要一个elf解析器 2 具有全局偏移寄存器的位置无关代
  • ARM Neon:如何从 uint8x16_t 转换为 uint8x8x2_t?

    我最近发现了关于vreinterpret q dsttype src类型转换运算符 https stackoverflow com a 43519190 2436175 但是 这似乎不支持所描述的数据类型的转换这个链接 http infoc
  • ARM Linux 如何模拟 PTE 的脏位、访问位和文件位?

    As per pgtable 2 level h https git kernel org cgit linux kernel git torvalds linux git tree arch arm include asm pgtable
  • RAM 存储二进制数和汇编语言的冒泡排序

    我必须使用 ARM v7 执行一个例程 在 RAM 内存中存储 10 个二进制数 然后使用冒泡排序对这些数字从高到低进行排序 我应该如何开始 func bubbleSortAscendingU32 ldr r3 r0 4 mov r1 9
  • 将 GCC 内联汇编与采用立即值的指令结合使用

    问题 我正在为 ARM Cortex M3 处理器开发定制操作系统 为了与我的内核交互 用户线程必须生成 SuperVisor Call SVC 指令 以前称为 SWI 用于软件中断 该指令在ARM ARM中的定义是 这意味着该指令需要即时
  • ARM 中只有两个操作数的 ADD 或 SUB

    我正在学习ARM汇编语言 我读过 ADD 应该有 3 个操作数 然而 我见过很多案例 现实中只有两种 例如 STR R1 SP 0x20 var 1C LDR R1 a lua 0x1DE4E6 MOVS R0 R4 haystack AD
  • 读取和打印手臂组件中的字符串

    我正在使用 ARMSim 刚刚开始学习汇编 所以如果我看起来一无所知 请原谅我 但我正在尝试从输入文件中读取字符串 然后将其打印到输出屏幕 到目前为止我有 equ SWI Open 0x66 open a file equ SWI Clos
  • Linux malloc() 在 ARM 和 x86 上的行为是否不同?

    这个网站上有很多关于内存分配的问题 但是我 找不到专门解决我的问题的人 这 问题 https stackoverflow com questions 19148296 linux memory overcommit details似乎最接近
  • 源和目标具有不同的 EABI 版本

    我正在尝试使用 ARM 工具链编译 so 文件 但是我不断收到这个错误 错误 源对象的 EABI 版本为 0 但目标对象的 EABI 版本为 5 我无法更改工具链中的任何内容 因为我必须使用给定的工具链 我以前从未见过这个错误 我使用了这个
  • 当端点和 PMA 地址均更改时,CubeMX 生成的 USB HID 设备发送错误数据

    我正在调试我正在创建的复合设备的问题 并在新生成的仅 CubeMX 代码中重新创建了该问题 以使其更容易解决 我添加了少量代码main 让我发送 USB HID 鼠标点击 并在按下蓝色按钮时使 LED 闪烁 uint8 t click re
  • 如何在 Android 设备上运行 VS Code [重复]

    这个问题在这里已经有答案了 我有 Galaxy Tab S6 它具有替代笔记本电脑的很酷的功能 例如连接鼠标和键盘 但不幸的是它运行 Android 操作系统 并且没有很多开发应用程序可用于 Android 所以我想是否有一个选项可以在至少
  • 移动数组中的元素

    我需要一点帮助 我想将数组中的元素向上移动一个元素 以便新位置 1 包含位置 1 中的旧值 new 2 包含 old 1 依此类推 旧的最后一个值被丢弃 第一个位置的新值是我每秒给出的新值 我使用大小为 10 的数组 uint32 t TE
  • 基于 Windows 8 ARM 的平板电脑上的 VB6

    随着 Windows 8 将支持 VB6 我的问题是 Microsoft 是否在任何地方表示 是或否 VB6 应用程序将在基于 ARM 的平板电脑上运行 如果没有 是否有任何 ARM 模拟器 以便我们可以在 Windows 8 ARM 平板
  • 了解 U-Boot 内存占用

    我不明白加载 U Boot 时 RAM 中发生了什么 我正在开发 Xilinx Zynq ZC702 评估套件 并尝试使用 U Boot 在其上加载 Linux 内核 于是我使用Xilinx工具Vivado和SDK生成了一个BOOT bin
  • arm-linux-gnueabi 编译器选项

    我在用 ARM Linux gnueabi gcc在 Linux 中为 ARM 处理器编译 C 程序 但是 我不确定它编译的默认 ARM 模式是什么 例如 对于 C 代码 test c unsigned int main return 0x
  • 如何模拟ARM处理器运行环境并加载Linux内核模块?

    我尝试加载我的vmlinux into gdb并使用 ARM 内核模拟器 但我不明白为什么我会得到Undefined target command sim 这是外壳输出 arm eabi gdb vmlinux GNU gdb GDB 7

随机推荐

  • tcym4

    aHR0cHM6Ly93d3cudmVyeWJpbi5jb20vPzQzZjZiYThlYTU5NzcxZGEjeVhtbjN5em43YVVPLzlKYUVrK3JWdWE3SXA2ck9HWHlock5BbkFzL2hyaz0 复制下方
  • CTFHub-WEB-文件上传

    目录 1 无限制 2 前端验证 编辑 3 htaccess 4 MIME验证 5 文件头检查 6 00截断 7 双写后缀 1 无限制 进入靶场环境中看到有上传文件的选项 本关写明无限制所以我们直接上传php后门文件 上传成功后用蚁剑连接 这
  • JVM深入理解内部类

    原文链接 https blog csdn net Hacker ZhiDian article details 82193100 原文链接 https blog csdn net coder what article details 893
  • echarts分裂地图与线性动画

    前言 如果没有geoJSON数据 可以去 datav官网 导出 定义数据结构 const pointsDataArr name 鄂州 展示的名称 routeName region 黄冈 lable 黄冈线 value 10 路线 value
  • FLAGS标志寄存器

    标志寄存器 Flags Register FR 又称程序状态字 Program Status Word PSW 这是一个存放条件标志 控制标志寄存器 主要用于反映处理器的状态和运算结果的某些特征及控制指令的执行 CF Carry Flag
  • Python实现SSA智能麻雀搜索算法优化XGBoost回归模型(XGBRegressor算法)项目实战

    说明 这是一个机器学习实战项目 附带数据 代码 文档 视频讲解 如需数据 代码 文档 视频讲解可以直接到文章最后获取 1 项目背景 麻雀搜索算法 Sparrow Search Algorithm SSA 是一种新型的群智能优化算法 在202
  • 数字化转型时代—人人必学的7项商业分析思维

    月説小飞象交流会 人生总有不期而遇的温暖和生生不息的希望 内部交流 29期 数字化转型时代 人人必学的7项商业分析思维 data analysis 分享人 Sophia 数字化时代 数据连接一切 数据驱动一切 数据重塑一切 数据是企业数字化
  • 【计算机网络 -- 期末复习】

    例题讲解 IP地址 必考知识点 子网掩码 子网划分 第一栗 子网划分题目的答案一般不唯一 我们主要采用下方的写法 第二栗 路由跳转 数据传输 栗1 栗2 CSMA CD数据传输 路由转发 2 比特率与波特率转换 四相位表示 四种变化来表示四
  • Mybatis课堂笔记-狂神说

    Mybatis 环境 JDK1 8 Mysql 5 7 maven 3 8 4 IDEA 回顾 JDBC Mysql Java基础 Maven Junit 课堂源码及笔记 https gitee com mileschen02 mybati
  • python 基本操作注意事项

    一 缩进与空格 二 编写规范 1 注释 2 符合规范性 命名要见名知意 3 不要用关键字命名 三 PEP8编码规范 1 收藏 https www python org dev peps pep 0008 2 可读性 可维护性
  • 图的基本知识点及遍历代码

    图 一 引出 1 七桥问题 欧拉回路判定规则 如果通奇数桥的地方多于两个 则不存在欧拉回路 如果只有两个地方通奇数桥 可以从这两个地方之一出发 找到欧拉回路 如果没有一个地方是通奇数桥的 则无论从哪里出发 都能找到欧拉回路 二 基本概念 1
  • 串口printf和USB虚拟串口printf

    C语言中printf是个功能强大的函数 可以打印各种格式的数据 在单片机程序调试时 也经常需要将数据打印出来查看 之前介绍过通过Jlink的RTT功能进行打印 Jlink使用技巧之RTT和J Scope 今天将介绍通过串口和USB虚拟串口的
  • 从YOLOv1到YOLOv3,目标检测的进化之路

    本文来自 CSDN 网站 作者 EasonApp 作者专栏 http dwz cn 7ZGrif YOLOv1 这是继 RCNN fast RCNN 和 faster RCNN之后 Ross Girshick 针对 DL 目标检测速度问题提
  • 微众银行面试题

    1 切糖果 时间限制 3000MS 内存限制 589824KB 题目描述 小美想要买糖果店的一根长长的糖果 糖果店顾客可以从中选取一个位置然后老板会在那切断 糖果前端到那个切断位置的糖果就会出售给这位顾客 这个糖果其实不同段有着不同的口味
  • Safari无痕浏览影响localStorage

    最近项目中遇到一个问题 有一个登录页 点击登录后 公司两个土豪金都不能正常跳转 但是登录请求已经发送到服务器了 其他人的手机都正常 项目又要急着上线 内心这个抓狂啊 最后静下心来 终于把问题给解决了 解决过程 由于服务器有接收到客户端的aj
  • emc re 整改 超标_CE认证EMC测试不合格,如何整改 ;

    首先我们来从EMC测试项目构成说起 EMC主要包含两大项 EMI 干扰 和EMS 产品抗干扰和敏感度 当然这两大项中又包括许多小项目 EMI主要测试项 RE 产品辐射 发射 CE 产品传导干扰 Harmonic 谐波 Ficker 闪烁 E
  • Java--垃圾回收机制

    垃圾回收算法思想 引用计数 标记清除 先标记 然后再整理 会存在效率低下的问题 存在内存碎片 进而提前触发GC 复制拷贝 将内存区域分为两块 一块存储对象 如果对象满了 那么将存活的对象移动到另外的一块区域中 新生代中 空间利用率底 标记整
  • win11系统之安装MySQL8.0版本

    win11系统之安装MySQL8 0版本 一 本次实践介绍 1 1 MySQL简介 1 2MySQL8 0特点 1 2 环境介绍 二 下载MySQL软件包 2 1 MySQL官网 2 2 下载MySQL安装包 三 环境配置工作 3 1 解压
  • en结尾的单词_en后缀形容词——动词

    形容词 动词 名词 直接加en Bright brighten brightness Broad broaden broadness Dark darken darkness Deaf deafen deafness Deep deepen
  • 【正点原子STM32连载】第五十七章 USB读卡器(Slave)实验 摘自【正点原子】MiniPro STM32H750 开发指南_V1.1

    1 实验平台 正点原子MiniPro H750开发板 2 平台购买地址 https detail tmall com item htm id 677017430560 3 全套实验源码 手册 视频下载地址 http www openedv