STM32存储左右互搏 SPI总线FATS文件读写SD/MicroSD/TF卡

STM32存储左右互搏 SPI总线FATS文件读写SD/MicroSD/TF卡

SD/MicroSD/TF卡是基于FLASH的一种常见非易失存储单元,由接口协议电路和FLASH构成。市面上由不同尺寸和不同容量的卡,手机领域用的TF卡实际就是MicroSD卡,尺寸比SD卡小,而电路和协议操作则是一样。这里介绍STM32CUBEIDE开发平台HAL库SPI总线FATS文件操作读写SD/MicroSD/TF卡的例程。

除了在硬件板子上集成SD/MicroSD/TF卡插槽的方式,也可以使用SD/MicroSD/TF卡模块,如下图所示为其中一种(支持MicroSD/TF卡):
在这里插入图片描述

SD/MicroSD/TF卡访问接口

SD/MicroSD/TF卡可以通过访问更快的SDIO专用协议接口或是访问慢一些的普通SPI接口进行操作,两种协议接口复用管脚。通过SPI接口进行操作,上面介绍的模块的接口连接特性如下:
在这里插入图片描述
共6个引脚(GND、VCC、MISO、MOSI、SCK、CS)与标准SPI接口对应。除了供电为5V,通讯管脚的电平由于模块内部进行了转换,直接和STM32的一个SPI接口连接即可。
在这里插入图片描述
如果不采用模块,直接集成卡槽使用,SDIO协议管脚和SPI协议管脚的复用关系如下:
在这里插入图片描述

例程采用STM32F401CCU6芯片(兼容STM32F401RCT6, 仅封装不同)对4GB的TF卡进行操作

STM32工程配置

首先建立基本工程并设置时钟:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
配置SPI1:
在这里插入图片描述
在这里插入图片描述
不配置DMA:
在这里插入图片描述
选择PA4管脚为软件代码控制的片选,单独配置为GPIO输出, 需要注意,SPI FATS访问SD/MicroSD/TF卡必须采用PA4作为软件片选的管脚,采用其它管脚如PA0/PA1/PA15等作为SP1的软件片选时,读文件操作正常,而写文件操作在写成功后STM32会挂死,原因未明,芯片内部机制和FATS库配合存在潜在的问题,需要规避。在采用SPI2时,也要采用PA4作为软件片选的管脚。
在这里插入图片描述
在这里插入图片描述
配置FATAS参数:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
配置UART1做为通讯口:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
DMA不配置:
在这里插入图片描述
保存并生成初始代码:
在这里插入图片描述

STM32工程代码

UART串口printf打印输出实现参考:STM32 UART串口printf函数应用及浮点打印代码空间节省 (HAL)

建立SPI操作SD/MicroSD/TF卡库头文件SDdriver.h:

#include "main.h"
/*
CID	128bit	卡标识号;
RCA	16bit	相对卡地址(Relative card address):本地系统中卡的地址,动态变化。在主机初始的时候确定。SPI模式中没有;
CSD	128bit	卡描述数据:卡操作条件相关的信息数据;
SCR	64bit	SD配置寄存器:SD卡特定信息数据;
OCR	32bit	操作条件寄存器。
*/


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;

int             SD_RST(void);
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(uint32_t speed);
uint8_t spi_readwrite(uint8_t Txdata);

建立SPI操作SD/MicroSD/TF卡库头文件SDdriver.c:

#include "SDdriver.h"

extern SPI_HandleTypeDef hspi1;
extern void PY_Delay_us_t(uint32_t Delay);

uint8_t SD_TYPE=0x00;
MSD_CARDINFO SD0_CardInfo;

#define SD_CS_Pin GPIO_PIN_4
#define SD_CS_GPIO_Port GPIOA
#define SD_CS_EN HAL_GPIO_WritePin(SD_CS_GPIO_Port,SD_CS_Pin,GPIO_PIN_RESET)
#define SD_CS_DEN HAL_GPIO_WritePin(SD_CS_GPIO_Port,SD_CS_Pin,GPIO_PIN_SET)

int SD_RST(void)
{
  uint8_t rst;

  SD_CS_DEN;
  PY_Delay_us_t(20000);
  SD_CS_EN;
  PY_Delay_us_t(1);
  spi_readwrite(CMD0 | 0x40);
  spi_readwrite(0 >> 24);
  spi_readwrite(0 >> 16);
  spi_readwrite(0 >> 8);
  spi_readwrite(0);
  spi_readwrite(0x95);

  for(uint32_t i=0; i<1000; i++)
  {
	  rst=spi_readwrite(0xFF);
	  if((rst&0X80)==0) break;
	  PY_Delay_us_t(100);
  }
      if((rst&0X80)==0) return 0x01;
      else return 0;
}

