4. MCU端SPI外设初始化:时钟、极性、相位配置详解

好,咱们进入正题。上一章我们把SD卡的物理层和协议层理清楚了,这一章要干的事很具体——把MCU的SPI外设配好,让它能和SD卡对上话。

SPI初始化,说白了就是三件事:时钟、极性、相位。这三样配不对,卡就认不到。我在项目里见过太多人卡在这一步,折腾半天发现是CPOL和CPHA搞反了。

4.1 SPI时钟树与分频配置

先看时钟。MCU的SPI外设挂在哪条总线上,决定了它的时钟源和最大频率。以STM32F4为例,SPI1挂在APB2上,SPI2和SPI3挂在APB1上。APB2最高84MHz,APB1最高42MHz。

SD卡在SPI模式下,初始化阶段时钟不能太快。规范里写得很清楚:初始化时SPI时钟不能超过400kHz。为什么?因为卡还没完成上电初始化,内部逻辑还不稳定,跑快了容易丢数据。

关键点:初始化阶段用低速(≤400kHz),初始化完成后切到高速(通常10-20MHz)。

我习惯的做法是:先配一个低速分频系数,等卡初始化成功后再动态调整SPI的波特率寄存器。代码里大概这样:

// 初始化阶段:低速模式
SPI1->CR1 |= SPI_CR1_BR_2 | SPI_CR1_BR_1 | SPI_CR1_BR_0;  // 256分频
// 假设APB2=84MHz,SPI时钟 = 84MHz / 256 ≈ 328kHz,满足≤400kHz

// 初始化完成后:切换到高速模式
SPI1->CR1 &= ~(SPI_CR1_BR_2 | SPI_CR1_BR_1 | SPI_CR1_BR_0);
SPI1->CR1 |= SPI_CR1_BR_1;  // 4分频,SPI时钟 = 84MHz / 4 = 21MHz

小技巧:分频系数不要一次切到底。我一般分两步走:先切到1MHz左右确认卡能响应,再切到目标高速。这样万一卡对高频敏感,还能降回来。

4.2 SPI极性(CPOL)与相位(CPHA)详解

这两个参数是SPI配置里最容易出错的。咱们一个一个说清楚。

4.2.1 CPOL:时钟极性

CPOL决定SCLK空闲时的电平状态:

  • CPOL=0:空闲时SCLK为低电平
  • CPOL=1:空闲时SCLK为高电平

SD卡在SPI模式下要求CPOL=0。也就是说,没有数据传输时,时钟线保持低电平。

4.2.2 CPHA:时钟相位

CPHA决定数据在哪个时钟沿被采样:

  • CPHA=0:在第一个时钟沿采样数据
  • CPHA=1:在第二个时钟沿采样数据

SD卡要求CPHA=0。结合CPOL=0,就是:空闲低电平,第一个时钟沿(上升沿)采样数据。

注意:有些MCU的SPI外设默认配置不是CPOL=0、CPHA=0。比如我遇到过某款国产MCU,复位后CPOL默认是1。如果你直接拿默认配置去读SD卡,卡永远不响应。所以初始化时一定要显式设置这两个位。

配置代码示例:

// SPI模式0:CPOL=0, CPHA=0
SPI1->CR1 &= ~(SPI_CR1_CPOL | SPI_CR1_CPHA);  // 清零
// 或者直接写:
SPI1->CR1 |= (0 << SPI_CR1_CPOL_Pos) | (0 << SPI_CR1_CPHA_Pos);

4.3 数据格式与传输顺序

除了时钟,还有两个位要配:数据帧格式和MSB/LSB顺序。

参数 SD卡要求 说明
数据帧格式 8位 SD卡SPI命令和响应都是8位字节
MSB/LSB MSB first 高位先传,这是SPI的常规做法
片选极性 低电平有效 CS拉低选中卡,拉高释放

配置代码:

// 8位数据帧,MSB first
SPI1->CR1 &= ~SPI_CR1_DFF;   // 0: 8位数据帧
SPI1->CR1 &= ~SPI_CR1_LSBFIRST; // 0: MSB first

// 软件控制片选(推荐)
// 配置CS引脚为GPIO输出,手动拉高拉低
GPIO_SetBits(GPIOA, GPIO_Pin_4);  // CS初始高电平

为什么推荐软件控制CS?因为SD卡的SPI模式需要精确控制CS的拉高时机,尤其是在发送命令和读取响应之间。硬件自动CS往往时机不对,容易导致卡状态机紊乱。

