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       0xFFenum _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 statusspi_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 transmissiondo{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 respif(buff[2]==0X01&&buff[3]==0XAA)//卡是否支持2.7~3.6V{retry=0XFFFE;do{SD_sendcmd(CMD55,0,0X01);	//发送CMD55rst=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);			//发送CMD55rst=SD_sendcmd(CMD41,0,0X01);	//发送CMD41if(rst<=1){SD_TYPE=V1;retry=0XFFFE;do //等待退出IDLE模式{SD_sendcmd(CMD55,0,0X01);	//发送CMD55rst=SD_sendcmd(CMD41,0,0X01);//发送CMD41}while(rst&&retry--);}else//MMC卡不支持CMD55+CMD41识别{SD_TYPE=MMC;//MMC V3retry=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);//忽略crcspi_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信息,如果期间出错,返回0if(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 == 1DRESULT USER_write (BYTE pdrv, const BYTE *buff, DWORD sector, UINT count);
#endif /* _USE_WRITE == 1 */
#if _USE_IOCTL == 1DRESULT USER_ioctl (BYTE pdrv, BYTE cmd, void *buff);
#endif /* _USE_IOCTL == 1 */Diskio_drvTypeDef  USER_Driver =
{USER_initialize,USER_status,USER_read,
#if  _USE_WRITEUSER_write,
#endif  /* _USE_WRITE == 1 */
#if  _USE_IOCTL == 1USER_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() operationFRESULT 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 writeextern 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 Pathfor(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 formattingif(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 directoryif ( 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 fileif(retSD == FR_OK){printf("\r\nFile open or creation successful\r\n");retSD = f_write( &file, (const void *)WBuffer, sizeof(WBuffer), &byteswritten); //Write dataif(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 fileif(retSD == FR_OK){printf("\r\nFile open successful\r\n");retSD = f_read( &file, (void *)rBuffer, sizeof(rBuffer), &bytesread); //Read dataif(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 fileif(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 locatingif(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 fileif(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 locatingif(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–

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Leo赠书活动-21期 《一篇讲明白 Hadoop 生态的三大部件》

✅作者简介&#xff1a;大家好&#xff0c;我是Leo&#xff0c;热爱Java后端开发者&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;Leo的博客 &#x1f49e;当前专栏&#xff1a; 赠书活动专栏 ✨特色专栏&#xff1a;…

【算法刷题】链表笔试题解析(1)

一、链表分割 题目描述&#xff1a; 链接&#xff1a;链表分割 题目分析&#xff1a; 这题直接处理并不好做&#xff0c;我们可以构建前后两个链表&#xff0c;将小于x值的结点放在链表a内&#xff0c;将其它结点放在链表b内&#xff0c;这样将原链表遍历完后&#xff0c;原链…

Day23 代码随想录(1刷) 二叉树

669. 修剪二叉搜索树 给你二叉搜索树的根节点 root &#xff0c;同时给定最小边界low 和最大边界 high。通过修剪二叉搜索树&#xff0c;使得所有节点的值在[low, high]中。修剪树 不应该 改变保留在树中的元素的相对结构 (即&#xff0c;如果没有被移除&#xff0c;原有的父代…

PHP图床程序优化版:图片外链服务、图床API服务、图片CDN加速与破解防盗链

图片免费上传 支持本地储存、FTP储存、第三方云储存&#xff08;阿里云 OSS、腾讯云 COS、七牛云等&#xff09;。 图片外链加速 一键转换第三方网站的图片外链地址为图床可分享的图片地址&#xff08;支持CDN&#xff09;。 图片解析服务 直接将第三方外链图片地址显示为…

oracle docker安装

修改下载的Image的REPOSITORY和TAG属性 修改下载的Image的REPOSITORY和TAG属性&#xff1a;docker tag <IMAGE ID> <REPOSITORY NAME> docker tag 3fa112fd3642 aliyun/oracle_11g 参考网址 使用docker images时&#xff0c;可能会出现REPOSITORY和TAG均为none的镜…

【教程】iOS 手机抓包工具介绍及教程

&#x1f4f1; 最近又发现APP Store一款宝藏软件&#xff0c;克魔助手抓包工具&#xff0c;app刚上架&#xff0c;功能不断迭代中&#xff0c;目前18软妹币实惠价可享受终身版&#xff01;现在是下手的最好时机。 引言 移动端开发中&#xff0c;抓包工具已成为必备的工具之一…

shell脚本发布nginx vue2 项目示例

nginx、git、node.js安装略过。 使git pull或者git push不需要输入密码操作方法 非docker安装nginx&#xff01;&#xff01;&#xff01; 姊妹篇&#xff08;docker安装nginx&#xff09;&#xff1a;shell脚本发布docker-nginx vue2 项目示例 pro_build.sh 注意&#xff1…

Linux基础IO(操作系统层面理解文件)

目录 一、认识 open 函数 1.1 理解文件 1.2 open 函数 1.3 函数选项和宏 二、 open 函数的返回值 三、 fd 的本质 3.1 各部分内容及关系 3.2 如何确定进程对应文件 四、Linux 一切皆文件&#xff1f; 一、认识 open 函数 在C语言中学习文件操作时&#xff0c;我们学…

基于SpringBoot和Vue的课程作业管理系统的设计与实现

今天要和大家聊的是一款基于SpringBoot和Vue的课程作业管理系统的设计与实现。 &#xff01;&#xff01;&#xff01; 有需要的小伙伴可以通过文章末尾名片咨询我哦&#xff01;&#xff01;&#xff01; &#x1f495;&#x1f495;作者&#xff1a;李同学 &#x1f495;&am…

element表格 加滚动,监听底部实现分页加载

表格要实现滚动很简单&#xff0c;给他加一个高度即可 height"300" 然后是监听事件 mounted() {this.lazyLoading();}, methods:{lazyLoading(){let dom document.querySelector(".el-table__body-wrapper");dom.addEventListener("scroll", (…

踩坑uniapp中打包Andiord app,在真机调试时地图以及定位功能可以正常使用,打包成app后失效的问题

首先看到这是uni官网提出的&#xff0c;app上建议使用高德地图。 下面就用高德地图进行配置。 步骤一&#xff1a;登陆高德地图控制台 名称和类型根据自己情况填写选择即可 步骤二&#xff1a; 添加key 步骤三&#xff1a;取到SHA1 进入uniapp开发官网 点击应用名称&#…

算法模型离线评估方案

1 引言2需求背景3特征评估 3.1特征表维护3.2样本频率分布直方图概览3.3Pearson相关系数计算3.4缺失率计算4模型评估 4.1模型离线AUC评估 4.1.1什么是AUC4.1.2AUC的优势:4.2TensorBoard可视化 4.2.1项目中集成tensorboard4.2.2启动tensorboard命令4.2.3可视化效果4.3离线实验记录…

SpringBoot SpringMVC (详解)

6. SpringBoot Spring 的诞⽣是为了简化 Java 程序的开发的&#xff0c;⽽ Spring Boot 是为了快速开发 Spring 程序开发而诞生的。 Spring Boot 的优点&#xff1a; 快速集成框架&#xff0c;Spring Boot 提供了启动添加依赖的功能&#xff0c;⽤于秒级集成各种框架。内置运⾏…

JAVA WEB 能够实现整个文件夹的上传下载吗?

导入项目&#xff1a; 导入到Eclipse&#xff1a;导入项目 导入到IDEA&#xff1a;导入项目 springboot统一配置&#xff1a;springboot-配置 下载示例&#xff1a; https://gitee.com/xproer/up6-jsp-eclipse/tree/6.5.40/ 工程 NOSQL NOSQL示例不需要任何配置&#xff0c;可…

【面试经典150 | 】最长递增子序列

文章目录 Tag题目来源解题思路方法一&#xff1a;动态规划 写在最后 Tag 【动态规划】【数组】 题目来源 300. 最长递增子序列 解题思路 方法一&#xff1a;动态规划 定义状态 dp[i] 表示以位置 i 对应整数为末尾的最长递增子序列的长度。 状态转移 我们从小到大计算 dp…

FASTAPI系列 16-其他响应类型

FASTAPI系列 16-其他响应类型 文章目录 FASTAPI系列 16-其他响应类型前言一、HTMLResponse 响应 HTML二、纯文本响应三、另外的JSON 响应四、FileResponse文件五、StreamingResponse六、RedirectResponse 重定向请求总结更多内容&#xff0c;请关注公众号, 发送666 更可以得到免…

解决配置文件中文乱码

一、问题 二、解决方法

HTML5+CSS3小实例:文字边框视觉错位

实例:文字边框视觉错位 技术栈:HTML+CSS 效果: 源码: 【HTML】 <!DOCTYPE html> <html lang="zh-CN"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scal…

StringRedisTemplate与RedisTemplate详解【序列化的方式不同】

spring 封装了 RedisTemplate 对象来进行对redis的各种操作&#xff0c;它支持所有的 redis 原生的 api。在RedisTemplate中提供了几个常用的接口方法的使用&#xff0c;分别是: private ValueOperations<K, V> valueOps; private HashOperations<K, V> hashOps; …

TLSR8258 MTU、DLE(PDU)

本文基于泰凌微TLSR8258 M1S1 demo。 1.DLE&#xff1a;LE Data Packet Length Extension 中文全称&#xff1a;低功耗蓝牙数据包长度扩展。 这是一个在2014年12月2日正式发布的bluetooth for BLE添加的新特性&#xff0c;以支持在LL Data PDU更长的数据&#xff0c;最大支持…