STM32F103外挂SPI Flash,内嵌FatFS文件系统,并通过USB接PC可访问FatFS内容

本文介绍了在STM32上移植FatFS文件系统及USB功能的过程,包括下载源码、修改diskio.c文件、配置文件系统参数、实现USB读卡器功能等步骤,并分享了遇到的问题及解决方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  • 早两天往Stm32上移植Fatfs文件系统,花了一些时间;
  • 后面又花了些时间移植Stm32的USB功能;

在这个过程中,自己摸索了很多东西,也向群里的高人请教过,所以希望把这部分东西记录下,方便自己以后和想寻找这方面知识的人查看。

下面按照上面的介绍分几步来介绍移植驱动所做的工作。

fatfs文件系统的移植

        首先从fatfs官网下载驱动http://elm-chan.org/fsw/ff/arc/ff13a.zip

        

           在下载的源码里面可以看到source文件下面有上面这些文件。这就是我们要移植的文件系统。

            其实为了方便移植前辈们在这个上面已经完善了很多很多,我们只需要做比较少的改动就可以应用起来;我们需要修改的文件是diskio.c这个文件里面的几个标准接口:

  • DSTATUS disk_initialize (BYTE pdrv);
  • DSTATUS disk_status (BYTE pdrv);
  • DRESULT disk_read (BYTE pdrv, BYTE* buff, DWORD sector, UINT count);
  • DRESULT disk_write (BYTE pdrv, const BYTE* buff, DWORD sector, UINT count);
  • DRESULT disk_ioctl (BYTE pdrv, BYTE cmd, void* buff);
  • DWORD get_fattime (void);

在diskio.c中定义了几个磁盘设备:

#define DEV_RAM        0    /* Example: Map Ramdisk to physical drive 0 */
#define DEV_MMC        1    /* Example: Map MMC/SD card to physical drive 1 */
#define DEV_USB        2    /* Example: Map USB MSD to physical drive 2 */

在对应的几个操作函数里面也有这几个设备对应的操作,但是我们只用一个spi-flash所以就只定义一个:

#define    SPI_FLASH    0

定义扇区大小,块大小,扇区个数

#define FLASH_SECTOR_SIZE 	(4*1024)
#define FLASH_BLOCK_SIZE	64
u16 FLASH_SECTOR_COUNT = 4*1024*1024/(4*1024);

获取磁盘状态直接返回成功:

DSTATUS disk_status (
	BYTE pdrv		/* Physical drive nmuber to identify the drive */
)
{
	DSTATUS stat;
	int result;
	switch (pdrv) {
		case SPI_FLASH :return RES_OK;
	}
	return STA_NOINIT;
}

初始化磁盘的函数,主要是把spi-flash初始化:

DSTATUS disk_initialize (
	BYTE pdrv				/* Physical drive nmuber to identify the drive */
)
{
	DSTATUS stat;
	int result;
	char t = 0;
	switch (pdrv) {
		case SPI_FLASH :
			//init spi-bus
			SPI_Flash_Init();
			if(SPI_FLASH_TYPE != FLASH_ADDRESS)
				stat = RES_NOTRDY;
			else
				stat = RES_OK;
			return stat;
	}
	return STA_NOINIT;
}

读取文件系统驱动接口:读取单位以sector(扇区)为单位

DRESULT disk_read (
	BYTE pdrv,		/* Physical drive nmuber to identify the drive */
	BYTE *buff,		/* Data buffer to store read data */
	DWORD sector,	/* Start sector in LBA */
	UINT count		/* Number of sectors to read */
)
{
	int result;
	switch (pdrv) {
	case SPI_FLASH :
		for(;count>0;count--)
		{
			SPI_Flash_Read(buff,sector*FLASH_SECTOR_SIZE,FLASH_SECTOR_SIZE);
			sector++;
			buff+=FLASH_SECTOR_SIZE;
		}
		return RES_OK;
	}
	return RES_PARERR;
}

写文件系统驱动接口:写入也是以sector为单位

