STM32 FATFS - 在spi的SD卡中运行fatfs
参考文章
STM32 + CubeMX + 硬件SPI + SD卡 + FATFS_stm32cubemx fatfs-CSDN博客
例程地址:STM32FatFS: 基于stm32的fatfs例程,配合博客文章
基于野火STM32MINI开发板
STM32配置
系统模式配置

输出串口配置

SPI配置
使用全双工模式,禁用硬件NSS引脚,8位,MSB,256分频因为在协议在 SPI 模式初始化时(如发送 CMD0、CMD8 等命令),必须使用不超过 400kHz 的时钟频率。

NSS的GPIO配置

配置FATFS
使用自定义模式,支持简体中文编码,不建议开启长文件名支持,具体原因详见上一篇。

FREERTOS配置
选用V1版本,FATFS使用了V1版本的函数,选择V2版本编译不通过,增大栈总量,因为我在默认任务中打印了很多东西,所以需要加大栈。

增大默认任务的栈

配置系统时钟,这样可以更快的执行代码,这步完全可以不做。

配置生成独立.h.c文件

代码
创建sd卡的驱动文件,我是图省事直接添加到了Core文件夹中,分别创建sd.h和sd.c
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 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;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
sd.c文件
#include "sd.h"
#include <stdio.h>
#include "fatfs.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(0xFF);}//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(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); //发送CMD55r1=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); //发送CMD55r1=SD_sendcmd(CMD41,0,0X01); //发送CMD41if(r1<=1){ SD_TYPE=V1;retry=0XFFFE;do //等待退出IDLE模式{SD_sendcmd(CMD55,0,0X01); //发送CMD55r1=SD_sendcmd(CMD41,0,0X01);//发送CMD41}while(r1&&retry--);}else//MMC卡不支持CMD55+CMD41识别{SD_TYPE=MMC;//MMC V3retry=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);//忽略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 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信息,如果期间出错,返回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 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;DWORD fre_clust,AvailableSize,UsedSize; uint16_t TotalSpace;uint8_t res;printf("SD卡初始化!\r\n");res = SD_init(); //SD卡初始化if(res == 1){printf("SD卡初始化失败! \r\n"); }else{printf("SD卡初始化成功! \r\n"); }/* 挂载 */result=f_mount(&USERFatFS,USERPath,1); //挂载if(result == FR_NO_FILESYSTEM) //没有文件系统,格式化{
// test_sd =1; //用于测试格式化printf("没有文件系统! \r\n"); result = f_mkfs(USERPath, 0, 0); //格式化sd卡if(result == FR_OK){printf("格式化成功! \r\n"); result = f_mount(NULL,USERPath,1); //格式化后先取消挂载result = f_mount(&USERFatFS,USERPath,1); //重新挂载 if(result == FR_OK){printf("SD卡已经成功挂载,可以进进行文件写入测试!\r\n");} }else{printf("格式化失败! \r\n"); }}else if(res == FR_OK){printf("挂载成功! \r\n"); }else{printf("挂载失败! \r\n");}result = f_getfree(USERPath, &fre_clust, &fs); /* 根目录 */if ( result == 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""%ld MB available.\r\n""%ld 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)
{FIL file;uint8_t res = 0;UINT Bw;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); // 关闭文件
}void UnmountSD(void)
{f_mount(NULL, USERPath, 1); // 取消挂载
}
///END//
修改FATFS\Target\user_diskio.c文件
添加头文件
//添加头文件
#include "diskio.h" /* Declarations of disk functions */
#include "sd.h"
#include "cmsis_os.h"
修改USER_initialize函数
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;elsereturn RES_OK; // 初始化成功/* USER CODE END INIT */
}
修改USER_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 */
}
修改USER_read函数
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 */
}
修改USER_write函数
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 */
}
修改USER_ioctl函数
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 {osDelay(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 */
}
在mian.c中添加支持printf的函数
/* USER CODE BEGIN 0 */int __io_putchar(int ch)
{/* Implementation of __io_putchar *//* e.g. write a character to the UART1 and Loop until the end of transmission */HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFFFFFF);return ch;
}
int __io_getchar(void)
{/* Implementation of __io_getchar */char rxChar;// This loops in case of HAL timeout, but if an ok or error occurs, we continuewhile (HAL_UART_Receive(&huart1, (uint8_t *)&rxChar, 1, 0xFFFFFFFF) == HAL_TIMEOUT);return rxChar;
}
/* USER CODE END 0 */
下面编写freertos中的实现功能
修改freertos.c文件
添加头文件
/* USER CODE BEGIN Includes */
/* 添加头文件 */
#include <stdio.h>
#include "sd.h"/* USER CODE END Includes */
修改默认任务
void StartDefaultTask(void const * argument)
{/* USER CODE BEGIN StartDefaultTask *//* Infinite loop */char SD_FileName[] = "hello.txt";uint8_t WriteBuffer[] = "01 write buff to sd \r\n";uint8_t write_cnt =0; //写SD卡次数Get_SDCard_Capacity(); //得到使用内存并选择格式化osDelay(1000);WritetoSD(SD_FileName, WriteBuffer,sizeof(WriteBuffer));osDelay(1000);WritetoSD(SD_FileName, WriteBuffer,sizeof(WriteBuffer));UnmountSD();for(;;){osDelay(1);}/* USER CODE END StartDefaultTask */
}
运行结果


