ITPub博客

首页 > 嵌入式/内核开发 > 嵌入式/内核开发 > 《STM32H562开发指南 V1.0 》第五十九章 USB U盘(Host)实验

《STM32H562开发指南 V1.0 》第五十九章 USB U盘(Host)实验

原创 嵌入式/内核开发 作者:正点原子 时间:2026-01-13 09:36:36 0 删除 编辑

第五十九章 USB U盘(Host)实验


       本章我们介绍STM32H562的USB HOST应用,即通过USB HOST功能,实现读写U盘/读卡器等大容量USB存储设备。

       本章分为如下几个部分:

       59.1 U盘简介 

       59.2 硬件设计

       59.3 程序设计

       59.4 下载验证


        59.1 U盘简介

       U盘,全称USB闪存盘,英文名“USB flash disk”。它是一种使用USB接口的无需物理驱动器的微型高容量移动存储产品,通过USB接口与主机连接,实现即插即用,是最常用的移动存储设备之一。

       STM32H562的USB OTG FS支持U盘,并且ST官方提供了USB HOST大容量存储设备(MSC)例程,ST官方例程路径:光盘→8,STM32参考资料 →1,STM32CubeH5 固件包 →STM32Cube_FW_H5_V1.0 →Projects →STM32H573I-DK →Applications →USBX →Ux_Host_MSC。本实验,我们就要移植该例程到开发板上,以通过STM32H562的USB HOST接口,读写U盘或SD卡读卡器等设备。 


        59.2 硬件设计


       1. 例程功能

       本实验代码,开机后,检测字库,然后初始化USB HOST,并不断轮询。当检测并识别U盘后,在LCD上面显示U盘总容量和剩余容量,此时便可以通过USMART调用FATFS相关函数,来测试U盘数据的读写了,方法同FATFS实验一模一样。LED0闪烁,提示程序运行。


       2. 硬件资源

       1)LED灯  LED0 - PB4   LED1 - PB5

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

       3)正点原子2.8/3.5/4.3/7寸TFTLCD模块(仅限MCU屏,16位8080并口驱动)

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

       5)TIM6


       3. 原理图

       本开发板的USB HOST接口采用的是贴片USB母座,它和USB SLAVE是共用USB_DM和USB_DP信号的,所以USB HOST和USB SLAVE接口不能同时使用。USB HOST和STM32的连接原理图,如下图所示:


图59.2.1 USB母座与STM32的连接电路图


       从上图可以看出,USB母座(USB_HOST)是直接连接到STM32H562上面的,所以硬件上不需要我们做什么操作,可直接使用。

       需要注意的是:本实验被测试的U盘是通过USB母座(USB_HOST)连接到STM32H562的,此时,USB_SLAVE接口是不能连接USB设备或者主机的。如果需要用到USB转串口,数据线可以通过USB_ UART接口连接到电脑!

       下面,看看开发板上的USB母座(USB_HOST)的位置,请将U盘插入这个接口上。


图59.2.2 开发板U盘母座


        59.3 程序设计 


       59.3.1 程序流程图


图59.3.1.1 USB U盘(Host)实验程序流程图


       59.3.2 程序解析

       这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。本实验,在实验34 汉字显示实验基础上,添加USB相关代码到工程中,最终得到如图59.3.2.1所示的工程:


图59.3.2.1 USB U盘实验工程分组


       1. USB驱动代码

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

/**
 * @brief   HAL库USB HCD初始化MSP回调函数
 * @param   hpcd: USB HCD句柄
 * @retval  无
 */
void HAL_HCD_MspInit(HCD_HandleTypeDef* hhcd)
{
    RCC_OscInitTypeDef rcc_osc_init_struct = {0};
    RCC_PeriphCLKInitTypeDef rcc_periph_clk_init_struct = {0};
    GPIO_InitTypeDef gpio_init_struct = {0};
    
    if (hhcd->Instance == USB_DRD_FS)
    {
        /* 配置时钟 */
        rcc_osc_init_struct.OscillatorType = RCC_OSCILLATORTYPE_HSI48;
        rcc_osc_init_struct.HSI48State = RCC_HSI48_ON;
        HAL_RCC_OscConfig(&rcc_osc_init_struct);
        rcc_periph_clk_init_struct.PeriphClockSelection = RCC_PERIPHCLK_USB;
        rcc_periph_clk_init_struct.UsbClockSelection = RCC_USBCLKSOURCE_HSI48;
        HAL_RCCEx_PeriphCLKConfig(&rcc_periph_clk_init_struct);
        
        /* 使能USB电源检测器 */
        HAL_PWREx_EnableVddUSB();
        HAL_PWREx_EnableUSBVoltageDetector();
        
        /* 使能时钟 */
        __HAL_RCC_USB_CLK_ENABLE();
        __HAL_RCC_GPIOA_CLK_ENABLE();
        
        /* 初始化DM、DP引脚 */
        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_USB;
        HAL_GPIO_Init(GPIOA, &gpio_init_struct);
        
        /* 配置中断 */
        HAL_NVIC_SetPriority(USB_DRD_FS_IRQn, 0, 0);
        HAL_NVIC_EnableIRQ(USB_DRD_FS_IRQn);
    }
}

       HAL_HCD_MspInit函数,用于使能USB时钟,选择内核时钟源,初始化IO口,设置中断等。该函数在HAL_HCD_Init函数里面被调用。

       接下来介绍的是USB OTG 中断服务函数,其定义如下:

/**
 * @brief   USB中断服务函数
 * @param   无
 * @retval  无
 */
void USB_DRD_FS_IRQHandler(void)
{
    HAL_HCD_IRQHandler(&g_hcd_handle);
}

       USB_DRD_FS_IRQHandler()是USB的中断服务函数,通过调用HAL_HCD_IRQHandler函数,实现对USB各种事务的处理。

       最后介绍的是USB主机初始化函数,其定义如下:

/**
 * @brief   USB主机初始化
 * @param   无
 * @retval  初始化结果
 * @arg     0: 成功
 * @arg     1: 失败
 */
uint8_t app_usbx_host_init(void)
{
    /* 初始化USBX内存资源 */
if (ux_system_initialize((UCHAR *)ux_device_memory_pool, 
USBX_DEVICE_MEMORY_POOL_SIZE, UX_NULL, 0) != UX_SUCCESS)
    {
        return 1;
    }
    
    /* 初始化USB主机堆栈 */
    if (ux_host_stack_initialize(app_usbx_host_event_callback) != UX_SUCCESS)
    {
        return 1;
    }
    
    /* 注册USB主机类 */
if (ux_host_stack_class_register(_ux_system_host_class_storage_name, 
ux_host_class_storage_entry) != UX_SUCCESS)
    {
        return 1;
    }
    
    /* 初始化USB HCD */
    g_hcd_handle.Instance = USB_DRD_FS;
    g_hcd_handle.Init.dev_endpoints = 8;
    g_hcd_handle.Init.Host_channels = 8;
    g_hcd_handle.Init.speed = USBD_FS_SPEED;
    g_hcd_handle.Init.phy_itface = HCD_PHY_EMBEDDED;
    g_hcd_handle.Init.Sof_enable = DISABLE;
    g_hcd_handle.Init.low_power_enable = DISABLE;
    g_hcd_handle.Init.vbus_sensing_enable = DISABLE;
    g_hcd_handle.Init.bulk_doublebuffer_enable = DISABLE;
    g_hcd_handle.Init.iso_singlebuffer_enable = DISABLE;
    if (HAL_HCD_Init(&g_hcd_handle) != HAL_OK)
    {
        return 1;
    }
    ux_host_stack_hcd_register(_ux_system_host_hcd_stm32_name,
                                     _ux_hcd_stm32_initialize,
                                     (ULONG)USB_DRD_FS,
                                     (ULONG)&g_hcd_handle);
    HAL_HCD_Start(&g_hcd_handle);
    
    return 0;
}

       app_usbx_host_init()函数,用于初始化USB底层设置,比如:使用内部PHY,使用全速模式,不使能VBUS检测等,详见以上代码。  

       ux_host_msc.c提供U盘和FATFS文件系统之间的输入/输出接口函数,这里总共有7个函数,首先是MSC磁盘初始化相关函数,其定义如下:

/**
 * @brief   MSC读写磁盘初始化
 * @param   storage_instance: 磁盘实例
 * @retval  无
 */
VOID ux_host_msc_read_write_init(UX_HOST_CLASS_STORAGE *storage_instance)
{
    g_storage = storage_instance;
}
 
/**
 * @brief   MSC读写磁盘反初始化
 * @param   无
 * @retval  无
 */
VOID ux_host_msc_read_write_deinit(VOID)
{
    g_storage = UX_NULL;
}
 
/**
 * @brief   MSC磁盘初始化
 * @param   无
 * @retval  磁盘初始化结果
 * @arg     0: 成功
 * @arg     1: 失败
 */
