STM32 + CubeMX + 硬件SPI + SD卡 + FATFS

  这篇文章记录一下STM32 SD卡的调试过程,使用硬件SPI加fatfs文件系统的方式实现对文件的读写。

目录

  • 一、FATFS介绍
  • 二、Stm32CubeMx配置
  • 三、Keil代码编写
  • 1、在工程中增加代码
  • (1)sd.h
  • (2)sd.c
  • 2、修改CubeMx已生成的代码
  • (1)user_diskio.c
  • (2)main.h
  • (3)main.c
  • 四、硬件连接
  • 五、运行效果
  • 六、遇到的问题
  • 一、FATFS介绍

      FATFS是一个完全免费开源的FAT 文件系统模块,专门为小型的嵌入式系统而设计。完全用标准C 语言编写,所以具有良好的硬件平台独立性。可以移植到8051、PIC、AVR、SH、Z80、H8、ARM 等系列单片机上而只需做简单的修改。它支持FATl2、FATl6 和FAT32,支持多个存储媒介;有独立的缓冲区,可以对多个文件进行读/写,并特别对8 位单片机和16 位单片机做了优化。(以上内容引用自博客:原文链接)

    二、Stm32CubeMx配置

    1、RCC开启外部高速时钟(略)
    2、配置STLink调试口(略)
    3、配置串口方便调试输出(略)
    4、配置工程名、生成路径,之后生成工程(略)
    (1-4步的基础配置可以参考前面的文章《STM32基础工程模板创建》)
    5、SPI 配置,配置PA4为CS片选引脚


    7、Fatfs配置


    8、修改工程的栈区大小(这一步很重要,栈区不够会导致fatfs系统跑不起来)

    三、Keil代码编写

    1、在工程中增加代码

    (1)sd.h

    #ifndef __SD_H
    #define __SD_H
    
    #include "main.h"
    #include "spi.h"
    #include "ff.h"
    
    extern uint8_t SD_TYPE;
    
    //SD卡类型
    #define ERR     	0x00
    #define MMC				0x01
    #define V1				0x02
    #define V2				0x04
    #define V2HC			0x06
    
    #define DUMMY_BYTE				 0xFF 
    #define MSD_BLOCKSIZE			 512
    
    
    //CMD定义
    #define CMD0    0       //卡复位
    #define CMD1    1
    #define CMD8    8       //命令8 ,SEND_IF_COND
    #define CMD9    9       //命令9 ,读CSD数据
    #define CMD10   10      //命令10,读CID数据
    #define CMD12   12      //命令12,停止数据传输
    #define CMD16   16      //命令16,设置SectorSize 应返回0x00
    #define CMD17   17      //命令17,读sector
    #define CMD18   18      //命令18,读Multi sector
    #define CMD23   23      //命令23,设置多sector写入前预先擦除N个block
    #define CMD24   24      //命令24,写sector
    #define CMD25   25      //命令25,写Multi sector
    #define CMD41   41      //命令41,应返回0x00
    #define CMD55   55      //命令55,应返回0x01
    #define CMD58   58      //命令58,读OCR信息
    #define CMD59   59      //命令59,使能/禁止CRC,应返回0x00
    
    //数据写入回应字意义
    #define MSD_DATA_OK                0x05
    #define MSD_DATA_CRC_ERROR         0x0B
    #define MSD_DATA_WRITE_ERROR       0x0D
    #define MSD_DATA_OTHER_ERROR       0xFF
    //SD卡回应标记字
    #define MSD_RESPONSE_NO_ERROR      0x00
    #define MSD_IN_IDLE_STATE          0x01
    #define MSD_ERASE_RESET            0x02
    #define MSD_ILLEGAL_COMMAND        0x04
    #define MSD_COM_CRC_ERROR          0x08
    #define MSD_ERASE_SEQUENCE_ERROR   0x10
    #define MSD_ADDRESS_ERROR          0x20
    #define MSD_PARAMETER_ERROR        0x40
    #define MSD_RESPONSE_FAILURE       0xFF
    
    
    enum _CD_HOLD
    {
    	HOLD = 0,
    	RELEASE = 1,
    };
    
    typedef struct               /* Card Specific Data */
    {
      uint8_t  CSDStruct;            /* CSD structure */
      uint8_t  SysSpecVersion;       /* System specification version */
      uint8_t  Reserved1;            /* Reserved */
      uint8_t  TAAC;                 /* Data read access-time 1 */
      uint8_t  NSAC;                 /* Data read access-time 2 in CLK cycles */
      uint8_t  MaxBusClkFrec;        /* Max. bus clock frequency */
      uint16_t CardComdClasses;      /* Card command classes */
      uint8_t  RdBlockLen;           /* Max. read data block length */
      uint8_t  PartBlockRead;        /* Partial blocks for read allowed */
      uint8_t  WrBlockMisalign;      /* Write block misalignment */
      uint8_t  RdBlockMisalign;      /* Read block misalignment */
      uint8_t  DSRImpl;              /* DSR implemented */
      uint8_t  Reserved2;            /* Reserved */
      uint32_t DeviceSize;           /* Device Size */
      uint8_t  MaxRdCurrentVDDMin;   /* Max. read current @ VDD min */
      uint8_t  MaxRdCurrentVDDMax;   /* Max. read current @ VDD max */
      uint8_t  MaxWrCurrentVDDMin;   /* Max. write current @ VDD min */
      uint8_t  MaxWrCurrentVDDMax;   /* Max. write current @ VDD max */
      uint8_t  DeviceSizeMul;        /* Device size multiplier */
      uint8_t  EraseGrSize;          /* Erase group size */
      uint8_t  EraseGrMul;           /* Erase group size multiplier */
      uint8_t  WrProtectGrSize;      /* Write protect group size */
      uint8_t  WrProtectGrEnable;    /* Write protect group enable */
      uint8_t  ManDeflECC;           /* Manufacturer default ECC */
      uint8_t  WrSpeedFact;          /* Write speed factor */
      uint8_t  MaxWrBlockLen;        /* Max. write data block length */
      uint8_t  WriteBlockPaPartial;  /* Partial blocks for write allowed */
      uint8_t  Reserved3;            /* Reserded */
      uint8_t  ContentProtectAppli;  /* Content protection application */
      uint8_t  FileFormatGrouop;     /* File format group */
      uint8_t  CopyFlag;             /* Copy flag (OTP) */
      uint8_t  PermWrProtect;        /* Permanent write protection */
      uint8_t  TempWrProtect;        /* Temporary write protection */
      uint8_t  FileFormat;           /* File Format */
      uint8_t  ECC;                  /* ECC code */
      uint8_t  CSD_CRC;              /* CSD CRC */
      uint8_t  Reserved4;            /* always 1*/
    }
    MSD_CSD;
    
    typedef struct				 /*Card Identification Data*/
    {
      uint8_t  ManufacturerID;       /* ManufacturerID */
      uint16_t OEM_AppliID;          /* OEM/Application ID */
      uint32_t ProdName1;            /* Product Name part1 */
      uint8_t  ProdName2;            /* Product Name part2*/
      uint8_t  ProdRev;              /* Product Revision */
      uint32_t ProdSN;               /* Product Serial Number */
      uint8_t  Reserved1;            /* Reserved1 */
      uint16_t ManufactDate;         /* Manufacturing Date */
      uint8_t  CID_CRC;              /* CID CRC */
      uint8_t  Reserved2;            /* always 1 */
    }
    MSD_CID;
    
    typedef struct
    {
      MSD_CSD CSD;
      MSD_CID CID;
      uint32_t Capacity;              /* Card Capacity */
      uint32_t BlockSize;             /* Card Block Size */
      uint16_t RCA;
      uint8_t CardType;
      uint32_t SpaceTotal;            /* Total space size in file system */
      uint32_t SpaceFree;      	     /* Free space size in file system */
    }
    MSD_CARDINFO, *PMSD_CARDINFO;
    
    extern MSD_CARDINFO SD0_CardInfo;
    
    
    uint8_t		 	SD_init(void);
    void 				SD_CS(uint8_t p);
    uint32_t  	SD_GetSectorCount(void);
    uint8_t 		SD_GETCID (uint8_t *cid_data);
    uint8_t 		SD_GETCSD(uint8_t *csd_data);
    int 				MSD0_GetCardInfo(PMSD_CARDINFO SD0_CardInfo);
    uint8_t			SD_ReceiveData(uint8_t *data, uint16_t len);
    uint8_t 		SD_SendBlock(uint8_t*buf,uint8_t cmd);
    uint8_t 		SD_ReadDisk(uint8_t*buf,uint32_t sector,uint8_t cnt);
    uint8_t 		SD_WriteDisk(uint8_t*buf,uint32_t sector,uint8_t cnt);
    
    
    void SPI_setspeed(uint8_t speed);
    uint8_t spi_readwrite(uint8_t Txdata);
    void Get_SDCard_Capacity(void);
    void WritetoSD(char filename[], BYTE write_buff[], uint8_t bufSize);
    
    
    
    #endif
    
    
    

    (2)sd.c

    #include "sd.h"
    
    
    uint8_t DFF=0xFF;
    uint8_t test;
    uint8_t SD_TYPE=0x00;
    
    MSD_CARDINFO SD0_CardInfo;
    
    //
    //片选
    //
    void SD_CS(uint8_t p){
    	if(p==0){	
    		HAL_GPIO_WritePin(SD_CS_GPIO_Port,SD_CS_Pin,GPIO_PIN_SET);
    		}else{
    		HAL_GPIO_WritePin(SD_CS_GPIO_Port,SD_CS_Pin,GPIO_PIN_RESET);
    		}
    }
    ///
    //发送命令,发完释放
    //
    int SD_sendcmd(uint8_t cmd,uint32_t arg,uint8_t crc){
    	uint8_t r1;
      uint8_t retry;
    
      SD_CS(0);
    	HAL_Delay(20);
      SD_CS(1);
    	do{
    		retry=spi_readwrite(DFF);
    	}while(retry!=0xFF);
    
      spi_readwrite(cmd | 0x40);
      spi_readwrite(arg >> 24);
      spi_readwrite(arg >> 16);
      spi_readwrite(arg >> 8);
      spi_readwrite(arg);
      spi_readwrite(crc);
      if(cmd==CMD12)spi_readwrite(DFF);
      do
    	{
    		r1=spi_readwrite(0xFF);
    	}while(r1&0X80);
    	
    	return r1;
    }
    
    
    /
    //SD卡初始化
    
    uint8_t SD_init(void)
    {
    	uint8_t r1;	
    	uint8_t buff[6] = {0};
    	uint16_t retry; 
    	uint8_t i;
    	
    //	MX_SPI3_Init();
    	SPI_setspeed(SPI_BAUDRATEPRESCALER_256);
    	SD_CS(0);
    	for(retry=0;retry<10;retry++){
    			spi_readwrite(DFF);
    	}
    	
    	//SD卡进入IDLE状态
    	do{
    		r1 = SD_sendcmd(CMD0 ,0, 0x95);	
    	}while(r1!=0x01);
    	
    	//查看SD卡的类型
    	SD_TYPE=0;
    	r1 = SD_sendcmd(CMD8, 0x1AA, 0x87);
    	if(r1==0x01){
    		for(i=0;i<4;i++)buff[i]=spi_readwrite(DFF);	//Get trailing return value of R7 resp
    		if(buff[2]==0X01&&buff[3]==0XAA)//卡是否支持2.7~3.6V
    		{
    			retry=0XFFFE;
    			do
    			{
    				SD_sendcmd(CMD55,0,0X01);	//发送CMD55
    				r1=SD_sendcmd(CMD41,0x40000000,0X01);//发送CMD41
    			}while(r1&&retry--);
    			if(retry&&SD_sendcmd(CMD58,0,0X01)==0)//鉴别SD2.0卡版本开始
    			{
    				for(i=0;i<4;i++)buff[i]=spi_readwrite(0XFF);//得到OCR值
    				if(buff[0]&0x40){
    					SD_TYPE=V2HC;
    				}else {
    					SD_TYPE=V2;
    				}						
    			}
    		}else{
    			SD_sendcmd(CMD55,0,0X01);			//发送CMD55
    			r1=SD_sendcmd(CMD41,0,0X01);	//发送CMD41
    			if(r1<=1)
    			{		
    				SD_TYPE=V1;
    				retry=0XFFFE;
    				do //等待退出IDLE模式
    				{
    					SD_sendcmd(CMD55,0,0X01);	//发送CMD55
    					r1=SD_sendcmd(CMD41,0,0X01);//发送CMD41
    				}while(r1&&retry--);
    			}else//MMC卡不支持CMD55+CMD41识别
    			{
    				SD_TYPE=MMC;//MMC V3
    				retry=0XFFFE;
    				do //等待退出IDLE模式
    				{											    
    					r1=SD_sendcmd(CMD1,0,0X01);//发送CMD1
    				}while(r1&&retry--);  
    			}
    			if(retry==0||SD_sendcmd(CMD16,512,0X01)!=0)SD_TYPE=ERR;//错误的卡
    		}
    	}
    	SD_CS(0);
    	SPI_setspeed(SPI_BAUDRATEPRESCALER_2);
    	if(SD_TYPE)return 0;
    	else return 1;
    }
    
    
    //读取指定长度数据
    uint8_t SD_ReceiveData(uint8_t *data, uint16_t len)
    {
    
       uint8_t r1;
       SD_CS(1);									   
       do
       { 
          r1 = spi_readwrite(0xFF);	
          HAL_Delay(100);
    		}while(r1 != 0xFE);	
      while(len--)
      {
       *data = spi_readwrite(0xFF);
       data++;
      }
      spi_readwrite(0xFF);
      spi_readwrite(0xFF); 										  		
      return 0;
    }
    //向sd卡写入一个数据包的内容 512字节
    uint8_t SD_SendBlock(uint8_t*buf,uint8_t cmd)
    {	
    	uint16_t t;	
    uint8_t r1;	
    	do{
    		r1=spi_readwrite(0xFF);
    	}while(r1!=0xFF);
    	
    	spi_readwrite(cmd);
    	if(cmd!=0XFD)//不是结束指令
    	{
    		for(t=0;t<512;t++)spi_readwrite(buf[t]);//提高速度,减少函数传参时间
    	    spi_readwrite(0xFF);//忽略crc
    	    spi_readwrite(0xFF);
    		t=spi_readwrite(0xFF);//接收响应
    		if((t&0x1F)!=0x05)return 2;//响应错误									  					    
    	}						 									  					    
        return 0;//写入成功
    }
    
    //获取CID信息
    uint8_t SD_GETCID (uint8_t *cid_data)
    {
    		uint8_t r1;
    	  r1=SD_sendcmd(CMD10,0,0x01); //读取CID寄存器
    		if(r1==0x00){
    			r1=SD_ReceiveData(cid_data,16);
    		}
    		SD_CS(0);
    		if(r1)return 1;
    		else return 0;
    }
    //获取CSD信息
    uint8_t SD_GETCSD(uint8_t *csd_data){
    		uint8_t r1;	 
        r1=SD_sendcmd(CMD9,0,0x01);//发CMD9命令,读CSD寄存器
        if(r1==0)
    	{
        	r1=SD_ReceiveData(csd_data, 16);//接收16个字节的数据 
        }
    	SD_CS(0);//取消片选
    	if(r1)return 1;
    	else return 0;
    }
    //获取SD卡的总扇区数
    uint32_t SD_GetSectorCount(void)
    {
        uint8_t csd[16];
        uint32_t Capacity;  
        uint8_t n;
    		uint16_t csize;  					    
    	//取CSD信息,如果期间出错,返回0
        if(SD_GETCSD(csd)!=0) return 0;	    
        //如果为SDHC卡,按照下面方式计算
        if((csd[0]&0xC0)==0x40)	 //V2.00的卡
        {	
    		csize = csd[9] + ((uint16_t)csd[8] << 8) + 1;
    		Capacity = (uint32_t)csize << 10;//得到扇区数	 		   
        }else//V1.XX的卡
        {	
    		n = (csd[5] & 15) + ((csd[10] & 128) >> 7) + ((csd[9] & 3) << 1) + 2;
    		csize = (csd[8] >> 6) + ((uint16_t)csd[7] << 2) + ((uint16_t)(csd[6] & 3) << 10) + 1;
    		Capacity= (uint32_t)csize << (n - 9);//得到扇区数   
        }
        return Capacity;
    }
    int MSD0_GetCardInfo(PMSD_CARDINFO SD0_CardInfo)
    {
      uint8_t r1;
      uint8_t CSD_Tab[16];
      uint8_t CID_Tab[16];
    
      /* Send CMD9, Read CSD */
      r1 = SD_sendcmd(CMD9, 0, 0xFF);
      if(r1 != 0x00)
      {
        return r1;
      }
    
      if(SD_ReceiveData(CSD_Tab, 16))
      {
    	return 1;
      }
    
      /* Send CMD10, Read CID */
      r1 = SD_sendcmd(CMD10, 0, 0xFF);
      if(r1 != 0x00)
      {
        return r1;
      }
    
      if(SD_ReceiveData(CID_Tab, 16))
      {
    	return 2;
      }  
    
      /* Byte 0 */
      SD0_CardInfo->CSD.CSDStruct = (CSD_Tab[0] & 0xC0) >> 6;
      SD0_CardInfo->CSD.SysSpecVersion = (CSD_Tab[0] & 0x3C) >> 2;
      SD0_CardInfo->CSD.Reserved1 = CSD_Tab[0] & 0x03;
      /* Byte 1 */
      SD0_CardInfo->CSD.TAAC = CSD_Tab[1] ;
      /* Byte 2 */
      SD0_CardInfo->CSD.NSAC = CSD_Tab[2];
      /* Byte 3 */
      SD0_CardInfo->CSD.MaxBusClkFrec = CSD_Tab[3];
      /* Byte 4 */
      SD0_CardInfo->CSD.CardComdClasses = CSD_Tab[4] << 4;
      /* Byte 5 */
      SD0_CardInfo->CSD.CardComdClasses |= (CSD_Tab[5] & 0xF0) >> 4;
      SD0_CardInfo->CSD.RdBlockLen = CSD_Tab[5] & 0x0F;
      /* Byte 6 */
      SD0_CardInfo->CSD.PartBlockRead = (CSD_Tab[6] & 0x80) >> 7;
      SD0_CardInfo->CSD.WrBlockMisalign = (CSD_Tab[6] & 0x40) >> 6;
      SD0_CardInfo->CSD.RdBlockMisalign = (CSD_Tab[6] & 0x20) >> 5;
      SD0_CardInfo->CSD.DSRImpl = (CSD_Tab[6] & 0x10) >> 4;
      SD0_CardInfo->CSD.Reserved2 = 0; /* Reserved */
      SD0_CardInfo->CSD.DeviceSize = (CSD_Tab[6] & 0x03) << 10;
      /* Byte 7 */
      SD0_CardInfo->CSD.DeviceSize |= (CSD_Tab[7]) << 2;
      /* Byte 8 */
      SD0_CardInfo->CSD.DeviceSize |= (CSD_Tab[8] & 0xC0) >> 6;
      SD0_CardInfo->CSD.MaxRdCurrentVDDMin = (CSD_Tab[8] & 0x38) >> 3;
      SD0_CardInfo->CSD.MaxRdCurrentVDDMax = (CSD_Tab[8] & 0x07);
      /* Byte 9 */
      SD0_CardInfo->CSD.MaxWrCurrentVDDMin = (CSD_Tab[9] & 0xE0) >> 5;
      SD0_CardInfo->CSD.MaxWrCurrentVDDMax = (CSD_Tab[9] & 0x1C) >> 2;
      SD0_CardInfo->CSD.DeviceSizeMul = (CSD_Tab[9] & 0x03) << 1;
      /* Byte 10 */
      SD0_CardInfo->CSD.DeviceSizeMul |= (CSD_Tab[10] & 0x80) >> 7;
      SD0_CardInfo->CSD.EraseGrSize = (CSD_Tab[10] & 0x7C) >> 2;
      SD0_CardInfo->CSD.EraseGrMul = (CSD_Tab[10] & 0x03) << 3;
      /* Byte 11 */
      SD0_CardInfo->CSD.EraseGrMul |= (CSD_Tab[11] & 0xE0) >> 5;
      SD0_CardInfo->CSD.WrProtectGrSize = (CSD_Tab[11] & 0x1F);
      /* Byte 12 */
      SD0_CardInfo->CSD.WrProtectGrEnable = (CSD_Tab[12] & 0x80) >> 7;
      SD0_CardInfo->CSD.ManDeflECC = (CSD_Tab[12] & 0x60) >> 5;
      SD0_CardInfo->CSD.WrSpeedFact = (CSD_Tab[12] & 0x1C) >> 2;
      SD0_CardInfo->CSD.MaxWrBlockLen = (CSD_Tab[12] & 0x03) << 2;
      /* Byte 13 */
      SD0_CardInfo->CSD.MaxWrBlockLen |= (CSD_Tab[13] & 0xc0) >> 6;
      SD0_CardInfo->CSD.WriteBlockPaPartial = (CSD_Tab[13] & 0x20) >> 5;
      SD0_CardInfo->CSD.Reserved3 = 0;
      SD0_CardInfo->CSD.ContentProtectAppli = (CSD_Tab[13] & 0x01);
      /* Byte 14 */
      SD0_CardInfo->CSD.FileFormatGrouop = (CSD_Tab[14] & 0x80) >> 7;
      SD0_CardInfo->CSD.CopyFlag = (CSD_Tab[14] & 0x40) >> 6;
      SD0_CardInfo->CSD.PermWrProtect = (CSD_Tab[14] & 0x20) >> 5;
      SD0_CardInfo->CSD.TempWrProtect = (CSD_Tab[14] & 0x10) >> 4;
      SD0_CardInfo->CSD.FileFormat = (CSD_Tab[14] & 0x0C) >> 2;
      SD0_CardInfo->CSD.ECC = (CSD_Tab[14] & 0x03);
      /* Byte 15 */
      SD0_CardInfo->CSD.CSD_CRC = (CSD_Tab[15] & 0xFE) >> 1;
      SD0_CardInfo->CSD.Reserved4 = 1;
    
      if(SD0_CardInfo->CardType == V2HC)
      {
    	 /* Byte 7 */
    	 SD0_CardInfo->CSD.DeviceSize = (uint16_t)(CSD_Tab[8]) *256;
    	 /* Byte 8 */
    	 SD0_CardInfo->CSD.DeviceSize += CSD_Tab[9] ;
      }
    
      SD0_CardInfo->Capacity = SD0_CardInfo->CSD.DeviceSize * MSD_BLOCKSIZE * 1024;
      SD0_CardInfo->BlockSize = MSD_BLOCKSIZE;
    
      /* Byte 0 */
      SD0_CardInfo->CID.ManufacturerID = CID_Tab[0];
      /* Byte 1 */
      SD0_CardInfo->CID.OEM_AppliID = CID_Tab[1] << 8;
      /* Byte 2 */
      SD0_CardInfo->CID.OEM_AppliID |= CID_Tab[2];
      /* Byte 3 */
      SD0_CardInfo->CID.ProdName1 = CID_Tab[3] << 24;
      /* Byte 4 */
      SD0_CardInfo->CID.ProdName1 |= CID_Tab[4] << 16;
      /* Byte 5 */
      SD0_CardInfo->CID.ProdName1 |= CID_Tab[5] << 8;
      /* Byte 6 */
      SD0_CardInfo->CID.ProdName1 |= CID_Tab[6];
      /* Byte 7 */
      SD0_CardInfo->CID.ProdName2 = CID_Tab[7];
      /* Byte 8 */
      SD0_CardInfo->CID.ProdRev = CID_Tab[8];
      /* Byte 9 */
      SD0_CardInfo->CID.ProdSN = CID_Tab[9] << 24;
      /* Byte 10 */
      SD0_CardInfo->CID.ProdSN |= CID_Tab[10] << 16;
      /* Byte 11 */
      SD0_CardInfo->CID.ProdSN |= CID_Tab[11] << 8;
      /* Byte 12 */
      SD0_CardInfo->CID.ProdSN |= CID_Tab[12];
      /* Byte 13 */
      SD0_CardInfo->CID.Reserved1 |= (CID_Tab[13] & 0xF0) >> 4;
      /* Byte 14 */
      SD0_CardInfo->CID.ManufactDate = (CID_Tab[13] & 0x0F) << 8;
      /* Byte 15 */
      SD0_CardInfo->CID.ManufactDate |= CID_Tab[14];
      /* Byte 16 */
      SD0_CardInfo->CID.CID_CRC = (CID_Tab[15] & 0xFE) >> 1;
      SD0_CardInfo->CID.Reserved2 = 1;
    
      return 0;  
    }
    
    
    //写SD卡
    //buf:数据缓存区
    //sector:起始扇区
    //cnt:扇区数
    //返回值:0,ok;其他,失败.
    uint8_t SD_WriteDisk(uint8_t*buf,uint32_t sector,uint8_t cnt)
    {
    	uint8_t r1;
    	if(SD_TYPE!=V2HC)sector *= 512;//转换为字节地址
    	if(cnt==1)
    	{
    		r1=SD_sendcmd(CMD24,sector,0X01);//读命令
    		if(r1==0)//指令发送成功
    		{
    			r1=SD_SendBlock(buf,0xFE);//写512个字节	   
    		}
    	}else
    	{
    		if(SD_TYPE!=MMC)
    		{
    			SD_sendcmd(CMD55,0,0X01);	
    			SD_sendcmd(CMD23,cnt,0X01);//发送指令	
    		}
     		r1=SD_sendcmd(CMD25,sector,0X01);//连续读命令
    		if(r1==0)
    		{
    			do
    			{
    				r1=SD_SendBlock(buf,0xFC);//接收512个字节	 
    				buf+=512;  
    			}while(--cnt && r1==0);
    			r1=SD_SendBlock(0,0xFD);//接收512个字节 
    		}
    	}   
    	SD_CS(0);//取消片选
    	return r1;//
    }	
    //读SD卡
    //buf:数据缓存区
    //sector:扇区
    //cnt:扇区数
    //返回值:0,ok;其他,失败.
    uint8_t SD_ReadDisk(uint8_t*buf,uint32_t sector,uint8_t cnt)
    {
    	uint8_t r1;
    	if(SD_TYPE!=V2HC)sector <<= 9;//转换为字节地址
    	if(cnt==1)
    	{
    		r1=SD_sendcmd(CMD17,sector,0X01);//读命令
    		if(r1==0)//指令发送成功
    		{
    			r1=SD_ReceiveData(buf,512);//接收512个字节	   
    		}
    	}else
    	{
    		r1=SD_sendcmd(CMD18,sector,0X01);//连续读命令
    		do
    		{
    			r1=SD_ReceiveData(buf,512);//接收512个字节	 
    			buf+=512;  
    		}while(--cnt && r1==0); 	
    		SD_sendcmd(CMD12,0,0X01);	//发送停止命令
    	}   
    	SD_CS(0);//取消片选
    	return r1;//
    }
    
    
    
    
    uint8_t spi_readwrite(uint8_t Txdata){
    	uint8_t Rxdata;	
    	HAL_SPI_TransmitReceive(&hspi1,&Txdata,&Rxdata,1,100);
    	return Rxdata;
    }
    //SPI1波特率设置
    void SPI_setspeed(uint8_t speed){
    	hspi1.Init.BaudRatePrescaler = speed;
    }
    
    
    void Get_SDCard_Capacity(void)
    {
    	FRESULT result;
    	FATFS FS;
    	FATFS *fs;
    	DWORD fre_clust,AvailableSize,UsedSize;  
    	uint16_t TotalSpace;
    	uint8_t res;
    	
    	res = SD_init();		//SD卡初始化
    	if(res == 1)
    	{
    		printf("SD卡初始化失败! \r\n");		
    	}
    	else
    	{
    		printf("SD卡初始化成功! \r\n");		
    	}
    	
    	/* 挂载 */
    	res=f_mount(&FS,"0:",1);		//挂载
    	if (res != FR_OK)
    	{
    		printf("FileSystem Mounted Failed (%d)\r\n", result);
    	}
    
    	res = f_getfree("0:", &fre_clust, &fs);  /* 根目录 */
    	if ( res == FR_OK ) 
    	{
    		TotalSpace=(uint16_t)(((fs->n_fatent - 2) * fs->csize ) / 2 /1024);
    		AvailableSize=(uint16_t)((fre_clust * fs->csize) / 2 /1024);
    		UsedSize=TotalSpace-AvailableSize;              
    		/* Print free space in unit of MB (assuming 512 bytes/sector) */
    		printf("\r\n%d MB total drive space.\r\n""%d MB available.\r\n""%d MB  used.\r\n",TotalSpace, AvailableSize,UsedSize);
    	}
    	else 
    	{
    		printf("Get SDCard Capacity Failed (%d)\r\n", result);
    	}		
    } 
    
    
    void WritetoSD(char filename[], BYTE write_buff[], uint8_t bufSize)
    {
    	FATFS fs;
    	FIL file;
    	uint8_t res=0;
    	UINT Bw;	
    
    	res = SD_init();		//SD卡初始化
    	
    	if(res == 1)
    	{
    		printf("SD卡初始化失败! \r\n");		
    	}
    	else
    	{
    		printf("SD卡初始化成功! \r\n");		
    	}
    	
    	res=f_mount(&fs,"0:",1);		//挂载
    	
    //	if(test_sd == 0)		//用于测试格式化
    	if(res == FR_NO_FILESYSTEM)		//没有文件系统,格式化
    	{
    //		test_sd =1;				//用于测试格式化
    		printf("没有文件系统! \r\n");		
    		res = f_mkfs("", 0, 0);		//格式化sd卡
    		if(res == FR_OK)
    		{
    			printf("格式化成功! \r\n");		
    			res = f_mount(NULL,"0:",1); 		//格式化后先取消挂载
    			res = f_mount(&fs,"0:",1);			//重新挂载	
    			if(res == FR_OK)
    			{
    				printf("SD卡已经成功挂载,可以进进行文件写入测试!\r\n");
    			}	
    		}
    		else
    		{
    			printf("格式化失败! \r\n");		
    		}
    	}
    	else if(res == FR_OK)
    	{
    		printf("挂载成功! \r\n");		
    	}
    	else
    	{
    		printf("挂载失败! \r\n");
    	}	
    
    	res = f_open(&file,filename,FA_OPEN_ALWAYS |FA_WRITE);
    	if((res & FR_DENIED) == FR_DENIED)
    	{
    		printf("卡存储已满,写入失败!\r\n");		
    	}
    	
    	f_lseek(&file, f_size(&file));//确保写词写入不会覆盖之前的数据
    	if(res == FR_OK)
    	{
    		printf("打开成功/创建文件成功! \r\n");		
    		res = f_write(&file,write_buff,bufSize,&Bw);		//写数据到SD卡
    		if(res == FR_OK)
    		{
    			printf("文件写入成功! \r\n");			
    		}
    		else
    		{
    			printf("文件写入失败! \r\n");
    		}		
    	}
    	else
    	{
    		printf("打开文件失败!\r\n");
    	}	
    	
    	f_close(&file);						//关闭文件		
    	f_mount(NULL,"0:",1);		 //取消挂载
    	
    }
    ///END//
    
    
    

    2、修改CubeMx已生成的代码

    (1)user_diskio.c

    //添加头文件
    #include "diskio.h"		/* Declarations of disk functions */
    #include "sd.h"
    
    //Cubemx会自动生成下面几个函数名,但需要用户自己补全函数实现。拷贝下面几个函数,替换原有函数。
    
    /**
      * @brief  Initializes a Drive
      * @param  pdrv: Physical drive number (0..)
      * @retval DSTATUS: Operation status
      */
    DSTATUS USER_initialize (
    	BYTE pdrv           /* Physical drive nmuber to identify the drive */
    )
    {
      /* USER CODE BEGIN INIT */
      uint8_t res;
    	res = SD_init();//SD_Initialize() 
    		 	if(res)//STM32 SPI的bug,在sd卡操作失败的时候如果不执行下面的语句,可能导致SPI读写异常
    			{
    				SPI_setspeed(SPI_BAUDRATEPRESCALER_256);
    				spi_readwrite(0xff);//提供额外的8个时钟
    				SPI_setspeed(SPI_BAUDRATEPRESCALER_2);
    			}
    	if(res)return  STA_NOINIT;
    	else return RES_OK; //初始化成功
      /* USER CODE END INIT */
    }
    
    /**
      * @brief  Gets Disk Status
      * @param  pdrv: Physical drive number (0..)
      * @retval DSTATUS: Operation status
      */
    DSTATUS USER_status (
    	BYTE pdrv       /* Physical drive number to identify the drive */
    )
    {
      /* USER CODE BEGIN STATUS */
      switch (pdrv)
    	{
    		case 0 :
    			return RES_OK;
    		case 1 :
    			return RES_OK;
    		case 2 :
    			return RES_OK;
    		default:
    			return STA_NOINIT;
    	}
      /* USER CODE END STATUS */
    }
    
    /**
      * @brief  Reads Sector(s)
      * @param  pdrv: Physical drive number (0..)
      * @param  *buff: Data buffer to store read data
      * @param  sector: Sector address (LBA)
      * @param  count: Number of sectors to read (1..128)
      * @retval DRESULT: Operation result
      */
    DRESULT USER_read (
    	BYTE pdrv,      /* Physical drive nmuber to identify the drive */
    	BYTE *buff,     /* Data buffer to store read data */
    	DWORD sector,   /* Sector address in LBA */
    	UINT count      /* Number of sectors to read */
    )
    {
      /* USER CODE BEGIN READ */
      uint8_t res;
    	if( !count )
    	{    
    		return RES_PARERR;  /* count不能等于0,否则返回参数错误 */
    	}
    	switch (pdrv)
    	{
    		case 0:
    		    res=SD_ReadDisk(buff,sector,count);	 
    				if(res == 0){
    					return RES_OK;
    				}else{
    					return RES_ERROR;
    				}                                               
    		default:
    			return RES_ERROR;
    	}
      /* USER CODE END READ */
    }
    
    /**
      * @brief  Writes Sector(s)
      * @param  pdrv: Physical drive number (0..)
      * @param  *buff: Data to be written
      * @param  sector: Sector address (LBA)
      * @param  count: Number of sectors to write (1..128)
      * @retval DRESULT: Operation result
      */
    #if _USE_WRITE == 1
    DRESULT USER_write (
    	BYTE pdrv,          /* Physical drive nmuber to identify the drive */
    	const BYTE *buff,   /* Data to be written */
    	DWORD sector,       /* Sector address in LBA */
    	UINT count          /* Number of sectors to write */
    )
    {
      /* USER CODE BEGIN WRITE */
      /* USER CODE HERE */
      uint8_t  res;
    	if( !count )
    	{    
    		return RES_PARERR;  /* count不能等于0,否则返回参数错误 */
    	}
    	switch (pdrv)
    	{
    		case 0:
    		    res=SD_WriteDisk((uint8_t *)buff,sector,count);
    				if(res == 0){
    					return RES_OK;
    				}else{
    					return RES_ERROR;
    				}                                                
    		default:return RES_ERROR;
    	}
      /* USER CODE END WRITE */
    }
    #endif /* _USE_WRITE == 1 */
    
    /**
      * @brief  I/O control operation
      * @param  pdrv: Physical drive number (0..)
      * @param  cmd: Control code
      * @param  *buff: Buffer to send/receive control data
      * @retval DRESULT: Operation result
      */
    #if _USE_IOCTL == 1
    DRESULT USER_ioctl (
    	BYTE pdrv,      /* Physical drive nmuber (0..) */
    	BYTE cmd,       /* Control code */
    	void *buff      /* Buffer to send/receive control data */
    )
    {
      /* USER CODE BEGIN IOCTL */
        DRESULT res;
    	 switch(cmd)
    	    {
    		    case CTRL_SYNC:
    						SD_CS(1);
    						do{
    							HAL_Delay(20);
    						}while(spi_readwrite(0xFF)!=0xFF);
    						res=RES_OK;
    						SD_CS(0);
    		        break;	 
    		    case GET_SECTOR_SIZE:
    		        *(WORD*)buff = 512;
    		        res = RES_OK;
    		        break;	 
    		    case GET_BLOCK_SIZE:
    		        *(WORD*)buff = 8;
    		        res = RES_OK;
    		        break;	 
    		    case GET_SECTOR_COUNT:
    		        *(DWORD*)buff = SD_GetSectorCount();
    		        res = RES_OK;
    		        break;
    		    default:
    		        res = RES_PARERR;
    		        break;
    	    }
    		return res;
      /* USER CODE END IOCTL */
    }
    #endif /* _USE_IOCTL == 1 */
    

    (2)main.h

    #include <stdio.h>		/* 添加头文件 */
    
    
    int fputc(int ch, FILE *f);		/* 声明fputc,方便使用printf函数 */
    int fgetc(FILE *f);
    
    

    (3)main.c

    /* 添加头文件 */
    #include <stdio.h>
    #include "sd.h"
    
    char SD_FileName[] = "hello.txt";
    uint8_t WriteBuffer[] = "01 write buff to sd \r\n";
    uint8_t write_cnt =0;	//写SD卡次数
    
    int main(void)
    {
      HAL_Init();
      SystemClock_Config();
      MX_GPIO_Init();
      MX_USART1_UART_Init();
      MX_SPI1_Init();
      MX_FATFS_Init();
      
      Get_SDCard_Capacity();	//得到使用内存并选择格式化
    
      while (1)
      {
    		HAL_Delay(500);
    		WritetoSD(SD_FileName, WriteBuffer,sizeof(WriteBuffer));	
    		HAL_Delay(500);
    		write_cnt ++;
    		
    		while(write_cnt > 5)
    		{	
    			printf(" while \r\n");
    			HAL_Delay(500);
    		}
      }
    }
    
    
    

    四、硬件连接

    STM32开发板 SD模块
    5V VCC
    PA4 CS
    PA6 MISO
    GND GND
    PA5 SCK
    PA7 MOSI

    五、运行效果

  • 烧录后串口显示打印初始化、挂载、写入成功
  • 将SD卡插入读卡器
  • 六、遇到的问题

    1、我使用的是SD卡模块调试的,模块要用5V供电,否则跑不起来
    2、要配置栈的大小,FATFS系统比较吃RAM空间,要把栈空间改大一点(CubeMx配置的第八步)

    作者:Lin201230

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32 + CubeMX + 硬件SPI + SD卡 + FATFS

    发表回复