恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

STM32F103 SPI读写SD卡:从命令帧到FatFs移植实践

  • 首页
  • 资讯中心
  • /
  • STM32F103 SPI读写SD卡:从命令帧到FatFs移植实践

相关资讯

PTB-XL心电数据集分类实战:从环境搭建到PyTorch模型训练完整指南 2026/9/16 21:13:26
Django网页防篡改取证系统:HTML结构化比对与图像完整性校验 2026/9/16 21:13:26
瑞数6代JSVMP动态Cookie逆向:从412到200的攻防拆解 2026/9/16 21:13:26

最新资讯

《长沙红棉板材哪里有?5 家板材 / 全屋定制渠道对比》
军用信号处理板级需求规格书:需求工程实战方法与指标验证
打包虚拟环境到当前目录
YOLOv8实操指南:从环境配置到RK3588端侧部署
Velero 镜像标签策略:SemVer 发布标签、latest 与 main 标签的使用与实现原理
Oracle 19c RAC集群节点重新添加全流程详解:从环境准备到实例验证

今日推荐

IoT-For-Beginners 智能语音计时器:Wio Terminal 基于 DMAC 与 Flash 的音频采集实战
基于MATLAB的CRI显色指数计算:从SPD光谱到Ra的完整流程
JSP+Servlet+MySQL博客系统源码部署与优化全攻略

本周热门

AI SDK Harness 依赖更新指南:掌握 harness 包 SDK 依赖的升级、桥接同步与一致性校验
Refine v5 Ant Design NumberField 组件实战:基于 Intl 的本地化数字格式化
Flutter应用改名全指南:从Android到iOS的配置与工具实践

本月精选

自研推理加速器Redwood:两周内实现PyTorch模型高效部署的实战教程
V4L2摄像头采集实战:从camera_client.rar到出图全流程解析
从“谁发明了钢琴键”到知识问答智能体:RAG与记忆工程实践

STM32F103 SPI读写SD卡:从命令帧到FatFs移植实践

