1.引言
不知不觉中大学生活就要结束了,在实习中遇到了需要用GD32F103+SD卡+USB+文件系统的项目,就是让USB挂载SD卡让设备与电脑数据通信。其中的一些原理由于本人见解不够,该文章只做提供经验,望大佬们勿喷,也希望大佬们能提供见解。
2.初始化工作
在网上很少关于GD32F1系列在USB上的资料,特别还是关于MSC(大容量存储设备类),网上大多数都为STM32的资料,在本站中我也只看到了GD32F4的资料,所以对于小白的我来说无疑晴天霹雳,由于没有做过相关的项目,所以只能照着STM32的资料摸黑去写了,也就导致许多原理一知半解,还是希望大佬们勿喷。废话不多说了。
首先在兆易创新官网里有关于GD32F1的标准库包,我们需要的SD卡以及USB的协议都在里面有示例代码。如下图
解压后为这样
在Examples中就有关于SDIO和USB的协议代码,由于GD32F1中文手册上明确显示
这两个有何不同我也无法通彻说明,老哥们可以自行查找一番,所以我们需要的就是SDIO和USBD文件夹里的代码,SDIO里的.c文件和.h头文件不用做什么修改,直接用就行了,但要确保你的SD卡所用协议为SDIO而不是SPI,而且引脚要与GD32F1上的SDIO引脚相连。
SD卡就不做多赘述了,主要是在USBD上,官方给的是内部FLASH与USB挂载相连,我们需要改的就是将内部FLASH改为SD卡。
在这个文件夹下有工程实例,打开即可查看兆易官方写的代码,在internal_flash_if.c里就放着内部flash的初始化代码和读写函数,当然,我们这里用不着。大致查看一番可知,主函数里有时钟初始化,引脚初始化,USBD初始化,中断设置,连接USB,等待USB连接。
首先说明,USB是需要在48MHZ时钟下工作的,所以系统时钟是需要更改成96MHZ在进行2分频给到USB的,当然,在官方例程这里就是96MHZ然后进行了2分频的。过来是引脚初始化,USB需要一个上拉电阻来让USB进入高速模式啥的(解释的不清楚,可以去看一下大佬们的文章),这个引脚分配就要根据你手上的设备来设置了。过来是USBD的初始化,不用管,我也看不懂思密达,后面有时间再补吧。中断设置是很重要的分配,中断设置后文再说吧,因为需要扯到其他模块,得它抢占它,它又要抢占另一个。这里我的项目分组为3位抢占级,1位优先级。过来是USBD的连接,不用管,后面这个死循环删掉,或者改为延时,又或者直接什么都不要,因为这个循环的意义就是等待USB的插入连接,不插USB那就会一直在这个死循环里,项目并不是一直需要连接USB的,大家供电都是电池,所以这个死循环没必要放在项目中,拿来做调试就行。
3.移植工作
简单描述GD32F1官方例程后,我们就要将代码移植到自己的工程里了。
需要的就是刚刚说的SDIO和USBD下的文件,当然相信各位大佬们移植工作都能完美进行,这里就不作多描述了。
移植完就长这样,文件系统是以上版本的
#include "gd32f10x.h"
#include "ffconf.h"
#include "diskio.h"
#include "sdcard.h "
#define SECTOR_SIZE 512U //定义SD内存卡每扇区大小512字节
extern sd_card_info_struct sd_cardinfo; /* information of SD card */
/*-----------------------------------------------------------------------*/
/* Initialize Disk Drive */
/*-----------------------------------------------------------------------*/
DSTATUS disk_initialize(BYTE drv) /* Physical drive number (0) */
{
return 0;
}
/*-----------------------------------------------------------------------*/
/* Get Disk Status */
/*-----------------------------------------------------------------------*/
DSTATUS disk_status ( BYTE drv) /* Physical drive number (0) */
{
return 0;
}
/*-----------------------------------------------------------------------*/
/* Read Sector(s) */
/*-----------------------------------------------------------------------*/
DRESULT disk_read(
BYTE drv, /* Physical drive number (0) */
BYTE *buff, /* Pointer to the data buffer to store read data */
DWORD sector, /* Start sector number (LBA) */
BYTE count /* Sector count (1..255) */
)
{
if(count<=1)/* 1个sector的读操作 */
sd_block_read((UINT *)buff, sector << 9, SECTOR_SIZE);
else/* 多个sector的读操作 */
sd_multiblocks_read((UINT *)buff, sector << 9, SECTOR_SIZE, count);
return RES_OK;
}
/*-----------------------------------------------------------------------*/
/* Write Sector(s) */
/*-----------------------------------------------------------------------*/
#if _READONLY == 0
DRESULT disk_write (
BYTE drv, /* Physical drive number (0) */
const BYTE *buff, /* Pointer to the data to be written */
DWORD sector, /* Start sector number (LBA) */
BYTE count /* Sector count (1..255) */
)
{
if(count<=1)/* 1个sector的写操作 */
sd_block_write((UINT *)buff, sector << 9, SECTOR_SIZE);
else/* 多个sector的写操作 */
sd_multiblocks_write((UINT *)buff, sector << 9, SECTOR_SIZE, count);
return RES_OK;
}
#endif /* _READONLY */
/*-----------------------------------------------------------------------*/
/* Get current time */
/*-----------------------------------------------------------------------*/
DWORD get_fattime ()
{
return 0;
}
/*-----------------------------------------------------------------------*/
/* Miscellaneous Functions */
/*-----------------------------------------------------------------------*/
DRESULT disk_ioctl (
BYTE drv, // Physical drive number (0)
BYTE ctrl, // Control code
void *buff // Buffer to send/receive control data
)
{
DRESULT res;
switch(ctrl)
{
case GET_SECTOR_COUNT:
*(DWORD *)buff = sd_cardinfo.card_capacity / (sd_cardinfo.card_blocksize);
break;
case GET_SECTOR_SIZE:
*(WORD *)buff = sd_cardinfo.card_blocksize;
break;
case GET_BLOCK_SIZE:
*(DWORD *)buff = 1;
break;
}
return RES_OK;
}
这个是diskio.c的代码,我这个文件系统这也是跟着大佬写的,自己改了改,此篇文章主要还是讲解USB+SD卡,文件系统这部分大佬们要先完成。文件系统的初始化也就是SD卡的初始化,因为我在程序中SD卡初始化是比文件系统挂载前的,所以初始化没写东西。
那为什么要贴出文件系统的代码呢,因为文件系统是对SD卡进行读写,USB也是要对SD卡读写,所以是有关联的。可以拿来做参照。
4.更改代码
想要完成USB对SD卡读写,有五个最重要的地方,分别是
USB挂载SD卡的块字节,
USB挂载SD卡的块数量,
USB对SD卡的初始化,
USB对SD卡的读函数,
USB对SD卡的写函数
这五个修改好,USB就能顺利挂载上SD卡(当然这个观点也是我看了许多资料,许多大佬说的),首先SD卡的块字节毋庸置疑是512,这个是固定的,难就难在剩下四个的修改。这五个重中之重都放在了msc_mem.c文件中
在这个结构体里可以看到我们的重要参数都在这里,放一张STM32的USBD作比较
感谢正点原子原子哥,上图为正点原子读卡器实验代码中的usbd_storage.c的代码,STM32的这个文件与GD32的msc_men.c文件内容都大似相同的。区别在于,STM32的块字节和块数量是放在一个函数里的,而GD32是放在结构体中。下图为STM32的。
这里可以看到STM32可以通过SD卡的结构体去获取SD卡的块数量,而GD32的只能用常量去配置初始化,这就导致我想用STM32这种方式去配GD32的办法行不通。
上图为我最开始写的,我的SD卡为128M的,所以想这样去配,这显然是不对的。
经过不断查看,我最终跑去调用到块字节和块数量的函数里配置这两个重要参数。
这些代码在usbd_msc_scsi.c文件中,通过外部声明的SD卡这个结构体去获取容量,当然这个块数量是我在SDIO文件夹中的那个main.c的card_info_get();中得到的,参考这个函数中的这段代码
block_count = (sd_cardinfo.card_csd.c_size + 1)*1024;
block_size = 512;
printf("\r\n## Device size is %dKB ##", sd_card_capacity_get());
printf("\r\n## Block size is %dB ##", block_size);
printf("\r\n## Block count is %d ##", block_count);
块数量可由这样得到,然后就可以替换掉原先的块数量的宏定义了。下图为初始样子,大佬们可以对照一下。
通过这个底层函数直接修改块数量和块字节,所以在最开始的结构体中不管修改什么都用不着。块数量和块字节就完成了。
接下来是初始化,没啥好说的,直接上代码
/*!
\brief initialize the storage medium
\param[in] lun: logical unit number
\param[out] none
\retval status
*/
static int8_t mem_init (uint8_t lun)
{
// flash_init();
if (sd_io_init() == SD_OK)
{
return USBD_OK;
}
else
{
return USBD_FAIL;
}
return 0;
}
就是简单调用SD卡初始化就行。
最重要的读写函数,首先先上代码
/*!
\brief read data from the medium
\param[in] lun: logical unit number
\param[in] buf: pointer to the buffer to save data
\param[in] blkaddr: address of 1st block to be read
\param[in] blklen: number of blocks to be read
\param[out] none
\retval Status
*/
static int8_t mem_read (uint8_t lun, uint8_t *buf, uint32_t blkaddr, uint16_t blklen)
{
// flash_read_multi_blocks(buf, blkaddr, BLOCK_SIZE, blklen);
if(blklen<=1)/* 1个sector的读操作 */
{
sd_block_read((uint32_t *)buf, blkaddr, 512);
}
else/* 多个sector的读操作 */
{
sd_multiblocks_read((uint32_t *)buf, blkaddr, 512, blklen);
}
return USBD_OK;
}
/*!
\brief write data to the medium
\param[in] lun: logical unit number
\param[in] buf: pointer to the buffer to write
\param[in] blkaddr: address of 1st block to be read
\param[in] blkaddr: address of blocks to be written
\param[out] none
\retval Status
*/
static int8_t mem_write (uint8_t lun, uint8_t *buf, uint32_t blkaddr, uint16_t blklen)
{
// flash_write_multi_blocks (buf, blkaddr, BLOCK_SIZE, blklen);
if(blklen<=1)/* 1个sector的写操作 */
{
sd_block_write((uint32_t *)buf, blkaddr, 512);
}
else/* 多个sector的写操作 */
{
sd_multiblocks_write((uint32_t *)buf, blkaddr, 512, blklen);
}
return USBD_OK;
}
这部分代码是参照diskio.c文件写的,可以看上面的代码看一下。这里修改好读写函数,在后面需要用。还是要进入usbd_msc_scsi.c文件中。
这里有个逻辑,程序从scsi_read_format_capacity和scsi_read_capacity10中得到SD卡的相关信息,然后传到scsi_write10和scsi_read10函数中供函数调用,读写函数又分别传到scsi_process_write和scsi_process_read中。
scsi_write10和scsi_read10函数都基本没做什么更改,我就是把msc->scsi_blk_size[lun]直接改成了512。当然不改也无所谓,毕竟在之前的更改中我们已经把msc->scsi_blk_size[lun]设置成了512.
scsi_process_write和scsi_process_read中也不用做更改,不过就是我之前摸黑搞了好久,后面发现还是原版的香,给大佬们分享一下我的失败经历
可以看到注释的地方就是我自己的更改,因为我一直拿STM32的例程来对照,还有本站的GD32F4的来对照,一直都觉得自己要改成和别的芯片一样才能运行起来,后面经过几天的逻辑分析,发现并不用更改,直接用原来的就行了。引以为鉴,不要自作聪明,哭了呜呜呜。总得来说还是自己不清楚这些原理。
最后说一下中断方面的事,首先需要一个系统滴答的中断,其次就是SD卡的中断,最后是USB的中断,看一个博主说的,你在调用USB程序时,USB中断触发后会进入SD卡的相关程序从而SD卡会中断,SD卡中断又会进入系统滴答中断,所以在分配抢占级时,将USB的中断放在后面,主要还是要参照自己的项目来实现。我自己本身用了串口中断,定时器中断,系统定时器中断,SD卡中断,USB中断,我就是把USB的中断抢占级放后面,比较重要的中断放前面。当然你也可以装实时操作系统进来,我的还是处于裸机开发的。
5.实验结果展示
这里的名字没有更改,还是内部flash的名字,更改名字还是在msc.mem.c文件里。
6.总结
需要更改的地方msc_mem.c,这个文件是上层的,与单片机直接调用的。在usbd_conf.h中可以去配置你的上拉引脚,有个地方,MSC_MEDIA_PACKET_SIZE(在usbd_conf.h里)这里我配置成了2048,看有博主说这里调大可以加快USB速率,还望考察,不过好像不能超过2048,我也没去考证,我就看到在SD卡的读写函数里有个判断条件是不能超过2048的。
此文章为小白类文章,希望各位大佬轻点喷,当然也想大佬们能指出意见,愿这篇文章能帮助到你。