SD/MMC主机接口驱动开发:中断、时钟与数据传输核心机制解析
2026/8/3 22:53:25 网站建设 项目流程

1. 从寄存器手册到驱动实战:SD/MMC主机接口核心机制深度解析

在嵌入式系统开发中,SD/MMC主机接口(SDHI)是实现外部存储扩展的关键硬件模块。无论是运行Linux的智能设备,还是跑着RTOS的工控主板,只要涉及到SD卡或eMMC存储,背后都离不开对SDHI寄存器的精准操控。很多开发者拿到芯片手册,看到动辄几十页的寄存器描述,常常感到无从下手——每个位域代表什么?中断如何配合?DMA怎么开启?时钟又该如何配置?这些问题如果只靠翻阅手册的片段信息,很难形成系统性的认知。

实际上,SDHI的寄存器编程并非简单的“填值”,而是一套环环相扣的状态机控制逻辑。中断、时钟、数据传输这三者构成了SDHI驱动的铁三角:中断负责异步事件通知,让CPU从轮询中解放;时钟决定了通信的“心跳”,直接影响兼容性与速率;数据传输则通过缓冲区、DMA和总线宽度等机制,决定了实际吞吐量的上限。本文将基于瑞萨RA8D2微控制器的SDHI模块,深入拆解这几个核心寄存器组的工作原理、配置要点和实战中的“坑”,目标是让你不仅能看懂手册,更能写出稳定、高效的SD卡驱动代码。无论你是正在调试SD卡启动的嵌入式新手,还是寻求性能优化的资深工程师,相信这些从实际项目中沉淀下来的细节与经验,都能带来直接的帮助。

2. 中断机制深度剖析:从事件通知到高效响应

中断是嵌入式系统实现实时响应的基石。对于SDHI这种涉及相对低速外设的操作,采用轮询方式会严重浪费CPU资源并增加系统功耗。而合理利用中断,可以让CPU在等待卡响应或数据传输完成时,转而处理其他任务,极大地提升系统整体效率。SDHI的中断体系主要围绕两个状态寄存器(SD_INFO1,SD_INFO2)和对应的两个中断屏蔽寄存器(SD_INFO1_MASK,SD_INFO2_MASK)展开。

2.1 中断源分类与SD_INFO1_MASK寄存器详解

SD_INFO1寄存器主要报告一些常规的、非错误类的事件状态,其对应的SD_INFO1_MASK寄存器则用于控制这些事件是否产生中断请求。理解每个位的含义,是正确配置中断的第一步。

响应结束中断(RSPENDM, Bit 0):当SDHI发送完一条命令并成功接收到卡的响应(如R1、R3、R7等)后,SD_INFO1.RSPEND标志位会被硬件置1。如果RSPENDM位为0(不屏蔽),则会向CPU产生一个中断。这是最常用的中断之一,用于通知主机“命令已执行完毕,可以读取响应数据了”。在单命令操作中,我们通常在发送命令后等待此中断;在多块传输中,它也标志着每个命令阶段的结束。

访问结束中断(ACENDM, Bit 2):当一次数据块(单块或多块)传输完成时,SD_INFO1.ACEND标志位置1。如果ACENDM位为0,则产生中断。这个中断至关重要,它告诉主机“缓冲区中的数据已经准备好被读取”(读操作),或者“主机提供的数据已被卡全部接收”(写操作)。在多块读写操作中,它是判断传输流程是否正常结束的关键信号。

卡检测中断(SDCDINM/SDCDRMM, Bit 4/3)与DAT3检测中断(SDD3INM/SDD3RMM, Bit 9/8):这两组中断分别对应SD卡插座上的检测引脚SDnCDSDnDAT3SDnCD是标准的卡存在检测引脚,而SDnDAT3在SDIO模式下也可用于卡检测。INM对应插入(Insertion)事件,RMM对应移除(Removal)事件。使能这些中断后,系统可以实时感知卡的插拔,从而自动进行初始化和卸载,实现热插拔功能。在实际硬件设计中,需要确认你的板卡电路是否将这两个引脚正确连接到了卡座的对应管脚,否则中断将无法触发。

