1. SPI四种模式到底在解决什么问题
SPI通信调不通,十次里有八次栽在模式匹配上。我见过太多人对着示波器抓了半天波形,最后发现从机手册上写的是Mode 3,代码里配的是Mode 0。这种事不分新手老手,因为SPI的四种模式本质上就是两个二进制位的组合,但这两个位决定了数据在时钟的哪个边沿采样、空闲时时钟是高还是低,一旦错位,读回来的数据要么全FF要么全00,要么就是看似有规律实则完全错乱的鬼数据。
先把核心概念说清楚。SPI是同步串行通信,主机产生时钟,数据在时钟的驱动下一位一位地移入移出。这里有两个关键参数:时钟极性(CPOL)和时钟相位(CPHA)。CPOL决定时钟空闲时是高电平还是低电平,CPHA决定数据在第一个边沿还是第二个边沿被采样。两个参数各取0或1,组合起来就是四种模式:Mode 0(CPOL=0, CPHA=0)、Mode 1(CPOL=0, CPHA=1)、Mode 2(CPOL=1, CPHA=0)、Mode 3(CPOL=1, CPHA=1)。
为什么要有四种模式?因为不同厂商的芯片设计习惯不同。有的芯片在时钟上升沿输出数据、下降沿采样,有的反过来。早期没有统一标准,各家做各家的,后来虽然大多数人用Mode 0和Mode 3,但Mode 1和Mode 2依然存在于大量传感器、Flash、ADC芯片中。你没法要求所有芯片都统一,只能让主机去适配从机。
注意:SPI模式没有“自动检测”机制。主机必须主动配置成从机要求的模式,配错了通信必然失败。这一点和I2C不同,I2C有起始条件和地址机制,SPI什么都没有,全靠事先约定。
适合谁看这篇内容?如果你正在用STM32、FPGA、或者任何MCU驱动SPI外设,抓过波形但不确定采样边沿对不对,或者手上有几个不同模式的SPI芯片要挂在同一条总线上,那这篇内容就是给你写的。我会把四种模式的时序拆开讲,配上实际配置代码和示波器抓波形的判断方法,最后给一张速查表,以后拿到任何SPI芯片,三分钟内确定该用哪个模式。
2. 四种模式时序的底层逻辑拆解
2.1 CPOL和CPHA到底怎么影响数据采样
先建立一个基本认知:SPI通信的本质是移位寄存器。主机和从机各有一个8位(或更多位)的移位寄存器,时钟每来一个边沿,数据就移一位。问题是,时钟有上升沿和下降沿两个边沿,数据在哪个边沿“输出”、在哪个边沿“采样”,必须提前约定好。
CPOL=0意味着时钟空闲时是低电平。当时钟开始工作时,第一个边沿必然是上升沿。CPOL=1则相反,空闲时高电平,第一个边沿是下降沿。
CPHA=0意味着数据在第一个边沿被采样。注意,采样和输出是两回事。CPHA=0时,数据在第一个边沿之前就已经准备好(输出),第一个边沿到来时从机(或主机)读取数据。CPHA=1则是在第二个边沿采样,第一个边沿用来输出数据。
把这两个参数组合起来,四种模式的时序行为就清晰了:
| 模式 | CPOL | CPHA | 空闲时钟 | 第一个边沿 | 采样边沿 | 输出边沿 |
|---|---|---|---|---|---|---|
| Mode 0 | 0 | 0 | 低 | 上升 | 上升 | 下降 |
| Mode 1 | 0 | 1 | 低 | 上升 | 下降 | 上升 |
| Mode 2 | 1 | 0 | 高 | 下降 | 下降 | 上升 |
| Mode 3 | 1 | 1 | 高 | 下降 | 上升 | 下降 |
这张表建议存下来。我调试SPI的时候经常对着它确认,尤其是Mode 1和Mode 2,这两个模式用得少,容易记混。
2.2 为什么Mode 0和Mode 3最常用
实际项目中,Mode 0和Mode 3的出现频率远高于Mode 1和Mode 2。原因不复杂:这两种模式的采样边沿和输出边沿是“错开”的,数据在半个时钟周期内保持稳定,留给从机采样的窗口更大,时序余量更充足。
Mode 0的时序是这样的:时钟空闲低电平,第一个上升沿采样数据,下降沿输出下一位。也就是说,数据在上升沿被读走,然后在下降沿切换。从机的数据建立时间是从上一个下降沿到当前上升沿,有半个周期;保持时间是从上升沿到下一个下降沿,也有半个周期。这个对称性让Mode 0在高速通信时更稳定。
Mode 3则是Mode 0的“镜像”:空闲高电平,第一个下降沿采样,上升沿输出。逻辑一样,只是极性反了。很多SPI Flash(比如W25Q系列)默认支持Mode 0和Mode 3,因为这两种模式在高速下时序余量最好。
Mode 1和Mode 2的问题在于,采样边沿和输出边沿是同一个类型的边沿(比如Mode 1都是上升沿输出、下降沿采样,但第一个边沿是上升沿用于输出,第二个边沿下降沿用于采样)。这意味着数据在第一个边沿输出后,只有半个周期就会被采样,建立时间相对紧张。低速下没问题,高速下容易出错。
实操心得:如果你在选型阶段有主动权,尽量选支持Mode 0或Mode 3的芯片。如果不得不用Mode 1或Mode 2,把SPI时钟先降到1MHz以下调通,再逐步往上提,观察波形是否出现采样错误。
2.3 从机手册里怎么快速找到模式信息
拿到一颗SPI芯片,第一件事是翻手册找时序图。不同厂商的写法不一样,但核心信息就那几个:CPOL、CPHA、或者直接写“Mode 0/1/2/3”。有些手册不直接写模式,而是画一张时序图,标出“SCK”、“MOSI”、“MISO”、“CS”的波形,你需要自己判断。
判断方法:看SCK空闲时是高还是低,确定CPOL;看数据在第一个边沿还是第二个边沿变化,确定CPHA。注意,有些手册画的是“数据在上升沿变化”,那采样就是在下降沿,对应CPHA=1。这个逻辑要理清楚,不然容易反。
还有一种情况:手册只写了“数据在SCK上升沿采样”,没提CPOL。这时候你需要看SCK空闲电平。如果空闲低,那就是Mode 0;如果空闲高,那就是Mode 2。因为采样边沿是上升沿,CPOL=0时第一个边沿是上升沿(Mode 0),CPOL=1时第二个边沿是上升沿(Mode 2)。
3. STM32平台上四种模式的配置与实测
3.1 CubeMX中SPI参数配置的对应关系
用STM32CubeMX配置SPI时,界面上的参数和四种模式的对应关系很直接。在SPI配置页里,有两个关键选项:Clock Polarity和Clock Phase。Clock Polarity选Low就是CPOL=0,选High就是CPOL=1;Clock Phase选1 Edge就是CPHA=0,选2 Edge就是CPHA=1。
具体对应:
- Mode 0:Clock Polarity = Low,Clock Phase = 1 Edge
- Mode 1:Clock Polarity = Low,Clock Phase = 2 Edge
- Mode 2:Clock Polarity = High,Clock Phase = 1 Edge
- Mode 3:Clock Polarity = High,Clock Phase = 2 Edge
这里有个容易踩的坑:CubeMX里Clock Phase的“1 Edge”和“2 Edge”指的是第一个边沿和第二个边沿,不是上升沿和下降沿。很多人看到“1 Edge”以为是上升沿,结果配错了。记住,Edge是边沿的序号,不是边沿的类型。
另外,NSS(片选)的设置也需要注意。CubeMX里可以选择硬件NSS或软件NSS。硬件NSS由SPI外设自动控制,但STM32的硬件NSS在多主机场景下容易出问题,一般建议用软件NSS,手动控制GPIO。软件NSS的灵活性更高,也更容易调试。
// STM32 HAL库中SPI初始化结构体的关键字段 hspi1.Instance = SPI1; hspi1.Init.Mode = SPI_MODE_MASTER; hspi1.Init.Direction = SPI_DIRECTION_2LINES; hspi1.Init.DataSize = SPI_DATASIZE_8BIT; hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; // CPOL=0 hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; // CPHA=0 hspi1.Init.NSS = SPI_NSS_SOFT; hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_128; hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; hspi1.Init.TIMode = SPI_TIMODE_DISABLE; hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;上面这段代码配置的是Mode 0。如果要改成Mode 3,只需要把CLKPolarity改成SPI_POLARITY_HIGH,CLKPhase改成SPI_PHASE_2EDGE。其他字段不变。
3.2 用示波器抓波形判断模式是否匹配
配置完之后,怎么确认模式对了?最直接的方法是用示波器或逻辑分析仪抓SCK和MOSI的波形。以Mode 0为例,你应该看到:CS拉低后,SCK从低电平开始,第一个上升沿时MOSI上的数据是稳定的,下降沿时数据切换。如果看到数据在上升沿切换,那说明实际工作在Mode 1或Mode 3,和配置不符。
我一般会发一个固定的字节,比如0xA5(二进制10100101),然后抓波形对照。0xA5的位模式是1-0-1-0-0-1-0-1,在示波器上很容易辨认。如果抓到的位顺序反了,可能是MSB/LSB设置错了;如果位顺序对但采样位置不对,就是模式配错了。
还有一种情况:波形看起来完全不对,SCK没有输出,或者MOSI一直是高电平。这通常不是模式问题,而是SPI外设没使能、GPIO复用没配好、或者CS没拉低。排查顺序应该是:先确认SCK有没有波形,再确认MOSI有没有数据,最后才看采样边沿。
注意:用示波器抓SPI波形时,建议用四通道同时抓CS、SCK、MOSI、MISO。CS是判断通信起止的关键,没有CS参考,单看SCK和MOSI很难判断第一个边沿是哪个。
3.3 DMA方式读取数据时的模式注意事项
用DMA方式读SPI数据时,模式配置本身不变,但有几个额外的坑要注意。首先是DMA的传输方向:SPI全双工模式下,发送和接收是同时进行的,所以DMA需要配置两个通道,一个发一个收。如果只配了接收DMA没配发送DMA,SPI时钟不会产生,因为主机没有发送数据就不会产生时钟。
其次是数据对齐。STM32的SPI数据寄存器是16位的,但实际数据可能是8位。用DMA时,如果外设数据宽度和内存数据宽度设置不一致,会出现数据错位。比如SPI配置为8位数据,DMA外设地址宽度应该设为字节,内存地址宽度也设为字节。如果设成了半字,读回来的数据会多出无效字节。
// SPI1 RX DMA配置示例 hdma_spi1_rx.Instance = DMA1_Channel2; hdma_spi1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY; hdma_spi1_rx.Init.PeriphInc = DMA_PINC_DISABLE; hdma_spi1_rx.Init.MemInc = DMA_MINC_ENABLE; hdma_spi1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; hdma_spi1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; hdma_spi1_rx.Init.Mode = DMA_NORMAL; hdma_spi1_rx.Init.Priority = DMA_PRIORITY_HIGH;还有一个容易忽略的点:DMA传输完成中断。用DMA读数据时,CPU不参与每个字节的搬运,但需要在DMA传输完成后处理数据。如果没开传输完成中断,或者中断里没清标志位,会导致下一次传输无法启动。我遇到过DMA只工作一次就卡死的情况,最后发现是中断标志没清。
另外,SPI的DMA请求使能要在SPI使能之后、DMA配置完成之后。顺序错了会导致DMA请求丢失。正确的顺序是:配置SPI -> 配置DMA -> 使能DMA -> 使能SPI的DMA请求 -> 拉低CS -> 启动DMA传输。
4. 不同模式在实际芯片上的对比测试
4.1 用同一颗Flash芯片测试四种模式
为了直观对比四种模式的差异,我拿W25Q64 Flash做了一组测试。W25Q64支持Mode 0和Mode 3,手册里明确写了这两种模式。我分别用Mode 0和Mode 3读取同一个地址的数据,对比波形和读取结果。
测试条件:SPI时钟设为10MHz,读取地址0x000000的3个字节。Mode 0下,SCK空闲低,第一个上升沿采样,读取结果正常。Mode 3下,SCK空闲高,第一个下降沿采样,读取结果也正常。两种模式读出的数据完全一致,因为Flash内部会自动适配这两种模式。
但把时钟提到30MHz后,Mode 0的波形依然干净,Mode 3的波形在下降沿附近出现了轻微振铃。这是因为Mode 3的空闲高电平在切换时更容易受到PCB走线寄生电容的影响。当然,这跟板子设计有关,不是绝对的。
然后我故意把主机配成Mode 1去读W25Q64,结果读回来的全是0xFF。用示波器看,SCK和MOSI的波形都有,但MISO一直是高阻态。原因是Flash在Mode 1下不响应,它只认Mode 0和Mode 3。这个测试说明:从机不支持的模式,主机配了也没用,从机根本不会驱动MISO。
4.2 多从机不同模式挂同一条总线的处理方案
实际项目中经常遇到这种情况:一条SPI总线上挂了多个从机,但它们的模式不一样。比如一个传感器用Mode 0,一个ADC用Mode 3。这时候怎么办?
方案一:分时复用,动态切换模式。在访问不同从机之前,重新配置SPI的CPOL和CPHA。STM32的SPI外设支持在运行时修改CPOL和CPHA,但要注意:修改这些位之前必须先禁用SPI(清除SPE位),修改完再重新使能。如果在传输过程中修改,会导致时序错乱。
方案二:用软件SPI模拟。如果从机数量不多、速率要求不高,可以用GPIO模拟SPI时序,针对每个从机单独控制时钟极性和相位。软件SPI的灵活性最高,但速率上不去,一般不超过1MHz。
方案三:加SPI多路复用器。用硬件多路复用器切换SCK、MOSI、MISO到不同的从机,每个从机可以独立配置模式。但这样增加了硬件成本,而且多路复用器本身会引入延迟和寄生参数。
我一般优先选方案一,因为改动最小。但要注意,动态切换模式时,CS必须保持高电平,确保从机处于空闲状态。切换完成后,再拉低对应从机的CS开始通信。
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 动态切换模式 | 无需额外硬件 | 切换有开销,高速下可能不稳 | 从机数量少,速率要求一般 |
| 软件SPI | 灵活性最高 | 速率低,占CPU | 低速、少量从机 |
| 硬件多路复用 | 各从机独立 | 成本高,引入延迟 | 高速、多从机 |
4.3 模式不匹配时的典型波形特征
模式配错时,波形会有一些典型特征,认识这些特征能帮你快速定位问题。最常见的是数据移位:读回来的数据比预期多一位或少一位,或者高低位颠倒。这是因为采样边沿错了,导致数据在错误的时刻被读取。
另一种典型特征是MISO一直为高或一直为低。如果MISO一直是高电平,可能是从机没有驱动MISO(模式不支持或CS没拉低);如果一直是低电平,可能是从机在错误的时间驱动了MISO,导致总线冲突。
还有一种情况是数据间歇性错误。低速下正常,高速下出错。这通常是时序余量不足,采样边沿太靠近数据切换边沿。解决办法是降低时钟频率,或者换用Mode 0/Mode 3。
实操心得:调试SPI时,先用手册确认从机支持的模式,再用示波器确认主机输出的波形符合该模式,最后检查CS、MISO的连接。三步走完,90%的问题都能定位。
5. 常见问题排查与避坑指南
5.1 SPI通信完全不生效的排查顺序
SPI调不通,按这个顺序排查,基本不会漏:
- 确认CS是否拉低。CS没拉低,从机根本不会响应。用万用表或示波器测CS引脚,通信期间应该是低电平。
- 确认SCK是否有波形。SCK没波形,说明SPI外设没使能、GPIO复用没配好、或者DMA没启动。先解决时钟问题。
- 确认MOSI是否有数据。MOSI没数据,说明发送寄存器没写入,或者DMA发送通道没配置。
- 确认模式是否匹配。对照从机手册,检查CPOL和CPHA设置。
- 确认MISO是否被从机驱动。如果MISO一直高阻,说明从机没响应,可能是模式不对、CS不对、或者从机供电有问题。
- 确认时钟频率是否过高。降低到1MHz以下再试,排除时序余量问题。
这个顺序是从“有没有”到“对不对”的递进。先确认最基本的信号有没有,再确认参数对不对。很多人一上来就怀疑模式,结果发现是CS没接好。
5.2 模式配错导致的数据错位案例分析
我之前调一颗压力传感器,手册上写的是Mode 1,我配成了Mode 0。读出来的压力值一直是量程的一半,而且不随压力变化。用示波器抓波形,发现MISO上的数据在SCK上升沿变化,但我在上升沿采样,采到的是变化中的值,导致数据错位。
改成Mode 1后,数据在下降沿采样,上升沿变化,采样点落在数据稳定区,读数立刻正常了。这个案例说明:模式配错不一定导致通信完全失败,但会导致数据错误。如果读回来的数据“看起来有规律但不对”,优先怀疑模式。
还有一种更隐蔽的情况:数据在低速下正常,高速下错位。这是因为模式虽然配对了,但采样边沿太靠近数据切换边沿,高速下建立时间不够。解决办法是换用Mode 0或Mode 3,这两种模式的采样窗口更大。
5.3 硬件片选与软件片选的模式影响
硬件片选(NSS)由SPI外设自动控制,通信开始时自动拉低,结束时自动拉高。软件片选则是手动控制GPIO。从模式配置的角度看,硬件片选和软件片选不影响CPOL和CPHA,但会影响通信的起止时序。
硬件片选的问题是:STM32的NSS在主机模式下如果配置为硬件模式,当NSS引脚被外部拉低时,SPI会自动进入从机模式,导致通信混乱。所以主机模式下一般建议用软件片选,把NSS配置为SPI_NSS_SOFT,然后用普通GPIO控制CS。
软件片选的好处是灵活:可以在CS拉低之前先配置好模式,CS拉高之后再做其他操作。而且多个从机可以共用SCK、MOSI、MISO,只用不同的GPIO做CS,节省引脚。
注意:用软件片选时,CS的拉低和拉高之间要留足够的延时。CS拉低后不要立刻发数据,等几个微秒让从机准备好;数据发完后也不要立刻拉高CS,等最后一个时钟边沿完成后再拉高。这个延时在高速通信时尤其重要。
5.4 常见问题速查表
| 现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| 读回全0xFF | MISO未驱动、模式不支持 | 测MISO电平 | 确认从机支持的模式,检查CS |
| 读回全0x00 | 总线冲突、从机未响应 | 测MISO波形 | 检查CS和模式配置 |
| 数据错位 | 模式配错、采样边沿不对 | 抓SCK和MOSI波形 | 对照手册改CPOL/CPHA |
| 低速正常高速出错 | 时序余量不足 | 降低时钟测试 | 换Mode 0/3,或降频 |
| 只工作一次 | DMA中断未清、CS未拉高 | 查中断标志 | 清标志,确保CS拉高 |
| 数据高低位颠倒 | MSB/LSB设置错误 | 查FirstBit配置 | 改SPI_FIRSTBIT_MSB/LSB |
6. 模式选择与代码复用的实战建议
6.1 拿到新芯片后三分钟确定模式的流程
我总结了一个快速确定SPI模式的流程,拿到任何新芯片都能用:
第一步,翻手册找“SPI Timing”或“Serial Interface”章节,找CPOL、CPHA、或者Mode字样。如果直接写了Mode 0/1/2/3,直接对应配置。
第二步,如果手册只给了时序图,看SCK空闲电平定CPOL,看数据在第一个还是第二个边沿变化定CPHA。注意,数据变化的边沿和采样的边沿是相反的。
第三步,如果手册什么都没写清楚,用排除法:先试Mode 0,不行试Mode 3,再不行试Mode 1和Mode 2。每次只改模式,其他不变。一般试到第二个就能通。
第四步,通了之后用示波器确认波形,确保采样点落在数据稳定区。如果波形看起来勉强,降低时钟频率再试。
这个流程我用了很多次,基本三分钟内能确定模式。关键是不要瞎试,要有逻辑地排除。
6.2 代码中如何优雅地支持四种模式切换
如果项目需要支持多种模式的从机,代码结构上建议把SPI配置封装成函数,传入模式参数。这样切换模式只需要改一个参数,不用改一堆寄存器。
typedef enum { SPI_MODE_0 = 0, SPI_MODE_1, SPI_MODE_2, SPI_MODE_3 } spi_mode_t; void SPI_SetMode(SPI_HandleTypeDef *hspi, spi_mode_t mode) { // 先禁用SPI __HAL_SPI_DISABLE(hspi); switch (mode) { case SPI_MODE_0: hspi->Init.CLKPolarity = SPI_POLARITY_LOW; hspi->Init.CLKPhase = SPI_PHASE_1EDGE; break; case SPI_MODE_1: hspi->Init.CLKPolarity = SPI_POLARITY_LOW; hspi->Init.CLKPhase = SPI_PHASE_2EDGE; break; case SPI_MODE_2: hspi->Init.CLKPolarity = SPI_POLARITY_HIGH; hspi->Init.CLKPhase = SPI_PHASE_1EDGE; break; case SPI_MODE_3: hspi->Init.CLKPolarity = SPI_POLARITY_HIGH; hspi->Init.CLKPhase = SPI_PHASE_2EDGE; break; } HAL_SPI_Init(hspi); __HAL_SPI_ENABLE(hspi); }这个函数的核心是:先禁用SPI,改配置,重新初始化,再使能。注意HAL_SPI_Init会重新配置所有SPI寄存器,所以调用前要确保其他参数(波特率、数据宽度等)已经设置好。
实操心得:动态切换模式时,如果SPI正在传输,必须先等传输完成再禁用。否则会导致当前传输的数据丢失,从机可能进入不确定状态。我一般会在切换前检查SPI的BSY标志位,确保空闲后再操作。
6.3 高速通信下模式选择的经验法则
速率超过10MHz时,模式选择就变得很关键。我的经验法则是:
- 优先选Mode 0或Mode 3,这两种模式的采样窗口最大,时序余量最充足。
- 如果从机只支持Mode 1或Mode 2,尽量把时钟降到5MHz以下,或者缩短PCB走线,减少寄生电容。
- 用DMA方式传输时,模式本身不影响DMA,但高速下DMA的响应延迟可能成为瓶颈。建议用双缓冲DMA,减少CPU干预。
- 如果波形出现振铃或过冲,加串联电阻(一般22Ω到100Ω)做阻抗匹配,能明显改善信号质量。
最后再分享一个小技巧:调试SPI时,如果手头没有示波器,可以用逻辑分析仪的低速采样模式先抓个大概。逻辑分析仪虽然采样率不如示波器,但能解码SPI协议,直接告诉你每个字节是什么,比看波形更直观。我常用的一款是Saleae Logic,配合它的SPI解码器,能快速定位是模式问题还是数据问题。