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

STM32开发进阶SD卡操作经验分享

[复制链接]
攻城狮Melo 发布时间:2023-4-11 19:23
微信图片_20230411192319.jpg
) i8 b! f0 Q+ Z) {
SD卡(Secure Digital Memory Card)即:安全数字内存卡,它是在MMC的基础上发展而来,是一种基于半导体快闪记忆器的新一代记忆设备,它被广泛地于便携式装置上使用,例如数码相机、个人数码助理(PDA)和多媒体播放器等。SD卡由日本松下、东芝及美国SanDisk公司于1999年8月共同开发研制。
6 t# r! d  F$ b  J6 J; f0 C/ e5 z
  • SD卡简介
    3 n: G4 x; V! I3 D$ J
SD非常小巧,可以嵌入到设备中,在嵌入式开发中,产品需要存储一些容量叫大的文件,就可能会运用到SD卡。SD卡按容量分类,可以分为3类:SD卡、SDHC卡、SDXC卡,如下表所示。

* }# s  I' T, j$ T
微信图片_20230411192315.png

0 T; B( a8 M7 e$ }' }
容量大小不同的SD卡,其内部的操作标准是不同的。很多外设会明确告诉用户本设备最大能够支持容量多大的外扩,不能无限制的扩存,因为容量越大,操作不同,对控制器的要求比较高。当前设备的控制器不足以支持大容量,所以有外扩限制。

- \- g7 T$ N. t8 L3 {+ Y& E% H
微信图片_20230411192311.png

2 f# d$ C9 Z3 K3 N$ z$ y9 p
SD卡由9个引脚与外部通信,支持SPI和SDIO两种模式,不同模式下,SD卡引脚功能描述如下表所示。

8 u; P  v5 B. y' N: A

- A4 f+ w9 t& d* t$ x& Q. f
微信图片_20230411192306.png 4 k2 b* \: [! ^8 ]
5 p7 Z+ Q5 L/ {0 b1 Z/ `
  • SD卡的物理结构
    7 i7 V) W% [+ `. N) _1 A+ t2 U) Z

7 }/ p" ~# |6 F+ H% \
一张SD卡包括有存储单元、存储单元接口、电源检测、卡及接口控制器和接口驱动器5个部分。
+ n: H# y' P; L! X" F/ k
微信图片_20230411192301.png

. e' b$ O* v/ N: ^( M2 T8 t  J: _: c  r
SD卡由9个引脚与外部通信,支持SPI和SDIO两种模式,不同模式下,SD卡引脚功能描述如下表所示。
" q- w+ v& g2 H- v& O

" z" Q2 c' \, X; g2 Z  b5 {5 B2 s$ q6 l- P5 v: p
存储单元:是存储数据部件,存储单元通过存储单元接口与卡控制单元进行数据传输;
. q) D3 m' h- e7 t
电源检测单元:保证SD卡工作在合适的电压下,如出现掉电或上状态时,它会使控制单元和存储单元接口复位;

# }& M* t9 B  G3 P
卡及接口控制单元:控制SD卡的运行状态,它包括有8个寄存器;

" Z' p! F' z- h# F- L' X5 p
接口驱动器:控制SD卡引脚的输入输出。
4 \0 [) a6 T6 ?" D/ r9 D* V5 W3 `

3 D2 I0 Q  D: N" s
  • SD卡内部寄存器
    & ?3 b7 G$ w/ `" H  C" P
SD卡总共有8个寄存器,用于设定或表示SD卡信息。这些寄存器只能通过对应的命令访问,程序控制中只需要发送组合命令就可以实现SD卡的控制以及读写操作。

' m5 G* C9 Y3 N5 O5 J
微信图片_20230411192255.png
+ c3 f  F, H0 S/ y3 @8 A
  • SDIO接口
    7 i& ^, C8 O+ M% [# ]9 f2 J# U
SDIO全称是安全数字输入/输出接口,多媒体卡(MMC)、SD卡、SD I/O卡都有SDIO接口。STM32F407系列控制器有一个SDIO主机接口,它可以与MMC卡、SD卡、SD I/O卡以及CE-ATA设备进行数据传输。也就是说SDIO接口不仅仅只会操作SD卡,凡是符合SDIO接口通信的设备都可以被操作。

3 k1 y! F) R* l" N6 o6 d9 v
微信图片_20230411192250.png

* V8 q) p1 ?4 A- `% Q. P2 d
SDIO 具有以下特性:
完全兼容 多媒体卡系统规范版本 4.2。卡支持三种不同数据总线模式:1 位(默认)、4 位和 8 位
完全兼容先前版本的多媒体卡(向前兼容性)
完全兼容 SD 存储卡规范版本2.0
完全兼容 SD I/O 卡规范版本 2.0 : 卡支持两种不同数据总线模式:1 位(默认)和 4 位
完全支持 CE-ATA 功能(完全符合 CE-ATA  数字协议版本 1.1)
对于 8 位模式,数据传输高达 48 MHz
数据和命令输出使能信号,控制外部双向驱动程序。
SDIO 不具备兼容 SPI 的通信模式。

. W1 i) s; S* W7 B+ D
  • SDIO组成
    $ R$ T+ c" B: z
SDIO 适配器块提供特定于 MMC/SD/SD I/O 卡的所有功能,如时钟生成单元、命令和数据传输。
' I' m. B- ]0 t" |' ~1 r7 x
APB2 接口访问 SDIO 适配器寄存器,并且生成中断和 DMA 请求信号。