配置心得:在系统初始化阶段,我通常会将SD_INFO1_MASK寄存器初始化为0xFFFF,即屏蔽所有中断。然后在驱动加载或卡检测完成后,根据当前操作模式有选择地打开中断。例如,在常规读写时,使能RSPENDMACENDM;在需要支持热插拔的应用中,再使能卡检测相关中断。这样做可以避免在初始化或不稳定状态下,被无关的中断频繁打扰。

2.2 错误与超时中断:SD_INFO2_MASK寄存器的关键作用

如果说SD_INFO1报告的是“正常流程”,那么SD_INFO2报告的就是“异常情况”。SD_INFO2_MASK用于管理这些错误事件的中断请求,合理的配置能帮助快速定位问题。

命令与数据错误中断

  • 命令错误(CMDEM, Bit 0):当卡返回的响应中的命令索引(Command Index)与主机发送的不符时触发。这通常意味着通信链路存在严重问题,或者卡处于非预期状态。
  • CRC错误(CRCEM, Bit 1):在响应或数据的CRC校验失败时触发。CRC错误是物理层信号质量问题的典型标志,可能由时钟不稳定、布线过长、干扰严重引起。
  • 结束位错误(ENDEM, Bit 2):未检测到数据包结束位。这也属于物理层或时序问题。

超时错误中断

  • 响应超时(RSPTOM, Bit 6):命令发出后,超过640个SD时钟周期仍未收到卡的响应。最常见的原因是卡未初始化、卡处于休眠状态、或时钟频率过高卡无法跟进。
  • 数据超时(DTOM, Bit 3):在数据读或写阶段,数据流中断超过预设时间。在多块读取中,如果卡未能及时提供下一个数据块,就会触发此超时。

缓冲区访问错误中断(ILWM/ILRM, Bit 4/5):当软件在错误的时机(例如DMA传输期间或缓冲区未就绪时)尝试读写SD_BUF0寄存器,硬件会检测到非法访问并置位相应标志。使能其中断有助于在开发阶段快速发现驱动程序的逻辑错误,例如缓冲区指针管理混乱或DMA配置不当。

缓冲区就绪中断(BWEM/BREM, Bit 9/8):这两个中断标志非常有用,但使用时需特别注意与DMA模式的互斥关系。BWE(Buffer Write Enable)在缓冲区可写入新数据时置位,BRE(Buffer Read Enable)在缓冲区有数据可读时置位。它们为不使用DMA的PIO(Programmed I/O)模式提供了高效的数据搬运时机通知。然而,手册中明确警告:当BWEMBREM位为0(即不屏蔽,允许中断)时,必须将SD_DMAEN.DMAEN位设为0(禁用DMA)。反之,当启用DMA时(DMAEN=1),必须将BWEMBREM都设为1(屏蔽中断)。这是因为DMA控制器和CPU中断对缓冲区的访问是互斥的,错误的配置会导致数据损坏或系统挂起。

避坑指南:在调试初期,建议将SD_INFO2_MASK中所有的错误中断(CMDEM, CRCEM, ENDEM, DTOM, RSPTOM, ILWM, ILRM)全部打开(设为0)。这样,任何通信异常都会立刻以中断形式上报,结合日志可以快速定位问题阶段。待驱动稳定后,可以根据应用需求选择性关闭某些中断,例如在极端追求性能且环境稳定的场合,可以屏蔽CRC错误中断,但必须在软件中保留轮询检查该标志的流程。

2.3 中断服务程序(ISR)的设计要点

编写SDHI的中断服务程序,核心任务是“识别中断源、清除标志位、通知上层任务”。

  1. 中断源识别:进入ISR后,首先应读取SD_INFO1SD_INFO2寄存器,判断具体是哪个(或哪些)标志位触发了中断。注意,多个事件可能同时发生,代码需要能处理这种情况。
  2. 标志位清除:对于SD_INFO1中的标志(如RSPEND,ACEND),通过向对应位写0来清除。对于SD_INFO2中的错误标志,通常也需要写0清除,但有些错误(如超时)可能需要在处理完错误恢复流程(如复位SDHI或重初始化卡)后再清除。
  3. 事件通知:清除标志后,ISR应通过设置信号量、事件标志组或向消息队列发送消息等方式,通知等待中的驱动任务。务必遵循“快进快出”原则,ISR内只做最必要的硬件操作和通知,复杂的数据处理、重试逻辑应放到任务线程中执行。
  4. 错误处理:对于错误中断,ISR除了通知,还应记录错误类型和上下文(如当时正在执行的命令),以便上层进行统计、重试或报错。严重的错误(如连续CRC错误)可能触发整个SDHI模块的软复位(通过SOFT_RST寄存器)。
