你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【经验分享】STM32 FatFS 移植经验分享

[复制链接]
STMCU小助手 发布时间:2022-1-28 16:32
前言与废话; 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函数,具体的代码如下。
  1. int fputc(int ch, FILE * f)9 m5 O& ?9 y$ `' a9 n
  2. {9 }, n1 ?' X9 U0 q. F2 c
  3.   USART_SendData(USART1, (uint8_t)ch);3 G9 k9 x: W* |. c7 `
  4.   while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET );
    4 E  Y" p  ]% V3 t' J
  5.   return ch;+ [1 t& v$ I7 S  j1 f) j
  6. }
复制代码

/ 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,这两个模式是等价的。
  1. void SPI1_Config(void). }! b, o" C) Q0 D" Z9 N# C9 F
  2. {
    . O7 |1 C: [; M$ }9 F3 Y% [6 e( s! A
  3.   //使能APB2上相关时钟# ^- u9 X6 {6 s; _' e. _( M
  4.   //使能SPI时钟,使能GPIOA时钟: K, E9 w. ]8 d1 n1 W- x
  5.   RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1 |\1 r% K0 M' g7 l+ E
  6.                          RCC_APB2Periph_GPIOA ,ENABLE );+ Y3 ^/ h' S3 Z, x. q5 e
  7.   //定义一个GPIO结构体2 |- \# @" d  ?$ Y8 [
  8.   GPIO_InitTypeDef  GPIO_InitStructure;- }, {9 `0 a( B& w. p
  9.   //SPI SCK MOSI
    ( o4 o6 u+ f4 t; l0 Y
  10.   GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5  |  GPIO_Pin_7;3 j( g7 K% R. D
  11.   GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;, I6 i: }/ E  J# ^1 `& v+ i/ \$ e
  12.   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//复用推挽输出1 c8 x% e% V4 E8 m
  13.   GPIO_Init(GPIOA, &GPIO_InitStructure);" S% E6 D9 N; H5 Z7 Y
  14.   //SPI MISO
    " z: y( _9 l2 W$ h/ `( J5 [
  15.   GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;# I9 l$ m" k9 b. w, f8 o6 d# Z
  16.   GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    7 E. i, r. l4 b* B
  17.   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//上拉输入0 I: Q; D$ d. @, ]
  18.   GPIO_Init(GPIOA, &GPIO_InitStructure);7 |' [3 D* L" W' u6 z) @9 W
  19.   //自定义SPI结构体4 `3 r. r; |! a
  20.   SPI_InitTypeDef SPI_InitStructure;
    9 p4 W4 Y, b  o; B2 ?$ X
  21.   //双线双向全双工6 X: @: @# P/ E
  22.   SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;" E) W; y' V* d
  23. //主机模式/ |: e0 r% u$ `+ t% Y
  24.   SPI_InitStructure.SPI_Mode = SPI_Mode_Master;$ J- C; c9 p& E" r9 I
  25.   //8位帧结构; c3 ~: q( z& e7 k( r
  26.   SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;- v0 }* K9 B; x. ]
  27.   //时钟空闲时为低- j% ~( `$ d) I5 w" c" F" {6 Z% a
  28.   SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;: j, {! E1 ~6 D. n; l9 z
  29.   //第一个上升沿捕获数据。模式,02 j4 W9 d! g$ m+ p7 U- v: C% s$ w+ s
  30.   SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;, x+ Q* Y6 s; X6 C
  31.   //MSS 端口软件控制,实际没有使用
    + v) c1 ]3 Z; L* t. A8 ]
  32.   SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
    ' @6 Q) X4 d( _( p
  33.   //SPI时钟72Mhz / 256 = 281.25K  < 400K5 k/ a: n5 @  A9 {' U
  34.   SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;
    . \8 B. c6 e2 t: l
  35.   //数据传输高位在前
    : h/ v% \7 ^' x  w( c
  36.   SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;9 p2 v5 ], J+ U9 f/ F
  37.   SPI_InitStructure.SPI_CRCPolynomial = 7;//
    + o  ?: x  n' o  p" r& ?
  38.   //初始化SPI1. S+ {1 F. k- E
  39.   SPI_Init(SPI1, &SPI_InitStructure);. t5 o- S* z& O5 |- @$ j
  40.   //使能SPI10 L; ^1 p1 w) h' ^8 P6 v. L
  41.   SPI_Cmd(SPI1, ENABLE);/ j( H' B$ L7 J  |1 A
  42. }