4.4 完整的SPI初始化流程

把上面所有配置串起来,一个完整的SPI初始化函数大概长这样:

void SPI_SD_Init(void)
{
    // 1. 使能SPI和GPIO时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
    
    // 2. 配置GPIO复用功能
    // SCK: PA5, MISO: PA6, MOSI: PA7, CS: PA4
    GPIO_InitTypeDef gpio;
    gpio.GPIO_Mode = GPIO_Mode_AF;
    gpio.GPIO_Speed = GPIO_Speed_50MHz;
    gpio.GPIO_OType = GPIO_OType_PP;
    gpio.GPIO_PuPd = GPIO_PuPd_UP;
    
    // SCK, MOSI, CS 推挽输出
    gpio.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7 | GPIO_Pin_4;
    GPIO_Init(GPIOA, &gpio);
    
    // MISO 浮空输入
    gpio.GPIO_Pin = GPIO_Pin_6;
    gpio.GPIO_Mode = GPIO_Mode_AF;
    gpio.GPIO_PuPd = GPIO_PuPd_NOPULL;
    GPIO_Init(GPIOA, &gpio);
    
    // 3. 配置SPI外设
    SPI_InitTypeDef spi;
    SPI_StructInit(&spi);
    spi.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
    spi.SPI_Mode = SPI_Mode_Master;
    spi.SPI_DataSize = SPI_DataSize_8b;
    spi.SPI_CPOL = SPI_CPOL_Low;      // CPOL=0
    spi.SPI_CPHA = SPI_CPHA_1Edge;    // CPHA=0
    spi.SPI_NSS = SPI_NSS_Soft;       // 软件NSS
    spi.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; // 低速
    spi.SPI_FirstBit = SPI_FirstBit_MSB;
    SPI_Init(SPI1, &spi);
    
    // 4. 使能SPI
    SPI_Cmd(SPI1, ENABLE);
    
    // 5. CS初始高电平
    GPIO_SetBits(GPIOA, GPIO_Pin_4);
}

4.5 SPI配置与SD卡模式的对应关系

为了让你更直观地理解,我画了一张图,把SPI配置和SD卡SPI模式的关系串起来:

SPI外设配置与SD卡SPI模式对应关系 MCU SPI外设 • 时钟源:APB1/APB2 • 分频系数:低速/高速 • CPOL=0, CPHA=0 • 8位数据,MSB first • 软件CS控制 SPI 总线 SCLK: 空闲低电平 MOSI/MISO: 上升沿采样 SD卡 (SPI模式) • 初始化时钟 ≤400kHz • 工作时钟 ≤25MHz • 模式0 (CPOL=0, CPHA=0) • 命令:6字节固定格式 • 响应:R1/R2/R3等 配置要点总结 1. 时钟分频:初始化用256分频(~328kHz),完成后切到4分频(~21MHz) 2. 模式匹配:MCU必须配成CPOL=0, CPHA=0,与SD卡SPI模式0一致 3. 数据格式:8位数据帧,MSB first,全双工模式 4. 片选控制:使用软件CS,手动拉高拉低,确保时序精确 5. 常见错误:CPOL/CPHA配反、分频系数太大或太小、CS时序不对

4.6 常见配置错误与排查方法

我在调试SD卡驱动时,SPI配置这块踩过不少坑。下面列几个最常见的:

我曾经遇到过一个案例:某款MCU的SPI外设,CPOL和CPHA的极性定义和标准SPI规范是反的。手册上写CPOL=0是空闲高电平,但实际测试发现CPOL=0才是空闲低电平。最后用示波器量了SCLK波形才确认。所以,不要完全相信手册,一定要用示波器实测。

排查步骤:

  1. 先看SCLK波形:空闲时是低电平还是高电平?确认CPOL对不对。
  2. 再看数据采样点:在SCLK的哪个沿有数据变化?确认CPHA对不对。
  3. 量一下时钟频率:初始化阶段是否≤400kHz?高速阶段是否在卡的支持范围内?
  4. 检查CS时序:发送命令前CS是否已拉低?命令发送完后CS是否保持低电平直到响应读完?

小技巧:如果你没有示波器,可以用一个简单的方法验证SPI配置——先发一个0xFF字节,看MISO上有没有回波。如果SPI配置正确,MISO应该能收到卡返回的数据(至少是0xFF或0x00)。如果MISO一直没反应,八成是CPOL/CPHA配错了。