
简介这份PDF文档是NXP S12SPIV3手册的中文翻译版面向使用MCU进行同步串行通信开发的嵌入式工程师、学生与自学者主要解决SPI协议英文资料阅读门槛高、寄存器配置细节查阅不便的问题。压缩包仅含1个PDF文件大小约641KB便于随查随用。内容围绕SPIV3模块展开先概述SPI主/从模式、准模式、从选择输出、双缓冲数据寄存器、串行时钟极性与相位可编程、模式故障标识等特性再梳理运行、等待、停止三种操作模式及低功耗控制要点同时逐一解释MOSI、MISO、SS、SCK四个外部信号的方向与用途并结合方框图说明状态、控制、数据寄存器、转换逻辑、波特率发生器和主从控制逻辑。文档还给出内存映射表并对SPICR1、SPICR2、SPIBR、SPISR、SPIDR等寄存器的地址、访问权限和逐位定义进行说明。已有4810人学习适合作为SPI接口调试、寄存器配置和协议复习时的中文速查材料。1. 四根线跑全双工SPI 把可靠性押在了物理层很多人第一次看 SPI 时序图会觉得这套协议简陋得不像话没有地址帧、没有起始位、没有应答位、没有校验连从机是否在线都无从得知。可它偏偏是板级通信里最耐用的那一种。反直觉的地方在于SPI 把通常由协议层承担的责任全部下推给了电气与时钟——SS 片选决定此刻谁有权驱动 MISOSCK 的边沿决定数据在哪一刻被采样主机与从机各自一个 8 位移位寄存器串成 16 位环发送和接收是同一个移位动作的两面。理解了这一层示波器上那些看起来像玄学的错位、丢位、慢一拍就都能落到具体的位和边沿上。这份 SPIV3 中文手册的价值正在这里它把寄存器位、时序边界、低功耗模式下的例外行为一条条摊开是排障时真正能对着看的底图。2. 分布式 16 位移位寄存器一次写 SPIDR 到底发生了什么2.1 两个 8 位寄存器怎么拼成一个环SPI 全双工的本质用一句话概括发送和接收不是两个动作而是同一个移位动作的两端。主机把自己的 SPIDR8 位和从机的 SPIDR 通过 MOSI、MISO 两条线首尾相连拼成一个分布式 16 位移位寄存器。主机在 MOSI 上推出 1 位的同时从机在 MISO 上推回 1 位两边的数据在同一个 SCK 边沿上各前进一格。8 个 SCK 周期之后主机 SPIDR 里装的是从机原来写进去的字节从机 SPIDR 里装的是主机写进去的字节——一次完整的交换。这个模型解释了两件在日常开发里反复撞见的事。第一SPI 没有只发或只收。你只想发一帧命令、不关心回什么硬件依然会在 MISO 上收进 8 位通常是从机在空闲时输出的一串无效电平反过来你只想读一个传感器寄存器也必须往 MOSI 上推一个 dummy 字节因为时钟是你自己发的不推数据就没有 SCK从机也就不会把数据移出来。第二SPI 的数据长度默认是 8 位多字节传输靠软件连续写 SPIDR 完成硬件不负责组帧也不负责告诉你这是一帧的第几个字节。协议语义全部在软件里这是 SPI 灵活性的来源也是它容易被写错的原因。还有一个细节常被忽略SPICR1 里有一位 LSBFE控制先移高位还是先移低位。手册特别强调LSBFE 不改变数据在寄存器里的位置读写 SPIDR 永远是从 bit7 的 MSB 开始组织LSBFE 只影响移出移位寄存器的顺序。这一点在对接某些低位优先的外设时非常关键——如果你按低位优先就得把数据倒过来写的思路处理波形会全错。2.2 双缓冲寄存器与 SPTEF/SPIF 的握手SPIDR 在地址表里只占 0x0005 一个字节但物理上是两个寄存器共用一个地址写访问落到发送数据寄存器读访问落到接收数据寄存器。这就是所谓双缓冲。双缓冲的意义是让 CPU 在上一字节还在移位的时候就把下一字节塞进发送缓冲从而做到背靠背连续传输中间不留气泡。围绕这两个缓冲硬件提供了两个状态位状态位所在寄存器含义清除方式SPTEFSPISR bit5发送数据寄存器为空可以写入新数据读 SPISR 且 SPTEF1随后写 SPIDRSPIFSPISR bit7接收数据已复制到 SPIDR读 SPISR 且 SPIF1随后读 SPIDR这里的清除序列是硬件设计里最容易被写错的地方。手册写得很直白只有先读到 SPTEF 为 1随后对 SPIDR 的写操作才有效如果读到的 SPTEF 不是 1这次写会被直接忽略不报错、不置标志数据就这么无声无息地丢了。SPIF 同理它是一个读状态寄存器 读数据寄存器的两步序列只读数据不清标志只读状态也不清标志。下面这段轮询收发是这套握手最直白的落地/* SPI 基地址由板级头文件给出SPI_SR/SPI_DR 分别映射到 SPISR 与 SPIDR */ uint8_t spi_xfer(uint8_t tx) { /* 第一步等发送缓冲空。这一步的读操作同时是 SPTEF 清零序列的前半段 */ while ((SPI-SR SPI_SR_SPTEF) 0) { /* 若这里长时间不返回优先查 SPE 是否为 1、MSTR 是否与预期一致 */ } /* 第二步写入待发送字节。硬件会在半个 SCK 周期后启动移位 */ SPI-DR tx; /* 第三步等接收完成。SPIF1 表示 8 位已全部移入接收缓冲 */ while ((SPI-SR SPI_SR_SPIF) 0) { /* 从机模式下若 SS 被拉高这里会永远等不到务必先确认片选 */ } /* 第四步读 SPIDR取回从机数据同时完成后半段清零序列 */ return SPI-DR; }这段代码的两个while不是可有可无的保险而是协议的一部分。第一步的读 SPISR 承担了申请写权限的语义第三步的读 SPISR 承担了确认接收完成的语义。把它们省掉、改成延时等待是很多血泪经验的起点——在低速下能跑一旦总线时钟提上去丢字节的概率立刻上来。还有一个坑藏在 SPIF 的服务时机里。手册明确说明如果 SPIF 在下一次传输结束前没有被服务标志一直挂着后一次传输的数据不会被复制到 SPIDR这次传输被忽略。也就是说SPI 的接收缓冲只有一级溢出不是覆盖旧数据而是直接丢弃新数据并且不给你任何溢出标志。这一点和串口那种带 ORE 标志的外设完全不同调试时特别容易误判成从机没发。2.3 主机写数据后的半个 SCK 周期主机模式下往 SPIDR 写完数据并不会立刻看到 SCK 抖动硬件会先等半个 SCK 周期然后才启动时钟。这半个周期不是实现上的偷懒它同时解决了两件事一是给 SS 输出留出建立时间让从机在被选中的状态下迎接第一个边沿二是给 CPHA1 的时序格式留出第一个边沿用于输出首位的空间。这半个周期的存在直接导致了一个常见的测量困惑用逻辑分析仪抓波形时从写寄存器这条软件标记到第一个 SCK 边沿之间总有一段对不齐的延迟。它不是中断延迟也不是总线延迟而是硬件固有的半周期。把这一点记在脑子里可以省掉大量在软件里找为什么慢了一拍的时间。3. CPOL 与 CPHA四种时钟格式到底在哪个边沿采样3.1 奇数边沿与偶数边沿的分工SPI 有四种时序格式由 SPICR1 里的 CPOL 与 CPHA 两位组合而成。CPOL 决定空闲时 SCK 是高还是低CPHA 决定数据在奇数边沿还是偶数边沿被采样。这两个位的作用经常被说反正确的分工是CPHA0数据在 SCK 的奇数边沿第 1、3、5…15 个边沿被采样或输出在偶数边沿移位。CPHA1数据在 SCK 的偶数边沿第 2、4、6…16 个边沿被采样在奇数边沿移位。这里必须提醒一件事这份中文手册在 SPICR1 的 CPHA 位描述里有一处明显的笔误把 CPHA1 也写成了奇数边只是括号里给了 2、4、6…16 的编号。理解时应以 1.4.3.2 与 1.4.3.3 两节对时序的文字描述为准即 CPHA1 对应偶数边沿采样。这种手册笔误在实际项目中会实打实地让人多调半天看到位描述和时序描述打架时永远信时序那一节。两种格式的差异用一句更工程化的话说CPHA0 是片选一拉低首位就已经在线上CPHA1 是先来一个边沿把首位推上线第二个边沿才开始采。前者适合那些片选有效后就立刻输出首位的从机后者适合那些需要先看到时钟才肯输出数据的从机。主机和从机的 CPOL、CPHA 必须完全一致否则采到的就是相邻位的错位数据——表现为读回来的值像是整体左移或右移了一位。CPOL 本身不影响传输格式只影响空闲电平。四种组合在实际项目里常被叫作 Mode 0 到 Mode 3其中 Mode 0CPOL0CPHA0和 Mode 3CPOL1CPHA1用得最多。手册还提到一个实用点有些系统会在两次传输之间临时改变相位和极性以便同一个主机对接要求不同的多个从机。这种做法可行但有代价——见 4.3 节改这些位会中止当前传输。3.2 SS 抖动、连续传输与背靠背从机模式下SS 不只是选中我这么简单它还直接控制着移位寄存器的行为。手册里有两句话值得反复读第一SS 为高时SCK 输入被忽略不发生内部移位。也就是说从机不需要判断时钟是不是给我的片选没拉低时钟来了也不动。第二更反直觉的一条CPHA0 时如果连续传输中 SS 一直保持低电平没有断开SPIDR 里的内容不会被发送从机发送的是上一次接收到的内容。原因是此时移位寄存器的输入来自上一轮的环回结果而不是新写入的 SPIDR。要让新数据真正上线SS 必须拉高至少半个 SCK 周期的空闲时间让内部状态复位。主机侧同理SSOE 使能时SS 输出在连续传输中也要断开一个最小空闲时间。这条规则解释了一个经典的翻车现场CPHA0 的从机主机连着发两个字节第二个字节读回来是第一字节的回环值。软件上看着一切正常SPIF 也置了数据就是不对。CPHA1 没有这个限制SS 可以一直保持低电平做背靠背传输所以手册直接建议——单主单从系统优先选 CPHA1。3.3 模式故障 MODF把主机强行降级为从机SPI 只有一种错误状态模式故障。它的触发条件很朴素——SPI 配置为主机且 MODFEN 使能此时 SS 输入被拉低。SS 在主机模式下本来是输出片选别人现在变成一个低电平输入唯一合理的解释就是总线上还有另一个主机在同时驱动 MOSI 和 SCK。这是硬件层面的总线争用检测没有软件参与。检测到之后硬件的反应是立即且不可协商的MSTR 位自动清 0SPI 从主机切换成从机SCK、MOSI、MISO 三个引脚强制为高阻输入避免与另一个主机硬碰硬正在进行的传输立即中止SPI 进入空闲态。如果 SPIE 使能还会发起一次中断。清除 MODF 的序列和前面几个标志一样讲究顺序读 SPISR此时 MODF1随后写 SPICR1。清掉之后SPI 不会自动回到主机模式需要软件重新置 MSTR——这个不自动恢复的设计是刻意的避免在两个主机还在争用时反复切换。SS 引脚的角色由 MODFEN 和 SSOE 两位共同决定四种组合的行为差异很大MODFENSSOE主机模式下 SS 的行为从机模式下 SS 的行为00不用于 SPI从机选择输入01不用于 SPI从机选择输入10带 MODF 检测的输入从机选择输入11从机选择输出MODF 被禁用从机选择输入这张表藏着一个绕不过去的取舍自动片选和总线争用检测不能同时要。SSOE1 让 SS 变成输出、自动在传输期间拉低此时模式故障被禁用想要 MODF 保护就得 SSOE0把 SS 当输入用片选只能靠软件用普通 GPIO 完成。多主机系统选后者单主机多从机系统选前者没有中间路线。4. 寄存器地图与 SPIBR 分频0x0000 到 0x0007 里的小世界4.1 内存映射与访问属性SPIV3 的寄存器窗口只有 8 个字节地址偏移从 0x0000 到 0x0007实际基地址由 SoC 层面定义。窗口虽小访问属性却不统一写错方向不会报错只会静默失效偏移寄存器访问属性备注0x0000SPICR1读/写中断使能、模式、时钟格式、位序0x0001SPICR2读/写部分位不可写0x0002SPIBR读/写部分位不可写0x0003SPISR只读写入被忽略0x0004保留—读返回 0写无效0x0005SPIDR读/写读接收缓冲写发送缓冲0x0006保留—读返回 0写无效0x0007保留—读返回 0写无效保留地址读回 0 这一点在调试时有实际价值如果你读 0x0004 得到的不是 0说明基地址算错了或者总线时序有问题问题不在 SPI 模块本身。SPISR 只读这一点也值得强调——试图通过写 SPISR 去清标志是无效操作所有标志的清除都依赖读状态寄存器 读/写数据或控制寄存器这种序列硬件用访问顺序而不是写操作来表达意图。SPICR1 里真正会咬人的位有这么几个SPE系统使能清 0 会强制空闲并复位状态位、SPIESPIF 或 MODF 触发中断、SPTIESPTEF 触发中断、MSTR主从选择、CPOL/CPHA时钟格式、SSOE片选输出使能、LSBFE位序。SPICR2 里则是 MODFEN模式故障使能、BIDIROE双向模式输出使能、SPISWAI等待模式停时钟、SPC0双向引脚配置。SPC0 值得单独说。置 1 之后 SPI 进入双向模式只用一根数据线主机模式下 MOSI 变成 MOMI双向数据口MISO 不再用于 SPI从机模式下 MISO 变成 SISOMOSI 不再使用。方向由 BIDIROE 控制。这个模式在引脚紧张的封装上很实用但有两点要注意一是双向模式下同一根线既发又收外部必须有合适的驱动能力配合二是如果主机双向模式下发生 MODFSPI 会自动切成从机此时被 SPI 占用的是 MISO 而不是 MOSI如果 MISO 原本被别的功能借用就会产生冲突。手册专门为这一点写了提示说明它在实际硬件上真的撞过。4.2 波特率为什么公式长成 (SPPR1)·2^(SPR1)SPIBR 只用 6 位分成两组SPPR[2:0] 是预分频SPR[2:0] 是选择位。分频因子的公式是BaudRateDivisor (SPPR 1) * 2^(SPR 1) BaudRate BusClock / BaudRateDivisor代入验证一下SPPR000、SPR000 时分频因子 1 × 2¹ 225 MHz 总线出 12.5 MHzSPPR001、SPR000 时2 × 2 4SPPR010、SPR000 时3 × 2 6SPPR111、SPR111 时8 × 2⁸ 204825 MHz 出 12.21 kHz。这个结构的设计动机很清晰单靠 2 的幂分频能得到的频率只有 12.5M、6.25M、3.125M 这串等比数列中间全是空洞。加上一个 1 到 8 的预分频就能做出 6、10、12、14 分频这类非 2 的幂结果让波特率更贴近外设的实际上限。比如某个从机最高只支持 5 MHz纯 2 的幂只能退到 3.125 MHz浪费带宽用 6 分频能拿到 4.167 MHz更接近上限。/* 根据目标波特率反算 SPPR 与 SPR返回最接近且不超过目标值的分频配置 */ uint16_t spi_calc_divisor(uint32_t bus_clk, uint32_t target_baud) { uint16_t best_div 0; uint32_t best_err 0xFFFFFFFF; for (uint16_t sppr 0; sppr 8; sppr) { for (uint16_t spr 0; spr 8; spr) { /* 分频因子 (SPPR1) * 2^(SPR1) */ uint16_t div (sppr 1) (spr 1); uint32_t baud bus_clk / div; /* 只接受不超过目标的配置误差取绝对值最小 */ if (baud target_baud) { uint32_t err target_baud - baud; if (err best_err) { best_err err; best_div (sppr 4) | (spr 1); } } } } return best_div; /* 高 3 位是 SPPR低 3 位是 SPR可直接写入 SPIBR */ }这段反算里有个容易忽略的约束分频因子越大波特率越低但从机的建立时间要求不会因为降速而消失。降速只能解决采样窗口太窄的问题解决不了信号完整性问题。另外波特率发生器只在主机模式且传输进行时激活其余时间驱动被关闭以降低静态电流——这也意味着从机模式下 SPIBR 的配置基本没有意义时钟是从外面进来的。4.3 改配置位为什么会中止传输主机模式下改 CPOL、CPHA、SSOE、LSBFE、MODFEN、SPC0、BIDIROESPC0 置起时、SPPR、SPR 中的任何一位硬件都会立即中止当前传输并强制 SPI 进入空闲态。手册对此有一句很重的提醒远程从机无法检测到这种情况所以主机必须自己保证从机也回到空闲。这个行为的动机不难理解这些位直接改变移位的节拍和方向传输中途改掉主从两边的位计数就对不上了与其产生一段无法解释的垃圾数据不如直接中止。但后果是那些传输途中顺手改个分频的代码会在高速下随机丢帧而且丢得毫无规律看起来像玄学。正确的做法是把所有配置变更收敛到传输开始之前传输过程中只碰 SPIDR 和状态位。如果确实需要在运行中换配置比如同一条总线上挂要求不同的从机必须显式地把 SS 拉高、等待足够长的空闲时间、改配置、再重新拉低片选。这段关总线—改配置—开总线的序列最好封装成一个函数别散落在业务代码里。5. 避坑SPI 调试台上最常见的五个翻车现场5.1 时序类的三个坑坑一从机永远返回 0xFF 或 0x00。现象是主机这边 SPIF 正常置位、时序也跑完了读回来的值恒定为全 1 或全 0。原因通常有三层一是 CPHA 配错主机在从机还没把首位推上线的时候就去采样采到的全是空闲电平二是 SS 压根没拉低从机按规则忽略 SCK移位寄存器不动MISO 保持高阻被上拉拉成全 1三是从机的 SPE 没置位引脚还没切给 SPI 功能。排查顺序建议从 SS 开始用示波器确认片选真的到了从机引脚再对 CPHA最后查从机的使能位。全 0 和全 1 的差别也有信息量全 1 更像上拉全 0 更像被主动驱动为低。坑二连续传输时数据慢一拍。现象是连发两个字节第二帧读回来的却是第一帧的回环值。根因就是 3.2 节那条规则——CPHA0 且 SS 一直保持低电平从机不会发送 SPIDR 里的新内容而是重复上一轮收到的数据。解决办法有两条要么在两次传输之间把 SS 拉高至少半个 SCK 周期要么改用 CPHA1 做背靠背传输。前者改动小后者效率高看外设支持哪种。坑三主机莫名变成从机MSTR 自己变 0。现象是跑着跑着 SPI 不工作了读 SPICR1 发现 MSTR 被清 0SPISR 里 MODF 置位。原因是 SS 引脚在主机模式下被拉低触发了模式故障保护。常见诱因是 SSOE0 却把 SS 配置成了带 MODF 检测的输入而这根线在板子上悬空或者被别的器件拉低也可能是多主机系统里真的发生了总线争用。处理方式是先判断这是误触发还是真故障单主机系统里基本是误触发把 MODFEN 清 0 或者改用 SSOE1 的自动片选多主机系统里就是真故障需要按 3.3 节的序列清标志并重新仲裁。5.2 寄存器握手类的两个坑坑四写 SPIDR 没反应SPTEF 一直不清。现象是调用了发送函数波形上什么都没有SPTEF 读回来一直是 0。原因是没有遵守先读 SPISR 且 SPTEF1再写 SPIDR的顺序。有些代码为了省事直接写 SPIDR 然后轮询硬件会把这次写静默丢弃。还有一种变体是中断里写、主循环里也写两处竞争同一个发送缓冲其中一次的读-写序列被另一次打断同样会丢。解决办法是把发送动作收口到一个函数里进出临界区别在多个上下文里同时碰 SPIDR。坑五低功耗唤醒后第一个字节是旧数据。现象是系统从等待或停止模式醒来读到的第一个字节是进入低功耗之前的老数据或者干脆没有产生 SPIF。原因在 1.4.8 节的注意事项里写得很细从机在等待模式下移位寄存器可能还在跟着主机的 SCK 走但移位寄存器的内容要等退出等待或停止模式之后才会复制到 SPIDRSPIF 也是那时候才产生。更微妙的是只有传输期间进入或退出等待模式才会触发这次复制如果从机是从空闲态进入等待、再从等待回到空闲既不产生 SPIF 也不复制 SPIDR。所以唤醒后第一件事不是读数据而是先确认当前是否处在一个字节的传输中间。6. 低功耗与中断的取舍从机为什么在等待模式还要多收一个字节等待模式下的 SPI 行为是这份手册里最容易被跳读、又最容易在真实项目里出事的一段。SPISWAI 置 1 后主机进入等待模式会停掉 SCK传输冻结退出后继续但从机的行为不一样——它会继续接收和发送一个字节然后才停下来。这个多收一个字节看着多余其实是 SPI 从机架构决定的无选择。从机没有独立时基它完全是被动器件靠数 SCK 边沿来确定当前移到了第几位。如果在一个字节的中途把时钟掐掉等系统醒来位计数和主机那边就对不上了整个帧的字节边界全废。硬件能做的只有一件事把当前这个字节走完让移位寄存器的边界重新对齐到字节。代价是功耗略高一点以及一个更隐蔽的后果——SPIF 不会立刻产生移位寄存器的内容要等退出等待或停止模式之后才复制进 SPIDR。如果软件在唤醒后立刻读 SPIDR读到的可能是上一轮的残值。第二个取舍在中断结构上。MODF、SPIF、SPTEF 三个标志是逻辑或的关系共同产生一个中断请求。这省下了一个中断向量代价是中断服务程序必须自己读 SPISR 分辨是哪一位触发的而且要小心处理清除一个标志时顺带影响了另一个的情况。相比之下如果三个标志各有独立向量代码会更直白但向量表和优先级仲裁的硬件开销就上去了。这个模块选择了省硬件、把复杂度交给软件属于典型的嵌入式取舍。验证这套行为最靠得住的办法不是打印日志而是用一根空闲 GPIO 在进出低功耗的前后翻转电平再用逻辑分析仪同时抓 SCK、SS 和这根标记线。这样能直接看到停在了第几个边沿醒来后第一个字节是从哪来的比在代码里猜快得多。我自己的教训是早期做一个低功耗采集节点从机在等待模式醒来后总是丢第一帧查了两天才发现问题不在驱动而在于唤醒后立刻读 SPIDR 的那个瞬间数据还没从移位寄存器复制过来。从那以后我每次写低功耗相关的 SPI 代码都强制在唤醒后先轮询一次 SPISR 确认 SPIF 的真实状态再动数据寄存器——这一步看着冗余却能省掉大量在深夜里对着波形图怀疑人生的时间。希望帮到你。本文还有配套的精品资源点击获取