百度360必应搜狗淘宝本站头条
当前位置:网站首页 > IT技术 > 正文

F103C8T6移植FATFS文件系统 版本R0.15

wptr33 2025-07-06 17:25 21 浏览

STM32F103C8T6芯片在W25Q64上移植FATFS(版本R0.15)

实现过程:

1、首先完成USART初始化和调试,用于传输信息到串口调试软件。

2、完成SPI相关参数配置及调试,用于单片机和存储芯片之间通讯。

3、完成W25Q64芯片初始化及基本操作函数。

4、移植FATFS到W25Q64芯片并进行文件读写操作。

一、首先第一步:配置USART

1、USART1想正常使用,需要完成以下步骤:

a、打开GPIO时钟;

b、初始化引脚;

c、打开USART1 时钟;

d、初始化USART1参数;

e、重映射fputc、fgetc函数;

f、编写USART1的函数。如SendByte、Receive_Byte、Send_String、Send_halfword等;

g、如果需要中断的话,需要配置NVIC及编写中断函数,本例中不需要中断。

2、具体各步如下:

a、RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //打开GPIOA时钟

b、初始化针脚:PA9为USART1的发送,PA10为USART1的接收脚。此处PA9的模式设置为复用推完输出(GPIO_Mode_AF_PP)。简单介绍一下GPIO的输出模式配置,GPIO输出有四种:开漏输出(GPIO_Mode_Out_OD)、推挽输出(GPIO_Mode_Out_PP)、复用推挽输出(GPIO_Mode_AF_PP)、复用开漏输出(GPIO_Mode_AF_OD)。根据下图GPIO基本结构图可以看出,USART1属于片上外设,所以选择复用功能输出,又因为开漏输出在信号为“1”时输出高阻态,而我们需要的USART的输出信号需用0和1的反转实现信号传输。因此USART1只能配置为复用推挽输出(GPIO_Mode_AF_PP)。

PA10为USART1的数据接收端,根据手册上的说明,可以配置为浮空输入或者带上拉输入(如下图)。GPIO输入模式配置有四种:浮空输入(GPIO_Mode_IN_FLOATING)、输入上拉(GPIO_Mode_IPU)、输入下拉(GPIO_Mode_IPD)、模拟输入(GPIO_Mode_AIN)。正常运行无通讯信号时,一般将针脚电平拉高,所以此处选择上拉输入。也可以选择浮空输入,由通讯对方将针脚信号拉高。

具体代码如下:


GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //初始化GPIO_Pin_9


GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA9针脚对应USART1的Tx


GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//一般默认为50MHz

GPIO_Init(GPIOA,&GPIO_InitStructure); //Tx,Transfer


GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //模式配置为上拉输入


GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; //PA10对应USART1的Rx

GPIO_Init(GPIOA,&GPIO_InitStructure); //Rx, Receive

b、打开USART1时钟

RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//开USART1时钟

c、配置USART1参数并启动USART1


USART_InitStructure.USART_BaudRate = 9600;//配置波特率为9600bps,


USART_InitStructure.USART_HardwareFlowControl =
USART_HardwareFlowControl_None;//硬件控制流无

USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;

//发送、接收模式


USART_InitStructure.USART_Parity = USART_Parity_No;//校验位无


USART_InitStructure.USART_StopBits = USART_StopBits_1;//停止位1位


USART_InitStructure.USART_WordLength = USART_WordLength_8b;//数据长度8位

USART_Init(USART1,&USART_InitStructure);//调用库函数进行初始化,其实就是把上述参数写入USART1的各对应寄存器中。配置完成之后记得调用USART_Cmd函数启动USART1。

d、重映射代码如下。重定向 c 库函数 printf 到串口,重定向后可使用 printf 函数;重定向 c 库函数 scanf 到串口,重写向后可使用 scanf、getchar 等函数。

int fputc(int ch, FILE *f)

{

Serial_SendByte((uint8_t)ch);

return (ch);

}


int fgetc(FILE *f)

{

while(USART_GetFlagStatus(USART1,USART_FLAG_RXNE) == RESET);

return (int)USART_ReceiveData(USART1);

}

3、可选择CH340G模块(USB转TTL)连接单片机和电脑,并在主函数中调用“printf”函数,在电脑串口助手中接收或发送数据。

二、SPI

1、SPI想正常使用,需要完成以下步骤:

a、打开GPIO时钟;

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//使用软件模拟SPI仅需打开GPIO时钟即可。

b、初始化引脚,按下图配置SPI针脚。

c、打开SPI 时钟;

RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);//如果使用软件模拟SPI,则不需要打开外设SPI时钟。


d、初始化SPI参数;可以定义为软件模拟SPI或者使用STM32自带的SPI外设。


#ifndef MySPI_Soft


void MySPI_Init(void)

