尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

STM32H7 SDMMC驱动开发实战:从初始化到高速读写

STM32H7 SDMMC驱动开发实战:从初始化到高速读写 1. 先搞清楚H7的SDMMC跟F4的SDIO有什么不同1.1 为什么叫SDMMC而不叫SDIO这是很多从F1/F4转过来的工程师的第一个疑问。STM32H7系列上的SDMMC外设虽然承担的功能和F4的SDIO基本一致都是用来挂SD卡、eMMC这类存储介质但它并不是SDIO的简单改名而是意法半导体在新一代M7内核芯片上重新设计的IP。具体来说新外设在命令路径和数据路径上做了更彻底的分工。旧SDIO把命令、响应、数据状态都堆在一个状态寄存器里排查问题的时候要对着参考手册翻半天H7的SDMMC把命令状态、数据状态、DMA状态分开控制寄存器也变得更有条理。寄存器命名也不同比如旧SDIO的SDIO_CMD、SDIO_STA在H7上变成了SDMMC_CMD、SDMMC_STA如果直接把旧代码拷过来编译这关就过不了。从功能上讲SDMMC额外支持了硬件流控、更灵活的超时配置、寄存器级别的电压切换控制对eMMC的兼容性也更好。反过来它也有不少新坑比如缓存一致性问题、时钟分频的计算方式、以及DMA链路配置这些后面我会逐个展开。1.2 SDMMC解决的核心问题SDMMC本身不负责文件系统它只解决三件事发送命令、接收响应、搬运数据。SD卡协议里那些复杂的时序、CRC校验、状态切换SDMMC硬件都会自动处理你只需要告诉它“发哪个命令”“数据从哪里来”“要传多长”然后等结果。举个例子你要读SD卡的扇区100CPU要做的事情是通过CMD17设置地址把数据长度设为512字节安排好DMA的目标缓冲区然后启动传输。剩下的时钟生成、数据采样、CRC校验、FIFO搬运全由SDMMC完成。这就是为什么说SDMMC是CPU与存储介质之间的桥。理解了这一点你就知道后面配置SDMMC时那些寄存器是干嘛的了。CMDR是命令通道DTIMER是数据超时DLEN是数据长度DCTRL是数据方向控制。所有这些加在一起才是一个完整的传输流程。2. 硬件设计原理图阶段就该避开的坑2.1 引脚复用和分配SDMMC1和SDMMC2可以挂在不同的GPIO组上。以常见的SDMMC1为例CK、CMD、D0到D3这六根线是标配如果要做8位模式或者接eMMC还需要D4到D7。H7的引脚复用表里面SDMMC通常对应AF12或AF11具体型号有差异用CubeMX打开芯片看引脚图是最快的确认方式。我个人建议除非有特殊的PCB布局约束否则不要手动乱改CubeMX生成的引脚分配。原因简单SDMMC的高速信号对走线有要求你手动挪一个引脚可能就把CK和D0之间的距离拉得很远或者让CMD穿过了别的信号区域。如果实在要调整至少保证CK、CMD、D0-D3这几根线尽量靠近保持长度一致。2.2 电平、上拉和供电问题H7的SDMMC接口同时支持1.8V和3.3V电平具体取决于挂载的介质。普通SD卡槽走3.3V板载eMMC多为1.8V。这里有个容易踩的坑CubeMX里面如果有关于SDMMC电压模式的选项它只是告诉你硬件上已经支持哪种电平不是软件配置。如果你在软件里写了1.8V模式但硬件根本没有1.8V的转换电路卡在初始化阶段就会表现得很诡异比如CMD8响应正常、ACMD41彻底没有回应。上拉电阻方面多数H7开发板在CMD和D0-D3上都加了上拉因为SD卡规范对这几根线的空闲电平有要求。如果你自己画板子建议在CMD和所有数据线上都接10k到47k的上拉而不是指望芯片内部弱上拉。我实测过有些卡在内部弱上拉下能工作换个卡就随机失败排查到最后就是上拉不够。供电就更直接了SD卡槽的VDD必须干净特别是高速读写时电流变化大。如果电源纹波偏大高速模式下偶尔会出现CRC错误。这时候不是SDMMC寄存器的问题是硬件的问题。所以在原理图阶段就把卡槽电源用独立的LDO或者带滤波的DC-DC供电与MCU的IO供电分开是值得做的事。2.3 走线长度与信号完整性SDMMC在初始化阶段是400kHz这时候随便飞线都能跑通但一旦切到50MHz高速模式走线就是生死线。CK是时钟CMD和D0-D3是同步信号所有信号都必须以CK为参考。如果某根数据线比CK长了几个厘米在高速采样时就会出现建立时间不足的问题。我的经验是对于50MHz的SDMMC信号CK与CMD/Dx之间的长度差控制在500mil约12.7mm以内比较稳妥并且尽量做到同一层布线避免打过孔。如果板子空间紧张至少保证D0-D3之间的差异不要太大CMD的差异可以稍微放宽一些。实际项目中我曾经在50MHz下遇到随机CRC失败改版时调整了走线长度后问题消失从那以后我再也不信“差不多就行”这种说法。3. 时钟、总线宽度与关键寄存器配置3.1 SDMMC内核时钟与卡时钟H7的SDMMC时钟来自RCC典型配置是通过PLL1Q或者PLL2R等时钟源产生。内核时钟不等于SD卡接口时钟SDMMC内部有一个分频器卡时钟 内核时钟 / (2 × CLKDIV)。初始化阶段要求卡时钟不超过400kHz高速模式通常是50MHz或更高。举个例子内核时钟配置为120MHz初始化阶段目标400kHzCLKDIV取150120MHz / 300 400kHz正好满足。高速模式目标50MHzCLKDIV取1120MHz / 2 60MHz超过了50MHz。这种情况下CubeMX可能会给出一个CLKDIV2的配置得到30MHz虽然降了一档但依然能跑出不错的读写速度。这种事不能想当然一定要打开CubeMX的时钟树页面看一下实际计算出来的卡时钟频率别只看引脚配置。3.2 总线宽度选择1位、4位还是8位SD卡通常用4位模式eMMC可以用8位这没什么好纠结的。问题是切换总线宽度的时机和顺序。SD卡规范里面总线宽度切换不是直接改寄存器就行而是要发ACMD6命令命令参数表示是1位还是4位然后才能改变SDMMC的DCTRL寄存器里的总线宽度位。如果顺序搞反卡会保持在旧模式通信时好时坏。我见过不少人在CubeMX里直接把SDMMC配置成4位模式然后调SD卡读写结果发现第一次初始化能过换一张卡就失败。原因就是底层驱动可能在ACMD6还没执行的时候就把硬件改成了4位卡没有收到正确的宽度切换命令。用HAL库时这个顺序一般由SD_InitCard处理但如果你自己写协议栈一定不要忽略这一步。3.3 硬件流控到底要不要开H7的SDMMC支持硬件流控主要是防止FIFO溢出或下溢。正常情况下建议开启。开启之后当FIFO快满或者快空时SDMMC会自动拉低卡时钟等待CPU或DMA跟上。这样做的好处是即使DMA因为总线仲裁延迟而慢了一点数据也不会错。我之所以特别强调这个功能是因为在M7双核或高负载场景下DMA访问内存可能会被其他主设备抢走带宽。如果在读写大块数据时不开硬件流控偶尔就会读到一两个错误的数据块而且不是每次必现排查起来很头疼。开启硬件流控后这类问题几乎绝迹代价只是性能略微下降完全值得。4. 初始化流程把SD卡“唤醒”是第一步4.1 从上电到传输就绪的完整步骤SD卡初始化本质上是一个状态机SDMMC的初始化流程同样要严格遵循上电后等待电压稳定至少1ms建议多等一会。发送CMD0让卡进入空闲状态。发送CMD8查询卡支持的电压范围判断是SD V2.0及以上还是老版本。发送ACMD41协商工作电压并等待卡返回就绪状态。发送CMD2读取CID发送CMD3获取RCA此时卡进入待机状态。发送CMD7选择卡进入传输状态。如果需要4位模式发送ACMD6切换总线宽度。通过CMD16设置块长度通常为512字节。这些步骤缺一不可。很多人初始化失败不是SDMMC寄存器配错而是ACMD41之前没有发CMD8或者ACMD41的电压参数填错导致卡一直不回应。还有一个细节CMD8的参数中包含了电压信息常见的配置是0x1AA表示支持2.7V-3.6V这个值不能随便改。4.2 HAL库初始化的几个隐藏坑用HAL库的时候HAL_SD_Init实际做了两件事初始化SDMMC硬件然后调用HAL_SD_InitCard来真正和卡通信。如果你在CubeMX中把SDMMC的时钟、GPIO都配置好了但HAL_SD_Init仍然返回超时可以从几个方向排查。第一确认HAL_GetTick是否正常工作。HAL库内部用SysTick做超时判断如果你在RTOS中重定向了HAL_GetTick或者SysTick中断被屏蔽了初始化会直接卡死。第二确认GPIO的AF复用功能是否正确这个问题在手动改过引脚后特别常见。第三确认SDMMC的时钟分频在初始化阶段确实让卡时钟低于400kHz。用示波器或者逻辑分析仪测一下CK引脚一切都能见分晓。4.3 初始化失败的自检方法我发现一个特别有效的自检方法逻辑分析仪接在CK、CMD、D0三根线上抓取上电后的波形。如果CMD0能发出去但响应没有回来问题可能在卡的电平或电源如果CMD0之后没有任何后续命令说明CMD0就卡住了多半是卡没有进入空闲状态。通过看波形能快速区分是软件问题还是硬件问题。软件问题通常是命令顺序错乱或者参数不对硬件问题则是电平不对、没上拉、供电不足等等。我自己的习惯是在做任何SDMMC驱动调试之前先把逻辑分析仪挂上因为这种方式最直观比反复猜寄存器配置高效得多。5. 数据传输实现轮询、中断还是DMA5.1 三种传输模式怎么选SDMMC的传输模式说到底就是数据怎么从FIFO搬到内存轮询模式代码简单但是在传输过程中CPU要死等不适合大数据量读写。中断模式每传输一定长度产生一次中断CPU可以在中断间隙干别的事但对于大块连续传输仍然会频繁进中断。DMA模式数据搬运完全由DMA控制器完成CPU只需要在传输完成时收到一个中断。在实际项目中大容量存储读写一定用DMA这是所有开发者的共识。但要注意DMA模式下缓存一致性问题变的不可忽视这也是H7比F1/F4多出来的必修课。5.2 缓存一致性M7开发者必须明白的事Cortex-M7带D-Cache默认情况下CPU读写内存时可能会经过缓存而DMA看到的是物理内存。两者之间如果不做同步就会出问题。比如你用DMA从SD卡读了一包数据到缓冲区CPU去读缓冲区却发现读到的还是旧数据因为缓存还没被无效化。反过来你用CPU把数据填进缓冲区再启动DMA去写卡如果没有先做缓存CleanDMA可能根本看不到最新数据。解决方案有两种。一个是把缓冲区放在非缓存区域比如通过MPU配置或者链接脚本指定段操作代价小但会牺牲一点内存性能。另一个是手动调用CMSIS函数读操作之后调用SCB_InvalidateDCache_by_Addr写操作之前调用SCB_CleanDCache_by_Addr。用HAL库的时候如果做了Cache维护还要注意缓冲区必须按32字节对齐否则CMSIS的Cache操作会出错因为Cache line的大小是32字节。我建议在初学阶段直接把缓冲区声明成对齐的非缓存段减少调试难度。等到项目性能吃紧时再考虑手动Cache维护。5.3 多块传输与超时机制SD卡读写通常以块为单位单块512字节。但高速场景下要使用多块传输也就是一次发送CMD18多块读取或CMD25多块写入连续搬多个块。多块传输结束时需要发送CMD12停止传输否则卡会一直认为是连续多块模式。HAL库的SD_ReadMultiBlocks会自动发CMD12但如果自己写寄存器代码这个很容易漏。关于超时SDMMC的DTIMER寄存器是用来设置数据阶段超时的。如果卡在擦除或写入过程中比较慢DTIMER设置太短会导致错误中断。SD卡在长时间写入时的确有几十毫秒甚至上百毫秒的忙状态所以超时值不要设太死。HAL库默认给的是很大的值基本够用但如果你开启低功耗模式或关闭中断要重新评估超时。6. 调试经验与常见问题速查6.1 用逻辑分析仪定位问题SD卡协议是公开的而且逻辑分析仪工具大多支持SD卡协议解码比如PulseView。把SDMMC的CK、CMD、D0-D3接到逻辑分析仪抓一段波形就能看到完整的命令序列。如果你看不懂波形至少能看出有没有命令响应、响应是否超时以及命令之间的间隔是否合理。我的做法是在初始化失败的时候抓上电到失败的完整序列然后对照SD卡规范文档手工分析命令状态。通常能很快定位到是CMD8的响应没收到还是ACMD41的忙状态没结束。这种手段比盲改驱动参数可靠得多建议条件允许的团队都准备一个至少4通道、采样率100MHz以上的逻辑分析仪。6.2 高速模式下的随机CRC错误随机CRC错误是SDMMC调试里最让人头疼的问题之一。这类问题的根源往往是信号完整性和时序问题而不是寄存器配置。常见的诱因有走线长度不一致、回路地面积过大、电源纹波偏高、板子负载过大导致电压跌落。排查顺序我建议是先用示波器看CK信号有没有过冲、振铃再量一下卡槽电源的纹
返回列表