zoukankan      html  css  js  c++  java
  • GD32F30x_使用外部FLASH模拟U盘

    一、工具

      1、硬件:GD32F30x系列单片机

      2、编译环境:KEIL

      3、Flash芯片:GD25Q256DF

      4、一根能够单片机连接电脑的USB数据线

    二、需求分析

      类似于我们平常使用的U盘,当单片机与电脑通过USB数据线进行连接的时候,电脑能够识别出单片机通过外部Flash模拟出的U盘,在电脑上能够对该U盘进行文件的相互拷贝,并且重新上电后数据不丢失。通过对USB的了解,USB分设备(Device)模式和主机(Host)模式,使用单片机模拟U盘是让USB工作在设备(Device)模式下。

    三、查看当前使用单片支持的USB功能

      目前市面上出现的32位的基于ARM架构的单片机基本都具有USB功能,根据单片机性能的不同对USB功能的支持也有所不同,下面来看一下我当前使用的这款单片机具备USB的哪些功能。

      通过查阅单片机的用户手册,发现数据手册上有两种介绍,如下图所示:

       用户手册上介绍USBD只适用于GD32F303系列芯片,如下图所示,而我用的芯片是GD32F305系列的,所以这一部分的内容不看。

       USBFS的介绍中包含GD32F305,包含我当前使用的芯片型号,下面我们阅览这一块对USB的介绍内容。

       概述中基本已经说明了当前我使用芯片USB的所具有的功能,当然我本次需要的USB设备(Device)功能当然也包含在内,如下图所示:

       该芯片USBFS的结构图,如下图所示:

       以及信号线描述,如下图所示:

       (其实我也不太懂USB的协议,用户手册上的大多介绍看的也是一脸懵逼,因为只是用,对很多细节的地方并没有去深究,也望读者不要计较太多。)

    三、USB驱动库移植

      1、在官方提供的固件库中找到USB驱动文件,全部拷贝到自己的工程中。

       2、在官方的固件库例程中找到USBFS->USB_Device->MSC例程,打开该文件夹将inc和src中关于usb的文件全部拷贝到自己工程中,具体内容如下图所示:

      inc文件夹打开所示:

      src文件夹打开所示

      我这里拷贝到了自己工程中的usb文件夹中,同时在该文件夹中创建一个usbd_norflash_access.c文件和usbd_norflash_access.h文件为后面添加外部FLASH驱动程序使用,如下图所示:

       3、打开自己的工程结构,添加上面拷贝的文件,添加完成的结果如下图所示:

       4、指定添加的头文件路径,同时添加USBFS的宏定义USE_USBFS,如下图所示:

       由于官方给的例程使用的方式是单片机的内部SRAM模拟U盘,做完以上后可以尝试编译一下,错误应该会很少。当然也直接可以把官方提供的例程直接下载到自己单片机上,先观察一下效果。

    四、将外部Flash的读写驱动程序添加到USB驱动中

       从这里开始我们才算是添加自己的东西,前面的工作只是对官方库的移植(外部Flash的读写函数我在另一篇文章中有介绍,有兴趣的可以去查看,链接:https://www.cnblogs.com/wenhao-Web/p/14052266.html)。

      1、打开usbd_norflash_access.h和usbd_norflash_access.c文件,进行如下修改:

      usbd_norflash_access.c文件中的内容

    #include "./usb/usb_conf.h"
    #include "./usb/usbd_norflash_access.h"
    #include "./gd25qxx/gd25q256.h"
    
    /*!
        rief      read data from multiple blocks of internal NORFLASH
        param[in]  pbuf: pointer to user buffer
        param[in]  read_addr: address to be read
        param[in]  block_size: size of block
        param[in]  block_num: number of block
        param[out] none
        
    etval     operation status
    */
    uint32_t  norflash_multi_blocks_read (uint8_t *pbuf,
                                      uint32_t read_addr,
                                      uint16_t block_size,
                                      uint32_t block_num)
    {
        gd25q256df_read_data(pbuf, read_addr, block_num*block_size);
        return 0;
    }
    
    /*!
        rief      write data from multiple blocks of internal NORFLASH
        param[in]  pbuf: pointer to user buffer
        param[in]  write_addr: address to be write
        param[in]  block_size: size of block
        param[in]  block_num: number of block
        param[out] none
        
    etval     operation status
    */
    uint32_t norflash_multi_blocks_write (uint8_t *pbuf,
                                      uint32_t write_addr,
                                      uint16_t block_size,
                                      uint32_t block_num)
    {
        gd25q256df_write_data(pbuf, write_addr, block_num*block_size);
        return 0;
    }

      usbd_norflash_access.h文件中的内容

    #ifndef USBD_NORFLASH_ACCESS_H
    #define USBD_NORFLASH_ACCESS_H
    
    #include "gd32f30x.h"
    
    #define NORFLASH_BLOCK_SIZE         512            /* 固定每个块大小为512 */
    #define NORFLASH_BLOCK_NUM          65536         /* (1024*1024*32/512) 使用32Mbyte */
    
    /* function declarations */
    /* read data from multiple blocks of internal NORFLASH */
    uint32_t  norflash_multi_blocks_read (uint8_t *pbuf,
                                      uint32_t read_addr,
                                      uint16_t block_size,
                                      uint32_t block_num);
    /* write data from multiple blocks of internal NORFLASH */
    uint32_t  norflash_multi_blocks_write (uint8_t *pbuf,
                                       uint32_t write_addr,
                                       uint16_t block_size,
                                       uint32_t block_num);
    
    #endif /* USBD_NORFLASH_ACCESS_H */

       上面两个文件的内容很简单,就是读和写,和官方提供的SRAM实现的写法基本一样。

       2、接着是对usbd_bbb_scsi.c文件中的内容进行修改

      修改的内容主要是把官方提供的SRAM的内容替换成NORFLASH的内容即可。

    /* USB mass storage format capacities data */
    uint8_t format_capacities_data[FORMAT_CAPACITIES_DATA_LENGTH] =
    {
        0x00, 0x00, 0x00,                             /* reserved */
        0x08,                                          /* capacity list length */
        (uint8_t)((NORFLASH_BLOCK_NUM - 1) >> 24),   /* number of blocks (MSB) */
        (uint8_t)((NORFLASH_BLOCK_NUM - 1) >> 16),   /* number of blocks (MSB) */
        (uint8_t)((NORFLASH_BLOCK_NUM - 1) >> 8),    /* number of blocks (MSB) */
        (uint8_t)(NORFLASH_BLOCK_NUM - 1),           /* number of blocks (MSB) */
        0x02,                                         /* bit0 - bit1:descriptor code */
        (uint8_t)((NORFLASH_BLOCK_SIZE) >> 16),      /* block length (MSB) */
        (uint8_t)((NORFLASH_BLOCK_SIZE) >> 8),       /* block length (MSB) */
        (uint8_t)(NORFLASH_BLOCK_SIZE)               /* block length (MSB) */
    };
    
    /* USB mass storage read capacities data */
    uint8_t read_capacities_data[READ_CAPACITIES_DATA_LENGTH] = 
    {
        (uint8_t)((NORFLASH_BLOCK_NUM - 1) >> 24),   /* last logical block address (MSB) */
        (uint8_t)((NORFLASH_BLOCK_NUM - 1) >> 16),   /* last logical block address (MSB) */
        (uint8_t)((NORFLASH_BLOCK_NUM - 1) >> 8),    /* last logical block address (MSB) */
        (uint8_t)(NORFLASH_BLOCK_NUM - 1),           /* last logical block address (MSB) */
    
        (uint8_t)((NORFLASH_BLOCK_SIZE) >> 24),      /* block length in bytes (MSB) */
        (uint8_t)((NORFLASH_BLOCK_SIZE) >> 16),      /* block length in bytes (MSB) */
        (uint8_t)((NORFLASH_BLOCK_SIZE) >> 8),       /* block length in bytes (MSB) */
        (uint8_t)(NORFLASH_BLOCK_SIZE)               /* block length in bytes (MSB) */
    };

      读的修改部分:

      写的修改部分: 

       3、USB启动部分程序,其实就是把官方的那一套内容全部拷贝到自己工程中。

      相关变量的定义和初始化

    usb_core_handle_struct usbhs_core_dev =
    {
        .dev = 
        {
            .dev_desc = (uint8_t *)&device_descripter,
            .config_desc = (uint8_t *)&configuration_descriptor,    
            .strings = usbd_strings,                                
            .class_init = msc_init,                                    
            .class_deinit = msc_deinit,                                
            .class_req_handler = msc_req_handler,                    
            .class_data_handler = msc_data_handler
        },
    
        .udelay = delay_us,
        .mdelay = delay_ms
    };
    
    void usb_clock_config(void);
    void usb_gpio_config(void);
    void usb_interrupt_config(void);
    
    uint8_t timer_prescaler = 0;
    uint32_t usbfs_prescaler = 0;

      主函数中的内容

    /*!
        rief      main function
        param[in]  none
        param[out] none
        
    etval     none
    */
    int main(void)
    {
        /* configure 4 bits pre-emption priority */
        nvic_priority_group_set(NVIC_PRIGROUP_PRE4_SUB0);
        
        bsp_spi1_init();
        gd25q256df_init();
        flash_id = gd25q256df_read_id();
        if(flash_id == 0xC84019)
        {        
            /* configure USB clock */
            usb_clock_config();    
            /* USB timer configure */
            timer_nvic_init();
            /* USB device stack configure */
            usbd_init(&usbhs_core_dev, USB_FS_CORE_ID);
            /* USB interrupt configure */
            usb_interrupt_config();    
            
            /* check if USB device is enumerated successfully */
            while (usbhs_core_dev.dev.status != USB_STATUS_CONFIGURED) {}            
        }    
        
        while(1);
    }

      USB时钟配置

    /*!
        rief      configure USB clock
        param[in]  none
        param[out] none
        
    etval     none
    */
    void usb_clock_config(void)
    {
        uint32_t system_clock = rcu_clock_freq_get(CK_SYS);
      
        if (system_clock == 48000000) {
            usbfs_prescaler = RCU_CKUSB_CKPLL_DIV1;
            timer_prescaler = 3;
        } else if (system_clock == 72000000) {
            usbfs_prescaler = RCU_CKUSB_CKPLL_DIV1_5;
            timer_prescaler = 5;
        } else if (system_clock == 120000000) {
            usbfs_prescaler = RCU_CKUSB_CKPLL_DIV2_5;
            timer_prescaler = 9;
        } else {
            /*  reserved  */
        }
    
        rcu_usb_clock_config(usbfs_prescaler);
        rcu_periph_clock_enable(RCU_USBFS);
    }

      USB驱动所使用到的中断配置

    /*!
        rief      configure USB interrupt
        param[in]  none
        param[out] none
        
    etval     none
    */
    void usb_interrupt_config(void)
    {
        nvic_irq_enable((uint8_t)USBFS_IRQn, 4U, 0U);
    
        /* enable the power module clock */
        rcu_periph_clock_enable(RCU_PMU);
    
        /* USB wakeup EXTI line configuration */
        exti_interrupt_flag_clear(EXTI_18);
        exti_init(EXTI_18, EXTI_INTERRUPT, EXTI_TRIG_RISING);
        exti_interrupt_enable(EXTI_18);
    
        nvic_irq_enable((uint8_t)USBFS_WKUP_IRQn, 1U, 0U);
    }

      USBFS中断函数

    /*!
        rief      this function handles USBD interrupt
        param[in]  none
        param[out] none
        
    etval     none
    */
    void  USBFS_IRQHandler (void)
    {
        usbd_isr (&usbhs_core_dev);
    }

      USBFS唤醒中断函数

    /*!
        rief      this function handles USBD wakeup interrupt request.
        param[in]  none
        param[out] none
        
    etval     none
    */
    void USBFS_WKUP_IRQHandler(void)
    {
        if (usbhs_core_dev.cfg.low_power) {
            SystemInit();
            rcu_usb_clock_config(usbfs_prescaler);
    
            rcu_periph_clock_enable(RCU_USBFS);
    
            usb_clock_ungate(&usbhs_core_dev);
        }
    
        exti_interrupt_flag_clear(EXTI_18);
    }

      定时器中断函数

    /*!
        rief      this function handles Timer0 updata interrupt request.
        param[in]  none
        param[out] none
        
    etval     none
    */
    void TIMER0_UP_TIMER9_IRQHandler(void)
    {
        timer_delay_irq();
    }

       至此编译一下,没有问题,单片机与电脑连接正常就会在电脑上显示一个U盘,如果是第一次使用还需格式化。试试向该U盘中创建、拷贝文件是否正常,最好是用一个与该U盘大小差不多的文件测试。

       如下图所示,是我连接电脑模拟出的U盘效果(在使用的过程中发现一个问题,就是拷贝大文件传输速度不连续,不知道是为什么,希望有知道的给与指导)

     #endif

  • 相关阅读:
    jQuery 选择器:元素选择器、#id 选择器、.class 选择器
    jQuery 语法:文档就绪事件
    jQuery 安装:多种方法在网页中添加 jQuery
    jQuery 简介:什么事jQuery?为什么要学jQuery?
    SQL 快速参考:SQL语句语法
    SQL FORMAT() 函数:对字段的显示进行格式化
    Mybatis-Plus逻辑删除
    Mybatis-Plus中的ActiveRecord
    Mybatis-Plus使用Oracle的序列
    SpringBoot整合Mybatis-Plus
  • 原文地址:https://www.cnblogs.com/wenhao-Web/p/14052863.html
Copyright © 2011-2022 走看看