关于GD32F103+SD卡+USB+文件系统实现

本文介绍了如何在GD32F103单片机上使用SD卡和USB实现文件系统,包括初始化过程、移植代码、关键参数修改和中断管理,适合初学者参考。
摘要由CSDN通过智能技术生成

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的。

        此文章为小白类文章,希望各位大佬轻点喷,当然也想大佬们能指出意见,愿这篇文章能帮助到你。

  • 30
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值