发布时间:2026/9/16 21:13:26
STM32F103 SPI读写SD卡:从命令帧到FatFs移植实践 简介在这份工程资源中围绕STM32F103微控制器搭建了一个基于串行外设接口SPI的存储卡读卡器示例可在Blue Pill开发板上运行通过SPI总线同存储卡通信再借助USB接口与上位机交换数据适合嵌入式开发者、电子爱好者及单片机初学者学习参考。整个压缩包约1.87兆字节共包含171个文件绝大多数为头文件与C语言源文件分别占96个和60个同时附有少量配置文件、工程文件及链接脚本可在System Workbench环境中直接导入、编译与烧录。工程内部提供了完整的底层驱动库涵盖存储卡、串行外设接口、通用输入输出及USB通信的初始化配置并明确给出了各引脚连接定义例如片选线接PA4、时钟线接PA5、主入从出接PA6、主出从入接PA7同时指出USB数据正线需要外接上拉电阻。另外工程目录按模块组织核心初始化代码与驱动库分层清晰便于查阅和修改。通过学习这套示例能够掌握单片机基于SPI协议读写存储卡的完整流程理解外设驱动封装、时钟配置与USB枚举机制也能为后续开发带存储功能的嵌入式应用提供可复用的代码框架。目前已有1188人学习下载适合作为课程设计或嵌入式入门项目的参考范例。1. 用 STM32F103 的 SPI 读 SD 卡先把协议栈看清再动手STM32F103 内置 SDIO 外设理论读卡带宽比 SPI 高出一倍多但 SDIO 把 PD/PC 一整组引脚和中断、DMA 全部牵扯进来。真到做读卡器、做日志存储或固件备份这类工具型项目我反而先走 SPI四根线接完就能跑命令帧是 48 位字节流SPI 时序和卡状态都能用逻辑分析仪直接看。这套方案覆盖了 SD 卡 SPI 协议里最常用的一段——上电、CMD0/CMD8/ACMD41 初始化、CMD17/CMD18 读块最后接 FatFs 读到具体文件。适合不想在第一版就被 SDIO 时钟分频和 DMA 中断绊住的场景。2. SD 卡 SPI 协议命令帧、响应与片选时序2.1 先从最小系统板的 sd 卡原理图看懂 4 根线microSD 座子有 8 个引脚但进 SPI 模式后真正用的只有四根SCLK、MOSI卡侧叫 DI、MISO卡侧叫 DO、CS卡侧是 DAT3。SPI 模式下 DAT2、DAT1 完全闲置所以读卡器的原理图比 SDIO 方案简单得多一个 8 脚 microSD 座、4 根信号线、3 个去耦电容就够。原理图里容易被忽略的是上拉CS 和 MISO 各放一个 10kΩ 上拉到 3.3VMOSI 和 SCLK 走线短可以不加。STM32F103 的 GPIO 就是 3.3V 电平microSD 也是 3.3V直连可行但我习惯在每根线上串 22Ω 电阻杜邦线超过 10cm 时边沿振铃能小不少。引脚映射按最小系统板默认走SPI1 的 SCK 在 PA5、MISO 在 PA6、MOSI 在 PA7CS 不接硬件 NSS用 PA4 普通 GPIO。这个映射不是唯一的SPI1 的引脚可以重映射到 PB3/PB4/PB5但默认映射最容易对照板子调试。SPI2 也能做引脚在 PB13/PB14/PB15只是挂在 APB1 上最高时钟只有 36MHz读卡场景没有必要非用 SPI1。2.2 48 位命令帧CMD0 到 CMD58 的同一个拼装模板SD 卡在 SPI 模式下没有命令表之外的花样所有操作都是 6 字节命令帧。格式固定1 bit 起始位(0) 1 bit 传输位(1) 6 bit 命令号 32 bit 参数 7 bit CRC 1 bit 停止位(1)共 48 位。落到 8 位 SPI 的字节流里就是 frame[0] 0x40 | cmdframe[1] 到 frame[4] 是参数大端frame[5] 是 CRC 和停止位拼一个字节void sd_send_cmd_frame(SPI_TypeDef *spi, uint8_t cmd, uint32_t arg, uint8_t crc) { uint8_t frame[6]; frame[0] 0x40 | (cmd 0x3F); // 起始位 传输位 命令号 frame[1] (arg 24) 0xFF; // 32 位参数按大端输出 frame[2] (arg 16) 0xFF; frame[3] (arg 8) 0xFF; frame[4] arg 0xFF; frame[5] (crc 1) | 0x01; // 7 位 CRC最低位是停止位 for (int i 0; i 6; i) { while (SPI_I2S_GetFlagStatus(spi, SPI_I2S_FLAG_TXE) RESET); SPI_I2S_SendData(spi, frame[i]); } }frame[5] 是最容易写错的地方。SD 卡规范里 CRC 只有 7 位停止位 1 位拼进一个字节正好是 (crc 1) | 0x01。初始化阶段只有 CMD0 和 CMD8 的 CRC 必须精确CMD0 固定 0x95、CMD8 固定 0x87进 SPI 模式后 CRC 校验默认关闭后续命令的 CRC 字节随便给都能过惯例写成 0x01。读卡场景真正用到的命令就这么几条命令参数期望响应用途CMD00x00000000R1 0x01复位并进入 SPI 模式CMD80x000001AAR1 0x01 回显 0x1AA识别 SDV2 卡与电压范围CMD550x00000000R1宣告下一条是 ACMDACMD410x40000000R1 0x00上电初始化HCS 位选 SDHCCMD580x00000000R3 5 字节读 OCR判断 CCS 地址单位CMD17块地址R1 0x00 数据块单块读CMD18块地址R1 0x00 连续数据块多块读CMD12 停止CMD120x00000000R1 0xFF 零字节停止多块读ACMD41 不是独立命令必须先发 CMD55 再发 CMD41中间不能插别的帧这是 SPI 模式最容易犯的顺序错误。2.3 响应判定最高位为 0 才算数命令发完卡不会立刻回SPI 是对称的主机出时钟、从机出数据所以主机得继续发 0xFF 把时钟打出来卡的响应就在这些空时钟里移出来。等响应的策略是轮询读回的字节最高位是 1 就继续读最高位变 0 说明响应起始字节到了。R1 就一个字节0x01 表示卡在 idle 状态0x00 就绪其余位是错误标志。R3/R7 都是 5 字节先等 R1再读 4 字节CMD8 的 R7 低 12 位必须回显 0x1AA回显不对说明卡不支持 3.3V 工作电压。读写字节在 STM32F103 上是同一个全双工动作发一个字节必然收到一个字节底层就一个函数uint8_t sd_spi_xfer(uint8_t data) { while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) RESET); SPI_I2S_SendData(SPI1, data); while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) RESET); return SPI_I2S_ReceiveData(SPI1); }sd_spi_xfer 是后面所有命令、响应、数据读写的唯一地基。一个实际经验响应字节之后很多卡还要几个空时钟才把数据令牌移上来所以在读数据之前多打 0xFF 时钟能消掉大半「偶发读错一两个字节」的怪问题。3. STM32F103 SPI 主模式配置标准库、CubeMX 与片选3.1 标准外设库 v3.5 的 SPI1 初始化SD 卡要求 SPI 模式 0CPOL 0时钟空闲低电平、CPHA 0第一个边沿采样。STM32F103 上电默认就是这个相位关键是后面改配置别动 CPOL/CPHA。初始化阶段 SPI 时钟必须不超过 400kHzAPB2 的 72MHz 除 256 得 281kHz正好压线等卡完成初始化再提速。标准外设库 v3.5 的完整初始化void spi1_init(void) { GPIO_InitTypeDef gpio; SPI_InitTypeDef spi; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1, ENABLE); gpio.GPIO_Pin GPIO_Pin_5 | GPIO_Pin_7; // SCK、MOSI推挽复用 gpio.GPIO_Mode GPIO_Mode_AF_PP; gpio.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, gpio); gpio.GPIO_Pin GPIO_Pin_6; // MISO输入靠外部上拉 gpio.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, gpio); gpio.GPIO_Pin GPIO_Pin_4; // CS普通推挽软件控制 gpio.GPIO_Mode GPIO_Mode_Out_PP; gpio.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, gpio); GPIO_SetBits(GPIOA, GPIO_Pin_4); // CS 默认高电平 spi.SPI_Direction SPI_Direction_2Lines_FullDuplex; spi.SPI_Mode SPI_Mode_Master; spi.SPI_DataSize SPI_DataSize_8b; spi.SPI_CPOL SPI_CPOL_Low; // 模式 0空闲低 spi.SPI_CPHA SPI_CPHA_1Edge; // 模式 0第一边沿采样 spi.SPI_NSS SPI_NSS_Soft; // 软件片选 spi.SPI_BaudRatePrescaler SPI_BaudRatePrescaler_256; // 281kHz spi.SPI_FirstBit SPI_FirstBit_MSB; SPI_Init(SPI1, spi); SPI_Cmd(SPI1, ENABLE); }SPI_FirstBit 必须是 MSBSD 命令帧是大端MISO 配成浮空输入而不是上拉输入是为了让外部 10kΩ 上拉起主导权板卡上有其他器件时不会打架。SPI_NSS_Soft 配置后还要确认 CR1 的 SSI 位是 1否则外设认为自己没被选中第一次发送直接出 MODF 错误TXE 再也不置位现象就是命令帧第一个字节死都发不出去。3.2 CubeMX 生成工程的最小改动CubeMX 的配置对应关系要认清楚SPI1 选 Full-Duplex MasterPrescaler 先选 256CPOL 选 LowCPHA 选 1 EdgeNSS 选 Disable。生成后在 MX_SPI1_Init 里把 NSS 改成软件模式并打开 SPIvoid MX_SPI1_Init(void) { 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; // 默认生成是 DISABLE hspi1.Init.BaudRatePrescaler SPI_BAUDRATEPRESCALER_256; hspi1.Init.FirstBit SPI_FIRSTBIT_MSB; HAL_SPI_Init(hspi1); __HAL_SPI_ENABLE(hspi1); // 生成代码不会帮你使能 }HAL 的一个坑是 HAL_SPI_Init 只配置寄存器不使能 SPE得手动 __HAL_SPI_ENABLE。HAL 库读写函数拆成 Transmit 和 Receive 两个但 SPI 全双工收的同时必须发所以读卡场景我直接用 HAL_SPI_TransmitReceive发送缓冲填 0xFF 打时钟uint8_t sd_hal_xfer(uint8_t data) { uint8_t tx data, rx 0; HAL_SPI_TransmitReceive(hspi1, tx, rx, 1, 100); return rx; }超时参数给 100ms正常响应几个字节内就回来超时说明 CS 没拉低或卡没上电属于方向性错误不该靠拉长时间兜底。3.3 软件片选与硬件片选SD 卡要求命令期间 CS 全程拉低SPI_NSS_Hard 会让外设每次传输开始自动拉低 NSS、结束拉高。SD 卡 SPI 协议的 CS 语义是整个命令-响应-数据阶段保持低电平硬件片选按字节翻转卡在命令帧中途看到 CS 拉高会直接判定命令中止。所以读卡器一律用软件片选NSS 配 SoftCS 用普通 GPIO发 CMD 前拉低数据全部读完再拉高。Linux 下 SPI 驱动里用 gpiod 控制片选也是同样的思路——把片选当普通信号管不交给 SPI 控制器自动翻转。软件模拟 SPI 同理片选逻辑和时钟逻辑分开写CS 电平保持时间完全由代码掌握调试时示波器探 CS 波形一眼就能看出是不是中途跳变。4. SD 卡初始化序列CMD0、CMD8、ACMD41 一步都不能省4.1 完整的最小初始化函数SD 卡上电后默认走 SD 模式必须靠 CMD0 才切进 SPI 模式而且 CMD0 之前要满足两个条件CS 保持高电平、时钟至少打 74 个。之后拉低 CS 依次执行 CMD0、CMD8、ACMD41。完整函数static uint8_t card_type; // CT_SDSC / CT_SDHC决定地址单位 uint8_t sd_init(void) { uint8_t resp, r7[5], ocr[5]; uint32_t timeout; SD_CS_HIGH(); for (int i 0; i 10; i) sd_spi_xfer(0xFF); // 80 个时钟 SD_CS_LOW(); resp sd_send_cmd_long(CMD0, 0x00000000, 0x95, r7, 1); if (resp ! 0x01) return SD_ERR_CMD0; // 必须 idle resp sd_send_cmd_long(CMD8, 0x000001AA, 0x87, r7, 5); if (resp 0x01 r7[3] 0x01 r7[4] 0xAA) { card_type CT_SD2; // SDV2/SDHC/SDXC } else if (resp 0x05) { card_type CT_SD1; // 老卡不支持 CMD8 } else { return SD_ERR_CMD8; } timeout 1000; do { sd_send_cmd_long(CMD55, 0x00000000, 0x01, r7, 1); resp sd_send_cmd_long(CMD41, (card_type CT_SD2) ? 0x40000000 : 0x00000000, 0x01, r7, 1); } while (resp ! 0x00 --timeout); if (timeout 0 || (resp 0x7E)) return SD_ERR_ACMD41; if (card_type CT_SD2) { sd_send_cmd_long(CMD58, 0, 0x01, ocr, 5); card_type (ocr[1] 0x40) ? CT_SDHC : CT_SDSC; // CCS 位 } return SD_OK; }sd_send_cmd_long 是上一章命令帧函数的扩展先等 R1 再读后续字节返回后参数校验在调用方做。整个序列里 CMD0 和 CMD8 的 CRC 是写死的CMD41 的 CRC 已经无所谓。4.2 响应位含义从 0x01 到 0x00 的迁移CMD0 返回 0x01 只是说卡进了 SPI 模式并处于 idle后面还有初始化没完成。CMD8 的判定分两路响应 0x01 且回显 0x1AA说明是 SDV2 及以上ACMD41 要带 HCS 位响应 0x05illegal command说明是 SDV1 老卡ACMD41 的 HCS 位必须清零否则老卡可能一直回 0x01 不推进。ACMD41 循环里 resp 从 0x01 变 0x00 表示初始化完成如果看到 0x3F 之类的值说明 CMD55/CMD41 之间插了别的帧整个序列要从 CMD0 重来。R1 的状态位含义值得记一张表位含义bit70响应起始标志读到 1 说明还在等待bit6参数错误Parameter Errorbit5地址错误Address Errorbit4擦除序列错误bit3CRC 错误bit2非法命令CMD8 对老卡回 0x05 就是它bit1擦除复位bit01idle 状态0就绪CMD58 只对 SDV2 卡做。OCR 的第 30 位 CCS 决定地址单位CCS1 是 SDHC/SDXC卡内部按块寻址后面 CMD17/CMD18 的地址直接传扇区号CCS0 是 SDSC地址要传字节地址也就是扇区号左移 9。这个位记不住也正常关键是初始化代码里落成一个 card_type 全局变量读块和接 FatFs 时都要用它换算。4.3 初始化卡死的三个典型位置第一个位置是 CMD0 永远等不到 0x01读回的全是 0xFF。先查 CS 是不是真拉低了再查 SCK 有没有时钟输出最后查 MISO 上拉——MISO 浮空时读到的一定是 0xFF。第二个位置是 ACMD41 循环超时常见于上电后 80 个时钟没打够或者 CMD8 阶段把老卡误判成 SDV2 带了 HCS 位。第三个位置是 CMD8 回显不对读回的字节不是 0x1AA多半是卡供电电压或接触问题换一张已知好卡对比能快速定位。初始化阶段把关键响应打到串口上比事后猜省事得多printf(CMD0%02X CMD8%02X R7%02X%02X%02X%02X ACMD41%02X\r\n, r0, r8, r7[1], r7[2], r7[3], r7[4], resp);5. 块读取实现CMD17 单块、CMD18 多块与 DMA 搬运5.1 CMD17 单块读取在 0xFF 里等 0xFE 令牌初始化完成后读数据分四步发 CMD17、等 R1、等数据令牌 0xFE、收 512 字节加 2 字节 CRC。数据令牌是一个独立于命令响应的字节值为 0xFE卡在响应和令牌之间可能插入任意数量的 0xFF所以要用轮询等不能假设固定间隔。uint8_t sd_read_block(uint32_t addr, uint8_t *buf) { uint8_t resp, token; uint32_t timeout; resp sd_send_cmd_long(CMD17, addr, 0x01, resp, 1); if (resp ! 0x00) return SD_ERR_RESP; timeout 0xFFFFF; do { token sd_spi_xfer(0xFF); } while (token 0xFF --timeout); if (token ! 0xFE) return SD_ERR_TOKEN; // 0xFE 才是数据起始 for (int i 0; i 512; i) { buf[i] sd_spi_xfer(0xFF); // 每个字节都由 0xFF 打时钟 } sd_spi_xfer(0xFF); // CRC 高字节读完丢弃 sd_spi_xfer(0xFF); // CRC 低字节 SD_CS_HIGH(); return SD_OK; }注意 CMD17 的地址参数SDHC 直接传扇区号SDSC 要传扇区号 9。卡片在 SPI 模式下不对数据块做 CRC 校验接收方可以读掉丢弃但如果后面要接真伪校验逻辑这两字节位置不能错——很多人数据对但整体错位就是 CRC 没读就直接去读下一块导致的。5.2 CMD18 多块读取连续数据流与 CMD12 终止多块读发一次 CMD18之后连续读 N 个 512 字节块每块前面都有一个 0xFE 令牌最后发 CMD12 才能让卡退出连续传输状态。实现时把「等令牌 收 512 字节」循环执行uint8_t sd_read_multi(uint32_t addr, uint32_t count, uint8_t *buf) { uint8_t resp sd_send_cmd_long(CMD18, addr, 0x01, resp, 1); if (resp ! 0x00) return SD_ERR_RESP; for (uint32_t i 0; i count; i) { uint32_t timeout 0xFFFFF; while (sd_spi_xfer(0xFF) ! 0xFE --timeout); if (timeout 0) { sd_send_cmd_long(CMD12, 0, 0x01, resp, 1); // 出错先停传输 return SD_ERR_TOKEN; } for (int j 0; j 512; j) buf[i * 512 j] sd_spi_xfer(0xFF); sd_spi_xfer(0xFF); sd_spi_xfer(0xFF); } sd_send_cmd_long(CMD12, 0, 0x01, resp, 1); // 正常结束也要 CMD12 SD_CS_HIGH(); return SD_OK; }CMD12 之后卡还会回若干 0xFF之后再拉高 CS 更稳妥。多块读最大的收益是省掉每块之间的命令往返连续读 1MB 时吞吐差距能到 30% 以上日志转储和镜像备份场景都值得用。5.3 提速与 DMA时钟分频上限和搬运瓶颈SPI 模式卡的最高时钟一般是 25MHzSTM32F103 的 SPI1 挂在 APB2 上 72MHz预分频取 /4 得 18MHz安全取 /2 得 36MHz 超过卡规格量产卡可能偶发读错。SPI2 挂在 APB1 只有 36MHz同样 /2 也是 18MHz。初始化完成后改分频值直接改 CR1 的 BR 位即可。CPU 逐字节搬 512 字节在高频下会拖慢整体常见做法是等令牌后用 DMA 收数据。SPI1_RX 对应 DMA1_Channel2方向外设到内存数据宽度 8 位void sd_read_block_dma(uint32_t addr, uint8_t *buf) { uint8_t resp; sd_send_cmd_long(CMD17, addr, 0x01, resp, 1); while (sd_spi_xfer(0xFF) ! 0xFE); // 等令牌仍是轮询 DMA_DeInit(DMA1_Channel2); DMA_InitTypeDef dma; dma.DMA_PeripheralBaseAddr (uint32_t)SPI1-DR; dma.DMA_MemoryBaseAddr (uint32_t)buf; dma.DMA_DIR DMA_DIR_PeripheralSRC; dma.DMA_BufferSize 514; // 512 数据 2 CRC dma.DMA_PeripheralInc DMA_PeripheralInc_Disable; dma.DMA_MemoryInc DMA_MemoryInc_Enable; dma.DMA_PeripheralDataSize DMA_PeripheralDataSize_Byte; dma.DMA_MemoryDataSize DMA_MemoryDataSize_Byte; dma.DMA_Mode DMA_Mode_Normal; dma.DMA_Priority DMA_Priority_High; DMA_Init(DMA1_Channel2, dma); DMA_Cmd(DMA1_Channel2, ENABLE); while (DMA_GetFlagStatus(DMA1_FLAG_TC2) RESET); DMA_ClearFlag(DMA1_FLAG_TC2); SD_CS_HIGH(); }DMA 搬运 514 字节包含 512 数据加 2 CRC搬运完再管 CSbuf 缓冲区必须留够 514 字节。DMA 模式下 SPI 的 RXNE 会被硬件清掉不要在 DMA 传输中间再用手动读 DR否则数据会错位。软件模拟 SPI 在这种场景下的劣势就在这逐位翻转 GPIO 最高也就 1~2MHz读一个扇区要多花几十倍时间因此读卡器这类数据密集型项目优先用硬件 SPI。6. 验证与排错串口打印扇区、对接 FatFs6.1 读扇区 0用 MBR 特征验证整条链路初始化成功后第一个验证动作是读扇区 0 并打印前面 16 个字节。格式化过的卡扇区 0 要么是 MBR 要么是引导扇区偏移 510/511 一定出现 0x55 0xAAFAT32 引导扇区以 0xEB 0x58 0x90 开头。这两个特征任一出现说明读链路已经通后面再接文件系统才有意义。串口打印时把数据按 16 进制分行输出对照 WinHex 或 DiskGenius 里看到的扇区内容能顺带验证字节序和地址换算没写反。6.2 FatFs 底层最小移植FatFs 只认扇区号disk_read 里按 card_type 决定地址单位DRESULT disk_read(BYTE pdrv, BYTE *buff, LBA_t sector, UINT count) { if (card_type CT_SDSC) sector 9; // SDSC 转字节地址 if (count 1) return sd_read_block(sector, buff) ? RES_ERROR : RES_OK; return sd_read_multi(sector, count, buff) ? RES_ERROR : RES_OK; }f_mount 之前 disk_initialize 返回 STA_NOINIT 就会失败最常见原因是初始化里把命令和数据阶段的 CS 控制混在一起——CS 必须在整个读块期间保持低不能在等令牌时就提前拉高。6.3 三个高频坑上拉、写保护、时钟极性第一个坑是 MISO 悬空。原理图漏上拉时现象很典型CMD0 永远回 0xFF初始化直接死在第一步逻辑分析仪看 MISO 是一条平的浮空电平。第二个坑是写保护。SPI 模式下主控读不到卡座锁开关的状态适配器锁住时读完全正常、写才失败遇到「没锁但是写保护」多半是适配器锁开关老化接触不良或者卡座 DAT3 脚虚焊——DAT3 在 SPI 模式就是 CS接触电阻会同时引发初始化不稳定。第三个坑是时钟极性。SPI 模式 0 和模式 3 都能被多数卡接受但模式 1 的采样沿正好和卡的数据输出窗口错开表现为响应偶发、数据块里固定某几位翻转。调整完这三个点再回看初始化阶段打印的响应码基本能一次跑通从 CMD0 到 f_open 的整条链路。本文还有配套的精品资源点击获取

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号