int SD_sendcmd(uint8_t cmd,uint32_t arg,uint8_t crc)
{
  uint8_t rst;
  uint8_t idle;

  SD_CS_DEN;
  PY_Delay_us_t(20000);
  SD_CS_EN;
  PY_Delay_us_t(1);
  do{
		idle=spi_readwrite(0xFF);
		PY_Delay_us_t(1);
  }while(idle!=0xFF); //Check SD idle status

  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(0xFF); //Stop data transmission

  do{
		rst=spi_readwrite(0xFF);
		PY_Delay_us_t(1);
  }while(rst&0x80);

  return rst;
}
/////////////////////////////////////////////////////////////
//SD卡初始化
////////////////////////////////////////////////////////////
uint8_t SD_init(void)
{
	uint8_t rst;
	uint8_t buff[6] = {0};
	uint16_t retry; 
	uint8_t i;

	SPI_setspeed(SPI_BAUDRATEPRESCALER_256);
	SD_CS_DEN;
	PY_Delay_us_t(1);
	for(retry=0;retry<10;retry++) //向总线最少发送74个脉冲,为了让SD卡正常启动 (唤醒SD卡)
	{
		spi_readwrite(0xFF);
	};
    //发送新的命令之前,需要取消之前的片选,额外发多 8个 CLK (发送0xFF无效数据),结束之前的操作。

	//SD卡进入IDLE状态
	do{
		rst = SD_RST();
		PY_Delay_us_t(1);
	}while(rst!=0x01);

	//查看SD卡的类型
	SD_TYPE=0;
	rst = SD_sendcmd(CMD8, 0x1AA, 0x87);

	if(rst==0x01)
	{
		for(i=0;i<4;i++) buff[i]=spi_readwrite(0xFF);	//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
				rst=SD_sendcmd(CMD41,0x40000000,0X01);//发送CMD41
			}while(rst&&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
			rst=SD_sendcmd(CMD41,0,0X01);	//发送CMD41
			if(rst<=1)
			{
				SD_TYPE=V1;
				retry=0XFFFE;
				do //等待退出IDLE模式
				{
					SD_sendcmd(CMD55,0,0X01);	//发送CMD55
					rst=SD_sendcmd(CMD41,0,0X01);//发送CMD41
				}while(rst&&retry--);
			}else//MMC卡不支持CMD55+CMD41识别
			{
				SD_TYPE=MMC;//MMC V3
				retry=0XFFFE;
				do //等待退出IDLE模式
				{											    
					rst=SD_sendcmd(CMD1,0,0X01);//发送CMD1
				}while(rst&&retry--);
			}
			if(retry==0||SD_sendcmd(CMD16,512,0X01)!=0)SD_TYPE=ERR;//错误的卡
		}
	}
	SD_CS_DEN;
	SPI_setspeed(SPI_BAUDRATEPRESCALER_2);
    return SD_TYPE;
}

