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

单片机存储器的那些事!

[复制链接]
gaosmile 发布时间:2021-1-12 14:03
摘要:在单片机开发板上或者是核心板上通常会看到除了MCU之外的的芯片—EEPROM和FLASH,一般是AT24CXX、W25QXX这两颗芯片。但在利用单片机做一些项目的时候,比如做一个小车,驱动一些外设、显示一些温湿度信息等,却发现一般没有用到这些芯片。在做一些显示的时候却会用到。他们与单片机之间的通信方式就是IIC和SPI通信,在单片机的开发中用到的非常多。很多小伙伴就会说了,用OLED来显示一些数据,IIC通信直接用别人的代码,驱动SD卡或者NRF24L01直接拿别人的SPI代码就可以啊,难道我还自己去写驱动吗?* M; k8 l" S0 y7 l
当然需要,学会了这些操作,层次就会提高很多,不信那就接着往下看!EEPROM AT24C02存储器学单片机的时候大家可能有一个问题,为啥是IIC读写EEPROM,而不是读写其他的东西。为什么大部分的单片机开发教程都教我利用IIC通信来读写EEPROM这颗AT24C02芯片?4针0.96寸OLED也是IIC操作的,为啥他们不叫我如何利用IIC通信来操作OLED?原因很简单,主要是读写EEPROM你学完了没有成就感,会读写EEPROM又怎么样?归根到底是没有掌握IIC体会到IIC通信的重要性。今年疫情很严重,有一款红外测温芯片mlx90641就是通过IIC来读取温度的。我想如果教程是IIC读写红外测温芯片,大家可能会比较感兴趣。言归正传,来说一说EEPROM。ROM是“Read Only Memory”的缩写,意为只能读的存储器。由于技术的发展,后来设计出了可以方便写入数据的 ROM,而这个“Read Only Memory”的名称被沿用下来了。EEPROM(Electrically Erasable Programmable ROM)是电可擦除存储器。EEPROM 可以重复擦写,EEPROM 是一种掉电后数据不丢失的存储器,常用来存储一些配置信息,以便系统重新上电的时候加载之。它的擦除和写入都是直接使用电路控制,不需要再使用外部设备来擦写。而且可以按字节为单位修改数据,无需整个芯片擦除。现在主要使用的ROM芯片都是EEPROM。24C02是一个2K Bit的串行EEPROM存储器(掉电不丢失),内部含有256个字节,在24C02里面有一个8字节的页写缓冲器。

操作任何的IIC设备一般都要知道从机地址,也就是利用单片机操作读写的那个设备的地址。一般来说对于IIC设备地址是7位,其中高 4 位固定为:1010 b,低 3 位则由 A0/A1/A2信号线的电平决定。所以一个IIC总线上可以挂载2^3=8个EEPROM芯片,当然一般一个单片机只有一块EEPROM芯片,所以我们直接把这个A2A1A0接地即可,当然接VCC也没有问题,如果接GND那么地址就是1010000(0X50),如果接VCC那么地址就是1010111(0X57)。

微信图片_20210112140121.png
因为24C02是一个2K Bit的串行EEPROM存储器(掉电不丢失),内部含有256个字节。也就是说有256个存储单元,一个字节就是一个存储单元,因为每个字节可以出存256个数,也就是说每个存储单元可以存0~255个数。我们可以这样理解,AT24C02是一栋教学楼,这个教学楼有256个房间(存储单元),没每个房间可以容纳256个学生(每个存储单元可以存储0 ~ 255个数)。而且这个芯片在断电的时候数据不会丢失,利用掉电不会丢失以及这款芯片容量不大的特性,可以大致判断它会在哪些地方可以用到。比如我们看电视得时候,正在看CCTV6电影频道,播放的声音比较大,那么这时候正好停电了。那么你下次来电时你打开电视机,电视机默认肯定是CCTV6电影频道,播放的声音也是很大。那么这些“频道”、“音量”这些数据就存在EEPROM里面,至于是不是ATC02就不一定了。3 B5 @/ V6 O- ~# [
总结:
  • 存储量少,用起来方便
  • 可以任意访问地址数据,每一个存储单片可以独立访问,
  • 写入前是不需要对写入的单片做独立的擦除
    $ O) }3 Z5 J1 Y  l
这三个特点对我们理解存储器的特性非常重要,因为接下来要说的FLASH芯片的特性就与它完全相反。FLASH  W25Q128存储器
+ v7 X7 z0 c, m* O2 F$ ^
微信图片_20210112140125.png

