1. 项目概述
在嵌入式系统里,尤其是涉及到存储卡、Wi-Fi模块或者各种I/O扩展卡的应用,MMC、SD、SDIO这三种接口标准几乎无处不在。它们背后的核心,就是那个负责与卡片“对话”的主机控制器。很多开发者拿到芯片手册,看到控制器章节里密密麻麻的寄存器描述和时序图,往往觉得头大,要么直接照搬参考代码,要么就只关心最基础的读写函数,对控制器内部如何高效、可靠地搬运数据一知半解。
我这些年调试过不少基于TI、NXP、ST等厂商MCU的SD卡和SDIO Wi-Fi模块项目,踩过的坑不少。我发现,真正决定一个存储或I/O外设性能上限和稳定性的,往往不是主频,而是你对控制器内部三大核心机制的理解:中断、DMA和缓冲区管理。中断决定了CPU响应事件的及时性,DMA决定了数据搬运的效率,而缓冲区管理则是连接前后两者的桥梁,决定了数据流的平滑程度。手册上的描述通常是割裂的、功能性的,但实际开发中,你需要把它们串成一个连贯的、可操作的工作流。
这篇文章,我就以一份典型的控制器手册(比如TI AM263x的文档)为蓝本,结合我的实战经验,把这三大块的核心原理、配置要点和那些手册上不会写的“坑”给彻底讲透。目标是让你看完之后,不仅能看懂寄存器,更能设计出高效、稳定的驱动,在面对SD卡突然掉速、SDIO设备通信异常这类问题时,能有清晰的排查思路。
2. 中断驱动模式:从被动等待到主动响应
在嵌入式开发早期,或者在一些极其简单的场景中,我们可能会用轮询(Polling)的方式:CPU不断地去读一个状态寄存器,看看数据准备好了没,或者传输完成了没。这种方式代码简单,但CPU利用率极低,大部分时间都在空转。中断驱动(Interrupt-Driven)模式就是为了解决这个问题而生的,它让外设在特定事件(比如数据准备好、传输完成、发生错误)发生时,主动“打断”CPU,通知它来处理。这样CPU就可以在等待期间去执行其他任务,大大提升了系统效率。
2.1 中断使能与事件识别
控制器内部有很多可以触发中断的事件源,比如命令完成、数据块传输就绪、缓冲区可写、发生CRC错误、超时等等。并不是所有事件都会自动产生中断信号到CPU,你需要先“订阅”它们。
核心寄存器:MMC_IE (Interrupt Enable)这个寄存器里的每一个位,都对应着一个可能的中断事件。你想让哪个事件触发中断,就把对应的使能位(比如BRR_EN,BWR_EN,CC_EN,TC_EN等)设置为1。这是一个非常关键的配置步骤,很多新手驱动写出来没反应,第一步就该检查这里设对了没有。
注意:通常建议在驱动初始化阶段,先清除所有中断状态,然后按需使能。例如,对于普通的数据读写,你至少需要使能“命令完成”(CC)和“传输完成”(TC)中断。如果要用DMA,可能还需要使能“缓冲区就绪”相关的中断。
当某个被使能的事件发生时,控制器内部的硬件逻辑会将该事件对应的状态位置位,并断言(拉高)通向CPU的中断线。这时,CPU会跳转到预先设置好的中断服务程序(ISR)中。
ISR里的标准操作流程:
- 读取MMC_STAT寄存器:这是中断服务程序的第一要务。这个寄存器就像是一个事件日志,哪个位是1,就表示哪个事件发生了。可能是好事(如
CC命令完成),也可能是坏事(如DTO数据超时)。 - 处理事件:根据
MMC_STAT的值,执行相应的操作。比如,如果是CC置位,说明上一条命令执行完毕,可以读取响应寄存器MMC_RSPxx获取结果;如果是BRR置位,说明接收缓冲区有数据可读,可以启动DMA或者CPU来搬数据。 - 清除中断状态:处理完事件后,必须手动清除
MMC_STAT中对应的状态位,否则中断线会一直保持有效,导致CPU反复进入中断,形成“中断风暴”。清除方法很特别:向你要清除的位写1。例如,要清除命令完成中断,就执行MMC_STAT = (1 << CC_BIT_POS)。写完之后再读该位,应该返回0。 - 可能的附加操作:有些复杂事件清除后,还需要通知中断控制器(如NVIC)本次中断处理结束。
2.2 两个特殊的“硬骨头”:CIRQ和ERRI
手册里特别强调了两个状态位不能通过简单的写1来清除,这是最容易出错的地方。
1. 卡中断 (CIRQ)这是SDIO卡特有的功能。SDIO设备(比如Wi-Fi模块)可以通过DAT1线主动向主机发送中断请求,通知主机有事件需要处理(例如,网络数据包到达)。这个中断状态记录在MMC_STAT[8] CIRQ。
- 为什么不能直接清除?因为这个中断源在SDIO卡内部,主机控制器只是检测到了这个信号。你直接清除控制器的状态位,但卡那边的中断源还在,信号线还是低电平,控制器下一秒又会检测到。
- 正确清除流程: a. 首先,在中断使能寄存器
MMC_IE中,屏蔽掉卡中断:MMC_IE[8] CIRQ_ENABLE = 0。这相当于告诉控制器:“先别管DAT1线上的中断信号了”。 b. 然后,你的ISR需要去处理SDIO卡触发中断的原因。这通常是通过读写SDIO卡的通用控制寄存器(CCCR)中的某个位来实现的,具体是哪个位取决于你的SDIO设备文档。 c. 处理完卡端的中断源后,再重新使能控制器的卡中断检测:MMC_IE[8] CIRQ_ENABLE = 1。
2. 错误中断 (ERRI)MMC_STAT[15] ERRI是一个错误中断的汇总标志。当MMC_STAT[31:16]这个高16位区域中任何一个具体的错误状态位(如CTO命令超时、CCRC命令CRC错误、DTO数据超时、DCRC数据CRC错误等)被置位时,ERRI位也会被自动置位。
- 清除方法:你不能直接写
ERRI位。它的清除是“自动”的,条件是所有高16位的具体错误状态位都被清除。所以,你的错误处理ISR应该遍历MMC_STAT[31:16],检查是哪个具体错误发生了,处理它(比如重试命令、报告错误日志),然后向那个具体的错误状态位写1来清除它。当所有具体错误位都清零后,ERRI位自然会跟着清零。
2.3 中断 vs. 轮询:如何选择?
虽然中断是主流,但轮询模式(Polling)依然有它的用武之地。通过清除MMC_ISE(Interrupt Status Enable?此处手册似应为MMC_IE的禁用)寄存器中某个事件的使能位,该事件将不会触发中断线。
- 轮询的应用场景:
- 极短延时操作:对于一些预期在几十个时钟周期内就能完成的操作(比如发送一个简单的命令并等待响应),轮询的延迟可能比中断的上下文切换开销更小。
- 关键时序或调试:在调试底层时序或初始化序列时,轮询可以让你精确控制代码的执行流程,便于单步跟踪。
- 资源极度受限:在一些没有中断控制器或者RAM极小,无法负担ISR栈空间的超低端MCU上。
- 轮询的操作:软件需要在一个循环里不断读取
MMC_STAT寄存器,检查目标状态位是否变为1。检测到后,同样需要写1清除该状态位。注意,此时因为中断未被使能,所以清除操作不会影响中断线的状态(它本来就是无效的)。
实操心得:在大多数应用里,尤其是涉及大数据量传输的,一定要用中断。轮询只适合在驱动初始化的某些固定步骤中使用,例如等待上电稳定、发送CMD0复位卡等。混合使用是常见策略:初始化阶段用轮询确保时序严格,正常数据传输阶段切换到中断模式以释放CPU。
3. DMA传输模式:解放CPU的搬运工
如果说中断让CPU从“傻等”中解放出来,那么DMA(直接内存访问)就是让CPU从“搬运数据”这个体力活中彻底解脱。在读写SD卡这种动辄几MB的数据时,让CPU一个个字节地从控制器缓冲区读到内存,效率太低。DMA控制器可以在CPU“不知情”的情况下,完成数据在控制器缓冲区和系统内存之间的搬运。
3.1 理解“仅响应器”模式
这份手册明确指出,该MMC/SD/SDIO控制器仅支持DMA响应器(Responder)模式。这是什么意思?
- 发起器(Initiator)模式:外设控制器自己作为DMA传输的发起方,它主动向DMA控制器发起请求,并控制传输的地址、长度。功能更强,但设计复杂。
- 响应器(Responder)模式:外设控制器只是一个“被动”的角色。它只负责产生两个信号:“我有数据要给你”(读请求
SDMARREQN)和“我这里有空位可以放数据”(写请求SDMAWREQN)。至于数据从哪里来、到哪里去、搬多少,全由DMA控制器来规划和执行。这是更常见、也更易于集成的模式。
控制器通过SDMARREQN(读请求,低有效)和SDMAWREQN(写请求,低有效)这两根信号线与DMA控制器对话。
3.2 DMA传输的触发条件
不是任何时候控制器都会发DMA请求。要启动一次DMA传输,必须满足几个条件,这通常是在你发送一条数据读写命令(如CMD17读单块,CMD25写多块)时配置的:
- 设置DMA使能位:在发送命令前,需要将命令寄存器
MMC_CMD[0]的DE(DMA Enable)位设置为1。这个位是触发初始DMA请求的开关。 - 命令已发出:命令必须已经通过
MMC_CMD线发送给卡。 - 缓冲区就绪:
- 对于接收(读卡):控制器需要确认其内部缓冲区有足够空间(至少一个块
BLEN的大小)来容纳即将从卡上读来的数据,才会在数据到达后断言SDMARREQN。 - 对于发送(写卡):控制器需要确认其内部缓冲区有足够空间(至少一个块
BLEN的大小)来接收DMA将要写过来的数据,才会断言SDMAWREQN。
- 对于接收(读卡):控制器需要确认其内部缓冲区有足够空间(至少一个块
3.3 DMA接收模式详解(从卡读数据)
假设我们要从SD卡读取一个块的数据到系统内存。
- 配置:设置块长度
MMC_BLK[10:0] BLEN(比如512),使能DMA (MMC_CMD[0] DE=1),然后发送读命令(如CMD17)。 - 卡响应与数据传输:SD卡开始通过DAT线向控制器发送数据。控制器将数据存入其内部缓冲区。
- 请求产生:当一个完整的数据块被写入控制器缓冲区后,控制器立即断言
SDMARREQN信号,向DMA控制器“喊话”:“我有一个块的数据好了,快来拿!” - DMA响应:DMA控制器检测到请求,开始执行从控制器缓冲区(源地址)到系统内存(目标地址)的数据搬运。它可以从
MMC_DATA寄存器连续读取数据。 - 请求撤销:关键点来了!只要DMA控制器从缓冲区读取了一个字(32位),
SDMARREQN信号就会被立即撤销。这意味着这个请求信号是“电平触发”而非“脉冲触发”。它只在缓冲区有数据且DMA还没开始搬,或者没搬完时保持有效。 - 块传输完成:DMA控制器需要自己计算,要搬够一个块的数据量,即
BLEN字节。因为DMA访问是32位(4字节)对齐的,所以需要读取的次数是ceil(BLEN / 4)。例如,BLEN=512,就需要读128次。 - 流控与时钟停止:这是一个重要的性能优化和可靠性机制。在多个块传输(CMD18)且块大小较大时,如果DMA搬运速度跟不上卡发送数据的速度,控制器缓冲区可能会满。此时,控制器会暂时停止提供给SD卡的时钟(MMC_CLK)。卡检测不到时钟就会暂停发送。直到DMA(或CPU)从缓冲区读走一个完整块,腾出空间,时钟恢复,卡继续发送。这完美避免了数据溢出(Overflow)错误。
注意事项:这里解释了为什么DMA配置时,传输长度必须设置为
BLEN。如果DMA配置的长度小于BLEN,它搬完自己设定的长度后就停止了,但控制器还在等待搬完整个块,SDMARREQN会一直有效,导致DMA可能错误地发起第二次传输。如果DMA配置的长度大于BLEN,它会试图多读数据,可能读到无效或下一个块的数据,造成混乱。
3.4 DMA发送模式详解(向卡写数据)
向卡写数据的过程与接收对称,但方向相反。
- 配置:同样设置
BLEN,使能DMA,发送写命令(如CMD24/25)。 - 请求产生:控制器发现其内部缓冲区有足够空间容纳一个块的数据时,断言
SDMAWREQN信号,通知DMA控制器:“我这里有空位,可以送一个块的数据过来了!” - DMA响应:DMA控制器开始将系统内存中的数据搬运到控制器的
MMC_DATA寄存器。 - 请求撤销:同样,DMA控制器向缓冲区写入一个字(32位)后,
SDMAWREQN信号就被撤销。 - 控制器发送:控制器从自己的缓冲区读取数据,通过DAT线发送给SD卡。
- 流控:如果DMA送数据的速度太慢,控制器缓冲区快空了,它无法继续向卡发送数据。但写操作中,卡是接收方,通常没有“暂停”机制(除非使用更复杂的流控命令)。因此,更常见的问题是DMA太快,缓冲区满了,
SDMAWREQN撤销,DMA暂停,直到控制器发送完一部分数据腾出空间。这依赖于DMA控制器的“握手”协议。
3.5 DMA配置实战要点
- 源/目标地址:对于读卡(控制器到内存),源地址是控制器
MMC_DATA寄存器的固定内存映射地址,目标地址是你的系统内存缓冲区。对于写卡,则相反。 - 传输宽度与地址递增:必须设置为32位(字)访问。源/目标地址通常需要设置为递增模式。
- 传输长度:必须是
BLEN字节。在DMA控制器配置中,这个长度通常以“字节”为单位设置。 - 请求信号链接:正确配置DMA通道,将其外部请求源(External Request)映射到控制器的
SDMARREQN或SDMAWREQN信号。 - 循环模式:对于多块传输,可以配置DMA为“Ping-Pong”双缓冲区循环模式或自动重载模式,实现连续不间断传输。这需要与控制器的双缓冲机制配合,后面会讲到。
4. 缓冲区管理:数据流转的中枢与性能关键
控制器内部的缓冲区是连接高速系统总线(如AHB/AXI)和相对低速的SD/MMC总线之间的关键枢纽。它的管理策略直接决定了数据传输能否流畅、是否会出现上溢或下溢。
4.1 缓冲区结构:数据缓冲、预取与后写
控制器内部不止一个简单的FIFO,它通常包含三个部分:
- 数据缓冲区(Data Buffer):主缓冲区,用于暂存正在传输的数据块。
- 预取寄存器(Prefetch Register):位于数据缓冲区的读端口。它的读取速度比直接读数据缓冲区更快。当CPU或DMA发起读操作时,数据实际上是从预取寄存器中读出的,这相当于一个小的读缓存,提升了读效率。
- 后写缓冲区(Post-write Buffer):位于数据缓冲区的写端口。当CPU或DMA向
MMC_DATA寄存器写入时,数据先进���后写缓冲区,再异步写入主数据缓冲区。这解耦了写操作,提升了写效率。
一个重要的现象:如果你刚向MMC_DATA写入一个值,紧接着又读MMC_DATA,读回来的值很可能不是你刚才写入的那个。这是因为写操作进入了后写缓冲区��而读操作是从预取寄存器取数据。两者是物理上不同的寄存器。这在调试时需要注意,不要用这种方式来验证写入是否成功。
4.2 双缓冲与单缓冲模式
这是缓冲区管理的精髓,也是影响吞吐量的核心。模式的选择由你要传输的数据块大小(BLEN)和缓冲区总大小(MEM_SIZE)的关系决定。
1. 双缓冲模式(BLEN <= MEM_SIZE / 2)当每个数据块的大小不超过缓冲区总容量的一半时,控制器可以将缓冲区划分为两个相等的部分(A区和B区),并以“乒乓”方式工作。
- 工作流程:假设正在从卡读数据。
- 阶段1:卡的数据正在写入缓冲区的A区。
- 阶段2:A区写满后,卡的数据开始写入B区。同时,DMA或CPU可以从已经写满的A区读取数据到系统内存。
- 阶段3:B区写满后,卡的数据又切换回A区(此时A区数据已被读空)。同时,从B区读取数据。
- 优势:实现了真正的读写并行。卡向缓冲区写入一个块的同时,主机可以从缓冲区读取另一个块,几乎消除了等待时间,最大化总线利用率。这对于高性能连续读写至关重要。
- 配置:你只需要正确设置
BLEN,控制器硬件会自动管理乒乓切换。
2. 单缓冲模式(BLEN > MEM_SIZE / 2)当数据块大小超过缓冲区容量的一半时,整个缓冲区只能作为一个整体来存放一个数据块。
- 工作流程:
- 卡必须把整个块完全写入缓冲区后,才会产生“缓冲区读就绪”信号,主机才能开始读取。
- 同样,主机必须把整个块的数据完全写入缓冲区后,控制器才会开始向卡发送。
- 劣势:读写操作是串行的。在卡向缓冲区写数据时,主机只能等待,不能读取,反之亦然。这会引入空闲时间,降低整体吞吐率。
- 风险:如果试图在单缓冲模式下进行双向同时传输(例如全双工SDIO),控制器会报告“错误访问”(
MMC_STAT[29] BADA)。
寄存器关联:MMC_PSTATE[11] BRE(缓冲区读使能)和MMC_PSTATE[10] BWE(缓冲区写使能)这两个状态位直接反映了缓冲区是否就绪。在发起读/写MMC_DATA寄存器操作前,必须检查相应的BRE或BWE是否为1,否则会触发BADA错误。
4.3 内存大小、块长度与缓冲区关系表解读
手册中的表格是硬件设计的直接体现,也是你进行系统设计时的重要依据。
| 内存大小 (MEM_SIZE) | 支持的最大块长度 (BLEN) | 可双缓冲的最大块长 | 单缓冲的块长范围 |
|---|---|---|---|
| 512 字节 | 512 字节 | N/A | BLEN <= 512 |
| 1024 字节 | 1024 字节 | BLEN <= 512 | 512 < BLEN <= 1024 |
| 2048 字节 | 2048 字节 | BLEN <= 1024 | 1024 < BLEN <= 2048 |
| 4096 字节 | 2048 字节 | BLEN <= 2048 | N/A |
解读与实战选择:
- 如果你的控制器缓冲区是1024字节:
- 如果你想用双缓冲提升性能,那么请确保你的数据块长度设置为512字节或更小。这是SD卡的标准块大小,也是性能最优的常见选择。
- 如果你因为特殊需求必须使用1024字节的块,那么你将只能使用单缓冲模式,需要接受由此带来的性能损失。
- 最大块长度限制:注意最后一行的4096字节缓冲区,最大只支持2048字节的块。这意味着硬件设计可能只允许BLEN字段的最大值对应2048。不要试图配置超出
MMC_CAPA[17:16] MBL字段规定的最大值。 - 设计启示:在选型芯片或设计系统时,如果你需要高频宽、连续的大文件传输,选择一个具有较大内部缓冲区的SD主机控制器,并确保使用双缓冲模式,能带来显著的性能提升。
5. 完整数据传输流程与错误处理实战
理解了中断、DMA和缓冲区,我们把这些碎片拼起来,看一个完整的、稳健的数据读操作流程是怎样的。
5.1 读数据流程(中断+DMA模式)
初始化与配置:
- 配置GPIO复用、时钟。
- 软件复位控制器,等待复位完成 (
RESETDONE=1)。 - 配置总线宽度、时钟频率 (
SD_SYSCTL[15:6] CLKD)。 - 使能控制器的内部时钟 (
SD_SYSCTL[0] ICE=1),等待时钟稳定 (SD_SYSCTL[1] ICS=1)。 - 配置缓冲区相关:根据你的需求设置
BLEN。如果BLEN <= MEM_SIZE/2,硬件会自动使用双缓冲。 - 配置DMA控制器:设置源地址(
MMC_DATA)、目标地址(内存缓冲区)、传输长度(BLEN字节)、传输宽度(32位),并将DMA通道的请求源绑定到SDMARREQN。 - 使能中断:在
MMC_IE寄存器中使能“传输完成”(TC)、“命令完成”(CC)中断,如果需要也使能错误中断。在NVIC中使能SD控制器的中断。
发送读命令:
- 设置命令参数 (
MMC_ARG)。 - 在命令寄存器 (
MMC_CMD) 中,设置命令索引(如CMD17)、使能DMA (DE=1)、设置数据传输方向为从卡到主机 (DDIR=1)。 - 写入
MMC_CMD寄存器,命令被发出。
- 设置命令参数 (
中断服务程序(ISR)处理:
- 命令完成中断 (CC):读取
MMC_RSP10等寄存器获取命令响应(如R1),检查是否成功。清除CC状态位。 - 传输完成中断 (TC):表示一个数据块(或多块的最后一个)的DMA传输已经结束。此时,你应该检查是否有错误(如
DCRC,DTO),然后进行后续操作(如更新文件指针,准备下一个读写请求)。清除TC状态位。 - 缓冲区读就绪中断 (BRR):如果你使用CPU轮询搬数据而不是DMA,这个中断表示缓冲区有数据可读了。在DMA模式下,这个中断通常不需要使能,因为DMA请求会自动处理数据搬运。
- 错误中断 (ERRI):跳转到错误处理子程序。
- 命令完成中断 (CC):读取
DMA搬运:整个过程由DMA硬件在后台完成,CPU无需干预。控制器在收满一个块后拉请求,DMA搬走数据,直到搬完
BLEN字节。传输停止:对于多块读取(CMD18),需要发送CMD12命令来停止传输。手册中提到的“Stop at block gap”功能 (
MMC_HCTL[16] SBGR) 非常有用。使能它后,控制器会在一个块传输结束后自动暂停时钟,给你一个安全的窗口去发送停止命令,避免在数据流中间打断造成错误。
5.2 错误排查:从寄存器到现象
当传输失败时,MMC_STAT寄存器是你的第一诊断工具。以下是一些常见错误和排查思路:
| 错误状态位 | 含义 | 可能原因 | 排查步骤 |
|---|---|---|---|
CTO | 命令响应超时 | 1. 卡未正确插入或损坏。 2. 时钟频率在初始化阶段设置过高。 3. 命令线(CMD)上拉电阻缺失或损坏。 4. 电压不匹配。 | 1. 检查物理连接。 2. 初始化时使用最低时钟(如400kHz)。 3. 用示波器测量CMD线波形。 4. 检查 SD_VS电压设置。 |
CCRC | 命令响应CRC错误 | 1. 信号完整性差,CMD线受到干扰。 2. 时序不满足,采样点错误。 | 1. 检查PCB布线,确保CMD线短且远离干扰源。 2. 调整时钟相位 ( MMC_HCTL[2] HSPE),尝试在上升沿或下降沿采样。 |
DTO | 数据超时 | 1. 卡在处理数据时“忙”(特别是写操作后)。 2. 数据线(DAT)断开或短路。 3. 块长度 BLEN设置与卡不匹配。 | 1. 检查R1b响应中的忙位,或等待更长的忙时间。 2. 测量DAT线波形。 3. 确认使用的是标准512字节块,或卡支持的大容量块。 |
DCRC | 数据CRC错误 | 1. DAT信号完整性差。 2. 时钟抖动过大。 3. 电源噪声导致数据出错。 4. DMA配置错误,搬错了数据量。 | 1. 检查DAT线布线,等长、阻抗控制。 2. 确保时钟稳定,降低频率测试。 3. 检查电源纹波,增加去耦电容。 4. 核对DMA传输长度是否为 BLEN。 |
BADA | 错误的数据空间访问 | 1. 在BRE=0时尝试读MMC_DATA。2. 在 BWE=0时尝试写MMC_DATA。3. 单缓冲模式下尝试非法并发访问。 | 1. 在读/写数据前,务必检查MMC_PSTATE中的BRE/BWE位。2. 确保数据传输方向 DDIR在命令发送前已正确设置。 |
一个典型的调试流程:
- 初始化失败,卡在CMD8或ACMD41无响应 (
CTO):首先把时钟降到最低,用示波器看CMD线上是否有正确的命令波形发出,卡是否有任何响应波形。检查电源和上拉电阻。 - 能识别卡,但读写数据时随机出现
DCRC错误:这极可能是信号完整性问题。尝试降低总线频率(如从50MHz降到25MHz),如果错误消失,则证实是时序或信号质量问题。需要优化PCB布局布线。 - DMA传输数据错乱:检查DMA的源/目标地址是否配置正确,传输长度是否严格等于
BLEN,传输宽度是否为32位。确认DMA和SD控制器的时钟都已使能且稳定。
5.3 高级话题:时钟边沿与性能调优
手册在最后提到了输出信号生成边沿的选择 (MMC_HCTL[2] HSPE)。
- 下降沿生成 (HSPE=0):默认模式。数据在时钟下降沿变化,在上升沿被卡采样。这为卡的采样提供了最大的建立时间(
tMIS),有利于提高时序裕量,确保稳定性。 - 上升沿生成 (HSPE=1):数据在时钟上升沿变化。这可以增加主机控制器输出数据的保持时间(
tMOH),在某些情况下,尤其是使用高速模式(SDR)时,有助于达到更高的总线频率。
重要警告:在DDR(双倍数据率)模式下,绝对不能使用上升沿生成模式。因为DDR模式已经在时钟的上升沿和下降沿都传输数据,改变边沿会导致相位混乱。
调优建议:在稳定性优先的场合,使用默认的下降沿模式。当需要追求极限速度(例如SDR104模式),且遇到时序问题时,可以尝试切换到上升沿模式,并用示波器严格测量数据和时钟的时序关系,看是否满足SD规范的要求。
6. 总结与核心心得
MMC/SD/SDIO控制器是一个相对复杂的模块,但将其工作原理拆解为中断、DMA、缓冲区管理三个核心维度后,就变得清晰可管理。我的经验是,编写一个健壮的驱动,需要遵循以下原则:
初始化是根基:严格按照手册的初始化流程,特别是时钟使能和稳定等待。很多奇葩问题都源于初始化顺序不对或时钟未就绪。
中断处理要完备:ISR里一定要先读状态,再根据状态分支处理,最后正确清除状态位。对于CIRQ和ERRI,要使用特殊的清除流程。错误处理分支必不可少,至少要记录错误类型,便于调试。
DMA配置要对齐:DMA的传输长度必须与控制器设定的BLEN严格一致。理解“每块一次请求”和“一字撤销请求”的握手机制,是避免DMA传输错位的关键。
缓冲区模式心中有数:清楚你的控制器缓冲区大小,并根据你的数据块大小,预判是工作在高效的双缓冲模式还是串行的单缓冲模式。这对性能预估和调优很重要。
调试时寄存器是眼睛:MMC_STAT和MMC_PSTATE是你最好的朋友。任何异常,首先看它们。结合示波器观察CMD/DAT/CLK线上的实际波形,是定位硬件问题的终极手段。
最后,SD协议本身很复杂,但控制器硬件帮我们封装了大部分底层细节。我们的工作,就是通过正确配置这些寄存器,让硬件按照我们期望的方式高效、可靠地运转。理解本文所述的三大机制,你就掌握了与这个硬件伙伴顺畅沟通的语言。