//读取指定长度数据
uint8_t SD_ReceiveData(uint8_t *data, uint16_t len)
{
   uint8_t rst;
   SD_CS_EN;
   do
   { 
      rst = spi_readwrite(0xFF);
      PY_Delay_us_t(100);
	}while(rst != 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 rst;
	do{
		rst=spi_readwrite(0xFF);
	}while(rst!=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 rst;
	    rst=SD_sendcmd(CMD10,0,0x01); //读取CID寄存器
		if(rst==0x00)
		{
			rst=SD_ReceiveData(cid_data,16);
		}
		SD_CS_DEN;
		if(rst)return 1;
		else return 0;
}
//获取CSD信息
uint8_t SD_GETCSD(uint8_t *csd_data){
		uint8_t rst;
        rst=SD_sendcmd(CMD9,0,0x01);//发CMD9命令,读CSD寄存器
        if(rst==0)
	    {
    	   rst=SD_ReceiveData(csd_data, 16);//接收16个字节的数据
        }
	    SD_CS_DEN;//取消片选
	    if(rst)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 rst;
  uint8_t CSD_Tab[16];
  uint8_t CID_Tab[16];

  /* Send CMD9, Read CSD */
  rst = SD_sendcmd(CMD9, 0, 0xFF);
  if(rst != 0x00)
  {
    return rst;
  }

  if(SD_ReceiveData(CSD_Tab, 16))
  {
	return 1;
  }

  /* Send CMD10, Read CID */
  rst = SD_sendcmd(CMD10, 0, 0xFF);
  if(rst != 0x00)
  {
    return rst;
  }

  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 rst;
	if(SD_TYPE!=V2HC) sector *= 512;//转换为字节地址
	if(cnt==1)
	{
		rst=SD_sendcmd(CMD24,sector,0X01);//读命令
		if(rst==0)//指令发送成功
		{
			rst=SD_SendBlock(buf,0xFE);//写512个字节
		}
	}
	else
	{
		if(SD_TYPE!=MMC)
		{
			SD_sendcmd(CMD55,0,0X01);	
			SD_sendcmd(CMD23,cnt,0X01);//发送指令
		}
 		rst=SD_sendcmd(CMD25,sector,0X01);//连续读命令
		if(rst==0)
		{
			do
			{
				rst=SD_SendBlock(buf,0xFC);//接收512个字节
				buf+=512;  
			}while(--cnt && rst==0);
			rst=SD_SendBlock(0,0xFD);//接收512个字节
		}
	}   
	SD_CS_DEN;//取消片选
	return rst;//
}	
//读SD卡
//buf:数据缓存区
//sector:扇区
//cnt:扇区数
//返回值:0,ok;其他,失败.
uint8_t SD_ReadDisk(uint8_t*buf,uint32_t sector,uint8_t cnt)
{
	uint8_t rst;
	if(SD_TYPE!=V2HC)sector <<= 9;//转换为字节地址
	if(cnt==1)
	{
		rst=SD_sendcmd(CMD17,sector,0X01);//读命令
		if(rst==0)//指令发送成功
		{
			rst=SD_ReceiveData(buf,512);//接收512个字节
		}
	}
	else
	{
		rst=SD_sendcmd(CMD18,sector,0X01);//连续读命令
		do
		{
			rst=SD_ReceiveData(buf,512);//接收512个字节
			buf+=512;  
		}while(--cnt && rst==0);
		SD_sendcmd(CMD12,0,0X01);	//发送停止命令
	}   
	SD_CS_DEN;//取消片选
	return rst;//
}

uint8_t spi_readwrite(uint8_t Txdata)
{
	uint8_t rd = 0xa5;
	uint8_t td = Txdata;
	HAL_SPI_TransmitReceive(&hspi1, &td, &rd, 1 ,2700);
	return rd;
}
//SPI1波特率设置
void SPI_setspeed(uint32_t speed)
{
	hspi1.Init.BaudRatePrescaler = speed;
}


对ffconf.h添加包含信息:
在这里插入图片描述

#include "main.h"
#include "stm32f4xx_hal.h"

修改user_diskio.c,对文件操作函数与底层SD/MicroSD/TF卡读写提供连接:

/* USER CODE BEGIN Header */
/**
 ******************************************************************************
  * @file    user_diskio.c
  * @brief   This file includes a diskio driver skeleton to be completed by the user.
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2023 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
 /* USER CODE END Header */

#ifdef USE_OBSOLETE_USER_CODE_SECTION_0
/*
 * Warning: the user section 0 is no more in use (starting from CubeMx version 4.16.0)
 * To be suppressed in the future.
 * Kept to ensure backward compatibility with previous CubeMx versions when
 * migrating projects.
 * User code previously added there should be copied in the new user sections before
 * the section contents can be deleted.
 */
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
#endif

/* USER CODE BEGIN DECL */
/**************************s*******************************/
#include "diskio.h"		/* Declarations of disk functions */
#include "SDdriver.h"
/**************************e*******************************/
/* Includes ------------------------------------------------------------------*/
#include <string.h>
#include "ff_gen_drv.h"
/**************************s*******************************/
#define SD_CS_Pin GPIO_PIN_4
#define SD_CS_GPIO_Port GPIOA
#define SD_CS_EN HAL_GPIO_WritePin(SD_CS_GPIO_Port,SD_CS_Pin,GPIO_PIN_RESET)
#define SD_CS_DEN HAL_GPIO_WritePin(SD_CS_GPIO_Port,SD_CS_Pin,GPIO_PIN_SET)
/**************************e*******************************/
/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/

/* Private variables ---------------------------------------------------------*/
/* Disk status */
static volatile DSTATUS Stat = STA_NOINIT;

/* USER CODE END DECL */

/* Private function prototypes -----------------------------------------------*/
DSTATUS USER_initialize (BYTE pdrv);
DSTATUS USER_status (BYTE pdrv);
DRESULT USER_read (BYTE pdrv, BYTE *buff, DWORD sector, UINT count);
#if _USE_WRITE == 1
  DRESULT USER_write (BYTE pdrv, const BYTE *buff, DWORD sector, UINT count);
#endif /* _USE_WRITE == 1 */
#if _USE_IOCTL == 1
  DRESULT USER_ioctl (BYTE pdrv, BYTE cmd, void *buff);
#endif /* _USE_IOCTL == 1 */

Diskio_drvTypeDef  USER_Driver =
{
  USER_initialize,
  USER_status,
  USER_read,
#if  _USE_WRITE
  USER_write,
#endif  /* _USE_WRITE == 1 */
#if  _USE_IOCTL == 1
  USER_ioctl,
#endif /* _USE_IOCTL == 1 */
};

/* Private functions ---------------------------------------------------------*/

/**
  * @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 */
/**************************s*******************************/
	  uint8_t res;
	  res = SD_init();//SD_Initialize()

	  if(res) return RES_OK;
	  else return  STA_NOINIT;
/**************************e*******************************/
	/*
    Stat = STA_NOINIT;
    return Stat;
    */
  /* 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 */
/**************************s*******************************/
	switch (pdrv)
		{
			case 0 :
				return RES_OK;
			case 1 :
				return RES_OK;
			case 2 :
				return RES_OK;
			default:
				return STA_NOINIT;
		}
/**************************e*******************************/
	/*
    Stat = STA_NOINIT;
    return Stat;
    */
  /* 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 */
/**************************s*******************************/
	    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;
		}
/**************************e*******************************/
	/*
    return RES_OK;
    */
  /* 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 */
/**************************s*******************************/
	    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;
		}