FLSAH字面意思就是闪现、一瞬间的意思,所以FLSAH存储器又称闪存,与 EEPROM都是掉电后数据不丢失的存储器,但FLASH存储器容量普遍大于 EPROM,现在基本取代了它的地位。生活中常用的 U 盘、SD卡、SSD 固态硬盘以及我们 STM32 芯片内部用于存储程序的设备,都是 FLASH 类型的存储器。在存储控制上,最主要的区别是 FLASH 芯片只能一大片一大片地擦写,而 EEPROM可以单个字节擦写。% n; K$ [9 e& u1 O$ P2 V

FLASH 芯片的最小擦除单位为扇区(Sector),而一个块(Block)包含 16 个扇区,4Kbytes为一个Sector,16个扇区为1个Block。W25Q64  容量为8M字节(即 64M bit),  分为128块(Block),每一块的大小为64K字节,每块又分为16个扇区(Sector),那么每个扇区就是4K个字节。W25Q128 容量为16M字节(即 128M bit),分为256块(Block),每一块的大小为64K字节,每块又分为16个扇区(Sector),那么每个扇区就是4K个字节(4096个字节,也就是4096个存储单元)。

W25Qxx的最小擦除单位为一个扇区,也就是每一次必须擦除4K字节。所以必须给W25Qxx开辟至少4K的缓冲区,这样对单片机的RAM的要求比较高,要求芯片必须有4K以上的RAM才能很好的操作。所有的FLASH我们在写之前都要擦出对应的扇区,擦除后的数据是0XFF。我们可以这样理解。我们要改写FLASH芯片W25Q128的一个扇区中某一个数据,就必须在STM32芯片的内部RAM中开辟4K字节(4096字节)的缓冲区域。先把FLASH芯片W25Q128的一个扇区中数据全部读到STM32芯片的内部RAM中开辟4K字节(4096字节)的缓冲区域中去,把我们要改写的数据在缓冲区域改写好之后,再把FLASH芯片W25Q128的一个扇区中的数据全部擦除完毕,擦除完成之后再把数据写回去。这是写入数据的操作,在读数据的时候不需要以扇区为单位,想读哪个扇区就读哪个扇区的数据。
1 w2 }$ T& v- f! k2 A
/*************************************************************************
7 w! e: K! Q9 v0 u4 h6 P* Function Name : SPI_Flash_Write
4 M6 H* c/ p4 c. p6 B* Description   : 在指定地址开始写入指定长度的数据,该函数带擦除操作!3 ^% J) v% Y, x& E4 K6 t" i1 A+ C! v
* Input         :  *pBuffer:要写入数据的指针
; j% _' t& W/ f, X! @                    WriteAddr:开始写入的地址(24bit)
, Q  Y( H, y* q+ D! U  h                    NumByteToWrite:要写入的字节数(最大16 x 1024 x 1024)   o; C0 G. X; m
* Output        : None% W5 @) H: R3 n- y; b  ?
* Return        : None7 |/ }$ \. W! ]
****************************************************************************/
" r2 W7 k; Y# g* d2 s
void SPI_Flash_Write(u8* pBuffer, u32 WriteAddr, u16 NumByteToWrite)0 `! n! Z" `  p
{
) q1 j: l  e+ s6 r# |) ~ u8 NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;
) W, ]6 F/ L- ?9 u5 k" {, T0 { Addr = WriteAddr % 4096;//mod运算求余,若writeAddr是4096整数倍,运算结果Addr值为0 ( {+ y. I: t5 q1 _" V2 N
NumOfPage =  NumByteToWrite / 4096;//计算出要写多少整数扇区 8 c: q5 V& ]* D5 ~' |
NumOfSingle = NumByteToWrite % 4096;//mod运算求余,计算出剩余不满一扇区的字节数 - o3 w' I. x1 E. W, ?- `3 q* j# {" d
count = 4096 - Addr;//差count个数据值,刚好可以对齐到扇区地址 + ^& e0 s) M+ I
if (Addr == 0)//Addr=0,则WriteAddr刚好按扇区对齐或者说小于一个扇区
$ V6 E3 L" {4 W% ?" h# A {
3 x, P& y8 @3 Y* Z# x$ q& L# s  //NumByteToWrite < 4096,写入的字符串大小长度小于一个扇区(4096个字节)的大小,如22
5 E/ j% H8 A; f5 v3 y  if (NumOfPage == 0) * L8 C: {' s- e; n7 B' n
  {
6 V2 |8 b/ e- L3 k; s/ I/ E   SPI_Flash_Write_Page(pBuffer, WriteAddr, NumByteToWrite);) [8 k# Z% o% Q. n8 P3 G3 ?
  }
% O* L0 o( |, G* Y3 x% A* X  else //NumByteToWrite > 4096,写入的字符串大小长度大与一个扇区(4096个字节)的大小,如4098
/ {5 v; \6 L$ {  j' w& ]& K  { , S: p0 q2 T9 U; Q
   //先把整数扇区都写了
5 Q3 m& G) X0 T# d' y   while (NumOfPage--)
: w1 a2 ~  b/ I6 U   {5 [$ B3 a! k2 n5 o: ~$ F9 I
    SPI_Flash_Write_Page(pBuffer, WriteAddr, 4096);4 Z, W. J/ I% v: {1 ~# q0 ]
    WriteAddr +=  4096;# D' d8 Y: l8 y. }
    pBuffer += 4096;
% Q7 q. o7 k. f" I* C   }
. A; R5 k/ _1 Q/ L, r   //若有多余的不满一扇区的数据,把它写完* l, s/ `& q* ?
   SPI_Flash_Write_Page(pBuffer, WriteAddr, NumOfSingle);8 q; T& v% o. |- K% q) r: [
  }0 G" k: ]6 V2 i1 f$ x
}
0 e+ f  o: w- K$ p3 ]5 \ //若地址与 4096 不对齐$ G; y. O+ P0 a  I7 R
else //Addr不等于0,则要写入的WriteAddr地址与4096不对齐4 w3 {9 t- E# `5 m# ?
{. N+ |, `  Z, _- H
  //NumByteToWrite < 4096
* g  l2 [- r: `& i; N6 z& A  if (NumOfPage == 0)//大小不够一个扇区,如22
0 }, Y, M" [4 P( o; p8 K/ w4 I# i  {
" Y, m' l# z4 M4 S, l" Q. s( ]   //当前页剩余的count个位置比NumOfSingle小,一扇区写不完# Q' Z% v! w; [7 {+ i! n) K1 f
   if (NumOfSingle > count)
- w/ p5 m4 v6 ^' e4 p  E   {
# a: a; r3 r* }0 N* e6 m! R    temp = NumOfSingle - count;4 R/ A- t0 [( t
    //先写满当前扇区
+ v. Z+ X* L' f  F& _) X, u    SPI_Flash_Write_Page(pBuffer, WriteAddr, count);
9 \0 P# ]6 U6 b6 n3 ~    9 D, E$ b! [7 T0 Z# z# S) ], R' X
    WriteAddr +=  count;9 g: l' k8 Z$ p! ~# b! `8 o
    pBuffer += count;
; g; F8 |. A2 Y    //再写剩余的数据
! v- B# e* ~  B. V; ?9 N& W    SPI_Flash_Write_Page(pBuffer, WriteAddr, temp);7 s, ?+ H6 v  w4 S7 P6 U) x0 T( N; K
   }
, }" l: u# B3 ?3 D2 J   else //当前扇区剩余的count个位置能写完NumOfSingle个数据1 ?+ l2 t" T; w, `  l
   {
- ?) Y- I. ]+ d2 p, b1 E7 W    SPI_Flash_Write_Page(pBuffer, WriteAddr, NumByteToWrite);
5 n; m' j5 {: m& _# B   }  y0 Z, `$ F3 O+ x7 N. c% e+ ~# c3 `
  }0 ~  B+ O$ w7 m. v1 y. @( U
  else //NumByteToWrite > 4096  //大小够一个扇区,而且还超出一点点,如40983 [( O' h- E# `1 ~8 K5 w' z
  {* t3 K. d5 J) n* }# v0 k8 L6 W
   //地址不对齐多出的count分开处理,不加入这个运算
