|
前言与废话; Z. F, {/ _% O& \, d9 n9 W
做项目时网找资料,不会的东西上网查阅一下多半可以解决,一些尚未解决的问题也会有所启发。最近由于项目的需要,仔细阅读了SD卡相关内容,顺藤摸瓜学习FatFS。网上关于SD卡和FatFS的内容非常的多,重复的部分我就不介绍了,我把移植和使用部分的经验和大家分享一下。0 U/ Q7 F) O. B
刚开始的时候,我找来一些现成的代码研究一下,不用说看的是一头雾水。看FatFS示例代码,也不知如何移植。最后还是下定决心,慢慢的阅读FatFS的相关文档和范例代码,对于移植部分一点一点的研究,相信一定会有所收获。 ; m G' E8 c2 K/ P3 j8 c$ c
一、硬件准备
# _3 J! U: }# e2 B+ ?2 j 开始移植之前,你必须要有一块SD卡。从形状上来说,有普通的SD卡,有很小的microSD卡,microSD卡就是手机中长见的TF卡。购买microSD卡的时候,往往会附带一个SD卡套,那么小个头的microSD卡就变成了普通的SD卡,接口都是一样的。
( }. Q5 ?3 I1 F7 j6 y) |" I( Y 但是还是您注意了,建议大家购买2G以下的SD卡(如果可以的话,买个128M的SD卡就可以达到实验的效果,价格也非常便宜)。刚开始移植的时候,我使用了4G的SD卡,但是发现程序无法完成SD卡的初始化。查阅网上相关的资料,发现SD卡技术已2G作为分界线,大于或者等于4G的卡属于高速SD卡,和小于或者等于4G的SD卡略有区别。
8 k6 R3 j- j1 U e# h0 V/ V, V二、软件准备
2 ^7 Q7 `, k- L 在进行移植之前,先编写一些最简单的STM32程序。在调试之前,我都会完成USART的初始化和发送函数,通过串口把STM32的运行状态打印出来,这样配合Jlink硬件调试,可以很快的找到错误。由于SD卡可以使用SPI进行读写操作,所以还需要完成SPI的初始化工作。
6 s) a3 ^8 F5 S# d& Q5 m 先来说一下USART的操作,我个人比较喜欢使用系统的printf函数,所以还需要引入stdio头文件。在IAR中必须设定option的某个选项。如下图所示。 % `& g; V+ C: z8 B# d# t7 Q1 T
除了完成USART的初始化工作以外,还需要重写fputc函数,具体的代码如下。 - int fputc(int ch, FILE * f)9 m5 O& ?9 y$ `' a9 n
- {9 }, n1 ?' X9 U0 q. F2 c
- USART_SendData(USART1, (uint8_t)ch);3 G9 k9 x: W* |. c7 `
- while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET );
4 E Y" p ]% V3 t' J - return ch;+ [1 t& v$ I7 S j1 f) j
- }
复制代码
/ c4 K& a1 `0 L8 M, s& c然后说一下SPI的初始化工作。阅读网上的代码,发现STM32 V2的库函数和V3函数中,关于SPI端口初始化的部分还是有些出入的。 ?: X& }0 s0 H
V2库中,把SCK,MOSI,MISO全部设置为复用输出。而V3库中,SCK,MOSI设置为复用输出,而MISO设置为浮动输入。在SD的SPI接口中,SCK,MOSI和MOSI,甚至包括CS都使用了上拉电阻。
2 [& [2 z! O' X/ [+ x" R+ f7 V0 n您需要注意一下几点6 B" }# _& n' N- @
1. 没有上拉电阻时 MISO应该如何设置
3 p, M% l$ K4 }" r2 h/ d, J% o由于我的开发板中没有使用上拉电阻,若设定MISO为浮动输入的话,或许会有某些问题,由于SD卡的输出端口驱动能力很弱,很有可能就接收不到返回数据,事实也正是如此。所以MISO最后被我甚至成了上拉输入模式,具体的代码如下。(所以还是要相信过来人的电路图,老实的加一个上拉电阻。), n6 M( Q8 `; l4 }' c6 W" n$ g P
2. SPI的模式应该如何选择
: ?+ E6 V% J* t( m SPI的速度不能太快,在初始化时时钟设为400k以下为宜。3 O4 H! V/ a$ F
3. SPI的速度应该如何选择! f/ L! R+ v/ x( T6 c; ]" L( q
SD卡使用SPI的模式0和模式3,这两个模式是等价的。 - void SPI1_Config(void). }! b, o" C) Q0 D" Z9 N# C9 F
- {
. O7 |1 C: [; M$ }9 F3 Y% [6 e( s! A - //使能APB2上相关时钟# ^- u9 X6 {6 s; _' e. _( M
- //使能SPI时钟,使能GPIOA时钟: K, E9 w. ]8 d1 n1 W- x
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1 |\1 r% K0 M' g7 l+ E
- RCC_APB2Periph_GPIOA ,ENABLE );+ Y3 ^/ h' S3 Z, x. q5 e
- //定义一个GPIO结构体2 |- \# @" d ?$ Y8 [
- GPIO_InitTypeDef GPIO_InitStructure;- }, {9 `0 a( B& w. p
- //SPI SCK MOSI
( o4 o6 u+ f4 t; l0 Y - GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;3 j( g7 K% R. D
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;, I6 i: }/ E J# ^1 `& v+ i/ \$ e
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//复用推挽输出1 c8 x% e% V4 E8 m
- GPIO_Init(GPIOA, &GPIO_InitStructure);" S% E6 D9 N; H5 Z7 Y
- //SPI MISO
" z: y( _9 l2 W$ h/ `( J5 [ - GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;# I9 l$ m" k9 b. w, f8 o6 d# Z
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
7 E. i, r. l4 b* B - GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//上拉输入0 I: Q; D$ d. @, ]
- GPIO_Init(GPIOA, &GPIO_InitStructure);7 |' [3 D* L" W' u6 z) @9 W
- //自定义SPI结构体4 `3 r. r; |! a
- SPI_InitTypeDef SPI_InitStructure;
9 p4 W4 Y, b o; B2 ?$ X - //双线双向全双工6 X: @: @# P/ E
- SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;" E) W; y' V* d
- //主机模式/ |: e0 r% u$ `+ t% Y
- SPI_InitStructure.SPI_Mode = SPI_Mode_Master;$ J- C; c9 p& E" r9 I
- //8位帧结构; c3 ~: q( z& e7 k( r
- SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;- v0 }* K9 B; x. ]
- //时钟空闲时为低- j% ~( `$ d) I5 w" c" F" {6 Z% a
- SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;: j, {! E1 ~6 D. n; l9 z
- //第一个上升沿捕获数据。模式,02 j4 W9 d! g$ m+ p7 U- v: C% s$ w+ s
- SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;, x+ Q* Y6 s; X6 C
- //MSS 端口软件控制,实际没有使用
+ v) c1 ]3 Z; L* t. A8 ] - SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
' @6 Q) X4 d( _( p - //SPI时钟72Mhz / 256 = 281.25K < 400K5 k/ a: n5 @ A9 {' U
- SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;
. \8 B. c6 e2 t: l - //数据传输高位在前
: h/ v% \7 ^' x w( c - SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;9 p2 v5 ], J+ U9 f/ F
- SPI_InitStructure.SPI_CRCPolynomial = 7;//
+ o ?: x n' o p" r& ? - //初始化SPI1. S+ {1 F. k- E
- SPI_Init(SPI1, &SPI_InitStructure);. t5 o- S* z& O5 |- @$ j
- //使能SPI10 L; ^1 p1 w) h' ^8 P6 v. L
- SPI_Cmd(SPI1, ENABLE);/ j( H' B$ L7 J |1 A
- }
复制代码 $ P6 \0 S7 U8 G) v( l
除了初始化操作以外,还需要一个SPI发送函数和一个SPI接收函数。由于SPI是同步通信方式,所以SPI接收函数,实际上只需要发送0xFF就可以,具体的代码如下。 - uint8_t SPI1_SendByte(uint8_t byte)
7 Y+ U0 {' O8 `* n% s% F - {5 o3 f, g* P$ x4 L' D
- //等待发送缓冲寄存器为空
1 Y4 ~5 ?! E4 X9 F1 I0 [" q3 C+ _ - while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);
( M$ U, e' s/ S' r0 a0 @ - //发送数据: i6 f# D* h: N8 J8 Q0 Y& S! A
- SPI_I2S_SendData(SPI1, byte);. a5 r" k% x8 U Z( t1 p/ g- ]
- //等待接收缓冲寄存器为非空
$ q: S( J, q1 E! N - while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);* p' u+ m* Q, o, Z# V, G
- //返回从SPI通信中接收到的数据
5 r- [+ ~; }2 Y: u' ~* i - return SPI_I2S_ReceiveData(SPI1); O% P$ M, s8 t
- }
! r7 P6 Z' }6 y( {0 {) Y4 k: Q - uint8_t SPI1_ReceiveByte()1 Z" l0 y, K3 N, g
- {3 J/ F! v1 C8 N0 @! j- ^ N! |
- //等待发送缓冲寄存器为空
* g* Q$ y p; U* Y- A - while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);
; F: v5 {& R' l t. [7 M - //发送数据,通过发送xff,获得返回数据
3 n! P2 s& G5 E2 z6 q% O9 g7 G( i - SPI_I2S_SendData(SPI1, 0xff);+ b2 I4 w' c5 R
- //等待接收缓冲寄存器为非空0 B& y8 G) q9 N; \- Q, Y J, Q
- while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);- j9 Q9 j" K9 F' r( c6 F
- //返回从SPI通信中接收到的数据7 c5 K6 a& [1 A ^" Q: i
- return SPI_I2S_ReceiveData(SPI1);1 g, Z2 e% w `6 m" {! R4 j4 X3 T
- }
复制代码三、移植前的“心灵”准备3 \) G" I* O9 R! x! S) F! T4 A& m& \
“移植”实际上就是研究他人的代码,你必须敏锐的看清代码的核心内容,了解你必须要做什么,哪一些可以以后再实现。在移植的初步阶段,我建议您使用最简单的方法完成某些内容,而不是去重视代码效率。例如,在移植过程中需要使用到延时函数,可以使用软件延时,可以配合Systick延时,甚至可以使用uCOS的延时函数。但是我建议您在面对选择的时候选择最简单的函数——软件延时,虽然它不准确效率也不高,但是您可以把更多的精力投入到其他重要的内容中去,你会觉得移植是那么简单,而延时函数的效率提高是锦上添花的事情。
/ M8 K! l4 ?0 }, r# l 例如您在移植之前会查看FatFS中关于STM32的移植范例。在该范例中,有关于SD卡插入,SD卡上电控制,SD卡写保护检测的函数。除了这些函数之外,代码中通过宏定义的方法,可以选择使用DMA来传送SPI数据,初始化SD卡时使用低速SPI,读写块的时候使用高速SPI,虽然这些改动让您觉得代码强大而高效,但是对您的移植一定用处都没有。您需要从最简单的generic开始,如果从这个文件开始,您会觉得移植是那么的简单,仅需要十几分钟。我相信您看完文章就会了,其实非常的简单。
0 d& ?1 F* K3 c* H7 \3 }& V9 h7 B. A* i7 O: w/ Z* a9 {- S$ m/ v$ u+ H$ O
四、移植开始——从generic开始
2 R8 n* r' D6 ~% @! r7 s 您所需要操作的只是mmcbb文件,里面主要包括SD卡的初始化、读块和写块函数。其实修改仅需要三步。
4 n6 `0 y, \! X* p/ y 第一步,修改宏定义,添加合适的头文件,添加延时函数
* P9 c8 M& V. v1 a 第二步,修改多字节发送函数
; |- X6 F2 _! U' Y5 @ 第三步,修改多字节接收函数) @3 P0 a' B6 W" [+ Y0 ]% C7 y
下面我通过原代码和移植代码的比较,来说明这个移植问题。+ ?- ?2 Q- J, G7 X3 r: V" k' a- P0 d
4.1 修改头文件和宏定义
7 Y$ V# t/ G" c原代码如下 - /* Include device specific declareation file here */
- `6 j$ ?, M* }; R6 x+ O [ - #include <device.h>9 f2 V, a0 G5 q( V! H& a
- /* Initialize MMC control port (CS/CLK/DI:output, DO/WP/INS:input) */" U4 q% `& H8 ]/ p1 ]+ j# V
- #define INIT_PORT() { init_port(); }
- S- ~0 i* ~% D' N: G3 n - /* Delay n microseconds */( p: l; M: ]: j6 O, x& ?1 f
- #define DLY_US(n) { dly_us(n); }
2 d$ H8 ~2 Q$ Q! |2 g+ ?+ h - #define CS_H() bset(P0) /* Set MMC CS "high" */
; M* H( z+ F% s5 J - #define CS_L() bclr(P0) /* Set MMC CS "low" */; g) t$ A2 V4 R) \, u5 ?, [
- #define CK_H() bset(P1) /* Set MMC SCLK "high" */
/ g% A$ L: B8 |$ `9 h - #define CK_L() bclr(P1) /* Set MMC SCLK "low" */& b4 M3 ]! n4 N% [* z' f, [
- #define DI_H() bset(P2) /* Set MMC DI "high" */
* B' k* \$ u: X6 t# \ - #define DI_L() bclr(P2) /* Set MMC DI "low" */" X" d @) P7 ?, e8 ]; }
- #define DO btest(P3) /* Get MMC DO value (high:true, low:false) */
- u- F* q) x2 I" Q4 b - /* Socket: Card is inserted (yes:true, no:false, default:true) */9 z7 N) H2 F* e A. q) f6 L! B
- #define INS (1)& L" H. C8 b. Q7 N* I
- /* Socket: Card is write protected (yes:true, no:false, default:false) */.: B' D, m8 B) x! Y
- #define WP (0)
复制代码
/ k* A& `) O0 R/ [( R==========修改后的代码如下========== - /* Include device specific declareation file here */
; p# l9 G! K4 v1 N3 x" A4 W! G - #include "stm32f10x.h", W/ _* A* \! E. C1 q1 |5 W) S8 s- t
- #include "spi1.h"0 g9 U0 l+ R0 Z
- #include <stdio.h>
3 a8 H# I9 U6 W% L - /* Initialize MMC control port (CS/CLK/DI:output, DO/WP/INS:input) */
5 x6 f" Q) g/ U. _ - #define INIT_PORT() { init_port(); }
. |9 \; _; b) P ~- E - /* Set MMC CS "high" */
0 B5 X3 t+ G* O% V3 D. a - #define CS_H() GPIO_SetBits(GPIOE,GPIO_Pin_7), F. x. M; T- ~& [
- /* Set MMC CS "low" */
1 P9 P. O* v' @7 a+ M - #define CS_L() GPIO_ResetBits(GPIOE,GPIO_Pin_7)2 @* I$ l( H! l) }. R! d
- /* Delay n microseconds */
3 e; c4 j8 u0 M! t& I q. t - #define DLY_US(n) { dly_us(n); }
8 H3 _2 q0 t6 N* [: S$ m- o& M - /* Socket: Card is inserted (yes:true, no:false, default:true) */
9 c7 g& v8 g* \5 Y! \& M3 W6 k - #define INS (1); a4 t ~3 U. P' w& u
- /* Socket: Card is write protected (yes:true, no:false, default:false) */" W0 H3 O* i0 y5 c4 r
- #define WP (0)
复制代码
4 \; Z% a6 ?$ C3 `/ @使用STM32时需要包含STM3210x头文件;spi1.h包括了spi相关操作函数。修改了CS操作的宏定义。
6 Z6 ?0 Q" [1 K* u 除了一个宏定义外,还需要些一个延时函数和一个初始化函数。延时函数使用软件延时,很不精确,但是可以说明问题。初始化函数,只是配置CS端口,而SPI初始化工作在调用fatfs API函数时已完成初始化。(若是SPI初始化也完成了CS的操作,init_port()可以省略) - //初始化端口
) ^* \$ B+ I% p0 a7 o - void init_port()! J* H6 N n4 ^; Q7 u
- {
9 Z5 ~. c/ i l6 N - //初始化时钟GPIOE
) \, U& I5 Y/ @5 }8 O - RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE ,ENABLE );
& Z! y3 I, H; ]2 p) r7 [$ I) O9 | - //配置GPIOE.7
' s: Y, L' T$ e$ _; Y - //定义一个GPIO结构体
& b; X* E* W. w; x' N7 [! k7 B) \ - GPIO_InitTypeDef GPIO_InitStructure;
: Q. v5 [' c! T& Y+ c - GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;* u7 j% ]+ ]9 k, l
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
# J& K1 ]- z5 m- L- e- T2 I - GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;3 A* h+ v B1 `
- GPIO_Init(GPIOE, &GPIO_InitStructure);
( a" ?+ {" y1 p0 }8 Y( ~ - }4 b2 l: l( q2 [$ I& F8 q& R* Z$ O9 @
- //软件演示函数
0 L$ d: J7 h5 t/ U, j+ u - void dly_us(uint16_t n)+ Q! @9 |# o) d, m3 l8 f* i
- {8 A/ t4 N5 R. E$ g
- for( ; n > 0 ; n--)" }; x- b% F! ]8 d* X- ]% x
- for(uint8_t i = 100 ; i > 0 ; i--);
. X) m2 H* A, v( s- \$ h - }
复制代码 $ h. m" q4 `0 _- _
4.2 多字节发送函数- V0 z* `9 \" [) {# ~+ ~8 u
原代码和修改后的代码如下。 - static
1 _8 G- v% g5 Z u - void xmit_mmc (+ W2 K6 ~) Q8 p% L* [7 R
- const BYTE* buff, /* Data to be sent */% M# d, T! s. s- k7 L" T
- UINT bc /* Number of bytes to send */, N/ _: Y" [% ?9 j4 z+ H6 ` I
- )
' a- h; I% w+ C) y - {3 w, _5 J2 {. s1 U) }# N
- BYTE d;
) R: J* u; B$ M( O( D: ` - do {
2 L5 P1 C# O2 q9 D, N - d = *buff++; /* Get a byte to be sent */
+ h5 e& w) D# t' K8 ~2 F: d7 e, ]/ c - if (d & 0x80) DI_H(); else DI_L(); /* bit7 */
. B( E- @/ B8 r6 }, k - CK_H(); CK_L();" u8 D+ K8 c* `. X5 ?5 X8 {
- if (d & 0x40) DI_H(); else DI_L(); /* bit6 */
4 J" c2 C* X, o7 }5 ^3 V5 T8 l0 {7 | - CK_H(); CK_L();# o: q) j, Z8 X/ ~" y
- if (d & 0x20) DI_H(); else DI_L(); /* bit5 */) v4 E2 k; Z/ C) V" }
- CK_H(); CK_L();& P+ }4 I0 M, C- @* }
- if (d & 0x10) DI_H(); else DI_L(); /* bit4 */
" z& E( p8 Q6 h3 R - CK_H(); CK_L();
0 l6 }1 ]! q) E5 [ - if (d & 0x08) DI_H(); else DI_L(); /* bit3 */( ?& x' Q4 I, v3 T) o
- CK_H(); CK_L();4 ~- p. }/ f% p( g! e- W
- if (d & 0x04) DI_H(); else DI_L(); /* bit2 */. A2 `* b: l% \
- CK_H(); CK_L();0 ^7 h# T/ B1 i2 E/ e4 F
- if (d & 0x02) DI_H(); else DI_L(); /* bit1 */9 U* @4 B: N6 O7 b8 W& W. r. ~9 x, E
- CK_H(); CK_L();. @6 P' L7 u, K9 n4 U# z* ^
- if (d & 0x01) DI_H(); else DI_L(); /* bit0 */
5 Q' n; G$ e* ?- n: d% L' q - CK_H(); CK_L();
0 c$ C( F6 U6 B1 q1 ]( C' n - } while (--bc); l a& ], i% w6 @# j" T
- }6 f) ^( r3 T+ o. o+ h m
复制代码 & V: ?4 Q! p) {% d' S
==========修改后的代码=========== - static void xmit_mmc (const BYTE* buff, UINT bc)
: { J0 ^ C. \, W - {, @4 d* T, y; f
- BYTE d;, `( v6 \5 R5 M" o5 L+ s* d
- do {* H- F2 K5 t5 |' f+ g8 | {) M
- /* Get a byte to be sent *// F5 i. I- @- O: C( l' o: H0 b
- d = *buff++;7 F" t$ i: |, I2 i3 p0 @
- //通过SPI发送
( T! C+ `/ C& e! e - SPI1_SendByte(d);+ ]6 K' { [7 v1 I1 g# P
- } while (--bc);+ n8 {/ s m) i' m f
- }
复制代码
) C. e! ^! S; E2 V6 H* B# L+ ]7 M8 M4.3 多字节接收函数 r/ g1 f5 E* O6 v; I
原代码和修改后的代码如下。 - static) j5 h7 A0 s. z4 c1 Q1 L! }8 R
- void rcvr_mmc (
}' Q N0 v4 N6 j2 Q. l - BYTE *buff, /* Pointer to read buffer */# n- c. R' P* p) D9 @/ b
- UINT bc /* Number of bytes to receive */
, _# L& W& ^" z9 @8 U/ a/ ^' \' k1 j - )
g; Y; k, o0 C% Y; C) v - {
) A' t. a: K: f- ?( ]! [( z" r - BYTE r;! H7 Y( P& h4 t. X& b( r6 ?4 j
- DI_H(); /* Send 0xFF */* a1 P4 z x5 ]: X$ E, Q, E3 u) j
- do {
# k7 g$ T/ e' Y. b - r = 0; if (DO) r++; /* bit7 */
5 T8 c/ p4 v, D; X, y - CK_H(); CK_L();% }5 K+ j5 z5 H6 Q ^- z0 P
- r <<= 1; if (DO) r++; /* bit6 */
* ~! s% l+ z7 c) O - CK_H(); CK_L();. `3 s4 q7 M+ b& Q& [
- r <<= 1; if (DO) r++; /* bit5 */
9 R8 t+ B% J9 E; z7 {9 ? - CK_H(); CK_L();
v5 @- y; A( x+ V - r <<= 1; if (DO) r++; /* bit4 */
# t5 @4 _$ S+ x. l6 D$ i - CK_H(); CK_L(); a6 N2 e; e% v3 Y! i- r
- r <<= 1; if (DO) r++; /* bit3 *// t8 L# a5 I! ^/ O, f& w
- CK_H(); CK_L();
1 H, G* T d0 @, k# q - r <<= 1; if (DO) r++; /* bit2 */% x% ~3 S: C1 S& k; t
- CK_H(); CK_L();
" h" \7 b: b% G: k: K! ~ - r <<= 1; if (DO) r++; /* bit1 */
5 n! V6 P- T2 y4 a- c3 L u - CK_H(); CK_L();% W' D3 w* n, V( L, o& T, f
- r <<= 1; if (DO) r++; /* bit0 */
! D+ p0 J" o, _. T& f$ |3 Q; n - CK_H(); CK_L();
. E9 I/ R3 y* w7 u# D) n% C. i - *buff++ = r; /* Store a received byte */
3 Q% i( s/ `, c2 |8 a - } while (--bc);
0 ~' U# v9 J& m - }
% s3 ~, l* R0 \. Z' o: m; t& Z O/ U
复制代码
& C. w& W. \) e" e* T% O$ ]0 e===========修改后的函数=========== - static void rcvr_mmc ( BYTE *buff, UINT bc )
& M6 {7 Z! p$ ^9 |' v$ d+ Q - {
- G+ j( U" f. d* a5 }' ]2 { - BYTE r;; w" K# F z c9 {2 y, k+ J
- do {
6 |6 {, C' t9 ~! H - //重新赋值
+ {% R+ j3 o6 R9 [+ c - r = 0;
, ]+ e- S r) b - //通过SPI获得数据
% V v" E+ }3 V. J& v3 z7 M6 G - r = SPI1_ReceiveByte();
( i5 Y6 n7 H# |8 M% T - /* Store a received byte */
+ Y6 ^4 a" x! z' B4 Z# R - *buff++ = r;$ p) E( X# G6 }$ x6 K( j% `
- } while (--bc);8 a6 R/ W& `* ?
- }
复制代码 $ |5 j% R3 q9 G3 d/ b
在这里多说一句,源代码中6 p& D" b" Q. Y& v5 }+ N
DI_H(); /* Send 0xFF */) @0 m9 f G; e a, D; g1 {8 G
作者的本意应该是把IO设为输入状态,51系列单片机就是这么操作的,但是写代码注释写成了发送0xFF,其实并不需要发送0xFF。
+ R: j5 w1 c$ `4 [! x: r; T: W! C" O到这里就完成了fatfs的STM32移植工作,虽然只有简单的三步,但是却花了我整整三天的时间。我想您看了这样的描述,不知道能否在10分钟之内完成修改。1 \/ e% P- Y, \& m* M5 c( c
( c0 A; Z( g1 h5 N0 s: @
五 FatFS初步使用 x8 K+ X5 T+ a: T* f& R# H* d
接下来就是使用FatFS了,看了这个函数我找回了当初初学C语言的感觉,打开一个文件,然后读一些数据,然后创建另一个文件,在文件中写一些数据,最后关闭文件。 - int main(void)! r0 A P) w g& P, T
- {4 t8 r3 J" U% |5 }, Q
- //初始化Systick- \. D+ }" x/ m- ]' O, z1 q1 M% k; T
- RCC_Config();$ d% |7 {8 W! g. c9 a/ N( @
- //初始化串口" @( M8 v7 l2 l9 U; O7 }7 Y
- USART1_Config();! B- F. [7 |! Y j3 v3 M4 [( I
- //初始化SPI1
; O. G/ m" u% | ?6 ` - SPI1_Config();1 N6 S9 k) H R, ]9 I
- printf("start to read file\n");: j" M# B* F# L% h
- /* Register volume work area (never fails) */# K3 T: E5 S' F) J: ~. Y1 ^3 i
- f_mount(0, &fatfs);9 g2 w( \ @* [0 k, E& B
- printf("\nOpen a test file (test.txt).\n");
+ H |9 x5 a( a, s - rc = f_open(&fil, "test.txt", FA_READ);
; d( y1 v7 A. x; H1 h" I - if (rc) die(rc);
0 R0 s" H; \2 U/ R - printf("\nType the file content.\n");8 \* Q0 C; H3 Z: g% V3 T! A: }" O/ ?
- for (;;) {
6 r- \- u' |! ~! M, A9 @' V - rc = f_read(&fil, buff, sizeof(buff), &br); /* Read a chunk of file */, b- @: s4 J4 H1 }" c1 \5 ^
- if (rc || !br) break; /* Error or end of file */8 B$ e j! |: h" z# A% f/ ?& s- A: Q
- for (i = 0; i < br; i++) /* Type the data */6 W5 `- {! m8 s+ a3 J
- putchar(buff);! q. w% L/ }1 [7 Q S7 q6 ?) e
- }
' n. o& N0 d0 ], r1 r0 d2 `& W - if (rc) die(rc);, S+ x; M0 k. U9 _! m
- printf("\nClose the file.\n");8 ]) ^% K) J+ ]3 P! A c4 v
- rc = f_close(&fil);
$ Z1 w% t, D! T. T$ }2 C - if (rc) die(rc);/ i' f6 l D0 q) C4 d- ~5 K# X
- printf("\nCreate a new file (hello.txt).\n");) J! D; {/ }% X9 a: l b. O
- rc = f_open(&fil, "HELLO.TXT", FA_WRITE | FA_CREATE_ALWAYS);; G$ W* o+ G- u E4 D! z. U
- if (rc) die(rc);
4 _1 X3 ]! ]. S0 S9 N9 L% C( I- Y - printf("\nWrite a text data. (Hello world!)\n");
$ o3 G' `( k2 f) z3 w - rc = f_write(&fil, "Hello world!\r\n", 14, &bw);
. [% [2 a% m9 i: z* S! } - if (rc) die(rc);( @% g3 D' R0 {# R- c
- printf("%u bytes written.\n", bw);4 S/ F: `7 N+ a6 K" t
- printf("\nClose the file.\n");2 c; N8 g: u- |5 T% A
- rc = f_close(&fil);1 }* ^2 ~( ~4 N
- if (rc) die(rc);4 h, x5 {+ Y2 R
- while (1)
! B) {6 i+ Q) s v - {
m% w; ^( G; n1 S" g/ c - }
0 m/ _1 m) L5 ] - }3 R( L- ]. E$ Y6 p: B
复制代码 5 W) k" }2 B3 k) G: @
如果出现失败的话,程序会进入die函数,该函数会输出错误代码,并进入一个无限循环。2 x2 g9 ?4 \& v+ b# S4 A
通过串口的输出结果如下所示。
, E9 O4 x0 @* h6 s我再把SD卡从目标板上拿下,查看文件中的内容。的确hello.txt文件中写了hello world字符(应该还有回车和换行符)。
9 k3 h) I0 C/ I% d1 J+ x
六 我的错误经历
/ d. r* N' D# ^ 再快要移植成功的时候,我一运行程序,程序就进入die函数,并显示错误1,提示应该是SD卡操作错误。我通过断点调试和printf输出,把问题定位到发送cmd0处,返回的结果为一个非法的命令。我从CMD17命令入手,查阅了网上各位大神的经验,有说是发送命令的延时时候不够。但是照着这个修改之后问题存在,无奈之下在电脑面前苦苦思考。直到我的女朋友,在愚人节那天“玩”我,当时我正在仔细的检查代码,她和我说某某老师要找我并提醒我一定要拿手机,我收拾起我凌乱的思绪,立刻跑过去时,她却打电话给我说愚人节快乐。我很无奈但也有点开心的回到电脑面前,一动鼠标就看到了某些异样。' c& T6 C3 X3 X6 }
#define CMD17 (7) /* READ_SINGLE_BLOCK */
! E. ], V3 Y9 C9 | Q: w. o7 j我把CMD17命令的宏定义写成了7,而实际上是17。就这么一个简答的错误,花费了我一天的时间。也非常感谢女朋友的这个愚人节玩笑,没有她或许就无法发现这个问题。! v/ N( ~+ R. N6 X
一个尚未解决的问题!
2 H- x, c, l" s0 B) M0 O; {: R5 S 还有一个比较特殊的地方请聪明的你注意一下,在generic中man函数中,把这些定义在了main函数里面。这些定义如下 - FRESULT rc; /* Result code */
8 S" c4 p6 h8 I% T. m - FATFS fatfs; /* File system object */4 t/ o/ v! S& s! @* O
- FIL fil; /* File object */% U8 j3 U3 j" r4 w
- DIR dir; /* Directory object *// Q) s+ F; ^, K8 N
- FILINFO fno; /* File information object */
% X3 g/ G- P. F6 ? ^ - UINT bw, br, i;
: Y7 U9 {- e) J% m - BYTE buff[128];
复制代码 . n' ?9 d0 l9 s: }6 `2 B
如果把这些变量的声明都放在main函数中的话,系统将会运行到一个异常中,如下图所示。这个错误会让人非常的沮丧。虽然我没有找到原因,但是我找到了解决的方法。把这些变量的声明放在函数之外。1 }5 E4 n9 y' V/ i: |
3 O. a) v6 S$ t+ h
t1 I& p( @" |* I1 B% @6 H% N |