/**************************e*******************************/
	/*
    return RES_OK;
    */
  /* 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 */
/**************************s*******************************/
	     DRESULT res;
		 switch(cmd)
		    {
			    case CTRL_SYNC:
							SD_CS_EN;
							do{
								HAL_Delay(20);
							}while(spi_readwrite(0xFF)!=0xFF);
							res=RES_OK;
							SD_CS_DEN;
			        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;
/**************************e*******************************/
	/*
    DRESULT res = RES_ERROR;
    return res;
    */
  /* USER CODE END IOCTL */
}
#endif /* _USE_IOCTL == 1 */


注意这里设置了卡大小为4GB,可以根据实际情况修改:
在这里插入图片描述
代码实现在main.c文件里,实现如下功能:

  1. 串口收到0x01指令,初始化SD/MicroSD/TF卡
  2. 串口收到0x02指令,装载FATS文件操作系统
  3. 串口收到0x03指令,识别容量,此操作慢
  4. 串口收到0x04指令,创建/打开文件并从头位置写入数据
  5. 串口收到0x05指令,打开文件并从头位置读入数据
  6. 串口收到0x06指令,创建/打开文件并从特定位置写入数据
  7. 串口收到0x07指令,打开文件并从特定位置读入数据
  8. 串口收到0x08指令,读取CID信息
  9. 串口收到0x09指令,读取CSD信息

完整main.c代码如下:

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2023 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "fatfs.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "usart.h"
#include "SDdriver.h"
#include "string.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
__IO float usDelayBase;
void PY_usDelayTest(void)
{
  __IO uint32_t firstms, secondms;
  __IO uint32_t counter = 0;

  firstms = HAL_GetTick()+1;
  secondms = firstms+1;

  while(uwTick!=firstms) ;

  while(uwTick!=secondms) counter++;

  usDelayBase = ((float)counter)/1000;
}

void PY_Delay_us_t(uint32_t Delay)
{
  __IO uint32_t delayReg;
  __IO uint32_t usNum = (uint32_t)(Delay*usDelayBase);

  delayReg = 0;
  while(delayReg!=usNum) delayReg++;
}

void PY_usDelayOptimize(void)
{
  __IO uint32_t firstms, secondms;
  __IO float coe = 1.0;

  firstms = HAL_GetTick();
  PY_Delay_us_t(1000000) ;
  secondms = HAL_GetTick();

  coe = ((float)1000)/(secondms-firstms);
  usDelayBase = coe*usDelayBase;
}


void PY_Delay_us(uint32_t Delay)
{
  __IO uint32_t delayReg;

  __IO uint32_t msNum = Delay/1000;
  __IO uint32_t usNum = (uint32_t)((Delay%1000)*usDelayBase);

  if(msNum>0) HAL_Delay(msNum);

  delayReg = 0;
  while(delayReg!=usNum) delayReg++;
}
/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
SPI_HandleTypeDef hspi1;

UART_HandleTypeDef huart1;

/* USER CODE BEGIN PV */
uint8_t uart1_rx[16];
uint8_t cmd;
uint8_t SD_Status = 0; //SD initialization status (0: none; 1: OK)
uint8_t SD_mount_status = 0; //SD fats mount status indication (0: unmount; 1: mount)
uint8_t FATS_Buff[_MAX_SS]; //Buffer for f_mkfs() operation

FRESULT retSD;
FIL file;
FATFS *fs;
DWORD fre_clust, AvailableSize, UsedSize;
uint16_t TotalSpace;

UINT bytesread;
UINT byteswritten;
uint8_t rBuffer[20];      //Buffer for read
uint8_t WBuffer[20] ={1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20}; //Buffer for write

