STM32F429 Discovery开发板应用:实现SPI-SD Card文件写入(搭载FatFS文件系统)
MCU:STM32F429ZIT6开发环境:STM32CubeMX+MDK5
外购了一个SPI接口的SD Card模块,想要实现SD卡存储数据的功能。
首先需要打开STM32CubeMX工具。输入开发板MCU对应型号,找到开发板对应封装的MCU型号,双击打开(图中第三)。
此时,双击完后会关闭此界面,然后打开一个新界面。
然后,我们开始基本配置。
现在我们选择一个LED作为系统LED,该步骤可以忽略,只是本人喜欢这样子。以硬件原理图的LD3为例子。
基本配置除了时钟树外,基本上已经配置好了。
现在配置时钟树
基本配置已经配置完,现在开始配置实验使用的内容。
配置USART1,重定向printf函数作为串口输出。
然后配置SPI1,作为驱动SD Card读写的接口。
然后配置文件系统,可以让文件的使用更方便。
现在配置按键,触发中断处理一些事情。
配置完成,完善工程,生成工程。
到此,STM32CubeMX工具的使用结束!可以发现在桌面已经生成了SDCard_rw工程。
使用MDK5打开SDCard_rw工程打开。点击魔法棒,勾选微库。选择对应的下载器,勾选下载完复位允许。USB线一端接开发板USB_Device,一端接PC。
现在可以开始实验了
在usart.c中重定向printf函数,并在usart.h中声明。
1 //重定向c库函数printf到串口DEBUG_USART,重定向后可使用printf函数 2 int fputc(int ch, FILE *f) 3 { 4 HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 1000); 5 return (ch); 6 }
在sdcard_write工程下创建UserCode文件夹,编写drive_spisd.c和drive_spisd.h。
然后在MDK5这里的SDCard_rw工程添加一个新文件夹UserCode,装入drive_spisd.c。并在魔法棒这里加入头文件路径。
drive_spisd.c如下
1 /* Includes ------------------------------------------------------------------*/ 2 #include "drive_spisd.h" 3 /* Private includes ----------------------------------------------------------*/ 4 #include "spi.h" 5 #include "ff.h" 6 #include "usart.h" 7 /* Private typedef -----------------------------------------------------------*/ 8 9 /* Private define ------------------------------------------------------------*/ 10 11 /* Private macro -------------------------------------------------------------*/ 12 13 /* Private variables ---------------------------------------------------------*/ 14 uint8_t test; 15 uint8_t SD_TYPE = 0x00; 16 MSD_CARDINFO SD0_CardInfo; 17 char SD_FileName[] = "hello.txt"; 18 /* Private function prototypes -----------------------------------------------*/ 19 static int SD_SendCMD(uint8_t cmd, uint32_t arg, uint8_t crc); 20 static uint8_t SD_ReceiveData(uint8_t *data, uint16_t len); 21 static uint8_t SD_SendBlock(uint8_t*buf, uint8_t cmd); 22 /* Private user code ---------------------------------------------------------*/ 23 24 /** 25 * @brief SPI_CS片选 26 * @note None 27 * @retval None 28 */ 29 void SPISD_CS(uint8_t p) 30 { 31 HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, (p==0)?GPIO_PIN_SET:GPIO_PIN_RESET); 32 } 33 34 /** 35 * @brief 发送命令(CMD0~CMD63),发完释放 36 * @note 命令格式:0+传输标志(1-命令、0-响应)+CONTENT(6+32)+7CRC+1 37 * @retval None 38 */ 39 static int SD_SendCMD(uint8_t cmd, uint32_t arg, uint8_t crc) 40 { 41 uint8_t r1,retry; 42 43 SPISD_CS(0); //取消片选 44 HAL_Delay(20); 45 SPISD_CS(1); //选通 46 47 //SD卡的SPI通信协议规定,每个命令操作之前都需要发送至少8个时钟周期 48 do 49 { 50 retry = SPI_ReadWrite(0xFF); 51 }while(retry != 0xFF); 52 53 SPI_ReadWrite(cmd | 0x40); 54 SPI_ReadWrite(arg >> 24); 55 SPI_ReadWrite(arg >> 16); 56 SPI_ReadWrite(arg >> 8); 57 SPI_ReadWrite(arg); 58 SPI_ReadWrite(crc); 59 60 if(cmd == CMD12) 61 SPI_ReadWrite(0xFF); 62 do 63 { 64 r1 = SPI_ReadWrite(0xFF); 65 }while(r1 & 0x80); 66 67 return r1; 68 } 69 70 //SD卡初始化 71 uint8_t SD_Init(void) 72 { 73 uint8_t r1,i; 74 uint8_t buff[6] = {0}; 75 uint16_t retry; 76 77 SPI_SetSpeed(SPI_BAUDRATEPRESCALER_256); 78 SPISD_CS(0); 79 for(retry=0;retry<10;retry++) 80 SPI_ReadWrite(0xFF); 81 82 //SD卡进入IDLE状态 83 do 84 { 85 r1 = SD_SendCMD(CMD0 ,0, 0x95); 86 }while(r1 != 0x01); 87 88 //查看SD卡的类型 89 SD_TYPE = 0; 90 r1 = SD_SendCMD(CMD8, 0x1AA, 0x87); 91 if(r1 == 0x01) 92 { 93 for(i=0;i<4;i++) 94 buff[i] = SPI_ReadWrite(0xFF); //Get trailing return value of R7 resp 95 if( buff[2]==0X01 && buff[3]==0XAA ) //卡是否支持2.7~3.6V 96 { 97 retry = 0XFFFE; 98 do 99 { 100 SD_SendCMD(CMD55, 0, 0X01); //发送CMD55 101 r1 = SD_SendCMD(CMD41, 0x40000000, 0X01); //发送CMD41 102 }while(r1&&retry--); 103 104 if(retry && SD_SendCMD(CMD58, 0, 0X01) == 0) //鉴别SD2.0卡版本开始 105 { 106 for(i=0;i<4;i++) 107 buff[i] = SPI_ReadWrite(0XFF); //得到OCR值 108 SD_TYPE = (buff[0]&0x40) ? V2HC:V2; 109 } 110 }else 111 { 112 SD_SendCMD(CMD55, 0, 0X01); //发送CMD55 113 r1 = SD_SendCMD(CMD41, 0, 0X01); //发送CMD41 114 if(r1<=1) 115 { 116 SD_TYPE = V1; 117 retry = 0XFFFE; 118 do //等待退出IDLE模式 119 { 120 SD_SendCMD(CMD55, 0, 0X01); //发送CMD55 121 r1 = SD_SendCMD(CMD41, 0, 0X01); //发送CMD41 122 }while(r1&&retry--); 123 }else //MMC卡不支持CMD55+CMD41识别 124 { 125 SD_TYPE = MMC; //MMC V3 126 retry = 0XFFFE; 127 do //等待退出IDLE模式 128 { 129 r1 = SD_SendCMD(CMD1, 0, 0X01); //发送CMD1 130 }while(r1&&retry--); 131 } 132 if( retry==0 || SD_SendCMD(CMD16, 512, 0X01)!=0 ) 133 SD_TYPE = ERR; //错误的卡 134 } 135 } 136 SPISD_CS(0); 137 SPI_SetSpeed(SPI_BAUDRATEPRESCALER_4); 138 139 return SD_TYPE?0:1; 140 } 141 142 void FileSystem_Init(void) 143 { 144 FATFS *fs; 145 DWORD fre_clust, AvailableSize, UserSize; 146 uint8_t res; 147 uint8_t *work; 148 uint16_t TotalSpace; 149 150 res = SD_Init(); 151 if(res == 1) 152 printf("SD卡初始化失败! \r\n"); 153 154 res = f_mount(&USERFatFS, USERPath, 1); //挂载 155 if(res == FR_NO_FILESYSTEM) //没有文件系统,格式化 156 { 157 printf("没有文件系统! \r\n"); 158 159 work = malloc(_MIN_SS); 160 res = f_mkfs(USERPath, FM_FAT, 0, work, _MIN_SS); //格式化sd卡 161 free(work); 162 163 if(res == FR_OK) 164 { 165 res = f_mount(NULL, USERPath, 1); //格式化后先取消挂载 166 res = f_mount(&USERFatFS, USERPath, 1); //重新挂载 167 if(res == FR_OK) 168 { 169 printf("SD卡已经成功挂载,可以进进行文件写入测试! \r\n"); 170 } 171 } 172 else 173 { 174 printf("格式化失败! \r\n"); 175 } 176 }else if(res == FR_OK) 177 { 178 printf("挂载成功! \r\n"); 179 }else 180 { 181 printf("挂载失败! (%d)\r\n", res); 182 } 183 184 res = f_getfree(USERPath, &fre_clust, &fs); /* 根目录 */ 185 if ( res == FR_OK ) 186 { 187 TotalSpace = (uint16_t)(((fs->n_fatent - 2) * fs->csize ) / 2 /1024); 188 AvailableSize = (uint16_t)((fre_clust * fs->csize) / 2 /1024); 189 UserSize = TotalSpace - AvailableSize; 190 /* Print free space in unit of MB (assuming 512 bytes/sector) */ 191 printf("\r\n%d MB total drive space.\r\n%ld MB available.\r\n%ld MB used.\r\n",TotalSpace, AvailableSize, UserSize); 192 } 193 else 194 { 195 printf("Get SDCard Capacity Failed (%d)\r\n", res); 196 } 197 // f_mount(NULL, USERPath, 1); //取消挂载 198 } 199 200 /** 201 * @brief 读取指定长度数据 202 * @note None 203 * @retval None 204 */ 205 static uint8_t SD_ReceiveData(uint8_t *data, uint16_t len) 206 { 207 uint8_t r1; 208 209 SPISD_CS(1); 210 do 211 { 212 r1 = SPI_ReadWrite(0xFF); 213 HAL_Delay(100); 214 }while(r1 != 0xFE); 215 216 while(len--) 217 { 218 *data = SPI_ReadWrite(0xFF); 219 data++; 220 } 221 SPI_ReadWrite(0xFF); 222 SPI_ReadWrite(0xFF); 223 224 return 0; 225 } 226 227 /** 228 * @brief 向SD卡写入一个数据包(512字节)的内容 229 * @note None 230 * @retval None 231 */ 232 static uint8_t SD_SendBlock(uint8_t*buf, uint8_t cmd) 233 { 234 uint8_t r1; 235 uint16_t t; 236 237 do{ 238 r1 = SPI_ReadWrite(0xFF); 239 }while(r1!=0xFF); 240 241 SPI_ReadWrite(cmd); 242 if(cmd != 0XFD) //不是结束指令 243 { 244 for(t=0; t<512; t++) 245 SPI_ReadWrite(buf[t]); //提高速度,减少函数传参时间 246 SPI_ReadWrite(0xFF); //忽略crc 247 SPI_ReadWrite(0xFF); 248 t = SPI_ReadWrite(0xFF); //接收响应 249 if( (t&0x1F) != 0x05 ) 250 return 2; //响应错误 251 } 252 253 return 0; //写入成功 254 } 255 256 /** 257 * @brief CSD,卡的操作条件信息,128bit 258 * @note None 259 * @retval None 260 */ 261 uint8_t SD_GetCSD(uint8_t *csd_data) 262 { 263 uint8_t r1; 264 265 r1 = SD_SendCMD(CMD9, 0, 0x01); //读取CSD寄存器 266 if(r1 == 0x00) 267 r1 = SD_ReceiveData(csd_data, 16); //接收16个字节的数据 268 SPISD_CS(0); //取消片选 269 270 return r1?1:0; 271 } 272 273 /** 274 * @brief CID,卡识别号,128bit 275 * @note None 276 * @retval None 277 */ 278 uint8_t SD_GetCID(uint8_t *cid_data) 279 { 280 uint8_t r1; 281 282 r1 = SD_SendCMD(CMD10, 0, 0x01); //读取CID寄存器 283 if(r1==0x00) 284 r1 = SD_ReceiveData(cid_data, 16); //接收16个字节的数据 285 SPISD_CS(0); //取消片选 286 287 return r1?1:0; 288 } 289 290 //获取SD卡的总扇区数 291 uint32_t SD_GetSectorCount(void) 292 { 293 uint8_t n; 294 uint8_t csd[16]; 295 uint16_t csize; 296 uint32_t Capacity; 297 298 if(SD_GetCSD(csd) != 0) //取CSD信息,如果期间出错,返回0 299 return 0; 300 301 if( (csd[0]&0xC0) == 0x40 ) //如果为SDHC卡,按照下面方式计算。V2.00的卡 302 { 303 csize = csd[9] + ((uint16_t)csd[8] << 8) + 1; 304 Capacity = (uint32_t)csize << 10; //得到扇区数 305 }else //V1.xx的卡 306 { 307 n = (csd[5] & 15) + ((csd[10] & 128) >> 7) + ((csd[9] & 3) << 1) + 2; 308 csize = (csd[8] >> 6) + ((uint16_t)csd[7] << 2) + ((uint16_t)(csd[6] & 3) << 10) + 1; 309 Capacity = (uint32_t)csize << (n - 9); //得到扇区数 310 } 311 312 return Capacity; 313 } 314 315 int MSD0_GetCardInfo(PMSD_CARDINFO SD0_CardInfo) 316 { 317 uint8_t r1; 318 uint8_t CSD_Tab[16], CID_Tab[16]; 319 320 /* Send CMD9, Read CSD */ 321 r1 = SD_SendCMD(CMD9, 0, 0xFF); 322 if(r1 != 0x00) 323 return r1; 324 if(SD_ReceiveData(CSD_Tab, 16)) 325 return 1; 326 327 /* Send CMD10, Read CID */ 328 r1 = SD_SendCMD(CMD10, 0, 0xFF); 329 if(r1 != 0x00) 330 return r1; 331 if(SD_ReceiveData(CID_Tab, 16)) 332 return 2; 333 334 /* Byte 0 */ 335 SD0_CardInfo->CSD.CSDStruct = (CSD_Tab[0] & 0xC0) >> 6; 336 SD0_CardInfo->CSD.SysSpecVersion = (CSD_Tab[0] & 0x3C) >> 2; 337 SD0_CardInfo->CSD.Reserved1 = CSD_Tab[0] & 0x03; 338 /* Byte 1 */ 339 SD0_CardInfo->CSD.TAAC = CSD_Tab[1] ; 340 /* Byte 2 */ 341 SD0_CardInfo->CSD.NSAC = CSD_Tab[2]; 342 /* Byte 3 */ 343 SD0_CardInfo->CSD.MaxBusClkFrec = CSD_Tab[3]; 344 /* Byte 4 */ 345 SD0_CardInfo->CSD.CardComdClasses = CSD_Tab[4] << 4; 346 /* Byte 5 */ 347 SD0_CardInfo->CSD.CardComdClasses |= (CSD_Tab[5] & 0xF0) >> 4; 348 SD0_CardInfo->CSD.RdBlockLen = CSD_Tab[5] & 0x0F; 349 /* Byte 6 */ 350 SD0_CardInfo->CSD.PartBlockRead = (CSD_Tab[6] & 0x80) >> 7; 351 SD0_CardInfo->CSD.WrBlockMisalign = (CSD_Tab[6] & 0x40) >> 6; 352 SD0_CardInfo->CSD.RdBlockMisalign = (CSD_Tab[6] & 0x20) >> 5; 353 SD0_CardInfo->CSD.DSRImpl = (CSD_Tab[6] & 0x10) >> 4; 354 SD0_CardInfo->CSD.Reserved2 = 0; /* Reserved */ 355 SD0_CardInfo->CSD.DeviceSize = (CSD_Tab[6] & 0x03) << 10; 356 /* Byte 7 */ 357 SD0_CardInfo->CSD.DeviceSize |= (CSD_Tab[7]) << 2; 358 /* Byte 8 */ 359 SD0_CardInfo->CSD.DeviceSize |= (CSD_Tab[8] & 0xC0) >> 6; 360 SD0_CardInfo->CSD.MaxRdCurrentVDDMin = (CSD_Tab[8] & 0x38) >> 3; 361 SD0_CardInfo->CSD.MaxRdCurrentVDDMax = (CSD_Tab[8] & 0x07); 362 /* Byte 9 */ 363 SD0_CardInfo->CSD.MaxWrCurrentVDDMin = (CSD_Tab[9] & 0xE0) >> 5; 364 SD0_CardInfo->CSD.MaxWrCurrentVDDMax = (CSD_Tab[9] & 0x1C) >> 2; 365 SD0_CardInfo->CSD.DeviceSizeMul = (CSD_Tab[9] & 0x03) << 1; 366 /* Byte 10 */ 367 SD0_CardInfo->CSD.DeviceSizeMul |= (CSD_Tab[10] & 0x80) >> 7; 368 SD0_CardInfo->CSD.EraseGrSize = (CSD_Tab[10] & 0x7C) >> 2; 369 SD0_CardInfo->CSD.EraseGrMul = (CSD_Tab[10] & 0x03) << 3; 370 /* Byte 11 */ 371 SD0_CardInfo->CSD.EraseGrMul |= (CSD_Tab[11] & 0xE0) >> 5; 372 SD0_CardInfo->CSD.WrProtectGrSize = (CSD_Tab[11] & 0x1F); 373 /* Byte 12 */ 374 SD0_CardInfo->CSD.WrProtectGrEnable = (CSD_Tab[12] & 0x80) >> 7; 375 SD0_CardInfo->CSD.ManDeflECC = (CSD_Tab[12] & 0x60) >> 5; 376 SD0_CardInfo->CSD.WrSpeedFact = (CSD_Tab[12] & 0x1C) >> 2; 377 SD0_CardInfo->CSD.MaxWrBlockLen = (CSD_Tab[12] & 0x03) << 2; 378 /* Byte 13 */ 379 SD0_CardInfo->CSD.MaxWrBlockLen |= (CSD_Tab[13] & 0xc0) >> 6; 380 SD0_CardInfo->CSD.WriteBlockPaPartial = (CSD_Tab[13] & 0x20) >> 5; 381 SD0_CardInfo->CSD.Reserved3 = 0; 382 SD0_CardInfo->CSD.ContentProtectAppli = (CSD_Tab[13] & 0x01); 383 /* Byte 14 */ 384 SD0_CardInfo->CSD.FileFormatGrouop = (CSD_Tab[14] & 0x80) >> 7; 385 SD0_CardInfo->CSD.CopyFlag = (CSD_Tab[14] & 0x40) >> 6; 386 SD0_CardInfo->CSD.PermWrProtect = (CSD_Tab[14] & 0x20) >> 5; 387 SD0_CardInfo->CSD.TempWrProtect = (CSD_Tab[14] & 0x10) >> 4; 388 SD0_CardInfo->CSD.FileFormat = (CSD_Tab[14] & 0x0C) >> 2; 389 SD0_CardInfo->CSD.ECC = (CSD_Tab[14] & 0x03); 390 /* Byte 15 */ 391 SD0_CardInfo->CSD.CSD_CRC = (CSD_Tab[15] & 0xFE) >> 1; 392 SD0_CardInfo->CSD.Reserved4 = 1; 393 394 if(SD0_CardInfo->CardType == V2HC) 395 { 396 /* Byte 7 */ 397 SD0_CardInfo->CSD.DeviceSize = (uint16_t)(CSD_Tab[8]) *256; 398 /* Byte 8 */ 399 SD0_CardInfo->CSD.DeviceSize += CSD_Tab[9] ; 400 } 401 402 SD0_CardInfo->Capacity = SD0_CardInfo->CSD.DeviceSize * MSD_BLOCKSIZE * 1024; 403 SD0_CardInfo->BlockSize = MSD_BLOCKSIZE; 404 405 /* Byte 0 */ 406 SD0_CardInfo->CID.ManufacturerID = CID_Tab[0]; 407 /* Byte 1 */ 408 SD0_CardInfo->CID.OEM_AppliID = CID_Tab[1] << 8; 409 /* Byte 2 */ 410 SD0_CardInfo->CID.OEM_AppliID |= CID_Tab[2]; 411 /* Byte 3 */ 412 SD0_CardInfo->CID.ProdName1 = CID_Tab[3] << 24; 413 /* Byte 4 */ 414 SD0_CardInfo->CID.ProdName1 |= CID_Tab[4] << 16; 415 /* Byte 5 */ 416 SD0_CardInfo->CID.ProdName1 |= CID_Tab[5] << 8; 417 /* Byte 6 */ 418 SD0_CardInfo->CID.ProdName1 |= CID_Tab[6]; 419 /* Byte 7 */ 420 SD0_CardInfo->CID.ProdName2 = CID_Tab[7]; 421 /* Byte 8 */ 422 SD0_CardInfo->CID.ProdRev = CID_Tab[8]; 423 /* Byte 9 */ 424 SD0_CardInfo->CID.ProdSN = CID_Tab[9] << 24; 425 /* Byte 10 */ 426 SD0_CardInfo->CID.ProdSN |= CID_Tab[10] << 16; 427 /* Byte 11 */ 428 SD0_CardInfo->CID.ProdSN |= CID_Tab[11] << 8; 429 /* Byte 12 */ 430 SD0_CardInfo->CID.ProdSN |= CID_Tab[12]; 431 /* Byte 13 */ 432 SD0_CardInfo->CID.Reserved1 |= (CID_Tab[13] & 0xF0) >> 4; 433 /* Byte 14 */ 434 SD0_CardInfo->CID.ManufactDate = (CID_Tab[13] & 0x0F) << 8; 435 /* Byte 15 */ 436 SD0_CardInfo->CID.ManufactDate |= CID_Tab[14]; 437 /* Byte 16 */ 438 SD0_CardInfo->CID.CID_CRC = (CID_Tab[15] & 0xFE) >> 1; 439 SD0_CardInfo->CID.Reserved2 = 1; 440 441 return 0; 442 } 443 444 445 //写SD卡 446 //buf:数据缓存区 447 //sector:起始扇区 448 //cnt:扇区数 449 //返回值:0,ok;其他,失败. 450 uint8_t SD_WriteDisk(uint8_t *buf, uint32_t sector, uint8_t cnt) 451 { 452 uint8_t r1; 453 454 if(SD_TYPE != V2HC) 455 sector *= 512; //转换为字节地址 456 if(cnt == 1) 457 { 458 r1 = SD_SendCMD(CMD24, sector, 0x01); //读命令 459 if(r1 == 0) //指令发送成功 460 r1=SD_SendBlock(buf, 0xFE); //写512个字节 461 }else 462 { 463 if(SD_TYPE != MMC) 464 { 465 SD_SendCMD(CMD55, 0, 0x01); 466 SD_SendCMD(CMD23, cnt, 0x01); //发送指令 467 } 468 r1 = SD_SendCMD(CMD25, sector, 0x01); //连续读命令 469 if(r1 == 0) 470 { 471 do 472 { 473 r1 = SD_SendBlock(buf,0xFC); //接收512个字节 474 buf += 512; 475 }while(--cnt && r1==0); 476 r1 = SD_SendBlock(0,0xFD); //接收512个字节 477 } 478 } 479 SPISD_CS(0); //取消片选 480 481 return r1; 482 } 483 484 //读SD卡 485 //buf:数据缓存区 486 //sector:扇区 487 //cnt:扇区数 488 //返回值:0,ok;其他,失败. 489 uint8_t SD_ReadDisk(uint8_t*buf,uint32_t sector,uint8_t cnt) 490 { 491 uint8_t r1; 492 493 if(SD_TYPE != V2HC) 494 sector <<= 9; //转换为字节地址 495 if(cnt == 1) 496 { 497 r1 = SD_SendCMD(CMD17, sector, 0x01); //读命令 498 if(r1 == 0) 499 r1 = SD_ReceiveData(buf, 512); //接收512个字节 500 }else 501 { 502 r1 = SD_SendCMD(CMD18, sector, 0x01); //连续读命令 503 do 504 { 505 r1 = SD_ReceiveData(buf, 512); //接收512个字节 506 buf += 512; 507 }while(--cnt && r1==0); 508 SD_SendCMD(CMD12, 0, 0x01); //发送停止命令 509 } 510 SPISD_CS(0); //取消片选 511 512 return r1; 513 } 514 515 uint8_t SPI_ReadWrite(uint8_t Txdata) 516 { 517 uint8_t Rxdata; 518 HAL_SPI_TransmitReceive(&hspi1, &Txdata, &Rxdata, 1, 100); 519 return Rxdata; 520 } 521 522 //SPI1波特率设置 523 void SPI_SetSpeed(uint8_t speed) 524 { 525 hspi1.Init.BaudRatePrescaler = speed; 526 if (HAL_SPI_Init(&hspi1) != HAL_OK) 527 { 528 Error_Handler(); 529 } 530 } 531 ///////////////////////////END//////////////////////////////////////
drive_spisd.h如下
1 /* Define to prevent recursive inclusion -------------------------------------*/ 2 #ifndef __DRIVE_SPISD_H 3 #define __DRIVE_SPISD_H 4 5 #ifdef __cplusplus 6 extern "C" { 7 #endif 8 9 /* Includes ------------------------------------------------------------------*/ 10 #include "main.h" 11 /* Private includes ----------------------------------------------------------*/ 12 #include "fatfs.h" 13 /* Exported types ------------------------------------------------------------*/ 14 enum _CD_HOLD 15 { 16 HOLD = 0, 17 RELEASE = 1, 18 }; 19 20 typedef struct /* Card Specific Data */ 21 { 22 uint8_t CSDStruct; /* CSD structure */ 23 uint8_t SysSpecVersion; /* System specification version */ 24 uint8_t Reserved1; /* Reserved */ 25 uint8_t TAAC; /* Data read access-time 1 */ 26 uint8_t NSAC; /* Data read access-time 2 in CLK cycles */ 27 uint8_t MaxBusClkFrec; /* Max. bus clock frequency */ 28 uint16_t CardComdClasses; /* Card command classes */ 29 uint8_t RdBlockLen; /* Max. read data block length */ 30 uint8_t PartBlockRead; /* Partial blocks for read allowed */ 31 uint8_t WrBlockMisalign; /* Write block misalignment */ 32 uint8_t RdBlockMisalign; /* Read block misalignment */ 33 uint8_t DSRImpl; /* DSR implemented */ 34 uint8_t Reserved2; /* Reserved */ 35 uint32_t DeviceSize; /* Device Size */ 36 uint8_t MaxRdCurrentVDDMin; /* Max. read current @ VDD min */ 37 uint8_t MaxRdCurrentVDDMax; /* Max. read current @ VDD max */ 38 uint8_t MaxWrCurrentVDDMin; /* Max. write current @ VDD min */ 39 uint8_t MaxWrCurrentVDDMax; /* Max. write current @ VDD max */ 40 uint8_t DeviceSizeMul; /* Device size multiplier */ 41 uint8_t EraseGrSize; /* Erase group size */ 42 uint8_t EraseGrMul; /* Erase group size multiplier */ 43 uint8_t WrProtectGrSize; /* Write protect group size */ 44 uint8_t WrProtectGrEnable; /* Write protect group enable */ 45 uint8_t ManDeflECC; /* Manufacturer default ECC */ 46 uint8_t WrSpeedFact; /* Write speed factor */ 47 uint8_t MaxWrBlockLen; /* Max. write data block length */ 48 uint8_t WriteBlockPaPartial; /* Partial blocks for write allowed */ 49 uint8_t Reserved3; /* Reserded */ 50 uint8_t ContentProtectAppli; /* Content protection application */ 51 uint8_t FileFormatGrouop; /* File format group */ 52 uint8_t CopyFlag; /* Copy flag (OTP) */ 53 uint8_t PermWrProtect; /* Permanent write protection */ 54 uint8_t TempWrProtect; /* Temporary write protection */ 55 uint8_t FileFormat; /* File Format */ 56 uint8_t ECC; /* ECC code */ 57 uint8_t CSD_CRC; /* CSD CRC */ 58 uint8_t Reserved4; /* always 1*/ 59 }MSD_CSD; 60 61 typedef struct /*Card Identification Data*/ 62 { 63 uint8_t ManufacturerID; /* ManufacturerID */ 64 uint16_t OEM_AppliID; /* OEM/Application ID */ 65 uint32_t ProdName1; /* Product Name part1 */ 66 uint8_t ProdName2; /* Product Name part2*/ 67 uint8_t ProdRev; /* Product Revision */ 68 uint32_t ProdSN; /* Product Serial Number */ 69 uint8_t Reserved1; /* Reserved1 */ 70 uint16_t ManufactDate; /* Manufacturing Date */ 71 uint8_t CID_CRC; /* CID CRC */ 72 uint8_t Reserved2; /* always 1 */ 73 }MSD_CID; 74 75 typedef struct 76 { 77 MSD_CSD CSD; 78 MSD_CID CID; 79 uint32_t Capacity; /* Card Capacity */ 80 uint32_t BlockSize; /* Card Block Size */ 81 uint16_t RCA; 82 uint8_t CardType; 83 uint32_t SpaceTotal; /* Total space size in file system */ 84 uint32_t SpaceFree; /* Free space size in file system */ 85 }MSD_CARDINFO, *PMSD_CARDINFO; 86 87 extern MSD_CARDINFO SD0_CardInfo; 88 /* Exported constants --------------------------------------------------------*/ 89 90 /* Exported macro ------------------------------------------------------------*/ 91 //SD卡类型 92 #define ERR 0x00 93 #define MMC 0x01 94 #define V1 0x02 95 #define V2 0x04 96 #define V2HC 0x06 97 98 #define DUMMY_BYTE 0xFF 99 #define MSD_BLOCKSIZE 512 100 101 /* 102 CMD定义 103 bc:发送到所有卡,不返回响应 104 bcr:发送到所有卡,同时接收所有卡的响应 105 ac:发送到选定卡,无数据传输 106 adtc:发送到选定卡,有数据传输 107 */ 108 #define CMD0 0 //bc,卡复位到IDLE状态 109 #define CMD1 1 110 #define CMD8 8 //bcr,读SD卡接口条件,包含主机支持的电压信息,并询问卡是否支持 111 #define CMD9 9 //ac,读CSD数据 112 #define CMD10 10 //ac,读CID数据 113 #define CMD12 12 //ac,停止数据传输 114 #define CMD16 16 //ac,设置块长度(对于SDHC卡块命令长度固定为512字节) 应返回0x00 115 #define CMD17 17 //adtc,读单个块 116 #define CMD18 18 //adtc,读多个块。连续读块,直到被CMD12中断。 117 #define CMD23 23 //设置多sector写入前预先擦除N个block 118 #define CMD24 24 //adtc,写单个块 119 #define CMD25 25 //adtc,写多个块。连续写块,直到被CMD12中断。 120 #define CMD41 41 //应返回0x00 121 #define CMD55 55 //ac,指定下个命令为特定应用命令(ACMD),不是标准命令,应返回0x01 122 #define CMD58 58 //读OCR信息 123 #define CMD59 59 //使能/禁止CRC,应返回0x00 124 125 //数据写入回应字意义 126 #define MSD_DATA_OK 0x05 127 #define MSD_DATA_CRC_ERROR 0x0B 128 #define MSD_DATA_WRITE_ERROR 0x0D 129 #define MSD_DATA_OTHER_ERROR 0xFF 130 131 //SD卡回应标记字 132 #define MSD_RESPONSE_NO_ERROR 0x00 133 #define MSD_IN_IDLE_STATE 0x01 134 #define MSD_ERASE_RESET 0x02 135 #define MSD_ILLEGAL_COMMAND 0x04 136 #define MSD_COM_CRC_ERROR 0x08 137 #define MSD_ERASE_SEQUENCE_ERROR 0x10 138 #define MSD_ADDRESS_ERROR 0x20 139 #define MSD_PARAMETER_ERROR 0x40 140 #define MSD_RESPONSE_FAILURE 0xFF 141 142 /* Exported functions prototypes ---------------------------------------------*/ 143 void SPISD_CS(uint8_t p); 144 uint8_t SD_Init(void); 145 void WritetoSD(BYTE write_buff[],uint8_t bufSize); 146 void FileSystem_Init(void); 147 uint32_t SD_GetSectorCount(void); 148 uint8_t SD_GetCID(uint8_t *cid_data); 149 uint8_t SD_GetCSD(uint8_t *csd_data); 150 int MSD0_GetCardInfo(PMSD_CARDINFO SD0_CardInfo); 151 uint8_t SD_ReadDisk(uint8_t*buf, uint32_t sector, uint8_t cnt); 152 uint8_t SD_WriteDisk(uint8_t*buf, uint32_t sector, uint8_t cnt); 153 void SPI_SetSpeed(uint8_t speed); 154 uint8_t SPI_ReadWrite(uint8_t Txdata); 155 156 /* Private defines -----------------------------------------------------------*/ 157 extern uint8_t SD_TYPE; 158 #ifdef __cplusplus 159 } 160 #endif 161 162 #endif /* __DRIVE_SPISD_H */ 163 164 /************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
然后在文件系统文件夹里修改内容。
user_diskio.c如下
1 /* USER CODE BEGIN Header */ 2 /** 3 ****************************************************************************** 4 * @file user_diskio.c 5 * @brief This file includes a diskio driver skeleton to be completed by the user. 6 ****************************************************************************** 7 * @attention 8 * 9 * Copyright (c) 2023 STMicroelectronics. 10 * All rights reserved. 11 * 12 * This software is licensed under terms that can be found in the LICENSE file 13 * in the root directory of this software component. 14 * If no LICENSE file comes with this software, it is provided AS-IS. 15 * 16 ****************************************************************************** 17 */ 18 /* USER CODE END Header */ 19 20 #ifdef USE_OBSOLETE_USER_CODE_SECTION_0 21 /* 22 * Warning: the user section 0 is no more in use (starting from CubeMx version 4.16.0) 23 * To be suppressed in the future. 24 * Kept to ensure backward compatibility with previous CubeMx versions when 25 * migrating projects. 26 * User code previously added there should be copied in the new user sections before 27 * the section contents can be deleted. 28 */ 29 /* USER CODE BEGIN 0 */ 30 /* USER CODE END 0 */ 31 #endif 32 33 /* USER CODE BEGIN DECL */ 34 35 /* Includes ------------------------------------------------------------------*/ 36 #include <string.h> 37 #include "ff_gen_drv.h" 38 39 /* Private typedef -----------------------------------------------------------*/ 40 #include "drive_spisd.h" 41 /* Private define ------------------------------------------------------------*/ 42 43 /* Private variables ---------------------------------------------------------*/ 44 /* Disk status */ 45 static volatile DSTATUS Stat = STA_NOINIT; 46 47 /* USER CODE END DECL */ 48 49 /* Private function prototypes -----------------------------------------------*/ 50 DSTATUS USER_initialize (BYTE pdrv); 51 DSTATUS USER_status (BYTE pdrv); 52 DRESULT USER_read (BYTE pdrv, BYTE *buff, DWORD sector, UINT count); 53 #if _USE_WRITE == 1 54 DRESULT USER_write (BYTE pdrv, const BYTE *buff, DWORD sector, UINT count); 55 #endif /* _USE_WRITE == 1 */ 56 #if _USE_IOCTL == 1 57 DRESULT USER_ioctl (BYTE pdrv, BYTE cmd, void *buff); 58 #endif /* _USE_IOCTL == 1 */ 59 60 Diskio_drvTypeDef USER_Driver = 61 { 62 USER_initialize, 63 USER_status, 64 USER_read, 65 #if _USE_WRITE 66 USER_write, 67 #endif /* _USE_WRITE == 1 */ 68 #if _USE_IOCTL == 1 69 USER_ioctl, 70 #endif /* _USE_IOCTL == 1 */ 71 }; 72 73 /* Private functions ---------------------------------------------------------*/ 74 75 /** 76 * @brief Initializes a Drive 77 * @param pdrv: Physical drive number (0..) 78 * @retval DSTATUS: Operation status 79 */ 80 DSTATUS USER_initialize ( 81 BYTE pdrv /* Physical drive nmuber to identify the drive */ 82 ) 83 { 84 /* USER CODE BEGIN INIT */ 85 uint8_t res; 86 res = SD_Init(); 87 if(res) //STM32 SPI的bug,在sd卡操作失败的时候如果不执行下面的语句,可能导致SPI读写异常 88 { 89 SPI_SetSpeed(SPI_BAUDRATEPRESCALER_256); 90 SPI_ReadWrite(0xff); //提供额外的8个时钟 91 SPI_SetSpeed(SPI_BAUDRATEPRESCALER_4); 92 } 93 if(res) 94 return STA_NOINIT; 95 else 96 return RES_OK; 97 /* USER CODE END INIT */ 98 } 99 100 /** 101 * @brief Gets Disk Status 102 * @param pdrv: Physical drive number (0..) 103 * @retval DSTATUS: Operation status 104 */ 105 DSTATUS USER_status ( 106 BYTE pdrv /* Physical drive number to identify the drive */ 107 ) 108 { 109 /* USER CODE BEGIN STATUS */ 110 switch (pdrv) 111 { 112 case 0 : 113 return RES_OK; 114 case 1 : 115 return RES_OK; 116 case 2 : 117 return RES_OK; 118 default: 119 return STA_NOINIT; 120 } 121 /* USER CODE END STATUS */ 122 } 123 124 /** 125 * @brief Reads Sector(s) 126 * @param pdrv: Physical drive number (0..) 127 * @param *buff: Data buffer to store read data 128 * @param sector: Sector address (LBA) 129 * @param count: Number of sectors to read (1..128) 130 * @retval DRESULT: Operation result 131 */ 132 DRESULT USER_read ( 133 BYTE pdrv, /* Physical drive nmuber to identify the drive */ 134 BYTE *buff, /* Data buffer to store read data */ 135 DWORD sector, /* Sector address in LBA */ 136 UINT count /* Number of sectors to read */ 137 ) 138 { 139 /* USER CODE BEGIN READ */ 140 uint8_t res; 141 if( !count ) 142 { 143 return RES_PARERR; /* count不能等于0,否则返回参数错误 */ 144 } 145 switch (pdrv) 146 { 147 case 0: 148 res = SD_ReadDisk(buff,sector,count); 149 if(res == 0) 150 return RES_OK; 151 else 152 return RES_ERROR; 153 default: 154 return RES_ERROR; 155 } 156 /* USER CODE END READ */ 157 } 158 159 /** 160 * @brief Writes Sector(s) 161 * @param pdrv: Physical drive number (0..) 162 * @param *buff: Data to be written 163 * @param sector: Sector address (LBA) 164 * @param count: Number of sectors to write (1..128) 165 * @retval DRESULT: Operation result 166 */ 167 #if _USE_WRITE == 1 168 DRESULT USER_write ( 169 BYTE pdrv, /* Physical drive nmuber to identify the drive */ 170 const BYTE *buff, /* Data to be written */ 171 DWORD sector, /* Sector address in LBA */ 172 UINT count /* Number of sectors to write */ 173 ) 174 { 175 /* USER CODE BEGIN WRITE */ 176 /* USER CODE HERE */ 177 uint8_t res; 178 if( !count ) 179 return RES_PARERR; /* count不能等于0,否则返回参数错误 */ 180 switch (pdrv) 181 { 182 case 0: 183 res=SD_WriteDisk((uint8_t *)buff,sector,count); 184 if(res == 0) 185 return RES_OK; 186 else 187 return RES_ERROR; 188 default: 189 return RES_ERROR; 190 } 191 /* USER CODE END WRITE */ 192 } 193 #endif /* _USE_WRITE == 1 */ 194 195 /** 196 * @brief I/O control operation 197 * @param pdrv: Physical drive number (0..) 198 * @param cmd: Control code 199 * @param *buff: Buffer to send/receive control data 200 * @retval DRESULT: Operation result 201 */ 202 #if _USE_IOCTL == 1 203 DRESULT USER_ioctl ( 204 BYTE pdrv, /* Physical drive nmuber (0..) */ 205 BYTE cmd, /* Control code */ 206 void *buff /* Buffer to send/receive control data */ 207 ) 208 { 209 /* USER CODE BEGIN IOCTL */ 210 DRESULT res; 211 switch(cmd) 212 { 213 case CTRL_SYNC: 214 SPISD_CS(1); 215 do{ 216 HAL_Delay(20); 217 }while(SPI_ReadWrite(0xFF)!=0xFF); 218 res=RES_OK; 219 SPISD_CS(0); 220 break; 221 case GET_SECTOR_SIZE: 222 *(WORD*)buff = 512; 223 res = RES_OK; 224 break; 225 case GET_BLOCK_SIZE: 226 *(WORD*)buff = 8; 227 res = RES_OK; 228 break; 229 case GET_SECTOR_COUNT: 230 *(DWORD*)buff = SD_GetSectorCount(); 231 res = RES_OK; 232 break; 233 default: 234 res = RES_PARERR; 235 break; 236 } 237 return res; 238 /* USER CODE END IOCTL */ 239 } 240 #endif /* _USE_IOCTL == 1 */
main.c
1 /* USER CODE BEGIN Header */ 2 /** 3 ****************************************************************************** 4 * @file : main.c 5 * @brief : Main program body 6 ****************************************************************************** 7 * @attention 8 * 9 * Copyright (c) 2023 STMicroelectronics. 10 * All rights reserved. 11 * 12 * This software is licensed under terms that can be found in the LICENSE file 13 * in the root directory of this software component. 14 * If no LICENSE file comes with this software, it is provided AS-IS. 15 * 16 ****************************************************************************** 17 */ 18 /* USER CODE END Header */ 19 /* Includes ------------------------------------------------------------------*/ 20 #include "main.h" 21 #include "fatfs.h" 22 #include "spi.h" 23 #include "usart.h" 24 #include "gpio.h" 25 26 /* Private includes ----------------------------------------------------------*/ 27 /* USER CODE BEGIN Includes */ 28 #include "drive_spisd.h" 29 /* USER CODE END Includes */ 30 31 /* Private typedef -----------------------------------------------------------*/ 32 /* USER CODE BEGIN PTD */ 33 34 /* USER CODE END PTD */ 35 36 /* Private define ------------------------------------------------------------*/ 37 /* USER CODE BEGIN PD */ 38 /* USER CODE END PD */ 39 40 /* Private macro -------------------------------------------------------------*/ 41 /* USER CODE BEGIN PM */ 42 43 /* USER CODE END PM */ 44 45 /* Private variables ---------------------------------------------------------*/ 46 47 /* USER CODE BEGIN PV */ 48 uint8_t status = 0; 49 uint8_t writeBuf[] = "demo program forever no bug!!!\r\n"; 50 /* USER CODE END PV */ 51 52 /* Private function prototypes -----------------------------------------------*/ 53 void SystemClock_Config(void); 54 /* USER CODE BEGIN PFP */ 55 56 /* USER CODE END PFP */ 57 58 /* Private user code ---------------------------------------------------------*/ 59 /* USER CODE BEGIN 0 */ 60 61 /* USER CODE END 0 */ 62 63 /** 64 * @brief The application entry point. 65 * @retval int 66 */ 67 int main(void) 68 { 69 /* USER CODE BEGIN 1 */ 70 UINT Bw; 71 FIL file; 72 uint16_t cb_task = 0; 73 uint8_t res1 = 0, res2 = 0; 74 /* USER CODE END 1 */ 75 76 /* MCU Configuration--------------------------------------------------------*/ 77 78 /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ 79 HAL_Init(); 80 81 /* USER CODE BEGIN Init */ 82 83 /* USER CODE END Init */ 84 85 /* Configure the system clock */ 86 SystemClock_Config(); 87 88 /* USER CODE BEGIN SysInit */ 89 90 /* USER CODE END SysInit */ 91 92 /* Initialize all configured peripherals */ 93 MX_GPIO_Init(); 94 MX_SPI1_Init(); 95 MX_USART1_UART_Init(); 96 MX_FATFS_Init(); 97 /* USER CODE BEGIN 2 */ 98 FileSystem_Init(); //初始化文件系统 99 100 status = 1; 101 res1 = f_open(&file, "sdRW1.txt", FA_OPEN_ALWAYS | FA_WRITE); 102 if((res1 & FR_DENIED) == FR_DENIED) 103 printf("卡存储已满,写入失败! \r\n"); 104 /* USER CODE END 2 */ 105 106 /* Infinite loop */ 107 /* USER CODE BEGIN WHILE */ 108 while (1) 109 { 110 /* USER CODE END WHILE */ 111 112 /* USER CODE BEGIN 3 */ 113 if(status == 1) 114 { 115 if(res1 == FR_OK) 116 { 117 f_lseek(&file, f_size(&file)); //确保写入不会覆盖之前的数据 118 res2 = f_write(&file, writeBuf, sizeof(writeBuf), &Bw); //写数据到SD卡 119 if(res2 != FR_OK) 120 { 121 printf("文件写入失败! \r\n"); 122 HAL_GPIO_WritePin(User_led_GPIO_Port, User_led_Pin, GPIO_PIN_RESET); 123 }else 124 { 125 HAL_GPIO_WritePin(User_led_GPIO_Port, User_led_Pin, GPIO_PIN_SET); 126 } 127 } 128 else 129 { 130 printf("打开文件失败! %d\r\n",res1); 131 } 132 if(++cb_task%4096==0) 133 f_sync(&file); 134 }else if(status == 2) 135 { 136 f_close(&file); 137 f_mount(NULL, USERPath, 1); //取消挂载 138 HAL_GPIO_WritePin(User_led_GPIO_Port, User_led_Pin, GPIO_PIN_RESET); 139 status = 0; 140 } 141 HAL_Delay(10); 142 } 143 /* USER CODE END 3 */ 144 } 145 146 /** 147 * @brief System Clock Configuration 148 * @retval None 149 */ 150 void SystemClock_Config(void) 151 { 152 RCC_OscInitTypeDef RCC_OscInitStruct = {0}; 153 RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; 154 155 /** Configure the main internal regulator output voltage 156 */ 157 __HAL_RCC_PWR_CLK_ENABLE(); 158 __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1); 159 /** Initializes the RCC Oscillators according to the specified parameters 160 * in the RCC_OscInitTypeDef structure. 161 */ 162 RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; 163 RCC_OscInitStruct.HSEState = RCC_HSE_ON; 164 RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; 165 RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; 166 RCC_OscInitStruct.PLL.PLLM = 4; 167 RCC_OscInitStruct.PLL.PLLN = 168; 168 RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2; 169 RCC_OscInitStruct.PLL.PLLQ = 4; 170 if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) 171 { 172 Error_Handler(); 173 } 174 /** Initializes the CPU, AHB and APB buses clocks 175 */ 176 RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK 177 |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; 178 RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; 179 RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; 180 RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4; 181 RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2; 182 183 if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK) 184 { 185 Error_Handler(); 186 } 187 } 188 189 /* USER CODE BEGIN 4 */ 190 void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) 191 { 192 if(GPIO_Pin == User_Key_Pin) 193 { 194 status = 2; 195 } 196 } 197 /* USER CODE END 4 */ 198 199 /** 200 * @brief This function is executed in case of error occurrence. 201 * @retval None 202 */ 203 void Error_Handler(void) 204 { 205 /* USER CODE BEGIN Error_Handler_Debug */ 206 /* User can add his own implementation to report the HAL error return state */ 207 __disable_irq(); 208 while (1) 209 { 210 } 211 /* USER CODE END Error_Handler_Debug */ 212 } 213 214 #ifdef USE_FULL_ASSERT 215 /** 216 * @brief Reports the name of the source file and the source line number 217 * where the assert_param error has occurred. 218 * @param file: pointer to the source file name 219 * @param line: assert_param error line source number 220 * @retval None 221 */ 222 void assert_failed(uint8_t *file, uint32_t line) 223 { 224 /* USER CODE BEGIN 6 */ 225 /* User can add his own implementation to report the file name and line number, 226 ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */ 227 /* USER CODE END 6 */ 228 } 229 #endif /* USE_FULL_ASSERT */
实测是可以的,但是写速度并没有网上说的1M/s那么快,也有可能是我引线的问题。
时代越来越好,开发效率越来越高,希望能帮助到你!!!
还有就是,开源万岁。
posted on 2023-06-07 11:11 Couvrir洪荒猛兽 阅读(915) 评论(1) 编辑 收藏 举报