一、工具
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