|
1.SD卡介绍 [: \& }4 J& S; \
1.1 SD卡简介 很多单片机系统都需要大容量存储设备,以存储数据(常用的有U盘、FLASH芯片、SD卡等),比较而言SD卡是单片机大容量外部存储的首选,只需要少数几个IO口即可外扩一个容量从几十M到几十G的,且有多种体积尺寸可选(标准SD卡、TF卡等)的外部存储器。 4 H: n$ k1 Y. g5 E, d: _
SD卡(Secure Digital Memory Card)即:安全数码卡,它是在MMC的基础上发展而来,是一种基于半导体快闪记忆器的新一代记忆设备,它被广泛地于便携式装置上使用,例如数码相机、个人数码助理(PDA)和多媒体播放器等。SD卡由日本松下、东芝及美国SanDisk公司于1999年8月共同开发研制。! w3 U0 K/ G/ {. e$ Z' H
, b( j9 h; l) Q 5 P( W2 J* \# V4 R5 h- Q7 a
SD卡按容量分类,可以分为3类:SD卡、SDHC卡、SDXC卡,如下表所示: + j$ w* t# n4 h/ I
9 t) I6 d/ z# l' m% {8 V4 `. S
1.2 SD卡的物理结构及内部框图 SD卡的物理结构一般包括以下5个部分:
/ Z6 @5 W. y" F6 }6 f6 G存储单元:是存储数据部件; 存储单元接口:存储单元通过存储单元接口与卡控制单元进行数据传输; 电源检测单元:保证SD卡工作在合适的电压下,如出现掉电或上状态时,它会使控制单元和存储单元接口复位; 卡及接口控制单元:控制SD卡的运行状态,它包括有8个寄存器;
: r% E; V% @; i8 t接口驱动器:控制SD卡引脚的输入输出。
& [* T& P' O$ ~
0 Z! C' o- i# C2 `" t0 B8 K- L8 P * K' S1 ?$ H$ j
: o6 j1 E8 w3 T9 v5 r0 q1 C) h3 s9 A( T( j$ k1 L
SDIO由SDIO适配器和APB2接口两部分组成:7 b9 p7 O& m. F j. n: ?) _
名称 | 功能 | SDIO适配器 | 提供特定于MMC/SD/SD I/O卡的所有功能,如时钟生成单元、命令和数据传输 | APB2接口 | 访问SDIO适配器寄存器,并且生成中断和DMA请求信号 |
4 N0 Q$ B& H/ _$ \) U
下图是SDIO功能框图及SDIO适配器框图:
& r- O9 v3 K# j5 l- f( v% b
3 D1 U2 H6 u; H7 u' G& f
! @8 V+ Y; B! _8 }, ^# c5 |, P
1.3 SD卡命令 SD卡命令由主机发出,命令格式固定为48位,通过CMD线连续传输,数据线不参与。SD命令结构如下图示:由6个字节组成,字节1的最高2位固定为01、低6位为命令号(比如CMD16);字节2 ~ 5为命令参数(有的命令没有参数);字节6的高7位为CRC、最低位恒定为1 SD命令组成的详细说明如下: 9 s% J- C' }4 j0 J- Y) w
起始位和终止位:命令的主体包含在起始位与终止位之间,它们都只包含一个数据位,起始位为 0,终止位为 1。 传输标志:用于区分传输方向,该位为 1 时表示命令,方向为主机传输到 SD 卡,该位为 0时表示响应,方向为 SD卡传输到主机。 命令主体内容包括命令、地址信息/参数和 CRC 校验三个部分 命令号:它固定占用 6bit,所以总共有 64个命令(代号:CMD0~CMD63),每个命令都有特定的用途,部分命令不适用于 SD 卡操作,只是专门用于 MMC卡或者SD I/O卡。 地址/参数:每个命令有 32bit地址信息/参数用于命令附加内容,例如,广播命令没有地址信息,这 32bit用于指定参数,而寻址命令这 32bit用于指定目标 SD卡的地址。 ) g$ s8 u( B3 F7 G
CRC7 校验:长度为 7bit的校验位用于验证命令传输内容正确性,如果发生外部干扰导致传输数据个别位状态改变将导致校准失败,也意味着命令传输失败,SD卡不执行命令。
1 i M" A' h: l' |
0 q: V8 i) m$ \3 j5 \1 z 1.4 SD卡响应
; I D& @, w. g- F* L! f SD卡命令的响应由SD卡向主机发出,部分命令要求SD卡作出响应,这些响应多用于反馈SD卡的状态。基本特性如下:
% B* T" v7 \6 K8 f2 M& aSDIO总共有7个响应类型(代号:R1~R7),其中SD卡没有R4、R5类型响应。特定的命令对应有特定的响应类型,比如当主机发送CMD3命令时,可以得到响应R6。 与命令一样,SD卡的响应也是通过CMD线连续传输的。 9 d% I/ X# _0 y- y9 j2 f
根据响应内容大小可以分为短响应和长响应。短响应是48bit长度,只有R2类型是长响应,其长度为136bit。 + |4 D+ e/ d, t* t" ^/ @# j$ Y
- T- W6 J5 B0 O( X& d " `( c! T/ J, u7 `$ R
1 z; I7 m4 ?, |9 q4 c; G2 M
0 O! P6 U8 c8 A; zSD的读写操作是以块为操作对象。先发送命令开始传输,然后传输数据块,传输完数据块紧接着传输CRC检验值。最好发送停止命令停止数据传输 & ^/ ?5 S: X; B4 Q! ~
% }* M0 t8 p$ W' a! d# D
1.5 SD卡的操作模式及切换 SD卡有多个版本,STM32控制器目前最高支持《Physical Layer Simplified Specification V2.0》定义的SD卡,STM32控制器对SD卡进行数据读写之前需要识别卡的种类:V1.0标准卡、V2.0标准卡、V2.0高容量卡或者不被识别卡。 0 o* U3 ~( E8 V: d" `
SD卡系统定义了两种操作模式:卡识别模式和数据传输模式 " p/ `5 E/ i( T/ k9 q& I/ {4 w
在系统复位后,主机处于卡识别模式,寻找总线上可用的SDIO设备;同时,SD卡也处于卡识别模式,直到被主机识别到,即当SD卡接收到SEND_RCA(CMD3)命令后,SD卡就会进入数据传输模式,而主机在总线上所有卡被识别后也进入数据传输模式。
! E/ ~' y& q2 Q. {/ r) l4 @
5 u& G8 e$ [3 C, u
" ^6 t& H+ ~ z
3 M4 s, f) b+ x' @+ {
2.硬件设计  D1指示灯用来提示系统运行状态,K_UP写入数据,K_DOWN读取数据,TFTLCD用来显示SD卡的容量、类型等信息,串口1用来打印调试信息
: z* L' m- t( W6 Y4 m8 VD1指示灯 K_UP/K_DOWN/K_LEFT/K_RIGHT按键$ ^' H+ V1 v* b4 _
USART1串口 TFTLCD * l( T: w; l8 m6 u% @$ d& I' g% z/ A
TF卡 1 r$ R U6 r$ k2 _* a9 p
# R$ H8 ?& b9 A( i4 a
; W% k: e8 r! T/ _
从以上电路图可以看出,SD卡支持SPI和SDIO模式,两种模式可以通过端子进行切换,P4端子与SD卡连接,SD端子与STM32F1的SDIO接口连接,IO端子与STM32F1的SPI2接口连接,本例程使用SDIO模式(将P4端子与SD端子短接) " t1 V, r" ~9 C# w
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,使能流控,其余默认设置 : O* I& t- J/ d, s
7 A! n! z b7 d' q, L
➡️ 激活CRC功能,以避免后续读写SD卡报CRC校验错误 # g1 p8 L# Z3 [4 W2 c: |
. E7 M- Z% T6 D. u# I* T
➡️输入工程名,选择路径(不要有中文),选择MDK-ARM V5;勾选Generated periphera initialization as a pair of ‘.c/.h’ files per IP ;点击GENERATE CODE,生成工程代码 ! A& @) R3 i* J" d/ u" w
3.2 MDK-ARM软件编程 ➡️ 创建按键驱动文件key.c和key.h,参考按键输入例程 ➡️ 创建LCD驱动文件tftlcd.c 和tftlcd.h,参考TFTLCD显示例程 ➡️ 在sdio.c文件下可以看到sdio初始化函数,同时在该文件下添加显示SD卡信息函数 E2 _* r" j( J
- void MX_SDIO_SD_Init(void){
' P+ v" b3 P- I* G* P - hsd.Instance = SDIO;
4 L- L9 H& s; @3 [" i3 X$ t - hsd.Init.ClockEdge = SDIO_CLOCK_EDGE_RISING;//上升沿
$ k3 j9 b& c) D5 y% d- U - hsd.Init.ClockBypass = SDIO_CLOCK_BYPASS_DISABLE;//比使用bypass,直接用HCLK分配得到SDIO_CK
% U; J9 k- R9 o. n7 P - hsd.Init.ClockPowerSave = SDIO_CLOCK_POWER_SAVE_DISABLE;//空闲时不关闭时钟电源6 m1 Y3 ^, }& U+ j9 p- M/ E
- hsd.Init.BusWide = SDIO_BUS_WIDE_1B;//1位数据线; ~4 s) q& f" H+ y
- hsd.Init.HardwareFlowControl = SDIO_HARDWARE_FLOW_CONTROL_ENABLE;//开启硬件流控
6 z9 Z+ v: u' X" G2 h# e+ h - hsd.Init.ClockDiv = 4;//4分频
- X2 }1 r- L* A, z5 _9 j0 H - if (HAL_SD_Init(&hsd) != HAL_OK){/ g) A+ z2 q: i8 n8 V/ C: ]( K: t
- Error_Handler();8 a9 C7 N# }" q' Z
- }1 C) l. M+ G" `( u( G
- if (HAL_SD_ConfigWideBusOperation(&hsd, SDIO_BUS_WIDE_4B) != HAL_OK){4 n" x5 g) d: [2 r! b
- Error_Handler();//初始化完成后使能宽总线(4位)模式
" R/ M( ~3 b7 U0 D$ d - }: r" k$ A6 ]# v/ A0 h: g8 O+ W
- }
0 f0 _4 `% u9 f5 {) T - //显示SD卡信息函数
5 n8 G1 D" C! }! m* D/ d3 Y- ~3 _ - void Show_SDMMC_Info(void){( W! u5 g: D6 I+ ]. Z/ D
- HAL_SD_CardCIDTypeDef SDCard_CID;
6 C( _ W6 |! N, E: c6 B: g1 F( | - HAL_SD_CardInfoTypeDef SDCard_INFO;
# _. K5 Y; W" R - uint64_t CardCap; //SD卡容量* Z3 `5 L) T3 l$ @, V' y- _
- HAL_SD_GetCardCID(&hsd,&SDCard_CID); //获取CID
4 z* J" Q, U, M/ X, c - HAL_SD_GetCardInfo(&hsd,&SDCard_INFO);//获取SD卡信息+ @8 x7 L) S, f; h- W& k8 I- a, p
- FRONT_COLOR=BROWN;
4 W, \" L7 e& |0 }) o3 k: D' q - switch(SDCard_INFO.CardType) //SD卡类型
A( j v0 H- ~: R% v" w& m& k& g7 X5 U - {
7 V. ]* i! Y& W2 j - case CARD_SDSC:; B1 S$ O/ D8 W/ ]( m6 T
- if(SDCard_INFO.CardVersion == CARD_V1_X){+ \" }/ l# C/ C9 m. M% N5 s% Q! ]
- LCD_ShowString(120,190,50,16,16,(uint8_t *)"SDSC V1");" b$ L* m+ h: i% G' T* W
- printf("Card Type: SDSC V1\r\n");: [/ v5 i, _) y
- }
$ f1 x) u( Z2 [2 C - else if(SDCard_INFO.CardVersion == CARD_V2_X){" @( `1 S7 g8 V& P( |$ x1 [# r2 Y
- LCD_ShowString(120,190,50,16,16,(uint8_t *)"SDSC V2");& F: e8 x, s i' ?( @
- printf("Card Type: SDSC V2\r\n");
! k; a! r' v, P, n) B3 c - }
" A) t# W E2 c; _ - break;
3 c2 \$ u9 D& U5 n7 J6 }! \ - case CARD_SDHC_SDXC:* M2 m. b) I& N9 w. l
- LCD_ShowString(120,190,50,16,16,(uint8_t *)"SDHC");! V' x; K1 {8 q0 P0 R: B; X9 u
- printf("Card Type: SDHC\r\n");! @) B, V( M& i; `' c2 n/ s
- break;
/ P! w; h+ c3 O ~. _& T! a5 ~% a - }
( r2 h6 j2 A1 c: c4 z; m -
2 J6 B0 J4 s! p+ u( ` - CardCap = (uint64_t)(SDCard_INFO.LogBlockNbr)*(uint64_t)(SDCard_INFO.LogBlockSize)/1024/1024; //计算SD卡容量7 i# X* R" N$ I' w3 V
- printf("Card ManufacturerID:%d\r\n",SDCard_CID.ManufacturerID);//制造商ID. ~6 \. y& m( s3 ^( P& S# T
- printf("Card RCA:%d\r\n",SDCard_INFO.RelCardAdd); //卡相对地址 8 `$ l9 j- }( t$ M; w
- printf("LogBlockNbr:%d\r\n",SDCard_INFO.LogBlockNbr); //逻辑块数量
( j( E z$ S' d5 l6 F4 j" w - printf("LogBlockSize:%d\r\n",SDCard_INFO.LogBlockSize);//逻辑块大小
' h, n' I! {+ e2 m7 n/ w - printf("Card Capacity:%d MB\r\n",(uint32_t)CardCap); //显示容量( T5 C& I! p7 w; Q. o7 b; c- `
- printf("Card BlockSize:%d\r\n",SDCard_INFO.BlockSize);//块大小
# i: y9 @) R0 a4 o# t - LCD_ShowNum(120,210,CardCap,4,16);
: _; Z( n6 d& g( E8 {/ i - LCD_ShowString(160,210,50,16,16,(uint8_t *)"MB"); I! U2 ?5 W5 q# {- ]. ]
- }
复制代码 / F# B! D4 W9 R: a: w
➡️ 在main.c文件下编写SD卡读写测试代码 # `5 C# q( d o1 w
- uint8_t Buffer_Tx[512],Buffer_Rx[512] = {0};
$ r& O( T) |) S - uint32_t i;( F( s9 Z9 N, B
- int main(void){* a, C& w) ]' c( n: ?$ c! F
- uint8_t key;
7 q6 z) ^3 d, j; t& ^& f - HAL_Init();
, l4 b- F# M m& n1 S& e - SystemClock_Config();9 B7 n2 q: i5 k
- MX_GPIO_Init();
- z3 `' ?6 `% F3 X$ Y# Q! s4 E, u9 R - MX_FSMC_Init();
# p0 q6 `6 h. m% y5 o2 G - MX_SDIO_SD_Init();
7 S, a0 C8 M" a - MX_USART1_UART_Init();
+ g6 ~4 D1 J* Z - MX_CRC_Init();
7 k. S( E( p! J9 ^* A1 U* g - /* USER CODE BEGIN 2 */4 p8 ^3 k2 k: i, W: Q
- TFTLCD_Init();
6 w+ }' l3 U {4 }9 j - FRONT_COLOR=BROWN;
4 Y- d' Z# u: o: z2 s - LCD_DrawRectangle(25,25,215,135);
- g/ ^8 j! ]: N - FRONT_COLOR=RED; % [8 k7 F" d/ V; f6 j
- LCD_ShowString(30,30,200,16,16,(uint8_t *)"ANDYXI STM32"); + h& k% @) H! n) w- T, X9 R
- LCD_ShowString(30,50,200,16,16,(uint8_t *)"STM32CubeMX"); . u6 a) x3 y4 y" ]% K
- LCD_ShowString(30,70,200,16,16,(uint8_t *)"SDIO TEST");, Z- n$ e. i m5 C, g. S
- FRONT_COLOR=BLACK;
v' E; u; s' U3 _ - LCD_ShowString(30,90,200,16,16, (uint8_t *)"K_U:ReadSD K_D:WriteSD");3 S( H3 C i9 H
- LCD_ShowString(30,110,200,16,16,(uint8_t *)"K_R:EaseSD K_L:None");
: h- ]0 ~/ k; }5 S - FRONT_COLOR=BLUE;
2 O o# \! z# z2 P* A$ l8 N - LCD_ShowString(30,170,200,16,16,(uint8_t *)"SD Card Information");
- J# L$ `! n) T" ]+ O X - LCD_ShowString(30,190,80,16,16,(uint8_t *)"Card Type: ");. x; V4 _3 E' L/ T$ Y
- LCD_ShowString(30,210,80,16,16,(uint8_t *)"Card Capa:"); % w. O" y7 T- q- ?! Y$ q" @
- Show_SDMMC_Info();( r( [1 H/ d0 k, f9 N$ v8 y4 z
- memset(Buffer_Tx,0x15,sizeof(Buffer_Tx)); : g" J& r5 j2 _0 r% E u% s a( c. G( b
- /* USER CODE END 2 */
* |. `! J1 H5 z! \1 ], e - while (1){
( ~2 z5 G: s G" O - key = KEY_Scan(0);
1 [4 z4 L" K1 K; t2 N$ y - switch(key)
; P3 C; `' _- Q3 c+ E3 Y6 o - {
2 k1 C3 @ d2 t( O4 d - case KEY_RIGHT_PRES:
" j) c2 o1 D9 {) K, b( K1 z - if(HAL_SD_Erase(&hsd,0,1) == HAL_OK){
A2 o& Y& x6 |$ d1 Z( R. [ - while(HAL_SD_GetCardState(&hsd) != HAL_SD_CARD_TRANSFER);
, H& ]2 d4 z' m$ W. Q2 m, X9 | - printf("\r\nErase Block Success!\r\n");
! o1 x5 X- a: V# C - LCD_ShowString(30,250,200,16,16,(uint8_t *)"Erase Block Success!");
+ {) C) S; W: C) M9 K: O) B/ a& y - }
* h( U% w9 S' ^* D( ]% J - else{0 _" p' l* V0 K8 e5 v0 _/ m1 p$ a0 A
- printf("\r\nErase Block Failed!\r\n"); $ r: V8 l' d6 e, M6 {
- LCD_ShowString(30,250,200,16,16,(uint8_t *)"Erase Block Failed!"); / K; X! d+ a8 u4 V( u
- }
* P" t& T/ t- ]4 N* j! y( l - break;; Z: n6 f, T% w5 `
- case KEY_UP_PRES:# S. S! Q5 o/ U" m+ K
- if(HAL_SD_ReadBlocks(&hsd,Buffer_Rx,0,1,0xffffffff) == HAL_OK){; Q% D$ M% e( j4 r! n5 E+ g& C# a
- while(HAL_SD_GetCardState(&hsd) != HAL_SD_CARD_TRANSFER);& [: U1 h( _* Y( ^; a! G5 |
- printf("\r\nRead Block Success!\r\n"); n7 L; l( {2 L1 I
- for(i=0;i<sizeof(Buffer_Rx);i++)- e' g2 S2 \: F: `( ?
- printf("%02x ",Buffer_Rx[i]);
3 `. n5 S+ m# Y } - printf("\r\n");: h* m8 u4 e6 g' b4 A
- LCD_ShowString(30,250,200,16,16,(uint8_t *)"Read Block Success!");
) @0 M! m' X# t9 b0 z - }else{$ ]0 C1 R" L$ n) s0 U: f" x w
- printf("\r\nRead Block Failed!\r\n");
2 o* A& M' A: P; H# V; u' o/ F j - LCD_ShowString(30,250,200,16,16,(uint8_t *)"Read Block Success!");
: c5 [& }; w- ~. f' |* K" i. o - }
5 U1 p* p4 ?/ }" u7 ]: n( _# A) G, z - break; , f7 D) a' G0 |) r5 u ^
- case KEY_DOWN_PRES:) E% j, l4 B0 H% [& K# e) J
- if(HAL_SD_WriteBlocks(&hsd,Buffer_Tx,0,1,0xffffffff) == HAL_OK){
: ~% ?$ A6 h7 s/ I* k# h - while(HAL_SD_GetCardState(&hsd) != HAL_SD_CARD_TRANSFER);
0 A) e- R6 i/ a - printf("\r\nWrite Block Success!\r\n");
' N6 H& S# y$ p/ w2 N - for(i=0;i<sizeof(Buffer_Tx);i++)+ Q9 D9 n1 U# F1 V
- printf("0x%02x:%02x ",i,Buffer_Tx[i]);
2 X; R2 q; x" }4 K - printf("\r\n");
- @6 B7 \- U* l4 S2 ^2 E - LCD_ShowString(30,250,200,16,16,(uint8_t *)"Write Block Success!");8 Q1 z6 W# D" R# M3 ]- a
- }else{7 @+ [4 \& l# t" s- ^6 V
- printf("\r\nWrite Block Failed!\r\n");# P0 l8 N' F1 c
- LCD_ShowString(30,250,200,16,16,(uint8_t *)"Write Block Failed!");* y# c0 x/ u% Q& N6 Q+ y1 J
- }* |' F5 @% u) a3 j1 o; h( j3 \7 b( X
- break; l, K5 c0 V8 d- Q$ r# I
- }
+ S4 w! `2 j3 x0 O& O' w - HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_0);. f- k+ k% d3 n8 ~2 b
- HAL_Delay(200);# c' O' n, }0 a) D
- }
* \, R. q) W% V1 G) p - }
复制代码
0 a! C G# M4 x' s- n8 y) R4.下载验证
/ j0 a7 @7 ^ ?: ~& q: _编译无误下载到开发板后,可以看到D1指示灯不断闪烁,按下KEY_UP键读取SD卡数据,按下KEY_DOWN键写入数据,按下KEY_RIGHT键擦除数据,SD卡信息以及更新的信息都会显示在LCD屏上 5 ~" D$ Y1 J# `: d1 ?& K( I
! ?7 d6 @5 Z' A5 ^
: t6 D9 F g; @/ P \) C; Z
* k8 O! U' r( S7 l2 |6 ]* N3 K A
4 o: M" c7 Z- N; d. S& g+ r |