RA8D2 SDHI卡检测、中断与DMA传输机制详解

RA8D2 SDHI卡检测、中断与DMA传输机制详解
1. SD/MMC主机接口卡检测机制深度解析在嵌入式系统开发中SD/MMC主机接口SDHI是连接微控制器与外部存储卡的核心桥梁。这个接口的稳定性和可靠性很大程度上取决于其能否准确、及时地感知存储卡的物理状态变化也就是我们常说的“卡检测”。这听起来简单但在高速、低功耗的嵌入式场景下实现一个鲁棒的卡检测机制需要考虑硬件连接、信号防抖、中断响应等多个层面的细节。今天我就结合RA8D2微控制器的SDHI模块把卡检测这摊子事彻底讲透特别是两种检测方式的原理、差异和实际配置中的那些“坑”。1.1 两种物理检测机制SDnCD与SDnDAT3SDHI模块通常提供两种物理引脚用于卡检测它们的设计初衷和适用场景有所不同理解这一点是正确配置的基础。SDnCDCard Detect专用检测引脚这是最直接、最经典的检测方式。在硬件设计上SD卡座或TF卡座上通常会有一个机械开关这个开关的一端连接SDHI模块的SDnCD引脚另一端接地。当卡未插入时开关断开主机端需要通过一个上拉电阻将SDnCD引脚拉至高电平比如3.3V。这个上拉电阻的阻值不是随便选的它需要根据主机的驱动能力和信号完整性要求来确定通常在10kΩ到100kΩ之间。阻值太小功耗会增大阻值太大抗干扰能力会变弱容易误触发。当卡完全插入到位时机械开关被压下闭合SDnCD引脚被直接短接到地变为低电平。主机通过持续采样这个引脚的电平变化来判断卡的插入和移除。SDnDAT3引脚复用检测仅SD卡这是一种为了节省引脚而设计的方案主要针对SD卡不适用于MMC卡。SD卡协议中DAT3线在初始化阶段和某些特定模式下可以被主机控制并用于检测。具体实现是在主机端SDnDAT3引脚通过一个下拉电阻连接到地。当卡未插入时DAT3线是悬空的被下拉电阻拉低。当SD卡插入并上电后卡内部的DAT3线会有一个上拉电阻通常为50kΩ左右连接到卡供电从而将主机端的SDnDAT3引脚拉高。主机检测到这个从低到高的跳变即可判断SD卡已插入。这种方式省去了卡座上的专用检测开关和一根连线但对硬件时序和软件初始化的要求更高。注意使用SDnDAT3检测时务必确认你的硬件连接和卡类型。MMC卡不支持此功能如果硬件上只连接了DAT3线而卡座没有CD开关那么对MMC卡将无法检测。同时下拉电阻的阻值也需要仔细计算要确保能可靠地将悬空引脚拉低又不会在卡插入后与卡内上拉形成过大的分压导致高电平识别不明确通常建议在50kΩ左右。1.2 状态检测与防抖逻辑Mcycle周期的奥秘无论是哪种检测方式从物理引脚电平变化到系统确认为一个有效的“事件”中间必须经过防抖处理。机械开关的闭合和断开或者卡与卡座接触的瞬间都会产生一段时间的电平抖动或毛刺。如果主机直接采样这个跳变会导致系统频繁误报卡的插拔事件。RA8D2的SDHI模块通过一个可配置的Mcycle周期来解决这个问题。这个周期在SD_OPTION寄存器中设置其本质是一个去抖时间窗口。模块内部会持续采样SDnCD或SDnDAT3引脚的电平只有当该电平稳定地保持在新状态低电平或高电平超过Mcycle个SDHI时钟周期后才认为这是一个有效的状态变化并设置相应的状态标志位。以SDnCD检测插入为例初始无卡SDnCD为高。卡插入引脚被拉低。SDHI模块开始计时只有当低电平持续了Mcycle个时钟周期它才会将SD_INFO1寄存器中的SDCDIN标志位置1表示“检测到卡插入”。这个Mcycle的值需要根据你的SDHI时钟频率和期望的防抖时间来计算。例如如果SDHI时钟为50MHz你希望有10ms的防抖时间那么Mcycle应设置为10ms * 50MHz 500,000个周期。设置太短防抖效果差设置太长系统响应卡插入的延迟会变长影响用户体验。状态标志位的清除这里有一个关键细节SDCDIN、SDCDRM卡移除、SDD3IN、SDD3RM这些状态标志位不会自动清除。当它们被置1后会一直保持直到软件向该位写入0。这意味着在中断服务程序中你必须手动清除这些标志位否则该中断会持续触发或者你无法检测到下一次的状态变化。这是一个常见的疏忽点很多新手在调试时发现卡检测中断只触发一次就失效了往往就是因为忘了清标志位。1.3 硬件连接与寄存器配置实战理解了原理我们来看如何动手配置。这里以SDnCD检测为例给出一个典型的配置流程和代码片段思路。硬件连接检查确认你的卡座CD引脚是否已正确连接到MCU的SDHI_CD引脚。确认主机端的上拉电阻已正确焊接阻值合理例如47kΩ。用万用表或示波器测量无卡时CD引脚应为高电平VCC插入卡后应为低电平0V。软件初始化与配置 配置的核心是SD_OPTION寄存器和中断使能。// 假设 SDHI 基地址为 SDHI_BASE #define SDHI_BASE 0x4006C000 #define REG_SD_OPTION (*(volatile uint32_t *)(SDHI_BASE 0x28)) #define REG_SD_INFO1_MASK (*(volatile uint32_t *)(SDHI_BASE 0x84)) void sdhci_card_detect_init(void) { // 1. 配置Mcycle防抖时间。假设SDHI时钟PCLKB50MHz需要20ms防抖。 // Mcycle 20ms * 50MHz 1,000,000 cycles。 // 查阅手册SD_OPTION.MCYCLE位域可能只支持特定范围需按手册设置对应值。 // 此处假设[19:16]位为MCYCLE设置位值0x5代表某个分频后的计数值对应约20ms。 uint32_t temp REG_SD_OPTION; temp ~(0xF 16); // 清除MCYCLE位域 temp | (0x5 16); // 设置MCYCLE值 REG_SD_OPTION temp; // 2. 使能卡检测中断。 // SD_INFO1_MASK寄存器用于屏蔽中断。要接收中断需将对应屏蔽位清零。 // SDCDINM位卡插入中断屏蔽和SDCDRMM位卡移除中断屏蔽在SD_INFO1_MASK中。 // 假设SDCDINM是bit4SDCDRMM是bit5。写入0使能中断。 temp REG_SD_INFO1_MASK; temp ~((1u 4) | (1u 5)); // 清除bit4和bit5使能插入和移除中断 REG_SD_INFO1_MASK temp; // 3. 使能SDHI模块全局中断到NVIC嵌套向量中断控制器。 // 这步取决于具体MCU通常是设置NVIC的ISER寄存器。 NVIC_EnableIRQ(SDHI_MMC0_CARD_IRQn); // 使能卡检测中断线 } // 卡检测中断服务例程 void SDHI_MMC0_CARD_IRQHandler(void) { uint32_t info1 REG_SD_INFO1; // 检查并处理卡插入中断 if (info1 (1u 4)) { // 假设SDCDIN是bit4 // 卡已插入并稳定 printf(SD Card Inserted.\n); // 关键步骤清除中断标志位 REG_SD_INFO1 (1u 4); // 向SDCDIN位写1清零根据手册写1清零或写0清零需确认 // 通常手册描述为“clear by writing 0”但有些寄存器是写1清零务必核对 // 正确做法可能是REG_SD_INFO1 ~(1u 4); 仅清除该位保留其他位。 } // 检查并处理卡移除中断 if (info1 (1u 5)) { // 假设SDCDRM是bit5 // 卡已移除并稳定 printf(SD Card Removed.\n); // 清除中断标志位 REG_SD_INFO1 (1u 5); } }实操心得在清除SD_INFO1这类状态寄存器标志位时要特别小心。有些架构是“写1清零”有些是“写0清零”还有的需要“读取该寄存器后再写入特定值”。RA8D2手册明确写着“It is cleared by writing 0.”这意味着你需要向该位写0来清零但同时不能影响其他位。最安全的做法是REG_SD_INFO1 REG_SD_INFO1 ~(flag_bit_mask);即先读取当前值然后清除对应的位再写回。避免直接写入一个硬编码值那样可能会误清除其他未知的状态位。2. 写保护功能的工作原理与实现策略写保护功能是存储卡数据安全的一道重要硬件防线。SDHI接口的写保护机制同样包含硬件和软件两个层面理解其交互方式才能设计出既安全又灵活的系统。2.1 硬件写保护SDnWP引脚检测硬件写保护依赖于SD卡本身的写保护开关和卡座上的对应探针。当SD卡的写保护锁滑动到“锁定”位置时卡壳上的一个凹槽会被覆盖卡座内的探针连接至SDHI的SDnWP引脚会因此被压下或释放从而改变SDnWP引脚的电平状态。电平逻辑与上拉/下拉配置这里有一个容易混淆的点SDnWP引脚的电平逻辑关系。手册提到“pulled up or pulled down by the card insertion. The selection... is determined by the specification of the SD host device.” 这意味着主机端需要根据卡座和主机的设计规范决定是使用上拉电阻还是下拉电阻以及卡插入后写保护状态对应的是高电平还是低电平。常见的设计是SDnWP引脚通过一个上拉电阻连接到VCC卡座探针另一端接地。当写保护锁关闭锁定时探针被压下SDnWP引脚与地短路被拉低此时SD_INFO1寄存器中的SDWPMON位会被硬件置1表示写保护生效。当写保护锁打开时探针弹起SDnWP引脚被上拉电阻拉高SDWPMON为0表示可写入。这种“低电平有效”的设计最为普遍。但有些设计可能相反所以硬件原理图评审时必须确认这一点。状态同步时机SDWPMON的状态并不是实时反映SDnWP引脚电平的。手册指出“when the SDnWP state is reflected to SDWPMON... after the SD card is inserted.” 这意味着写保护状态是在卡插入事件被确认后才被采样并更新到SDWPMON位的。换句话说如果你在卡运行时去滑动写保护锁SDWPMON位可能不会立即改变需要等到下一次卡检测事件或者需要软件主动触发一次状态读取。在软件设计时不能假设SDWPMON是实时更新的应在卡初始化完成后读取一次并在每次进行写操作前作为安全检查的一环。2.2 软件写保护CMD命令锁机制除了物理开关SD/MMC卡协议还定义了一套通过命令实现的软件锁机制即密码保护CMD42。主机可以给卡设置一个密码此后任何写操作或擦除操作都必须先通过密码验证解锁才能进行。即使物理写保护开关是打开的如果卡被软件锁定写操作也会被拒绝。硬件写保护与软件锁的关系它们是独立且叠加的。最严格的情况是物理写保护锁关闭硬件锁定同时卡又设置了软件密码软件锁定。此时任何写操作都无法进行。通常嵌入式系统更依赖硬件写保护作为防误触发的第一道屏障因为它是纯物理的无需软件干预。软件锁则用于更高级别的安全需求。驱动层实现建议一个健壮的SDHI驱动应该在发送任何写命令如CMD24, CMD25之前执行以下检查序列检查卡是否在位通过SDCDIN或SDD3IN。检查硬件写保护状态读取SD_INFO1.SDWPMON。如果写保护生效应直接向上层返回“写保护错误”而不是尝试发送写命令。如果系统使用了软件锁则在此处进行密码验证流程。这样做可以避免向一个被写保护的卡发送写命令因为卡会返回错误响应但这浪费了总线时间和功耗从设计上讲也不够优雅。2.3 配置示例与避坑指南配置硬件写保护检测相对简单主要是确保中断和状态读取正确。// 在卡初始化函数中检查写保护状态 bool sdhci_is_write_protected(void) { // 首先确保卡已插入 if ((REG_SD_INFO1 (1u 4)) 0) { // 假设SDCDIN为bit4 return false; // 或无卡状态 } // 读取硬件写保护监控位假设SDWPMON是SD_INFO1的bit8 if (REG_SD_INFO1 (1u 8)) { return true; // 写保护生效 } return false; // 写保护未生效 } // 在写操作函数开头加入保护检查 int sdhci_write_blocks(uint32_t addr, const void *data, uint32_t num_blocks) { if (sdhci_is_write_protected()) { return SDHI_ERROR_WRITE_PROTECTED; } // ... 后续写命令发送流程 }常见问题排查写保护状态读取始终为固定值首先用万用表测量SDnWP引脚的实际电压确认在滑动写保护锁时电平是否变化。如果电平变化但软件读不到检查SD_INFO1寄存器的SDWPMON位定义是否正确以及是否需要在卡检测中断后或初始化后通过某个命令或寄存器操作来“刷新”这个状态。有些平台需要先使能写保护检测功能。写保护中断不触发与卡检测中断不同硬件写保护状态变化通常不产生独立的中断。SDWPMON只是一个状态位其变化不会直接触发SDHI中断。你需要通过轮询该位或者在卡检测中断服务程序中顺便检查它的方式来感知写保护状态的变化。如果你的应用需要实时响应写保护锁的滑动可能需要配置一个额外的GPIO外部中断到SDnWP引脚如果该引脚支持但这超出了SDHI模块本身的功能。3. SDHI中断机制全景与实战管理中断是SDHI模块实现高效、异步数据传输的核心。RA8D2的SDHI中断源众多管理好它们才能让SD卡操作既快速又稳定。这里我们不照本宣科地罗列表格而是从驱动工程师的角度梳理出中断管理的脉络和实战技巧。3.1 中断源分类与逻辑关系SDHI的中断本质上是由一系列状态标志位在SD_INFO1,SD_INFO2,SDIO_INFO1寄存器中触发的。但一个状态位置1并不必然导致CPU收到中断请求这里存在一个两级开关逻辑。第一级状态标志位。当特定事件发生时硬件自动置位对应的标志位。例如命令响应接收完成置位RSPEND数据缓冲区可写置位BWE发生CRC错误置位CRCE。第二级中断掩码Mask。每个状态标志位都有一个对应的掩码位位于SD_INFO1_MASK,SD_INFO2_MASK,SDIO_INFO1_MASK寄存器中。只有当状态标志位为1且其对应的中断掩码位为0时才会产生中断请求。掩码位为1表示“屏蔽”Masked该中断被关闭为0表示“使能”Enabled。这种设计给了软件极大的灵活性。例如在DMA传输数据时你可能只关心“传输结束”ACEND中断而不想被每一个“缓冲区空/满”BRE/BWE中断打扰这时你就可以只使能ACEND的中断而屏蔽BRE/BWE。中断分组与向量从手册表格看中断被分成了几组如“Card Access Interrupt (CACI)”、“Card Detect Interrupt (CDETI)”。在RA8D2中这些组可能映射到不同的中断向量IRQn。例如所有卡访问相关中断ACEND,RSPEND,BWE,BRE, 各种错误等可能共享一个中断线SDHI_MMCn_ACCS而卡检测中断SDD3IN,SDCDIN等共享另一条中断线SDHI_MMCn_CARD。在编写中断服务程序ISR时你需要先判断是哪个中断线触发然后在该ISR内检查具体的状态标志位来确定是哪一个子事件。3.2 关键中断处理流程详解我们以最常见的“单块读操作”为例拆解其中断处理的全过程。这个过程清晰地展示了状态标志、中断掩码和软件流程如何协同工作。步骤a初始化与启动。首先软件清除所有可能遗留的状态标志位向SD_INFO1和SD_INFO2写入特定值清零。然后进行关键配置设置SD时钟、传输数据大小并配置中断掩码。在启动命令前我们通常先屏蔽所有中断写入SD_INFO2_MASK0x0000_0B00等值目的是在设置阶段避免被意外中断打扰。步骤b命令发出与响应中断。写入SD_CMD寄存器发起CMD17读命令。硬件开始工作当它从SD卡收到命令响应时会置位SD_INFO1.RSPEND。由于此时RSPEND的中断掩码位是0未被屏蔽因此触发“Card Access Interrupt”。CPU跳转到对应的ISR。ISR内操作读取SD_INFO1和SD_INFO2确定中断源。发现是RSPEND置位。关键操作清除中断标志。向SD_INFO1寄存器的RSPEND位写入0根据手册规则将其清零。不清零会导致中断持续触发。从SD_RSP10寄存器读取命令响应内容并检查响应中是否包含错误如非法地址、命令不支持等。如果响应正常动态修改中断掩码。接下来要接收数据所以我们使能“数据缓冲区就绪中断”BRE和“访问结束中断”ACEND。通过写SD_INFO2_MASK0x0000_0A00来使能BRE中断写SD_INFO1_MASK0x0000_0319来使能ACEND中断。同时确保其他暂时不关心的中断如某些错误中断仍被屏蔽。步骤c数据接收中断。硬件将SD卡数据读入内部缓冲区SD_BUF0当缓冲区满或就绪时置位SD_INFO2.BRE再次触发中断。在BRE的ISR中清除BRE标志位。从SD_BUF0寄存器中快速读取指定大小SD_SIZE设置的数据存放到应用程序的内存中。重复此过程直到一个数据块传输完成。步骤d传输结束中断。一个数据块传输完成后硬件置位SD_INFO1.ACEND。在ACEND的ISR中清除ACEND标志位。此次单块读操作正式完成。软件可以准备下一次操作或通知上层任务。错误处理在上述任何阶段如果发生CRC错误、命令错误、超时等对应的错误标志位如CRCE,CMDE,RSPTO,DTO会被置位。如果这些错误的中断未被屏蔽也会触发中断。在错误处理ISR中除了清除中断标志还必须读取错误状态寄存器SD_ERR_STS1/2来查明具体错误原因并进行相应的恢复操作如重置SDHI控制器、重试命令等。3.3 中断服务程序编写最佳实践与陷阱编写SDHI的ISR是个精细活下面是一些血泪教训换来的经验1. 标志位清除的“原子性”与顺序清除中断标志位时务必遵循手册要求。RA8D2手册明确写道“When clearing the status flags... write 0 to the status flags to be cleared and write 1 to the status flags that are not being cleared.” 这意味着你需要构造一个值对要清的位写0对要保留的位写1。绝对不要简单地写入0xFFFFFFFF或0x00000000来清除这可能会误操作其他位。正确的做法通常是uint32_t info1 REG_SD_INFO1; // 假设要清除ACEND位bit0和RSPEND位bit1保留其他位 REG_SD_INFO1 info1 ~((1u0) | (1u1));2. 中断使能/屏蔽的时机不要在中断服务程序内部频繁地、全局地开关中断掩码。这可能导致中断丢失或状态混乱。正确的模式是在主流程的不同阶段预先设置好下一阶段期望的中断掩码模式。例如在发送命令前使能RSPEND中断在响应接收后在RSPEND的ISR末尾修改掩码以使能BRE中断。3. 超时管理中断是异步的必须考虑超时。硬件提供了超时状态位如RSPTO,DTO但软件也应该设置一个看门狗定时器。例如发送CMD17后如果500ms内都没有收到RSPEND或任何错误中断软件应主动超时进行错误恢复。不能无限期等待中断。4. 中断嵌套与优先级SDHI中断的优先级需要合理设置。数据缓冲区中断BRE/BWE通常需要高优先级和快速响应以避免缓冲区上溢或下溢。错误中断的优先级也应设高以便及时处理。卡检测中断的优先级可以设低一些。注意避免在ISR内进行耗时操作如大量内存拷贝对于数据搬运应使用DMA。5. 共享中断线的处理由于多个事件共享一个中断向量如SDHI_MMCn_ACCS你的ISR入口函数必须检查所有可能的状态位。一个标准的处理框架如下void SDHI_MMC0_ACCS_IRQHandler(void) { uint32_t info1 REG_SD_INFO1; uint32_t info2 REG_SD_INFO2; // 处理命令响应结束 if (info1 SD_INFO1_RSPEND_MASK) { handle_response_end(); REG_SD_INFO1 info1 ~SD_INFO1_RSPEND_MASK; // 清除标志 } // 处理数据缓冲区可读 if (info2 SD_INFO2_BRE_MASK) { handle_buffer_read_ready(); REG_SD_INFO2 info2 ~SD_INFO2_BRE_MASK; } // 处理访问结束 if (info1 SD_INFO1_ACEND_MASK) { handle_access_end(); REG_SD_INFO1 info1 ~SD_INFO1_ACEND_MASK; } // 处理各种错误 if (info2 (SD_INFO2_CRCE_MASK | SD_INFO2_CMDE_MASK | SD_INFO2_RSPTO_MASK)) { handle_error(info2); // 错误标志位清除可能需要在处理中根据错误类型分别进行 } }4. DMA传输请求与错误处理机制对于需要高速、大数据量传输的场景使用DMA直接内存访问来搬运SD缓冲区SD_BUF的数据是必不可少的。这能极大解放CPU同时减少传输延迟。RA8D2的SDHI模块提供了专门的DMA传输请求信号但其工作逻辑有一些需要特别注意的细节。4.1 DMA传输请求的触发与停止条件SDHI模块可以产生两种DMA请求SD_BUF写DMA请求主机从SD卡读数据到内存和SD_BUF读DMA请求主机从内存写数据到SD卡。它们的触发条件是对称的写DMA请求SDHI_MMCn_ODMSDBREQ当SD_INFO2.BWE位为1表示SD_BUF可写且SD_DMAEN.DMAEN位为1全局DMA使能时该请求信号被置为有效。读DMA请求当SD_INFO2.BRE位为1表示SD_BUF可读且SD_DMAEN.DMAEN位为1时该请求信号被置为有效。关键点在于停止条件正常完成当传输完一个数据块块大小由SD_SIZE定义的数据后DMA请求信号会自动取消Negated。这意味着对于多块传输每个块传输结束时请求信号会先取消下一个块数据准备好BRE/BWE再次置位时请求信号会再次产生。软件强制停止将SOFT_RST.SDRST位清零软件复位或设置SD_STOP.STP位为1会立即取消DMA请求。异常情况手册特别指出如果DMA传输过程中发生通信错误或超时DMA请求信号将不会被取消。这是一个非常重要的安全机制。因为此时数据传输已出错SDHI可能处于不确定状态如果DMA请求被取消DMA控制器可能停止工作而软件却不知道传输未完成。保持请求有效可以促使软件或DMA控制器进入错误处理流程。4.2 DMA传输的配置流程与核心约束配置SDHI的DMA传输不仅仅是开启DMAEN位那么简单必须遵循一个严格的流程否则极易导致数据错乱或DMA挂起。标准配置流程以读操作为例初始化阶段配置SDHI基本参数时钟、总线宽度等但先不要开启DMAEN。发送命令前设置好SD_SIZE块大小、SD_SECCNT块数多块传输时。配置DMA控制器本身源地址SD_BUF物理地址目标地址内存地址传输宽度总线宽度触发源SDHI DMA请求。关键一步在写入SD_CMD寄存器发起传输命令之前将SD_DMAEN.DMAEN位置1使能SDHI的DMA请求生成。命令发出写入SD_CMD启动读操作。DMA运作SDHI在数据就绪后置位BRE并发出DMA请求。DMA控制器接管数据搬运。传输结束处理一个块传输完成后BRE位不会立即自动清零。手册说明它是在“DMA传输完成对SD_BUF的写入请求后”才被清除。实际上通常需要在一个块传输完成的DMA传输完成中断或SDHI的ACEND中断中手动清除BRE或BWE位。重要约束手册强调“The number of DMA transfers must be n × one block”。这意味着你配置的DMA传输总字节数必须是SDHI块大小SD_SIZE的整数倍。你不能让DMA传输半块数据。如果内存缓冲区不是块大小的整数倍你需要用软件处理剩余部分或者调整缓冲区。一个致命的坑手册警告“Because the BWE bit... is not cleared in response to setting the STP/IOABT bit, or to a communications error or timeout, clear the bit to 0 before issuing the next command.” 这意味着如果因为错误或主动停止STP导致传输异常终止BRE/BWE位会保持为1。如果你不手动清除它在发起下一个命令时SDHI看到BRE/BWE仍为1且DMAEN也为1可能会立即产生一个DMA请求但这个请求对应的数据缓冲区是无效的从而导致DMA传输错误数据或系统崩溃。因此在任何异常处理路径中以及开始一次新的传输之前都必须检查并清除SD_INFO2中的BRE和BWE位。4.3 通信错误与超时的精细化处理SDHI模块定义了丰富的错误和超时状态位它们是调试和实现鲁棒驱动的关键。这些错误大致分为两类通信错误CRC错误、命令错误、结束位错误和超时错误响应超时、数据超时等。错误处理流程错误检测当错误发生时SD_INFO2寄存器中对应的错误标志位如CRCE,CMDE,ENDE,RSPTO,DTO会被置1。如果该错误的中断未被屏蔽会触发中断。错误溯源仅知道有错误不够还需知道具体原因。这时需要读取错误状态寄存器SD_ERR_STS1和SD_ERR_STS2。例如SD_INFO2.CRCE置位只表示发生了CRC相关错误但具体是响应CRC错RSPCRCE、数据CRC错RDCRCE还是状态令牌CRC错CRCTKE需要查SD_ERR_STS1的对应位。错误恢复这是最体现驱动质量的部分。不同错误需要不同的恢复策略响应超时RSPTO可能是卡未响应或命令不合法。应重试命令通常最多3次若仍失败则进行卡复位掉电再上电或SDHI控制器复位设置SDRST。数据CRC错误RDCRCE在读取数据时发生可能是信号质量差、时钟频率过高或卡有问题。可尝试降低SD时钟频率后重试。命令错误CMDE发送的命令索引与卡返回的响应中的命令索引不匹配。这通常是严重的协议错误应检查命令序列和卡状态机。状态清除错误处理完毕后必须清除错误标志。手册指出清除SD_ERR_STS1/2中的标志位可以通过写入SD_CMD寄存器即发送一个新命令或将SOFT_RST.SDRST位清零软件复位来实现。通常在错误恢复流程中执行一次SDHI软件复位是一个干净利落的选择但要注意这会中断所有进行中的操作。超时配置的学问超时时间通过SD_OPTION.TOP[3:0]位域配置。这个超时计数器用于数据超时DTO等场景。设置太短在慢速卡或干扰环境下容易误报超时设置太长系统在卡无响应时等待过久。需要根据所用卡的类型Class 2, 4, 10, UHS-I等和实际时钟频率来权衡。一个实用的方法是在卡初始化识别阶段使用一个较长的保守超时值在高速传输模式切换后再根据模式设置一个合理的较短超时值。5. 命令与数据传输操作流程全解手册中提供了从简单命令到多块读写等多种操作的流程图。这些流程图是实现的蓝图但直接照搬代码会很晦涩。我将以“单块读”和“多块写”为例解读其软件状态机的实现要点并补充流程图里没说的细节。5.1 单块读操作CMD17的软件状态机实现单块读是基础理解了它多块读和写操作都是在其基础上的扩展。我们抛开寄存器地址的魔数理解每个步骤的目的。状态0准备Preparation动作清除SD_INFO1/2所有状态标志。配置时钟(SD_CLK_CTRL)、块大小(SD_SIZE)。关键设置中断掩码。通常开始时我们只使能最关心的事件中断比如响应结束(RSPEND)和访问结束(ACEND)而暂时屏蔽数据缓冲区中断(BRE)和某些错误中断以简化初始状态。目的确保SDHI控制器处于一个干净的、已知的初始状态避免上次操作遗留的标志影响本次。状态1发送命令与等待响应Command Response动作写入命令参数(SD_ARG)和命令码(SD_CMD)。然后等待。事件与处理RSPEND中断触发。在ISR中清除RSPEND标志读取响应寄存器(SD_RSP10)。必须检查响应中的错误位如参数错误、地址错误、卡状态等。如果响应有误应在此处终止流程进行错误处理。状态2切换至数据接收模式Mode Switching动作响应正确后在RSPEND的ISR末尾或主循环中动态修改中断掩码。禁用RSPEND中断因为已处理完使能BRE缓冲区可读中断和ACEND访问结束中断。这一步是流程图中的“Enable the BRE interrupt”。目的让硬件在数据就绪时通知我们而不是让CPU轮询。状态3接收数据Data Reception事件与处理BRE中断触发。在ISR中清除BRE标志然后从SD_BUF0寄存器可能是一个FIFO或固定地址连续读取SD_SIZE字节的数据存入用户缓冲区。这里要注意数据对齐和字节序问题。状态4传输结束Completion事件与处理ACEND中断触发。在ISR中清除ACEND标志。此时一个完整的数据块已从卡传输到主机内存操作成功结束。收尾可以选择性地关闭相关中断掩码或将控制器状态复位准备下一次操作。错误状态的贯穿处理在上述任何状态如果发生CRCE,DTO等错误中断应立即跳转到错误处理例程。该例程应记录错误类型、尝试恢复如重试、复位并最终清理状态清除错误标志、BRE/BWE标志等将控制器返回到一个可用的空闲状态。5.2 多块写操作CMD25与内部/外部定时器选择多块写CMD25比单块写更复杂因为它涉及如何终止传输的问题。SDHI提供了两种机制内部自动停止和依赖外部定时器。内部自动停止SD_STOP.SEC1这是最常用的方式。在发送CMD25之前你将SD_STOP寄存器的SEC位置1并在SD_SECCNT寄存器中设置要写入的块数量。SDHI控制器会在传输完指定数量的块后自动发送一个CMD12STOP_TRANSMISSION命令来终止多块写入序列。这种方式简单可靠软件只需等待最终的ACEND中断即可。操作流程要点设置SD_STOP.SEC1和SD_SECCNT。发送CMD25。在BWE中断中循环写入数据块。传输完SD_SECCNT指定的块数后硬件自动发CMD12并在收到CMD12响应后产生ACEND中断。使用外部定时器针对MMC某些旧的MMC卡协议或特定应用场景下可能需要使用外部定时器来管理写超时。如图47.15所示其流程与内部停止类似但有几点关键不同命令不同使用的是CMD54而非CMD25。超时掩码需要设置SD_OPTION.TOUTMASK1。停止条件传输不是由块数决定而是由外部定时器超时或软件主动设置STP位来停止。当外部定时器超时它会触发一个流程最终导致SDHI停止传输并可能产生中断。错误处理分支流程图中有一个“Timeout by external timer”的判断分支如果发生则需要进行软件复位(SDRST)等处理。选择建议对于绝大多数SD卡操作强烈推荐使用**内部自动停止SEC1**方式。它由硬件保证原子性避免了软件在错误时机发送CMD12的风险。只有在遇到特定的、需要精确控制写入时序的MMC卡或者旧协议兼容时才考虑使用外部定时器模式。5.3 实战中的寄存器操作“黑话”解读手册流程图里充满了像W(SD_INFO2_MASK, 0x0000_0A00)这样的魔数直接抄容易出错必须理解其含义。0x0000_0A00这是一个针对SD_INFO2_MASK的值。我们需要查看SD_INFO2_MASK的位定义。假设BRE中断对应SD_INFO2_MASK的bit9BRE MaskBWE对应bit8。要使能BRE中断需要将bit9清零。0x0A00的二进制是0000 1010 0000 0000。bit9是0bit8是1不对这里需要仔细看。实际上这个值可能是为了在使能BRE的同时屏蔽其他位如ILA,BWE,RSPTO等。正确的理解方式是查阅寄存器定义表计算哪些位需要0使能中断哪些位需要1屏蔽中断。例如如果BRE是bit9要使能它该位应为0。所以0x0000_0A00(bit90, bit81) 可能是使能了BRE但屏蔽了BWE。这正符合单块读操作中我们只需要读中断(BRE)不需要写中断(BWE)的场景。通用方法不要死记硬背魔数。在代码中应该使用位掩码宏定义让意图更清晰#define SD_INFO2_MASK_BRE (1u 9) // BRE中断掩码位 #define SD_INFO2_MASK_BWE (1u 8) // BWE中断掩码位 #define SD_INFO2_MASK_RSPTO (1u 3) // 响应超时中断掩码位 // ... 其他位定义 // 使能BRE中断屏蔽BWE和RSPTO中断 uint32_t mask_value 0xFFFFFFFF; mask_value ~SD_INFO2_MASK_BRE; // BRE位清零使能 mask_value | SD_INFO2_MASK_BWE; // BWE位置1屏蔽 mask_value | SD_INFO2_MASK_RSPTO; // RSPTO位置1屏蔽 // ... 设置其他位 REG_SD_INFO2_MASK mask_value;这样写代码的可读性和可维护性远高于直接写一个十六进制魔数。