extern char USERPath[4];
/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_SPI1_Init(void);
static void MX_USART1_UART_Init(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
#define sector_byte_size 512
uint8_t sdbuffer[sector_byte_size];
uint8_t sdinfo[16];
/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */
	SD_mount_status = 0;
	uint32_t SD_Read_Size;

	char * dpath = "0:"; //Disk Path
    for(uint8_t i=0; i<4; i++)
    {
	  USERPath[i] = *(dpath+i);
    }

	const TCHAR* filepath = "0:test.txt";
  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_SPI1_Init();
  MX_USART1_UART_Init();
  MX_FATFS_Init();
  /* USER CODE BEGIN 2 */
  PY_usDelayTest();
  PY_usDelayOptimize();

  HAL_UART_Receive_IT(&huart1, uart1_rx, 1);

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
     if(cmd==1) //SD Init
     {
    	 cmd = 0;

    	 SD_Status = SD_init();
    	 if(SD_Status == 0) printf("\r\nSD initial failure\r\n");
    	 else
    	 {
        	 printf("\r\nSD type number: %d\r\n", SD_Status);
        	 printf("MMC: 1\r\n");
        	 printf("V1: 2\r\n");
        	 printf("V2: 4\r\n");
        	 printf("V2HC: 6\r\n");

    	 }


     }

     else if(cmd==2) //SD File System Mount
     {
    	 cmd = 0;

    	 if(SD_Status == 0) printf("\r\nSD initial failure\r\n");
    	 else
    	 {
    		 retSD=f_mount(&USERFatFS, (TCHAR const*)USERPath, 1);
    		 if (retSD != FR_OK)
    		 {
    		   printf("File system mount failure: %d\r\n", retSD);

    		   if(retSD==FR_NO_FILESYSTEM)
    		   {
    			   printf("No file system. Now to format......\r\n");
    			   retSD = f_mkfs((TCHAR const*)USERPath, FM_FAT, 1024, FATS_Buff, sizeof(FATS_Buff)); //SD formatting
    			   if(retSD == FR_OK)
    			   {
                      printf("SD formatting success!\r\n");
    			   }
    				else
    			   {
    					printf("SD formatting failure!\r\n");
    			   }

    		   }
    		 }
    		 else
    		 {
    			 SD_mount_status = 1;
    			 printf("File system mount success\r\n");
    		 }
    	 }


     }

     else if(cmd==3) //SD capacity recognition
     {
    	 cmd = 0;

    	 if(SD_mount_status==0) printf("\r\nSD File system not mounted: %d\r\n",retSD);
    	 else
    	 {
			    printf("Executing capacity recognition...It's slow...Waiting for minutes...\r\n");
				retSD = f_getfree((TCHAR const*)USERPath, &fre_clust, &fs);  //root directory

				if ( retSD == 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",(int)TotalSpace, (int)AvailableSize, (int)UsedSize);
				}
				else
				{
					printf("Get SDCard Capacity Failed (%d)\r\n", retSD);
				}

    	 }
     }

	 else if(cmd==4) //File creation and write
	 {
			  cmd = 0;

			  if(SD_mount_status==0) printf("\r\nSD File system not mounted: %d\r\n",retSD);
			  else
			  {
					retSD = f_open( &file, filepath, FA_CREATE_ALWAYS | FA_WRITE );  //Open or create file
					if(retSD == FR_OK)
					{
						printf("\r\nFile open or creation successful\r\n");

						retSD = f_write( &file, (const void *)WBuffer, sizeof(WBuffer), &byteswritten); //Write data

						if(retSD == FR_OK)
						{
							printf("\r\nFile write successful\r\n");

						}
						else
						{
							printf("\r\nFile write error: %d\r\n",retSD);
						}

						f_close(&file);   //Close file
					}
					else
					{
						printf("\r\nFile open or creation error %d\r\n",retSD);
					}
			   }

    }

    else if(cmd==5) //File read
    {
			  cmd = 0;

			  if(SD_mount_status==0) printf("\r\nSD File system not mounted: %d\r\n",retSD);
			  else
			  {
					retSD = f_open( &file, filepath, FA_OPEN_EXISTING | FA_READ); //Open file
					if(retSD == FR_OK)
					{
						printf("\r\nFile open successful\r\n");

						retSD = f_read( &file, (void *)rBuffer, sizeof(rBuffer), &bytesread); //Read data

						if(retSD == FR_OK)
						{
							printf("\r\nFile read successful\r\n");
							PY_Delay_us_t(200000);

							SD_Read_Size = sizeof(rBuffer);
							for(uint16_t i = 0;i < SD_Read_Size;i++)
							{
								printf("%d ", rBuffer[i]);
							}
							printf("\r\n");

						}
						else
						{
							printf("\r\nFile read error: %d\r\n", retSD);
						}
						f_close(&file); //Close file
					}
					else
					{
						printf("\r\nFile open error: %d\r\n", retSD);
					}
			  }

	}

	else if(cmd==6) //File locating write
    {
			  cmd = 0;

			  if(SD_mount_status==0) printf("\r\nSD File system not mounted: %d\r\n",retSD);
			  else
			  {
					retSD = f_open( &file, filepath, FA_CREATE_ALWAYS | FA_WRITE);  //Open or create file
					if(retSD == FR_OK)
					{
						printf("\r\nFile open or creation successful\r\n");

						retSD=f_lseek( &file, f_tell(&file) + sizeof(WBuffer) ); //move file operation pointer, f_tell(&file) gets file head locating

						if(retSD == FR_OK)
						{

							retSD = f_write( &file, (const void *)WBuffer, sizeof(WBuffer), &byteswritten);
							if(retSD == FR_OK)
							{
								printf("\r\nFile locating write successful\r\n");
							}
							else
							{
								printf("\r\nFile locating write error: %d\r\n", retSD);
							}

						}
						else
						{
							printf("\r\nFile pointer error: %d\r\n",retSD);
						}

						f_close(&file);   //Close file
					}
					else
					{
						printf("\r\nFile open or creation error %d\r\n",retSD);
					}
			  }
	}

    else if(cmd==7) //File locating read
	{
			  cmd = 0;

			  if(SD_mount_status==0) printf("\r\nSD File system not mounted: %d\r\n",retSD);
			  else
			  {
					retSD = f_open(&file, filepath, FA_OPEN_EXISTING | FA_READ); //Open file
					if(retSD == FR_OK)
					{
						printf("\r\nFile open successful\r\n");

						retSD =  f_lseek(&file,f_tell(&file)+ sizeof(WBuffer)/2); //move file operation pointer, f_tell(&file) gets file head locating

						if(retSD == FR_OK)
						{
							retSD = f_read( &file, (void *)rBuffer, sizeof(rBuffer), &bytesread);
							if(retSD == FR_OK)
							{
								printf("\r\nFile locating read successful\r\n");
								PY_Delay_us_t(200000);

								SD_Read_Size = sizeof(rBuffer);
								for(uint16_t i = 0;i < SD_Read_Size;i++)
								{
									printf("%d ",rBuffer[i]);
								}
								printf("\r\n");
							}
							else
							{
								printf("\r\nFile locating read error: %d\r\n",retSD);
							}
						}
						else
						{
							printf("\r\nFile pointer error: %d\r\n",retSD);
						}
						f_close(&file);
					}
					else
					{
						printf("\r\nFile open error: %d\r\n",retSD);
					}
			  }
     }


	 else if(cmd==8) //Get CID
     {
    	 cmd = 0;

    	 if(SD_Status == 0) printf("\r\nSD initial failure\r\n");
    	 else
    	 {
        	 if(SD_GETCID((uint8_t *)sdinfo)==0)
        	 {
        		printf("CID: ");
        		for(uint32_t i=0; i<16; i++)
        		{
        			printf("%.2x ", sdinfo[i]);
        		}
        		printf("\r\n");
        	 }
    	 }

     }

	 else if(cmd==9) //Get CSD
     {
    	 cmd = 0;

    	 if(SD_Status == 0) printf("\r\nSD initial failure\r\n");
    	 else
    	 {
        	 if(SD_GETCSD((uint8_t *)sdinfo)==0)
        	 {
        		printf("CSD: ");
        		for(uint32_t i=0; i<16; i++)
        		{
        			printf("%.2x ", sdinfo[i]);
        		}
        		printf("\r\n");
        	 }

    	 }

     }

	 else;

    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Configure the main internal regulator output voltage
  */
  __HAL_RCC_PWR_CLK_ENABLE();
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE2);

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLM = 25;
  RCC_OscInitStruct.PLL.PLLN = 336;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV4;
  RCC_OscInitStruct.PLL.PLLQ = 7;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }
}