DRESULT disk_write (
	BYTE pdrv,			/* Physical drive nmuber to identify the drive */
	const BYTE *buff,	/* Data to be written */
	DWORD sector,		/* Start sector in LBA */
	UINT count			/* Number of sectors to write */
)
{
	int result;
	switch (pdrv) {
	case SPI_FLASH :
		for(;count>0;count--)
		{
			SPI_Flash_Write((u8*)buff,sector*FLASH_SECTOR_SIZE,FLASH_SECTOR_SIZE);
			sector++;
			buff+=FLASH_SECTOR_SIZE;
		}
		return RES_OK;
	}
	return RES_PARERR;
}

文件系统一些控制命令:主要是获取一些磁盘的参数

DRESULT disk_ioctl (
	BYTE pdrv,		/* Physical drive nmuber (0..) */
	BYTE cmd,		/* Control code */
	void *buff		/* Buffer to send/receive control data */
)
{
	DRESULT res = RES_OK;
	int result;
	switch (pdrv) {
	case SPI_FLASH :
	    switch(cmd)
	    {
		    case CTRL_SYNC:
				res = RES_OK; 
		        break;	 
		    case GET_SECTOR_SIZE:
		        *(WORD*)buff = FLASH_SECTOR_SIZE;
		        res = RES_OK;
		        break;	 
		    case GET_BLOCK_SIZE:
		        *(WORD*)buff = FLASH_BLOCK_SIZE;
		        res = RES_OK;
		        break;	 
		    case GET_SECTOR_COUNT:
		        *(DWORD*)buff = FLASH_SECTOR_COUNT;
		        res = RES_OK;
		        break;
		    default:
		        res = RES_PARERR;
		        break;
	    }
	}
	return res;
}
文件系统时间的接口:
DWORD get_fattime (void)
{
    return ((rtc.w_year - 1980)<<25)|\
            (rtc.w_month<<21)|\
            (rtc.w_date<<16)|\
            (rtc.hour<<11)|\
            (rtc.min<<5)|\
            rtc.sec;
}

到这里驱动基本上就移植完了,下面要做的就是初始化文件系统:在ffconf.h文件中可以配置文件系统的参数和功能,因为一开始我们的flash是没有文件系统的,需要自己格式化一下,所以需要有创建文件系统的功能:

#define FF_USE_MKFS        1//默认是0
/* This option switches f_mkfs() function. (0:Disable or 1:Enable) */

创建文件系统:

首先尝试挂载文件系统

挂载成功则返回成功,挂载失败则创建文件系统

创建失败则返回失败,创建成功再一次尝试挂载

static FATFS sysFs;
static BYTE gFsWork[FF_MAX_SS]; /* Work area (larger is better for processing time) */
static BYTE gFsInited = 0;
static int fileSystemInit()
{
	FRESULT res = 0;
	res = f_mount (&sysFs,"0:",1);
	if(res != 0)
	{
		res = f_mkfs("0:", FM_FAT,0, gFsWork, sizeof gFsWork);
		if(res == 0)
		{
			res = f_mount (&sysFs,"0:",1);
			if(res == 0)
			{
				gFsInited = 1;
				return 0;
			}
			else
				return -1;
		}
		else
			return -1;
	}
	else
		gFsInited = 1;
	return 0;
}
文件系统测试:
static int fileSystemTest()
{
	FIL fil;
	UINT bw;
	FRESULT res = 0;
	if(gFsInited)
	{
		static char i = 0;
		char b[5] = {0};
		res = f_open(&fil, "0:/hello.txt", FA_OPEN_APPEND | FA_WRITE);
		if (res == 0)
		{
			/* Write a message */
			sprintf(b,"%d",i++%10);
			res = f_write(&fil, b, 1, &bw);
			f_close(&fil);
		}

		res = f_open(&fil, "0:/hello.txt", FA_READ);
		if (res == 0)
		{
			/* read a message */
			char buffer[256];
			memset(buffer,0,sizeof(256));
			res = f_read(&fil,buffer,255,&bw);
			printf("read:%d|%d =>>%s\r\n",res,bw,buffer);
			f_close(&fil);
		}
	}
	return 0;
}
测试输出:
read:0|1 =>>0
read:0|2 =>>01
read:0|3 =>>012
read:0|4 =>>0123
read:0|5 =>>01234
read:0|6 =>>012345
read:0|7 =>>0123456
read:0|8 =>>01234567
read:0|9 =>>012345678