6 [- `" s0 k, {" W
微信图片_20230411192246.png
! ?7 a9 ?% W2 D' |1 F/ U8 O
注:
1、默认情况下,SDIO_D0用于数据传输。初始化后,主机可以更改数据总线宽度。
2、SD 卡连接到总线,主机可以将数据传输配置为SDIO_D[3:0]
3、SDIO使用两个时钟信号:SDIO适配器时钟(SDIOCLK= 48 MHz)和APB2总线时钟(PCLK2)。
4、卡时钟(SDIO_CK):每个时钟周期在命令和数据线上传输1位命令或数据。对于SD或SDI/O卡,时钟频率可以在0MHz至25MHz间变化,当数据正式稳定传输的时候配置为24MHz。SDIO_CK计算公式:SDIO_CK=SDIOCLK/(2+CLKDIV)
5、SDIO适配器时钟(SDIOCLK):该时钟用于驱动SDIO适配器,可用于产生SDIO_CK时钟。对F4来说,SDIOCLK来自PLL48CK(48Mhz)。
6、F4:APB2总线接口时钟(PCLK2):该时钟用于驱动SDIO的APB2总线接口,其频率为PCLK2=84Mhz。

' b- C3 a2 G8 z" d
注意:在SD卡初始化时,SDIO_CK不可以超过400Khz,初始化完成后,可以设置为最大频率(但不可以超过SD卡最大操作频率)。

' j/ v  M$ Z' \0 k' O- |8 a
微信图片_20230411192243.png

4 c4 E7 O) Q) Y& i; g( }
该适配器由五个子单元组成:
1.适配器寄存器块 :适配器寄存器模块包含所有系统寄存器。
2.控制单元   : 控制单元包含电源管理功能和存储卡时钟的时钟分频器。
3.命令路径 :命令路径单元向卡发送命令并从卡接收响应。
4.数据路径 :数据路径子单元负责与卡相互传输数据。
5.数据 FIFO :数据 FIFO(先进先出)子单元是一个数据缓冲器,带发送和接收单元。FIFO 包含一个宽度为 32 位且深度为 32 字的数据缓冲器和发送/接收逻辑。(一共32 个单元,一个单元一个字)。所有的数据传输都要经过FIFO,便于管理。

0 m, ?! X/ A  A: ~# I+ n
  • SDIO 命令简介

    4 _; h6 o7 f: ]+ Y
SD命令由主机发出,以广播命令和寻址命令为例,广播命令是针对与SD主机总线连接的所有从设备发送的,寻址命令是指定某个地址设备进行命令传输。
; @7 U9 e" f1 c  `8 M" g9 a
SD命令格式固定为48bit,都是通过CMD线连续传输的,数据线不参与。
! v4 [. \" a6 m9 Q1 W
微信图片_20230411192239.png
) x1 s% S) Q4 k* o* x- E
SD命令的组成如下:
起始位和终止位:命令的主体包含在起始位与终止位之间,它们都只包含一个数据位,起始位为 0,终止位为 1。
0 B/ l  C5 y3 B: s0 h5 ?

3 p* ^8 F+ ]6 D0 {/ }
传输标志:用于区分传输方向,该位为 1 时表示命令,方向为主机传输到 SD 卡,该位为 0时表示响应,方向为 SD卡传输到主机。
: n# q5 ]  S( z! \$ [4 W
命令主体内容包括命令、地址信息/参数和 CRC 校验三个部分。

: m& j  N6 ?2 q$ c; j
命令号:它固定占用 6bit,所以总共有 64个命令(代号:CMD0~CMD63),每个命令都有特定的用途,部分命令不适用于 SD 卡操作,只是专门用于 MMC卡或者SD I/O卡。

0 y5 I2 g/ u* T6 a
地址/参数:每个命令有 32bit地址信息/参数用于命令附加内容,例如,广播命令没有地址信息,这 32bit用于指定参数,而寻址命令这 32bit用于指定目标 SD卡的地址。

% |# _9 |# v0 o1 L: w
CRC7 校验:长度为 7bit的校验位用于验证命令传输内容正确性,如果发生外部干扰导致传输数据个别位状态改变将导致校准失败,也意味着命令传输失败,SD卡不执行命令。
' y! I! |. d( A3 i' w  `
SD命令有4种类型:
1.无响应广播命令(bc),发送到所有卡,不返回任务响应;
2.带响应广播命令(bcr),发送到所有卡,同时接收来自所有卡响应;
3.寻址命令(ac),发送到选定卡,DAT线无数据传输;
5.寻址数据传输命令(adtc),发送到选定卡,DAT线有数据传输。
4 x2 n4 a+ [/ @- E0 k) j
在标准中定义了两种类型的通用命令:特定应用命令(ACMD)和常规命令(GEN_CMD),也就是说在64个命令作为常规命令的基础上加了特定的命令
% e! t- y- G3 o9 w* S% W2 h. J
要使用SD卡制造商特定的ACMD命令如ACMD6,需要在发送该命令之前无发送CMD55命令,告知SD卡接下来的命令为特定应用命令。CMD55命令只对紧接的第一个命令有效,SD卡如果检测到CMD55之后的第一条命令为ACMD则执行其特定应用功能,如果检测发现不是ACMD命令,则执行标准命令。

) \! M) U4 R) P! w* J/ `( r
SD命令响应由SD卡向主机发出,部分命令要求SD卡作出响应,这些响应多用于反馈SD卡的状态。基本特性如下:
3 ^/ I7 x% e# `0 |9 o% U# S" V
lSDIO总共有7个响应类型(代号:R1~R7),其中SD卡没有R4、R5类型响应。特定的命令对应有特定的响应类型,比如当主机发送CMD3命令时,可以得到响应R6。与命令一样,SD卡的响应也是通过CMD线连续传输的。根据响应内容大小可以分为短响应和长响应。短响应是48bit长度,只有R2类型是长响应,其长度为136bit。

5 E# t& W/ i, d
SDIO读数据
8 F: J& Y4 H) ^7 |; N2 q- N7 M
微信图片_20230411192236.png

