读写SD是嵌入式系统中一个比较基础的功能,在很多应用中都可以用得上SD卡。折腾了几天,总算移植成功了 最新版Fatfs(Fatfs R0.09) ,成功读写SD卡下文件。
1. SD卡/TF卡 硬件接口
SD卡有两种操作接口,SDIO和SPI。 使用SDIO口的速度比较快,SPI的速度比较慢 。
SD卡引脚描述如下: SD卡SPI接法如下:
我使用的是正点原子的开发板,所以采用的是SPI接口的模式。
TF卡SDIO 模式和SPI模式 引脚定义:
可以发现Micro SD卡只有8个引脚是因为比SD卡少了一个Vss。使用TF转SD的卡套套在Micro SD卡上,这样一来大小就和SD卡一样大,这时候卡套上的9个引脚就和SD卡一样了,你可以完全当做SD卡来操作。
2. SD卡底层驱动
SD卡的操作比较复杂,需要多看看一些文档 。 这里附上SD底层驱动代码,代码说明详见注释
3. Fatfs 移植
FatFs 软件包中相关文件:
ffconf.h FatFs 模块配置文件
ff.h FatFs 和应用模块公用的包含文件
ff.c FatFs 模块
diskio.h FatFs and disk I/O 模块公用的包含文件
integer.h 数据类型定义
option 可选的外部功能
diskio.c FatFs 与disk I/O 模块接口层文件(不属于 FatFs 需要由用户提供)
FatFs 配置,文件系统的配置项都在 ffconf.h 文件之中:
(1) _FS_TINY :这个选项在R0.07 版本之中开始出现,在之前的版本都是以独立的文件出现,现在通过一个宏来修改使用起来更方便;
(2) _FS_MINIMIZE、_FS_READONLY、_USE_STRFUNC、_USE_MKFS、_USE_FORWARD 这些宏是用来对文件系统进行裁剪
(3) _CODE_PAGE :本选项用于设置语言码的类型
(4) _USE_LFN :取值为0~3,主要用于长文件名的支持及缓冲区的动态分配:
0:不支持长文件名;
1:支持长文件名存储的静态分配,一般是存储在BSS 段;
2:支持长文件名存储的动态分配,存储在栈上;
3:支持长文件名存储的动态分配,存储在堆上。
(5) _MAX_LFN :可存储长文件的最大长度,其值一般为(12~255),但是缓冲区一般占(_MAX_LFN + 1) * 2 bytes;
(6) _LFN_UNICODE :为1 时才支持unicode 码;
(7) _FS_RPATH :R0.08a 版本改动配置项,取值范围0~2:
0:去除相对路径支持和函数;
1:开启相对路径并且开启f_chdrive()和f_chdir()两个函数;
2:在1 的基础上添加f_getcwd()函数。
(8) _VOLUMES :支持的逻辑设备数目;
(9) _MAX_SS :扇区缓冲的最大值,其值一般为512;
(10) _MULTI_PARTITION:定义为1 时,支持磁盘多个分区;
(11) _USE_ERASE :R0.08a 新加入的配置项,设置为1 时,支持扇区擦除;
(12) _WORD_ACCESS :如果定义为1,则可以使用word 访问;
(13) _FS_REENTRANT :定义为1 时,文件系统支持重入,但是需要加上跟操作系统信号量相关的几个函数,函数在syscall.c 文件中;
(14) _FS_SHARE :文件支持的共享数目。
Fatfs 开源文件系统 从R0.07e 之后 版本开始就不再提供底层接口文件 diskio.c 模板,这里附上根据
以上SD卡底层驱动对应的 diskio.c 源码:
002 |
/*-----------------------------------------------------------------------*/ |
003 |
/* Inidialize a Drive */ |
005 |
DSTATUS disk_initialize ( |
006 |
BYTE drv /* Physical drive nmuber (0..) */ |
013 |
return STA_NOINIT; //仅支持磁盘0的操作 |
017 |
if(state == STA_NODISK) |
023 |
return STA_NOINIT; //其他错误:初始化失败 |
033 |
/*-----------------------------------------------------------------------*/ |
034 |
/* Return Disk Status */ |
036 |
DSTATUS disk_status ( |
037 |
BYTE drv /* Physical drive nmuber (0..) */ |
042 |
return STA_NOINIT; //仅支持磁盘0操作 |
055 |
/*-----------------------------------------------------------------------*/ |
059 |
BYTE drv, /* Physical drive nmuber (0..) */ |
060 |
BYTE *buff, /* Data buffer to store read data */ |
061 |
DWORD sector, /* Sector address (LBA) */ |
062 |
BYTE count /* Number of sectors to read (1..255) */ |
068 |
return RES_PARERR; //仅支持单磁盘操作,count不能等于0,否则返回参数错误 |
072 |
return RES_NOTRDY; //没有检测到SD卡,报NOT READY错误 |
077 |
if(count==1) //1个sector的读操作 |
079 |
res = SD_ReadSingleBlock(sector, buff); |
083 |
res = SD_ReadMultiBlock(sector, buff, count); |
088 |
if(SD_ReadSingleBlock(sector, buff)!=0) |
096 |
//处理返回值,将SPI_SD_driver.c的返回值转成ff.c的返回值 |
109 |
/*-----------------------------------------------------------------------*/ |
110 |
/* Write Sector(s) */ |
114 |
BYTE drv, /* Physical drive nmuber (0..) */ |
115 |
const BYTE *buff, /* Data to be written */ |
116 |
DWORD sector, /* Sector address (LBA) */ |
117 |
BYTE count /* Number of sectors to write (1..255) */ |
124 |
return RES_PARERR; //仅支持单磁盘操作,count不能等于0,否则返回参数错误 |
128 |
return RES_NOTRDY; //没有检测到SD卡,报NOT READY错误 |
134 |
res = SD_WriteSingleBlock(sector, buff); |
138 |
res = SD_WriteMultiBlock(sector, buff, count); |
150 |
#endif /* _READONLY */ |
154 |
/*-----------------------------------------------------------------------*/ |
155 |
/* Miscellaneous Functions */ |
158 |
BYTE drv, /* Physical drive nmuber (0..) */ |
159 |
BYTE ctrl, /* Control code */ |
160 |
void *buff /* Buffer to send/receive control data */ |
168 |
return RES_PARERR; //仅支持单磁盘操作,否则返回参数错误 |
171 |
//FATFS目前版本仅需处理CTRL_SYNC,GET_SECTOR_COUNT,GET_BLOCK_SIZ三个命令 |
176 |
if(SD_WaitReady()==0) |
192 |
case GET_SECTOR_COUNT: |
193 |
*(DWORD*)buff = SD_GetCapacity(); |
204 |
/*-----------------------------------------------------------------------*/ |
205 |
/* User defined function to give a current time to fatfs module */ |
206 |
/* 31-25: Year(0-127 org.1980), 24-21: Month(1-12), 20-16: Day(1-31) */ |
207 |
/* 15-11: Hour(0-23), 10-5: Minute(0-59), 4-0: Second(0-29 *2) */ |
208 |
DWORD get_fattime (void) |
这里的结构函数为Fatfs提供和SD卡的通信接口。 在 最新版本的Fatfs中还加入了对中文文件名的支持,需要修改 ffconf.h
#define _CODE_PAGE 936 //- Simplified Chinese GBK (DBCS, OEM, Windows)
同时应该添加 option/cc936.c文件。但是这个文件有700多K占相当大的ROM, 像stm32F103RBT6这种小FLASH的MCU根本不行 ,加入当前工程文件中代码将增加160KB 左右。
配置好Stm32的串口和SPI等IO口设置后,就可以使用Fatfs做一些文件操作了。
4. Fatfs 文件操作
文件分配表FAT(File AllocationTable)用来记录文件所在位置的表格.它对于硬盘的使用是非常重要的,假若丢失文件分配表,那么硬盘上的数据就会因无法定位而不能使用了。
Fatfs 文件系统减轻了操作SD卡的工作量,调用其提供的函数就可以方便的操作文件,读写删改等。
这里提供一个main.c 示例:
004 |
FRESULT scan_files (char* path); |
006 |
#define F_PUTS 1 //测试向文件写入字符串 |
007 |
#define F_READ 1 //测试从文件中读出数据 |
008 |
#define F_UNLINK 0 //测试删除文件 |
009 |
#define SCAN_FILES 1 //测试目录扫描 |
015 |
BYTE buffer[4096]; //以上变量作为全局变量 可以避免一些Bug |
024 |
NVIC_Configuration(); |
025 |
USART_Configuration(); |
027 |
GPIO_Configuration(); |
034 |
//如果data.txt存在,则打开;否则,创建一个新文件 |
035 |
res = f_open(&file, "0:/data.txt",FA_OPEN_ALWAYS|FA_READ|FA_WRITE ); |
039 |
printf("
f_open() fail ..
"); |
041 |
printf("
f_open() success ..
"); |
046 |
while(1){ //使用f_read读文件 |
047 |
res = f_read(&file, buffer, 1, &br); //一次读一个字节知道读完全部文件信息 |
053 |
printf("
f_read() fail ..
"); |
056 |
if(f_eof(&file)) {break;} |
059 |
/*if( f_gets(buffer,sizeof(buffer),&file) != NULL) //使用f_gets读文件 ,存在 Bugs 待调试 |
063 |
printf("
f_gets() fail ..
"); |
071 |
//res = f_lseek(&file,(&file)->fsize); |
072 |
res = f_lseek(&file,file.fsize); |
074 |
n = f_puts("
hello dog ..
", &file) ; //向文件末写入字符串 |
078 |
printf("
f_puts() fail ..
"); |
080 |
printf("
f_puts() success ..
"); |
087 |
res = f_unlink("test.jpg"); //前提SD下存在一个test.jpg |
091 |
printf("
f_unlink() fail ..
"); |
093 |
printf("
f_unlink() success ..
"); |
100 |
printf("
the directory files :
"); |
101 |
scan_files("/"); //扫描根目录 |
113 |
char* path /* Start node to be scanned (also used as work area) */ |
120 |
char *fn; /* This function is assuming non-Unicode cfg. */ |
122 |
static char lfn[_MAX_LFN + 1]; |
124 |
fno.lfsize = sizeof lfn; |
128 |
res = f_opendir(&dir, path); /* Open the directory */ |
132 |
res = f_readdir(&dir, &fno); /* Read a directory item */ |
133 |
if (res != FR_OK || fno.fname[0] == 0) break; /* Break on error or end of dir */ |
134 |
if (fno.fname[0] == '.') continue; /* Ignore dot entry */ |
136 |
fn = *fno.lfname ? fno.lfname : fno.fname; |
140 |
if (fno.fattrib & AM_DIR) { /* It is a directory */ |
141 |
sprintf(&path[i], "/%s", fn); |
142 |
res = scan_files(path); |
143 |
if (res != FR_OK) break; |
145 |
} else { /* It is a file. */ |
146 |
printf("
%s/%s
", path, fn); |
其中 目录扫描函数 scan_files( char * path) 参数格式如下:
这里使用到了f_puts()函数,所以必须在ffconf.h 中修改 #define _USE_STRFUNC 1
关键字:stm32 Fatfs 读写SD卡
引用地址:
stm32 Fatfs 读写SD卡
推荐阅读最新更新时间:2024-03-16 14:35
STM32入门学习笔记之STM32F103环境搭建(下)
(3)在工程文件夹中创建四个子文件夹,HE AD ERWARE,OBJECT,USER和SYSTEM,各文件夹内容如下所示。 USER:存放工程文件, 汇编 启动文件与主函数的c文件 SYSTEM:存放系统文件,串口1文件和滴答 时钟 文件 HEADERWARE:存放其他的驱动文件 OBJECT:存放编译过程的链接文件以及最终的HEX文件 (4)新建工程,输入工程保存路径后点击保存。 (5)选择目标 芯片 型号,我们使用的芯片型号是 STM32 F103ZET6 (6)点击工程设置,添加系统文件夹 点击红框选中的位置来新建文件夹,双击新建的文件夹修改名称,创建和步骤3目录一样的文件夹,如下图所示。
[单片机]
基于STM32的心电采集仪方案设计分析
如今,心血管类疾病已经成为威胁人类身体健康的重要疾病之一,而清晰有效的心电图为诊断这类疾病提供了依据,心电采集电路是心电采集仪的关键部分,心电信号属于微弱信号,其频率范围在0.03~100 Hz之间,幅度在0~5 mV之间,同时心电信号还掺杂有大量的干扰信号,因此,设计良好的滤波电路和选择合适的控制器是得到有效心电信号的关键。基于此,本文设计了以STM32为控制核心,AD620和OP07为模拟前端的心电采集仪,本设计简单实用,噪声干扰得到了有效抑制。 1、总体设计方案 心电采集包括模拟采集和数字处理两部分,本设计通过AgCl电极和三导联线心电采集线采集人体心电信号,通过前置放大电路,带通滤波电路,50 Hz双T陷波后再经主放大
[单片机]
几张图对比STM32各系列产品特性和外设兼容性
STM32产品系列特性比较 下面是STM32F0、F1、F2、F4、L1各产品系列的特性进行对比: 外设兼容性分析对比 对STM32进行过研究的朋友,特别是使用过寄存器开发的朋友应该很明白STM32片上外设,进行过对比的朋友,会发现,各系列MCU的片上外设很多相似之处,甚至完全一样。 下面将F1分别和F0、F2、F4、L1对比一下,大家看看有哪些差异。 1.STM32 F1 与 F0 系列外设兼容性分析对比 2.STM32 F1 与 F2 系列外设兼容性分析对比 3.STM32 F1 与 F4 系列外设兼容性分析对比 4.STM32 F1 与 L1 系列外设兼容性分析对比 Pin引脚对比 S
[单片机]
STM32也能轻松跑Linux了 !STM32MP135核心板开发板评测
上个月, 意法半导体推出了新一代64位Cortex-A35内核,主频高达1.5GHz的STM32MP2x系列微处理器(MPU) ,这让STM32MP系列处理器又上了一个新的台阶。 最近,收到了一套米尔基于STM32MP135核心板及开发板,首次接触STM32MPx处理器,体验了一下,感觉还不错。 STM32MP135与普通STM32单片机在性能、价格、应用场景等各方面都有差异。同时,STM32MP135并非局限于裸机、RTOS,而是定位于更高的Linux操作系统平台。 下面就结合【米尔基于STM32MP135核心板及开发板】给大家讲解一下STM32MP135强悍的性能以及开发入门等相关的内容。 硬件平台介绍
[单片机]
重返STM32之---RTC使用
STM3f10x的RTC时能涉及到的寄存器有RCC,BKP和RTC这三个大类寄存器;其中RCC主要控制了实时时钟和备份区的电源使能和时钟使能;RTC模块和时钟配置系统的寄存器是在后备区域的(即BKP),通过BKP后备区域来存储RTC配置的数据可以让在系统复位或待机模式下唤醒后RTC里面配置的数据维持不变;为此备份区还得涉及一个寄存器PWR,电源管理寄存器,备份区的写保护位在PWR- CR的第八位。 由于整个RTC都是位于后备区,而且RTC的APB1总线和内核的APB1总线是独立的,所以在系统复位和唤醒时,RTC和BKP的那些时钟不用从新配置;他们只受Backup domain software reset这个位和系统完全掉电的影响。
[单片机]
解决STM32 SPI 半残废 NSS无法拉高
众所周知,STM32 SPI是个半残废,NSS无法自动拉高,所以使用SPI 从机会一直使能,当主机是一对多的时候,就会出现SPI从机互相干扰的问题。 我利用GPIO中断,代替NSS引脚,使用过程如下代码所示 1.初始化SPI 的IO口,其中NSS引脚先不管。 void GPIO_SPI12_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; //----- 第1步:打开SPI部件的时钟 --------------------------------------------------------------------------------------------
[单片机]
stm32库函数GPIO_Init()解析
GPIO_Init函数是IO引脚的初始化函数,进行个个引脚的初始化配置,主要接受两个参数,一个是配置引脚组(GPIO_TypeDef* GPIOx),一个是配置的参数( GPIO_InitTypeDef* GPIO_InitStruct),具体如下 void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct) /*其中第一个参数为那组引脚,每组拥有16个引脚,每组都具有不同的寄存器配置地址,第二个参数是一个数据结构,也就是将基本配置信息放在这个数据结构里面,再将这个结构传入函数进行配置*/ //其中数据机构可以表示为如下 typedef str
[单片机]
STM32系统架构
在小容量、中容量和 大容量产品中,主系统由以下部分构成: ● 四个驱动单元: ─ Cortex-M3内核DCode总线(D-bus),和系统总线(S-bus) ─ 通用DMA1和通用DMA2 ● 四个被动单元 ─ 内部SRAM ─ 内部闪存存储器 ─ FSMC ─ AHB到APB的桥(AHB2APBx),它连接所有的APB设备 这些都是通过一个多级的AHB总线构架相互连接的,如下图所示: 在互联型产品中,主系统由以下部分构成: ● 五个驱动单元: ─ Cortex-M3内核DCode总线(D-bus),和系统总线(S-bus) ─ 通用DMA1和通用DMA2 ─ 以太网DMA ● 三个被动单元 ─ 内部SRAM ─ 内部闪存
[单片机]