/**
  * @brief SPI1 Initialization Function
  * @param None
  * @retval None
  */
static void MX_SPI1_Init(void)
{

  /* USER CODE BEGIN SPI1_Init 0 */

  /* USER CODE END SPI1_Init 0 */

  /* USER CODE BEGIN SPI1_Init 1 */

  /* USER CODE END SPI1_Init 1 */
  /* SPI1 parameter configuration*/
  hspi1.Instance = SPI1;
  hspi1.Init.Mode = SPI_MODE_MASTER;
  hspi1.Init.Direction = SPI_DIRECTION_2LINES;
  hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
  hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
  hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
  hspi1.Init.NSS = SPI_NSS_SOFT;
  hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4;
  hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
  hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
  hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
  hspi1.Init.CRCPolynomial = 10;
  if (HAL_SPI_Init(&hspi1) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN SPI1_Init 2 */

  /* USER CODE END SPI1_Init 2 */

}

/**
  * @brief USART1 Initialization Function
  * @param None
  * @retval None
  */
static void MX_USART1_UART_Init(void)
{

  /* USER CODE BEGIN USART1_Init 0 */

  /* USER CODE END USART1_Init 0 */

  /* USER CODE BEGIN USART1_Init 1 */

  /* USER CODE END USART1_Init 1 */
  huart1.Instance = USART1;
  huart1.Init.BaudRate = 115200;
  huart1.Init.WordLength = UART_WORDLENGTH_8B;
  huart1.Init.StopBits = UART_STOPBITS_1;
  huart1.Init.Parity = UART_PARITY_NONE;
  huart1.Init.Mode = UART_MODE_TX_RX;
  huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart1.Init.OverSampling = UART_OVERSAMPLING_16;
  if (HAL_UART_Init(&huart1) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN USART1_Init 2 */

  /* USER CODE END USART1_Init 2 */

}

/**
  * @brief GPIO Initialization Function
  * @param None
  * @retval None
  */
static void MX_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOH_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, GPIO_PIN_SET);

  /*Configure GPIO pin : SPI1_CS_Pin */
  GPIO_InitStruct.Pin = SPI1_CS_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
  HAL_GPIO_Init(SPI1_CS_GPIO_Port, &GPIO_InitStruct);

}