相关文章:
STM32 FATFS - 在spi的SD卡中运行fatfs
参考文章 STM32 CubeMX 硬件SPI SD卡 FATFS_stm32cubemx fatfs-CSDN博客 例程地址:STM32FatFS: 基于stm32的fatfs例程,配合博客文章 基于野火STM32MINI开发板 STM32配置 系统模式配置 输出串口配置 SPI配置 使用全双工模式,禁用硬件…...
我的世界进阶模组开发教程——地形生成(1)
找到mc的屎山代码,找到net.minecraft.world.level.levelgen包,我们来看看mc是如何完成地形生成的 SurfaceRules 代码结构与核心功能解析 该代码是 Minecraft 世界生成模块中地表规则(SurfaceRules)的核心实现,用于控制地形表面的方块生成逻辑。以下从多角度进行拆解分析…...
FreeCAD傻瓜教程-装配体Assembly的详细使用过程
源起: 看了官方的教程说明,感觉太过简单,好多细节没有体现,且该部分的翻译还没有。这里是做个记录,对使用过程中的细节进行图文说明,以方便真正的新手能够快速应用,制作出自己的零件,…...
Linux快速安装docker和docker-componse步骤
在 CentOS 7 上安装 Docker 和 Docker Compose 的步骤如下: 1. 安装 Docker 1.1. 更新系统 首先,确保你的系统是最新版本: sudo yum update -y1.2. 安装必要的包 安装 yum-utils,这是管理 YUM 源的工具: sudo yu…...
数字电子技术基础(三十七)——利用Multisim软件实现16线-4线编码器和4线-16线译码器
1 利用Multisim软件来实现16线-4线编码器 在之前的博客中完成了利用Multisim软件实现8线-3线优先编码器,现在使用Multisim软件来实现16线-4线编码器,其原理图如下所示: 使用字发生器来实现16线-4线编码器,器件选择: …...
02_MySQL安装及配置
文章目录 一、下载二、安装及配置2.1、选择安装类型2.2、检查需要的依赖2.3、安装2.4、配置2.4.1、配置类型和网络2.4.2、配置账户和角色2.4.3、配置Windows服务2.4.4、让配置生效 2.5、验证是否安装成功 三、卸载3.1、运行MySQL安装工具3.2、卸载及清理3.3、卸载之后的检查工作…...
Windows11,微软软件(VSCODE/EDG)错误登录,0x80190001错误
修改网络设置 运行以下命令,打开网络共享中心 Start-Process "control.exe" -ArgumentList "/name Microsoft.NetworkAndSharingCenter" 点击左下角的 选项 TLS 1.1 1.2 1.3 这三个选项 1.0 不建议启用,不安全 1.1 可以不用启用…...
WPF 浅述IsHitTestVisible属性
WPF 浅述IsHitTestVisible属性 IsHitTestVisible 属性是 WPF 中一个非常重要的属性,它决定了一个控件是否可以作为 hit test 的一部分被检测到。理解这个属性对于处理交互事件(如鼠标点击、触摸等)非常重要。 IsHitTestVisible 属性的含义&am…...
力扣刷题-热题100题-第29题(c++、python)
19. 删除链表的倒数第 N 个结点 - 力扣(LeetCode)https://leetcode.cn/problems/remove-nth-node-from-end-of-list/description/?envTypestudy-plan-v2&envIdtop-100-liked 计算链表长度 对于链表,难的就是不知道有多少元素ÿ…...
NLP高频面试题(三十)——LLama系列模型介绍,包括LLama LLama2和LLama3
本文深入介绍Meta推出的LLama系列模型,包括LLama、LLama2和LLama3,探讨了它们的技术创新、应用场景以及对大语言模型发展的重要推动作用。通过系统地回顾各代模型的进化过程,分析其核心特性与技术亮点,为读者提供全面且深入的理解…...
torch.nn.Conv2d介绍——Pytorch中的二维卷积层
torch.nn.Conv2d是torch.nn模块中的二维卷积层类,用于构建神经网络中的二维卷积层。 1、基本语法 torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride1, padding0, dilation1, groups1, biasTrue, padding_modezeros, deviceNone, dtypeNone)将 2D …...
阻止上传可执行程序
点击工具中的文件服务器资源管理器 、然后点击文件屏蔽管理中的文件屏蔽,然后导入目标文件选择要限制的属性即可...
DirectX修复工具免费版下载安装教程(附安装包)
文章目录 前言一、DirectX修复工具免费版介绍二、DirectX修复工具免费版安装教程1. 下载安装包2. 解压文件3. 以管理员身份运行4. 开始检测与修复5. 查看修复详情 前言 本教程主要介绍的是DirectX修复工具免费版下载安装教程,帮您轻松解决 DirectX 相关问题。 一、…...
UE5学习笔记 FPS游戏制作33 游戏保存
文章目录 核心思想创建数据对象创建UIUI参数和方法打开UI存档文件的位置可以保存的数据类型 核心思想 UE自己有保存游戏的功能,核心节点,类似于json操作,需要一个数据类的对象来进行保存和读取 创建存档 加载存档 保存存档 创建数据对象…...
Git与SVN的区别以及各自的优势
前言:版本控制的诞生与意义 在软件开发的漫长历程中,代码的迭代与协作始终是核心挑战。从早期的“文件夹版本”到现代的分布式系统,版本控制系统(VCS)的进化史,本质上是人类对协作效率与数据安全的不懈追求…...
PipeWire 音频设计与实现分析三——日志子系统
日志子系统 PipeWire 的日志子系统的设计分为多个层次。PipeWire 用 struct spa_log 对象描述日志组件,用 struct spa_log_methods 对象描述日志组件打印各层级日志的多个方法。PipeWire 为日志子系统添加了 topic 机制,不同文件中的日志按功能以不同的…...
TypeScript vs. JavaScript:技术对比与核心差异解析
引言 在 Web 前端开发领域,JavaScript(JS)长期占据主导地位,但随着项目复杂度的提升,开发者逐渐面临维护性差、协作困难等问题。TypeScript(TS)作为 JavaScript 的超集,通过静态类型…...
关于 @Autowired 和 @Value 使用 private 字段的警告问题分析与解决方案
问题背景 在使用 Spring 框架进行开发时,我们经常会使用 Autowired 和 Value 注解来进行依赖注入和属性值注入。然而,当我们将这些注解应用于 private 字段时,IDE(如 IntelliJ IDEA)可能会显示警告信息,提…...
MySQL 进阶 面经级
会用数据库,找大厂工作是远远不够的。 本人2025美团暑期AI面试好几个MySQL场景问题不会答,已脏面评。遂在此整理学习! 文章目录 分片分区分区语法范围分区 (RANGE Partitioning)列表分区(LIST Partitionin…...
《C奥林匹斯宝典:基础篇 - 重载函数》
一、重载函数 (一)函数模板重载 详细解析:函数模板提供了一种通用的函数定义方式,可针对不同类型进行实例化。当存在函数模板与普通函数、其他函数模板同名时,就构成了函数模板重载。编译器在编译阶段,依…...
【408--考研复习笔记】计算机网络----知识点速览
目录 一、计算机网络体系结构 1.计算机网络的定义与功能: 2.网络体系结构相关概念: 3.OSI 七层模型与 TCP/IP 模型: 4.通信方式与交换技术: 电路交换 报文交换 分组交换 5.端到端通信和点到点通信: 6.计算机…...
TiDB 可观测性解读(二)丨算子执行信息性能诊断案例分享
导读 可观测性已经成为分布式系统成功运行的关键组成部分。如何借助多样、全面的数据,让架构师更简单、高效地定位问题、分析问题、解决问题,已经成为业内的一个技术焦点。本系列文章将深入解读 TiDB 的关键参数,帮助大家更好地观测系统的状…...
15:00开始面试,15:08就出来了,问的问题有点变态。。。
从小厂出来,没想到在另一家公司又寄了。 到这家公司开始上班,加班是每天必不可少的,看在钱给的比较多的份上,就不太计较了。没想到8月一纸通知,所有人不准加班,加班费不仅没有了,薪资还要降40%…...
蓝桥杯准备(前缀和差分)
import java.util.Scanner; public class qianzhuihe {public static void main(String[] args) {int N,M;Scanner scnew Scanner(System.in);Nsc.nextInt();Msc.nextInt();int []treesnew int[N1];//设为N1的意义,防止越界int []prefixSumnew int[N1];for(int i1;i…...
试用thymeleaf引入vue-element-admin(一)
作为后端程序员,一直使用springbootbootstarp做管理系统,对前端不是太了解,现在感觉bootstarp的admin ui一直不得劲,想切换成前端使用较多的ui,费了老鼻子劲。 我的目的不是前后端分离,而是一个人全栈&…...
Minimind 训练一个自己专属语言模型
发现了一个宝藏项目, 宣传是完全从0开始,仅用3块钱成本 2小时!即可训练出仅为25.8M的超小语言模型MiniMind,最小版本体积是 GPT-3 的 17000,做到最普通的个人GPU也可快速训练 https://github.com/jingyaogong/minimi…...
C++11QT复习 (七)
智能指针雏形 **Day7-1 智能指针雏形:独占语义与共享语义****1. 独占语义与共享语义****1.1 Circle 类:示例类** **2. 拷贝构造:独占语义(Unique Ownership)****2.1 代码解析** **3. 拷贝构造:共享语义&…...
STM32八股【5】----- TIM定时器
1. TIM定时器分类 STM32 的定时器主要分为以下几类: 高级定时器(Advanced TIM,TIM1/TIM8) 具备 PWM 生成、死区控制、互补输出等高级功能,适用于电机控制和功率转换应用。通用定时器(General-purpose TIM…...
单元测试之Arrange-Act-Assert(简称AAA)
Arrange-Act-Assert(简称AAA)是一种编写单元测试的标准模式,具有清晰的结构和明确的步骤,有助于提高测试的可读性、可维护性和可扩展性。以下是对每个步骤的详细说明: 1. Arrange(准备阶段) 在…...
厘米级定位赋能智造升级:品铂科技UWB技术驱动工厂全流程自动化与效能跃升”
在智能制造中的核心价值体现在高精度定位、流程优化、安全管理等多个维度,具体应用如下: 一、核心技术与定位能力 厘米级高精度定位 UWB技术通过纳秒级窄脉冲信号(带宽超500MHz)实现高时间分辨率,结合…...