9 x( s* ^; b7 j' t5 J
单个块读操作与多个块的读操作除命令不同外,还体现在读操作结束时,单个块读取一个块自动结束,而多个块还需要主机发送停止命令。写操作类似,也要多发一个结束命令,只不过写操作写数据前需要检查卡的状态是否为忙状态。
2 F6 q* M) m; W. Q
SDIO写数据

* Q7 ~' l# e/ x; A# m7 v. z. O
微信图片_20230411192231.png
0 s, r* |; a, {! w! Z/ }' F
  • SD卡操作模式

      E  @3 q- {2 X1 L# \# O
SD卡有多个版本,STM32控制器目前最高支持《Physical Layer SimplifiedSpecification V2.0》定义的SD卡,STM32控制器对SD卡进行数据读写之前需要识别卡的种类:V1.0标准卡、V2.0标准卡、V2.0高容量卡或者不被识别卡。

. r5 g9 r5 v- B1 b! J( M
SD卡系统定义了两种操作模式:卡识别模式和数据传输模式。
1 [, v" e: W) ~/ h% E5 ^9 H. \
在系统复位后,主机处于卡识别模式,寻找总线上可用的SDIO设备;同时,SD卡也处于卡识别模式,直到被主机识别到,即当SD卡接收到SEND_RCA(CMD3)命令后,SD卡就会进入数据传输模式,而主机在总线上所有卡被识别后也进入数据传输模式。
) J  K  e$ x- I- W& \2 q  B' U3 c
每个不同的操作模式下,SD卡都有不同状态,通过命令控制实现卡状态的切换,在不同的状态下做不同的事,比如在发送数据前需要SD卡处于传输状态,发送数据时,SD卡处于接收状态。

) [% ~: `, U6 g# k, q4 Z' A
微信图片_20230411192227.png
2 K5 {8 j1 b9 S# j4 G4 |
  • 卡识别模式

    ( f+ M: h- Z; i- @4 ~
微信图片_20230411192220.png

' E( g5 R/ n1 k1 Y. K
①上电后,主机发送CMD0让所有卡软复位从而进入空闲状态。
②主机发送CMD8确定卡的电压范围,并识别是否为2.0的卡
③主机发送ACMD41识别或拒绝不匹配它的电压范围的卡,SD卡需要工作在特定的电压范围之内
④主机发送CMD2来控制所有卡返回它们的卡识别号CID(128位)
⑤主机发送CMD3命令,让卡推荐一个RCA(16)地址作为以后通信的标识,之后都以RCA值作为身份标识进行信息交互。
: t) @0 @) u+ `( @7 k
注:在卡识别过程中,要求SD卡工作在识别时钟频率FOD的状态下,在SD卡初始化时,SDIO_CK不可以超过400Khz

0 l. H' K5 i6 q  `: G7 t: G4 D) w
  • 数据传输模式
    - Z: n- x" C' S# d. i
微信图片_20230411192214.png

  B0 }  ]. Z$ [" {
只有SD卡系统处于数据传输模式下才可以进行数据读写操作。数据传输模式下可以将主机SD时钟频率设置为FPP,默认最高为25MHz,频率切换可以通过CMD4命令来实现。通过CMD7命令加上RCA值来选定指定的卡,选中后SD卡进入数据传输状态,就可以发送CMD17读单个块,CMD18读多个块,读多个块时只有发送CMD12命令才会停止。SD卡再次进入传输状态,若不想对卡有任何操作可以再次发送CMD7命令加上RCA值来取消指定的卡,写操作与上述原理相同。
! z) C4 t- `" z$ B" ~' H% A: X. O& H
  • SD卡普通模式操作实例

    - y. H7 n, P8 P# u
实验内容:向SD卡写入数据后读出

% Y4 Y5 C$ O" S9 w1 K/ C2 U
实验步骤:

$ u) G9 c$ h5 {% F# }
1.配置RCC,与以往不同的是SDIO适配器的时钟是单独配置的,需要专用的SDIOCLK,标准工作在48MHz
- ]1 Y3 \) G+ A
微信图片_20230411192210.png
+ \8 c1 R3 F! L' }0 B8 o( b
2.配置SDIO
4 ?4 l" S4 w9 }$ b# W( W+ W

4 p2 m6 {$ Z- u9 U: P; V5 p( k6 w
微信图片_20230411192207.png
$ q, K1 o& U$ o) `9 Q2 h  `9 l: e
微信图片_20230411192203.png

2 {  |; m9 W% Y7 g
3.编写代码
  1. 2 ?( j/ f1 o2 n+ l% [% {1 `# C9 l8 h/ F
  2. //mian.c( b# l( O4 v( x7 y/ q+ z# a
  3. #include "main.h") t- k$ D2 Z  Z
  4. #include "stm32f4xx_hal.h"
    $ B- o  T0 X8 w4 e$ M# f
  5. #include "sdio.h"7 K+ @& |& L3 [9 I& U
  6. #include "usart.h"* D, S/ s( Q; w1 ~7 J4 I: I* D6 `
  7. #include "gpio.h"
    ) p; x6 ~! u+ b* f6 _" x
  8. #define SDBUF_SIZE   1024) l# [! i6 B+ t* y
  9. uint8_t SDBUF_TX[SDBUF_SIZE],SDBUF_RX[SDBUF_SIZE];//数据传输的buf,一个用于传输,一个用于接收
    , J2 K; C) x3 m; h7 u3 M) N
  10. //定义全局变量,最好不定义局部变量,防止因过大造成栈溢出
    , {7 Z, J- w- Y/ v& M' H* V: O
  11. int main(void)" k; y% N6 p4 h, Z: L8 g
  12. {
    ! e4 l: z) J/ J6 j/ l6 ^
  13.       uint32_t i;
    ) d' l5 S. g" Y! j
  14.       HAL_SD_CardInfoTypeDef pCardInfo;//定义结构体用来接收卡信息
    & e# C* T* i4 t( D
  15.       HAL_Init();5 c* C- a0 i4 |- |( _0 C" ?
  16.       SystemClock_Config();+ z8 ~( e# ~. [6 X4 R- m
  17.       MX_GPIO_Init();
    1 |3 x2 _' _" ~0 b5 H- I$ b0 G' `0 C7 E0 o
  18.       MX_SDIO_SD_Init();7 j' I- a+ m! E, c
  19.       MX_USART1_UART_Init();! v3 P3 _2 ^/ Q1 S( U
  20.       printf("this is sd test\n");- t! s% @  C4 e4 ~
  21.       //卡识别结束后就可以调用HAL_SD_GetCardInfo()函数获取卡信息并打印
    # S7 t2 }' h) s
  22.       HAL_SD_GetCardInfo(&hsd, &pCardInfo);
    & R) C/ W) U* I" z; S
  23.       printf("pCardInfo.CardType = %u\n",pCardInfo.CardType);; u6 {( L9 v; T1 r# E7 ^$ Z5 l
  24.       printf("pCardInfo.CardVersion = %u\n",pCardInfo.CardVersion);//版本
    7 q8 ?# Z: r, ?2 ]
  25.       printf("pCardInfo.BlockNbr = %u\n",pCardInfo.BlockNbr);//SD卡块数! R' {# O, b3 e0 c8 y/ r  e8 i8 s
  26.       printf("pCardInfo.BlockSize = %u\n",pCardInfo.BlockSize);//每一块大小$ ~5 |6 A! s7 c; a( W# J
  27.       /*--------------------SD卡写测试----------------------------------*/) Z' x/ r. v9 v; V2 h: Z
  28.       memset(SDBUF_TX, 0x8, SDBUF_SIZE);  /、填充TXbuf为0x88 Y' N4 `. Y7 v8 c$ f/ W
  29.       /**
    ) Q  I  N8 i7 z* H0 T% O
  30.          //函数功能及参数描述                             
    0 z9 [/ T- n! h- J
  31.         *HAL_StatusTypeDef HAL_SD_WriteBlocks(SD_HandleTypeDef *hsd, uint8_t *pData,
    , X  @' ]3 l8 @* E/ p% ~4 z
  32.                       *uint32_t BlockAdd, uint32_t NumberOfBlocks, uint32_t Timeout)
    : p  Y# \1 R3 g( X" x5 T
  33.         * @param  hsd Pointer to SD handle
    ' R2 [: k6 H7 A: h! o! K
  34.         * @param  pData pointer to the buffer that will contain the data to transmit
    ; a3 E# Z5 T7 w2 c0 M
  35.         * @param  BlockAdd Block Address where data will be written,从哪一个块开始写  
    0 M+ i* P  v3 }' C4 V
  36.         * @param  NumberOfBlocks Number of SD blocks to write   写几个块
    ' a. ]4 d2 q9 X4 c! K  ~% U
  37.         * @param  Timeout Specify timeout value 超时时间
    9 P6 |  ?+ U  d
  38.         * @retval HAL status
    : D( e8 @" a- w/ @
  39.         if( HAL_SD_WriteBlocks(&hsd, SDBUF_TX, 0 , 2, 1000) == HAL_OK)8 n+ v/ e$ n( b8 i3 Z' t1 k2 _0 @9 v! X
  40.         {
    4 `7 q' q" V# o" g9 Z
  41.             while(HAL_SD_GetCardState(&hsd) != HAL_SD_CARD_TRANSFER);//处于传输状态退出2 z0 L$ V& q+ w. V4 r6 K
  42.             printf("WriteBlocks Successfully\n");
    1 {  F5 X: Y( P) t
  43.             for(i=0; i<SDBUF_SIZE; i++)
    # _5 v, E4 u) F, ?
  44.             {
    7 K- n. a1 L0 ]2 {0 F9 h, g! G
  45.                 printf("%d ",SDBUF_TX[i]);
    - c9 q* R1 I- \
  46.             }
    + p% I& z% `/ S1 J
  47.             printf("\r\n");8 [8 j$ [# p1 L: Q1 i6 F
  48.         }
    & O9 |+ \$ {4 W+ q  s
  49.         else
      \3 F3 S& J% f  \7 Y% @# U6 Z3 l
  50.         {
      ^, F  T% B4 U; ^4 v0 L' z
  51.             printf("WriteBlocks Failed\n");# ~0 N2 h- u4 B2 S; }& K: @; u) r6 y
  52.         }5 U, g$ W4 \' n3 w0 ]) u1 a
  53.         /*--------------------SD 卡读测试----------------------------------*/. L9 L" M1 _& q# a
  54.         //与HAL_SD_WriteBlocks函数的参数功能相同
    . g0 y9 F3 D1 P$ ]- _' ?% l
  55.         if( HAL_SD_ReadBlocks(&hsd, SDBUF_RX , 0, 2, 1000) == HAL_OK)8 N3 ~! C2 A5 Z2 }+ e& [
  56.         {5 ~, X0 w) _$ q0 H
  57.             while(HAL_SD_GetCardState(&hsd) != HAL_SD_CARD_TRANSFER);//返回到传输状态退出( W  p  r8 y5 O9 \
  58.             printf("ReadBlocks Successfully\n");( G5 A3 b. b  M3 Y7 N: a' ~
  59.             for(i=0; i<SDBUF_SIZE; i++)
    ' \4 X1 v8 p+ ~# j5 D+ K
  60.             {
    ' m& h8 K0 \' `. q+ k
  61.                 printf("%d ",SDBUF_RX[i]);
    1 r3 [. ]$ n0 p( k$ k; j; V
  62.             }
    ! F5 k: W9 x2 E/ y: w6 g- L
  63.             printf("\r\n");
    / _4 n( f$ T2 `  \( D7 ]% Q* D
  64.         }
    & {+ F" q$ b! g( P8 T- f! [. f: D
  65.         else
    ; ^. w! x6 g5 X' J( c0 g
  66.         {$ U4 e0 z# h; S6 L; v- ?) J
  67.             printf("ReadBlocks Failed\n");
    ( A/ p" C/ B4 {; P( {/ N$ u: f
  68.         }$ L( e  x$ T9 x& n! Q! P
  69.         /*--------------------SD 擦除测试----------------------------------*/1 ^+ k: G* G5 K# ?7 O0 S9 i
  70.         if (HAL_SD_Erase(&hsd, 0, 1) == HAL_OK )" j& a8 ], N- n! ~9 E" T* y
  71.         {: H/ L5 n0 m! R# b; a% J$ X
  72.             while(HAL_SD_GetCardState(&hsd) != HAL_SD_CARD_TRANSFER);. S+ b0 T, d# A
  73.             printf("SD_Erase Successfully\n");
    8 {) u& Y# y; }- A) Q) v
  74.         }
    . q: [; c6 K2 w/ \# `" H7 M/ Q! p
  75.         else; x7 q0 \. I- e3 Z% o' ?- l, Q) _
  76.         {! a3 |1 `% q% G4 M0 d; n
  77.             printf("SD_Erase Failed\n");. O$ o5 {5 l3 H
  78.         }2 A2 @# s. F! W' ]+ y
  79.         /*--------------------SD 卡读测试----------------------------------*/
    3 |7 L  v( e6 V3 P0 v
  80.         if( HAL_SD_ReadBlocks(&hsd, SDBUF_RX , 0, 2, 1000) == HAL_OK)) P# G; O1 N: e) u0 U$ E9 \2 ]) Q8 |; Z% a
  81.         {
    ; \& {- M9 Y& W1 ]; Z
  82.             while(HAL_SD_GetCardState(&hsd) != HAL_SD_CARD_TRANSFER);
    / M& q/ P! \2 o# H/ I9 d3 v
  83.             printf("ReadBlocks Successfully\n");1 U+ V( |9 ]# P  u1 a' w+ f
  84.             for(i=0; i<SDBUF_SIZE; i++)
    # l% Q2 o. F/ [8 I# _: R+ ~- y
  85.             {: B9 P3 c4 X. u& _# m
  86.                 printf("%d ",SDBUF_RX[i]);
    ' ~8 h# g( N$ N/ q0 O
  87.             }
    4 Y/ V/ z7 @" M) L
  88.             printf("\r\n");3 d, j( \' w' |2 w" ]' u
  89.             }8 ?. j  {! j' x' X# J7 j0 I" I4 _' i
  90.         else
    ; B, u/ L6 Q6 M* S
  91.         {: Z5 Z, q' m/ e  e; y5 B" H" ]" ?" Y
  92.             printf("ReadBlocks Failed\n");
    3 Z+ r% R3 N3 I
  93.         }
    : }: B- W  J  m! u0 E* x0 h
  94.         while (1)' A* G1 e) ~6 n
  95.         {
    % R7 b. S/ v9 J- e; [
  96.         }  
    9 o2 ?5 u4 Q2 E' F! U( o
  97. }
复制代码
  1. //sdio.c
    7 i$ k, x4 I4 s+ J
  2. //此代码为工程自动生成,非用户编写,这里只做分析3 T/ ^6 Z2 ~6 k/ {
  3. #include "sdio.h"4 b# Z; w1 _1 [/ v
  4. #include "gpio.h"+ q3 O, G  d) t8 v9 x: U2 w! M
  5. /* USER CODE BEGIN 0 */
    0 M7 G' q" ^! s) B
  6. /* USER CODE END 0 */
    ! G+ K$ W: {1 }8 R" E$ `
  7. SD_HandleTypeDef hsd;
    * J8 C: v- T# v% F* o% V  g
  8. /* SDIO init function */8 }( c" }+ I0 f6 P( [
  9. void MX_SDIO_SD_Init(void)   //初始化配置
    . ~6 J, P; O: A3 Q  l
  10. {. b1 j' }  _! U
  11.   hsd.Instance = SDIO;    //SDIO句柄,整个系统中只有这一个
    4 `) y) h( G9 J/ N
  12.   hsd.Init.ClockEdge = SDIO_CLOCK_EDGE_RISING;
    + X  G8 u1 ^/ b
  13.   hsd.Init.ClockBypass = SDIO_CLOCK_BYPASS_DISABLE;; m& ~% i7 [4 w8 b0 D
  14.   hsd.Init.ClockPowerSave = SDIO_CLOCK_POWER_SAVE_DISABLE;: X# g5 L/ H0 i( ?* d6 X
  15.   hsd.Init.BusWide = SDIO_BUS_WIDE_1B;   //初始化时设置数据宽度为1位
    1 o* c8 l& H0 e7 m/ d  E
  16.   hsd.Init.HardwareFlowControl = SDIO_HARDWARE_FLOW_CONTROL_DISABLE;, y8 v' g" [& }1 I! v
  17.   hsd.Init.ClockDiv = 0;  e$ u* k! Y# n  j, K
  18. //HAL_SD_Init()函数内部做了很多事去识别SD卡,具体的卡识别代码分析在下方
    $ u+ F5 d5 u$ ]1 t# s) `9 S. W
  19. if (HAL_SD_Init(&hsd) != HAL_OK)
    # U* T4 C' f! h' d' |1 L* H
  20.   {. Q, ^0 V( A. @4 z
  21.     _Error_Handler(__FILE__, __LINE__);2 M2 i: |8 z7 G+ M; Z6 e) S
  22.   }  t' ]! {! s, ]. R# j( p: u7 M/ [
  23. //初始化结束后重新配置数据宽度为4位6 |' L: y: `7 V( F! F" u
  24. if (HAL_SD_ConfigWideBusOperation(&hsd, SDIO_BUS_WIDE_4B) != HAL_OK) 7 m, Y, R5 B$ f7 s; q: ?
  25.   {4 }( h4 s9 N6 V, W: t+ W
  26.     _Error_Handler(__FILE__, __LINE__);3 X2 W5 M& i3 g; ~* {, l
  27.   }
    - f9 z- }/ N3 I' c- L9 h6 a
  28. }7 J+ c0 o! J: y) m
  29. //卡识别代码如下( i# x4 X7 U: d! Q$ O
  30. HAL_StatusTypeDef HAL_SD_InitCard(SD_HandleTypeDef *hsd)
    . [9 Q% X0 o0 J8 h
  31. {( i9 c. `' X7 a) }
  32.   uint32_t errorstate = HAL_SD_ERROR_NONE;
    # e  L4 Q' x) }0 t6 l1 F
  33.   SD_InitTypeDef Init;
    & l: @7 l( k6 l2 v! `
  34. /* Default SDIO peripheral configuration for SD card initialization */' O" b$ A1 E/ c/ z% f+ O! _3 c
  35. //完成卡识别; U8 `8 ]: Z! B& W; y7 u4 o
  36.   Init.ClockEdge           = SDIO_CLOCK_EDGE_RISING;
    6 _4 a$ f. L4 z9 d
  37.   Init.ClockBypass         = SDIO_CLOCK_BYPASS_DISABLE;8 ?' I- {& B& r/ N- W+ u, ^
  38.   Init.ClockPowerSave      = SDIO_CLOCK_POWER_SAVE_DISABLE;
      `9 v2 m- Y: B1 z+ R4 z, J
  39.   Init.BusWide             = SDIO_BUS_WIDE_1B;  }0 y4 b: i/ K0 k
  40.   Init.HardwareFlowControl = SDIO_HARDWARE_FLOW_CONTROL_DISABLE;3 L) @* U0 \; Y6 U) |7 J
  41.   Init.ClockDiv            = SDIO_INIT_CLK_DIV;
    % B5 ]9 \9 h9 J3 q
  42. //SDIO_CK卡识别阶段要求时钟小于400KHz,宏定义
    ( _# l: {5 e5 H9 H
  43. //SDIO_CK计算公式:SDIO_CK=SDIOCLK/(2+CLKDIV)
    9 B2 S3 c" l6 R; \+ }
  44. /* SDIO Initialization Frequency (400KHz max) */
    / K( V. T8 n. ?: i4 `0 H' F
  45. /*#define SDIO_INIT_CLK_DIV     ((uint8_t)0x76)*/
    - c$ B! c  H+ k( _3 Z
  46. /* Initialize SDIO peripheral interface with default configuration */" F4 ^9 N0 f4 a8 P
  47.   SDIO_Init(hsd->Instance, Init);
    9 @6 s0 Q: A) ~6 T8 J
  48. /* Disable SDIO Clock */; J  D; y, k0 G+ Y+ v/ N0 E
  49.   __HAL_SD_DISABLE(hsd);
    ; R/ W9 ?, J4 `
  50. /* Set Power State to ON 对SD卡进行上电 */3 h- a9 `" V3 p% V: F) G
  51.   SDIO_PowerState_ON(hsd->Instance);/ D7 F4 Y4 n% |( D# c
  52. /* Enable SDIO Clock */
    - z2 x0 E6 ]: m/ R* H) t4 e
  53.   __HAL_SD_ENABLE(hsd);
    ' j$ ?. y) s9 ^  ~: A
  54. /* Required power up waiting time before starting the SD initialization
    , r! D6 T0 g! {4 l! M  p: q
  55.   sequence *// z& e( e2 w1 y/ l6 ^& s' d; X3 ~
  56.   HAL_Delay(2U);
    3 Z1 o- T1 p6 A& w0 D# w
  57. /* Identify card operating voltage */
    , `' w( b, H+ n, x; r" V+ F9 T" W
  58.   errorstate = SD_PowerON(hsd);" O1 A$ Y; Z* e1 H, |. n/ s
  59. if(errorstate != HAL_SD_ERROR_NONE)9 c& c; p3 u2 [
  60.   {
    ( y! _& W% u) G" V0 B$ m, t
  61.     hsd->State = HAL_SD_STATE_READY;
    6 ~" @4 Z8 L: R
  62.     hsd->ErrorCode |= errorstate;
    ; \( `3 D0 c! }) P+ k0 e: [
  63. return HAL_ERROR;
    " @  x4 e5 @- Y/ d' {! ]$ O7 R* {+ `
  64.   }
      c+ f9 H/ `2 B
  65. /* Card initialization */  {; \0 H( L) D: r
  66.   errorstate = SD_InitCard(hsd);! w5 b$ i3 j$ }% d% a2 y) R
  67. if(errorstate != HAL_SD_ERROR_NONE)
    9 b1 P1 r3 d8 D! y2 d# S6 t
  68.   {1 [' {( u8 h0 u( u
  69.     hsd->State = HAL_SD_STATE_READY;7 a) ]6 M9 @: N& K: |' ~
  70.     hsd->ErrorCode |= errorstate;0 t1 h- S) N$ b' L" v5 ^
  71.     return HAL_ERROR;8 I9 }0 C! K1 S! I" a
  72.   }% z2 F' n) B; F) l
  73. return HAL_OK;0 g* o" P0 U* f  y- P; }
  74. }
复制代码

; }% @$ D  x: @8 [8 W
  • SD卡DMA模式操作实例- m) C/ k6 O6 T% r8 f! G
    ) v& p( z/ B+ Y4 G
当SD卡中有大量的音视频需要读取时,整个过程需要CPU干预,这样CPU的利用率就会降低,因此大量数据的传输最好启用DMA。

5 v% u1 X# W. v6 Z! {
实验内容:向SD卡写入数据后读出。
; @7 j' o: X0 Y
实验步骤:工程的时钟配置和SDIO配置与普通模式相同,只有一点不同需要打开SDIO的全局中断,因为SDIO发送与接收完成后都需要中断去生成DMA请求。设置SDIO全局中断优先级更高一些,因为它内部还有很多其他中断,发生错误时更需要处理。

" U. E# E$ m/ I; J5 i
微信图片_20230411192200.png
4 P1 u; H3 {9 m, p( K
配置DMA
- Q9 M# c' v9 q6 W
微信图片_20230411192156.png

1 M/ t) Q- T! A6 U2 m
编写代码:
  1. //mian.c
    4 O2 F- s- @3 ?

  2. + e5 b, F* G: c/ {
  3. #include "main.h"
    " J7 [3 `- _& ]% y) d( ^; w- o- p. D
  4. #include "stm32f4xx_hal.h"
    , u% |1 x) d; F5 K, ^4 y7 D% P
  5. #include "dma.h"0 a: o; }0 V% V# F
  6. #include "sdio.h"
    ) }( d5 \8 X& M# s* ~% G
  7. #include "usart.h"
    6 l! b5 H1 @% ^5 f/ t! v4 [0 s
  8. #include "gpio.h"  H1 w6 ]7 ]: {; _0 g( o1 J$ G

  9. 5 r3 q  r7 I& q. P) D: o; j1 ^4 r
  10. #define SDBUF_SIZE   1024  J; }" @! J/ p8 z
  11. uint8_t SDBUF_TX[SDBUF_SIZE],SDBUF_RX[SDBUF_SIZE];//数据传输的buf,一个用于传输,一个用于接收
    3 C7 \# \4 l8 T' b* ^0 @
  12. //定义全局变量,最好不定义局部变量,防止因过大造成栈溢出
    % H- {4 K% T- i; G) }2 z0 ^
  13. uint8_t DMA_SEND_OK, DMA_RCV_OK;
    ; p2 A- ^) C3 R" W

  14. " F6 s: J( n% \; u& J( {1 f; s9 y
  15. int main(void)
      e9 B# U' \9 G' D
  16. {4 g6 b4 P* r/ h# s4 s
  17.       uint32_t i;$ t$ |4 ?7 o) _/ g9 p( l7 O  c8 _: o
  18.       HAL_SD_CardInfoTypeDef pCardInfo;//定义结构体用来接收卡信息
    , a4 I! o! d; P9 [* W1 Y+ @
  19.       HAL_Init();
    " u7 z( x, c& g" j6 g' O" A
  20.       SystemClock_Config();
    * U8 l" N3 |$ {' j# C* z( K
  21.       MX_GPIO_Init();$ X, J- J( \( C8 h4 O; m0 p
  22.       MX_DMA_Init();% E- _4 A; B, Y; v: H
  23.       MX_SDIO_SD_Init();
    ' d* R  n  a$ u$ l# T5 Z7 P
  24.       MX_USART1_UART_Init();3 K2 n  N4 n8 c/ p

  25. 7 |2 x0 L* ~- ?! [" a* d' z
  26.       printf("this is sd test\n");
    0 R0 n, w' f- Q5 l+ y! z
  27.       //卡识别结束后就可以调用HAL_SD_GetCardInfo()函数获取卡信息并打印
    + j' `# M- w: E2 I
  28.       HAL_SD_GetCardInfo(&hsd, &pCardInfo);
    , w% A, e( b$ f' Y
  29.       printf("pCardInfo.CardType = %u\n",pCardInfo.CardType);, |; I/ J. \0 J6 T6 ]! D$ x( I% {
  30.       printf("pCardInfo.CardVersion = %u\n",pCardInfo.CardVersion);//版本  P% s1 R0 E  U7 o2 @8 u
  31.       printf("pCardInfo.BlockNbr = %u\n",pCardInfo.BlockNbr);//SD卡块数) T/ g! H, V% g7 F2 w) j# m
  32.       printf("pCardInfo.BlockSize = %u\n",pCardInfo.BlockSize);//每一块大小! l) ^1 Z5 O% N* x5 N+ ]8 Y
  33.       /*------------------- SD DMA 写测试-------------------------------------*/& J$ y+ P4 b3 T" P, j  K5 n
  34.       memset(SDBUF_TX, 0x2, SDBUF_SIZE );7 p& `8 o5 Q9 ?' `
  35.       DMA_SEND_OK = 0;  //设置发送完成标志位为0,完成时为1
    ( K. s9 p7 Z3 L
  36.        /**
    2 Z7 A% ?1 Z0 |% s) ^& E& |" y
  37.        //函数原型1 V5 U4 P5 s7 j0 |3 r/ j1 W
  38.        *HAL_StatusTypeDef HAL_SD_WriteBlocks_DMA(SD_HandleTypeDef *hsd, uint8_t *pData, : D" Q- Q% Z5 J1 t/ m
  39.                                              uint32_t BlockAdd, uint32_t NumberOfBlocks)
    ) I; k7 i" ]5 v, i" _
  40.       3 I  H& M9 g2 Q5 @6 ^8 ~' i' r
  41.         * @param  hsd Pointer to SD handle; q* N. z; l) q! V5 q0 q" k( j
  42.         * @param  pData Pointer to the buffer that will contain the data to transmit& u2 a7 n  l5 E
  43.         * @param  BlockAdd Block Address where data will be written  从哪个块开始写
    1 e* t' ^" N+ p' S
  44.         * @param  NumberOfBlocks Number of blocks to write  写几个块( P, N8 F% E+ _2 U: `" ^
  45.         * @retval HAL status/ P9 X$ R, n. S. M( K. v
  46.         */
    + @- m& V3 d- T" `9 @
  47.       : n& ]* v5 h) \/ p9 W( g' z  [+ v
  48.       if( HAL_SD_WriteBlocks_DMA(&hsd, SDBUF_TX, 0, 1) == HAL_OK )
    , b5 ~+ T0 [: m! Z
  49.       {/ ~, s% S3 e9 j+ e1 F+ u- \9 J  U
  50.           //等待DMA传输完成,并且SD卡状态为传输状态+ ^4 G. G6 e  V1 T1 T2 ^
  51.           while( (DMA_SEND_OK ==0 ) || (HAL_SD_GetCardState(&hsd) != HAL_SD_CARD_TRANSFER));  
    8 M6 L3 ?$ g1 M, Y( }; `& ~! V1 g
  52.           printf("WriteBlocks Successfully\n");
    1 r6 u' x, m: l( z
  53.       
    $ P; \# `& T. v/ o
  54.           for(i=0; i<SDBUF_SIZE; i++)
    , G+ n. z& W0 S3 ?
  55.           {! z/ {/ J. Z0 i0 ?. ]& u
  56.             printf("%d ",SDBUF_TX[i]);& V! L6 U' [6 o  Z) v% y% {
  57.           }4 [$ F- g) ^/ M, B( R! B. l+ E
  58.           printf("\r\n");
    " G$ G  w0 J) S$ Q  L& j8 E- ~
  59.           }' n  r) ?6 L3 h6 w7 v4 A7 T9 c
  60.           else( G0 n; Q3 @: C5 Y3 T% [
  61.           {
    0 @" k# H8 F% C
  62.              printf("WriteBlocks Failed\n");
    * b5 f; _: E4 t9 _1 P
  63.           }3 N( b) k$ }. O+ |
  64.       
    . L7 K* E! N& q' P; N% n4 I. V7 u6 ]4 d
  65.           /*------------------- SD DMA 读测试-------------------------------------*/0 h* M2 z# t, V9 D, K
  66.           DMA_RCV_OK = 0;   //设置读取完成标志位为0,完成时为1
      q+ ^- w& q: p. j
  67.           if(HAL_SD_ReadBlocks_DMA(&hsd, SDBUF_RX, 0, 1) == HAL_OK): Z9 |8 A( X5 d2 I, K
  68.           {0 g. q; i# j4 L0 E5 B& Q1 ?3 F$ U; Z- t
  69.               while( (DMA_RCV_OK ==0) || (HAL_SD_GetCardState(&hsd) != HAL_SD_CARD_TRANSFER));          " a; [9 }1 p! Q( i" ~& g- V
  70.               printf("ReadBlocks Successfully\n");          " P  V! O+ R8 y. W: ~
  71.               for(i=0; i<SDBUF_SIZE; i++)7 F; ?) \  V) w% [0 V
  72.               {) n) _. ~  K( W- |2 Y4 a
  73.                 printf("%d ",SDBUF_RX[i]);
    ! t. E+ }$ K3 R+ D3 ~# J! Y
  74.               }- b, A* _. u0 m! }$ t
  75.               printf("\r\n");+ t2 d1 t" {/ l6 d
  76.            }
    7 x6 |# x$ [6 D, {
  77.             else
    - @& s: R' P3 r& e4 v+ N: P4 O
  78.             {- n% Z. f& P  p9 D, L- K) m  T
  79.               printf("ReadBlocks Failed\n");1 f* ]* k* y+ C6 Q' @5 T" V! E% A6 G
  80.             }
      x& }) E& a0 d, }8 Z) C9 M- J
  81.             while (1){}
    4 Q( X4 m1 i  B9 [
  82. }
复制代码
* C# `  W0 H2 Z! \$ K! R

  1. 6 W8 s1 q7 T+ N7 p
  2. //sdio.c& V9 |- q1 l0 G, E/ A. Q% `  U

  3. % d8 E4 M, \' y5 @3 J6 x1 t
  4. extern uint8_t DMA_SEND_OK, DMA_RCV_OK;0 G" h* Y. c! M# y4 G# o
  5. //发送完成中断处理& d1 f& y) M0 n6 e/ {/ z
  6. void HAL_SD_TxCpltCallback(SD_HandleTypeDef *hsd)" _9 T, a/ {: J' _- b3 X% l/ g4 e$ x2 [
  7. {! F7 k' A. ~2 ~( U4 W1 q+ `) F: X
  8.   DMA_SEND_OK = 1;
    ' l$ O' |* J9 }/ }, V  A
  9. }
    % a- v: H# ]% h
  10. //接收完成中断处理; \  V3 m' g# M- y# V
  11. void HAL_SD_RxCpltCallback(SD_HandleTypeDef *hsd)
    & O9 U6 S/ I: l$ ~- J
  12. {2 T5 B5 d$ s' ?0 Q3 s. ^
  13.   DMA_RCV_OK = 1;
    9 n$ P9 ~( w2 P4 ]
  14. }
复制代码
/ [% ?  z9 ^( S! f  b
至此,ARM开发进阶知识已全部更新完毕!下一系列介绍物联网操作系统开发!
' N' b0 s1 w' ~, F3 j& ]8 [$ f2 L# `' I1 k

4 `7 E. L; _% r/ D( |, }! z  q转载自: 骆驼听海
# @1 Q: _9 Z: d/ S) E  b5 w. Y; l如有侵权请联系删除
6 B7 Q; l+ d/ O( b4 j  F  t
赞 收藏 评论0 发布时间:2023-4-11 19:23

举报

0个回答

所属标签

相似技术帖

官网相关资源

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