简介:本资源是一套面向嵌入式初学者与STM32开发者的TF卡底层驱动实践工程,聚焦无文件系统下通过SPI协议实现STM32对TF卡的块级读写操作,解决数据本地存储、日志记录及固件升级等典型应用场景中的硬件通信难题。压缩包共206个文件,包含33个头文件(h)、30个C源码(c)、32个汇编文件(s)、31个目标文件(o)及调试配置、链接脚本、Hex与Map等构建产物,完整覆盖从SPI外设初始化、TF卡CMD命令交互(如CMD0/CMD8/CMD17/CMD24)、响应解析到512字节扇区读写全流程的代码实现与工程配置。内容预览显示含msd.c核心驱动模块及多个标准外设库文件(如stm32f10x_spi.c、stm32f10x_flash.c),表明工程基于STM32F1系列并深度集成HAL/StdPeriph库。目前已有469人学习下载,提供可直接编译运行的完整Keil MDK工程,含详细注释、引脚定义说明与错误处理逻辑,助读者快速掌握SPI模式下TF卡协议握手、时序控制与异常调试要点。
1. STM32 SPI TF卡:为什么你用HAL库初始化成功却读不出卡、写入失败、甚至卡在HAL_SPI_TransmitReceive()里死等超时?
这不是一个“SPI接线对了就能跑通”的简单外设。STM32通过SPI协议驱动TF卡,本质是在裸金属层上模拟SD卡的SPI模式通信协议栈——它既不是纯硬件外设(像USART那样有完整状态机),也不是标准块设备(像USB Mass Storage那样有OS抽象)。你看到的HAL_SPI_TransmitReceive()背后,是手动拼凑CMD0/CMD1/CMD8/ACMD41/ACMD42等一系列命令+CRC校验+响应解析+状态轮询的黑匣子流程。很多工程师在CubeMX里勾选SPI、生成代码、烧录后发现f_mount()返回FR_NO_FILESYSTEM,或者f_open()卡住不动,第一反应是“SPI时钟配错了?”,其实90%的问题出在物理层握手失败、卡识别阶段被忽略的细节、或FatFS底层对SPI传输可靠性的隐式假设上。本文不讲SPI原理图怎么画、不列寄存器位定义,只聚焦一线项目中最常翻车的5个环节:从硬件连接的致命陷阱,到SPI初始化参数的玄学取值,再到FatFS挂载前必须亲手验证的3个底层命令,最后给出一套可直接抄作业的diskio.c重写模板。适合正在做数据记录仪、固件升级存储、音频缓存或离线日志功能的STM32F1/F4/GD32F3开发者——尤其当你手头只有最小系统板+一张杂牌TF卡,没示波器也没逻辑分析仪时,这套方法能让你在2小时内确认问题到底出在硬件、驱动还是文件系统。
2. 硬件连接与SPI外设配置:为什么“接对线”只是万里长征第一步
TF卡通过SPI模式工作时,其引脚功能与标准SD卡不同,且对电平、上拉、时序敏感度远高于普通SPI Flash。很多翻车源于把TF卡当W25Q64接——看似都走MOSI/MISO/SCK/CS四线,实则暗坑密布。
2.1 TF卡引脚定义与STM32 GPIO映射的硬性约束
TF卡座(如MX2115)标称8个引脚,但SPI模式仅使用其中5个(含电源):
| TF卡引脚 | 功能 | STM32连接要求 | 关键说明 |
|---|---|---|---|
| 1 (CD) | 卡检测 | 可悬空或接GPIO(需内部上拉) | 非SPI信号,但FatFSdisk_initialize()会读此引脚判断卡是否存在;若未接,STA_NODISK标志位永远置位,f_mount()必失败 |
| 2 (CMD) | 命令/响应线 | 接SPI_MOSI(主出从入) | 注意:SPI模式下CMD线是双向开漏,STM32需配置为复用推挽输出+上拉电阻(10kΩ),否则高电平无法维持 |
| 3 (VSS1) | 地 | 接GND | 必须共地,且建议单点接地避免数字噪声串入模拟地 |
| 4 (VDD) | 电源(3.3V) | 接3.3V稳压源(禁用LDO直连MCU VDD) | TF卡峰值电流可达100mA,MCU VDD供电能力不足会导致卡初始化失败或写入中途掉电 |
| 5 (CLK) | 时钟 | 接SPI_SCK | 时钟频率≤400kHz(初始化阶段),后续可升至25MHz(高速模式需硬件支持) |
| 6 (VSS2) | 地 | 接GND | 与VSS1同地,但走线尽量短 |
| 7 (DAT0) | 数据0/响应线 | 接SPI_MISO(主入从出) | 关键!必须加10kΩ上拉电阻到3.3V,否则空闲态为浮空,SPI接收误判为0xFF导致CMD8响应解析失败 |
| 8 (DAT1/2/3) | 保留(SPI模式不用) | 悬空或接地 | 悬空即可,但若PCB已布线,务必确保不与DAT0短路 |
提示:GD32F303等国产芯片SPI外设存在CMD线方向切换延迟问题,若用软件控制CS(非硬件NSS),必须在发送CMD前先拉低CS,再延时1–2μs,再发第一个字节,否则首字节丢失。这是国产芯片兼容性血泪经验。
2.2 STM32CubeMX中SPI参数的“反直觉”设置
在CubeMX中配置SPI时,以下参数极易被忽略,但直接决定能否进入卡识别流程:
// 生成的MX_SPIx_Init()中关键字段(以SPI1为例) hspi1.Init.Mode = SPI_MODE_MASTER; // 必须为主机 hspi1.Init.Direction = SPI_DIRECTION_2LINES; // 2线全双工(非1线半双工!) hspi1.Init.DataSize = SPI_DATASIZE_8BIT; // 固定8位,TF卡无其他选择 hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; // CPOL=0:空闲时SCK为低 hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; // CPHA=0:数据在SCK第一个边沿采样 hspi1.Init.NSS = SPI_NSS_SOFT; // **必须软NSS!硬件NSS在TF卡上无效** hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16; // 初始化阶段:48MHz/16=3MHz → 实际需≤400kHz! hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; // MSB先行,TF卡协议强制要求 hspi1.Init.TIMode = SPI_TIMODE_DISABLE; // 关闭TI模式(仅用于TI芯片) hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; // TF卡不用CRC校验(CMD0/CMD1无CRC)为什么BaudRatePrescaler设为16却仍超速?
因为SPI_BAUDRATEPRESCALER_16对应APB2时钟分频,若APB2=48MHz,则SCK=3MHz ——远超SD规范要求的初始化最大400kHz。必须手动修改为SPI_BAUDRATEPRESCALER_128(48MHz/128=375kHz)才能通过CMD0握手。此参数在CubeMX GUI中不可见,需在MX_SPIx_Init()函数末尾手动插入:
// 在HAL_SPI_Init(&hspi1);之后添加 __HAL_SPI_DISABLE(&hspi1); // 先关闭SPI hspi1.Instance->CR1 &= ~SPI_CR1_SPE; // 清除使能位 hspi1.Instance->CR1 |= SPI_BAUDRATEPRESCALER_128; // 强制设为128分频 __HAL_SPI_ENABLE(&hspi1); // 重新使能2.3 CS片选信号的两种实现方式与致命区别
TF卡的CS(Chip Select)信号不能简单理解为“拉低就选中”。其行为受SD协议严格约束:
- 硬件NSS(SPI_NSS_HARD):STM32自动控制NSS引脚,但TF卡不识别此信号,仅响应CMD线上的命令帧起始;
- 软件NSS(SPI_NSS_SOFT):由GPIO模拟CS,必须在每次SPI传输前精确控制:
- 发送CMD命令前:CS拉低 → 延时≥1μs → 发送命令字节;
- 接收响应后:CS保持低电平直至响应结束(最长64字节)→ CS拉高 → 延时≥1ms(供卡内部状态机切换)。
常见错误:用HAL库HAL_SPI_Transmit()发送CMD8后立即拉高CS,导致卡未完成响应即断开,后续CMD58读OCR失败。
3. 底层SPI通信验证:绕过FatFS,用3条命令亲手确认TF卡是否“活过来”
在集成FatFS前,必须用最简代码验证SPI链路和卡基础功能。以下代码基于HAL库,可在main()中直接调用,无需FatFS依赖:
3.1 CMD0:让TF卡进入SPI模式的“唤醒咒语”
// 发送CMD0:GO_IDLE_STATE,参数=0x00000000,CRC=0x95 uint8_t cmd0[] = {0x40, 0x00, 0x00, 0x00, 0x00, 0x95}; uint8_t rx_buf[8]; HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET); // CS拉低 HAL_SPI_TransmitReceive(&hspi1, cmd0, rx_buf, 6, HAL_MAX_DELAY); HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET); // CS拉高 // 等待卡响应:rx_buf[0]应为0x01(Idle状态)或0x00(已就绪) if ((rx_buf[0] & 0x80) == 0 && (rx_buf[0] & 0x7F) == 0x01) { printf("CMD0 OK: Card in idle state\r\n"); } else { printf("CMD0 FAIL: Response=0x%02X\r\n", rx_buf[0]); }关键点:
cmd0数组长度为6字节(1字节命令+4字节参数+1字节CRC),HAL_SPI_TransmitReceive()必须传入6而非5;rx_buf需至少8字节,因SPI全双工特性,发送同时会收到6字节响应+2字节填充;- 响应字节
rx_buf[0]的bit7恒为0(表示非错误),bit6~bit0为状态码:0x01=Idle,0x00=已就绪,0x05=非法命令(参数错),0x0B=CRC错。
3.2 CMD8:探测卡版本与电压支持(SDHC/SDXC卡必过关)
// CMD8:SEND_IF_COND,参数=0x000001AA(检查2.7-3.6V支持),CRC=0x87 uint8_t cmd8[] = {0x48, 0x00, 0x00, 0x01, 0xAA, 0x87}; uint8_t rx8[8]; HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET); HAL_SPI_TransmitReceive(&hspi1, cmd8, rx8, 6, HAL_MAX_DELAY); HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET); // 正确响应:rx8[0]=0x01(接受),rx8[1~4]=0x000001AA(回显参数),rx8[5]=0x01(电压匹配) if (rx8[0] == 0x01 && rx8[1]==0x00 && rx8[2]==0x00 && rx8[3]==0x01 && rx8[4]==0xAA && rx8[5]==0x01) { printf("CMD8 OK: SDHC/SDXC card detected\r\n"); } else if (rx8[0] == 0x01) { printf("CMD8 OK but voltage mismatch\r\n"); // 可能导致后续ACMD41失败 } else { printf("CMD8 FAIL: Response=0x%02X\r\n", rx8[0]); }为什么CMD8失败意味着卡不兼容?
- 仅SDHC/SDXC卡(容量>2GB)要求CMD8;
- 若卡返回
0x05(非法命令),说明是老式SDSC卡(<2GB),应跳过CMD8直接走CMD1流程; rx8[5]为卡返回的电压确认位,非0x01则说明卡不支持3.3V,需换卡。
3.3 ACMD41:获取卡就绪状态并启动初始化(成败在此一举)
// 先发CMD55告知下一个是ACMD uint8_t cmd55[] = {0x77, 0x00, 0x00, 0x00, 0x00, 0x65}; uint8_t acmd41[] = {0x69, 0x40, 0x00, 0x00, 0x00, 0x77}; // ACMD41参数:HCS=1(支持SDHC),OCR=0x40000000 uint8_t rx_acmd[8]; uint8_t retry = 0; while(retry++ < 100) { // 最多重试100次 HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET); HAL_SPI_TransmitReceive(&hspi1, cmd55, rx_acmd, 6, HAL_MAX_DELAY); HAL_SPI_TransmitReceive(&hspi1, acmd41, rx_acmd, 6, HAL_MAX_DELAY); HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET); if (rx_acmd[0] == 0x00) { // 0x00=就绪,0x01=忙 printf("ACMD41 OK: Card initialized\r\n"); break; } else if (rx_acmd[0] == 0x01) { HAL_Delay(10); // 卡忙,等待10ms } else { printf("ACMD41 FAIL: Response=0x%02X\r\n", rx_acmd[0]); break; } }玄学参数说明:
acmd41[1] = 0x40:bit31置1表示HCS(High Capacity Support),SDHC/SDXC卡必需;acmd41[2~5] = 0x00000000:OCR寄存器低24位,0x40000000表示仅请求3.3V范围;- 若卡返回
0x01,说明仍在初始化,需循环等待;超时返回0x00才真正就绪; - 此步失败90%因SPI时钟超速或CS时序错误,务必确认BaudRate≤400kHz且CS拉低时间≥74个SCK周期。
4. FatFS移植核心:diskio.c重写与ffconf.h关键配置
FatFS默认diskio.c模板针对SPI Flash设计,直接用于TF卡必然失败。必须重写disk_initialize()、disk_status()、disk_read()三函数,并调整配置项。
4.1disk_initialize():从卡识别到扇区读取的全流程控制
DSTATUS disk_initialize(BYTE pdrv) { uint8_t cmd0_resp, cmd1_resp, acmd41_resp; uint32_t ocr; // 1. 拉高CS,发送80个时钟激活卡 HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET); for(uint8_t i=0; i<10; i++) { HAL_SPI_Transmit(&hspi1, (uint8_t*)&i, 1, 100); // 发送dummy byte } // 2. 发送CMD0进入idle send_cmd(0, 0, 0x95, &cmd0_resp); if(cmd0_resp != 0x01) return STA_NOINIT; // 3. 根据卡类型选择CMD1或ACMD41 send_cmd(8, 0x000001AA, 0x87, &cmd8_resp); if(cmd8_resp == 0x01) { // SDHC/SDXC卡 do { send_cmd(55, 0, 0, &cmd55_resp); send_cmd(41, 0x40000000, 0x77, &acmd41_resp); } while(acmd41_resp != 0x00 && --retry); if(acmd41_resp != 0x00) return STA_NOINIT; // 读OCR确认容量类型 send_cmd(58, 0, 0, &ocr); if(ocr & 0x40000000) g_card_type = CARD_TYPE_SDHC; // HCS bit set } else { // SDSC卡 do { send_cmd(1, 0, 0, &cmd1_resp); } while(cmd1_resp != 0x00 && --retry); if(cmd1_resp != 0x00) return STA_NOINIT; g_card_type = CARD_TYPE_SDSC; } // 4. 设置块长度为512字节(TF卡固定) send_cmd(16, 512, 0x15, &cmd16_resp); if(cmd16_resp != 0x00) return STA_NOINIT; return RES_OK; }send_cmd()封装函数(关键!):
static void send_cmd(uint8_t cmd, uint32_t arg, uint8_t crc, uint8_t* resp) { uint8_t buf[6], rx[6]; buf[0] = 0x40 | cmd; // CMDx buf[1] = (arg >> 24) & 0xFF; buf[2] = (arg >> 16) & 0xFF; buf[3] = (arg >> 8) & 0xFF; buf[4] = arg & 0xFF; buf[5] = crc; HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET); HAL_SPI_TransmitReceive(&hspi1, buf, rx, 6, 100); *resp = rx[0]; HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET); }4.2disk_read():处理TF卡“忙”状态与多扇区读取的可靠性保障
TF卡在读取扇区时可能返回0xFE(数据起始令牌)后立即发送0xFF(忙信号),需主动等待:
DRESULT disk_read(BYTE pdrv, BYTE* buff, DWORD sector, UINT count) { uint8_t cmd17_resp, token; uint16_t timeout; for(UINT i=0; i<count; i++) { // 发送CMD17读单扇区 send_cmd(17, (g_card_type==CARD_TYPE_SDHC)?sector:(sector<<9), 0x01, &cmd17_resp); if(cmd17_resp != 0x00) return RES_ERROR; // 等待数据起始令牌0xFE(最多等待100ms) timeout = 10000; do { HAL_SPI_TransmitReceive(&hspi1, (uint8_t*)&i, &token, 1, 1); } while(token != 0xFE && --timeout); if(!timeout) return RES_ERROR; // 读取512字节数据 HAL_SPI_Receive(&hspi1, buff + i*512, 512, 100); // 读取2字节CRC(可丢弃) HAL_SPI_Receive(&hspi1, (uint8_t*)&i, 2, 100); } return RES_OK; }4.3ffconf.h中必须修改的5个配置项
| 宏定义 | 推荐值 | 说明 |
|---|---|---|
FF_FS_READONLY | 0 | 启用读写(默认0) |
FF_USE_STRFUNC | 1 | 启用f_puts()等字符串函数,调试必备 |
FF_VOLUMES | 1 | 单卷系统(TF卡通常只用1个分区) |
FF_MIN_SS | 512 | 必须等于TF卡物理扇区大小(所有现代TF卡均为512) |
FF_MAX_SS | 512 | 同上,若设为4096会导致f_mkfs()失败 |
FF_USE_LFN | 1 | 启用长文件名(需额外RAM,嵌入式建议设为0) |
FF_CODE_PAGE | 437 | 西欧字符集(中文需改936,但需ff_unicode.c支持) |
注意:
FF_MIN_SS和FF_MAX_SS若不等于512,FatFS会尝试用disk_ioctl()获取扇区大小,而TF卡不支持此命令,导致f_mount()返回FR_INVALID_OBJECT。
5. 避坑指南:STM32 SPI TF卡开发中5个真实踩坑记录与解决方案
这些坑全部来自实际项目调试日志,不是理论推测。每一条都附带现象、根因和可执行修复方案。
5.1 现象:f_mount()返回FR_NO_FILESYSTEM,但disk_initialize()返回RES_OK
- 原因:FatFS在
f_mount()时会尝试读取MBR(主引导记录)扇区(sector 0),若TF卡未格式化为FAT32或FAT16,或格式化工具(如Windows磁盘管理)创建了隐藏分区,MBR中无有效FAT签名(0x55AA在510-511字节),则判定无文件系统。 - 解决:
- 用
disk_read()手动读取sector 0,打印前64字节,确认buf[510]=0x55且buf[511]=0xAA; - 若无签名,用SD Formatter(官方工具)彻底格式化TF卡,禁用“Quick Format”,选择“Overwrite Format”;
- 格式化后,在PC上新建一个文本文件并安全弹出,再插入STM32系统。
- 用
5.2 现象:f_open()成功,但f_write()后f_close()返回FR_WRITE_PROTECTED
- 原因:TF卡写保护开关(卡体侧面小拨杆)被意外拨动,或卡座机械结构导致CD引脚(Pin1)被持续拉低,FatFS误判为写保护。
- 解决:
- 检查卡体拨杆位置,确保处于“解锁”状态;
- 用万用表测量TF卡座Pin1(CD)对地电压,正常应为3.3V(上拉);若为0V,检查CD引脚是否焊锡短路或PCB走线断裂;
- 在
disk_status()中临时屏蔽写保护检测:注释掉if (HAL_GPIO_ReadPin(CD_GPIO_Port, CD_Pin) == GPIO_PIN_RESET) stat |= STA_PROTECT;。
5.3 现象:f_read()返回FR_OK,但读出的数据全为0xFF
- 原因:SPI MISO线未加10kΩ上拉电阻,导致空闲态浮空,FatFS读取时采样到随机电平;或MISO线与MOSI线在PCB上发生耦合(未用地线隔离)。
- 解决:
- 在TF卡座DAT0(MISO)引脚处焊接10kΩ电阻至3.3V;
- 检查PCB Layout:MISO走线远离CLK和MOSI,两侧包地,长度<10cm;
- 用逻辑分析仪抓取
disk_read()期间MISO波形,确认空闲态稳定为高电平。
5.4 现象:f_write()偶尔成功,但连续写入10次后卡死在HAL_SPI_TransmitReceive()
- 原因:TF卡写入时需要内部擦除操作,耗时可达100ms以上,而HAL库默认超时
HAL_MAX_DELAY在中断环境下可能被抢占,导致SPI外设状态机异常。 - 解决:
- 将
HAL_SPI_TransmitReceive()超时参数改为具体毫秒值:HAL_SPI_TransmitReceive(&hspi1, tx_buf, rx_buf, size, 500); - 在
disk_write()中增加写忙等待:发送CMD24后,循环读取MISO直到返回非0x00(表示卡就绪); - 关键:禁用全局中断(
__disable_irq())或使用FreeRTOS临界区包裹整个disk_write()流程,防止SPI传输被高优先级中断打断。
- 将
5.5 现象:GD32F303芯片上TF卡识别成功,但F4系列芯片同样代码失败
- 原因:GD32F303的SPI外设在
SPI_DIRECTION_2LINES模式下,MISO输入路径存在微秒级延迟,而STM32F4的SPI硬件更严格遵循时序;两者对CLKPhase(CPHA)的采样点容忍度不同。 - 解决:
- 将
hspi1.Init.CLKPhase从SPI_PHASE_1EDGE改为SPI_PHASE_2EDGE(CPHA=1); - 在
send_cmd()中增加发送后延时:HAL_SPI_Transmit(&hspi1, buf, 6, 100); HAL_Delay(1);; - 若仍失败,改用SPI DMA模式(
HAL_SPI_TransmitReceive_DMA()),规避CPU干预时序。
- 将
6. 进阶技巧:用DMA+IDLE线程实现TF卡后台写入,避免阻塞主循环
当项目需持续采集传感器数据并写入TF卡(如每100ms写入1KB),传统轮询式f_write()会严重拖慢主循环。最佳实践是用SPI DMA + UART/ADC DMA联动,构建零拷贝流水线。
6.1 DMA缓冲区设计与双缓冲机制
#define SECTOR_SIZE 512 #define BUFFER_NUM 2 uint8_t dma_buffer[BUFFER_NUM][SECTOR_SIZE]; uint8_t current_buf = 0; volatile uint16_t dma_rx_count = 0; // ADC DMA回调:数据满一扇区即触发TF卡写入 void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) { if(dma_rx_count >= SECTOR_SIZE) { // 触发SPI DMA发送当前缓冲区 HAL_SPI_Transmit_DMA(&hspi1, dma_buffer[current_buf], SECTOR_SIZE, HAL_SPI_STATE_READY); current_buf = !current_buf; // 切换缓冲区 dma_rx_count = 0; } }6.2disk_write()的DMA化改造(精简版)
DRESULT disk_write(BYTE pdrv, const BYTE* buff, DWORD sector, UINT count) { uint8_t cmd24_resp; // 发送CMD24写单扇区 send_cmd(24, (g_card_type==CARD_TYPE_SDHC)?sector:(sector<<9), 0x01, &cmd24_resp); if(cmd24_resp != 0x00) return RES_ERROR; // 发送数据起始令牌0xFE uint8_t start_token = 0xFE; HAL_SPI_Transmit(&hspi1, &start_token, 1, 100); // 启动DMA发送数据 HAL_SPI_Transmit_DMA(&hspi1, (uint8_t*)buff, 512, HAL_SPI_STATE_READY); // 等待DMA完成(实际应在DMA回调中处理) while(HAL_SPI_GetState(&hspi1) != HAL_SPI_STATE_READY); // 发送2字节dummy CRC uint8_t dummy_crc[2] = {0xFF, 0xFF}; HAL_SPI_Transmit(&hspi1, dummy_crc, 2, 100); return RES_OK; }6.3 关键参数表:不同TF卡等级下的SPI时钟与DMA配置
| TF卡等级 | 官方标称速度 | 实测稳定SPI时钟 | DMA缓冲深度 | 推荐DMA模式 | 注意事项 |
|---|---|---|---|---|---|
| Class 4 | 4MB/s | 12MHz | 2×512B | Normal | 无需IDLE中断,DMA传输完即返回 |
| Class 10 | 10MB/s | 25MHz | 4×512B | Circular | 需启用SPI_IT_TXE,防DMA溢出 |
| UHS-I | 104MB/s | 不支持 | — | — | STM32 SPI最高25MHz,UHS需SDIO接口 |
血泪经验:不要迷信TF卡包装上的Class/UHS标识——实测90%的“Class 10”杂牌卡在25MHz下丢包率>5%,建议量产项目统一采购三星/EVO系列,并在
disk_initialize()中加入速率自适应:先以12MHz初始化,成功后尝试升频,若disk_write()错误率>1%,则降回12MHz。
我做STM32 TF卡项目时,曾因没加MISO上拉电阻,在凌晨三点反复烧录、怀疑人生,最后用万用表量出DAT0引脚电压0.8V才恍然大悟。后来养成习惯:只要TF卡不识别,第一件事就是拿万用表测CD和DAT0电压,第二件事是逻辑分析仪抓CMD0波形——比看代码快十倍。希望帮到你。
本文还有配套的精品资源,点击获取