usb功能的移植

这部分驱动用原子哥的实验”实验50 USB读卡器实验“中的文件:

拷贝例程中的驱动:USB文件下到项目中,然后把刚才项目中初始化和测试文件系统的代码注释掉。

第一步:案例中定义了两个存储介质(SD卡和SPI-Flash),但是我们只需要SPI-Flash,所以将

#define MAX_LUN  0//原来为1
然后把磁盘操作改为只是对磁盘0有效:
u16 MAL_Write(u8 lun, u32 Memory_Offset, u32 *Writebuff, u16 Transfer_Length)
{	  
	u8 STA;
	switch (lun)
	{
		case 0:
			STA=0;
			SPI_Flash_Write((u8*)Writebuff, Memory_Offset, Transfer_Length);   		  
			break; 
		default:
			return MAL_FAIL;
	}
	if(STA!=0)return MAL_FAIL;
	return MAL_OK;
}

u16 MAL_Read(u8 lun, u32 Memory_Offset, u32 *Readbuff, u16 Transfer_Length)
{
	u8 STA;
	switch (lun)
	{
		case 0:
			STA=0;
			SPI_Flash_Read((u8*)Readbuff, Memory_Offset, Transfer_Length);   		  
			break;	  
		default:
			return MAL_FAIL;
	}
	if(STA!=0)return MAL_FAIL;
	return MAL_OK;
}

u16 MAL_GetStatus (u8 lun)
{
    switch(lun)
    {
    case 0:
        return MAL_OK;
    case 1:
        return MAL_FAIL;
    case 2:
        return MAL_FAIL;
    default:
        return MAL_FAIL;
    }
}
初始化的地方添加SPI-Flash初始化的操作和U盘信息初始化:
u16 MAL_Init(u8 lun)
{
  u16 status = MAL_OK;  
  switch (lun)
  {
    case 0:		
		Mass_Memory_Size[0]= 1024*1024*4;//4M字节
		Mass_Block_Size[0] = 512;
		Mass_Block_Count[0]= Mass_Memory_Size[0]/Mass_Block_Size[0];
		SPI_Flash_Init();
		if(SPI_FLASH_TYPE != FLASH_ADDRESS)
			status = RES_NOTRDY;
		else
			status = RES_OK;
		break;
	default:
      return MAL_FAIL;
  }
  return status;
}

第二步:先将案例中的和本项目中相冲突的代码去掉,例如LED部分;

实验案例中的这两个文件先不要加入到项目中stm32f10x_it.h、stm32f10x_it.c,因为我自己起的案例中已经有这两个文件了,只要将stm32f10x_it.c中的中断函数和头文件包含内容复制到项目中的stm32f10x_it.c;

还有几个头文件包含问题就能编译过。

第三步:开始初始化USB
static void usb_port_set(u8 enable)
{
	GPIO_InitTypeDef  GPIO_InitStructure;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	if(enable)
		_SetCNTR(_GetCNTR()&(~(1<<1)));//退出断电模式
	else
	{	  
		_SetCNTR(_GetCNTR()|(1<<1));  // 断电模式
		GPIO_InitStructure.GPIO_Pin =GPIO_Pin_12;
		GPIO_InitStructure.GPIO_Mode =GPIO_Mode_IPD;
		GPIO_InitStructure.GPIO_Speed =GPIO_Speed_50MHz;
		GPIO_Init(GPIOA, &GPIO_InitStructure); 
		GPIO_ResetBits(GPIOA,GPIO_Pin_12);
	}
}
//USB口初始化
static void UsbPortInit()
{
	//USB先断开再连接
	usb_port_set(0);
	delay_ms(100);
	usb_port_set(1);
	//USB中断配置
	USB_Interrupts_Config();
	//USB时钟配置
	Set_USBClock();
	//USB初始化
	USB_Init();
}

