1. 项目概述
在嵌入式系统开发中,存储模块的稳定性和效率往往是决定产品成败的关键细节之一。尤其是在资源受限、对功耗和实时性有严格要求的场景下,如何与MMC/SD这类通用存储卡高效、可靠地通信,是每个嵌入式工程师必须啃下的硬骨头。今天,我们就来深入剖析一款经典的嵌入式处理器——摩托罗拉(后为飞思卡尔)MC68SZ328内部的MMC/SD主机控制器。这份来自其官方参考手册的文档,虽然年代久远,但其设计思想和对协议细节的把握,至今仍具有极高的学习和参考价值。它不仅仅是一份寄存器说明书,更是一扇窗口,让我们得以窥见在硬件层面如何精准地驾驭复杂的MMC/SD协议,处理从卡初始化、数据读写到安全擦除、中断响应等一系列高级操作。无论你是正在为老产品维护寻找线索,还是希望从经典设计中汲取硬件接口编程的精髓,这篇文章都将为你提供一份详尽的“地图”。
2. 核心硬件与协议基础解析
2.1 MC68SZ328与MMC/SD主机控制器定位
MC68SZ328是一款基于龙珠(DragonBall)核心的微处理器,广泛应用于早期的PDA、工业控制终端等嵌入式设备。其集成的MMC/SD主机控制器,是芯片与外部MMC(多媒体卡)、SD(安全数字卡)进行物理层和数据链路层通信的专用硬件模块。它的核心价值在于,将复杂的、时序要求严格的MMC/SD协议通信过程,通过一组精心设计的寄存器暴露给软件开发者,从而极大地简化了驱动开发难度。开发者无需用GPIO模拟复杂的时序,只需读写这些寄存器,控制器硬件便会自动完成命令发送、响应接收、数据搬运乃至CRC校验等工作。
2.2 MMC/SD通信协议精要
要理解控制器的编程模型,必须先掌握MMC/SD通信的基本范式。这是一种典型的主从式、命令-响应型串行通信协议。
命令与响应格式:所有通信由主机发起。主机通过CMD线发送一个48位的命令帧,包含命令索引(如CMD0, CMD7)、32位参数和7位CRC。卡在收到命令后,会在CMD线上回复一个响应帧。响应有多种格式(R1, R1b, R2, R3等),长度和内容因命令而异,但核心都包含一个**卡状态(Card Status)**字段,这是一个32位的寄存器,实时反映了卡的操作状态和错误信息。手册中表17-4对此进行了巨细无遗的说明,这是我们进行错误诊断和流程控制的根本依据。
卡的状态机:MMC/SD卡内部维护着一个状态机(如idle, ready, ident, tran, data, rcv, prg等)。任何命令都必须在卡处于特定状态时才能被接受和执行。例如,CMD7(选择卡)只能在卡处于
stby(待命)状态时使用,执行后卡进入tran(传输)状态,才能进行数据读写。理解状态迁移是编写正确初始化序列和操作流程的前提。数据传送:数据通过DAT线(1位或4位模式)传输,以块(Block)为单位。块长度通常为512字节,可通过CMD16设置。读写操作分为单块(CMD17/CMD24)和多块(CMD18/CMD25)模式。多块读写时,需要以CMD12(停止传输)命令来终止。
应用特定命令(ACMD):这是SD卡协议的一个扩展机制。要发送一个ACMD(如ACMD41用于SD卡初始化),必须先发送一个CMD55(APP_CMD)命令,通知卡下一个命令是应用命令。这是一个非常容易出错的点,很多驱动问题都源于ACMD序列不正确。
3. 命令集深度解析与实战应用
手册中的表17-5列出了完整的命令集,我们可以将其分为几个功能组来理解,并探讨其编程实践。
3.1 卡识别与初始化命令组
这是上电后与卡建立通信的第一步,流程必须严格。
- CMD0 (GO_IDLE_STATE):软件复位命令。无论卡处于何种状态,收到此命令后都会复位到
idle状态。这是所有通信的起点。在硬件上,通常需要先给卡供电并保持至少74个时钟周期稳定,再发送CMD0。 - CMD1 (SEND_OP_COND) / ACMD41 (SD_APP_OP_COND):初始化命令。MMC卡使用CMD1,SD卡使用ACMD41。主机通过此命令向卡发送操作条件(如电压范围),卡回复其操作条件寄存器(OCR)内容。这是一个反复协商的过程,主机可能需要发送多次,直到OCR中的“忙”位清除,表示卡初始化完成。实操要点:必须根据卡的类型(通过CMD8或OCR内容判断)选择正确的命令,对SD卡必须使用
CMD55 + ACMD41序列。 - CMD2 (ALL_SEND_CID):获取所有卡的唯一CID(卡识别号)。在总线上广播此命令,所有卡都会回复其CID。CID是全局唯一的。
- CMD3 (SET_RELATIVE_ADDR):为卡分配相对地址(RCA)。因为总线上可能有多个卡,CID太长不适合频繁寻址,所以主机为每个卡分配一个16位的短地址RCA,后续操作都基于RCA进行。
- CMD7 (SELECT/DESELECT_CARD):选择/取消选择卡。通过指定RCA,将目标卡从
stby状态切换到tran状态,只有处于tran状态的卡才能进行数据读写。发送RCA=0可以取消选择所有卡。
初始化流程伪代码示例:
// 1. 硬件上电,提供稳定时钟(>74 cycles) mmc_power_on(); delay_ms(10); // 2. 发送CMD0复位所有卡到idle状态 send_cmd(CMD0, 0, R1); // 3. 发送CMD8 (仅SD卡v2.0以上支持) 检查电压兼容性 send_cmd(CMD8, 0x1AA, R7); // 参数0x1AA表示支持2.7-3.6V,检查模式 if (response_ok) { card_type = SD_CARD_V2; } else { card_type = MMC_CARD; // 或SD卡v1.x } // 4. 发送初始化命令(循环直到卡就绪) uint32_t arg = HOST_VOLTAGE_WINDOW; // 主机支持的电压范围 if (card_type == SD_CARD_V2 || card_type == SD_CARD_V1) { // SD卡使用ACMD41 do { send_cmd(CMD55, current_rca, R1); // 先发CMD55 send_cmd(ACMD41, arg, R3); // 再发ACMD41 } while (!(ocr & (1 << 31))); // 检查OCR第31位(卡上电完成位) } else { // MMC卡使用CMD1 do { send_cmd(CMD1, arg, R3); } while (!(ocr & (1 << 31))); } // 5. 获取CID (CMD2) 和分配RCA (CMD3) send_cmd(CMD2, 0, R2); // 获取CID send_cmd(CMD3, 0, R1); // 分配RCA,响应中会包含主机分配的RCA current_rca = response_rca; // 6. 获取CSD (CMD9) 并选择卡 (CMD7) send_cmd(CMD9, current_rca, R2); // 获取CSD,其中包含卡容量、块大小等信息 send_cmd(CMD7, current_rca, R1b); // 选择卡,进入tran状态3.2 数据读写命令组
这是核心的数据操作命令。
- CMD16 (SET_BLOCKLEN):设置块长度。虽然很多卡默认块长为512字节,但显式设置是一个好习惯,确保主机和卡对块大小的理解一致。
- CMD17/18/24/25:单块/多块读写。这是最常用的命令。关键点:
- 发送读写命令前,必须确保卡已被选中(处于
tran状态)。 - 多块读写时,主机必须在数据传输结束后发送CMD12来终止传输,否则卡会一直等待数据,导致总线挂起。
- 写操作后,卡会进入
prg(编程)状态,此时DAT0线会被拉低(忙信号)。主机必须通过轮询卡状态(CMD13)或检测DAT0线,等待编程完成,才能进行下一步操作。
- 发送读写命令前,必须确保卡已被选中(处于
3.3 擦除与写保护命令组
涉及卡的安全管理和存储空间维护。
- CMD32-38:擦除相关命令。擦除操作是“标记-执行”两步过程:先用CMD32/33标记起始和结束扇区(或CMD35/36标记擦除组),最后用CMD38执行擦除。这允许精确控制擦除范围。
- CMD28-30:写保护命令。如果卡支持写保护,可以通过这些命令设置/清除/查询特定地址范围的写保护状态。
- CMD42 (LOCK_UNLOCK):卡锁定/解锁。这是安全功能,可以为卡设置密码(PWD)并锁定。锁定后,任何读写擦除操作都需要先解锁。手册中特别提到了**强制擦除(Forced Erase)**流程:当用户忘记密码时,可以通过发送一个特殊的LOCK_UNLOCK命令(数据块中仅ERASE位为1)来擦除卡上所有数据(包括密码本身),从而解锁卡。重要警告:对已解锁的卡执行锁定操作,或对已锁定的卡执行解锁操作(密码错误),都会导致
LOCK_UNLOCK_FAILED错误位置位。
3.4 特殊功能命令组
- CMD55 + ACMDxx:应用特定命令。如前所述,这是SD卡功能的扩展入口。
- CMD56 (GEN_CMD):通用命令。用于传输供应商特定的数据块,格式和含义由厂商定义,为定制功能留出了空间。
- CMD40 (GO_IRQ_STATE):中断模式。这是一个高级功能,旨在降低主机轮询开销。主机将所有卡置入中断等待状态,卡在内部事件(如数据准备就绪)发生时主动发起响应。手册详细描述了其仲裁机制(开漏输出,多卡同时响应时表现为“线与”)和退出方法。
4. 状态寄存器与错误处理实战
卡状态寄存器(表17-4)是调试的“眼睛”。它不仅仅是一个错误标志的集合,更精确反映了卡在执行上一个命令时的内部状况。
4.1 状态位分类与处理策略
状态位按类型(Type)可分为:
- E (Error bit):错误位。如
OUT_OF_RANGE(地址超限)、COM_CRC_ERROR(命令CRC错误)、ILLEGAL_COMMAND(非法命令)等。一旦发生,当前操作肯定失败,需要软件介入处理。 - S (Status bit):状态位。如
CARD_IS_LOCKED(卡被锁定)、READY_FOR_DATA(缓冲区空,准备接收数据)等。用于指示卡的当前状况,驱动流程决策。 - R/X (检测时机):
R表示该位在命令响应时被检测和设置;X表示在命令执行过程中被检测和设置,主机需要通过发送CMD13(查询状态)来读取。
按清除条件(Clear condition)可分为:
- C (Clear by read):读取状态寄存器后自动清除。大多数错误位属于此类。
- B (Clear by next valid command):收到下一个有效命令后清除。如
ILLEGAL_COMMAND。 - A (According to card state):根据卡的实际状态变化。如
CARD_IS_LOCKED。
编程策略:在发送任何命令后,尤其是可能失败的命令(如写、擦除、解锁),都应读取卡状态(CMD13)。不仅要检查错误位(E),还要检查状态位(S),例如READY_FOR_DATA位在写操作时至关重要。
4.2 典型错误排查流程
假设一次写操作失败,卡状态返回0xA000800(二进制1010 0000 0000 0000 1000 0000 0000)。我们逐位解析:
- Bit 31 (
OUT_OF_RANGE) = 1:错误,地址超出范围。 - Bit 29 (
BLOCK_LEN_ERROR) = 1:错误,块长度错误。 - Bit 25 (
CARD_IS_LOCKED) = 1:状态,卡被锁定。 - Bit 8 (
READY_FOR_DATA) = 1:状态,缓冲区空(对于写操作,这通常是正常的)。
诊断与行动:
- 卡被锁定:这是根本原因。在卡锁定的状态下,除了解锁或强制擦除命令,其他所有访问命令都会被拒绝,并可能伴随其他错误(如地址错误,因为命令根本未被执行到地址解析阶段)。
- 处理顺序:首先处理
CARD_IS_LOCKED。发送CMD13确认状态,然后尝试使用正确的密码发送CMD42解锁。如果密码错误或忘记,考虑使用强制擦除流程(会丢失所有数据)。 - 错误清除:在解决锁定问题后,这些错误位(OUT_OF_RANGE, BLOCK_LEN_ERROR)需要被清除。由于它们是
C类(读取清除),只需再发送一次CMD13读取状态,错误位就会清零。但请注意,在卡仍处于锁定状态时读取,这些错误位可能依然存在,所以必须在解锁成功后进行。
这个例子清晰地展示了状态寄存器各位的关联性,以及如何通过它进行层层递进的故障诊断。
5. MC68SZ328主机控制器编程模型详解
手册第17.7节是驱动开发者的“操作面板”。所有对MMC/SD卡的控制,最终都归结为对这些寄存器的读写。
5.1 关键寄存器功能与配置流程
时钟控制寄存器 (STR_STP_CLK, 0xFFFE0300):
- SYSRST (Bit 3)和MMCSDEN (Bit 2):模块软复位和使能。手册给出了一个必须严格遵守的写入序列:
0x0008 -> 0x000d -> 0x0005。这个序列的目的是在使能时钟前确保模块处于确定状态,避免电源或时钟毛刺导致异常。任何偏离此序列的操作都可能导致控制器无法正常工作。 - START_CLK/STOP_CLK (Bit 1-0):控制MMC/SD_CLK输出。严禁同时设置为1。通常,在初始化阶段启动时钟,在进入低功耗模式时停止时钟。
- SYSRST (Bit 3)和MMCSDEN (Bit 2):模块软复位和使能。手册给出了一个必须严格遵守的写入序列:
命令与数据控制寄存器 (CMD_DAT_CONT, 0xFFFE0310):这是发送命令前的“指令组装台”。
- FRES (Bits 2-0):设置期望的响应格式。例如,发送CMD0(无响应)应设为
000,发送CMD13(查询状态,响应为R1)应设为001。设置错误会导致控制器无法正确解析卡的响应。 - DATEN (Bit 3):指示当前命令是否包含数据传输。例如,CMD17(读单块)需要将此位置1,CMD13(查询状态)则置0。
- WRRD (Bit 4):指示数据传输方向。0为读(主机从卡读),1为写(主机向卡写)。
- STRBLK (Bit 5):选择流模式或块模式。常规读写都是块模式(0)。
- BUSW (Bits 9-8):设置数据总线宽度。00为1-bit,10为4-bit。注意:切换到4-bit模式是SD卡的高级功能,需要先通过ACMD6命令通知卡,然后再设置此寄存器,两者必须匹配。
- FRES (Bits 2-0):设置期望的响应格式。例如,发送CMD0(无响应)应设为
命令与参数寄存器 (CMD, ARGUMENTH/L, 0xFFFE032C/0330/0332):
CMD寄存器低6位写入命令索引(如CMD17对应0x11)。- 32位命令参数写入
ARGUMENTH(高16位)和ARGUMENTL(低16位)两个寄存器。
状态寄存器 (STATUS, 0xFFFE0304):反映控制器自身状态,而非卡状态。
- ECR (Bit 13):命令响应结束。硬件在收到卡的完整响应后置位,通知软件可以读取响应FIFO了。
- AOD (Bit 12)/DTD (Bit 11):访问操作完成/数据传输完成。用于判断控制器何时结束一次命令或数据事务。
- RCRCERR, CRCRDERR, CRCWRERR (Bits 5,3,2):CRC错误。分别对应响应CRC错误、读数据CRC错误、写数据CRC错误。硬件校验失败时置位。
- TORERR, TORDDATERR (Bits 1,0):响应超时和读数据超时。当卡在规定时钟周期内没有响应或没有数据时置位。超时周期由
RES_TO和READ_TO寄存器配置。
数据缓冲区相关寄存器 (BUFFER_ACCESS, BUF_PART_FULL, 0xFFFE0338/033C):
- 控制器内部有一个8x16位的FIFO作为数据缓冲区。对于512字节的扇区,需要32次DMA传输(每次传输16字节,即8次16位读/写)来完成。
BUFFER_ACCESS是数据端口,读写该寄存器会推进FIFO指针。BUF_PART_FULL用于流模式写入,标识最后一次写入的数据是否填满了缓冲区。
5.2 完整的数据读取流程(以DMA为例)
下面我们勾勒一个利用DMA从卡读取单个扇区的完整寄存器级操作流程,这比看伪代码更贴近硬件:
前期配置:
- 配置系统时钟和GPIO,使能MMC/SD控制器模块(按序列写
STR_STP_CLK)。 - 配置
CLK_RATE寄存器,根据SYSCLK频率计算并设置分频器,得到合适的MMC/SD_CLK(通常初始化阶段用较低频率如400kHz,识别后切换到更高频率)。 - 通过CMD9获取卡的CSD,确认卡支持的模式和最高频率,然后调整
CLK_RATE。 - 通过ACMD6和
CMD_DAT_CONT的BUSW位,协商并切换到4位数据总线模式(如果支持且需要)。
- 配置系统时钟和GPIO,使能MMC/SD控制器模块(按序列写
发起读命令:
- 确保目标卡已被选中(通过CMD7)。
- 设置块长度寄存器
BLK_LEN为512(0x200)。 - 设置
CMD_DAT_CONT寄存器:FRES=001(R1响应),DATEN=1(有数据),WRRD=0(读操作),STRBLK=0(块模式)。 - 设置命令参数:将目标扇区地址(LBA)写入
ARGUMENTH和ARGUMENTL。 - 设置命令寄存器
CMD为0x11(CMD17的索引)。 - 触发命令发送:向
CMD寄存器写入操作通常由硬件自动触发,或者需要向某个触发位写1(具体需查勘误表或示例代码)。这一步是实际启动总线通信的时刻。
等待与响应处理:
- 轮询
STATUS寄存器的ECR位,等待命令响应结束。 - 如果
ECR置位,从RES_FIFO寄存器读取响应内容(通常是卡状态R1)。检查响应中的错误位。 - 如果无错误,继续轮询
STATUS寄存器的DTD位,或等待DMA中断/缓冲区就绪中断(通过INT_MASK使能)。
- 轮询
DMA数据搬运:
- 在配置DMA控制器时,设置源地址为
BUFFER_ACCESS寄存器地址,目标地址为系统内存缓冲区。 - 设置DMA传输长度为32次(因为512字节 / 16字节每次 = 32次)。
- 使能控制器的
BUFRDY(缓冲区就绪)中断,并将其连接到DMA请求。当FIFO中有数据可读时,控制器会发出DMA请求。 - DMA控制器自动将数据从FIFO搬移到内存。
- 在配置DMA控制器时,设置源地址为
结束与检查:
- DMA传输完成后,
DTD位应置位。 - 再次发送CMD13读取卡状态,确认没有发生读错误(如
CARD_ECC_FAILED)。 - 如果需要,可以取消选择当前卡(CMD7 + RCA=0)。
- DMA传输完成后,
5.3 中断模式编程要点
手册17.5.5节描述的中断模式(CMD40)是一种低功耗设计。配置流程如下:
- 确保所有卡处于
stby状态。 - 发送CMD40 (
GO_IRQ_STATE),将所有支持该模式的卡置于等待中断状态。 - 主机进入低功耗模式,但保持时钟活动。
- 当卡有数据 ready 或其他内部事件时,它会主动在CMD线上发送一个响应(R5格式),将主机唤醒。
- 主机收到中断响应后,通过标准流程(CMD7选择卡,然后读写)处理请求。
- 处理完毕,可再次发送CMD40进入中断等待。
关键陷阱:中断响应是在开漏模式下发送的,这意味着如果多个卡同时响应,总线会表现为“线与”,主机可能只收到一个有效的合并响应。因此,主机在收到中断后,可能需要轮询所有卡(通过CMD13)来确定究竟是哪个卡发出了请求。此外,主机也可以通过发送一个特殊的CMD40(RCA=0x0000)来主动终止中断模式,将所有卡拉回stby状态。
6. 常见问题排查与调试技巧
基于多年的嵌入式调试经验,与MC68SZ328这类老式控制器打交道时,以下几个问题是高频雷区:
卡无法初始化(一直返回0xFFFFFFF或超时)
- 检查硬件:这是第一步也是最关键的一步。用示波器测量MMC/SD_CLK、CMD、DAT0-DAT3四条线。确保时钟频率在初始化阶段足够低(100-400kHz),波形干净无过冲。测量卡座的电源电压是否稳定(3.3V±5%),上电时序是否符合要求(先供电,后给时钟)。
- 检查命令序列:确认发送的是正确的初始化命令序列(CMD0 -> CMD8 -> ACMD41循环)。对于SDv1卡或MMC卡,CMD8可能不支持,需要根据响应调整流程。务必注意ACMD41之前必须先发CMD55。
- 检查控制器配置:确认
STR_STP_CLK寄存器已按正确序列使能,CLK_RATE分频设置正确,CMD_DAT_CONT中的响应格式FRES设置无误。
可以识别卡但无法读写数据
- 检查卡状态:在发送读写命令前,先用CMD13查询卡状态。确保卡处于
tran状态(CURRENT_STATE字段为4),并且READY_FOR_DATA位为1。 - 检查块长度:虽然很多卡默认512字节,但显式发送CMD16设置块长度是一个好习惯。确保
BLK_LEN寄存器设置与命令一致。 - 检查总线宽度:如果你试图使用4-bit模式,请确认:1)卡支持4-bit模式(通过SCR寄存器或ACMD6响应判断);2)你已经通过ACMD6命令成功将卡切换到4-bit模式;3)控制器的
CMD_DAT_CONT.BUSW位也相应设置为10。 - 检查DMA/中断配置:如果使用DMA,检查DMA通道配置是否正确,源地址是否为
BUFFER_ACCESS寄存器地址,传输长度是否匹配(512字节)。检查INT_MASK寄存器是否屏蔽了必要的中断(BUFRDY,DTRAN等)。
- 检查卡状态:在发送读写命令前,先用CMD13查询卡状态。确保卡处于
写操作成功但数据校验错误
- 等待编程完成:写命令(CMD24/25)后,卡会进入
prg状态。此时必须等待卡编程完成。有两种方法:1) 轮询法:持续发送CMD13,直到卡状态中的CURRENT_STATE从prg变回tran;2) 检测DAT0线:在prg状态,DAT0线会被卡拉低为忙信号,变高则表示完成。绝对不能在忙信号期间发起新操作。 - 检查写保护:确认卡没有物理写保护,也没有通过CMD28设置软件写保护。如果
WP_VIOLATION错误位置位,这就是原因。
- 等待编程完成:写命令(CMD24/25)后,卡会进入
间歇性CRC错误或超时错误
- 时序问题:提高时钟质量,检查PCB走线,确保信号完整性。过长或阻抗不匹配的走线会引起信号反射。
- 电源噪声:存储卡在读写,尤其是写操作时,电流会有较大波动。确保电源去耦电容(通常在卡座附近放置一个10uF钽电容和几个100nF陶瓷电容)充足且布局合理。
- 软件延迟不足:在命令之间、状态检查之间加入合理的延时。虽然硬件控制器处理了大部分时序,但某些操作(如卡从
prg状态退出)需要毫秒级的时间,软件轮询间隔太短可能误判。
使用ACMD或CMD56等特殊命令失败
- 序列错误:对于ACMD,牢记“CMD55 + ACMDxx”是一个不可分割的原子操作。发送CMD55后,必须紧接着发送ACMDxx,中间不能插入任何其他命令(包括查询状态的CMD13)。
- 状态未更新:发送CMD55后,卡的
APP_CMD状态位会置位。但如果你紧接着发送了一个非ACMD的命令,该位会清除。确保你的状态机逻辑正确处理了这个位。 - CMD56参数错误:CMD56的最后一个参数位(bit 0)指示方向(0=读,1=写),数据块格式由厂商定义,必须严格遵循特定卡的文档。
调试这类底层硬件驱动,逻辑分析仪或支持SD协议解码的示波器是神器。它们可以直观地展示总线上的每一个命令、响应和数据包,让你清晰地看到是命令没发对,还是卡没响应,或者是数据位错了,能节省大量盲目猜测的时间。