|
1.SD卡介绍 3 }7 R9 b! r1 X. C
1.1 SD卡简介 很多单片机系统都需要大容量存储设备,以存储数据(常用的有U盘、FLASH芯片、SD卡等),比较而言SD卡是单片机大容量外部存储的首选,只需要少数几个IO口即可外扩一个容量从几十M到几十G的,且有多种体积尺寸可选(标准SD卡、TF卡等)的外部存储器。
( \$ @4 N" t& S: n; _, k1 d
SD卡(Secure Digital Memory Card)即:安全数码卡,它是在MMC的基础上发展而来,是一种基于半导体快闪记忆器的新一代记忆设备,它被广泛地于便携式装置上使用,例如数码相机、个人数码助理(PDA)和多媒体播放器等。SD卡由日本松下、东芝及美国SanDisk公司于1999年8月共同开发研制。
; K9 r8 R! r9 d# y; f* c' l4 d
" w7 Y1 S! k- {% F
SD卡按容量分类,可以分为3类:SD卡、SDHC卡、SDXC卡,如下表所示:
* ~6 g* n& A# d8 e' g
8 A& T+ x! S. P- C5 q! ~
1.2 SD卡的物理结构及内部框图 SD卡的物理结构一般包括以下5个部分:
7 F8 V: U4 Y" X* F V q存储单元:是存储数据部件; 存储单元接口:存储单元通过存储单元接口与卡控制单元进行数据传输; 电源检测单元:保证SD卡工作在合适的电压下,如出现掉电或上状态时,它会使控制单元和存储单元接口复位; 卡及接口控制单元:控制SD卡的运行状态,它包括有8个寄存器;
. K. @- b0 ^/ `3 O接口驱动器:控制SD卡引脚的输入输出。 0 r* x2 i& R' f8 c$ l/ ^' b
& D- w$ @- {. a3 w
% t0 M( Z( \4 y# V/ Y
, c* R5 D1 c& V
0 ]+ W9 g# {& v- A/ ZSDIO由SDIO适配器和APB2接口两部分组成:
& H! A# }+ ^2 ]0 d3 J- ~ 名称 | 功能 | SDIO适配器 | 提供特定于MMC/SD/SD I/O卡的所有功能,如时钟生成单元、命令和数据传输 | APB2接口 | 访问SDIO适配器寄存器,并且生成中断和DMA请求信号 |
0 D. W6 b3 Y& s; O6 [$ k
下图是SDIO功能框图及SDIO适配器框图:
1 k1 G6 ~& z$ r# X; n
S6 M( V+ \6 v1 i" [4 }* `
$ v$ M' x* j/ I1 Z
1.3 SD卡命令 SD卡命令由主机发出,命令格式固定为48位,通过CMD线连续传输,数据线不参与。SD命令结构如下图示:由6个字节组成,字节1的最高2位固定为01、低6位为命令号(比如CMD16);字节2 ~ 5为命令参数(有的命令没有参数);字节6的高7位为CRC、最低位恒定为1 SD命令组成的详细说明如下:
. x1 `4 ]/ z: X. u# K起始位和终止位:命令的主体包含在起始位与终止位之间,它们都只包含一个数据位,起始位为 0,终止位为 1。 传输标志:用于区分传输方向,该位为 1 时表示命令,方向为主机传输到 SD 卡,该位为 0时表示响应,方向为 SD卡传输到主机。 命令主体内容包括命令、地址信息/参数和 CRC 校验三个部分 命令号:它固定占用 6bit,所以总共有 64个命令(代号:CMD0~CMD63),每个命令都有特定的用途,部分命令不适用于 SD 卡操作,只是专门用于 MMC卡或者SD I/O卡。 地址/参数:每个命令有 32bit地址信息/参数用于命令附加内容,例如,广播命令没有地址信息,这 32bit用于指定参数,而寻址命令这 32bit用于指定目标 SD卡的地址。 0 G0 `8 A V! z. F3 ~3 h" r8 x( o
CRC7 校验:长度为 7bit的校验位用于验证命令传输内容正确性,如果发生外部干扰导致传输数据个别位状态改变将导致校准失败,也意味着命令传输失败,SD卡不执行命令。 7 x- |4 ?; q) f' o
/ ^2 E% f" {9 s; U. s Y# [
4 g2 p+ y! i4 Z0 M6 D. a" G( s$ V8 }
1.4 SD卡响应
/ s% N( y. z1 Q# G9 i- C$ N SD卡命令的响应由SD卡向主机发出,部分命令要求SD卡作出响应,这些响应多用于反馈SD卡的状态。基本特性如下: 3 ^6 j' S8 U6 y0 ~; G% U0 U! [8 k
SDIO总共有7个响应类型(代号:R1~R7),其中SD卡没有R4、R5类型响应。特定的命令对应有特定的响应类型,比如当主机发送CMD3命令时,可以得到响应R6。 与命令一样,SD卡的响应也是通过CMD线连续传输的。
: g/ S) q/ y3 r6 G! D. N5 J1 p根据响应内容大小可以分为短响应和长响应。短响应是48bit长度,只有R2类型是长响应,其长度为136bit。
% U* T* n) B. {! q5 U. j
7 C' n6 I7 `9 t9 N : W2 Z* |2 }( {9 j3 B9 s
+ [5 @9 E5 D, R* S3 {$ ^5 }
( q6 Q% a! [: }2 d1 i( JSD的读写操作是以块为操作对象。先发送命令开始传输,然后传输数据块,传输完数据块紧接着传输CRC检验值。最好发送停止命令停止数据传输
- z! N2 q- L8 r: l3 Z
8 g4 l2 k3 d/ G: K2 }2 @* l
1.5 SD卡的操作模式及切换 SD卡有多个版本,STM32控制器目前最高支持《Physical Layer Simplified Specification V2.0》定义的SD卡,STM32控制器对SD卡进行数据读写之前需要识别卡的种类:V1.0标准卡、V2.0标准卡、V2.0高容量卡或者不被识别卡。 # K4 j: j: j7 `6 @5 q
SD卡系统定义了两种操作模式:卡识别模式和数据传输模式 D$ B7 F' w$ {! X5 I
在系统复位后,主机处于卡识别模式,寻找总线上可用的SDIO设备;同时,SD卡也处于卡识别模式,直到被主机识别到,即当SD卡接收到SEND_RCA(CMD3)命令后,SD卡就会进入数据传输模式,而主机在总线上所有卡被识别后也进入数据传输模式。 + y% x; ] d7 i. ?
) K! \* `8 H1 E% z6 Z+ ^# P; G
6 s$ C! a) f/ C3 q7 V: ]
3 H7 ]: L; r3 g# e9 n" I
2.硬件设计  D1指示灯用来提示系统运行状态,K_UP写入数据,K_DOWN读取数据,TFTLCD用来显示SD卡的容量、类型等信息,串口1用来打印调试信息
2 M& T0 d. u0 l: qD1指示灯 K_UP/K_DOWN/K_LEFT/K_RIGHT按键, K* w; i9 R/ d* m2 W7 k5 R1 C
USART1串口 TFTLCD ! L6 }7 m, Q7 |# K; C: m
TF卡
9 E( u' u1 g( N8 n
* K0 y' `: G" A2 u( Q
# f0 o! A) l) U8 g" g3 `' D
从以上电路图可以看出,SD卡支持SPI和SDIO模式,两种模式可以通过端子进行切换,P4端子与SD卡连接,SD端子与STM32F1的SDIO接口连接,IO端子与STM32F1的SPI2接口连接,本例程使用SDIO模式(将P4端子与SD端子短接)
- U6 |0 \ ]. o! J4 b
3.软件设计 3.1 STM32CubeMX设置 ➡️ RCC设置外接HSE,时钟设置为72M ➡️ PC0设置为GPIO推挽输出模式、上拉、高速、默认输出电平为高电平 ➡️ PA0设置为GPIO输入模式、下拉模式;PE2/PE3/PE4设置为GPIO输入模式、上拉模式 ➡️ USART1选择为异步通讯方式,波特率设置为115200Bits/s,传输数据长度为8Bit,无奇偶校验,1位停止位 ➡️ 激活FSMC ➡️ 激活SDIO,选择4线SD模式,分频因子设为4,使能流控,其余默认设置 " k& ]# g# `& Q1 K; o6 G( B
7 K+ ?' z$ \) E+ F
➡️ 激活CRC功能,以避免后续读写SD卡报CRC校验错误 ( [. _3 D# w, H( v% a
" {" P) }2 b; P9 T$ l, z
➡️输入工程名,选择路径(不要有中文),选择MDK-ARM V5;勾选Generated periphera initialization as a pair of ‘.c/.h’ files per IP ;点击GENERATE CODE,生成工程代码
# C. d% R1 U2 c1 ~" L
3.2 MDK-ARM软件编程 ➡️ 创建按键驱动文件key.c和key.h,参考按键输入例程 ➡️ 创建LCD驱动文件tftlcd.c 和tftlcd.h,参考TFTLCD显示例程 ➡️ 在sdio.c文件下可以看到sdio初始化函数,同时在该文件下添加显示SD卡信息函数
9 b& l: x! s* h7 y! ^1 A( F8 U
- void MX_SDIO_SD_Init(void){, b# g- q% ~2 R' ]% }2 P/ N' X
- hsd.Instance = SDIO;
, W; ]6 H9 M q) k/ K- y - hsd.Init.ClockEdge = SDIO_CLOCK_EDGE_RISING;//上升沿; B& q, }0 ? `0 V2 k
- hsd.Init.ClockBypass = SDIO_CLOCK_BYPASS_DISABLE;//比使用bypass,直接用HCLK分配得到SDIO_CK8 m. X6 j' R' p- B
- hsd.Init.ClockPowerSave = SDIO_CLOCK_POWER_SAVE_DISABLE;//空闲时不关闭时钟电源3 L6 J' r r' J/ I+ Q. F% W
- hsd.Init.BusWide = SDIO_BUS_WIDE_1B;//1位数据线( E; t4 W- V4 A6 R; ~, {& ~* T$ s& @
- hsd.Init.HardwareFlowControl = SDIO_HARDWARE_FLOW_CONTROL_ENABLE;//开启硬件流控
3 K0 r Z6 m; v - hsd.Init.ClockDiv = 4;//4分频0 Z; ~! V" S+ {( o6 i' I% I1 O
- if (HAL_SD_Init(&hsd) != HAL_OK){
! ]& V4 x3 ` z8 C! D - Error_Handler();
' a5 A& w* t8 `$ V. l - }
8 Z0 l0 H T. G7 ^/ ~ - if (HAL_SD_ConfigWideBusOperation(&hsd, SDIO_BUS_WIDE_4B) != HAL_OK){/ k/ K1 M% R0 u
- Error_Handler();//初始化完成后使能宽总线(4位)模式
6 W2 g6 a3 @! D/ w - }
9 L. T) E/ f# }7 v, X - }2 y P0 z% B% o! y4 \
- //显示SD卡信息函数
& N9 `- n2 X% c1 v1 N* b - void Show_SDMMC_Info(void){
6 X1 d" Z5 c8 u$ Y" X' t - HAL_SD_CardCIDTypeDef SDCard_CID;$ ]5 e3 d! ]6 M. C! r
- HAL_SD_CardInfoTypeDef SDCard_INFO;
1 E6 I0 F; |' H) z8 ~* z6 W - uint64_t CardCap; //SD卡容量+ ?/ }1 k, p; S/ `
- HAL_SD_GetCardCID(&hsd,&SDCard_CID); //获取CID1 n9 l2 p8 _6 H6 u0 G6 {
- HAL_SD_GetCardInfo(&hsd,&SDCard_INFO);//获取SD卡信息+ ]: I" i8 e6 o% v3 D5 Q1 s
- FRONT_COLOR=BROWN;
) q& S1 {; W( m# W. u: c) a - switch(SDCard_INFO.CardType) //SD卡类型3 c6 x: P$ u) N/ w' V: T1 H. [
- {! \. e, O# L" N0 o) c/ Q: O
- case CARD_SDSC:
" P6 A+ |4 J5 }. {' C- b& h - if(SDCard_INFO.CardVersion == CARD_V1_X){
* R: ~+ L: o: c( K; C8 N |: y - LCD_ShowString(120,190,50,16,16,(uint8_t *)"SDSC V1");
% J6 m( v7 R2 f - printf("Card Type: SDSC V1\r\n");6 C6 M5 z! |5 D+ z
- }7 \/ L4 L. y; f- y7 R" L
- else if(SDCard_INFO.CardVersion == CARD_V2_X){+ g/ a/ H8 x% o. h! T _
- LCD_ShowString(120,190,50,16,16,(uint8_t *)"SDSC V2");% ]7 I# |7 f0 B- Y
- printf("Card Type: SDSC V2\r\n");" m$ t/ c9 ^- h- w7 c0 b, ?0 Z3 d
- }9 n e% \+ _1 A' v3 |: z
- break;/ s0 T- r7 {+ ?
- case CARD_SDHC_SDXC:, v. B7 I- G8 n& ~
- LCD_ShowString(120,190,50,16,16,(uint8_t *)"SDHC");
: A% }; a, K1 @: A/ x - printf("Card Type: SDHC\r\n");
/ U+ L/ d8 |1 H* l% W$ v- Q - break;# [6 z3 f! C: C. Z. o o
- }* |( L+ Q2 f2 f3 _' ^2 I: c
- 8 x ^6 N& N( k" y7 u
- CardCap = (uint64_t)(SDCard_INFO.LogBlockNbr)*(uint64_t)(SDCard_INFO.LogBlockSize)/1024/1024; //计算SD卡容量
0 @+ H. K+ C; v# z) S2 O. C, O) L+ x - printf("Card ManufacturerID:%d\r\n",SDCard_CID.ManufacturerID);//制造商ID v" Y5 @/ X! o, s1 y( U
- printf("Card RCA:%d\r\n",SDCard_INFO.RelCardAdd); //卡相对地址 4 Y5 @3 I: O6 k
- printf("LogBlockNbr:%d\r\n",SDCard_INFO.LogBlockNbr); //逻辑块数量5 W4 p/ w* B5 @% w( L% f- M3 V" Q* j% n
- printf("LogBlockSize:%d\r\n",SDCard_INFO.LogBlockSize);//逻辑块大小
# `% e: n& p& U/ u - printf("Card Capacity:%d MB\r\n",(uint32_t)CardCap); //显示容量, g) m8 I- t+ I; ^0 z, e
- printf("Card BlockSize:%d\r\n",SDCard_INFO.BlockSize);//块大小
# O8 b3 u8 M1 X- E1 \1 n - LCD_ShowNum(120,210,CardCap,4,16);
" Q2 p( K) l+ \6 U - LCD_ShowString(160,210,50,16,16,(uint8_t *)"MB");
6 p# [1 x) y. e. I- p. B - }
复制代码 % q1 T9 f7 j: H9 Y* L5 s( m8 e' T4 x4 G
➡️ 在main.c文件下编写SD卡读写测试代码 0 u; f! C) P4 C: q
- uint8_t Buffer_Tx[512],Buffer_Rx[512] = {0};
^) S/ D2 k2 B! Y5 r - uint32_t i;
) x- q) b8 M p2 p4 `! C - int main(void){
( u# a1 v8 F; q+ E - uint8_t key;0 u5 O0 s; o3 a6 n/ @: h9 _) ?
- HAL_Init();8 ~/ \" x6 v( ` @
- SystemClock_Config();
- x, d- A# z7 u1 P: g/ C0 v - MX_GPIO_Init();
+ d2 l% T& O/ c4 t - MX_FSMC_Init();
% ^( S- k3 g3 D+ b( b. C! ] - MX_SDIO_SD_Init();
, K) L! Z3 m' n - MX_USART1_UART_Init(); t5 E/ A2 L- d) C- g; M. k! l* z
- MX_CRC_Init();# }- E5 p8 x8 |+ I0 j) x! g8 ]
- /* USER CODE BEGIN 2 */
/ K7 h, Q8 V$ H; M G. w/ Q - TFTLCD_Init(); + v% L4 P7 G. g
- FRONT_COLOR=BROWN;
! h- F( y9 U3 C& G - LCD_DrawRectangle(25,25,215,135);
9 x6 Z6 P- I: h - FRONT_COLOR=RED; ! Q) A% K9 f+ z0 R F, S) W5 K
- LCD_ShowString(30,30,200,16,16,(uint8_t *)"ANDYXI STM32"); - z" @! F6 d3 J
- LCD_ShowString(30,50,200,16,16,(uint8_t *)"STM32CubeMX");
+ ^5 o: D2 s' u) o5 D/ c" \) Q - LCD_ShowString(30,70,200,16,16,(uint8_t *)"SDIO TEST");" w" Y( C! n# H9 {2 d& A% G& Q v. V
- FRONT_COLOR=BLACK;, Z+ G- w1 p" q0 S; ?5 J; ^ y& t! A
- LCD_ShowString(30,90,200,16,16, (uint8_t *)"K_U:ReadSD K_D:WriteSD");; R3 Z6 {7 [* g) o9 l9 m
- LCD_ShowString(30,110,200,16,16,(uint8_t *)"K_R:EaseSD K_L:None");, f6 u8 {, s, ]# Q5 G9 i3 Q
- FRONT_COLOR=BLUE;- Q# b8 j6 K! S! M! Y7 Y3 Z4 j& ?8 J. t
- LCD_ShowString(30,170,200,16,16,(uint8_t *)"SD Card Information");
{7 S2 r& F! S - LCD_ShowString(30,190,80,16,16,(uint8_t *)"Card Type: ");
" s) u, c$ ?5 j! J9 s - LCD_ShowString(30,210,80,16,16,(uint8_t *)"Card Capa:");
1 H1 S' ]8 o( _+ D3 X( h! n - Show_SDMMC_Info();
/ ?% D0 X, e! N$ J8 {% |2 D - memset(Buffer_Tx,0x15,sizeof(Buffer_Tx));
* w& o m j, m5 \6 I N. f - /* USER CODE END 2 */
+ g4 K# Y4 c O0 Y - while (1){
8 g. x, H- ^, ]0 L I+ v5 k - key = KEY_Scan(0);" w" G* L9 E* ?6 @. G, ^
- switch(key)! h$ G0 Z, y$ H" e8 C0 }# f6 Q
- {" l; b% V6 S7 ?! v
- case KEY_RIGHT_PRES:
' B% e) Y6 b$ @ - if(HAL_SD_Erase(&hsd,0,1) == HAL_OK){
; p% U# O+ t. u* K h% Z" }% w - while(HAL_SD_GetCardState(&hsd) != HAL_SD_CARD_TRANSFER);9 c. A3 n3 }- H# w1 m2 I. Z% Y
- printf("\r\nErase Block Success!\r\n");
9 `( x( i3 A9 p& r3 K( F5 b - LCD_ShowString(30,250,200,16,16,(uint8_t *)"Erase Block Success!");
% J( Y x3 i3 f) s4 ~) E* z - }" i8 p3 p( }, L7 F& _$ \" d% K& D
- else{
* K4 a) T5 F2 S h1 v5 F, Y3 _ - printf("\r\nErase Block Failed!\r\n"); + O! y9 G2 r$ f, l: S0 u c
- LCD_ShowString(30,250,200,16,16,(uint8_t *)"Erase Block Failed!"); : K/ e, j6 d+ y
- }* x- C1 ^ R! U8 a+ f. X% @0 y
- break;
. L. j! O2 A; F5 M: C" B - case KEY_UP_PRES:
: R2 }! _* L" ]+ y, r. f- ` - if(HAL_SD_ReadBlocks(&hsd,Buffer_Rx,0,1,0xffffffff) == HAL_OK){0 ?( {; I% m2 n& [- |
- while(HAL_SD_GetCardState(&hsd) != HAL_SD_CARD_TRANSFER);* }) |: {2 G& o5 Q2 v
- printf("\r\nRead Block Success!\r\n");. ?1 F" V$ U$ ?" X* ?1 e, A# y0 f% ?
- for(i=0;i<sizeof(Buffer_Rx);i++)4 D! \: K9 e2 }- |8 G6 Z3 j- z
- printf("%02x ",Buffer_Rx[i]);( {$ _% Q3 L# e; ]
- printf("\r\n");
, @4 |- ?1 w. q( e3 ]8 _ - LCD_ShowString(30,250,200,16,16,(uint8_t *)"Read Block Success!");) J3 [) K6 O8 W+ B' r
- }else{3 G+ c( p- _) A' T: n
- printf("\r\nRead Block Failed!\r\n");
" I& V$ k$ O; y4 N0 j3 f5 r# W - LCD_ShowString(30,250,200,16,16,(uint8_t *)"Read Block Success!");
* E* n3 Q4 k7 H/ F+ \6 d2 U - }: Z1 C; M8 O j/ S- p6 v: c! |# k
- break; 9 @. _6 z, g, r5 q- F
- case KEY_DOWN_PRES:
! H& ~( ?2 R |+ W, @ - if(HAL_SD_WriteBlocks(&hsd,Buffer_Tx,0,1,0xffffffff) == HAL_OK){8 x' C3 \$ g7 D c; r4 w
- while(HAL_SD_GetCardState(&hsd) != HAL_SD_CARD_TRANSFER);
, G- N( T; ~) [: U5 l0 n - printf("\r\nWrite Block Success!\r\n");
; o+ H( C3 `1 o- y6 ] - for(i=0;i<sizeof(Buffer_Tx);i++)
% K0 _, @+ a. A! I - printf("0x%02x:%02x ",i,Buffer_Tx[i]);
5 S7 p' O& W/ M+ z' Z8 ]( I - printf("\r\n");
9 m/ Z; `) Y1 J9 M9 U - LCD_ShowString(30,250,200,16,16,(uint8_t *)"Write Block Success!");$ P0 t* s! z( H9 a; F
- }else{3 x* M \0 B5 A' T6 T: ?7 D3 S
- printf("\r\nWrite Block Failed!\r\n");7 c2 a2 y4 {5 Y4 x a% m3 X1 d4 K
- LCD_ShowString(30,250,200,16,16,(uint8_t *)"Write Block Failed!");" t7 E2 ]: [& ?" Y' h* T5 @
- }6 |$ S8 c4 U0 K' ^$ k
- break;- K, u% t% b2 ^) I8 i& o3 t
- }- Y# U( \# e4 ?; m: o, ]* `
- HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_0);
* |/ ]6 V/ Z; O& d - HAL_Delay(200);
+ d2 z ] `- Q/ ^+ G - }: D M( L! s; Q; c
- }
复制代码
. I6 B4 B6 h" N4.下载验证
, X; W) S' z' d0 r9 Y% z编译无误下载到开发板后,可以看到D1指示灯不断闪烁,按下KEY_UP键读取SD卡数据,按下KEY_DOWN键写入数据,按下KEY_RIGHT键擦除数据,SD卡信息以及更新的信息都会显示在LCD屏上 : _5 l" G( Z8 z N" Q
+ F' I' N5 \1 z+ V
+ i( g4 t/ K, T- h& o1 }. F) p: @! Y0 e, ~
|