[APM32F4] 【APM32F411V开发板测评】+SPI_SDCARD+Fatfs简单测试

[复制链接]
1171|1
 楼主| wuxiubo 发表于 2024-5-30 16:57 | 显示全部楼层 |阅读模式
7173533b009de3ca59fdcb64c8cdde8e
首先我们要知道SD卡是可以两种通信方式的,一种是SDIO,一种是SPI。
SD卡版本说明
SD卡版本:SD V1.X(即SD标准卡)最大容量2GB
SD V2.0 2.0版本的标准卡,最多2GB
SD V2.0HC 2.0高容量卡,最多32GB
说明: 本程序主要针对SD卡2.0 HC 2.0高容量卡协议进行说明。
SD卡默认操作的扇区大小是512字节。扇区大小,可以通过指令设置。就算不是512,也可以通过指令设置成512,因为这个值不太大,占用内存不太多,适合单片机使用。
有时候遇到卡不能挂载可能是卡的原因,仔细看报错信息,然后换卡。2.0和1.0的卡命令不相同。
ab03381958fa2ab5a8b55a58ea4ecd84
我们在其基础上选择SPI2,和FATFS两个选项,由于SPI通信方式的SD卡在其中并未支持,所以SD卡中的选项并未支持,后续要通过自己添加函数进行通信。
9090bcc79a68d2528cb8758cb8a448ec
其自己会生成中间件和应用函数。
  1. /**

  2. ******************************************************************************

  3. * [url=home.php?mod=space&uid=288409]@file[/url] user_diskio_spi.c

  4. * [url=home.php?mod=space&uid=247401]@brief[/url] This file contains the implementation of the user_diskio_spi FatFs

  5. * driver.

  6. ******************************************************************************

  7. * Portions copyright (C) 2014, ChaN, all rights reserved.

  8. * Portions copyright (C) 2017, kiwih, all rights reserved.

  9. *

  10. * This software is a free software and there is NO WARRANTY.

  11. * No restriction on use. You can use, modify and redistribute it for

  12. * personal, non-profit or commercial products UNDER YOUR RESPONSIBILITY.

  13. * Redistributions of source code must retain the above copyright notice.

  14. *

  15. ******************************************************************************

  16. */

  17. //This code was ported by kiwih from a copywrited (C) library written by ChaN

  18. //available at http://elm-chan.org/fsw/ff/ffsample.zip

  19. //(text at http://elm-chan.org/fsw/ff/00index_e.html)

  20. //This file provides the FatFs driver functions and SPI code required to manage

  21. //an SPI-connected MMC or compatible SD card with FAT

  22. //It is designed to be wrapped by a cubemx generated user_diskio.c file.

  23. #include "stm32f4xx_hal.h" /* Provide the low-level HAL functions */

  24. #include "user_diskio_spi.h"

  25. //Make sure you set #define SD_SPI_HANDLE as some hspix in main.h

  26. //Make sure you set #define SD_CS_GPIO_Port as some GPIO port in main.h

  27. //Make sure you set #define SD_CS_Pin as some GPIO pin in main.h

  28. extern SPI_HandleTypeDef hspi2;

  29. /* Function prototypes */

  30. //(Note that the _256 is used as a mask to clear the prescalar bits as it provides binary 111 in the correct position)

  31. #define FCLK_SLOW() { MODIFY_REG(hspi2.Instance->CR1, SPI_BAUDRATEPRESCALER_256, SPI_BAUDRATEPRESCALER_128); }/* Set SCLK = slow, approx 280 KBits/s*/

  32. #define FCLK_FAST() { MODIFY_REG(hspi2.Instance->CR1, SPI_BAUDRATEPRESCALER_256, SPI_BAUDRATEPRESCALER_8); }/* Set SCLK = fast, approx 4.5 MBits/s */

  33. #define CS_HIGH(){HAL_GPIO_WritePin(SD_CS_GPIO_Port, SD_CS_Pin, GPIO_PIN_SET);}

  34. #define CS_LOW(){HAL_GPIO_WritePin(SD_CS_GPIO_Port, SD_CS_Pin, GPIO_PIN_RESET);}

  35. /*--------------------------------------------------------------------------

  36. Module Private Functions

  37. ---------------------------------------------------------------------------*/

  38. /* MMC/SD command */

  39. #define CMD0(0)/* GO_IDLE_STATE */

  40. #define CMD1(1)/* SEND_OP_COND (MMC) */

  41. #defineACMD41(0x80+41)/* SEND_OP_COND (SDC) */

  42. #define CMD8(8)/* SEND_IF_COND */

  43. #define CMD9(9)/* SEND_CSD */

  44. #define CMD10(10)/* SEND_CID */

  45. #define CMD12(12)/* STOP_TRANSMISSION */

  46. #define ACMD13(0x80+13)/* SD_STATUS (SDC) */

  47. #define CMD16(16)/* SET_BLOCKLEN */

  48. #define CMD17(17)/* READ_SINGLE_BLOCK */

  49. #define CMD18(18)/* READ_MULTIPLE_BLOCK */

  50. #define CMD23(23)/* SET_BLOCK_COUNT (MMC) */

  51. #defineACMD23(0x80+23)/* SET_WR_BLK_ERASE_COUNT (SDC) */

  52. #define CMD24(24)/* WRITE_BLOCK */

  53. #define CMD25(25)/* WRITE_MULTIPLE_BLOCK */

  54. #define CMD32(32)/* ERASE_ER_BLK_START */

  55. #define CMD33(33)/* ERASE_ER_BLK_END */

  56. #define CMD38(38)/* ERASE */

  57. #define CMD55(55)/* APP_CMD */

  58. #define CMD58(58)/* READ_OCR */

  59. /* MMC card type flags (MMC_GET_TYPE) */

  60. #define CT_MMC0x01/* MMC ver 3 */

  61. #define CT_SD10x02/* SD ver 1 */

  62. #define CT_SD20x04/* SD ver 2 */

  63. #define CT_SDC(CT_SD1|CT_SD2)/* SD */

  64. #define CT_BLOCK0x08/* Block addressing */

  65. static volatile

  66. DSTATUS Stat = STA_NOINIT;/* Physical drive status */

  67. static

  68. BYTE CardType;/* Card type flags */

  69. uint32_t spiTimerTickStart;

  70. uint32_t spiTimerTickDelay;

  71. void SPI_Timer_On(uint32_t waitTicks) {

  72. spiTimerTickStart = HAL_GetTick();

  73. spiTimerTickDelay = waitTicks;

  74. }

  75. uint8_t SPI_Timer_Status() {

  76. return ((HAL_GetTick() - spiTimerTickStart) < spiTimerTickDelay);

  77. }

  78. /*-----------------------------------------------------------------------*/

  79. /* SPI controls (Platform dependent) */

  80. /*-----------------------------------------------------------------------*/

  81. /* Exchange a byte */

  82. static

  83. BYTE xchg_spi (

  84. BYTE dat/* Data to send */

  85. )

  86. {

  87. BYTE rxDat;

  88. HAL_SPI_TransmitReceive(&hspi2, &dat, &rxDat, 1, 1000);

  89. return rxDat;

  90. }

  91. /* Receive multiple byte */

  92. static

  93. void rcvr_spi_multi (

  94. BYTE *buff,/* Pointer to data buffer */

  95. UINT btr/* Number of bytes to receive (even number) */

  96. )

  97. {

  98. for(UINT i=0; i<btr; i++) {

  99. *(buff+i) = xchg_spi(0xFF);

  100. }

  101. }

  102. #if _USE_WRITE

  103. /* Send multiple byte */

  104. static

  105. void xmit_spi_multi (

  106. const BYTE *buff,/* Pointer to the data */

  107. UINT btx/* Number of bytes to send (even number) */

  108. )

  109. {

  110. for(UINT i=0; i<btx; i++) {

  111. xchg_spi(*(buff+i));

  112. }

  113. }

  114. #endif

  115. /*-----------------------------------------------------------------------*/

  116. /* Wait for card ready */

  117. /*-----------------------------------------------------------------------*/

  118. static

  119. int wait_ready (/* 1:Ready, 0:Timeout */

  120. UINT wt/* Timeout [ms] */

  121. )

  122. {

  123. BYTE d;

  124. //wait_ready needs its own timer, unfortunately, so it can't use the

  125. //spi_timer functions

  126. uint32_t waitSpiTimerTickStart;

  127. uint32_t waitSpiTimerTickDelay;

  128. waitSpiTimerTickStart = HAL_GetTick();

  129. waitSpiTimerTickDelay = (uint32_t)wt;

  130. do {

  131. d = xchg_spi(0xFF);

  132. /* This loop takes a time. Insert rot_rdq() here for multitask envilonment. */

  133. } while (d != 0xFF && ((HAL_GetTick() - waitSpiTimerTickStart) < waitSpiTimerTickDelay));/* Wait for card goes ready or timeout */

  134. return (d == 0xFF) ? 1 : 0;

  135. }

  136. /*-----------------------------------------------------------------------*/

  137. /* Despiselect card and release SPI */

  138. /*-----------------------------------------------------------------------*/

  139. static

  140. void despiselect (void)

  141. {

  142. CS_HIGH();/* Set CS# high */

  143. xchg_spi(0xFF);/* Dummy clock (force DO hi-z for multiple slave SPI) */

  144. }

  145. /*-----------------------------------------------------------------------*/

  146. /* Select card and wait for ready */

  147. /*-----------------------------------------------------------------------*/

  148. static

  149. int spiselect (void)/* 1:OK, 0:Timeout */

  150. {

  151. CS_LOW();/* Set CS# low */

  152. xchg_spi(0xFF);/* Dummy clock (force DO enabled) */

  153. if (wait_ready(500)) return 1;/* Wait for card ready */

  154. despiselect();

  155. return 0;/* Timeout */

  156. }

  157. /*-----------------------------------------------------------------------*/

  158. /* Receive a data packet from the MMC */

  159. /*-----------------------------------------------------------------------*/

  160. static

  161. int rcvr_datablock (/* 1:OK, 0:Error */

  162. BYTE *buff,/* Data buffer */

  163. UINT btr/* Data block length (byte) */

  164. )

  165. {

  166. BYTE token;

  167. SPI_Timer_On(200);

  168. do {/* Wait for DataStart token in timeout of 200ms */

  169. token = xchg_spi(0xFF);

  170. /* This loop will take a time. Insert rot_rdq() here for multitask envilonment. */

  171. } while ((token == 0xFF) && SPI_Timer_Status());

  172. if(token != 0xFE) return 0;/* Function fails if invalid DataStart token or timeout */

  173. rcvr_spi_multi(buff, btr);/* Store trailing data to the buffer */

  174. xchg_spi(0xFF); xchg_spi(0xFF);/* Discard CRC */

  175. return 1;/* Function succeeded */

  176. }

  177. /*-----------------------------------------------------------------------*/

  178. /* Send a data packet to the MMC */

  179. /*-----------------------------------------------------------------------*/

  180. #if _USE_WRITE

  181. static

  182. int xmit_datablock (/* 1:OK, 0:Failed */

  183. const BYTE *buff,/* Ponter to 512 byte data to be sent */

  184. BYTE token/* Token */

  185. )

  186. {

  187. BYTE resp;

  188. if (!wait_ready(500)) return 0;/* Wait for card ready */

  189. xchg_spi(token);/* Send token */

  190. if (token != 0xFD) {/* Send data if token is other than StopTran */

  191. xmit_spi_multi(buff, 512);/* Data */

  192. xchg_spi(0xFF); xchg_spi(0xFF);/* Dummy CRC */

  193. resp = xchg_spi(0xFF);/* Receive data resp */

  194. if ((resp & 0x1F) != 0x05) return 0;/* Function fails if the data packet was not accepted */

  195. }

  196. return 1;

  197. }

  198. #endif

  199. /*-----------------------------------------------------------------------*/

  200. /* Send a command packet to the MMC */

  201. /*-----------------------------------------------------------------------*/

  202. static

  203. BYTE send_cmd (/* Return value: R1 resp (bit7==1:Failed to send) */

  204. BYTE cmd,/* Command index */

  205. DWORD arg/* Argument */

  206. )

  207. {

  208. BYTE n, res;

  209. if (cmd & 0x80) {/* Send a CMD55 prior to ACMD<n> */

  210. cmd &= 0x7F;

  211. res = send_cmd(CMD55, 0);

  212. if (res > 1) return res;

  213. }

  214. /* Select the card and wait for ready except to stop multiple block read */

  215. if (cmd != CMD12) {

  216. despiselect();

  217. if (!spiselect()) return 0xFF;

  218. }

  219. /* Send command packet */

  220. xchg_spi(0x40 | cmd);/* Start + command index */

  221. xchg_spi((BYTE)(arg >> 24));/* Argument[31..24] */

  222. xchg_spi((BYTE)(arg >> 16));/* Argument[23..16] */

  223. xchg_spi((BYTE)(arg >> 8));/* Argument[15..8] */

  224. xchg_spi((BYTE)arg);/* Argument[7..0] */

  225. n = 0x01;/* Dummy CRC + Stop */

  226. if (cmd == CMD0) n = 0x95;/* Valid CRC for CMD0(0) */

  227. if (cmd == CMD8) n = 0x87;/* Valid CRC for CMD8(0x1AA) */

  228. xchg_spi(n);

  229. /* Receive command resp */

  230. if (cmd == CMD12) xchg_spi(0xFF);/* Diacard following one byte when CMD12 */

  231. n = 10;/* Wait for response (10 bytes max) */

  232. do {

  233. res = xchg_spi(0xFF);

  234. } while ((res & 0x80) && --n);

  235. return res;/* Return received response */

  236. }

  237. /*--------------------------------------------------------------------------

  238. Public FatFs Functions (wrapped in user_diskio.c)

  239. ---------------------------------------------------------------------------*/

  240. //The following functions are defined as inline because they aren't the functions that

  241. //are passed to FatFs - they are wrapped by autogenerated (non-inline) cubemx template

  242. //code.

  243. //If you do not wish to use cubemx, remove the "inline" from these functions here

  244. //and in the associated .h

  245. /*-----------------------------------------------------------------------*/

  246. /* Initialize disk drive */

  247. /*-----------------------------------------------------------------------*/

  248. inline DSTATUS USER_SPI_initialize (

  249. BYTE drv/* Physical drive number (0) */

  250. )

  251. {

  252. BYTE n, cmd, ty, ocr[4];

  253. if (drv != 0) return STA_NOINIT;/* Supports only drive 0 */

  254. //assume SPI already init init_spi();/* Initialize SPI */

  255. if (Stat & STA_NODISK) return Stat;/* Is card existing in the soket? */

  256. //FCLK_SLOW();//低速模式

  257. //FCLK_FAST();//快速模式

  258. for (n = 10; n; n--) xchg_spi(0xFF);/* Send 80 dummy clocks */

  259. ty = 0;

  260. if (send_cmd(CMD0, 0) == 1) {/* Put the card SPI/Idle state */

  261. SPI_Timer_On(1000);/* Initialization timeout = 1 sec */

  262. if (send_cmd(CMD8, 0x1AA) == 1) {/* SDv2? */

  263. for (n = 0; n < 4; n++) ocr[n] = xchg_spi(0xFF);/* Get 32 bit return value of R7 resp */

  264. if (ocr[2] == 0x01 && ocr[3] == 0xAA) {/* Is the card supports vcc of 2.7-3.6V? */

  265. while (SPI_Timer_Status() && send_cmd(ACMD41, 1UL << 30)) ;/* Wait for end of initialization with ACMD41(HCS) */

  266. if (SPI_Timer_Status() && send_cmd(CMD58, 0) == 0) {/* Check CCS bit in the OCR */

  267. for (n = 0; n < 4; n++) ocr[n] = xchg_spi(0xFF);

  268. ty = (ocr[0] & 0x40) ? CT_SD2 | CT_BLOCK : CT_SD2;/* Card id SDv2 */

  269. }

  270. }

  271. } else {/* Not SDv2 card */

  272. if (send_cmd(ACMD41, 0) <= 1) {/* SDv1 or MMC? */

  273. ty = CT_SD1; cmd = ACMD41;/* SDv1 (ACMD41(0)) */

  274. } else {

  275. ty = CT_MMC; cmd = CMD1;/* MMCv3 (CMD1(0)) */

  276. }

  277. while (SPI_Timer_Status() && send_cmd(cmd, 0)) ;/* Wait for end of initialization */

  278. if (!SPI_Timer_Status() || send_cmd(CMD16, 512) != 0)/* Set block length: 512 */

  279. ty = 0;

  280. }

  281. }

  282. CardType = ty;/* Card type */

  283. despiselect();

  284. if (ty) {/* OK */

  285. FCLK_FAST();/* Set fast clock */

  286. Stat &= ~STA_NOINIT;/* Clear STA_NOINIT flag */

  287. } else {/* Failed */

  288. Stat = STA_NOINIT;

  289. }

  290. return Stat;

  291. }

  292. /*-----------------------------------------------------------------------*/

  293. /* Get disk status */

  294. /*-----------------------------------------------------------------------*/

  295. inline DSTATUS USER_SPI_status (

  296. BYTE drv/* Physical drive number (0) */

  297. )

  298. {

  299. if (drv) return STA_NOINIT;/* Supports only drive 0 */

  300. return Stat;/* Return disk status */

  301. }

  302. /*-----------------------------------------------------------------------*/

  303. /* Read sector(s) */

  304. /*-----------------------------------------------------------------------*/

  305. inline DRESULT USER_SPI_read (

  306. BYTE drv,/* Physical drive number (0) */

  307. BYTE *buff,/* Pointer to the data buffer to store read data */

  308. DWORD sector,/* Start sector number (LBA) */

  309. UINT count/* Number of sectors to read (1..128) */

  310. )

  311. {

  312. if (drv || !count) return RES_PARERR;/* Check parameter */

  313. if (Stat & STA_NOINIT) return RES_NOTRDY;/* Check if drive is ready */

  314. if (!(CardType & CT_BLOCK)) sector *= 512;/* LBA ot BA conversion (byte addressing cards) */

  315. if (count == 1) {/* Single sector read */

  316. if ((send_cmd(CMD17, sector) == 0)/* READ_SINGLE_BLOCK */

  317. && rcvr_datablock(buff, 512)) {

  318. count = 0;

  319. }

  320. }

  321. else {/* Multiple sector read */

  322. if (send_cmd(CMD18, sector) == 0) {/* READ_MULTIPLE_BLOCK */

  323. do {

  324. if (!rcvr_datablock(buff, 512)) break;

  325. buff += 512;

  326. } while (--count);

  327. send_cmd(CMD12, 0);/* STOP_TRANSMISSION */

  328. }

  329. }

  330. despiselect();

  331. return count ? RES_ERROR : RES_OK;/* Return result */

  332. }

  333. /*-----------------------------------------------------------------------*/

  334. /* Write sector(s) */

  335. /*-----------------------------------------------------------------------*/

  336. #if _USE_WRITE

  337. inline DRESULT USER_SPI_write (

  338. BYTE drv,/* Physical drive number (0) */

  339. const BYTE *buff,/* Ponter to the data to write */

  340. DWORD sector,/* Start sector number (LBA) */

  341. UINT count/* Number of sectors to write (1..128) */

  342. )

  343. {

  344. if (drv || !count) return RES_PARERR;/* Check parameter */

  345. if (Stat & STA_NOINIT) return RES_NOTRDY;/* Check drive status */

  346. if (Stat & STA_PROTECT) return RES_WRPRT;/* Check write protect */

  347. if (!(CardType & CT_BLOCK)) sector *= 512;/* LBA ==> BA conversion (byte addressing cards) */

  348. if (count == 1) {/* Single sector write */

  349. if ((send_cmd(CMD24, sector) == 0)/* WRITE_BLOCK */

  350. && xmit_datablock(buff, 0xFE)) {

  351. count = 0;

  352. }

  353. }

  354. else {/* Multiple sector write */

  355. if (CardType & CT_SDC) send_cmd(ACMD23, count);/* Predefine number of sectors */

  356. if (send_cmd(CMD25, sector) == 0) {/* WRITE_MULTIPLE_BLOCK */

  357. do {

  358. if (!xmit_datablock(buff, 0xFC)) break;

  359. buff += 512;

  360. } while (--count);

  361. if (!xmit_datablock(0, 0xFD)) count = 1;/* STOP_TRAN token */

  362. }

  363. }

  364. despiselect();

  365. return count ? RES_ERROR : RES_OK;/* Return result */

  366. }

  367. #endif

  368. /*-----------------------------------------------------------------------*/

  369. /* Miscellaneous drive controls other than data read/write */

  370. /*-----------------------------------------------------------------------*/

  371. #if _USE_IOCTL

  372. inline DRESULT USER_SPI_ioctl (

  373. BYTE drv,/* Physical drive number (0) */

  374. BYTE cmd,/* Control command code */

  375. void *buff/* Pointer to the conrtol data */

  376. )

  377. {

  378. DRESULT res;

  379. BYTE n, csd[16];

  380. DWORD *dp, st, ed, csize;

  381. if (drv) return RES_PARERR;/* Check parameter */

  382. if (Stat & STA_NOINIT) return RES_NOTRDY;/* Check if drive is ready */

  383. res = RES_ERROR;

  384. switch (cmd) {

  385. case CTRL_SYNC :/* Wait for end of internal write process of the drive */

  386. if (spiselect()) res = RES_OK;

  387. break;

  388. case GET_SECTOR_COUNT :/* Get drive capacity in unit of sector (DWORD) */

  389. if ((send_cmd(CMD9, 0) == 0) && rcvr_datablock(csd, 16)) {

  390. if ((csd[0] >> 6) == 1) {/* SDC ver 2.00 */

  391. csize = csd[9] + ((WORD)csd[8] << 8) + ((DWORD)(csd[7] & 63) << 16) + 1;

  392. *(DWORD*)buff = csize << 10;

  393. } else {/* SDC ver 1.XX or MMC ver 3 */

  394. n = (csd[5] & 15) + ((csd[10] & 128) >> 7) + ((csd[9] & 3) << 1) + 2;

  395. csize = (csd[8] >> 6) + ((WORD)csd[7] << 2) + ((WORD)(csd[6] & 3) << 10) + 1;

  396. *(DWORD*)buff = csize << (n - 9);

  397. }

  398. res = RES_OK;

  399. }

  400. break;

  401. case GET_BLOCK_SIZE :/* Get erase block size in unit of sector (DWORD) */

  402. if (CardType & CT_SD2) {/* SDC ver 2.00 */

  403. if (send_cmd(ACMD13, 0) == 0) {/* Read SD status */

  404. xchg_spi(0xFF);

  405. if (rcvr_datablock(csd, 16)) {/* Read partial block */

  406. for (n = 64 - 16; n; n--) xchg_spi(0xFF);/* Purge trailing data */

  407. *(DWORD*)buff = 16UL << (csd[10] >> 4);

  408. res = RES_OK;

  409. }

  410. }

  411. } else {/* SDC ver 1.XX or MMC */

  412. if ((send_cmd(CMD9, 0) == 0) && rcvr_datablock(csd, 16)) {/* Read CSD */

  413. if (CardType & CT_SD1) {/* SDC ver 1.XX */

  414. *(DWORD*)buff = (((csd[10] & 63) << 1) + ((WORD)(csd[11] & 128) >> 7) + 1) << ((csd[13] >> 6) - 1);

  415. } else {/* MMC */

  416. *(DWORD*)buff = ((WORD)((csd[10] & 124) >> 2) + 1) * (((csd[11] & 3) << 3) + ((csd[11] & 224) >> 5) + 1);

  417. }

  418. res = RES_OK;

  419. }

  420. }

  421. break;

  422. case CTRL_TRIM :/* Erase a block of sectors (used when _USE_ERASE == 1) */

  423. if (!(CardType & CT_SDC)) break;/* Check if the card is SDC */

  424. if (USER_SPI_ioctl(drv, MMC_GET_CSD, csd)) break;/* Get CSD */

  425. if (!(csd[0] >> 6) && !(csd[10] & 0x40)) break;/* Check if sector erase can be applied to the card */

  426. dp = buff; st = dp[0]; ed = dp[1];/* Load sector block */

  427. if (!(CardType & CT_BLOCK)) {

  428. st *= 512; ed *= 512;

  429. }

  430. if (send_cmd(CMD32, st) == 0 && send_cmd(CMD33, ed) == 0 && send_cmd(CMD38, 0) == 0 && wait_ready(30000)) {/* Erase sector block */

  431. res = RES_OK;/* FatFs does not check result of this command */

  432. }

  433. break;

  434. default:

  435. res = RES_PARERR;

  436. }

  437. despiselect();

  438. return res;

  439. }

  440. #endif

我们添加上SPI通信的接口。修改user_diskio.c中间的函数。
c210aaa3ecd0cc1b00956c79309713e4
在主函数中添加应用函数,通过fres追踪其返回值就能看到是否有报错信息。
b3ab949a62f3d7f9ea0fe6ac995e51c1
根据其内容我们确定SD卡中的内容需要创建txt文档,然后他会读取文档内容和写入文档内容。
705050a12944a5faae64c46f8e1b71ae
最终测试可以正常读写。
1d6854807946cb12dd91982fdb9bb3b7

星辰大海不退缩 发表于 2024-6-22 21:01 | 显示全部楼层
都是用ST的进行兼容性移植
您需要登录后才可以回帖 登录 | 注册

本版积分规则

67

主题

259

帖子

2

粉丝
快速回复 在线客服 返回列表 返回顶部