uint8_t ux_host_msc_disk_init(void)
{
    if (g_storage == UX_NULL)
    {
        return 1;
    }
    
    return 0;
}

       下面介绍的是MSC获取磁盘扇区大小函数,其定义如下:

/**
 * @brief   MSC获取磁盘扇区大小
 * @param   无
 * @retval  磁盘扇区大小
 */
uint32_t ux_host_msc_disk_get_sector_size(void)
{
    if (g_storage == UX_NULL)
    {
        return 0;
    }
    
    return (uint32_t)g_storage->ux_host_class_storage_sector_size;
}

       ux_host_msc_disk_get_sector_size函数,用于获取U盘扇区大小。

       下面介绍的是U盘读扇区操作函数,其定义如下:

/**
 * @brief   MSC读磁盘
 * @param   buf: 数据
 * @param   sector: 起始扇区
 * @param   cnt: 扇区数量
 * @retval  读磁盘结果
 * @arg     0: 成功
 * @arg     1: 失败
 */
uint8_t ux_host_msc_disk_read(uint8_t *buf, uint32_t sector, uint32_t cnt)
{
    if (g_storage == UX_NULL)
    {
        return 1;
    }
    
if (ux_host_class_storage_media_read(g_storage, (ULONG)sector, (ULONG)cnt, 
(UCHAR *)buf) != UX_SUCCESS)
    {
        return 1;
    }
    
    return 0;
}

       ux_host_msc_disk_read函数,用于从U盘指定位置,读取指定长度的数据。

       下面介绍的是U盘写扇区操作函数,其定义如下:

/**
 * @brief   MSC写磁盘
 * @param   buf: 数据
 * @param   sector: 起始扇区
 * @param   cnt: 扇区数量
 * @retval  写磁盘结果
 * @arg     0: 成功
 * @arg     1: 失败
 */
uint8_t ux_host_msc_disk_write(uint8_t *buf, uint32_t sector, uint32_t cnt)
{
    if (g_storage == UX_NULL)
    {
        return 1;
    }
    
if (ux_host_class_storage_media_write(g_storage, (ULONG)sector, (ULONG)cnt, 
(UCHAR *)buf) != UX_SUCCESS)
    {
        return 1;
    }
    
    return 0;
}

       ux_host_msc_disk_write函数,用于往U盘指定位置,写入指定长度的数据。

       最后介绍的是MSC获取磁盘扇区数量函数,其定义如下:

/**
 * @brief   MSC获取磁盘扇区数量
 * @param   无
 * @retval  磁盘扇区数量
 */
uint32_t ux_host_msc_disk_get_sector_number(void)
{
    if (g_storage == UX_NULL)
    {
        return 0;
    }
    
    return (uint32_t)g_storage->ux_host_class_storage_last_sector_number;
}

       ux_host_msc_disk_get_sector_number函数,可以用于获取U盘扇区数量。

       另外,我们需要修改FATFS相关文件来支持U盘,在diskio.c里面和FATFS完成对接,同时需要设置FATFS支持3个磁盘(SD卡、QSPI FLASH和U盘),在ffconf.h文件里面将FF_VOLUMES的宏定义值改成3,以支持FATFS操作U盘。

       2. main.c代码

       下面是main.c的代码,具体如下:

/**
 * @brief   USB主机设备连接回调函数
 * @param   无
 * @retval  无
 */
void app_usbx_host_device_connect_callback(void)
{
    uint32_t total;
    uint32_t free;
    
    LED1(0);
    f_mount(fs[2], "2:", 1);
    text_show_string(30, 110, 200, 16, "U盘连接成功!  ", 16, 0, BLUE);
    if (exfuns_get_free((uint8_t *)"2:", &total, &free) == 0)
    {
        text_show_string(30, 130, 200, 16, "U盘容量:      MB", 16, 0, BLUE);
        text_show_string(30, 150, 200, 16, "U盘可用空间:      MB", 16, 0, BLUE);
        lcd_show_num(102, 130, total >> 10, 5, 16, BLUE);
        lcd_show_num(134, 150, free >> 10, 5, 16, BLUE);
    }
    else
    {
        text_show_string(30, 130, 200, 16, "获取U盘容量失败", 16, 0, BLUE);
    }
}
 
/**
 * @brief   USB主机设备断开回调函数
 * @param   无
 * @retval  无
 */
void app_usbx_host_device_disconnect_callback(void)
{
    LED1(1);
    f_unmount("2:");
    text_show_string(30, 110, 200, 16, "等待U盘插入...", 16, 0, BLUE);
    lcd_fill(30, 130, 240, 166, WHITE);
}
 
