ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

STM32H7上FreeRTOS与SDMMC冲突导致FatFs挂载失败的原因与解决

STM32H7上FreeRTOS与SDMMC冲突导致FatFs挂载失败的原因与解决 STM32H7上同时跑FreeRTOS和SDMMCFatFs在f_mount这一步直接给我甩了个失败返回值这个坑我踩了不止一次。先亮个结论大部分这类问题并不是FatFs本身导致的而是RTOS环境改变了SDMMC外设的运行条件。这篇文章我会把从现象、原理、排错到最终配置的完整链路全部拆开适合正在调STM32H7 SDMMC FreeRTOS FatFs组合的嵌入式工程师参考特别是那种“裸机能过一加RTOS就挂”的诡异情况。1. 同一个f_mount调用裸机秒过、带上FreeRTOS就挂——先把问题边界划清楚1.1 现象复现与错误码信息我调试的板子是STM32H750SD卡用的是4线SDMMC模式CubeMX生成代码底层HAL库上面跑了FreeRTOS。单独一个裸机工程同一个SD卡、同一套引脚配置f_mount一次就过读写文件也正常。一旦把SDMMC初始化和文件系统挂载这段逻辑放进FreeRTOS任务里f_mount返回值经常不是FR_NOT_READY3就是FR_NO_FILESYSTEM13。偶尔能挂上但复位后再挂又失败完全看心情。先说FatFs返回码的含义这对排查方向很重要返回值十进值含义FR_OK0成功FR_NOT_READY3物理驱动器无法工作底层驱动初始化失败或读取扇区失败FR_NO_FILESYSTEM13卷上没有有效的FAT文件系统可能是卡没格式化也可能是底层读错了数据FR_INT_ERR2底层驱动返回了预期外的扇区数或状态我看到FR_NOT_READY第一反应是底层SD卡驱动没有准备好。但问题是裸机明明能过驱动本身应该是可用的。真正的原因集中在RTOS的调度行为、中断优先级、内存访问权限、时基冲突这几方面改变了SDMMC底层驱动的工作条件。1.2 初步排除清单不是所有卡和工程都有问题在深入代码之前我建议先快速排除几个最常见、代价最低的因素避免在后面排查中把时间浪费在配置错误上。第一SD卡本身格式。FatFs默认配置下只支持FAT12/16/32如果卡是exFAT旧版本FatFs会返回FR_NO_FILESYSTEM。我的卡是FAT32格式但也不排除有些卡出厂是exFAT。建议一开始就在Windows或Linux上格式化FAT32排除这个变量。第二CubeMX生成的SDMMC引脚配置。这里最容易漏的是CMD、CK、D0-D3的上下拉电阻。SD卡总线在空闲状态下CMD和D0-D3都需要被拉高如果CubeMX生成的GPIO配置里没有正确设置上拉卡可能偶尔能识别偶尔不行。裸机能过说明大概率没问题但如果你的板子上有外部上拉这个因素可以放一放。第三SDMMC的时钟频率。初始化阶段SD卡时钟必须在400kHz以下SD规范要求传输阶段可以切到25MHz或50MHz。CubeMX会自动处理分频值但如果你手动改过时钟树初始化时钟可能被设置得太高导致CMD0直接无响应。这个也要先确认一遍。把以上三个变量排除之后剩下的问题才是真正和FreeRTOS相关的下面一层层分析。2. FreeRTOS SDMMC组合下的三大“隐形刺客”时序、内存、中断优先级2.1 SD卡初始化过程的硬性时序为什么会被RTOS悄悄撕碎很多人在调SD卡时并不关心SD卡协议本身的时序要求HAL库封装得太好导致我们以为只要调用HAL_SD_Init就万事大吉。实际上SD卡上电后存在一个明确的初始化序列上电至少等待2.5ms让卡内部电源稳定然后发送CMD0进入空闲态接着发送CMD8确认电压范围再发送ACMD41不断轮询直到卡退出上电流程busy状态结束。这个过程有一个关键特性ACMD41的轮询次数在极端情况下可能很多尤其是首次上电时卡内部如果正在做坏块管理或擦除响应会明显变慢。裸机环境下CPU从发CMD0到等ACMD41的过程中没有被其他代码抢占几百毫秒内完成初始化很正常。但在FreeRTOS环境下初始化代码运行在一个任务里如果这个任务优先级不是最高那么它随时可能被其他任务抢占。一旦ACMD41的响应延迟刚好超过HAL库内部的超时时间HAL_SD_Init内部用的是基于HAL_GetTick的超时循环就会被判定为超时卡初始化失败。更微妙的是有些卡在ACMD41失败一次后需要重新发送CMD0才能恢复状态机而我们的代码并不会自动做这种重试最后f_mount自然返回FR_NOT_READY。这个问题的本质不是SDMMC本身的问题而是RTOS的任务抢占影响了SD卡初始化函数的连续执行。解决办法并不是把所有任务都暂停这会违背用RTOS的初衷而是要在设计上保证给SD卡初始化任务一个足够高的优先级或者把整个初始化放在启动调度器之前完成或者加入重试机制。2.2 H7专坑DMA看不见DTCM里的数据如果你用CubeMX选择了SDMMC的DMA模式很多工程为了性能都会选DMA那么恭喜你H7还有一个独特的深坑等着——DMA无法访问DTCM内存。STM32H7的内存布局里DTCM0x20000000 - 0x2001FFFF是直接挂在CPU内核上的紧耦合内存访问延迟极低因此一些启动文件、堆栈默认会被链接到DTCM区域。但问题是片上外设的DMA控制器是访问不了DTCM的。如果你定义在C文件里的全局缓冲区被链接到了DTCMSDMMC的IDMA在读取或写入时根本拿不到数据。具体表现就是HAL_SD_ReadBlocks返回HAL_OK但缓冲区的数据全是0x00或0xFF或者HAL_SD_WriteBlocks返回HAL_OK但卡上写的是乱码。FatFs去读MBR主引导记录时读到全是空数据识别不到有效的分区表于是返回FR_NO_FILESYSTEM或FR_INT_ERR。这个问题在裸机工程里可能不会暴露因为裸机工程如果没开优化变量可能被分配到SRAM或者AXI SRAM但如果开了高优化等级或者工程默认链接脚本把所有静态变量放DTCM就会踩中。而FreeRTOS工程中堆栈、任务控制块甚至用户全局变量都可能因为链接脚本配置而被塞进DTCM所以带上RTOS反而更容易触发这个隐藏问题。解决思路很简单把SDMMC DMA所需的数据缓冲区明确放到DMA可访问的区域比如AXI SRAM0x24000000、SRAM1/2/30x30000000或者SRAM40x38000000。最稳妥的做法是定义一个大数组并指定段属性。2.3 中断优先级配置违背FreeRTOS规则回调函数直接死锁第三个隐形刺客是中断优先级这个坑非常隐蔽而且一旦踩中排查成本极高。FreeRTOS要求所有能从ISR中调用FreeRTOS API的中断其优先级数值必须大于等于configMAX_SYSCALL_INTERRUPT_PRIORITY。我用CubeMX生成FreeRTOS工程时默认configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY是5也就是说中断优先级数值不能小于5否则不能在中断里调用FromISR结尾的函数。SDMMC的DMA传输完成中断或者SDMMC自身的全局中断在HAL库的例程里往往被设置为最高优先级优先级数值为0或1因为在裸机环境下我们希望SD卡中断能及时响应。但放到FreeRTOS工程里如果SDMMC中断优先级高于FreeRTOS可管理中断的优先级阈值就会出问题。最常见的情况是当SDMMC中断触发时FreeRTOS内核正在临界区或调度器被锁定的状态下但此时中断又调用了FromISR函数导致内核状态被破坏。表现可能是挂载偶尔成功、偶尔崩溃也可能是f_mount在调用过程中直接进入HardFault或者卡死在某个等待信号量的地方。另外还有一个H7上非常容易忽略的问题如果使用了DMA模式DMA的传输完成中断也需要设置合适的优先级。很多工程只改了SDMMC本身的中断优先级忘了改DMA的中断优先级同样会造成问题。还有一个和FreeRTOS关系很大的坑HAL库默认使用SysTick作为时间基准但FreeRTOS也使用SysTick作为系统节拍。两个功能抢同一个中断源RTO系统节拍和HAL的HAL_GetTick都会乱套。HAL_Delay和SDMMC的超时判断全依赖HAL_GetTick一旦时基被FreeRTOS接管HAL_GetTick可能长时间不更新或跳变SD卡初始化函数的超时判断就会失效。CubeMX在生成FreeRTOS工程时通常会自动把HAL时基切换到TIM6或其他定时器但如果你自己手动搭工程这一步经常被漏掉。3. 完整排查链路从CubeMX配置到diskio实现一层一层剥开问题3.1 第一步在裸机工程里建立挂载基线我自己排查这类问题第一步永远是先建立一个裸机基线。不是因为裸机工程一定正确而是为了分离变量如果裸机下SD卡读写都正常那么SD卡硬件、电路、格式、SDMMC基本配置都OK问题就极大概率出在RTOS环境相关部分如果裸机下都失败那先别碰RTOS把硬件和底层驱动调通再说。我手头有一个最小裸机工程CubeMX里SDMMC1配成SD 4-bit模式时钟树确认SDMMC1时钟源为PLL1Q或PLL2P频率不超过200MHz。生成代码后只调用HAL_SD_Init、HAL_SD_ReadBlocks、HAL_SD_WriteBlocks再加上FatFs。测试结果是格式化、文件创建、读写全部正常。这一步确认了硬件和驱动基础没问题。如果你发现裸机也失败可以检查这几个点SDMMC1时钟源是否打开时钟树里SDMMC1时钟显示是否正常。SDMMC引脚复用是否选对H7的SDMMC1引脚较多复用到PF0、PF1、PD2、PC8-PC12这些不同封装差异大。卡座上是否有CD卡检测引脚。如果板子没有接CD脚在CubeMX里应该把SD卡检测配置为disabled或者在代码里不检测CD状态。3.2 第二步核对时基与SysTick归属当我确认裸机OK后回到FreeRTOS工程第一步就是检查HAL时基。打开stm32h7xx_hal_conf.h看HAL_TICK_FREQ和时基来源。如果是CubeMX一键生成的FreeRTOS工程它会在main.c里初始化TIM6作为HAL时基源。但如果你是自己移植的很可能忘了这一步。这一步排查最快在FreeRTOS启动后调度器跑起来后打印HAL_GetTick()看它是否正常递增。如果HAL_GetTick根本不走或者走得很慢那就说明SysTick被FreeRTOS占用HAL时基没有切换。解决办法也很直接CubeMX里在SYS选项里面把Timebase Source从SysTick改成TIM6重新生成代码。这样HAL_GetTick由TIM6提供FreeRTOS继续使用SysTick两者互不干扰。SDMMC的HAL超时也就不再依赖被FreeRTOS劫持的SysTick了。3.3 第三步检查中断优先级分组与SDMMC回调路径接下来是中断优先级。在FreeRTOS工程里必须确保NVIC优先级分组为Group44位抢占优先级0-15FatFs和SDMMC相关的所有中断优先级数值都设为大于等于5。我一般直接把SDMMC1全局中断和DMA中断都设为优先级5这样既能满足FreeRTOS的可管理要求又能保证中断响应速度不至于太低。其实对于SD卡这种外设跑在50MHz时钟下中断实时性要求并不苛刻完全可以把优先级往低放。代码层面检查HAL_SD_MspInit里是否有这样的配置HAL_NVIC_SetPriority(SDMMC1_IRQn, 5, 0); HAL_NVIC_EnableIRQ(SDMMC1_IRQn); HAL_NVIC_SetPriority(DMA2_Stream3_IRQn, 5, 0); HAL_NVIC_EnableIRQ(DMA2_Stream3_IRQn);需要注意H7上SDMMC的DMA可能走DMA2的Stream3或Stream6具体取决于CubeMX自动生成的代码。关键是所有相关中断优先级都要大于等于configMAX_SYSCALL_INTERRUPT_PRIORITY的值。另外SDMMC的HAL回调函数中如果你启用了DMA模式传输完成的回调会从DMA中断函数里被调用。如果你在回调函数里用了FreeRTOS的信号量给任务发通知那么中断优先级必须满足上述规则这也就解释了为什么优先级设置错误会直接导致死锁或HardFault。3.4 第四步处理D-Cache与DMA缓冲区位置这一步是H7系列绕不开的坎。很多H7工程默认开启了D-Cache而D-Cache和DMA之间有一条著名的规则DMA写入的内存CPU不一定能立即看到CPU写入的内存DMA也不一定能立即看到。如果不做Cache维护或把缓冲区配置成non-cacheableSD卡数据就会出现随机性错误。结合前面讲到的DTCM问题我的处理方案是双管齐下第一把SD卡的DMA缓冲区放在DMA可访问区域。我在工程里定义了一个全局缓冲区__attribute__((section(.sram1))) uint8_t sd_sector_buffer[512] __attribute__((aligned(32)));这个段属性把缓冲区放到了SRAM10x30000000起始该区域DMA可以访问。链接脚本需要包含.sram1段定义CubeMX生成的GCC链接脚本里通常有这块区域。第二用MPU把这块缓冲区所在区域配置为non-cacheable。CubeMX中可以在MPU配置里添加一个Region地址设为0x30000000大小设为SRAM1的对应范围属性设置为Device或Non-cacheable。如果不想改链接脚本也可以直接在代码里用MPU配置函数void MPU_Config(void) { MPU_Region_InitTypeDef MPU_InitStruct {0}; HAL_MPU_Disable(); MPU_InitStruct.Enable MPU_REGION_ENABLE; MPU_InitStruct.BaseAddress 0x30000000; MPU_InitStruct.Size MPU_REGION_SIZE_16KB; MPU_InitStruct.AccessPermission MPU_REGION_FULL_ACCESS; MPU_InitStruct.IsBufferable MPU_ACCESS_NOT_BUFFERABLE; MPU_InitStruct.IsCacheable MPU_ACCESS_NOT_CACHEABLE; MPU_InitStruct.TypeExtField MPU_TEX_LEVEL0; MPU_InitStruct.Number MPU_REGION_NUMBER0; HAL_MPU_ConfigRegion(MPU_InitStruct); HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT); }注意这个MPU配置要在系统初始化时调用放在调度器启动之前。配置完成后CPU访问SRAM1时不再经过CacheDMA读写的数据就能被CPU实时看到fatfs读写的数据一致性就有了保证。如果你不想动MPU另一个方案是在每次DMA读写前后手动做Cache Clean和InvalidateSCB_CleanDCache_by_Addr((uint32_t *)buffer, length); HAL_SD_ReadBlocks(...); SCB_InvalidateDCache_by_Addr((uint32_t *)buffer, length);但手动维护Cache容易漏一旦漏掉就出随机Bug我建议最好还是用MPU配置non-cacheable区域一劳永逸。3.5 第五步在diskio层补上同步机制即便上面全部处理完还有一个经常被忽略的问题FatFs的diskio层在FreeRTOS环境下工作时的线程安全性。如果你只有一个任务在访问SD卡可能侥幸不会出问题但一旦有任务A在写日志任务B在读取配置文件两个任务同时调用disk_read或disk_writeSDMMC的寄存器状态就会被打乱。我最终在diskio.c里加了一个互斥信号量封装。思路是在SD卡初始化时创建互斥量在disk_read、disk_write、disk_status等函数中操作前获取互斥量操作后释放。static SemaphoreHandle_t sd_mutex; void SD_Mutex_Init(void) { sd_mutex xSemaphoreCreateMutex(); } int disk_read(BYTE pdrv, BYTE *buff, LBA_t sector, UINT count) { int result RES_ERROR; if (xSemaphoreTake(sd_mutex, portMAX_DELAY) ! pdTRUE) return RES_ERROR; if (pdrv 0 SD_Status() 0) { // 调用HAL_SD_ReadBlocks注意处理好缓存地址对齐 result RES_OK; } xSemaphoreGive(sd_mutex); return result; }这里的互斥信号量建议在SystemInit之后、调度器启动前创建。如果工程只有一个文件系统访问者互斥锁不是必需的但加上它整个系统在后期扩展任务数量时会安全很多。另外在disk_initialize中我还增加了重试逻辑。SD卡在极端情况下初始化会失败一次我在HAL_SD_Init返回失败后会重新调用三次每次间隔100ms这样可以大幅提高挂载成功率。3.6 第六步验证与其他任务共存时的稳定性以上修改完成后我搭建了一个典型的业务场景来验证稳定性一个任务每隔500ms向SD卡写入一条日志另一个任务每3秒读取一次配置文件还有一个命令交互任务通过串口触发文件列表操作。在这个多任务竞争的场景下连续跑了8小时f_mount失败的问题没有再出现。中途我还做了几十次断电重启实验每次都能正常挂载。为了定位哪一步起了决定性作用我还做了几个对比测试只改中断优先级问题概率下降但不彻底只改Cache配置问题变为偶发的文件数据错误把时基从SysTick改到TIM6之后挂载失败概率明显下降加上diskio层的互斥和重试后才真正稳定。这说明这类问题往往不是单一原因而是多个因素叠加的结果。4. 最终可用配置与核心代码照着改就能稳住挂载4.1 CubeMX侧的配置清单我现在的标准做法如下直接在CubeMX里按这套配置生成工程可以省掉后面大部分踩坑时间。配置项值说明SDMMC1模式SD 4-bit Wide bus默认SDMMC1支持4位/8位优先4位SDMMC1时钟200MHz用于内部时钟源实际总线频率由分频控制SDMMC1中断优先级5必须不低于configMAX_SYSCALL_INTERRUPT_PRIORITYDMA中断优先级5DMA传输完成中断也要注意Timebase SourceTIM6不要把SysTick留给HAL要和FreeRTOS分开NVIC优先级分组4 bits preemptGroup4FreeRTOS硬性要求Cache开启但不信任配合MPU把SD卡buffer区域设为non-cacheableSD卡检测无CD脚则Disable避免检测不到卡而挂载失败4.2 diskio.c中的关键改动在diskio.c里我给所有底层访问函数都加了互斥保护并且增加了初始化重试#define SD_INIT_RETRY_COUNT 3 int disk_initialize(BYTE pdrv) { int retry 0; if (pdrv ! 0) return RES_PARERR; if (xSemaphoreTake(sd_mutex, portMAX_DELAY) ! pdTRUE) return RES_ERROR; do { if (HAL_SD_Init(hsd) HAL_OK) { if (HAL_SD_GetCardStatus(hsd) HAL_OK) { retry SD_INIT_RETRY_COUNT; break; } } HAL_Delay(100); retry; } while (retry SD_INIT_RETRY_COUNT); xSemaphoreGive(sd_mutex); if (retry SD_INIT_RETRY_COUNT) return RES_ERROR; return RES_OK; }这个设计要注意在disk_initialize里加了互斥等待而disk_read/disk_write中也加了互斥那么F_mount在调用disk_initialize时会先去拿互斥初始化完成后释放然后内部再调用disk_read拿互斥因为互斥量在disk_initialize里已经被释放所以不会死锁。如果你用的是递归互斥量也可以但普通互斥量只要保证不在持锁状态下调用其他需要同一把锁的函数就行。另外一个关键点是FatFs底层读取时传入的buff可能不是32字节对齐的而SDMMC的DMA要求缓冲区地址按32字节对齐。FatFs内部有对齐的临时缓冲区但如果配置了_USE_LFN或者某些特殊设置可能会直接传入非对齐地址。我通常在disk_read里做一个拷贝中转#if defined(__ICCARM__) #pragma data_alignment32 #elif defined(__GNUC__) __attribute__((aligned(32))) #endif static uint8_t aligned_buffer[512]; int disk_read(BYTE pdrv, BYTE *buff, LBA_t sector, UINT count) { // 直接基于aligned_buffer做DMA读取再拷贝到buff if ((uint32_t)buff % 32 ! 0) { HAL_SD_ReadBlocks(hsd, aligned_buffer, sector, 1, HAL_MAX_DELAY); memcpy(buff, aligned_buffer, 512); } else { HAL_SD_ReadBlocks(hsd, buff, sector, 1, HAL_MAX_DELAY); } }4.3 挂载时机的安排一个很多教程没提过的细节f_mount在哪个任务里调用、什么时候调用直接影响成功率。我最初是放在上电后最早运行的任务里结果发现SD卡还没完全初始化好f_mount就开始读卡。后来我改成了先创建一个独立的“存储服务任务”优先级设为中等在调度器启动后延迟200ms执行f_mount。为什么延迟200ms不是玄学是给SD卡上电留出稳定时间同时让其他任务先跑起来避免SD卡初始化任务和系统里其他高优先级任务在启动瞬间争抢CPU导致ACMD41的轮询过程被频繁打断。挂载函数如下void Storage_Task(void *argument) { vTaskDelay(pdMS_TO_TICKS(200)); if (f_mount(SDFatFS, SD:, 1) ! FR_OK) { // 打印错误码或者做一次重新挂载 vTaskDelay(pdMS_TO_TICKS(500)); f_mount(SDFatFS, SD:, 1); } else { // 挂载成功开始业务 } while (1) { vTaskDelay(pdMS_TO_TICKS(1000)); } }这个200ms延时配合disk_initialize里的重试逻辑能覆盖绝大多数卡的上电慢、响应慢问题。4.4 几个非常容易被忽略的细节我在这里把几个测试中踩过的细节一起列出来都是实际代码里踩过的坑很多甚至会让你怀疑卡坏了SD卡槽的CD引脚如果悬空不接CubeMX默认会启用SDMMC的卡检测结果HAL_SD_Init里检测不到卡。CubeMX里SD card detect配置改为Disabled或者在HAL_SD_MspInit里把CD引脚的GPIO配置注释掉。H7的SDMMC在全速运行时某些卡需要一个“发ACMD6设置块长度”的过程。HAL库内部有实现但如果你的工程使用的是老版本HAL库需要检查这个命令是否正常执行否则会出现读取扇区半成功半失败的情况。如果使用FreeRTOS FatFs时开启了CPU Cache不要把FatFs的工作缓冲区如SD卡驱动中的FATFS结构体定义在DTCM或者被Cache保护的区域。FATFS结构体本身很大默认链接到DTCM并不导致直接错误因为CPU可以访问DTCM只是不能DMA访问。但最安全的做法是把所有和SD卡交互的数据都放到non-cacheable区域FATFS结构体留在普通内存即可因为它不会被DMA直接访问。当你的工程里同时用了SDMMC和以太网时要注意两边DMA内存区域别重叠。H7的以太网DMA描述符也要求非Cacheable对齐如果MPU配置的non-cacheable区域太小或者地址冲突两边都可能出诡异问题。5. 这套排查思路的迁移价值不只是SD卡FreeRTOS下几乎所有外设都适用5.1 从SDMMC延伸到SPI Flash、以太网的排查心法回过头看这次排查过程的每一步对于FreeRTOS下跑其他外设尤其是带DMA的外设都有直接迁移价值。首先是时序敏感问题。不只是SD卡SPI Nor Flash的JEDEC ID读取、以太网PHY的启动、USB设备的枚举都有等待外部设备响应的过程。这些过程在裸机里无压力的延时或者轮询放进RTOS后就会被任务优先级、中断抢占影响。如果某一类外设在RTOS下时好时坏优先检查它是否有“连续发送一串命令”的初始化序列并考虑把它做成带重试、带延时、带较高优先级的专门任务。其次是中断优先级。在FreeRTOS环境下任何用到DMA和中断的外设都要检查中断优先级是否符合configMAX_SYSCALL_INTERRUPT_PRIORITY。这是FreeRTOS的硬性规则违反它就会出现随机死锁或HardFault。我在SPI Flash、以太网LwIP的工程中都遇到过同类问题表现形式千奇百怪但根因都是中断里调用了FreeRTOS API而优先级不满足要求。再次是DMA可见内存。H7的DMA访问不到DTCM这个特性必然影响所有使用DMA的外设包括SDMMC、SPI、ETH、ADC、UART。只要外设用了DMA它的缓冲区就必须在DMA可访问区域并且最好配置为non-cacheable。这个经验对所有H7系列的工程都适用。最后是时基。RTOS用SysTickHAL就不能再用SysTick。一个工程里绝对不能有两个实体同时抢SysTick的中断服务函数。检查方法很简单看整个工程代码里是不是只有一处SysTick_Handler定义而且它最终调用了xPortSysTickHandler同时HAL_GetTick由另一个定时器驱动。这个规则在移植任何RTOS到STM32时都适用。5.2 我个人的几条心得调整了许多次之后我现在的做法是新工程只要确定要用FreeRTOS和FatFs就直接先把时基分开、中断优先级统一、DMA缓冲区放到非Cache区域、diskio层加互斥并带重试这四件事做掉再开始写业务逻辑。后面遇到问题往往就不是这些基础配置出问题了排查范围可以大幅缩小调试效率明显提高。一个小技巧是在f_mount失败时不要只打印错误码可以在disk_read和disk_initialize里临时加一个GPIO翻转或串口打印直观看到底层调用到了哪一步。我有一次排查就是在disk_initialize里发现HAL_SD_Init永远在超时循环里出不来才定位到时基混乱的问题。还有如果你的项目里用到了多个任务访问同一张SD卡一定要在diskio层加互斥。不加互斥出现的问题非常随机可能是两个任务同时写导致文件系统目录项错乱可能是某个任务读到半截数据更奇怪的是某个任务在等待DMA中断信号量时永远等不到。加互斥后这些问题都消失了。这套组合拳打下来我的STM32H7板子在FreeRTOS环境下f_mount挂载SD卡的成功率从原来的时好时坏变成了稳定通过连续断电上百次都没有再复现。现在每当我看到有人遇到“裸机正常、上RTOS就挂”的外设问题我基本都会建议先按这个顺序排查大概率能解决问题。
返回列表