一、工具
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" /*! \brief 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 \retval 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; } /*! \brief 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 \retval 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;
主函數中的內容
/*! \brief main function \param[in] none \param[out] none \retval 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時鍾配置
/*! \brief configure USB clock \param[in] none \param[out] none \retval 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驅動所使用到的中斷配置
/*! \brief configure USB interrupt \param[in] none \param[out] none \retval 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中斷函數
/*! \brief this function handles USBD interrupt \param[in] none \param[out] none \retval none */ void USBFS_IRQHandler (void) { usbd_isr (&usbhs_core_dev); }
USBFS喚醒中斷函數
/*! \brief this function handles USBD wakeup interrupt request. \param[in] none \param[out] none \retval 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); }
定時器中斷函數
/*! \brief this function handles Timer0 updata interrupt request. \param[in] none \param[out] none \retval none */ void TIMER0_UP_TIMER9_IRQHandler(void) { timer_delay_irq(); }
至此編譯一下,沒有問題,單片機與電腦連接正常就會在電腦上顯示一個U盤,如果是第一次使用還需格式化。試試向該U盤中創建、拷貝文件是否正常,最好是用一個與該U盤大小差不多的文件測試。
如下圖所示,是我連接電腦模擬出的U盤效果(在使用的過程中發現一個問題,就是拷貝大文件傳輸速度不連續,不知道是為什么,希望有知道的給與指導):
#endif