/* USER CODE BEGIN 4 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	if(huart==&huart1)
	{
		cmd = uart1_rx[0];
		HAL_UART_Receive_IT(&huart1, uart1_rx, 1);
	}

}
/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */


STM32例程测试

串口指令0x01测试效果如下:
在这里插入图片描述
串口指令0x02测试效果如下:
在这里插入图片描述

串口指令0x03测试效果如下:
在这里插入图片描述

串口指令0x04测试效果如下:
在这里插入图片描述

串口指令0x05测试效果如下:
在这里插入图片描述

串口指令0x06测试效果如下:
在这里插入图片描述

串口指令0x07测试效果如下:
在这里插入图片描述

串口指令0x08测试效果如下:
在这里插入图片描述

串口指令0x09测试效果如下:
在这里插入图片描述

STM32例程下载

STM32F401CCU6 SPI总线FATS读写SD/MicroSD/TF卡例程下载

–End–

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/488325.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SQLite数据库文件损坏的可能几种情况(一)

返回&#xff1a;SQLite—系列文章目录 上一篇&#xff1a;SQLiteC/C接口详细介绍sqlite3_stmt类&#xff08;十三&#xff09; 下一篇&#xff1a;SQLite使用的临时文件&#xff08;二&#xff09; 概述 SQLite数据库具有很强的抗损坏能力。如果应用程序崩溃&#xff0c…

Excel·VBA数组平均分组问题

看到一个帖子《excel吧-数据分组问题》&#xff0c;对一组数据分成4组&#xff0c;使每组的和值相近 上一篇文章《ExcelVBA数组分组问题》&#xff0c;解决了这个帖子问题的第1步&#xff0c;即获取所有数组分组形式的问题 接下来要获取分组和值最相近的一组&#xff0c;只需计…

Docker 搭建Redis集群

目录 1. 3主3从架构说明 2. 3主3从Redis集群配置 2.1关闭防火墙启动docker后台服务 2.2 新建6个docker容器实例 2.3 进去任意一台redis容器&#xff0c;为6台机器构建集群关系 2.4 进去6381&#xff0c;查看集群状态 3. 主从容错切换迁移 3.1 数据读写存储 3.1.1 查看…

【代驾+顺风车+货运】全开源双端APP代驾+顺风车+货运代驾小程序源码

内容目录 一、详细介绍二、效果展示1.部分代码2.效果图展示 一、详细介绍 系统是基于Thinkphpuniapp开发的&#xff0c;全开源未加密&#xff0c;这套源码可以拿回去自己做二开 后台用户端司机端 功能详情介绍&#xff1a; 车主实名认证&#xff0c;驾驶证认证&#xff0c;车…

【Spring】IoCDI详解

1. IoC详解 前面提到过IoC就是将对象的控制权交由Spring的IoC容器进行管理&#xff0c;由Spring的IoC容器创建和销毁bean&#xff0c;那么既然涉及到容器&#xff0c;就一定包含以下两方面功能&#xff1a; bean的存储bean的获取 1.1 类注解 Spring框架为了更好地服务应用程…

GIT开发中的使用

GIT 什么是Git&#xff1f; Git是一个版本控制器&#xff1a;可以记录工程的每一次改动和版本迭代的一个管理系统 注意事项&#xff1a; 所有的版本控制系统&#xff0c;其实只能跟踪文本文件的改动&#xff08;如TXT文件、网页、所有的程序代码等&#xff09;&#xff0c;…

数据库系统概论(超详解!!!) 第四节 关系数据库标准语言SQL(Ⅱ)

