1. 项目概述:从寄存器手册到驱动实战
如果你正在开发嵌入式Linux或RTOS下的存储驱动,或者你负责的板卡上挂载了SD卡、eMMC或SDIO设备,那么你迟早要和MMC/SD/SDIO主机控制器打交道。这东西远不止是配置几个GPIO和时钟那么简单,它的核心是一套精密的“交通管制系统”,负责在CPU、内存和外部存储卡之间高效、可靠地搬运数据。
手册里密密麻麻的寄存器位描述,比如MMC_IE、MMC_STAT、MMC_BLK,常常让人望而生畏。但究其本质,控制器要解决的核心矛盾就两个:如何及时知道“有事情发生了”(中断与状态轮询),以及如何高效地把数据搬来搬去(DMA与缓冲区管理)。中断是“主动敲门”,告诉你命令完成了、数据准备好了或者出错了;DMA是“自动传送带”,让数据在卡和内存之间流动时尽量不打扰CPU;而缓冲区则是这条传送带上的“中转站”,其大小和管理策略直接决定了传输是顺畅还是拥堵。
本文将带你穿透TI AM261x这类芯片参考手册中关于MMC/SD/SDIO控制器(以SD控制器为例)的技术细节,不仅解释每个关键寄存器位是干什么的,更会结合我十多年在嵌入式存储驱动调试中的实际经验,告诉你这些机制在真实的驱动代码中是如何协同工作的,有哪些容易踩的坑,以及如何根据你的应用场景(比如高吞吐量连续录像还是小文件随机读写)来优化配置。无论你是正在啃手册的驱动开发者,还是希望深入理解系统外设交互的嵌入式软件工程师,这篇文章都将为你提供从理论到实践的完整视角。
2. 核心机制深度解析
MMC/SD/SDIO控制器作为主机与存储卡之间的桥梁,其设计哲学是在保证协议兼容性的前提下,最大化数据传输的效率和可靠性。它抽象了底层复杂的物理层和链路层时序,向上提供了一套可编程的寄存器接口。驱动开发者通过配置这些寄存器,来指挥控制器完成卡初始化、命令发送、数据搬运等一系列操作。理解其核心机制,是编写稳定高效驱动的基础。
2.1 中断驱动操作:如何高效处理异步事件
中断是控制器与CPU通信的“神经末梢”。当控制器内部发生某个特定事件(如命令完成、数据块就绪、传输错误)时,它会通过拉高中断信号线来“通知”CPU。相比于让CPU不断地去查询(轮询)状态寄存器,中断机制能极大地解放CPU算力,使其能够处理其他任务,只在需要时被唤醒,这对于低功耗系统和实时响应场景至关重要。
2.1.1 中断使能与状态识别
控制器的中断逻辑围绕两个核心寄存器展开:中断使能寄存器(MMC_IE)和中断状态寄存器(MMC_STAT)。
中断使能寄存器(MMC_IE)是一个“开关板”。它的每一位对应一种可能触发中断的事件。例如:
CMD_COMPLETE (CC): 命令传输完成。XFER_COMPLETE (TC): 数据块传输完成。BUFFER_READ_READY (BRR): 缓冲区中有数据可读(对于读操作)。BUFFER_WRITE_READY (BWR): 缓冲区中有空间可写(对于写操作)。DATA_CRC_ERROR (DCRC): 接收到的数据CRC校验错误。CARD_INTERRUPT (CIRQ): SDIO卡产生了中断(仅SDIO模式)。
在驱动初始化时,你需要根据你的操作需求,有选择地打开这些“开关”。例如,如果你只关心命令是否完成和数据是否传输完毕,那么只需使能CC和TC位。如果你使用SDIO设备的功能中断,则必须使能CIRQ。
中断状态寄存器(MMC_STAT)是一个“事件记录本”。当中断事件发生时,对应的状态位会被硬件自动置1。即使你没有使能该事件的中断(即MMC_IE对应位为0),事件的发生依然会被记录在MMC_STAT中,你可以通过轮询的方式读取它。
当中断线被触发,CPU跳转到中断服务程序(ISR)后,标准的处理流程如下:
- 读取MMC_STAT寄存器:这是第一步,也是最重要的一步。通过读取该寄存器,ISR可以精确地知道是哪个(或哪些)事件触发了本次中断。
- 处理事件:根据识别出的事件类型,执行相应的操作。例如,如果是
TC(传输完成),可能意味着一个DMA传输结束,可以准备下一个;如果是DCRC(数据CRC错误),则需要进行错误处理,如重试或上报。 - 清除中断状态:处理完事件后,必须通过向MMC_STAT寄存器的对应位写1来清除该状态位。这个操作有两个作用:一是告知控制器“这个中断我已经处理了”,二是释放(拉低)中断信号线,为下一次中断触发做好准备。一个常见的误区是试图通过写0来清除状态,这是无效的,必须写1。
实操心得:中断状态读取与清除的原子性在多任务或可能嵌套中断的复杂系统中,读取和清除
MMC_STAT需要小心。理想情况下,这个操作应该是原子的。一种稳健的做法是:在ISR中,先将读取到的MMC_STAT值保存到一个局部变量中,然后立刻用这个值(或一个所有待处理事件位的掩码)写回MMC_STAT寄存器进行清除。这样可以避免在“读取”和“写入”之间,又发生了新的事件而被覆盖或遗漏。许多控制器硬件设计保证了“写1清除”操作的原子性,但驱动代码最好以最安全的方式编写。
2.1.2 特殊中断位的处理:CIRQ与ERRI
手册中特别强调了两类特殊的中断状态位,它们的清除方式与常规位不同:
卡中断(CIRQ):这是由SDIO卡主动发起的中断。
MMC_STAT[8] CIRQ位不能通过写1直接清除。正确的处理流程是:- 首先,在
MMC_IE寄存器中屏蔽卡中断(将CIRQ_ENABLE位清0)。这可以防止在清理过程中产生新的中断干扰。 - 然后,你的中断服务程序需要去查询并清除SDIO卡内部的中断源。这通常通过读写SDIO卡的通用控制寄存器(CCCR)中的特定位来完成,具体操作取决于SDIO设备的功能。
- 完成上述操作后,
CIRQ状态位可能会由硬件自动清除,或者你需要重新使能CIRQ_ENABLE,具体行为需参考芯片手册。
- 首先,在
错误中断(ERRI):
MMC_STAT[15] ERRI是一个聚合错误标志。当MMC_STAT[31:16]这个高16位区间内的任何一个具体错误状态位(如CTO命令超时、CCRC命令CRC错误、DTO数据超时等)被置位时,ERRI位也会被置位。ERRI位本身也无法直接写1清除。它的清除条件是:当MMC_STAT[31:16]区间内所有具体的错误状态位都被清除后,ERRI位会自动清零。因此,处理错误中断时,你需要逐一检查并清除[31:16]中的具体错误位。
2.1.3 轮询模式:简单场景下的备选方案
当你不希望使用中断,或者在某些极简、确定性要求极高的场景下,可以禁用特定事件的中断能力(通过MMC_IE寄存器)。此时,中断线将不会被触发。 取而代之的是轮询(Polling)模式:软件需要主动地、周期性地去读取MMC_STAT寄存器,检查感兴趣的事件状态位是否被置1。一旦检测到事件发生,处理逻辑与中断模式类似,同样需要写1清除状态位。轮询模式虽然增加了CPU开销,但避免了中断上下文切换的开销和不确定性,在裸机或对延迟有严格上限的简单任务中仍有其用武之地。
2.2 DMA传输模式:解放CPU的数据搬运工
直接内存访问(DMA)是提升系统性能的关键技术。在MMC/SD/SDIO控制器中,DMA的目标是将CPU从繁重的数据搬运工作中解脱出来。控制器本身通常作为DMA响应器(Responder),这意味着它不主动发起复杂的DMA传输事务,而是向系统级的DMA控制器(如EDMA)发出请求,由后者来执行具体的内存读写操作。
2.2.1 DMA响应器模式工作原理
控制器通过两根独立的信号线与DMA控制器连接:SDMARREQN(读请求)和SDMAWREQN(写请求)。当控制器准备好接收或发送数据时,会拉低对应的请求线。
DMA传输的启动需要满足几个条件:
- DMA使能:在发送数据传输命令(如CMD17读单块,CMD25写多块)之前,需要设置
MMC_CMD[0] DE(DMA Enable)位为1。这相当于告诉控制器:“接下来的数据传输请使用DMA通道”。 - 命令已发送:相应的读/写命令已经通过CMD线发送给卡。
- 缓冲区就绪:
- 对于读操作(DMA Receive):控制器从卡接收到完整的一个数据块并存入其内部缓冲区后,才会断言
SDMARREQN信号,通知DMA控制器“数据已备好,快来取”。 - 对于写操作(DMA Transmit):当控制器的内部缓冲区有足够的空间容纳一个完整的数据块时,会断言
SDMAWREQN信号,通知DMA控制器“缓冲区已清空,可以送下一块数据过来了”。
- 对于读操作(DMA Receive):控制器从卡接收到完整的一个数据块并存入其内部缓冲区后,才会断言
这里的关键概念是块(Block)。所有DMA请求都是以块为单位产生的。块的大小由MMC_BLK[10:0] BLEN字段定义,常见的是512字节,但也支持其他大小(如1024、2048字节,取决于控制器能力)。
2.2.2 DMA接收模式详解
假设我们执行一个多块读操作(CMD18),BLEN设置为512字节。
- 控制器从卡读取512字节数据,填满其内部缓冲区的一个块区域。
- 控制器拉低
SDMARREQN信号,向DMA控制器发出请求。 - DMA控制器收到请求,开始从控制器的数据寄存器(
MMC_DATA)读取数据到系统内存。这里有一个重要细节:SDMARREQN信号在DMA控制器读取第一个字(32位)后就会被释放(拉高)。这意味着每个数据块只产生一次DMA请求脉冲。 - DMA控制器需要负责将整个块(512字节)读完。它可以选择进行一次性的“单次(1-shot)”传输,或者分成多次“突发(Burst)”传输。无论哪种方式,DMA控制器内部必须维护一个计数器,确保总共读取
Integer(BLEN/4) + 1次(因为每次访问32位,即4字节)。对于512字节,就是128 + 1 = 129次32位读取。 - 在DMA控制器搬运当前块数据的同时,控制器的缓冲区可以开始接收下一个数据块,实现流水线操作,前提是缓冲区足够大(见后续缓冲区管理部分)。
- 如果DMA控制器尚未完成前一个块的读取(即未读满
BLEN字节),而控制器又收到了下一个完整块,新的DMA请求会被内部屏蔽,直到前一个请求被服务完。这避免了数据覆盖。
注意事项:时钟门控与流控手册中提到一个高级特性:在进行大于512字节的多块传输时,如果缓冲区满了,控制器会暂时停止提供给卡的时钟(MMC_CLK)。这是一种硬件流控机制,防止从卡端涌来的数据过快导致缓冲区溢出。时钟会一直保持停止,直到CPU或DMA从缓冲区中读取了一个完整块的数据,腾出空间。这在编写驱动时通常无需特别处理,但理解这一点有助于调试一些与吞吐量相关的性能问题。
2.2.3 DMA发送模式详解
写操作(DMA Transmit)是读操作的逆过程。
- 当控制器内部缓冲区有至少
BLEN字节的空闲空间时,拉低SDMAWREQN信号。 - DMA控制器将系统内存中的一个数据块(
BLEN字节)写入控制器的MMC_DATA寄存器。 - 当DMA控制器写入第一个字后,
SDMAWREQN请求信号即被释放。 - DMA控制器需要完成整个块的写入。同样,如果DMA写入速度慢,导致缓冲区空间不足以容纳下一个新块,新的写请求会被屏蔽。
2.2.4 DMA配置要点与性能考量
- DMA控制器配置:你需要在系统DMA控制器(如EDMA)中正确配置源/目标地址、传输数量(
BLEN)、地址递增模式等。控制器发出的请求通常连接到DMA控制器的特定通道。 - 块大小(BLEN)选择:较大的块(如2048字节)可以减少中断/DMA请求的频率,提升大文件连续读写的效率。但较小的块(如512字节)可能更适合文件系统元数据等随机小访问。需要根据应用场景权衡。
- 双缓冲支持:DMA的高效性很大程度上依赖于控制器内部是否支持双缓冲(Ping-Pong Buffer),这允许数据搬入和搬出同时进行。这取决于
BLEN和缓冲区总大小的关系,我们将在下一节详细讨论。
2.3 缓冲区管理:数据吞吐量的心脏
数据缓冲区是MMC/SD/SDIO控制器的核心枢纽,所有数据都要经过这里。它的管理策略直接决定了数据传输是“单车道拥堵”还是“双车道并行”。
2.3.1 缓冲区结构与访问接口
控制器的数据缓冲区对外表现为一个32位的寄存器:MMC_DATA。驱动或DMA控制器通过读写这个寄存器来与缓冲区交换数据。但背后,缓冲区可能包含三个部分:
- 主数据缓冲区:存储数据的主体。
- 预取寄存器(Prefetch Register):用于读操作。当从缓冲区读数据时,数据会先被预取到这个寄存器,使得后续的读取访问更快。
- 后写缓冲区(Post-write Buffer):用于写操作。当向
MMC_DATA写入数据时,数据先进入这个缓冲区,再异步写入主缓冲区。
这就解释了手册中的一个重要现象:连续对MMC_DATA进行一次写和一次读,读到的数据并不是刚才写入的数据。因为写操作进入了后写缓冲区,而读操作来自预取寄存器,它们不是同一个物理存储单元。
访问MMC_DATA寄存器有严格的条件,由两个状态位控制:
MMC_PSTATE[11] BRE(Buffer Read Enable):为1时,才允许从MMC_DATA读取数据(表示缓冲区有数据可读)。MMC_PSTATE[10] BWE(Buffer Write Enable):为1时,才允许向MMC_DATA写入数据(表示缓冲区有空间可写)。 如果违反这些条件进行访问,会触发MMC_STAT[29] BADA(Bad Access)错误。
2.3.2 双缓冲与单缓冲模式
这是缓冲区管理最精髓的部分。控制器根据要传输的数据块大小(BLEN)与缓冲区总内存大小(MEM_SIZE)的一半进行比较,动态选择工作模式。
1. 双缓冲模式(当 BLEN <= MEM_SIZE / 2 时)
- 原理:控制器将内部缓冲区逻辑上划分为两个相等的部分(A区和B区)。当A区正在被DMA或CPU读取数据时,B区可以同时接收从卡来的新数据,反之亦然。这种“乒乓”操作实现了真正的并行流水线。
- 效果:数据吞吐量接近理论峰值,因为数据的搬入和搬出在时间上几乎重叠。
- 示例:假设
MEM_SIZE为1024字节,BLEN为512字节。那么每个区大小正好是512字节。在读取数据时,DMA从A区搬走数据的同时,控制器可以将下一个512字节的数据写入B区。
2. 单缓冲模式(当 BLEN > MEM_SIZE / 2 时)
- 原理:整个缓冲区作为一个整体使用。在任一时刻,缓冲区只能服务于一个方向的数据流(要么全部用于接收来自卡的数据,要么全部用于向卡发送数据)。
- 效果:数据传输是串行的。控制器必须等当前块的数据全部被DMA搬出缓冲区后,才能开始接收下一个块;或者等DMA填满整个缓冲区后,才能开始向卡发送数据。这会引入等待时间,降低吞吐量。
- 风险:如果试图在单缓冲模式下同时进行读和写(例如错误的并发操作),会触发
BADA错误。
手册中的表格清晰地展示了这种关系:
| 内存大小 (MEMSIZE) | 支持的最大块长 (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) |
驱动设计启示为了获得最佳性能,在驱动初始化时,应尽可能查询或设置控制器的
MEM_SIZE(通常通过MMC_CAPA寄存器),并选择BLEN使其满足BLEN <= MEM_SIZE/2,从而启用双缓冲模式。对于大多数SD/eMMC应用,512字节的块长是标准且通常能启用双缓冲的。对于高性能eMMC设备,可能会使用更大的块(如1024或2048字节),此时需要确保控制器的缓冲区足够大。
2.3.3 传输方向配置
在进行任何数据传输之前,必须正确设置MMC_CMD[4] DDIR位。该位为0表示数据从主机到卡(写操作),为1表示数据从卡到主机(读操作)。这个配置直接影响缓冲区的读写使能逻辑和DMA请求的触发条件。忘记设置或设置错误是导致传输失败或BADA错误的常见原因。
3. 驱动开发实战流程与核心环节
理解了核心机制后,我们将其串联起来,看一个典型的SD卡驱动初始化及数据读写流程是如何实现的。这里以SD Memory Card为例,SDIO和MMC的流程在识别阶段有所不同。
3.1 控制器初始化与卡识别流程
这是驱动启动的第一步,目标是让控制器进入一个已知的、稳定的状态,并识别出连接的卡类型。
步骤1:全局模块初始化在访问SD控制器寄存器之前,需要确保其所在的“生态系统”已经就绪:
- 电源、复位、时钟(PRCM):使能控制器模块的接口时钟和功能时钟。很多SoC的SD控制器时钟是分频自某个PLL,需要正确配置。
- 控制模块(Control Module):配置引脚复用,将SD控制器的CMD、DAT、CLK等信号线正确映射到芯片的物理引脚上。
- 中断控制器(如VIM):如果使用中断模式,需要配置中断控制器,将SD控制器的中断线映射到CPU的某个中断向量,并设置优先级和使能。
- DMA控制器(如EDMA):如果使用DMA,需要预先配置好DMA通道的参数,如传输类型、地址、链接等,但通常此时还未激活。
步骤2:控制器软复位通过向SD_SYSCONFIG寄存器的SOFTRESET位写1,触发控制器内部复位。然后轮询SD_SYSSTATUS寄存器的RESETDONE位,直到它变为1,表示复位完成。这确保了所有寄存器恢复到上电默认状态。
步骤3:设置控制器能力从MMC_CAPA等寄存器读取控制器的硬件能力,如支持的最高时钟频率、电压、总线宽度等。有时也需要根据板级设计(如电平转换器支持的最高电压)来限制实际使用的电压(通过MMC_HCTL的SDVS字段)。
步骤4:总线配置与上电
- 设置电压与总线宽度:通过
MMC_HCTL寄存器配置卡的工作电压(如3.3V)和总线宽度(1位或4位)。同时,设置SDBP位为1以打开卡槽电源。 - 使能内部时钟并设置低速:设置
MMC_SYSCTL[0] ICE为1使能内部时钟。在卡识别阶段,协议要求时钟频率不能超过400kHz。因此,需要配置MMC_SYSCTL[15:6] CLKD分频器,将时钟降到低速(例如100-400kHz)。轮询ICS位直到为1,表示时钟稳定。 - 发送初始化序列:设置
MMC_CON[1] INIT为1,控制器会向CMD线发送至少74个时钟周期的高电平,使卡进入空闲状态。 - 发送CMD0(GO_IDLE_STATE):让卡复位到Idle状态。
步骤5:卡类型识别(简化流程)这是一个命令交互的过程,驱动需要根据卡的响应来判断其类型:
- 发送CMD8(SEND_IF_COND):这是一个SD卡V2.0引入的指令,用于询问卡是否支持某些电压和检查模式。如果卡响应(无超时
CTO),则它是SD卡(V2.0或更高)。如果命令超时,则可能是SD卡V1.x或MMC卡。 - 发送ACMD41(SD_SEND_OP_COND):对于SD卡,需要先发CMD55(APP_CMD)告知下一个是应用命令,再发ACMD41,带上主机支持的能力(如高容量支持)。卡会在响应中返回其忙状态和操作条件。重复此过程直到卡不再忙。
- 发送CMD1(MMC_SEND_OP_COND):对于MMC卡,发送CMD1进行初始化。
- 获取卡标识(CID)和相对地址(RCA):发送CMD2(ALL_SEND_CID)获取卡的唯一标识符。然后发送CMD3(SEND_RELATIVE_ADDR),让卡分配一个用于后续寻址的短地址(RCA)。
- 选择卡:发送CMD7(SELECT/DESELECT_CARD)并带上RCA,使该卡进入传输状态(Transfer State),此时才能进行数据读写。
实操心得:超时与重试机制卡识别过程充满了不确定性。每个命令发送后,都必须检查
MMC_STAT寄存器中的命令完成(CC)和命令超时(CTO)标志。CTO超时不一定代表卡不存在,可能是卡尚未准备好响应。一个健壮的驱动会在关键步骤(如ACMD41)实现重试机制,例如循环发送命令并等待一小段时间(如10ms),最多重试N次(如100次),直到收到有效响应或最终失败。
3.2 数据传输命令执行流程
以DMA模式下的多块读(CMD18)为例,详解一次数据传输的完整寄存器操作序列:
参数准备:
- 设置
MMC_BLK寄存器,定义块大小(BLEN,如512)。 - 设置
MMC_ARG寄存器,填入命令参数(如起始扇区地址)。 - 配置
MMC_CMD寄存器:CMDINDEX: 填入命令号18。DE(DMA Enable): 置1,启用DMA。DDIR: 置1,表示读操作。TYP/RSPTYP: 根据SD协议设置命令类型和响应类型。
- (可选)如果使用“块间隙停止”功能,设置
MMC_HCTL[16] SBGR。
- 设置
启动传输:
- 将配置好的
MMC_CMD值写入寄存器。这个写操作会触发控制器立即通过CMD线向卡发送命令。
- 将配置好的
DMA传输与中断处理:
- 控制器收到卡的响应后,会置位
CC标志(如果使能了中断,则触发中断)。 - 卡开始发送数据。当第一个完整的数据块(512字节)到达控制器缓冲区后,控制器断言
SDMARREQN信号。 - DMA控制器被唤醒,开始从
MMC_DATA寄存器读取数据到目标内存。 - 当整个数据块传输完成,控制器会置位
TC(Transfer Complete)标志。 - 对于多块读,上述“块就绪 -> DMA请求 -> 块传输完成”的过程会重复,直到所有请求的块传输完毕。
- 控制器收到卡的响应后,会置位
停止传输:
- 对于多块读,需要发送CMD12(STOP_TRANSMISSION)来终止传输。如果使能了
SBGR,控制器会自动在块间隙发送CMD12。 - 发送CMD12后,等待其
CC标志,确认停止命令已被卡接受。
- 对于多块读,需要发送CMD12(STOP_TRANSMISSION)来终止传输。如果使能了
错误检查:
- 在整个过程中,ISR或主循环需要持续监控
MMC_STAT寄存器中的错误位,如DCRC(数据CRC错误)、DTO(数据超时)等,并进行相应处理。
- 在整个过程中,ISR或主循环需要持续监控
3.3 时钟与信号边沿配置
为了达到更高的数据传输速率(如SDR104模式需要208MHz时钟),时序裕量变得非常紧张。控制器提供了信号边沿调整功能。
- 默认模式(下降沿驱动):
MMC_HCTL[2] HSPE = 0。控制器在时钟的下降沿更新CMD和DAT信号。这有助于最大化保持时间(Hold Time)的裕量。 - 高速模式(上升沿驱动):
MMC_HCTL[2] HSPE = 1。控制器在时钟的上升沿更新信号。这有助于最大化建立时间(Setup Time)的裕量,对于高频操作至关重要。特别注意:在双倍数据率(DDR)模式下,绝对不能使用此模式。
在驱动中,通常在完成卡识别、准备切换到高速模式(如SDR25, SDR50)时,根据芯片数据手册的推荐或实际信号完整性测试结果,来配置HSPE位。
4. 调试技巧与常见问题排查
即使理解了所有原理,在实际开发中依然会遇到各种问题。以下是我在多年调试中总结的一些常见陷阱和排查思路。
4.1 常见问题速查表
| 问题现象 | 可能原因 | 排查步骤与解决方法 |
|---|---|---|
| 卡识别失败,CMD8或ACMD41无响应 | 1. 电源/时钟未就绪。 2. 引脚复用错误。 3. 时钟频率在识别阶段过高。 4. 卡本身损坏或接触不良。 | 1. 用示波器测量卡槽的VCC和CLK引脚,确认电压正常且有时钟信号。 2. 检查控制模块的引脚复用配置,确认CMD/DAT/CLK线正确映射。 3. 确保在发送CMD8前,时钟分频器已设置为低速(≤400kHz)。 4. 更换SD卡或读卡器测试。 |
数据传输过程中出现BADA错误 | 1. 在BRE=0时尝试读MMC_DATA,或在BWE=0时尝试写MMC_DATA。2. 单缓冲模式下试图并发读写。 3. DDIR方向位设置错误。 | 1. 在读写MMC_DATA前,务必检查MMC_PSTATE中的BRE或BWE位。2. 检查 BLEN和MEM_SIZE,确认是否意外进入了单缓冲模式。确保代码没有并发访问。3. 在启动传输命令前,再次确认 MMC_CMD[4] DDIR位设置是否正确。 |
DMA传输启动后无反应,SDMARREQN/SDMAWREQN无信号 | 1.MMC_CMD[0] DE位未置1。2. DMA控制器通道未正确配置或未使能。 3. 数据块大小 BLEN设置与DMA传输量不匹配。4. 目标内存地址不可访问或未对齐。 | 1. 检查发送的读写命令寄存器值,确认DE位已使能。2. 检查DMA控制器的通道配置:源/目标地址、传输数量(应为 BLEN字节)、请求信号映射是否正确。3. 确保DMA配置的传输字节数等于 BLEN。4. 检查DMA目标内存地址是否在有效范围内,对于某些DMA或SD控制器,地址可能需要32位对齐。 |
| 中断无法触发或只触发一次 | 1. 中断使能寄存器MMC_IE未正确配置。2. 中断状态寄存器 MMC_STAT未及时清除。3. 系统中断控制器(如GIC、NVIC)未配置。 4. 对于 CIRQ,未按正确流程处理。 | 1. 确认在启动操作前,已使能了所需事件的中断位(如CC,TC,BRR等)。2. 在ISR中,读取 MMC_STAT后,必须写1清除已处理的事件位。这是最常见的原因。3. 确认CPU全局中断已打开,且SD控制器的中断号已在系统中断控制器中正确配置和使能。 4. 对于SDIO卡中断,参考“特殊中断位的处理”一节。 |
| 传输速度远低于预期 | 1. 时钟频率未提升到卡支持的最高速。 2. 未使用4位宽总线模式。 3. 未启用DMA,或DMA配置效率低。 4. 块大小 BLEN设置过小,或未启用双缓冲。5. 文件系统或上层软件瓶颈。 | 1. 卡识别完成后,通过CMD6切换卡到高速模式,并提高控制器时钟分频。 2. 通过ACMD6将总线宽度切换到4位。 3. 启用DMA传输,并检查DMA是否为突发(Burst)模式。 4. 尝试增大 BLEN(如从512到1024),并确认满足BLEN <= MEM_SIZE/2以启用双缓冲。5. 使用 dd命令直接读写块设备进行测速,排除文件系统影响。 |
| 写操作后数据校验错误 | 1. 未等待写操作完成就断电或移除卡。 2. 缓存(Cache)一致性问题。 3. DMA传输的内存区域有误。 | 1. 写命令完成后,SD卡内部可能仍在编程(Programming)。需要发送CMD13(SEND_STATUS)查询卡状态,直到READY_FOR_DATA位为1。2. 如果DMA目标内存是Cacheable的,在启动DMA写操作前,需要清理(Clean)该内存区域的缓存;在DMA读操作完成后,需要无效(Invalidate)该区域的缓存。或者使用非缓存(Non-cacheable)内存区域。 3. 核对DMA传输的目标地址是否正确。 |
4.2 高级调试手段
- 利用测试寄存器(MMC_FE):手册中提到
MMC_FE寄存器可以用于手动强制产生某些事件或中断。这在驱动开发早期非常有用,可以在不插卡的情况下,模拟命令完成、数据传输等中断,来测试你的中断服务程序逻辑是否正确。 - 状态寄存器轮询诊断:当问题复杂时,不要只依赖中断。在关键流程中(如卡识别循环),主动轮询
MMC_PSTATE寄存器。关注CMDI(命令线禁止)和DATI(数据线禁止)位,它们为1时表示控制器正忙,禁止发送新命令。这能帮你判断流程卡在了哪一步。 - 逻辑分析仪/示波器抓取波形:对于硬件时序问题(如识别失败、高速模式不稳定),软件调试可能无能为力。必须使用逻辑分析仪抓取CMD、DAT、CLK线上的实际波形。检查:
- 命令和响应的CRC是否正确。
- 数据读写时的建立/保持时间是否满足卡和控制器数据手册的要求。
- 时钟频率和占空比是否正确。
- 上电时序和电压是否稳定。
- 分阶段测试:将驱动初始化过程分解为:电源时钟初始化 -> 控制器复位 -> 低速识别 -> 高速切换 -> 单块PIO读写 -> 多块DMA读写。确保每一步都稳定后再进入下一步,可以快速定位问题阶段。
驱动开发是一个系统工程,MMC/SD/SDIO控制器只是其中一环。除了控制器本身的配置,电源的稳定性、PCB的信号完整性、时钟的抖动、DMA控制器的性能、甚至操作系统的调度延迟,都可能影响最终的稳定性和性能。理解本文阐述的核心机制,结合扎实的调试方法,才能构建出稳定高效的存储子系统。