// 一个简化的SDHI中断服务程序示例(伪代码) void SDHI_IRQHandler(void) { uint32_t info1 = SDHI->SD_INFO1; uint32_t info2 = SDHI->SD_INFO2; uint32_t events = 0; // 处理SD_INFO1事件 if (info1 & SD_INFO1_RSPEND_Msk) { SDHI->SD_INFO1 = ~SD_INFO1_RSPEND_Msk; // 写0清除标志 events |= SD_EVENT_CMD_COMPLETE; } if (info1 & SD_INFO1_ACEND_Msk) { SDHI->SD_INFO1 = ~SD_INFO1_ACEND_Msk; events |= SD_EVENT_DATA_COMPLETE; } // 处理SD_INFO2错误事件 if (info2 & SD_INFO2_CMDE_Msk) { SDHI->SD_INFO2 = ~SD_INFO2_CMDE_Msk; events |= SD_EVENT_CMD_ERROR; } if (info2 & SD_INFO2_CRCE_Msk) { SDHI->SD_INFO2 = ~SD_INFO2_CRCE_Msk; events |= SD_EVENT_CRC_ERROR; } if (info2 & SD_INFO2_RSPTO_Msk) { SDHI->SD_INFO2 = ~SD_INFO2_RSPTO_Msk; events |= SD_EVENT_RESP_TIMEOUT; } // ... 处理其他错误标志 // 如果有事件发生,通知驱动任务 if (events) { osMessageQueuePut(sd_event_queue, &events, 0, 0); } }

3. 时钟系统精讲:SD_CLK_CTRL寄存器与通信速率控制

SD/MMC通信是同步串行通信,时钟信号(SDnCLK)由主机产生,卡在时钟边沿采样数据。因此,时钟的配置直接决定了通信的速率、稳定性和兼容性。SD_CLK_CTRL寄存器是控制这一切的核心。

3.1 时钟分频与频率选择(CLKSEL[7:0])

CLKSEL[7:0]这8个位用于设置SDHI输出时钟(SDnCLK)的频率。其公式为:SDnCLK = PCLKB / (2 * (CLKSEL + 1)),其中PCLKB是SDHI模块的APB总线时钟。

手册中给出的是一系列离散值对应表,例如0x00对应PCLKB/20x01对应PCLKB/4,以此类推直至0x80对应PCLKB/512这里有一个非常重要的细节:CLKSEL的值并非直接的分频系数,而是代表一个索引。例如,如果你想得到10MHz的SD时钟,而PCLKB为100MHz,那么分频系数应为10。代入公式:10 = 2 * (CLKSEL + 1) => CLKSEL = 4。但查看表格,0x04对应的值是PCLKB/16(分频系数16),0x03对应PCLKB/8(分频系数8)。显然,4不是表格中的合法值。这意味着你无法得到精确的10MHz,只能在12.5MHzPCLKB/8,CLKSEL=0x03)和6.25MHzPCLKB/16,CLKSEL=0x04)之间选择。

实战选择策略

  1. 初始化阶段(Identification Mode):SD协议规定,卡在初始化阶段,时钟频率不能超过400kHz。假设PCLKB=50MHz,我们需要SDnCLK <= 400kHz,即分频系数 >= 125。查表,0x40PCLKB/256)得到约195kHz,0x20PCLKB/128)得到约390kHz。应选择0x20,它最接近且不超过400kHz上限。
  2. 数据传输阶段(Data Transfer Mode):初始化完成后,可以通过CMD6ACMD6命令切换到高速模式(High Speed),并相应提高时钟频率。此时频率可以选择卡支持的更高频率(如25MHz、50MHz)。同样根据PCLKB计算并选择最接近且不超过目标频率的CLKSEL值。
  3. 切换时机:必须在卡完成初始化(收到CMD8ACMD41响应,并进入Ready状态)后,才能提高时钟频率。切换时,先通过CMD7选中卡,然后发送切换命令,最后再修改SD_CLK_CTRL.CLKSEL寄存器。