int main(void)
{
    uint8_t t = 0;
    uint8_t res;
 
    HAL_Init();                           /* HAL库初始化 */
    HAL_ICACHE_Enable();                  /* 使能指令缓存 */
    sys_stm32_clock_init(250, 4, 2, 2);  /* 配置时钟,250MHz */
    delay_init(250);                      /* 初始化延时 */
    usart_init(115200);                   /* 初始化串口 */
    led_init();                           /* 初始化LED */
    lcd_init();                           /* 初始化LCD */
    usmart_dev.init(250);                 /* 初始化USMART */
    my_mem_init(SRAMIN);                  /* 初始化内部内存池 */
    exfuns_init();                        /* 为exfuns申请内存 */
    f_mount(fs[0], "0:", 1);              /* 挂载SD卡 */
    f_mount(fs[1], "1:", 1);              /* 挂载NOR Flash */
    
    /* 检查字库 */
    while (fonts_init() != 0)
    {
        lcd_show_string(30, 30, 200, 16, 16, "Font Error!  ", RED);
        delay_ms(500);
        lcd_show_string(30, 30, 200, 16, 16, "Please Check!", RED);
        delay_ms(500);
    }
    lcd_fill(30, 30, 30 + 200, 30 + 16, WHITE);
    
    text_show_string(30, 50, 200, 16, "STM32", 16, 0, RED);
    text_show_string(30, 70, 200, 16, "USB U盘(Host)实验", 16, 0, RED);
    text_show_string(30, 90, 200, 16, "ATOM@ALIENTEK", 16, 0, RED);
    text_show_string(30, 110, 200, 16, "等待U盘插入...", 16, 0, BLUE);
    
    /* USB主机初始化 */
    res = app_usbx_host_init();
    while (res != 0)
    {
        lcd_show_string(30, 130, 200, 16, 16, "USB Host Init Failed!", RED);
        delay_ms(500);
        lcd_show_string(30, 130, 200, 16, 16, "Please Check!        ", RED);
        delay_ms(500);
    }
    
    /* 每1毫秒运行一次独立模式下的USBX系统任务 */
    btim_timx_int_init(1000 - 1, 250 - 1);
    
    while (1)
    {
        if (++t == 20)
        {
            t = 0;
            LED0_TOGGLE();
        }
        
        delay_ms(10);
    }
}

       app_usbx_host_device_connect_callback函数,其作用是:当U盘连接成功,会挂载U盘、显示U盘连接成功/U盘容量等信息。 

       app_usbx_host_device_disconnect_callback函数,其作用是:当U盘断开连接,会取消挂载U盘、显示"等待U盘插入..."。

       main函数中,先进行系统和外设初始化,检查字库,显示实验信息、初始化USB等,然后每1毫秒运行一次独立模式下的USBX系统任务。


        59.4 下载验证

       将程序下载到开发板后,然后在USB_HOST端子插入U盘/读卡器(带卡),注意:此时USB SLAVE口不要插USB线到电脑,否则会干扰!

       等U盘成功识别后,便可以看到LCD显示U盘容量等信息,如图59.4.1所示:


图59.4.1 U盘识别成功


       此时,我们便可以通过USMART来测试U盘读写功能等,如图59.4.2所示。

       框图右边的序号对应的是下方调用的函数序号。按照1、2、3、4、1顺序调用对应的函数,便可以得到图59.4.2的测试结果。下面列出0~4序号对应的函数:

       0:list

       1:mf_scan_files("2:")

       2:mf_open("2:test u disk.txt",7)

       3:mf_write("这是一个测试,写入文件",22)

       4:mf_close()

       这里的测试主要是通过发送:mf_open("2:test u disk.txt",7),在U盘根目录创建test u disk.txt这个文件,然后发送:mf_write("这是一个测试,写入文件",22),写入“这是一个测试,写入文件”到这个文件里面,然后发送:mf_close(),关闭文件,完成一次文件创建。最后,发送:mf_scan_files("2:"),扫描U盘根目录文件,发现U盘根目录下多出了一个test u disk.txt的文件,说明U盘写入成功。

       这样,就完成了本实验的设计目的:实现U盘的读写操作。最后,大家还可以调用其它函数,实现相关功能测试,这里就不给大家一一演示了。


图59.4.2 测试U盘


来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70040860/viewspace-3107744/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2024-08-30

  • 博文量
    588
  • 访问量
    764178