当USB-device插入的时候,从设备需要将USB-D+通过1.5K电阻拉高,让主机识别到这是一个高速USB设备,所以在Mass_init()的函数中有

void MASS_init()
{
    MAL_Config();//在mass初始化的时候添加磁盘初始化动作
    ......
    /* Connect the device */
    PowerOn();
    ......
}

RESULT PowerOn(void)
{
    ......
    /*** cable plugged-in ? ***/
    /*while(!CablePluggedIn());*/
    USB_Cable_Config(ENABLE);
    ......
}
当USB上电时,将电阻上拉到3.3V,让主机识别到
void USB_Cable_Config (FunctionalState NewState)
{
	GPIO_InitTypeDef GPIO_InitStructure;
  	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
	GPIO_Init(GPIOC, &GPIO_InitStructure);
	if (NewState != DISABLE)
		GPIO_SetBits(GPIOC,GPIO_Pin_11);
	else
		GPIO_ResetBits(GPIOC,GPIO_Pin_11);
}
第四步:添加USB状态检测
static void Fatfs_task(void *pdata)
{
	u8 offline_cnt=0;
	u8 tct=0;
	u8 USB_STA;
	u8 Divece_STA;
	//log_debug("erase chip\r\n");SPI_Flash_Erase_Chip();
	RTC_Init();//this will delay starting
	printf("Now:%04d-%02d-%02d %02d:%02d:%02d\r\n",
		LocalSetting.rtc.w_year,LocalSetting.rtc.w_month,LocalSetting.rtc.w_date,
		LocalSetting.rtc.hour,LocalSetting.rtc.min,LocalSetting.rtc.sec);
#if 0
	if(fileSystemInit())
		printf("fileSystemInit fail\r\n");
	else
		printf("fileSystemInit ok\r\n");
#endif
	UsbPortInit();
	while(1)
	{
		delay_ms(1);
		if(USB_STA!=USB_STATUS_REG)//状态改变了 
		{
			if(USB_STATUS_REG&0x01)//正在写 	  
			{
				//printf("USB Writing...\r\n");//提示USB正在写入数据	 
			}
			if(USB_STATUS_REG&0x02)//正在读
			{
				printf("USB Reading...\r\n");//提示USB正在读出数据		 
			}
			if(USB_STATUS_REG&0x04)
				printf("USB Write Err\r\n");//提示写入错误
			if(USB_STATUS_REG&0x08)
				printf("USB Read  Err\r\n");//提示读出错误
			USB_STA=USB_STATUS_REG;//记录最后的状态
		}
		if(Divece_STA!=bDeviceState) 
		{
			if(bDeviceState==CONFIGURED)
				printf("USB Connected\r\n");//提示USB连接已经建立
			else
				printf("USB DisConnected\r\n");//提示USB被拔出了
			Divece_STA=bDeviceState;
		}
		tct++;
		if(tct==200)
		{
			tct=0;
			if(USB_STATUS_REG&0x10)
			{
				offline_cnt=0;//USB连接了,则清除offline计数器
				bDeviceState=CONFIGURED;
			}
			else//没有得到轮询 
			{
				offline_cnt++;	
				if(offline_cnt>10)
					bDeviceState=UNCONNECTED;//2s内没收到在线标记,代表USB被拔出了
			}
			USB_STATUS_REG=0;
		}
	}
}

编译下载后就能看到pc端的U盘了,格式化以后就能使用。

但是发现格式化的时候只有480KB的大小。

经过很久的查找资料发现将文件系统的操作单位改为和USB的Mass_Block_Size大小一样为512时就能检测到4MB的U盘。

#define FLASH_SECTOR_SIZE     512//(4*1024)