3.2 时钟输出使能与自动控制(CLKEN与CLKCTRLEN)

  • CLKEN位:这是时钟输出的总开关。CLKEN=0时,SDnCLK引脚强制输出低电平;CLKEN=1时,允许时钟输出。手册强调,在向SD_CMD寄存器写入命令启动命令序列之前,必须确保CLKEN=1通常我们在初始化SDHI模块时,就将其置1。
  • CLKCTRLEN位:这是一个非常实用的节能功能。当CLKCTRLEN=1时,启用SD/MMC时钟输出自动控制。在此模式下,硬件会自动管理时钟的启停:
    • 启动:在软件向SD_CMD寄存器写入命令后,硬件自动开始输出时钟。
    • 停止:在命令序列结束后,再输出8个SD时钟周期,然后自动停止时钟输出。
    • 这意味着在命令与命令之间、数据块与数据块之间的空闲期,时钟是静止的,可以有效降低系统功耗和EMI。对于电池供电设备,强烈建议启用此功能。

一个重要限制:当SD_INFO2.SD_CLK_CTRLEN状态标志为0时(通常意味着时钟控制器正忙或处于某种不稳定状态),软件不能对SD_CLK_CTRL寄存器进行写操作。因此,在修改时钟频率前,最好先检查SD_INFO2.CBSY(命令忙)标志,确保当前没有正在进行的命令。

调试经验:时钟问题导致的故障现象非常多样。如果遇到卡无法识别、响应超时,首先用示波器测量SDnCLK引脚。确认:

  1. 频率是否正确(初始化阶段是否低于400kHz)?
  2. 波形是否干净(过冲、振铃会影响采样)?
  3. CLKCTRLEN启用时,时钟是否在命令间正确启停?
  4. 如果使用DMA,过高的SD时钟可能导致DMA来不及搬运数据,触发缓冲区溢出错误。此时需要适当降低时钟频率,或者优化DMA通道优先级和总线仲裁。

4. 数据传输的引擎:长度、宽度、缓冲区与DMA配置

数据传输是SDHI的核心功能,涉及多个寄存器的协同配置。目标是实现准确、高效、可靠的数据搬运。

4.1 传输数据长度设定:SD_SIZE寄存器

SD_SIZE.LEN[9:0]指定了单次传输的数据块大小(以字节为单位)。它的配置并非完全自由,需要根据传输模式来设定:

  • 单块传输(Single Block):可以设置为1到512字节之间的任意值。这对于读写SD卡中的CSD、CID寄存器,或进行非512字节扇区对齐的访问非常有用。
  • 多块传输(Multiple Block):情况稍复杂。
    • 如果使能了自动发送CMD12(停止命令)的功能,则传输数据大小只能固定为512字节。这是SD协议标准多块读写的要求。
    • 如果未使能自动CMD12,则块大小可以设置为32, 64, 128, 256, 或512字节。但请注意:对于多块读操作,非512字节的设置仅在使用CMD53进行SDIO多块传输时才有效。对于标准的SD存储卡多块读(CMD18),块大小通常也应是512字节。

关键约束绝对不要在SD_INFO2.CBSY标志为1(命令忙)时,改写SD_SIZE寄存器。这会导致不可预知的行为。正确的做法是在发送读写命令(CMD17/18/24/25)之前,先配置好SD_SIZE

4.2 总线宽度与超时控制:SD_OPTION寄存器

SD_OPTION寄存器集成了几个关键配置。

总线宽度(WIDTH与WIDTH8位): SD卡支持1-bit、4-bit模式,SDIO卡还支持8-bit模式。通过WIDTHWIDTH8位的组合进行选择。上电默认是1-bit模式。在初始化序列中,主机通过ACMD6命令通知卡切换到4-bit模式,然后才将SD_OPTION寄存器中的总线宽度配置改为4-bit。顺序反了会导致通信失败。对于8-bit模式,除了设置寄存器,还需要通过CMD52CMD53等SDIO命令进行配置。

