1. 项目概述与核心价值在嵌入式系统开发中串行外设接口SPI几乎是工程师的“瑞士军刀”。无论是驱动一块TFT屏幕、读取一个高精度ADC的数据还是与外部Flash存储器进行大数据量交换SPI都以其简单、高效、全双工的特性成为首选。然而在实际项目中我们常常面临两个看似矛盾的需求一是极致的速度以满足实时控制或高速数据采集二是极致的精简以在引脚资源紧张的MCU上连接更多外设。这正是SPI高速模式和三线模式所要解决的核心问题。很多开发者对SPI的基本四线模式SPICLK, SPISIMO, SPISOMI, SPISTE已经驾轻就熟但一旦涉及到需要将通信速率推到芯片极限或者需要将四个引脚节省为一个时往往就会遇到手册读不懂、配置后通信异常、数据收发混乱等问题。其根本原因在于这两种模式并非简单的“开关”它们改变了SPI模块内部数据路径和引脚的行为逻辑如果对底层机制理解不透彻仅靠复制粘贴代码很难成功。本文将以TI C2000系列微控制器特别是TMS320F28004x的SPI模块为蓝本结合我十多年在电机控制、数字电源等高速实时系统开发中的踩坑经验为你彻底拆解SPI高速模式与三线模式的配置奥秘。我不会只给你寄存器列表和代码片段而是会带你深入每个配置位背后的设计意图解释为什么在特定模式下需要特定的操作序列并分享那些数据手册里不会写的调试技巧和避坑指南。无论你是正在优化现有SPI通信带宽还是在为新产品设计寻找节省引脚的方案这篇文章都能提供从原理到实战的完整参考。2. SPI高速模式深度解析与极限速率实现2.1 高速模式的本质与硬件约束SPI高速模式High-Speed Mode的核心目标非常直接突破常规模式下的时钟速率限制实现接近外设低速时钟LSPCLK四分之一的理论最高速率。在C2000的SPI模块中常规模式下SPICLK的最大频率为LSPCLK/SPIBRR1当SPIBRR设置为最小值0、1或2时公式退化为LSPCLK/4。而高速模式通过设置HS_MODE位允许SPI模块在更苛刻的时序要求下稳定工作在这个极限速率上。为什么需要专门开启一个“高速模式”这并非软件魔法而是与芯片内部的物理设计紧密相关。开启HS_MODE后GPIO模块会为指定的SPI引脚选择一组专用的、驱动能力更强、翻转速度更快的复用功能引脚。你可以把它想象成普通公路和高速公路的区别。普通GPIO引脚就像城市道路有红绿灯和限速而高速模式下的专用引脚就像是封闭的高速公路专为高速数据流设计减少了信号路径上的延迟和失真。因此实现高速模式的第一步永远不是直接配置SPI寄存器而是正确配置GPIO。以TMS320F28004x为例其GPIO复用控制寄存器GPxGMUX/GPxMUX为每个引脚提供了多种功能选择。你必须查阅具体芯片的数据手册和引脚复用表找到标有“HS”或明确支持高速SPI的复用选项。一个常见的错误是仅配置了普通的SPI功能而忽略了高速复用选项导致即使软件设置了HS_MODE物理引脚也无法支持目标速率通信必然失败。注意高速模式对PCB布局和负载电容极为敏感。数据手册中“Loading on the pins must not exceed the value stated in the device data sheet”这句话至关重要。它意味着连接到SPI引脚上的总容性负载包括走线、连接器、外设输入电容必须低于规定值通常是几个皮法。过大的负载会导致信号边沿变缓眼图闭合在高速下产生误码。在layout时应尽量缩短SPI走线避免过孔并确保参考地平面完整。2.2 高速模式配置流程与实战代码理解了硬件前提后我们来看具体的软件配置步骤。假设系统主频SYSCLK为100MHz且低速外设时钟预分频器LOSPCP配置为不分频即LSPCLK SYSCLK 100MHz我们的目标是配置SPI-A模块工作在25MHzLSPCLK/4的全双工高速模式。第一步配置GPIO为高速模式引脚。这是最容易被忽略但决定成败的一步。你需要操作的是GPIO的复用控制寄存器。// 假设SPIA的引脚为GPIO16 (SPISIMOA), GPIO17 (SPISOMIA), GPIO18 (SPICLKA), GPIO19 (SPISTEA) // 首先将引脚功能选择为SPI的“高速”复用选项这通常在GPxGMUX1寄存器中 // 具体位域需参考芯片的TRM这里以典型情况为例 EALLOW; // 解除寄存器保护 // 配置GPIO16和GPIO17为SPI-A高速模式下的主出从入和主入从出 GpioCtrlRegs.GPAGMUX1.bit.GPIO16 1; // 选择高速SPISIMOA功能 GpioCtrlRegs.GPAGMUX1.bit.GPIO17 1; // 选择高速SPISOMIA功能 // 配置GPIO18和GPIO19为SPI-A时钟和片选 GpioCtrlRegs.GPAGMUX1.bit.GPIO18 1; // 选择高速SPICLKA功能 GpioCtrlRegs.GPAGMUX1.bit.GPIO19 1; // 选择高速SPISTEA功能 EDIS; // 恢复寄存器保护第二步遵循标准的SPI配置流程并在其中启用高速模式。配置SPI时务必先将其置于软件复位状态SPISWRESET 0完成所有配置后再释放复位SPISWRESET 1。这是一个重要的安全操作可以避免在配置过程中产生毛刺或不可预知的通信。void Spia_HighSpeed_Master_Init(void) { // 1. 将SPI模块置于软件复位状态此时所有状态标志被清除配置可安全更改 SpiaRegs.SPICCR.bit.SPISWRESET 0; // 2. 配置SPI为控制器主模式 SpiaRegs.SPICTL.bit.MASTER_SLAVE 1; // 1 Master // 3. 配置时钟极性和相位这里以模式0为例 (CPOL0, CPHA0) // 数据在SCK上升沿采样下降沿输出。空闲时SCK为低电平。 SpiaRegs.SPICCR.bit.CLKPOLARITY 0; SpiaRegs.SPICTL.bit.CLK_PHASE 0; // 4. 关键步骤启用高速模式 SpiaRegs.SPICCR.bit.HS_MODE 1; // 5. 设置波特率。目标为LSPCLK/4 100MHz/4 25MHz。 // 根据公式当SPIBRR 3 时SPICLK LSPCLK/(31) 25MHz // 注意在高速模式下也必须遵循此公式。手册中“up to LSPCLK/4”指的是理论极限 // 实际设置仍通过SPIBRR配置但硬件会启用对应的快速驱动电路。 SpiaRegs.SPIBRR 3; // 6. 设置字符长度例如16位 SpiaRegs.SPICCR.bit.SPICHAR 0xF; // 16-bit word length (151) // 7. 使能发送通道TALK1如果需要中断也可在此处使能 SpiaRegs.SPICTL.bit.TALK 1; // Enable transmission // SpiaRegs.SPICTL.bit.SPIINTENA 1; // 如需中断则使能 // 8. 清除可能存在的状态标志良好的编程习惯 SpiaRegs.SPISTS.bit.OVERRUN_FLAG 1; // 写1清除超限标志 // INT_FLAG会在读取SPIRXBUF时自动清除 // 9. 释放SPI模块的软件复位使其开始工作 SpiaRegs.SPICCR.bit.SPISWRESET 1; }第三步数据传输与注意事项。高速模式下的数据传输代码与普通模式无异因为速度的提升完全由硬件实现。但正因为速度快对软件时序的要求更严苛。Uint16 Spia_Master_TransmitHighSpeed(Uint16 data) { Uint16 receivedData; // 等待发送缓冲区为空非FIFO模式下的典型做法 // 在高速连续传输时建议使用FIFO或DMA而非轮询此标志以避免CPU瓶颈。 while(SpiaRegs.SPISTS.bit.BUFFULL_FLAG 1) { // 等待直到发送缓冲区可接受新数据 } // 写入数据启动传输 SpiaRegs.SPITXBUF data; // 等待接收完成INT_FLAG置位 while(SpiaRegs.SPISTS.bit.INT_FLAG ! 1) { // 等待传输完成 } // 读取接收到的数据 receivedData SpiaRegs.SPIRXBUF; return receivedData; }实操心得在25MHz甚至更高的速率下使用轮询INT_FLAG的方式会大量消耗CPU资源可能无法处理连续的数据流。强烈建议在高速模式下启用SPI的FIFO先入先出缓冲区功能并配合中断或DMA直接存储器访问进行数据传输。FIFO可以缓存最多16个16位数据允许CPU批量写入或读取极大地减轻了中断负担。DMA则可以在无需CPU干预的情况下在外设和内存之间搬运数据是实现超高速、大数据量SPI通信的终极方案。配置FIFO和DMA的细节我们会在后续章节详细讨论。2.3 高速模式下的常见问题与调试技巧即使配置完全正确高速SPI通信仍可能出问题。以下是我在实际项目中总结的几个排查要点时钟信号质量差这是高速通信的头号杀手。务必使用示波器测量SPICLK信号。观察其上升/下降时间是否陡峭波形是否干净过冲、振铃要小频率是否准确。如果波形畸变检查驱动强度设置如果MCU支持、串联匹配电阻是否合适通常在22-33欧姆并检查PCB走线是否过长或靠近噪声源。数据建立和保持时间不满足SPI通信要求数据在时钟边沿前后有一段稳定的时间建立时间和保持时间。在高速下这个时间窗口非常窄。如果外设芯片的数据手册要求建立时间t_SU为5ns而你的MCU在时钟边沿前3ns才切换数据线就会导致采样错误。解决方法a) 尝试调整SPI的时钟相位CLK_PHASE位这可以改变数据输出和采样相对于时钟边沿的位置。b) 降低通信速率。c) 选择时序更宽松的外设。软件延迟导致从设备响应超时某些SPI从设备如Flash存储器在接收到命令后需要一段时间进行内部操作如擦除、编程在此期间它会忽略时钟。主设备必须等待这段时间。在低速时简单的软件延时DELAY_US()可能就够用。但在高速系统中这种忙等待极其浪费。更好的做法是利用SPI模块的SPISTE片选信号。在发送命令后拉高SPISTE取消选中执行其他任务然后拉低SPISTE再继续通信。或者查询从设备的状态寄存器如果支持。电源噪声影响高速数字信号切换会产生瞬间的大电流如果电源去耦不足会在电源平面上产生噪声影响SPI模块甚至整个系统的稳定性。务必在靠近MCU和高速外设的电源引脚处放置足够且合适容值的去耦电容例如一个10uF的钽电容加一个0.1uF的陶瓷电容。3. SPI三线模式原理与节省引脚的奥秘3.1 三线模式的诞生与工作原理标准的SPI需要四根线时钟SPICLK、主出从入SPISIMO、主入从出SPISOMI和片选SPISTE可选。当你的MCU需要连接多个SPI设备或者引脚资源已经捉襟见肘时减少一根数据线意味着可以多控制一个外设或者选用更小封装的MCU这对成本敏感型产品至关重要。SPI三线模式3-Wire Mode的精髓就是将独立的SPISIMO和SPISOMI两根单向数据线合并为一根双向数据线。在主机端这根线叫做SPIMOMIMaster Out, Master In在从机端叫做SPISISOSlave In, Slave Out。模式的切换通过设置SPIPRI寄存器中的TRIWIRE位为1来实现。其内部原理图可以简单理解为在四线模式下SPI模块内部有一个发送移位寄存器和一个接收移位寄存器它们分别连接到SPISIMO和SPISOMI引脚。而在三线模式下模块内部通过一个模拟开关将发送路径和接收路径在芯片内部短接了起来。这意味着主机发送的数据会立刻被主机自己收到同样从机发送的数据也会被从机自己收到。这是一个非常重要的特性也是三线模式编程逻辑与四线模式根本不同的原因。引脚功能的变化如下表所示引脚名称四线模式三线模式 (主机)三线模式 (从机)SPICLK时钟时钟时钟SPISTE片选片选片选SPISIMO主机输出SPIMOMI (双向)未连接(自由)SPISOMI主机输入未连接(自由)SPISISO (双向)3.2 TALK位的关键作用与数据传输流程三线模式之所以需要特殊的编程顺序核心就在于SPICTL寄存器中的TALK位。这个位控制着发送输出驱动器的使能。在四线模式下TALK通常一直置1使能发送因为收发路径是独立的。但在三线模式下这根双向数据线在同一时刻只能有一个方向的数据流。主机发送流程设置TALK 1打开主机的发送驱动器将SPIMOMI引脚配置为输出。写入数据到SPITXBUF主机开始发送数据。由于内部收发短接主机也会同时接收到自己发送的数据。传输完成后必须执行一次“虚读”读取SPIRXBUF将接收到的“垃圾数据”即自己发送的数据清除掉否则会占用缓冲区影响下一次接收。主机接收流程这是与四线模式差异最大的地方设置TALK 0关闭主机的发送驱动器。此时SPIMOMI引脚变为高阻态输入模式从机可以驱动这根线。写入一个“哑元数据”Dummy Data到SPITXBUF。这个操作会启动SPI时钟生成。因为TALK0哑元数据并不会被实际驱动到SPIMOMI引脚上。时钟信号触发从机输出数据。主机在SPIMOMI引脚上采样从机发送的数据。传输完成后读取SPIRXBUF得到的就是从机发送的真实数据。从机的逻辑与主机对称。关键在于理解在三线模式下任何一次SPI时钟的生成都必然伴随着一次“发送”操作写入SPITXBUF但数据是否真的出现在物理引脚上取决于TALK位的状态。3.3 三线模式配置与代码实战下面我们分别给出主机和从机在三线模式下的初始化及收发代码示例。主机端初始化与收发函数void Spia_3Wire_Master_Init(void) { EALLOW; // 配置GPIO: SPICLKA, SPISTEA, SPISIMOA 为SPI功能 // SPISOMIA可以配置为普通GPIO或其他功能因为它不再被SPI使用 GpioCtrlRegs.GPAGMUX1.bit.GPIO16 1; // SPISIMOA GpioCtrlRegs.GPAGMUX1.bit.GPIO18 1; // SPICLKA GpioCtrlRegs.GPAGMUX1.bit.GPIO19 1; // SPISTEA // GPIO17 (SPISOMIA) 可以不配置为SPI功能 EDIS; SpiaRegs.SPICCR.bit.SPISWRESET 0; // 进入软件复位 SpiaRegs.SPICTL.bit.MASTER_SLAVE 1; // 主机模式 SpiaRegs.SPICCR.bit.CLKPOLARITY 0; // 时钟极性0 SpiaRegs.SPICTL.bit.CLK_PHASE 0; // 时钟相位0 SpiaRegs.SPIBRR 9; // 设置波特率例如 LSPCLK/10 SpiaRegs.SPICCR.bit.SPICHAR 7; // 8位数据 (71) // !!! 启用三线模式 !!! SpiaRegs.SPIPRI.bit.TRIWIRE 1; SpiaRegs.SPICTL.bit.TALK 1; // 默认使能发送便于启动通信 SpiaRegs.SPICCR.bit.SPISWRESET 1; // 退出复位 } // 三线模式主机发送函数 Uint16 Spia_3Wire_Master_Transmit(Uint16 txData) { Uint16 dummy; // 确保TALK位为1使能发送驱动器 SpiaRegs.SPICTL.bit.TALK 1; SpiaRegs.SPITXBUF txData; // 启动发送 while(SpiaRegs.SPISTS.bit.INT_FLAG ! 1) {} // 等待传输完成 dummy SpiaRegs.SPIRXBUF; // !!! 必须虚读清除自己发出的数据 !!! return dummy; // 通常忽略返回值因为它是自己发出的数据 } // 三线模式主机接收函数 Uint16 Spia_3Wire_Master_Receive(void) { Uint16 rxData; // 关键将TALK位清零关闭发送驱动器让从机控制数据线 SpiaRegs.SPICTL.bit.TALK 0; // 写入哑元数据以生成时钟但数据不会输出到引脚 SpiaRegs.SPITXBUF 0xFFFF; // 哑元数据可以是任意值 while(SpiaRegs.SPISTS.bit.INT_FLAG ! 1) {} // 等待从机数据接收完成 rxData SpiaRegs.SPIRXBUF; // 读取从机发来的真实数据 // 接收完成后可以将TALK置回1为下次发送做准备 SpiaRegs.SPICTL.bit.TALK 1; return rxData; }从机端初始化与收发函数假设为SPI-B模块void Spib_3Wire_Slave_Init(void) { EALLOW; // 配置GPIO: SPICLKB, SPISTEB, SPISOMIB 为SPI功能 // SPISIMOB可以配置为普通GPIO GpioCtrlRegs.GPAGMUX1.bit.GPIO17 2; // 假设GPIO17是SPISOMIB GpioCtrlRegs.GPAGMUX1.bit.GPIO20 2; // 假设GPIO20是SPICLKB GpioCtrlRegs.GPAGMUX1.bit.GPIO21 2; // 假设GPIO21是SPISTEB EDIS; SpibRegs.SPICCR.bit.SPISWRESET 0; SpibRegs.SPICTL.bit.MASTER_SLAVE 0; // 从机模式 SpibRegs.SPICCR.bit.CLKPOLARITY 0; // 必须与主机匹配 SpibRegs.SPICTL.bit.CLK_PHASE 0; // 必须与主机匹配 SpibRegs.SPICCR.bit.SPICHAR 7; // 字符长度匹配主机 // !!! 启用三线模式 !!! SpibRegs.SPIPRI.bit.TRIWIRE 1; // 从机通常先处于接收状态所以TALK先设为0 SpibRegs.SPICTL.bit.TALK 0; SpibRegs.SPICCR.bit.SPISWRESET 1; } // 三线模式从机发送函数当需要回复主机时 void Spib_3Wire_Slave_Transmit(Uint16 txData) { Uint16 dummy; // 从机要发送需使能TALK SpibRegs.SPICTL.bit.TALK 1; SpibRegs.SPITXBUF txData; while(SpibRegs.SPISTS.bit.INT_FLAG ! 1) {} dummy SpibRegs.SPIRXBUF; // 虚读清除自己发出的数据 // 发送完毕通常切回接收状态 SpibRegs.SPICTL.bit.TALK 0; } // 三线模式从机接收函数常态 Uint16 Spib_3Wire_Slave_Receive(void) { Uint16 rxData; // 确保TALK为0处于接收状态 // 从机接收无需主动操作等待主机时钟即可 while(SpibRegs.SPISTS.bit.INT_FLAG ! 1) {} rxData SpibRegs.SPIRXBUF; return rxData; }避坑指南三线模式调试中最常见的问题是数据收发混乱根本原因往往是TALK位状态管理错误。一个经典的错误是主机在接收流程中忘记将TALK清零导致主机仍然驱动着数据线与试图发送数据的从机发生“总线冲突”表现为波形异常或数据全为0xFF/0x00。务必使用逻辑分析仪或示波器同时捕获SPICLK和双向数据线SPIMOMI/SPISISO的波形观察在主机发送、主机接收、从机发送、从机接收四个阶段数据线的驱动源是否正确切换。另外确保主从双方的时钟极性CLKPOLARITY和相位CLK_PHASE设置完全一致这是所有SPI通信的基础。4. C2000 SPI高级功能与实战应用4.1 FIFO与中断机制解放CPU的利器无论是高速模式还是三线模式当数据流量增大时频繁的CPU轮询或中断响应都会成为系统瓶颈。C2000的SPI模块内置了16级深度的硬件FIFO可以显著优化数据传输效率。FIFO的核心思想是批处理。发送时CPU可以一次性向发送FIFO写入最多16个数据SPI模块会按顺序自动发送接收时SPI模块将收到的数据存入接收FIFO攒够一定数量可设置再通知CPU一次性读取。这减少了中断触发次数让CPU有更多时间处理其他任务。配置SPI FIFO主要涉及三个寄存器SPIFFTX发送FIFO控制、SPIFFRX接收FIFO控制和SPIFFCTFIFO控制。void Spia_FIFO_Init(void) { // ... 先完成基本的SPI配置模式、波特率等... // 1. 在软件复位状态下配置FIFO SpiaRegs.SPICCR.bit.SPISWRESET 0; // 2. 使能FIFO增强功能 SpiaRegs.SPIFFTX.bit.SPIFFENA 1; // 3. 复位TX FIFO和RX FIFO然后释放复位 SpiaRegs.SPIFFTX.bit.TXFIFO 0; // 复位TX FIFO SpiaRegs.SPIFFRX.bit.RXFIFORESET 0; // 复位RX FIFO // 稍作延时 DELAY_US(1); SpiaRegs.SPIFFTX.bit.TXFIFO 1; // 释放TX FIFO复位 SpiaRegs.SPIFFRX.bit.RXFIFORESET 1; // 释放RX FIFO复位 // 4. 设置FIFO中断触发水平 // 例如当发送FIFO中剩余字数 0 (即空) 时产生中断请求CPU填充数据 SpiaRegs.SPIFFTX.bit.TXFFIL 0; // 例如当接收FIFO中字数 8 时产生中断通知CPU读取一批数据 SpiaRegs.SPIFFRX.bit.RXFFIL 8; // 5. 使能FIFO中断如果需要 SpiaRegs.SPIFFTX.bit.TXFFIENA 1; // 使能TX FIFO中断 SpiaRegs.SPIFFRX.bit.RXFFIENA 1; // 使能RX FIFO中断 // 6. 清除可能存在的FIFO中断标志 SpiaRegs.SPIFFTX.bit.TXFFINTCLR 1; SpiaRegs.SPIFFRX.bit.RXFFINTCLR 1; SpiaRegs.SPIFFRX.bit.RXFFOVFCLR 1; // 清除溢出标志 // 7. 释放SPI软件复位 SpiaRegs.SPICCR.bit.SPISWRESET 1; }在FIFO中断服务程序中你的操作将变得非常高效__interrupt void spiTxFifoIsr(void) { Uint16 i; // 检查是否是TX FIFO中断 if(SpiaRegs.SPIFFTX.bit.TXFFINT 1) { // TX FIFO空了需要填充数据 for(i 0; i 16; i) // 一次性填满FIFO { SpiaRegs.SPITXBUF yourTxDataBuffer[txIndex]; } SpiaRegs.SPIFFTX.bit.TXFFINTCLR 1; // 清除中断标志 } // ... 处理其他中断标志 ... PieCtrlRegs.PIEACK.all PIEACK_GROUP6; // 应答PIE中断 }SPIFFCT寄存器中的TXDLY位用于控制FIFO中的数据转移到发送移位寄存器之间的延迟以SPICLK周期为单位。这在某些需要严格控制数据流间隔的协议中非常有用例如某些音频编码器要求数据帧之间具有固定的时间间隔。4.2 DMA与SPI的强强联合零CPU开销的数据搬运对于持续不断的高速数据流如音频流、图像传感器数据即使使用FIFO中断CPU的负担仍然很重。此时DMA是终极解决方案。DMA控制器可以在SPI接收/发送FIFO和内存之间直接搬运数据完全不需要CPU参与。C2000的DMA可以配置为由SPI的接收/发送FIFO中断事件触发。例如当接收FIFO中的数据达到预设水平时会产生一个脉冲给DMADMA随即发起一次传输将FIFO中的数据搬移到指定的内存数组里。配置SPI DMA的流程相对复杂涉及DMA通道的配置源地址、目的地址、传输数量、触发源等但其核心思想是事件驱动、硬件搬运。一旦配置完成你的主循环可能只需要检查一个由DMA中断更新的数据缓冲区标志位然后处理已经准备好的大批量数据即可SPI通信本身不再消耗任何CPU周期。4.3 数字音频应用与STEINV位在一些特定的应用场景如I2S数字音频接口SPI模块也能派上用场。C2000支持利用两个SPI模块配合STEINVSPISTE取反位来接收左右声道分离的音频数据。其原理是一个SPI模块配置为正常的从机其SPISTE信号低有效另一个SPI模块也配置为从机但将其STEINV位置1使其SPISTE信号高有效。将这两个SPISTE引脚连接到音频接收器提供的左右声道时钟L/R CLK上。当L/R CLK为低时选中第一个SPI右声道数据存入该模块当L/R CLK为高时选中第二个SPI左声道数据存入第二个模块。这样两个SPI模块就协同完成了一个I2S数据流的接收。这种用法非常巧妙但需要注意时序匹配。数据手册明确指出标准C28x SPI的时序要求限制了其持的音频格式。通常只有在时钟相位配置为CLKPOLARITY0且CLK_PHASE1数据在时钟上升沿锁存时才能支持标准的右对齐音频格式。在设计此类应用时必须仔细计算音频位时钟BCLK和左右时钟LRCK的频率确保其满足SPI从模式下的最大输入时钟频率通常为LSPCLK/4。5. 寄存器精讲与配置陷阱虽然库函数和驱动程序简化了开发但深入理解关键寄存器是解决复杂问题和进行深度优化的基础。这里针对几个容易出错的寄存器位进行补充说明。SPICCR.7 (SPISWRESET - SPI软件复位)这是配置SPI的“安全开关”。任何对工作模式、波特率、字符长度等关键配置的修改都必须在SPISWRESET0的前提下进行。修改完成后再置SPISWRESET1使其生效。绝对禁止在SPI通信过程中即正在发送或接收数据时修改配置这会导致不可预知的错误。SPISTS.6 (INT_FLAG - 中断标志)在非FIFO模式下这是判断一次传输完成的主要标志。读取SPIRXBUF的操作会自动清除此标志。这是一个“读清除”型标志。在FIFO模式下此标志的行为发生变化应改用FIFO状态位TXFFST,RXFFST或FIFO中断标志TXFFINT,RXFFINT来判断。SPISTS.7 (OVERRUN_FLAG - 超限标志)当一个新的字符已经接收完成并准备移入SPIRXBUF但之前的字符还未被CPU读取时此标志置位且新数据会覆盖旧数据丢失。此标志必须通过软件写1来清除。一个良好的习惯是在SPI中断服务程序ISR中先读取数据然后立即写1清除超限标志。SPIPRI.5/.4 (SOFT/FREE - 仿真控制位)这两个位决定了在代码调试过程中遇到断点仿真器挂起CPU时SPI模块的行为。FREE1表示自由运行即使CPU暂停SPI也继续工作这可能导致数据丢失或外设状态混乱。FREE0且SOFT0表示立即停止可能停在数据位的中间破坏通信帧。FREE0且SOFT1是折中方案完成当前传输后停止。在调试与SPI外设交互紧密的代码时需要根据情况谨慎设置这两个位否则会出现“全速运行正常单步调试就出错”的诡异现象。字符长度对齐问题SPICHAR设置的是“字长-1”。例如要传输8位数据应设置SPICHAR 7。数据写入SPITXBUF时必须左对齐而读取SPIRXBUF时得到的数据是右对齐的。对于小于16位的传输用户软件需要自行处理未使用的高位。例如发送8位数据0xAB应左移8位后写入SpiaRegs.SPITXBUF 0xAB 8;。接收到的16位数据你需要右移并掩码来获取有效的8位receivedData (SpiaRegs.SPIRXBUF 8) 0xFF;。忘记对齐操作是导致SPI通信数据错位的常见原因之一。