/ L( d/ `8 ?! t3 D- B) c   NumByteToWrite -= count;+ t& l' Q- S9 P% c9 {! ^2 O5 k
   NumOfPage =  NumByteToWrite / 4096;$ Q, n" X9 t4 I2 F. O/ }/ |
   NumOfSingle = NumByteToWrite % 4096;   
# R9 ~3 y; m* o+ H   //先写完count个数据,为的是让下一次要写的地址对齐- O) d& B. {) B& g7 d
   SPI_Flash_Write_Page(pBuffer, WriteAddr, count);
6 ]% v5 f) _& s( G' b  M- Y   
$ a+ P: }, E9 b   //接下来就重复地址对齐的情况 */9 L6 e. B2 w- f) M
   WriteAddr +=  count;) P; |; W% O6 [" C, V
   pBuffer += count;
* \5 Z6 H" B. e. w   //把整数扇区都写了*/
& R/ X4 H2 x" x   while (NumOfPage--)
7 m( i9 v. N* i4 i  X   {
6 L/ e) d. n  W) C    SPI_Flash_Write_Page(pBuffer, WriteAddr, 4096);8 z0 o3 q/ y* k- o6 R& `* U3 Y
    WriteAddr +=  1096;$ {) I# T9 A, Q5 V0 l; U) u7 a
    pBuffer += 4096;
# r; f, x) ?7 V   }
: G3 z. j* {6 z' g- f   //若有多余的不满一扇区的数据,把它写完7 ], w( D! V$ z/ \
   if (NumOfSingle != 0): {9 Y- y* N" E" O& i$ |( y8 W
   {
$ F& V0 M. j+ q% j4 @5 S    SPI_Flash_Write_Page(pBuffer, WriteAddr, NumOfSingle);
- W- g8 ?  v' f/ l   }) }& D) Z. r6 `2 D) g0 `. z
  }
7 n$ U9 ^6 Z$ ~6 K) }! V }  q$ t- D* U7 |
}总结:
/**********************************************************************7 H! I. Y$ J: [5 T
* Function Name : SPI_Flash_Read
& b7 A( K, U) h* Description   : 在指定地址开始读取指定长度的数据+ P, A2 S" {( ~* X+ z& C
* Input         :  *pBuffer:存储读出数据的指针
/ ~7 F7 n: a% Y% _& J               ReadAddr:开始读取的地址(24bit)
6 |- `0 s$ N* \, W- M! a               NumByteToRead:要读取的字节数(最大 16 x 1024 x 1024)
' F# r! l9 K' k$ s5 i" j5 ^* Output        : None
2 u  _$ N- \% b' P" a* Return        : None
) w; j5 n) P, ^) I- z" ~. b& ]************************************************************************/
7 k; i+ w7 B0 T# }2 ~6 s2 q7 F) x
void SPI_Flash_Read(u8* pBuffer,u32 ReadAddr,u16 NumByteToRead)   
3 n2 `2 e. x, ~! E; I4 [2 W$ }{
. P+ n6 I8 m! p+ ]. Z  u16 i;                    4 n, x4 a: i5 `
SPI_FLASH_CS=0;                              //使能器件   
* k8 _1 [) f6 h    SPI1_ReadWriteByte(CMD_W25X_ReadData);          //发送读取命令   " Y  ^* g! f$ ?; A2 m; b4 n0 j" V
    SPI1_ReadWriteByte((ReadAddr& 0xFF0000)>>16);   //发送扇区地址的高8bit   