一个重要的警告:手册明确指出,对于1字节的写传输,只能设置为4-bit或1-bit宽度,不能设置为8-bit宽度。这是因为8-bit模式下,一次最小访问单位是4字节(32位),无法处理单字节写入。

超时计数器(TOP[3:0]与CTOP[3:0])

  • TOP[3:0]:设置数据读写和响应等待的超时周期基数,以SDHI时钟周期为单位。超时时间 =2^(TOP+13)个SD时钟周期。例如,TOP=0x4,超时时间为2^(4+13) = 2^17 = 131072个时钟周期。在50MHz时钟下,约2.6ms。对于慢速卡或长距离布线,需要设置更长的超时。
  • CTOP[3:0]:专用于卡检测(Card Detection)的超时计数器,以PCLKB周期为单位。时间 =PCLKB * 2^(CTOP+10)。这个时间通常用于去抖,防止因接触瞬间不稳定而误触发插拔事件。

超时屏蔽(TOUTMASK):当此位置1时,禁用硬件超时检测功能。这意味着即使等待时间超过TOP设定的值,SD_ERR_STS2寄存器中的超时标志(如RSPTO,DTO)也不会被置位。除非有特殊需求(如调试阶段),否则不要屏蔽超时。如果因为屏蔽超时而导致命令序列卡死,唯一的恢复方法是触发SDHI软件复位(SOFT_RST.SDRST)。

4.3 数据缓冲区(SD_BUF0)与字节序交换

SD_BUF0是CPU或DMA与SDHI内部缓冲区交互的窗口。它是一个32位寄存器。SDHI内部有两个512字节的缓冲区(双缓冲),用于平滑数据流。在连续多块读取时,当一个缓冲区满,硬件可以暂停SD时钟,等待CPU/DMA取走数据;当缓冲区有空闲时,再恢复时钟继续接收。这避免了数据溢出。

字节序交换(EXT_SWAP寄存器): 这是一个非常实用的功能,用于解决主机CPU字节序(Endianness)与SD总线字节序不一致的问题。SD总线协议规定,数据位[31:0]对应字节[3,2,1,0](大端序或网络字节序)。而ARM Cortex-M系列CPU通常是小端序(字节[0,1,2,3]对应数据位[7:0, 15:8, 23:16, 31:24])。

  • 如果CPU以32位方式读取SD_BUF0,得到的数据字节顺序可能是反的。
  • 通过设置EXT_SWAP.BRSWP=1(读交换)和BWSWP=1(写交换),硬件会在数据进出SD_BUF0时自动完成字节序的交换。这样,CPU看到的就是符合自身字节序的正确数据,大大简化了驱动代码。

同样需要注意:不能在SD_INFO2.CBSY为1时修改EXT_SWAP寄存器。

4.4 DMA传输配置:SD_DMAEN寄存器

对于大数据量的读写(如图片、音频、视频文件),使用DMA可以极大解放CPU。SDHI的DMA配置非常简单,本质上是一个开关。

  • DMAEN位:置1使能DMA传输模式。使能后,对SD_BUF0寄存器的访问将由DMA控制器接管,而非CPU。
  • 关键互锁关系:如前所述,DMA模式与PIO模式下的缓冲区就绪中断是互斥的。启用DMA(DMAEN=1)时,必须屏蔽BWEMBREM中断(SD_INFO2_MASK.BWEM=1, BREM=1)。反之,如果想使用中断驱动的PIO模式,必须先关闭DMA(DMAEN=0)。
  • 配置顺序:正确的顺序是:先配置好DMA控制器本身(源/目标地址、传输长度、触发源等),然后设置SD_DMAEN.DMAEN=1,最后再启动SD命令(写SD_CMD)。DMA传输的完成,通常会结合SD_INFO1.ACEND中断和DMA控制器本身的中断来共同判断。

5. 错误状态诊断与SDIO模式进阶

即使配置无误,在实际通信中仍会遇到各种错误。SDHI提供了两个错误状态寄存器(SD_ERR_STS1,SD_ERR_STS2)来帮助诊断。

5.1 错误状态寄存器详解与排查流程

