读写SD是嵌入式系统中一个比较基础的功能,在很多应用中都可以用得上SD卡。折腾了几天,总算移植成功了 最新版Fatfs(Fatfs R0.09) ,成功读写SD卡下文件。

FatFs (
http://elm-chan.org/fsw/ff/00index_e.html)是一个通用的文件系统模块,用于在小型嵌入式系统中实现FAT文件系统。 FatFs 的编写遵循ANSI C,因此不依赖于硬件平台。它可以嵌入到便宜的微控制器中,如 8051, PIC, AVR, SH, Z80, H8, ARM 等等,不需要做任何修改。
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 源码:
001 | #include "common.h" |
002 | /*-----------------------------------------------------------------------*/ |
003 | /* Inidialize a Drive */ |
004 |
005 | DSTATUS disk_initialize ( |
006 | BYTE drv /* Physical drive nmuber (0..) */ |
007 | ) |
008 | { |
009 | u8 state; |
010 |
011 | if (drv) |
012 | { |
013 | return STA_NOINIT; //仅支持磁盘0的操作 |
014 | } |
015 |
016 | state = SD_Init(); |
017 | if (state == STA_NODISK) |
018 | { |
019 | return STA_NODISK; |
020 | } |
021 | else if (state != 0) |
022 | { |
023 | return STA_NOINIT; //其他错误:初始化失败 |
024 | } |
025 | else |
026 | { |
027 | return 0; //初始化成功 |
028 | } |
029 | } |
030 |
031 |
032 |
033 | /*-----------------------------------------------------------------------*/ |
034 | /* Return Disk Status */ |
035 |
036 | DSTATUS disk_status ( |
037 | BYTE drv /* Physical drive nmuber (0..) */ |
038 | ) |
039 | { |
040 | if (drv) |
041 | { |
042 | return STA_NOINIT; //仅支持磁盘0操作 |
043 | } |
044 |
045 | //检查SD卡是否插入 |
046 | if (!SD_DET()) |
047 | { |
048 | return STA_NODISK; |
049 | } |
050 | return 0; |
051 | } |
052 |
053 |
054 |
055 | /*-----------------------------------------------------------------------*/ |
056 | /* Read Sector(s) */ |
057 |
058 | DRESULT disk_read ( |
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) */ |
063 | ) |
064 | { |
065 | u8 res=0; |
066 | if (drv || !count) |
067 | { |
068 | return RES_PARERR; //仅支持单磁盘操作,count不能等于0,否则返回参数错误 |
069 | } |
070 | if (!SD_DET()) |
071 | { |
072 | return RES_NOTRDY; //没有检测到SD卡,报NOT READY错误 |
073 | } |
074 |
075 | |
076 | |
077 | if (count==1) //1个sector的读操作 |
078 | { |
079 | res = SD_ReadSingleBlock(sector, buff); |
080 | } |
081 | else //多个sector的读操作 |
082 | { |
083 | res = SD_ReadMultiBlock(sector, buff, count); |
084 | } |
085 | /* |
086 | do |
087 | { |
088 | if(SD_ReadSingleBlock(sector, buff)!=0) |
089 | { |
090 | res = 1; |
091 | break; |
092 | } |
093 | buff+=512; |
094 | }while(--count); |
095 | */ |
096 | //处理返回值,将SPI_SD_driver.c的返回值转成ff.c的返回值 |
097 | if (res == 0x00) |
098 | { |
099 | return RES_OK; |
100 | } |
101 | else |
102 | { |
103 | return RES_ERROR; |
104 | } |
105 | } |
106 |
107 |
108 |
109 | /*-----------------------------------------------------------------------*/ |
110 | /* Write Sector(s) */ |
111 |
112 | #if _READONLY == 0 |
113 | DRESULT disk_write ( |
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) */ |
118 | ) |
119 | { |
120 | u8 res; |
121 |
122 | if (drv || !count) |
123 | { |
124 | return RES_PARERR; //仅支持单磁盘操作,count不能等于0,否则返回参数错误 |
125 | } |
126 | if (!SD_DET()) |
127 | { |
128 | return RES_NOTRDY; //没有检测到SD卡,报NOT READY错误 |
129 | } |
130 |
131 | // 读写操作 |
132 | if (count == 1) |
133 | { |
134 | res = SD_WriteSingleBlock(sector, buff); |
135 | } |
136 | else |
137 | { |
138 | res = SD_WriteMultiBlock(sector, buff, count); |
139 | } |
140 | // 返回值转换 |
141 | if (res == 0) |
142 | { |
143 | return RES_OK; |
144 | } |
145 | else |
146 | { |
147 | return RES_ERROR; |
148 | } |
149 | } |
150 | #endif /* _READONLY */ |
151 |
152 |
153 |
154 | /*-----------------------------------------------------------------------*/ |
155 | /* Miscellaneous Functions */ |
156 |
157 | DRESULT disk_ioctl ( |
158 | BYTE drv, /* Physical drive nmuber (0..) */ |
159 | BYTE ctrl, /* Control code */ |
160 | void *buff /* Buffer to send/receive control data */ |
161 | ) |
162 | { |
163 | DRESULT res; |
164 |
165 |
166 | if (drv) |
167 | { |
168 | return RES_PARERR; //仅支持单磁盘操作,否则返回参数错误 |
169 | } |
170 | |
171 | //FATFS目前版本仅需处理CTRL_SYNC,GET_SECTOR_COUNT,GET_BLOCK_SIZ三个命令 |
172 | switch (ctrl) |
173 | { |
174 | case CTRL_SYNC: |
175 | SD_CS_ENABLE(); |
176 | if (SD_WaitReady()==0) |
177 | { |
178 | res = RES_OK; |
179 | } |
180 | else |
181 | { |
182 | res = RES_ERROR; |
183 | } |
184 | SD_CS_DISABLE(); |
185 | break ; |
186 | |
187 | case GET_BLOCK_SIZE: |
188 | *( WORD *)buff = 512; |
189 | res = RES_OK; |
190 | break ; |
191 |
192 | case GET_SECTOR_COUNT: |
193 | *( DWORD *)buff = SD_GetCapacity(); |
194 | res = RES_OK; |
195 | break ; |
196 | default : |
197 | res = RES_PARERR; |
198 | break ; |
199 | } |
200 |
201 | return res; |
202 | } |
203 |
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 ) |
209 | { |
210 | return 0; |
211 | } |
这里的结构函数为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 示例:
001 | #include "common.h" |
002 | #include <string.h> |
003 |
004 | FRESULT scan_files ( char * path); |
005 |
006 | #define F_PUTS 1 //测试向文件写入字符串 |
007 | #define F_READ 1 //测试从文件中读出数据 |
008 | #define F_UNLINK 0 //测试删除文件 |
009 | #define SCAN_FILES 1 //测试目录扫描 |
010 |
011 | FATFS fs; |
012 | FRESULT res; |
013 | FIL file; |
014 | UINT br; |
015 | BYTE buffer[4096]; //以上变量作为全局变量 可以避免一些Bug |
016 |
017 | int main( void ) |
018 | { |
019 | u16 i,n; |
020 | |
021 |
022 | //stm32 初始化 |
023 | RCC_Configuration(); |
024 | NVIC_Configuration(); |
025 | USART_Configuration(); |
026 | SPI_Configuration(); |
027 | GPIO_Configuration(); |
028 |
029 |
030 | //fatfs 操作 |
031 |
032 | f_mount(0, &fs); |
033 |
034 | //如果data.txt存在,则打开;否则,创建一个新文件 |
035 | res = f_open(&file, "0:/data.txt" ,FA_OPEN_ALWAYS|FA_READ|FA_WRITE ); |
036 |
037 | if (res!=FR_OK) |
038 | { |
039 | printf ( "\r\n f_open() fail .. \r\n" ); |
040 | } else { |
041 | printf ( "\r\n f_open() success .. \r\n" ); |
042 | } |
043 |
044 | #if F_READ |
045 |
046 | while (1){ //使用f_read读文件 |
047 | res = f_read(&file, buffer, 1, &br); //一次读一个字节知道读完全部文件信息 |
048 |
049 | if (res == FR_OK ) |
050 | { |
051 | printf ( "%s" ,buffer); |
052 | } else { |
053 | printf ( "\r\n f_read() fail .. \r\n" ); |
054 | } |
055 |
056 | if (f_eof(&file)) { break ;} |
057 | } |
058 |
059 | /*if( f_gets(buffer,sizeof(buffer),&file) != NULL) //使用f_gets读文件 ,存在 Bugs 待调试 |
060 | { |
061 | printf("%s",buffer); |
062 | }else{ |
063 | printf("\r\n f_gets() fail .. \r\n"); |
064 | } */ |
065 |
066 | #endif |
067 |
068 | #if F_PUTS |
069 |
070 | //将指针指向文件末 |
071 | //res = f_lseek(&file,(&file)->fsize); |
072 | res = f_lseek(&file,file.fsize); |
073 |
074 | n = f_puts( "\r\n hello dog ..\r\n" , &file) ; //向文件末写入字符串 |
075 | |
076 | if (n<1) //判断写是否成功 |
077 | { |
078 | printf ( "\r\n f_puts() fail .. \r\n" ); |
079 | } else { |
080 | printf ( "\r\n f_puts() success .. \r\n" ); |
081 | } |
082 | |
083 | #endif |
084 |
085 | #if F_UNLINK |
086 |
087 | res = f_unlink( "test.jpg" ); //前提SD下存在一个test.jpg |
088 |
089 | if (res!=FR_OK) |
090 | { |
091 | printf ( "\r\n f_unlink() fail .. \r\n" ); |
092 | } else { |
093 | printf ( "\r\n f_unlink() success .. \r\n" ); |
094 | } |
095 |
096 | #endif |
097 |
098 | #if SCAN_FILES |
099 |
100 | printf ( "\r\n the directory files : \r\n" ); |
101 | scan_files( "/" ); //扫描根目录 |
102 |
103 | #endif |
104 |
105 | f_close(&file); |
106 | f_mount(0, NULL); |
107 |
108 | while (1); |
109 | } |
110 |
111 |
112 | FRESULT scan_files ( |
113 | char * path /* Start node to be scanned (also used as work area) */ |
114 | ) |
115 | { |
116 | FRESULT res; |
117 | FILINFO fno; |
118 | DIR dir; |
119 | int i; |
120 | char *fn; /* This function is assuming non-Unicode cfg. */ |
121 | #if _USE_LFN |
122 | static char lfn[_MAX_LFN + 1]; |
123 | fno.lfname = lfn; |
124 | fno.lfsize = sizeof lfn; |
125 | #endif |
126 |
127 |
128 | res = f_opendir(&dir, path); /* Open the directory */ |
129 | if (res == FR_OK) { |
130 | i = strlen (path); |
131 | for (;;) { |
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 */ |
135 | #if _USE_LFN |
136 | fn = *fno.lfname ? fno.lfname : fno.fname; |
137 | #else |
138 | fn = fno.fname; |
139 | #endif |
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 ; |
144 | path[i] = 0; |
145 | } else { /* It is a file. */ |
146 | printf ( "\r\n %s/%s \r\n" , path, fn); |
147 | } |
148 | } |
149 | } |
150 |
151 | return res; |
152 | } |
其中 目录扫描函数 scan_files( char * path) 参数格式如下:

这里使用到了f_puts()函数,所以必须在ffconf.h 中修改 #define _USE_STRFUNC 1