有几点需要注意的是:

  1. Mass_Block_Size的大小只能设置为512,不然PC识别USB大容量存储设备
  2. 文件系统的操作单位大小FLASH_SECTOR_SIZE要和Mass_Block_Size一致
  3. 最好在文件系统准备好以后再去挂载USB设备
  4. 如果单片机在文件系统中创建或者修改文件以后,在PC端是不能被立马查看到的,需要重新插拔。

其中有些问题可能还不是很理解或者有描述不准确的地方,希望大家一起探讨。

<think>好的,用户问的是执行`res_flash=f_mkfs("1:",0,0);`时出现`res_flash=12`的原因。首先,我需要回忆一下FATFS库中的返回代码。FRESULT类型的结果代码中,12对应的是FR_INVALID_DRIVE,也就是无效的驱动器号。 用户提供的函数调用参数是"1:",所以可能驱动器1没有被正确配置或者不存在。这时候要考虑用户是否在FATFS中正确挂载了驱动器1。通常,在FATFS中,驱动器号从0开始,比如0:、1:等,但需要确保底层磁盘初始化函数(比如disk_initialize)支持该驱动器号。 下来,可能用户启用了多分区,但配置不正确。比如,_MULTI_PARTITION设置可能需要检查,或者物理驱动器号是否正确。例如,驱动器1可能对应不同的物理存储设备,而该设备没有正确初始化或不存在。 另外,底层驱动的问题也需要考虑。比如disk_initialize函数是否对驱动器1返回成功,或者存储设备本身有问题,比如未连或硬件故障。 还可能用户在使用f_mkfs时没有提供足够的工作缓冲区,但根据之前的回答,用户之前的示例中提到了需要工作缓冲区,但在这个函数调用中用户只传入了三个参数,而实际f_mkfs可能需要更多参数,比如工作缓冲区和其大小。可能用户漏掉了这些参数,导致函数调用错误,从而返回FR_INVALID_DRIVE。不过根据FATFS的文档,f_mkfs的参数可能根据版本不同而有所变化。例如,旧版本可能需要5个参数,包括工作区缓冲区,而用户只传了三个,这可能导致参数错误,进而返回错误代码。但根据用户提供的函数调用,可能用户使用的是旧版本或不同的配置,需要确认。 另一个可能性是路径参数错误,比如驱动器号超出了配置的_VOLUMES数量。例如,如果_VOLUMES设置为1,那么只能使用0:,而使用1:就会返回FR_INVALID_DRIVE。 需要检查用户的ffconf.h配置,特别是_VOLUMES和_MULTI_PARTITION的设置。如果_VOLUMES设置为1,那么驱动器1是不可用的。同时,如果启用了多分区,驱动器号的格式是否正确,比如“1:”是否有效,或者是否需要更详细的路径。 另外,用户可能误用了驱动器号,比如实际硬件只有一个存储设备,但用户尝试格式化第二个驱动器。这时候需要确认物理驱动器是否存在,以及对应的硬件初始化是否正确。 总结可能的原因: 1. 驱动器号无效,可能因为配置中_VOLUMES设置不足。 2. 底层驱动未正确支持驱动器1,比如disk_initialize返回错误。 3. 函数调用参数不正确,比如缺少工作缓冲区参数。 4. 存储设备硬件问题,如未连或初始化失败。 5. 多分区配置错误,导致驱动器1的分区不存在。 下来需要询问用户是否检查了这些方面,或者提供更多配置和代码细节,以便进一步诊断。比如检查ffconf.h中的_VOLUMES设置,确认disk_initialize对驱动器1的处理,以及函数调用的参数是否正确。当</think>调用 `res_flash = f_mkfs("1:", 0, 0);` 后返回 `res_flash = 12`(即 `FR_INVALID_DRIVE`)时,表示 **逻辑驱动器号无效** 或 **底层驱动未正确初始化**。以下是具体原因和排查方向: --- ### **错误码 `FRESULT = 12` 的含义** 在 FATFS 中,`FR_INVALID_DRIVE` 表示: - 指定的驱动器号(如 `"1:"`)在系统中未定义。 - 底层物理驱动(如 SD 卡、Flash 芯片)未正确初始化或不可用。 --- ### **常见原因及解决方法** #### 1. **逻辑驱动器号未配置** - **问题**: FATFS 的 `_VOLUMES` 宏(在 `ffconf.h` 中定义)限制了最大支持的逻辑驱动器数量。 例如,若 `_VOLUMES` 设为 `1`,则仅允许使用 `"0:"`,而 `"1:"` 会触发此错误。 - **解决**: 在 `ffconf.h` 中增大 `_VOLUMES` 的值,确保覆盖所有需要的逻辑驱动器号。 --- #### 2. **物理驱动未初始化** - **问题**: 逻辑驱动器 `"1:"` 对应的物理设备(如 SPI Flash、SD 卡)未正确初始化,导致 FATFS 无法访问。 - **排查步骤**: 1. 确认底层驱动代码(如 `disk_initialize()` 函数)支持多驱动器号(例如 `0` 和 `1`)。 2. 检查硬件连是否正常(如 SPI 引脚、电源)。 3. 在调用 `f_mkfs` 前,先尝试挂载驱动器: ```c FATFS fs; FRESULT res = f_mount(&fs, "1:", 1); // 挂载驱动器1 if (res != FR_OK) { // 挂载失败,说明物理驱动问题 } ``` -- #### 3. **多分区配置冲突** - **问题**: 若启用了 `_MULTI_PARTITION` 功能,逻辑驱动器 `"1:"` 可能需要明确指定分区号(如 `"1:0"`)。 - **解决**: 根据 FATFS 的多分区配置,调整路径格式: ```c res_flash = f_mkfs("1:0", 0, 0); // 指定驱动器1的第0个分区 ``` --- #### 4. **函数参数缺失** - **问题**: `f_mkfs` 在某些版本中需要额外参数(如工作缓冲区),若未传入会导致未定义行为。 标准函数原型为: ```c FRESULT f_mkfs ( const TCHAR* path, BYTE sfd, UINT au, void* work, // 工作缓冲区 UINT len // 缓冲区大小 ); ``` - **解决**: 补充工作缓冲区参数: ```c BYTE work_buf[_MAX_SS]; // 缓冲区大小通常为扇区大小(如 512 字节) res_flash = f_mkfs("1:", 0, 0, work_buf, sizeof(work_buf)); ``` --- #### 5. **硬件或文件系统损坏** - **问题**: 存储设备物理损坏或已有文件系统结构损坏(如异常断电导致分区表丢失)。 - **解决**: 1. 尝试重新初始化硬件(如复位控制器、重新插拔 SD 卡)。 2. 使用低级工具(如 `fdisk`、`chip erase`)彻底擦除存储设备。 --- ### **调试建议** 1. **检查 `ffconf.h` 配置**: - 确保 `_VOLUMES` 支持驱动器号 `1`。 - 确认 `_MULTI_PARTITION` 和 `_USE_MKFS` 已启用。 2. **验证物理驱动状态**: 在调用 `f_mkfs` 前,通过底层函数直读写物理扇区,确认设备可正常访问: ```c BYTE buffer[512]; DRESULT dres = disk_read(1, buffer, 0, 1); // 读取驱动器1的第0扇区 if (dres != RES_OK) { // 物理驱动访问失败 } ``` 3. **简化测试用例**: 尝试格式化默认驱动器(如 `"0:"`),排除代码逻辑问题: ```c res_flash = f_mkfs("0:", 0, 0, work_buf, sizeof(work_buf)); ``` --- ### **总结** - **核心原因**:驱动器号 `"1:"` 未被系统识别或底层驱动异常。 - **关键排查点**: - FATFS 配置(`_VOLUMES`、`_MULTI_PARTITION`)。 - 物理设备初始化及硬件连。 - 函数参数完整性(尤其是工作缓冲区)。
评论 27
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值