F103C8T6移植FATFS文件系统 版本R0.15
wptr33 2025-07-06 17:25 16 浏览
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、需要源码的朋友可以私信我。
相关推荐
- MySQL进阶五之自动读写分离mysql-proxy
-
自动读写分离目前,大量现网用户的业务场景中存在读多写少、业务负载无法预测等情况,在有大量读请求的应用场景下,单个实例可能无法承受读取压力,甚至会对业务产生影响。为了实现读取能力的弹性扩展,分担数据库压...
- 3分钟短文 | Laravel SQL筛选两个日期之间的记录,怎么写?
-
引言今天说一个细分的需求,在模型中,或者使用laravel提供的EloquentORM功能,构造查询语句时,返回位于两个指定的日期之间的条目。应该怎么写?本文通过几个例子,为大家梳理一下。学习时...
- 一文由浅入深带你完全掌握MySQL的锁机制原理与应用
-
本文将跟大家聊聊InnoDB的锁。本文比较长,包括一条SQL是如何加锁的,一些加锁规则、如何分析和解决死锁问题等内容,建议耐心读完,肯定对大家有帮助的。为什么需要加锁呢?...
- 验证Mysql中联合索引的最左匹配原则
-
后端面试中一定是必问mysql的,在以往的面试中好几个面试官都反馈我Mysql基础不行,今天来着重复习一下自己的弱点知识。在Mysql调优中索引优化又是非常重要的方法,不管公司的大小只要后端项目中用到...
- MySQL索引解析(联合索引/最左前缀/覆盖索引/索引下推)
-
目录1.索引基础...
- 你会看 MySQL 的执行计划(EXPLAIN)吗?
-
SQL执行太慢怎么办?我们通常会使用EXPLAIN命令来查看SQL的执行计划,然后根据执行计划找出问题所在并进行优化。用法简介...
- MySQL 从入门到精通(四)之索引结构
-
索引概述索引(index),是帮助MySQL高效获取数据的数据结构(有序),在数据之外,数据库系统还维护者满足特定查询算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构...
- mysql总结——面试中最常问到的知识点
-
mysql作为开源数据库中的榜一大哥,一直是面试官们考察的重中之重。今天,我们来总结一下mysql的知识点,供大家复习参照,看完这些知识点,再加上一些边角细节,基本上能够应付大多mysql相关面试了(...
- mysql总结——面试中最常问到的知识点(2)
-
首先我们回顾一下上篇内容,主要复习了索引,事务,锁,以及SQL优化的工具。本篇文章接着写后面的内容。性能优化索引优化,SQL中索引的相关优化主要有以下几个方面:最好是全匹配。如果是联合索引的话,遵循最...
- MySQL基础全知全解!超详细无废话!轻松上手~
-
本期内容提醒:全篇2300+字,篇幅较长,可搭配饭菜一同“食”用,全篇无废话(除了这句),干货满满,可收藏供后期反复观看。注:MySQL中语法不区分大小写,本篇中...
- 深入剖析 MySQL 中的锁机制原理_mysql 锁详解
-
在互联网软件开发领域,MySQL作为一款广泛应用的关系型数据库管理系统,其锁机制在保障数据一致性和实现并发控制方面扮演着举足轻重的角色。对于互联网软件开发人员而言,深入理解MySQL的锁机制原理...
- Java 与 MySQL 性能优化:MySQL分区表设计与性能优化全解析
-
引言在数据库管理领域,随着数据量的不断增长,如何高效地管理和操作数据成为了一个关键问题。MySQL分区表作为一种有效的数据管理技术,能够将大型表划分为多个更小、更易管理的分区,从而提升数据库的性能和可...
- MySQL基础篇:DQL数据查询操作_mysql 查
-
一、基础查询DQL基础查询语法SELECT字段列表FROM表名列表WHERE条件列表GROUPBY分组字段列表HAVING分组后条件列表ORDERBY排序字段列表LIMIT...
- MySql:索引的基本使用_mysql索引的使用和原理
-
一、索引基础概念1.什么是索引?索引是数据库表的特殊数据结构(通常是B+树),用于...
- 一周热门
-
-
C# 13 和 .NET 9 全知道 :13 使用 ASP.NET Core 构建网站 (1)
-
程序员的开源月刊《HelloGitHub》第 71 期
-
详细介绍一下Redis的Watch机制,可以利用Watch机制来做什么?
-
假如有100W个用户抢一张票,除了负载均衡办法,怎么支持高并发?
-
Java面试必考问题:什么是乐观锁与悲观锁
-
如何将AI助手接入微信(打开ai手机助手)
-
redission YYDS spring boot redission 使用
-
SparkSQL——DataFrame的创建与使用
-
一文带你了解Redis与Memcached? redis与memcached的区别
-
如何利用Redis进行事务处理呢? 如何利用redis进行事务处理呢英文
-
- 最近发表
- 标签列表
-
- git pull (33)
- git fetch (35)
- mysql insert (35)
- mysql distinct (37)
- concat_ws (36)
- java continue (36)
- jenkins官网 (37)
- mysql 子查询 (37)
- python元组 (33)
- mybatis 分页 (35)
- vba split (37)
- redis watch (34)
- python list sort (37)
- nvarchar2 (34)
- mysql not null (36)
- hmset (35)
- python telnet (35)
- python readlines() 方法 (36)
- munmap (35)
- docker network create (35)
- redis 集合 (37)
- python sftp (37)
- setpriority (34)
- c语言 switch (34)
- git commit (34)