SD_ERR_STS1主要包含CRC相关错误和命令响应错误:

  • CMDE0/1:命令索引错误。检查发送的命令码(CMDIDX)是否正确,或卡是否处于预期状态(如是否已选中)。
  • RSPLENE0/1:响应长度错误。检查是否配置了正确的响应类型(如期望R2却收到R1)。
  • RSPCRCE0/1,RDCRCE:响应或读数据CRC错误。这是最常见的错误之一,强烈建议用示波器检查SD_CLK和SD_CMD/DAT信号质量,检查走线、阻抗匹配和电源噪声。
  • CRCTKE,CRCTK[2:0]:CRC状态令牌错误。发生在写操作后,卡会返回一个CRC状态令牌(正常值应为010b)。如果不是这个值,说明写操作未被卡接受。

SD_ERR_STS2主要包含超时错误:

  • RSPTO0/1:响应超时。卡在规定时间内未回复。检查:1) 卡是否上电、初始化完成? 2) 时钟频率在初始化阶段是否过高? 3)CMD0(GO_IDLE_STATE)是否需要更长的时间?
  • BSYTO0/1:忙超时。卡在执行某些操作(如擦除、写入)时,拉低DAT0线表示忙。超时说明卡处理时间过长。对于写操作,这是正常的,需要根据卡的类型等待足够时间。
  • RDTO:读数据超时。在多块读取中,下一个数据块未及时到来。可能是卡性能问题,或时钟不匹配。
  • CRCTO,CRCBSYTO:与写操作后CRC状态令牌相关的超时。

系统化的排查流程

  1. 确认物理连接:测量VDD、CLK、CMD、DATx的电压和波形。
  2. 检查初始化序列:确保从上电、CMD0CMD8ACMD41CMD2CMD3的每一步都成功,响应符合预期。
  3. 核对寄存器配置:时钟频率、总线宽度、数据长度、超时时间是否与卡的能力和当前操作模式匹配?
  4. 分析错误状态:发生错误时,立刻读取SD_ERR_STS1SD_ERR_STS2,锁定错误类型。
  5. 实施恢复操作:对于超时或CRC错误,简单的重试(重新发送命令)可能有效。对于持续错误,可能需要降低时钟频率,或执行更彻底的复位(CMD0SOFT_RST)。

5.2 SDIO模式特殊控制

SDIO_MODE,SDIO_INFO1,SDIO_INFO1_MASK这三个寄存器是专门为SDIO卡(如Wi-Fi、蓝牙模块)设计的。SDIO协议在SD存储协议基础上增加了中断、暂停/恢复等功能。

  • 中断接收:通过设置SDIO_MODE.INTEN=1,并使能SDIO_INFO1_MASK.IOIRQM中断,主机可以接收来自SDIO卡的中断请求(通过DAT1线)。这在Wi-Fi模块有数据到达时非常有用。
  • 读等待(Read Wait)RWREQ位用于在CMD53多块读序列中,请求卡进入读等待状态,暂停数据传输。主机可以在此时处理其他事务,然后再释放等待继续读取。
  • 中止(Abort)与公共命令(Public Cmd)IOABTC52PUB位用于控制CMD53多块传输的终止方式。IOABT=1会立即中止传输并发送CMD52C52PUB=1则在传输完当前块后,再发送CMD52结束序列。这用于满足SDIO规范中特定的流控需求。

SDIO驱动的复杂度远高于纯存储卡,需要仔细阅读SDIO卡的具体规范来配合这些寄存器进行控制。

6. 软件复位与实战配置流程示例

最后,SOFT_RST寄存器提供了对SDHI模块的软件复位功能。写SDRST=0触发复位,写SDRST=1释放复位。复位会初始化一大批寄存器到默认状态(如表47.4所列)。这是驱动中最后的“杀手锏”,当遇到无法恢复的通信错误、状态机卡死时,执行一次软件复位,然后重新初始化整个SDHI和卡,往往是有效的。

下面是一个简化的SDHI初始化及单块读操作流程,汇总了上述关键点:

// 假设寄存器基地址已定义为 SDHI_BASE void sdhi_init(void) { // 1. 可选:软件复位,确保模块处于已知状态 SDHI->SOFT_RST = 0; delay_us(10); SDHI->SOFT_RST = 1; delay_us(100); // 2. 配置时钟:初始化阶段低于400kHz // 假设PCLKB=50MHz,目标SDCLK=390kHz (PCLKB/128) while (SDHI->SD_INFO2 & SD_INFO2_CBSY_Msk); // 等待命令空闲 SDHI->SD_CLK_CTRL = (0x20 << 0) | (1 << 8); // CLKSEL=0x20, CLKEN=1 // CLKCTRLEN可根据需求设置,此处先不启用以便观察时钟 // 3. 配置总线宽度(默认为1-bit,初始化后再切换) // 配置超时(例如设置一个中等偏长的超时) SDHI->SD_OPTION = (0x4 << 4) | (0x1 << 8); // TOP=0x4 (~2.6ms @50MHz), TOUTMASK=0(使能超时) // 4. 配置中断:先屏蔽所有,后续按需打开 SDHI->SD_INFO1_MASK = 0xFFFF; SDHI->SD_INFO2_MASK = 0xFFFF; // 使能SDHI模块全局中断(需根据具体MCU的中断控制器配置) // 5. 配置DMA(如果使用): 此处以PIO为例,故禁用DMA SDHI->SD_DMAEN = 0; // 6. 配置字节序(根据CPU端序决定) SDHI->EXT_SWAP = (1 << 6) | (1 << 7); // BRSWP=1, BWSWP=1,使能字节交换(针对小端CPU) } int sd_read_single_block(uint32_t sector, uint8_t *buffer) { // 0. 确保卡已初始化并处于传输状态 // 1. 设置传输数据长度(512字节) while (SDHI->SD_INFO2 & SD_INFO2_CBSY_Msk); SDHI->SD_SIZE = 512; // 2. 配置中断:使能命令结束和数据访问结束中断,使能错误中断 SDHI->SD_INFO1_MASK &= ~(SD_INFO1_RSPEND_Msk | SD_INFO1_ACEND_Msk); SDHI->SD_INFO2_MASK &= ~(SD_INFO2_CMDE_Msk | SD_INFO2_CRCE_Msk | SD_INFO2_RSPTO_Msk | SD_INFO2_DTO_Msk); // 3. 发送CMD17 (READ_SINGLE_BLOCK) uint32_t argument = sector; // 对于SDSC卡,地址是字节地址;对于SDHC/XC,是扇区号。 SDHI->SD_ARG = argument; SDHI->SD_CMD = (17 << 8) | (1 << 11); // CMDIDX=17, WITH_DATA=1 // 4. 等待命令响应结束中断(RSPEND) if (osEventFlagsWait(sd_event_flags, SD_EVENT_CMD_COMPLETE, osFlagsWaitAny, 100) != SD_EVENT_CMD_COMPLETE) { // 超时,检查RSPTO标志 return -1; } // 读取响应(例如R1)到变量,检查卡状态 uint32_t response = SDHI->SD_RSP0; // 5. 等待数据访问结束中断(ACEND) if (osEventFlagsWait(sd_event_flags, SD_EVENT_DATA_COMPLETE, osFlagsWaitAny, 500) != SD_EVENT_DATA_COMPLETE) { // 超时,检查DTO标志 return -2; } // 6. 从SD_BUF0读取数据(PIO模式,循环读取) uint32_t *buf32 = (uint32_t*)buffer; for (int i = 0; i < 512 / 4; i++) { buf32[i] = SDHI->SD_BUF0; } // 7. 检查是否有错误中断发生 if (SDHI->SD_INFO2 & (SD_INFO2_CMDE_Msk | SD_INFO2_CRCE_Msk)) { // 处理错误... return -3; } // 8. 恢复中断屏蔽(可选) SDHI->SD_INFO1_MASK |= (SD_INFO1_RSPEND_Msk | SD_INFO1_ACEND_Msk); return 0; // 成功 }

驱动SD/MMC主机接口,是一个对时序、状态和错误处理要求极其严格的过程。理解每个寄存器位背后的硬件行为,建立清晰的初始化、命令发送、数据搬运和错误处理的状态机逻辑,是成功的关键。从保守的初始配置开始,逐步提高时钟速率,并充分利用中断和DMA来优化性能,最终就能构建出稳定高效的存储子系统。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询