{

GPIO_InitTypeDef GPIO_InitStructure;

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;

GPIO_InitStructure.GPIO_Pin = SPI_SS_PIN | SPI_MOSI_PIN | SPI_CLK_PIN;

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_Init(GPIOA,&GPIO_InitStructure);



GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//MISO定义为上拉输入,在Slave设备输出0时,此针脚为0,在输出为1时,针脚为1。如果没有输出时,针脚信号为1。

GPIO_InitStructure.GPIO_Pin = SPI_MISO_PIN;

GPIO_Init(GPIOA,&GPIO_InitStructure);


SPI_SS_SET;


SPI_CLK_RESET;//SPI时钟信号需要根据SPI的CPOL设置情况选择初始化时是置0还是置1。

}

#else

void MySPI_Init(void)

{

SPI_InitTypeDef SPI_InitStructure;

GPIO_InitTypeDef GPIO_InitStructure;


RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);


GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;

GPIO_InitStructure.GPIO_Pin = SPI_SS_PIN;

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_Init(GPIOA,&GPIO_InitStructure);//片选信号因为下面设置为软件控制,所以此处设置为推挽输出


GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;

GPIO_InitStructure.GPIO_Pin = SPI_CLK_PIN | SPI_MOSI_PIN;

GPIO_Init(GPIOA,&GPIO_InitStructure);//因为选择使用外设SPI,根据上面GPIO结构图得知,片上外设输出需要选择复用功能输出,因此此处选择复用推挽输出。


GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;

GPIO_InitStructure.GPIO_Pin = SPI_MISO_PIN;

GPIO_Init(GPIOA,&GPIO_InitStructure);//输入信号(针对STM32)和软件模拟SPI一致,选择上拉输入。



SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2;//配置SPI时钟波特率

SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;


SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;//定义时钟相位关系

SPI_InitStructure.SPI_CRCPolynomial = 7;


SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;//定义数据帧格式


SPI_InitStructure.SPI_Direction =
SPI_Direction_2Lines_FullDuplex;//配置通讯方式为2线全双工


SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;//大小端


SPI_InitStructure.SPI_Mode = SPI_Mode_Master;//设置STM32为主

SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;//配置软件触发

SPI_Init(SPI1, &SPI_InitStructure);

SPI_Cmd(SPI1, ENABLE);//以上代码为外设SPI的相关参数


//SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Tx, ENABLE);如需使用DMA转运数据,则需要打开DMA功能,本例中不需要,所以注释掉。


SPI_SS_SET;//拉高片选信号。

}

e、编写SPI交换数据函数,同样区分软件模拟或采用片上外设。

#ifndef MySPI_Soft


uint8_t MySPI_SwapByte(uint8_t SendByte)

{

uint8_t i,ReceiveByte=0x00;

for (i=0;i<8;i++)

{

if(SendByte & (0x80>>i))

SPI_MOSI_SET;

else

SPI_MOSI_RESET;

SPI_CLK_SET;

Delay_us(5);

if(SPI_MISO_GET)

ReceiveByte |= (0x80>>i);

SPI_CLK_RESET;

Delay_us(5);

}


return ReceiveByte;

}



#else


uint8_t MySPI_SwapByte(uint8_t SendByte)

{

while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) != SET);

SPI_I2S_SendData(SPI1,SendByte);

while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) != SET);

return SPI_I2S_ReceiveData(SPI1);

}


#endif


三、W25Q64

在W25Q64模块中主要完成W25Q64的读写擦除函数。在W25Q64中地址寻址范围0x000000~0x7F0000,共24位,分布如下:


其中:页地址占4位;扇区地址4位;块地址占8位,但是最高位未使用,实际占7位。共128块 、2048扇(128*16)、32768页(128*16*16)、每页256字节。合计(128*16*16*256)/1024/1024=8MB。

注意事项:

1、W25Q64最大写入单位为页(256B),最小擦除单位为扇区(4096B)。此参数非常重要,关系到是否能移植成功,一定要理解。

2、W25Q64的批量写入读出数据一定要先测试能够正常使用。包括批量写入超过1页(256字节)的数据、批量写入一个扇区的数据。因为在文件系统中最小执行单位是扇区。一定要确保一个扇区的数据写入正常。可以通过对Buff(大小一个扇区4096字节)赋值,然后写入数据,清空Buff,再读出数据存入Buff,输出Buff到USART,观察读出数据是否正确。

3、再写入数据之前一定要进行擦除,W25Q64芯片支持最小扇区擦除,在执行文件操作时,写多少擦多少。

4、本例中批量写入数据的数量以字节为单位。

四、FATFS移植

1、ffconfig.h修改:

#define FF_FS_NORTC 1

#define FF_MAX_SS 4096

#define FF_VOLUMES 1

#define FF_CODE_PAGE 437

#define FF_FS_READONLY 0

2、diskio.c文件修改

2.1

#include "W25Q64.h" //包含头文件

/* Definitions of physical drive number for each drive */

//#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 */

#define W25Q64 0 //定义W25Q64

2.2、修改五个函数,这四个函数与W25Q64中的读写函数息息相关。具体代码见源码。

disk_status()

disk_initialize()

disk_read()