复制代码
$ P6 \0 S7 U8 G) v( l
除了初始化操作以外,还需要一个SPI发送函数和一个SPI接收函数。由于SPI是同步通信方式,所以SPI接收函数,实际上只需要发送0xFF就可以,具体的代码如下。
  1. uint8_t SPI1_SendByte(uint8_t byte)
    7 Y+ U0 {' O8 `* n% s% F
  2. {5 o3 f, g* P$ x4 L' D
  3.   //等待发送缓冲寄存器为空
    1 Y4 ~5 ?! E4 X9 F1 I0 [" q3 C+ _
  4.   while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);
    ( M$ U, e' s/ S' r0 a0 @
  5.   //发送数据: i6 f# D* h: N8 J8 Q0 Y& S! A
  6.   SPI_I2S_SendData(SPI1, byte);. a5 r" k% x8 U  Z( t1 p/ g- ]
  7.   //等待接收缓冲寄存器为非空
    $ q: S( J, q1 E! N
  8.   while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);* p' u+ m* Q, o, Z# V, G
  9.   //返回从SPI通信中接收到的数据
    5 r- [+ ~; }2 Y: u' ~* i
  10.   return SPI_I2S_ReceiveData(SPI1);  O% P$ M, s8 t
  11. }
    ! r7 P6 Z' }6 y( {0 {) Y4 k: Q
  12. uint8_t SPI1_ReceiveByte()1 Z" l0 y, K3 N, g
  13. {3 J/ F! v1 C8 N0 @! j- ^  N! |
  14.   //等待发送缓冲寄存器为空
    * g* Q$ y  p; U* Y- A
  15.   while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);
    ; F: v5 {& R' l  t. [7 M
  16.   //发送数据,通过发送xff,获得返回数据
    3 n! P2 s& G5 E2 z6 q% O9 g7 G( i
  17.   SPI_I2S_SendData(SPI1, 0xff);+ b2 I4 w' c5 R
  18.   //等待接收缓冲寄存器为非空0 B& y8 G) q9 N; \- Q, Y  J, Q
  19.   while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);- j9 Q9 j" K9 F' r( c6 F
  20.   //返回从SPI通信中接收到的数据7 c5 K6 a& [1 A  ^" Q: i
  21.   return SPI_I2S_ReceiveData(SPI1);1 g, Z2 e% w  `6 m" {! R4 j4 X3 T
  22. }
复制代码
三、移植前的“心灵”准备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原代码如下
  1. /* Include device specific declareation file here */
    - `6 j$ ?, M* }; R6 x+ O  [
  2. #include <device.h>9 f2 V, a0 G5 q( V! H& a
  3. /* Initialize MMC control port (CS/CLK/DI:output, DO/WP/INS:input) */" U4 q% `& H8 ]/ p1 ]+ j# V
  4. #define        INIT_PORT()        { init_port(); }
    - S- ~0 i* ~% D' N: G3 n
  5. /* Delay n microseconds */( p: l; M: ]: j6 O, x& ?1 f
  6. #define DLY_US(n)        { dly_us(n); }
    2 d$ H8 ~2 Q$ Q! |2 g+ ?+ h
  7. #define CS_H()                bset(P0)           /* Set MMC CS "high" */
    ; M* H( z+ F% s5 J
  8. #define CS_L()                 bclr(P0)            /* Set MMC CS "low" */; g) t$ A2 V4 R) \, u5 ?, [
  9. #define CK_H()                bset(P1)           /* Set MMC SCLK "high" */
    / g% A$ L: B8 |$ `9 h
  10. #define  CK_L()                bclr(P1)            /* Set MMC SCLK "low" */& b4 M3 ]! n4 N% [* z' f, [
  11. #define DI_H()                 bset(P2)           /* Set MMC DI "high" */
    * B' k* \$ u: X6 t# \
  12. #define DI_L()                 bclr(P2)            /* Set MMC DI "low" */" X" d  @) P7 ?, e8 ]; }
  13. #define DO                     btest(P3)          /* Get MMC DO value (high:true, low:false) */
    - u- F* q) x2 I" Q4 b
  14. /* Socket: Card is inserted (yes:true, no:false, default:true) */9 z7 N) H2 F* e  A. q) f6 L! B
  15. #define        INS                        (1)& L" H. C8 b. Q7 N* I
  16. /* Socket: Card is write protected (yes:true, no:false, default:false) */.: B' D, m8 B) x! Y
  17. #define        WP                        (0)
复制代码

/ k* A& `) O0 R/ [( R
==========修改后的代码如下==========
  1. /* Include device specific declareation file here */
    ; p# l9 G! K4 v1 N3 x" A4 W! G
  2. #include "stm32f10x.h", W/ _* A* \! E. C1 q1 |5 W) S8 s- t
  3. #include "spi1.h"0 g9 U0 l+ R0 Z
  4. #include <stdio.h>
    3 a8 H# I9 U6 W% L
  5. /* Initialize MMC control port (CS/CLK/DI:output, DO/WP/INS:input) */
    5 x6 f" Q) g/ U. _
  6. #define        INIT_PORT()        { init_port(); }
    . |9 \; _; b) P  ~- E
  7. /* Set MMC CS "high" */
    0 B5 X3 t+ G* O% V3 D. a
  8. #define        CS_H()                  GPIO_SetBits(GPIOE,GPIO_Pin_7), F. x. M; T- ~& [
  9. /* Set MMC CS "low" */
    1 P9 P. O* v' @7 a+ M
  10. #define CS_L()                  GPIO_ResetBits(GPIOE,GPIO_Pin_7)2 @* I$ l( H! l) }. R! d
  11. /* Delay n microseconds */
    3 e; c4 j8 u0 M! t& I  q. t
  12. #define DLY_US(n)        { dly_us(n); }
    8 H3 _2 q0 t6 N* [: S$ m- o& M
  13. /* Socket: Card is inserted (yes:true, no:false, default:true) */
    9 c7 g& v8 g* \5 Y! \& M3 W6 k
  14. #define        INS                          (1); a4 t  ~3 U. P' w& u
  15. /* Socket: Card is write protected (yes:true, no:false, default:false) */" W0 H3 O* i0 y5 c4 r
  16. #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()可以省略)
  1. //初始化端口
    ) ^* \$ B+ I% p0 a7 o
  2. void init_port()! J* H6 N  n4 ^; Q7 u
  3. {
    9 Z5 ~. c/ i  l6 N
  4.   //初始化时钟GPIOE
    ) \, U& I5 Y/ @5 }8 O
  5.   RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE ,ENABLE );
    & Z! y3 I, H; ]2 p) r7 [$ I) O9 |
  6.   //配置GPIOE.7
    ' s: Y, L' T$ e$ _; Y
  7.   //定义一个GPIO结构体
    & b; X* E* W. w; x' N7 [! k7 B) \
  8.   GPIO_InitTypeDef  GPIO_InitStructure;
    : Q. v5 [' c! T& Y+ c
  9.   GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;* u7 j% ]+ ]9 k, l
  10.   GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    # J& K1 ]- z5 m- L- e- T2 I
  11.   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;3 A* h+ v  B1 `
  12.   GPIO_Init(GPIOE, &GPIO_InitStructure);
    ( a" ?+ {" y1 p0 }8 Y( ~
  13. }4 b2 l: l( q2 [$ I& F8 q& R* Z$ O9 @
  14. //软件演示函数
    0 L$ d: J7 h5 t/ U, j+ u
  15. void dly_us(uint16_t n)+ Q! @9 |# o) d, m3 l8 f* i
  16. {8 A/ t4 N5 R. E$ g
  17.   for( ; n > 0 ; n--)" }; x- b% F! ]8 d* X- ]% x
  18.     for(uint8_t i = 100 ; i > 0 ; i--);
    . X) m2 H* A, v( s- \$ h
  19. }
复制代码
$ h. m" q4 `0 _- _
4.2 多字节发送函数- V0 z* `9 \" [) {# ~+ ~8 u
原代码和修改后的代码如下。
  1. static
    1 _8 G- v% g5 Z  u
  2. void xmit_mmc (+ W2 K6 ~) Q8 p% L* [7 R
  3.         const BYTE* buff,        /* Data to be sent */% M# d, T! s. s- k7 L" T
  4.         UINT bc                                /* Number of bytes to send */, N/ _: Y" [% ?9 j4 z+ H6 `  I
  5. )
    ' a- h; I% w+ C) y
  6. {3 w, _5 J2 {. s1 U) }# N
  7.         BYTE d;
    ) R: J* u; B$ M( O( D: `
  8.         do {
    2 L5 P1 C# O2 q9 D, N
  9.                 d = *buff++;        /* Get a byte to be sent */
    + h5 e& w) D# t' K8 ~2 F: d7 e, ]/ c
  10.                 if (d & 0x80) DI_H(); else DI_L();        /* bit7 */
    . B( E- @/ B8 r6 }, k
  11.                 CK_H(); CK_L();" u8 D+ K8 c* `. X5 ?5 X8 {
  12.                 if (d & 0x40) DI_H(); else DI_L();        /* bit6 */
    4 J" c2 C* X, o7 }5 ^3 V5 T8 l0 {7 |
  13.                 CK_H(); CK_L();# o: q) j, Z8 X/ ~" y
  14.                 if (d & 0x20) DI_H(); else DI_L();        /* bit5 */) v4 E2 k; Z/ C) V" }
  15.                 CK_H(); CK_L();& P+ }4 I0 M, C- @* }
  16.                 if (d & 0x10) DI_H(); else DI_L();        /* bit4 */
    " z& E( p8 Q6 h3 R
  17.                 CK_H(); CK_L();
    0 l6 }1 ]! q) E5 [
  18.                 if (d & 0x08) DI_H(); else DI_L();        /* bit3 */( ?& x' Q4 I, v3 T) o
  19.                 CK_H(); CK_L();4 ~- p. }/ f% p( g! e- W
  20.                 if (d & 0x04) DI_H(); else DI_L();        /* bit2 */. A2 `* b: l% \
  21.                 CK_H(); CK_L();0 ^7 h# T/ B1 i2 E/ e4 F
  22.                 if (d & 0x02) DI_H(); else DI_L();        /* bit1 */9 U* @4 B: N6 O7 b8 W& W. r. ~9 x, E
  23.                 CK_H(); CK_L();. @6 P' L7 u, K9 n4 U# z* ^
  24.                 if (d & 0x01) DI_H(); else DI_L();        /* bit0 */
    5 Q' n; G$ e* ?- n: d% L' q
  25.                 CK_H(); CK_L();
    0 c$ C( F6 U6 B1 q1 ]( C' n
  26.         } while (--bc);  l  a& ], i% w6 @# j" T
  27. }6 f) ^( r3 T+ o. o+ h  m
复制代码
& V: ?4 Q! p) {% d' S
==========修改后的代码===========
  1. static void xmit_mmc (const BYTE* buff,        UINT bc)
    : {  J0 ^  C. \, W
  2. {, @4 d* T, y; f
  3.         BYTE d;, `( v6 \5 R5 M" o5 L+ s* d
  4.         do {* H- F2 K5 t5 |' f+ g8 |  {) M
  5.     /* Get a byte to be sent *// F5 i. I- @- O: C( l' o: H0 b
  6.         d = *buff++;7 F" t$ i: |, I2 i3 p0 @
  7.     //通过SPI发送
    ( T! C+ `/ C& e! e
  8.     SPI1_SendByte(d);+ ]6 K' {  [7 v1 I1 g# P
  9.         } while (--bc);+ n8 {/ s  m) i' m  f
  10. }
复制代码

) C. e! ^! S; E2 V6 H* B# L+ ]7 M8 M
4.3 多字节接收函数  r/ g1 f5 E* O6 v; I
原代码和修改后的代码如下。
  1. static) j5 h7 A0 s. z4 c1 Q1 L! }8 R
  2. void rcvr_mmc (
      }' Q  N0 v4 N6 j2 Q. l
  3.         BYTE *buff,        /* Pointer to read buffer */# n- c. R' P* p) D9 @/ b
  4.         UINT bc                /* Number of bytes to receive */
    , _# L& W& ^" z9 @8 U/ a/ ^' \' k1 j
  5. )
      g; Y; k, o0 C% Y; C) v
  6. {
    ) A' t. a: K: f- ?( ]! [( z" r
  7.         BYTE r;! H7 Y( P& h4 t. X& b( r6 ?4 j
  8.         DI_H();         /* Send 0xFF */* a1 P4 z  x5 ]: X$ E, Q, E3 u) j
  9.         do {
    # k7 g$ T/ e' Y. b
  10.                 r = 0;   if (DO) r++;        /* bit7 */
    5 T8 c/ p4 v, D; X, y
  11.                 CK_H(); CK_L();% }5 K+ j5 z5 H6 Q  ^- z0 P
  12.                 r <<= 1; if (DO) r++;        /* bit6 */
    * ~! s% l+ z7 c) O
  13.                 CK_H(); CK_L();. `3 s4 q7 M+ b& Q& [
  14.                 r <<= 1; if (DO) r++;        /* bit5 */
    9 R8 t+ B% J9 E; z7 {9 ?
  15.                 CK_H(); CK_L();
      v5 @- y; A( x+ V
  16.                 r <<= 1; if (DO) r++;        /* bit4 */
    # t5 @4 _$ S+ x. l6 D$ i
  17.                 CK_H(); CK_L();  a6 N2 e; e% v3 Y! i- r
  18.                 r <<= 1; if (DO) r++;        /* bit3 *// t8 L# a5 I! ^/ O, f& w
  19.                 CK_H(); CK_L();
    1 H, G* T  d0 @, k# q
  20.                 r <<= 1; if (DO) r++;        /* bit2 */% x% ~3 S: C1 S& k; t
  21.                 CK_H(); CK_L();
    " h" \7 b: b% G: k: K! ~
  22.                 r <<= 1; if (DO) r++;        /* bit1 */
    5 n! V6 P- T2 y4 a- c3 L  u
  23.                 CK_H(); CK_L();% W' D3 w* n, V( L, o& T, f
  24.                 r <<= 1; if (DO) r++;        /* bit0 */
    ! D+ p0 J" o, _. T& f$ |3 Q; n
  25.                 CK_H(); CK_L();
    . E9 I/ R3 y* w7 u# D) n% C. i
  26.                 *buff++ = r;                        /* Store a received byte */
    3 Q% i( s/ `, c2 |8 a
  27.         } while (--bc);
    0 ~' U# v9 J& m
  28. }
    % s3 ~, l* R0 \. Z' o: m; t& Z  O/ U
复制代码

& C. w& W. \) e" e* T% O$ ]0 e
===========修改后的函数===========
  1. static void rcvr_mmc ( BYTE *buff,        UINT bc        )
    & M6 {7 Z! p$ ^9 |' v$ d+ Q
  2. {
    - G+ j( U" f. d* a5 }' ]2 {
  3.         BYTE r;; w" K# F  z  c9 {2 y, k+ J
  4.         do {
    6 |6 {, C' t9 ~! H
  5.     //重新赋值
    + {% R+ j3 o6 R9 [+ c
  6.                 r = 0;
    , ]+ e- S  r) b
  7.     //通过SPI获得数据
    % V  v" E+ }3 V. J& v3 z7 M6 G
  8.     r = SPI1_ReceiveByte();
    ( i5 Y6 n7 H# |8 M% T
  9.     /* Store a received byte */
    + Y6 ^4 a" x! z' B4 Z# R
  10.                 *buff++ = r;$ p) E( X# G6 }$ x6 K( j% `
  11.         } while (--bc);8 a6 R/ W& `* ?
  12. }
复制代码
$ |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语言的感觉,打开一个文件,然后读一些数据,然后创建另一个文件,在文件中写一些数据,最后关闭文件。
  1. int main(void)! r0 A  P) w  g& P, T
  2. {4 t8 r3 J" U% |5 }, Q
  3.   //初始化Systick- \. D+ }" x/ m- ]' O, z1 q1 M% k; T
  4.   RCC_Config();$ d% |7 {8 W! g. c9 a/ N( @
  5.   //初始化串口" @( M8 v7 l2 l9 U; O7 }7 Y
  6.   USART1_Config();! B- F. [7 |! Y  j3 v3 M4 [( I
  7.   //初始化SPI1
    ; O. G/ m" u% |  ?6 `
  8.   SPI1_Config();1 N6 S9 k) H  R, ]9 I
  9.   printf("start to read file\n");: j" M# B* F# L% h
  10.   /* Register volume work area (never fails) */# K3 T: E5 S' F) J: ~. Y1 ^3 i
  11.   f_mount(0, &fatfs);9 g2 w( \  @* [0 k, E& B
  12.   printf("\nOpen a test file (test.txt).\n");
    + H  |9 x5 a( a, s
  13. rc = f_open(&fil, "test.txt", FA_READ);
    ; d( y1 v7 A. x; H1 h" I
  14. if (rc) die(rc);
    0 R0 s" H; \2 U/ R
  15.         printf("\nType the file content.\n");8 \* Q0 C; H3 Z: g% V3 T! A: }" O/ ?
  16.         for (;;) {
    6 r- \- u' |! ~! M, A9 @' V
  17.                 rc = f_read(&fil, buff, sizeof(buff), &br);        /* Read a chunk of file */, b- @: s4 J4 H1 }" c1 \5 ^
  18.                 if (rc || !br) break;                                    /* Error or end of file */8 B$ e  j! |: h" z# A% f/ ?& s- A: Q
  19.                 for (i = 0; i < br; i++)                                /* Type the data */6 W5 `- {! m8 s+ a3 J
  20.                         putchar(buff);! q. w% L/ }1 [7 Q  S7 q6 ?) e
  21.         }
    ' n. o& N0 d0 ], r1 r0 d2 `& W
  22.         if (rc) die(rc);, S+ x; M0 k. U9 _! m
  23.         printf("\nClose the file.\n");8 ]) ^% K) J+ ]3 P! A  c4 v
  24.         rc = f_close(&fil);
    $ Z1 w% t, D! T. T$ }2 C
  25.         if (rc) die(rc);/ i' f6 l  D0 q) C4 d- ~5 K# X
  26.         printf("\nCreate a new file (hello.txt).\n");) J! D; {/ }% X9 a: l  b. O
  27.         rc = f_open(&fil, "HELLO.TXT", FA_WRITE | FA_CREATE_ALWAYS);; G$ W* o+ G- u  E4 D! z. U
  28.         if (rc) die(rc);
    4 _1 X3 ]! ]. S0 S9 N9 L% C( I- Y
  29.         printf("\nWrite a text data. (Hello world!)\n");
    $ o3 G' `( k2 f) z3 w
  30.         rc = f_write(&fil, "Hello world!\r\n", 14, &bw);
    . [% [2 a% m9 i: z* S! }
  31.         if (rc) die(rc);( @% g3 D' R0 {# R- c
  32.         printf("%u bytes written.\n", bw);4 S/ F: `7 N+ a6 K" t
  33.         printf("\nClose the file.\n");2 c; N8 g: u- |5 T% A
  34.         rc = f_close(&fil);1 }* ^2 ~( ~4 N
  35.         if (rc) die(rc);4 h, x5 {+ Y2 R
  36.   while (1)
    ! B) {6 i+ Q) s  v
  37.   {
      m% w; ^( G; n1 S" g/ c
  38.   }
    0 m/ _1 m) L5 ]
  39. }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函数里面。这些定义如下
  1.           FRESULT rc;                                /* Result code */
    8 S" c4 p6 h8 I% T. m
  2.         FATFS fatfs;                                /* File system object */4 t/ o/ v! S& s! @* O
  3.         FIL fil;                                                /* File object */% U8 j3 U3 j" r4 w
  4.         DIR dir;                                        /* Directory object *// Q) s+ F; ^, K8 N
  5.         FILINFO fno;                                /* File information object */
    % X3 g/ G- P. F6 ?  ^
  6.         UINT bw, br, i;
    : Y7 U9 {- e) J% m
  7.         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
赞 收藏 评论0 发布时间:2022-1-28 16:32

举报

0个回答

所属标签

相似技术帖

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版