1.数据查询 SELECT [ ALL | DISTINCT] <目标列表达式>[&#xff0c;<目标列表达式>] … FROM <表名或视图名>[&#xff0c; <表名或视图名> ] … [ WHERE <条件表达式> ] [ GROUP BY <列名1> [ HAVING <条件表达式> ] ] [ ORDER BY…

Mac 装 虚拟机 vmware、centos7等

vmware&#xff1a; https://www.vmware.com/products/fusion.html centos7 清华镜像&#xff1a; 暂时没有官方的 m1 arm架构镜像 centos7 链接: https://pan.baidu.com/s/1oZw1cLyl6Uo3lAD2_FqfEw?pwdzjt4 提取码: zjt4 复制这段内容后打开百度网盘手机App&#xff0c;操…

2015年认证杯SPSSPRO杯数学建模C题(第二阶段)荒漠区动植物关系的研究全过程文档及程序

2015年认证杯SPSSPRO杯数学建模 C题 荒漠区动植物关系的研究 原题再现&#xff1a; 环境与发展是当今世界所普遍关注的重大问题, 随着全球与区域经济的迅猛发展, 人类也正以前所未有的规模和强度影响着环境、改变着环境, 使全球的生命支持系统受到了严重创伤, 出现了全球变暖…

生物信息学 GO、KEGG

文章目录 北大基因本体论分子通路KEGGGO注释分子通路鉴定 关于同源 相似性 b站链接&#xff1a;北大课程 概述了当前生物信息学领域中几个重要的概念和工具&#xff0c;介绍基因本体论&#xff08;Gene Ontology, GO&#xff09;、分子通路知识库KEGG&#xff08;Kyoto Encyclo…

Redis进阶

缓存雪崩 缓存穿透 缓存击穿 Redis在项目中常用作缓存来使用&#xff0c;主要用两大作用&#xff1a; 1.提升系统的性能 Redis基于内存&#xff0c;IO效率远高于MySql数据库 2.减少数据库压力 Redis处理很多请求&#xff0c;使用Redis作为缓存可以减少数据库的请求量&…

9.2024

使用冒泡排序给{10 ,1,35,61,89,36,55}排序 代码&#xff1a; public class 第九题 {public static void main(String[] args) {int a[]{10,1,35,61,89,36,55};for (int i0;i<a.length-1;i){for (int j0;j<a.length-1;j){if (a[j]>a[j1]){int temp0;tempa[j];a[j]a[…

数字量化值Digital Number, 辐射亮度Radiance, 反射率Reflectance,发射率Emissive

我们经常听到有人困惑于图像的像素值储存的是什么信息&#xff0c;以及如何获取所需的值。这里我们总结以下几个概念。 数字量化值&#xff08;Digital Number &#xff1a;DN&#xff09; 像素值的通用术语是数字量化值或DN值&#xff0c;它通常被用来描述还没有校准到具有意…

hbase启动错误-local host is“master:XXXX“ destination is:master

博主的安装前提&#xff1a; zookeeper安装完成&#xff0c;且启动成功 hdfs高可用安装&#xff0c;yarn高可用安装&#xff0c;且启动成功 报错原因&#xff1a;端口配置不对 解决方案&#xff1a; 输入&#xff1a;hdfs getconf -confKey fs.default.name 然后把相应的…

Spring Cloud 网关Gateway + 配置中心

网关 网络的接口&#xff0c;负责请求的路由、转发、身份校验 路由&#xff1a;告诉请求去哪找 转发&#xff1a;请求找不到直接带请求过去 路由及转发 判断前端请求的规则就这么配 当前情况下只需要访问8080端口 就可以完成对全部微服务的访问 路由属性 登录校验 没必要在每…

如果有意外,这个窗口就会弹出,希望你们能够看到!——夜读(逆天打工人爬取热门微信文章解读)

第一个日二更 引言Python 代码第一篇 定时任务运行结果 第二篇 人民日报 【夜读】最好的教养&#xff0c;是对家人和颜悦色结尾 时间不会无缘无故增加 也不会无缘无故减少 我们唯一能够控制就是 加大时间的密度 引言 为了不让我在大庭广众下大喊我是沙比 我来更新文章啦 这次带…

网络七层模型之物理层:理解网络通信的架构(一)

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

【漏洞复现】商混ERP系统 DictionaryEdit.aspx接口处存在SQL注入漏洞

免责声明&#xff1a;文章来源互联网收集整理&#xff0c;请勿利用文章内的相关技术从事非法测试&#xff0c;由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;所产生的一切不良后果与文章作者无关。该…

并发编程之的虚假唤醒和精准唤醒的详细解析

虚假唤醒 例子 wait&#xff08;&#xff09;是object类自带的方法&#xff0c;在jdk有介绍&#xff0c;有可能出现中断、虚假唤醒 也就是在下面的例子中 if(number ! 0){this.wait(); } 当线程成功进入if语句块中&#xff0c;发生了中断&#xff0c;cpu跑去调度别的进程了&am…

厨余垃圾处理设备工业监控PLC连接APP小程序智能软硬件开发之功能结构篇

厨余垃圾处理设备工业监控PLC连接APP小程序智能软硬件开发之功能结构篇 好几年前&#xff0c;应朋友之邀&#xff0c;为其工厂的厨余垃圾处理设备研发一套用于对现场的生产及维护进行远程查看、管理和质量监控的厨余垃圾处理设备工业监控PLC连接APP小程序智能软硬件系统。 因为…