: `2 r; K! v' H5 N( M' I) ^    SPI1_ReadWriteByte((ReadAddr& 0xFF00)>>8);    //发送扇区地址的中间8bit & _0 N; f! _; e& G1 A
    SPI1_ReadWriteByte( ReadAddr& 0xFF);      //发送扇区地址的低8bit
$ T8 m1 k: c& Y6 w    for(i=0;i<NumByteToRead;i++); a# n( ~5 @' F( i( N% C& p4 G
{
& N8 v! a5 v, ^  E# a( D' @        pBuffer=SPI1_ReadWriteByte(0XFF);   //循环读数  
! f& p# k' v  p. ~; l' x    }+ \! E2 z6 [. O6 r0 ^! T6 I
SPI_FLASH_CS=1;                            //取消片选            
4 C* ]2 f& z' ]$ U. q}  , J+ A/ Y3 R- n1 e" G5 u  ^
总结:
  • 存储量大
  • 不能任意访问字节地址数据,每一个存储单片不可以独立访问,最小读取单元是一个扇区
  • 写入前是必须对写入的扇区做独立的擦除操作。擦除的目的是使存储单元的数据全为1* T0 {) c: h) o8 l
SD卡大容量存储器

SD 卡(Secure Digital Memory Card)在我们生活中已经非常普遍了,控制器对 SD卡进行读写通信操作一般有两种通信接口可选,一种是 SPI接口,另外一种是 SDIO 接口。SDIO全称是安全数字输入/输出接口,多媒体卡(MMC)、SD卡、SD I/O 卡(专指使用SDIO 接口的一些输入输出设备)都可使用 SDIO 接口通讯。STM32F10x 系列控制器有一 个 SDIO 主机接口,它支持与上述使用 SDIO 接口的设备进行数据传输。

微信图片_20210112140130.png

STM32F10x 系列控制器只支持 SD 卡规范版本 2.0,即只支持标准容量SD和高容量 SDHC 标准卡,不支持超大容量 SDXC 标准卡,所以可以支持的最高卡容量是 32GB。SD 卡一般都支持 SDIO 和 SPI 这两种接口。另外,STM32F42x 系列控制器的 SDIO 是不支持 SPI通信模式的,如果需要用到 SPI通信只能使用 SPI外设。因为SPI通信方式操作SD卡的数据线只有一根,而如果用SDIO的通信方式操作SD卡的数据线却又3根。为了节省资源一般在STM32F10x 系列控制器上用SPI的通信方式,而在引脚资源比较多的F4系列上就用SDIO的通信方式了。
; Z* O( `- K9 c/ u# b3 a2 b


' J% B2 L1 [* d" z5 _1 v9 o5 {
微信图片_20210112140135.png

