嵌入式MMC/SD驱动开发:从底层协议到实战优化
1. 项目概述与核心价值
在嵌入式系统开发中,存储是绕不开的一环。无论是运行日志、用户配置还是固件升级包,都需要一个可靠、通用且成本可控的存储介质。MMC(MultiMediaCard)和SD(Secure Digital)卡,凭借其小巧的体积、标准化的接口和巨大的市场保有量,成为了嵌入式领域非易失性存储的首选方案之一。然而,将一张小小的存储卡集成到你的嵌入式设备中,并让它稳定、高效地工作,远不止是连接几根线那么简单。其底层是一套完整的命令-响应协议、状态机管理和数据搬运机制。
很多开发者初次接触MMC/SD驱动时,往往止步于使用现成的库函数,对底层如何发起一个写操作、为何有时需要等待、以及如何应对各种错误状态一知半解。当遇到性能瓶颈或诡异的读写失败时,这种“黑盒”式的理解就显得捉襟见肘。本文将以飞思卡尔(现恩智浦)MC9328MX1处理器手册中的MMC/SD模块功能示例为蓝本,深入解析块读写、流访问与保护管理三大核心功能的实现细节与设计逻辑。我将结合自己多年在工控和消费电子领域的踩坑经验,不仅告诉你代码怎么写,更会剖析每个步骤背后的“为什么”,并分享那些数据手册上不会写的调试技巧和避坑指南。无论你是正在从头编写底层驱动的工程师,还是希望优化现有存储性能的开发者,这篇文章都能为你提供从原理到实践的完整参考。
2. 底层通信机制与状态机解析
在深入具体功能之前,我们必须先理解MMC/SD卡与主机控制器(即MCU中的MMC/SD模块)对话的基本规则。这就像两个人交流,需要共同的语言和明确的流程。
2.1 命令-响应协议:如何与存储卡“对话”
MMC/SD通信基于一种主从式的命令-响应模型。所有交互都由主机(我们的MCU)发起。主机通过CMD线发送一个6字节的命令帧,卡在收到命令后,通过同一根CMD线回复一个响应帧。数据传输则在独立的DAT线上进行。
一个命令帧包含:
- 起始位:总是0。
- 传输位:1表示主机到卡(命令),0表示卡到主机(响应)。
- 命令索引:如CMD17(0x11)代表读取单个块。
- 参数:32位,对于读写命令通常是目标地址。
- CRC7校验码:用于校验命令帧的完整性。
- 结束位:总是1。
响应帧格式有多种,最常见的是R1(48位),它包含了命令索引和至关重要的32位卡状态寄存器(Card Status Register)。这个状态寄存器是我们判断卡是否准备好、操作是否成功、出现了何种错误的唯一依据。代码示例中反复出现的send_cmd_wait_resp函数,其核心工作就是封装这个发送命令、等待并解析响应的过程。在实现这个函数时,超时机制是必须的,我遇到过因卡响应慢或接触不良导致驱动死等的情况,一个稳健的驱动应该在等待若干毫秒无响应后返回超时错误。
2.2 卡的状态机:理解“卡在做什么”
MMC/SD卡内部维护着一个状态机,这是驱动逻辑正确的基础。卡在任何时刻都处于一个特定状态,例如Idle(空闲)、Ready(就绪)、Transfer(传输)、Data(发送/接收数据)、Programming(编程,即内部闪存写入)。状态之间的转换由特定命令触发。
手册代码中,在每次数据传输前都先发送SEND_STATUS (CMD13)并检查READY_FOR_DATA状态位,这绝非多余操作。因为卡在完成上一个写操作(处于Programming状态)时,是无法接受新数据的。盲目发送写命令会导致CMD_CRC_ERROR或ILLEGAL_COMMAND。一个关键的经验是:对于写操作,不仅要检查READY_FOR_DATA,在连续写入时,更佳实践是轮询状态直到卡的CURRENT_STATE从Programming (0x07)回到Transfer (0x04)。这能确保前一个块已完全写入闪存,避免缓存未满导致的丢失。
2.3 总线宽度与时钟频率:性能的双引擎
初始化完成后,默认是1-bit数据总线模式。通过APP_CMD (CMD55)+SET_BUS_WIDTH (ACMD6)序列可以将总线切换到4-bit模式,理论传输带宽提升至4倍。代码中对此有判断逻辑。但要注意,切换必须在卡处于Transfer状态时进行。
另一个影响性能的关键因素是时钟频率(SDCLK)。手册中流读写部分给出的最大速度计算公式,其参数(TRAN_SPEED,TAAC,NSAC,R2W_FACTOR)都来自卡的CSD (Card Specific Data)寄存器。一个常见的误区是:初始化后立刻将时钟调到最高。正确的做法是,在识别卡阶段(CMD2, CMD3)使用较低时钟(如400kHz),完成识别并读取CSD后,再根据卡支持的能力逐步提升时钟至理想值。对于老款卡或某些工业级卡,过高的时钟会导致数据错位(CRC错误)。我的习惯是,在驱动中实现一个可降级的时钟配置表,当高频下出现连续错误时,自动回退一档时钟频率。
3. 块模式读写详解与实战
块模式(Block Mode)是MMC/SD最常用、最标准的数据传输方式,它与文件系统(如FAT32)的簇(Cluster)或扇区(Sector)概念天然契合。一次传输的数据块大小通常是512字节,这也是通过SET_BLOCKLEN (CMD16)命令设置的。
3.1 块写入流程深度拆解
让我们逐行分析手册中的block_write轮询示例,并补充关键细节:
检查卡状态:
send_cmd_wait_resp(SEND_STATUS, ...)并循环检查READY_FOR_DATA。这是数据传输前的必要握手。设置块数量:
write_reg(NOB, <nob>)。这个寄存器是控制器内部的,告诉DMA或轮询逻辑本次要传输多少个块。注意:对于单块写入(nob==1),有些控制器此步骤可省略,但显式设置是良好习惯。设置块长度:
send_cmd_wait_resp(SET_BLOCKLEN, 0x00, 0x0200, 0x01, 0x40)。参数0x0200即十进制的512。这里有个坑:虽然SDHC(容量>=2GB)卡强制块大小为512字节且忽略此命令,但为了兼容MMC和标准SD卡,此命令必须发送。配置总线宽度:根据
buswidth参数,通过APP_CMD+SET_BUS_WIDTH序列切换。发送写命令:
- 单块写入:
WRITE_SINGLE_BLOCK (CMD25)。命令发出后,卡进入接收状态,等待主机通过DAT线发送数据块+CRC16。 - 多块写入:
WRITE_MULTIPLE_BLOCK (CMD25)。此后卡会连续接收多个数据块,直到主机发送STOP_TRANSMISSION (CMD12)。 - 命令参数中的奥秘:示例代码中命令的第四个参数(如
0x19,0x219)是命令索引和CRC7的拼接值。0x19是CMD25的索引(25=0x19)左移8位?这里需要结合控制器数据手册看,它可能包含了控制器特定的格式。在实现时,应参考你的主控芯片手册来构造这个参数,切勿直接照抄。
- 单块写入:
数据搬运(轮询方式):
while(!FIFO empty in STATUS is true); // 等待FIFO空,准备接收数据 if(buswidth==4-bit mode) { for(i=0;i<(nob*8);i++) { // 注意:4-bit模式下,一次访问32位(4字节),所以循环次数是 `nob*512/4 = nob*128`?这里手册示例的 `nob*8` 疑似有误,应是 `nob*128`。 while(!FIFO full in STATUS); // 等待FIFO有空间 for(j=0;j<32;j++) { // 32次循环,每次写1字节到BUFFER_ACCESS?这里逻辑是填充32字节到FIFO。 BUFFER_ACCESS = SDRAM_ADDR[i*32+j]; } } }这里存在一个关键疑点和优化点:示例中的循环结构
(nob*8)和(nob*32)令人困惑。合理的解释是,BUFFER_ACCESS寄存器可能是32位(4字节)宽的。在4-bit总线模式下,一次写操作可以填充4字节到FIFO,因此内层循环j<32可能代表一次填充32字节(即8个32位字)。但无论如何,轮询FIFO状态进行数据搬运是效率最低的方式,它会完全占用CPU。在实际项目中,只要硬件支持,应优先使用DMA。结束传输:数据发送完毕后,等待
Data Transfer Done和card bus is stop状态。对于多块写入,必须发送STOP_TRANS (CMD12)来终止传输序列。
3.2 块读取流程与DMA应用
块读取流程与写入对称,命令换为READ_SINGLE_BLOCK (CMD17)或READ_MULTIPLE_BLOCK (CMD18)。手册提供了DMA方式的示例,这是性能关键。
DMA配置要点:
- 源地址:设置为控制器的数据缓冲区寄存器(如
BUFFER_ACCESS)。 - 目标地址:设置为系统内存(SDRAM)中的目标缓冲区地址。
- 传输总量:
nob * 512字节。 - 突发深度:需要匹配总线宽度。4-bit模式(实际是4条数据线,并行传输4位?这里指SD的4-bit宽总线模式,一次传输4位,但控制器FIFO宽度可能是32位)下,一次DMA请求可能传输32位(4字节),所以突发深度设为32(指32个总线位宽的数据?)。这需要根据控制器DMA设计来定。配置错误会导致DMA传输数据错位。
- 启动时机:必须在发送读命令之后,再使能DMA。因为读命令会触发卡开始发送数据,DMA需要捕捉到数据就绪信号(通常是FIFO非空中断或信号)才开始搬运。
轮询读取的陷阱:示例中轮询读取是检查!FIFO full,这意味着FIFO中已有数据可供读取。对于读操作,如果CPU处理不及时,FIFO满了而卡还在发送数据,就会发生上溢(Overrun),导致数据丢失和错误。因此,读操作的轮询间隔必须非常短,或者使用中断/DMA。
3.3 块模式下的对齐与错误处理
手册特别提到了块未对齐错误(ADDRESS_ERROR)。当允许部分块写入(WRITE_BL_PARTIAL)且起始地址不是块大小的整数倍时,如果卡不支持非对齐访问,就会触发此错误。安全起见,在驱动层应确保所有读写操作的地址和长度都是块大小(512字节)的整数倍。文件系统层负责处理非对齐的读写请求,通常会通过缓存合并成对齐的块操作再下发给驱动。
关键错误状态位解析:
ADDRESS_ERROR:地址未对齐错误。BLOCK_LEN_ERROR:传输的块长度非法。WP_VIOLATION:试图写入写保护区域。CARD_IS_LOCKED:卡被密码锁定。COM_CRC_ERROR/ILLEGAL_COMMAND:通信链路或命令序列问题。
一个实用的调试技巧:在每次命令发送后,不要只检查成功与否,应该将返回的32位状态寄存器值以十六进制打印出来。通过查表(如表20-22),可以精确定位问题。例如,状态码0x00000900可能意味着READY_FOR_DATA位为0(卡忙),同时APP_CMD位被设置(上一条命令是CMD55)。
4. 流模式访问原理与应用场景
流模式(Stream Mode)是MMC卡(注意:SD卡可能不支持)特有的一种传输方式,它打破了块的限制,允许以字节流的形式连续传输数据,且不附加CRC校验。
4.1 流模式与块模式的本质区别
- 数据单元:块模式以固定长度块(如512字节)为单位,每个块后跟CRC16。流模式以字节为单位,无CRC。
- 命令:流写使用
WRITE_DAT_UNTIL_STOP (CMD20),流读使用READ_DAT_UNTIL_STOP (CMD11)。数据传输由STOP_TRANSMISSION (CMD12)终止。 - 效率:由于省去了每个块的CRC开销,流模式在传输连续、大量的数据时(如音频录制、原始数据采集)理论上效率更高。
- 可靠性:没有CRC,意味着数据传输的完整性完全依赖于物理链路的稳定性。在电气噪声较大的环境中,风险更高。
4.2 流写入的实战与风险控制
流写入的代码框架与块写入类似,但有显著不同:
write_reg(NOB, 0xffff):流模式不预设块数量,通常将此寄存器设为最大值或忽略。- 命令参数:流写命令
CMD20的参数构造与块写不同(示例中为0x79)。 - 无精确长度控制:主机持续发送数据,直到主动发送
CMD12停止。如果发送数据量超过了卡的容量,超出的数据会被卡丢弃。 - 最大速度限制:手册给出了流写入的最大时钟频率计算公式。务必遵守!如果主机时钟过快,卡内部编程速度跟不上,会导致上溢(Overrun)错误(
OVERRUN状态位置位),传输中止。在驱动实现时,应根据从CSD中读取的TAAC、NSAC、R2W_FACTOR等参数动态计算并设置一个安全的时钟。
4.3 流读取的注意事项
流读取面临的主要风险是下溢(Underrun)。如果主机(MCU)读取数据的速度跟不上卡发送数据的速度,FIFO或缓冲区就会溢出,导致数据丢失并触发UNDERRUN错误。
规避下溢的策略:
- 使用DMA:这是最有效的方法。配置DMA在FIFO有数据时自动搬运,解放CPU。
- 提高读取优先级:如果使用中断,流读取中断应设为最高优先级之一,确保数据能被及时取走。
- 流量控制:在软件层面,确保接收缓冲区足够大,并且处理数据的任务不会长时间阻塞。
- 降低时钟频率:与流写入一样,使用公式计算安全时钟,不要盲目追求最高速。
流模式的应用场景:它非常适合传输自描述或容错率高的实时流数据。例如,原始PCM音频数据、连续采集的传感器原始值(后续会进行滤波和校验)。对于文件数据等需要绝对完整性的场景,块模式是更安全的选择。
5. 擦除操作:扇区擦除与组擦除
闪存(Flash)的特性决定了在写入新数据前,必须先擦除对应的存储单元(将其置为1)。MMC/SD卡内部使用NAND Flash,因此也遵循这一规则。擦除操作以扇区(Sector)或擦除组(Erase Group)为单位,后者是多个扇区的集合,大小由卡定义在CSD中。
5.1 擦除命令序列解析
擦除不是单一命令,而是一个标准的四步序列,其精妙之处在于“标记(Tag)”机制:
- 标记起始地址:
TAG_SECTOR_START (CMD32)或TAG_ERASE_GROUP_START (CMD35)。告诉卡擦除范围的开始。 - 标记结束地址:
TAG_SECTOR_END (CMD33)或TAG_ERASE_GROUP_END (CMD36)。告诉卡擦除范围的结束。 - (可选)取消标记:
UNTAG_SECTOR (CMD34)或UNTAG_ERASE_GROUP (CMD37)。可以从已标记的范围内排除某些扇区或组。最多支持16次取消操作。 - 执行擦除:
ERASE (CMD38)。卡开始擦除所有被标记且未被取消的区间。
这个设计的好处是:允许主机灵活地定义一块可能不连续、不规则的区域进行一次性擦除,提升了效率。如果命令序列被打乱(例如,直接发送ERASE),卡会设置ERASE_SEQ_ERROR并重置整个序列。
5.2 擦除过程中的状态管理与性能优化
擦除是一个物理过程,耗时很长(毫秒到秒级)。发送ERASE命令后,卡会进入Programming状态,并将DAT线��低(busy)。主机可以通过轮询SEND_STATUS命令来等待擦除完成。
一个重要的优化技巧:在卡忙于擦除时,主机可以使用SELECT/DESELECT_CARD (CMD7)命令取消选中当前卡。这样可以将总线释放出来,去与其他卡(如果支持多卡)通信,或者让MCU处理其他任务。当需要知道擦除是否完成时,再重新选中该卡并查询状态。这在实际的多任务或带文件系统的嵌入式环境中非常有用。
写保护处理:如果标记的擦除范围内包含写保护的扇区,卡会跳过这些扇区(只擦除非保护部分),并设置WP_ERASE_SKIP状态位。这确保了保护数据的安全性。
6. 多层次保护管理机制剖析
数据安全是存储系统的重要方面。MMC/SD提供了从物理到逻辑的多层次保护。
6.1 卡内部写保护
这是一种由卡内部CSD寄存器控制的软件保护机制。
- 永久写保护:由制造商设置,不可更改。
- 临时写保护组:如果卡支持(
WP_GRP_ENABLE位为1),主机可以通过SET_WRITE_PROT (CMD28)和CLR_WRITE_PROT (CMD29)来设置或清除特定组的写保护。保护单位是WP_GRP_SIZE个扇区。 - 查询保护状态:
SEND_WRITE_PROT (CMD30)可以读取一个数据块,其中包含32个保护位,每个位代表一个保护组的状态。
应用场景:在设备中划分一个“系统区”存放关键固件或配置,通过软件将其写保护,防止被应用程序意外覆盖。
6.2 机械写保护开关
这是SD卡特有的物理开关。当开关滑到“锁定”位置时,卡套上的一个触点会断开,卡套的检测引脚会通知主机控制器“卡被写保护”。关键在于:这个开关状态卡本身并不知道!它完全依赖于主机控制器去检测并遵守。因此,一个负责任的驱动在检测到写保护开关打开时,应该拒绝一切写和擦除命令,并在上层返回“介质写保护”错误。
6.3 密码保护机制实战指南
密码保护(Lock/Unlock)是SD卡(SD Security)的一项强大功能。它通过LOCK_UNLOCK (CMD42)命令配合一个数据块来实现。这个数据块的结构(如表20-21)包含了操作模式、密码长度和密码本身。
6.3.1 核心操作流程与避坑点
设置密码:
- 必须选中卡(
Transfer状态)。 - 用
SET_BLOCKLEN设置数据块长度。长度 = 1(模式字节) + 1(PWD_LEN字节) + 密码长度(字节)。 - 发送
CMD42,数据块中SET_PWD=1,并填写PWD_LEN和PWD。 - 关键点:
PWD_LEN一旦非零,卡上电后即自动锁定。如果想设置密码后立即锁定,需同时设置LOCK_UNLOCK=1。
- 必须选中卡(
锁定/解锁卡:
- 锁定:发送
CMD42,LOCK_UNLOCK=1,并提供正确的密码和长度。 - 解锁:发送
CMD42,LOCK_UNLOCK=0,并提供正确的密码和长度。 - 重要特性:解锁仅对当前上电会话有效。断电再上电后,卡会自动重新锁定。要永久解除锁定,必须使用“清除密码”操作。
- 锁定:发送
清除密码:
- 发送
CMD42,CLR_PWD=1,并提供正确的当前密码和长度。 - 成功后,
PWD和PWD_LEN被清零,卡永久解除密码保护。
- 发送
强制擦除(忘记密码):
- 这是最后的救命稻草。发送
CMD42,数据块中仅ERASE=1,其他位为0,块长度为1。 - 成功后,卡内所有用户数据、密码均被擦除,卡恢复为未锁定状态。
- 警告:此操作不可逆!且只能对已锁定的卡进行。
- 这是最后的救命稻草。发送
6.3.2 密码管理实战经验
- 密码存储:密码不应硬编码在驱动中。通常由上层应用(如系统设置)提供并管理。驱动层提供锁/解锁接口。
- 错误处理:任何密码操作失败(密码错误、长度不符、状态不对),卡都会设置
LOCK_UNLOCK_FAILED错误位。驱动应检查此位并返回明确的错误码给上层。 - 性能影响:每次发送
CMD42都需要传输一个数据块(包含密码),相比普通命令更耗时。频繁锁/解锁会影响性能。 - 兼容性:此功能是SD规范的一部分,但并非所有SD卡都支持。在尝试密码操作前,应通过读取SCR(SD Configuration Register)或尝试发送
CMD42来检测卡是否支持该功能。
7. 状态寄存器:系统调试的“眼睛”
卡状态寄存器(Card Status Register)和SD状态寄存器(SD Status Register)是驱动调试中最宝贵的工具。它们就像汽车的仪表盘,实时反映了卡的健康状况和操作结果。
7.1 卡状态寄存器关键位实战解读
除了前面提到的常见错误位,以下几个状态位在调试中尤为有用:
CURRENT_STATE(位12:9):直接告诉你卡处于状态机的哪个位置。在调试初始化、命令序列错误时,首先看这里。例如,如果你在Programming状态尝试发读写命令,肯定会失败。READY_FOR_DATA(位8):这是数据传输的“绿灯”。在启动任何读写、流操作前,必须确认此位为1。APP_CMD(位5):这是一个标志位。如果上一条命令是CMD55,那么此位置1,告诉卡下一条命令是应用特定命令(ACMD)。如果你发送ACMD6前忘了发CMD55,卡会将其解释为普通CMD6而失败,此时检查此位可帮助定位问题。
7.2 状态寄存器的读取与清除机制
状态寄存器中的位有不同的清除条件(Clear Condition):
- Clear by read (C):读取状态(通过
CMD13)后即清除。例如各种错误位(ADDRESS_ERROR,COM_CRC_ERROR)。这意味着你必须及时读取状态来获取错误信息,否则下次读取时错误信息可能已消失。 - According to state (A):随着卡状态改变而自动清除。如
CARD_IS_LOCKED。 - By next command (B):接收到下一个有效命令后清除。如
CURRENT_STATE。
最佳实践:在驱动中实现一个get_card_status()函数,它发送CMD13并返回完整的32位状态值。在关键操作(初始化、读写、擦除)前后都调用此函数,并将状态值记录到日志或通过调试接口输出,这是定位复杂问题的利器。
8. 常见问题排查与性能优化实录
基于以上原理,下面整理一份我在实际项目中遇到的典型问题及解决方案。
8.1 初始化失败
- 现象:卡无法进入
Ready状态,响应CMD8或ACMD41超时或无响应。 - 排查:
- 电气检查:首先用示波器检查CMD、DAT、CLK线的波形。确保电压电平正确(3.3V),信号无过冲、振铃。CLK频率在初始化阶段是否过高(应低于400kHz)。
- 上电时序:确保在给卡供电稳定至少1ms后,再开始发送时钟和命令。有些卡需要更长的上电复位时间。
- 命令CRC:早期MMC卡可能不检查CMD0的CRC,但SD卡检查。确保你的驱动计算并填充了正确的CRC7。对于
CMD8,其CRC是固定的。 - 卡类型检测:流程是否正确?先发
CMD8判断是否SD2.0+,再发ACMD41带HCS位判断是否SDHC/SDXC。
8.2 读写数据不稳定,偶发CRC错误
- 现象:读写操作大部分时间正常,但偶尔会失败,状态寄存器显示
COM_CRC_ERROR或CC_ERROR。 - 排查与解决:
- 时钟抖动:这是最常见原因。提高SDCLK的驱动能力,或在靠近卡座的位置串联一个22-33欧姆的电阻进行阻抗匹配。
- 电源噪声:SD卡在读写,尤其是写入时,电流会有较大波动。确保电源走线足够宽,并在卡座的VCC和GND引脚附近放置一个10uF钽电容和一个0.1uF陶瓷电容进行去耦。
- 布线问题:CMD、DAT0-3、CLK线应等长、紧耦合,并远离高频噪声源(如开关电源、电机驱动)。
- 软件重试:在驱动层为读写操作增加简单的重试机制(例如,最多3次)。遇到CRC错误时,重试当前块的操作,往往能成功。
8.3 多块写入速度慢
- 现象:使用多块写入命令,但实测速度远低于理论值。
- 优化:
- 使用DMA:这是提升速度最有效的手段。将CPU从繁重的数据搬运中解放出来。
- 增大FIFO阈值:如果控制器支持,将DMA请求的FIFO阈值调高,减少DMA启动次数,提升总线效率。
- 检查时钟频率:确保在识别卡后,已将时钟切换到卡支持的最高频率(通过读取CSD的
TRAN_SPEED字段)。 - 避免频繁查询状态:在多块写入过程中,不必在每个块后都查询状态。可以在全部数据通过DMA发送完毕后,再等待
Programming状态结束。但需要在发送CMD12停止命令前确保卡已准备好。
8.4 流模式传输数据错乱
- 现象:使用流模式录制音频或数据,回放时发现杂音或数据错误。
- 排查:
- 下溢/上溢:首先检查状态寄存器的
UNDERRUN或OVERRUN位。这明确指向主机与卡速度不匹配。 - 降低时钟:使用手册公式重新计算并设置一个更保守的流模式时钟频率。
- 提高任务优先级:如果使用RTOS,确保读取流数据的中断服务程序或任务的优先级足够高,不会被长时间阻塞。
- 增加缓冲区:在应用层增加一个大的环形缓冲区,流数据由DMA或高优先级任务快速存入缓冲区,再由较低优先级的任务慢慢处理,以平滑数据流。
- 下溢/上溢:首先检查状态寄存器的
8.5 密码保护功能异常
- 现象:
LOCK_UNLOCK命令总是返回LOCK_UNLOCK_FAILED。 - 排查:
- 块长度设置:这是最容易出错的地方。
SET_BLOCKLEN设置的长度必须严格等于:1(模式字节) + 1(PWD_LEN字节) + 密码实际长度(字节)。如果是要替换密码,长度还要加上旧密码的长度。 - 密码编码:密码数据是作为二进制数据块发送的,不是ASCII字符串。确保你传入的密码字节数组是正确的。
- 卡状态:必须在
Transfer状态,且卡已被选中(CMD7)。 - 卡是否支持:并非所有SD卡都支持密码保护。尝试前最好先确认。
- 块长度设置:这是最容易出错的地方。
通过深入理解MMC/SD模块的块读写、流访问与保护管理机制,并掌握这些实战中的排查技巧,你就能构建出稳定、高效且安全的嵌入式存储驱动。这不仅仅是让一块卡工作起来,更是为整个嵌入式系统的数据可靠性奠定了坚实的基础。记住,好的驱动是沉默的基石,它默默无闻,却至关重要。