disk_write()

disk_ioctl()

3、注意:disk_ioctl函数中,扇区数量配置为2048,扇区大小为4096,最小擦除扇区为1。

在disk_write函数中,第三个参数为扇区号,因此对应的写地址需要将扇区号左移12位,将扇区地址转换为W25Q64的实际地址。第四个参数为写入的扇区数量,也需要将第四个参数左移12位,转换为写入的Byte数。

五、测试

六、本次移植体会:

1、出现数据未擦除完成,直接开始写入数据,造成数据混乱。出现格式化不成功;文件系统写入数据不正确,再次挂载时报“FR_NO_FILESYSTEM”等异常情况。

2、一般情况下不要怀疑ff.c文件的正确性。开始出现异常后一直在f_mkfs函数中查找原因,将f_mkfs函数中的程序分析了半天也没有找到原因。一般来说出现问题的地方是W25Q64芯片的写入、读出函数。因此一定要首先测试“擦除”、“整页、多页写入读出”都正常的情况下再着手进行FATFS移植。

3、可以尝试设置FF_MAX_SS 512或者1024进行移植。由于W25Q64只支持扇区擦除(4096B),选择FF_MAX_SS =512时,文件系统最小操作单位是512B,因此需要自己编写擦除2页的函数(将对应的页先写入0xFF,再进行数据写入操作)。这个我自己没有尝试,感兴趣的朋友可以试一试。

4、本次移植参考了野火电子的零死角玩转STM32教程。

5、需要源码的朋友可以私信我。

相关推荐

oracle数据导入导出_oracle数据导入导出工具

关于oracle的数据导入导出,这个功能的使用场景,一般是换服务环境,把原先的oracle数据导入到另外一台oracle数据库,或者导出备份使用。只不过oracle的导入导出命令不好记忆,稍稍有点复杂...

继续学习Python中的while true/break语句

上次讲到if语句的用法,大家在微信公众号问了小编很多问题,那么小编在这几种解决一下,1.else和elif是子模块,不能单独使用2.一个if语句中可以包括很多个elif语句,但结尾只能有一个...

python continue和break的区别_python中break语句和continue语句的区别

python中循环语句经常会使用continue和break,那么这2者的区别是?continue是跳出本次循环,进行下一次循环;break是跳出整个循环;例如:...

简单学Python——关键字6——break和continue

Python退出循环,有break语句和continue语句两种实现方式。break语句和continue语句的区别:break语句作用是终止循环。continue语句作用是跳出本轮循环,继续下一次循...

2-1,0基础学Python之 break退出循环、 continue继续循环 多重循

用for循环或者while循环时,如果要在循环体内直接退出循环,可以使用break语句。比如计算1至100的整数和,我们用while来实现:sum=0x=1whileTrue...

Python 中 break 和 continue 傻傻分不清

大家好啊,我是大田。...

python中的流程控制语句:continue、break 和 return使用方法

Python中,continue、break和return是控制流程的关键语句,用于在循环或函数中提前退出或跳过某些操作。它们的用途和区别如下:1.continue(跳过当前循环的剩余部分,进...

L017:continue和break - 教程文案

continue和break在Python中,continue和break是用于控制循环(如for和while)执行流程的关键字,它们的作用如下:1.continue:跳过当前迭代,...

作为前端开发者,你都经历过怎样的面试?

已经裸辞1个月了,最近开始投简历找工作,遇到各种各样的面试,今天分享一下。其实在职的时候也做过面试官,面试官时,感觉自己问的问题很难区分候选人的能力,最好的办法就是看看候选人的github上的代码仓库...

面试被问 const 是否不可变?这样回答才显功底

作为前端开发者,我在学习ES6特性时,总被const的"善变"搞得一头雾水——为什么用const声明的数组还能push元素?为什么基本类型赋值就会报错?直到翻遍MDN文档、对着内存图反...

2023金九银十必看前端面试题!2w字精品!

导文2023金九银十必看前端面试题!金九银十黄金期来了想要跳槽的小伙伴快来看啊CSS1.请解释CSS的盒模型是什么,并描述其组成部分。...

前端面试总结_前端面试题整理

记得当时大二的时候,看到实验室的学长学姐忙于各种春招,有些收获了大厂offer,有些还在苦苦面试,其实那时候的心里还蛮忐忑的,不知道自己大三的时候会是什么样的一个水平,所以从19年的寒假放完,大二下学...

由浅入深,66条JavaScript面试知识点(七)

作者:JakeZhang转发链接:https://juejin.im/post/5ef8377f6fb9a07e693a6061目录...

2024前端面试真题之—VUE篇_前端面试题vue2020及答案

添加图片注释,不超过140字(可选)...

今年最常见的前端面试题,你会做几道?

在面试或招聘前端开发人员时,期望、现实和需求之间总是存在着巨大差距。面试其实是一个交流想法的地方,挑战人们的思考方式,并客观地分析给定的问题。可以通过面试了解人们如何做出决策,了解一个人对技术和解决问...