/ f% L, I2 N- Q
  • SD容量有8MB、16MB、32MB、64MB、128MB、256MB、512MB、1GB、2GB (磁盘格式FAT12、FAT16)
  • SDHC容量有2GB、4GB、8GB、16GB、32GB(磁盘格式FAT32)
  • SDXC容量有32GB、48GB、64GB、128GB、256GB(磁盘格式exFAT)
    ; C8 g, r. x6 W& Z( o, C% k
3.1初始化
1、初始化与SD卡连接的硬件条件(MCU的SPI配置,IO口配置);
2、上电延时(>74 个 CLK);
3、复位卡(CMD0),进入IDLE状态;
4、发送CMD8,检查是否支持2.0协议;
5、根据不同协议检查SD卡(命令包括:CMD55、CMD41、CMD58 和 CMD1 等);
6、取消片选,发多 8个CLK,结束初始化
/*******************************************************************************
, X# O( A$ x& U, u* P* y# r* Function Name  : SD_Init
- i# q- ^, f: s/ x) }  ^! S* Description    : 初始化SD卡$ U, l) n- h9 E8 ]" A- p2 V; N
* Input          : None
, {/ Z, B0 J7 A  E3 B* Output         : None
: `8 Z3 h$ @: [# A3 r, t0 r/ f* Return         : u8
3 B" M2 M2 v% X* c7 I/ Q# k0 E# L3 k*                  0:NO_ERR
* X! \/ u7 A$ I- {*                  1:TIME_OUT
  k# m6 l" }$ T*                  99:NO_CARD
; F" Y/ b9 {1 D) R9 K9 q- }*******************************************************************************/

2 _3 O" `) k* m% P' Uu8 SD_Init(void)
6 B* n6 C5 ~4 k% }, Y& l% |{
7 I* n/ }  ?. o6 n9 t( W! M    u8 r1;      // 存放SD卡的返回值' f. o# ?9 I* o9 o% v! q
    u16 retry;  // 用来进行超时计数
& I9 h. l2 G! e) _- w    u8 buf[4];  
' k# ]& I4 n, | u16 i;+ K$ U9 ~: }0 y. P/ A* j8 C8 {! ?
SD_SPI_Init();  //初始化IO/ Y" L% [& n& X# K# O9 p- N
  SD_SPI_SpeedLow(); //设置到低速模式 " ?- Q3 B8 s# @0 x3 @, ?$ F
  //先产生至少74个脉冲,让SD卡自己初始化完成; `2 w4 v# F3 T+ R
  for(i=0;i<10;i++)1 |$ U2 @3 u5 w0 [* K! P
{# s$ ^% ]* z0 e/ I7 `
     SD_SPI_WriteByte(0XFF);////80clks7 @/ H4 g# Q( j( ~- H# D7 G. u
}
: C) h: ^0 W' s  \" c' g, F/ P3 | //-----------------SD卡复位到idle开始-----------------: j( d# o( u2 y& `# W& w7 k4 c% n
//循环连续发送CMD0,直到SD卡返回0x01,进入IDLE状态
: s8 ?! L  W6 i4 O5 q$ L //超时则直接退出" }( I/ \" l8 s2 g
retry=0;7 l" H8 k7 q; t* A! X
do0 \: m( g6 ^8 G3 T% U- [4 T% U' e
{' C# G- {7 q3 y' C# q  w
  r1=SD_SendCmd(CMD0,0,0x95);//进入IDLE状态,作用是让SD卡进入SPI模式。这里的CRC校验位0x95是固定的,不能修改
! J% r. ^( c  a" L+ {3 i  retry++;! ]. s& ~; I) A/ Q& G) W
}while((r1!=0X01) && (retry<20));//如果 SD 卡有正确的回应,代码就继续执行,如果没有回应程序就终止执行。
6 D1 S4 Q, {& @+ w- A* D. k  //跳出循环后,检查原因:初始化成功?or 重试超时?8 B; A- n3 J+ }, m
if(retry==20) return 1;   //超时返回1
( a, O% k( o* V# g7 H$ j SD_Type=0;//默认无卡
4 i/ O( q' Q% J: q" `" `" u& _ //下面是V2.0卡的初始化
1 \8 a) k/ r$ u# g    //其中需要读取OCR数据,判断是SD2.0还是SD2.0HC卡6 ]) m' m  m% z2 P+ ?, K
if(r1==0X01)9 o5 ?8 Y) e6 t% i: U% q  C
{6 \1 p4 K* a' A% O) V
  if(SD_SendCmd(CMD8,0x1AA,0x87)==1)//SD V2.0
* N9 e- K+ d" _. D7 }5 [) C2 N7 P  {
) m' y# L* y* L& m   //V2.0的卡,CMD8命令后会传回4字节的数据,要跳过再结束本命令
  V" A+ g9 J' U   buf[0]=SD_SPI_ReadByte(); //should be 0x00) Z' Z8 ]& f5 r0 {8 f1 G( V; m6 O6 D
   buf[1]=SD_SPI_ReadByte(); //should be 0x00/ q$ t8 N; s( H* w. u3 k( N
   buf[2]=SD_SPI_ReadByte(); //should be 0x01$ u# K# }4 j0 k0 m% J; s  |
   buf[3]=SD_SPI_ReadByte(); //should be 0xAA- R0 @+ c  j' {1 i+ ~0 ]
   if(buf[2]==0X01&&buf[3]==0XAA)//判断卡是否支持2.7~3.6V的电压范围0 ^+ d' c- f; l" w/ a
   {$ v5 j( b- G9 ?# @& k
    retry=0XFFFE;
) B: F% `) X( w  |- D& }2 k: W7 Z, ^    //发卡初始化指令CMD55+CMD41" n5 m' @. o: R0 T, ]/ V
    do  r  |! O) f0 e, _2 ~8 X
    {  I+ P$ J+ n9 f& S) ^1 O, H
     SD_SendCmd(CMD55,0,0X01); //发送CMD55
$ d  E' R* K% @& M0 ]7 j5 s     r1=SD_SendCmd(CMD41,0x40000000,0X01);//发送CMD41$ l7 d+ j& g' C* ?3 ?, d, h
    }while(r1&&retry--);   
, r3 o3 D0 {5 N& D7 j   
7 F1 q4 h) w& ]5 t1 ~6 t    //初始化指令发送完成,接下来获取OCR信息     
3 l6 T. @6 Y. \0 w- S& H    //-----------鉴别SD2.0卡版本开始-----------$ b( Q% a' ~1 ]' W; r
    if(retry&&SD_SendCmd(CMD58,0,0X01)==0)//鉴别SD2.0卡版本开始
  S5 @' p1 o/ {$ o3 B    {
& H9 L$ z; i- z9 O     //读OCR指令发出后,紧接着是4字节的OCR信息( @* j* _6 C" q( E8 X; W# F
     buf[0]=SD_SPI_ReadByte();
! X$ k9 V9 f. y0 v4 m2 M; r     buf[1]=SD_SPI_ReadByte();
1 K" I4 u0 o0 _/ {  j     buf[2]=SD_SPI_ReadByte(); 4 F% E8 H6 l% }9 f9 I" Z
     buf[3]=SD_SPI_ReadByte();# i/ k; R$ v- S! [4 p
     //检查接收到的OCR中的bit30位(CCS),确定其为SD2.0还是SDHC
) O  Q9 p. e! u. A     //如果CCS=1:为SDV2.0HC的2.0高容量卡   CCS=0:为SDV2.0的2.0版本的标准卡     # O5 s6 G. G4 a( a# r
     if(buf[0]&0x40)
' A6 o$ ~6 \2 i# N4 a      SD_Type=SD_TYPE_V2HC;    //检查CCS
7 J. ^2 W9 K& _" R     else
) f3 B. W: W* \( ]; [      SD_Type=SD_TYPE_V2;  
' k) A) r  [' \% l- Q6 s/ b* Y     LCD_ShowNum(164,250,SD_Type,5,16);//显示SD卡容量
8 i, y) W; N* B- M    //-----------鉴别SD2.0卡版本结束-----------      
2 ~- W. F- `+ K. {$ x' m. M4 g' a* q    }
+ _* I, z# Q, Z  E0 u   }   ! o9 s: c2 @$ o& v
  }  
' P5 k  a  K0 \( K  //如果卡片版本信息是v1.0版本的,即r1=0x05,则进行以下初始化
; D5 ~7 D8 }5 \# N& H- ~% \3 K, ?  else//SD V1.0/ MMC V30 O/ C8 c& k+ `2 {
  {1 a/ _$ F6 ^0 U+ }% }8 N
   //先发CMD55,应返回0x01;否则出错& O7 D3 j1 m! q( o5 X# P
   r1 = SD_SendCmd(CMD55,0,0X01);  //发送CMD55
5 R* E$ V. ?& U- u+ u3 V% L      if(r1 != 0x01)
6 D5 E  v- o% Q* O    return r1;   
# B" U& u  z) o" L   //得到正确响应后,发ACMD41,应得到返回值0x006 c) S8 C* X3 w. a$ }3 o) h  V+ I
   r1=SD_SendCmd(CMD41,0,0X01); //发送CMD41
+ T4 k% D- p  e% \& D( E% o4 j) w   if(r1<=1)
: b/ B& y* N- W4 g+ j) s   {  3 ~- G+ v/ Z' `4 P; A, [+ V/ m: Q
    SD_Type=SD_TYPE_V1;
; G; R7 s9 U6 u# g! Q' d& ]    retry=0XFFFE;( V- `) z3 X' a; m5 L" F
    do //等待退出IDLE模式
" A9 b6 a2 A) `    {1 }/ `; q& M) l. b, K0 Q
     SD_SendCmd(CMD55,0,0X01); //发送CMD556 n0 s  v' i; ]9 d
     r1=SD_SendCmd(CMD41,0,0X01);//发送CMD416 h$ p8 b; S! H5 N/ p
    }while(r1&&retry--);9 o8 p6 ^4 f, L8 k1 J& M
   }else//MMC卡不支持CMD55+CMD41识别# R1 j. n7 N' }: ~4 j# g6 I
   {( J2 S3 k& ^4 h6 P
    SD_Type=SD_TYPE_MMC;//MMC V3  U) `; q8 q: ^6 i; i& |9 [
    retry=0XFFFE;2 ~- W' K* U* A! j- M
    do //等待退出IDLE模式
. ^! z9 u% I7 k7 n- o/ p    {               
" e& z/ q; N$ a2 }! s     r1=SD_SendCmd(CMD1,0,0X01);//发送CMD1,发送MMC卡初始化命令
" I" L' d6 H+ `7 R) N. N) m8 [    }while(r1&&retry--);  6 E% \( S6 u5 v* x# ?
   }
& v$ D/ D  G6 ^8 P1 [   if(retry==0||SD_SendCmd(CMD16,512,0X01)!=0)
3 r7 C) G4 F# T( Q* n! F3 k6 H/ g    SD_Type=SD_TYPE_ERR;//错误的卡; Q: w0 ?! W& T9 o! z9 c
  }4 W$ n: ^) i' W% G' Y( i$ [: s  f
}
- `0 G/ s3 y  n. R9 F SD_DisSelect();//取消片选
! _& ^" S! e( h1 R* U1 i SD_SPI_SpeedHigh();//高速
+ w- d- Q0 B9 |4 E0 f4 w: m if(SD_Type)  return 0;6 R1 b: u' k. Y$ `  I
else if(r1)  return r1;     
8 T7 ^: q; f5 E( L% O2 s/ } return 0xaa;//其他错误
8 Z5 s# c% K* M. G6 h}
& p9 n' v/ X# w1 H
3.2写数据通过 CMD24实现
1、发送CMD24;
2、接收卡响应R1;
3、发送写数据起始令牌 0XFE;
4、发送数据;
5、发送2字节的伪CRC;
6、禁止片选之后,发多8个CLK;
/*******************************************************************************
# u* K& ~" M$ E3 _  g) _% \* Function Name  : SD_WriteDisk
2 e' H' d9 J0 M/ v/ H* Description    : 向SD卡写数据
8 C' J+ c; q' o3 b* r% K2 }* Input          : buf:数据缓存区# ~/ ^" d2 w1 q7 `' }9 u
*                  sector:扇区
- N0 R- I- ]' @% X0 q. n*                  cnt:扇区数8 J# K2 O3 I4 _! I) e
* Output         : None/ u0 D$ Z# c& ~) w1 ~
* Return         : u8 4 G' J: L2 c  C  @9 Q
*                  0:ok
. ~, E& m" J, j4 U: c7 z2 F*                  其他,失败.
/ K$ p3 g  B# b3 t*******************************************************************************/

6 ~& L4 Q/ q' n2 n, R# @9 vu8 SD_WriteDisk(u8*buf,u32 sector,u8 cnt)  C( m' d; ]( S+ B* e1 a
{
% t1 y0 A( j- f, F, I- R u8 r1;5 ]0 z, n  K$ [: H
if(SD_Type!=SD_TYPE_V2HC)sector *= 512;//转换为字节地址4 o9 P+ c& I0 l  |
if(cnt==1)
  n, z1 ?" G* [( ?2 [9 o+ c1 N( Z( A {
* Z: E' O! D9 t( Z  r1=SD_SendCmd(CMD24,sector,0X01);//读命令4 o8 f! m& A4 b- E. V& w
  if(r1==0)//指令发送成功
- \6 j: \& y* o/ y  U* s/ o  {8 i7 v  {& a# f% x% ^) L
   r1=SD_SendBlock(buf,0xFE);//写512个字节      u8 k. H7 C8 p8 Z; `: q4 N" c
  }
- ^  G% c+ {0 ?) C }else
' g$ K4 d) q4 B/ j {5 I. K4 E% |; m! c) `1 m- J- R/ @
  if(SD_Type!=SD_TYPE_MMC)7 m, d1 d7 }. k! L0 H( `& R5 }
  {4 ~4 N( R% W2 r6 d3 Z( t
   SD_SendCmd(CMD55,0,0X01); 4 F, p* y6 t5 `; I5 H1 F
   SD_SendCmd(CMD23,cnt,0X01);//发送指令
$ j; C! y0 U0 E, G0 }  }$ W" `4 g4 m. N( ^: G! e
   r1=SD_SendCmd(CMD25,sector,0X01);//连续读命令
3 C  m% O! G8 m6 W3 F3 _  if(r1==0)
! U- Y  r/ ~3 C, \2 J% k  {0 h( ?: T( ^5 k" @3 Q
   do
% ]/ ~2 X" |9 e+ ]9 g7 C6 \   {
$ H3 s+ q% u4 Z    r1=SD_SendBlock(buf,0xFC);//接收512个字节  
1 N8 _( h; _8 B3 T; i9 t    buf+=512;  
) A& Q& K, \& M1 F- ?   }while(--cnt && r1==0);
& Q! c3 O( |& ~  p/ G0 {( E, N- d% r   r1=SD_SendBlock(0,0xFD);//接收512个字节 0 x1 n1 b) W$ Q
  }
! ~( t4 C; Q2 f }   8 u$ z2 d* D* q  |; r( e
SD_DisSelect();//取消片选,释放SPI总线
& J2 O' d7 P$ U return r1;
3 S, U/ F  F& G# F6 `7 w+ A}
( Z- S, _6 x  j: d
3.3读取数据通过 CMD17实现
1、发送CMD17;
2、接收卡响应R1;
3、接收数据起始令牌 0XFE;
4、接收数据;
5、接收2个字节的 CRC,如果不使用CRC,这两个字节在读取后可以丢掉。
6、禁止片选之后,发多8个CLK;/*******************************************************************************
" A8 A5 F$ G! @! z% K* Function Name  : SD_ReadDisk. I& r9 w; X1 t! T! C' E
* Description    : 读SD卡数据: x/ G( O; G* t' F+ O) \! d
* Input          : buf:数据缓存区
) L7 V8 ^; M+ J" V. a! w5 Z9 p" P5 n*               sector:扇区' f/ ?0 O! Q9 U* a1 U' D" `, l1 _0 m
*               cnt:扇区数1 M; {+ c6 x) E8 Z  j/ {
* Output         : None
$ W4 j% E" S7 I7 B0 C7 g7 B* Return         : u8
4 J/ a& W" v7 L) j. ^* H*                  0:ok( e% m, Y' \$ Z1 z
*                  其他,失败.
8 I0 U0 Y+ t' e' {. Z- G* P7 p*******************************************************************************/

# p- [" @( s  qu8 SD_ReadDisk(u8*buf,u32 sector,u8 cnt): `' N& {% t; |  d: l
{1 M" Y8 e1 }5 M1 m5 e
u8 r1;2 Q' i# B4 C- M9 H- A3 b
if(SD_Type!=SD_TYPE_V2HC)sector <<= 9;//转换为字节地址
+ M& d; @7 s: O- l$ h1 K if(cnt==1)% `7 M5 G9 p' C0 O
{" i5 M( ~& c9 D. o( z/ }
  r1=SD_SendCmd(CMD17,sector,0X01);//读命令, m8 _' y$ n- T7 m8 y# g/ J. w
  if(r1==0)//指令发送成功1 T0 C. H; @9 z' h- ]& D) P
  {5 W3 G& A  v7 x5 ^: B9 D
   r1=SD_RecvData(buf,512);//接收512个字节   
( j/ V& \8 C+ F  }
$ L% E! K: [* X9 w5 x" x }else
7 o% V9 Q* ~% }! I5 ?: V% X {
' |% E: v3 X! g9 Z! h  r1=SD_SendCmd(CMD18,sector,0X01);//连续读命令
/ A) v7 j. H' L  do
- ]8 q, g0 S, F4 y- |& [  {6 J* p+ R' I/ w8 A
   r1=SD_RecvData(buf,512);//接收512个字节  : s4 s! g- h6 p$ ^4 t
   buf+=512;  5 i: f- Y7 H- N/ |/ m$ j. B8 ^
  }while(--cnt && r1==0);  
; ^- T- }+ I/ L$ H/ u' i  SD_SendCmd(CMD12,0,0X01); //发送停止命令
+ r$ i2 o7 v& e8 {' _9 G }   
8 V: }& U6 B9 F3 f( M SD_DisSelect();//取消片选
4 ?9 g7 r1 P! a+ e/ w4 d# ^ return r1;
: G) O# ?+ Z1 |3 p}

8 F& t+ f! a( g( ^
赞 收藏 评论1 发布时间:2021-1-12 14:03

举报

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