SPI 模式错收发错位:CPOL/CPHA 与从设备相反,对照 datasheet 时序图对齐
一、开篇:一个真实翻车现场
用 SPI 读一片 ADC(STM32F4 主,ADC 从),发命令、收数据,结果读出来的值整体错位一位,或者干脆全 0xFF / 全 0x00。我确认过接线、CS 时序、字节序都对,折腾半天才发现:SPI 的时钟极性/相位(CPOL/CPHA)和 ADC datasheet 要求的不一致——主机在错误的边沿采样,数据整体错一位。
把 SPI_InitStruct.CLKPhase 和 CLKPolarity 按 datasheet 时序图改对后,数据立刻正常。这是 SPI 最经典也最容易被忽略的坑。
根因一句话:SPI 有 4 种模式(由 CPOL 时钟极性、CPHA 时钟相位组合),决定"空闲时时钟电平"和"在时钟的哪个边沿采样数据"。主机和从机模式必须一致,否则采样边沿错一位,收到的数据整帧错位/反相。
适用读者:SPI 通信读出乱数据、全 0xFF/0x00、错位一位的同学。
读完你能做:对照从机 datasheet 时序图,确定 CPOL/CPHA,把主机 SPI 模式配一致,根治错位。
二、先搞懂:CPOL/CPHA 到底定义什么(认知)
2.1 两个参数
- CPOL(时钟极性):0=空闲时 SCK 低;1=空闲时 SCK 高。
- CPHA(时钟相位):0=在 SCK 第一个边沿(CPOL 决定的"起始边沿")采样数据;1=在 SCK 第二个边沿采样。
组合出 4 种模式(0~3)。主从必须同一模式,否则采样边沿差半个时钟,数据错一位。
2.2 为什么错位
假设从机在"上升沿锁存、下降沿输出",要求模式 (CPOL=0, CPHA=1)(第二个边沿=下降沿采样,从机在上升沿改变数据)。若主机配成模式 0(第一个边沿=上升沿采样),它会在从机"刚改变数据"的瞬间去采样 → 采到的是上一比特的尾巴 → 整帧错位。
| 0 | 0 | 0 | 上升沿(第一个) |
| 1 | 0 | 1 | 下降沿(第二个) |
| 2 | 1 | 0 | 下降沿(第一个) |
| 3 | 1 | 1 | 上升沿(第二个) |
#mermaid-svg-X5Pagf30ZqCbIb3C{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-X5Pagf30ZqCbIb3C .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-X5Pagf30ZqCbIb3C .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-X5Pagf30ZqCbIb3C .error-icon{fill:#552222;}#mermaid-svg-X5Pagf30ZqCbIb3C .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-X5Pagf30ZqCbIb3C .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-X5Pagf30ZqCbIb3C .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-X5Pagf30ZqCbIb3C .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-X5Pagf30ZqCbIb3C .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-X5Pagf30ZqCbIb3C .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-X5Pagf30ZqCbIb3C .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-X5Pagf30ZqCbIb3C .marker{fill:#333333;stroke:#333333;}#mermaid-svg-X5Pagf30ZqCbIb3C .marker.cross{stroke:#333333;}#mermaid-svg-X5Pagf30ZqCbIb3C svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-X5Pagf30ZqCbIb3C p{margin:0;}#mermaid-svg-X5Pagf30ZqCbIb3C .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-X5Pagf30ZqCbIb3C .cluster-label text{fill:#333;}#mermaid-svg-X5Pagf30ZqCbIb3C .cluster-label span{color:#333;}#mermaid-svg-X5Pagf30ZqCbIb3C .cluster-label span p{background-color:transparent;}#mermaid-svg-X5Pagf30ZqCbIb3C .label text,#mermaid-svg-X5Pagf30ZqCbIb3C span{fill:#333;color:#333;}#mermaid-svg-X5Pagf30ZqCbIb3C .node rect,#mermaid-svg-X5Pagf30ZqCbIb3C .node circle,#mermaid-svg-X5Pagf30ZqCbIb3C .node ellipse,#mermaid-svg-X5Pagf30ZqCbIb3C .node polygon,#mermaid-svg-X5Pagf30ZqCbIb3C .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-X5Pagf30ZqCbIb3C .rough-node .label text,#mermaid-svg-X5Pagf30ZqCbIb3C .node .label text,#mermaid-svg-X5Pagf30ZqCbIb3C .image-shape .label,#mermaid-svg-X5Pagf30ZqCbIb3C .icon-shape .label{text-anchor:middle;}#mermaid-svg-X5Pagf30ZqCbIb3C .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-X5Pagf30ZqCbIb3C .rough-node .label,#mermaid-svg-X5Pagf30ZqCbIb3C .node .label,#mermaid-svg-X5Pagf30ZqCbIb3C .image-shape .label,#mermaid-svg-X5Pagf30ZqCbIb3C .icon-shape .label{text-align:center;}#mermaid-svg-X5Pagf30ZqCbIb3C .node.clickable{cursor:pointer;}#mermaid-svg-X5Pagf30ZqCbIb3C .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-X5Pagf30ZqCbIb3C .arrowheadPath{fill:#333333;}#mermaid-svg-X5Pagf30ZqCbIb3C .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-X5Pagf30ZqCbIb3C .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-X5Pagf30ZqCbIb3C .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-X5Pagf30ZqCbIb3C .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-X5Pagf30ZqCbIb3C .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-X5Pagf30ZqCbIb3C .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-X5Pagf30ZqCbIb3C .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-X5Pagf30ZqCbIb3C .cluster text{fill:#333;}#mermaid-svg-X5Pagf30ZqCbIb3C .cluster span{color:#333;}#mermaid-svg-X5Pagf30ZqCbIb3C div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-X5Pagf30ZqCbIb3C .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-X5Pagf30ZqCbIb3C rect.text{fill:none;stroke-width:0;}#mermaid-svg-X5Pagf30ZqCbIb3C .icon-shape,#mermaid-svg-X5Pagf30ZqCbIb3C .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-X5Pagf30ZqCbIb3C .icon-shape p,#mermaid-svg-X5Pagf30ZqCbIb3C .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-X5Pagf30ZqCbIb3C .icon-shape rect,#mermaid-svg-X5Pagf30ZqCbIb3C .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-X5Pagf30ZqCbIb3C .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-X5Pagf30ZqCbIb3C .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-X5Pagf30ZqCbIb3C :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
如 CPOL0 CPHA1
读datasheet时序图
从机模式?
主机配Mode1
采样边沿一致 数据正确
主机配错模式
采样错位一位 乱数据
图 1:SPI 模式对齐流程(如图 1 所示,配错即错位)。
[注意] 很多 datasheet 不直接写"Mode 1",而是画时序图:看"空闲 SCK 电平"(定 CPOL)+“数据在时钟哪个边沿稳定/变化”(定 CPHA)。要学会从图读,而非盲猜。
三、为什么会错位(原理 + 真实错误代码)
3.1 我最初的写法(错误版)
// ❌ 错误写法:随手配了 Mode0,没对照从机 datasheet
hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; // CPOL=0
hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; // CPHA=0 → Mode0
// 实际从机 ADC 要求 Mode3(CPOL=1,CPHA=1),采样边沿全错
错在哪:
[坑] 铁律:SPI 主从模式必须一致。配之前先读从机 datasheet 时序图定 CPOL/CPHA,别用"默认 Mode0"盲配。
3.2 为什么"有时看起来对"
某些从机对模式容错高(如纯移位寄存器类),或数据本身多位相同,错位一位不易察觉;一旦数据变化频繁/边界位重要,立刻暴露。也有的从机内部做了双边沿采样兜底,掩盖了问题——但别依赖运气。
| 主从模式一致 | 正确 |
| 主从模式差一位 | 数据错位 |
四、正确解法:对照 datasheet 配模式(完整落地)
4.1 方案对比
| 盲配 Mode0 | 易错 |
| 读时序图定 CPOL/CPHA | 正解 |
4.2 配置(照着点)
CubeMX:SPI → Configuration → Parameter Settings 里 CPOL / CPHA 按从机手册选。或代码里 hspi1.Init.CLKPolarity/CLKPhase 设对后 HAL_SPI_Init。
4.3 对照方法(非代码,关键步骤)
[坑] 两个翻车点收好:
4.4 改完的实测对比
| 读出数据 | 错位/全 FF | 正确 |
| 抗变速 | 随速率变 | 稳定 |
| 需重配 | 反复试 | 一次对照定 |
五、收尾
要点复盘
最佳实践清单
- SPI 接线后第一件事:对照从机时序图定 CPOL/CPHA。
- CPOL 看空闲 SCK 电平,CPHA 看采样是第一个还是第二个边沿。
- 主从模式不一致 → 数据错位一位,典型表现全 0xFF/0x00/乱值。
- 多个不同从机时,每次切换 CS 前重配 SPI 模式(或选兼容模式的总线)。
- 示波器抓 SCK+MISO,看采样边沿数据是否稳定,一眼验证模式对错。
进阶延伸:有些器件支持"任意模式"(自同步),但多数严格;SPI 还有 MSB/LSB 先行(帧格式)也需和从机一致,别忘了查。
互动:你 SPI 还踩过啥?MSB/LSB 反了、Word Size 不对、片选极性反?评论区交流。
网硕互联帮助中心





评论前必须登录!
注册