LPC21xx/22xx SPI与SSP接口详解:从寄存器配置到实战避坑指南

发布时间:2026/6/21 6:08:02
LPC21xx/22xx SPI与SSP接口详解:从寄存器配置到实战避坑指南 1. LPC21xx/22xx SPI与SSP接口嵌入式通信的基石在嵌入式系统开发中微控制器与外设之间的通信是构建功能的核心。当I2C的速度无法满足需求而UART的异步特性又带来时序上的复杂性时SPISerial Peripheral Interface就成了许多工程师的首选。它是一种简单、高速、全双工的同步串行总线以其“一主多从”的架构和仅需四根线的物理连接在驱动TFT屏幕、读写Flash芯片、连接高速ADC/DAC等场景中无处不在。NXP的LPC21xx/22xx系列ARM7微控制器作为一代经典其内置的SPI控制器设计精炼而强大而部分增强型号提供的SSPSynchronous Serial Port接口则带来了更强的灵活性和FIFO缓冲。但无论是使用基础的SPI还是功能更丰富的SSP从寄存器配置到稳定通信中间每一步都藏着细节。很多新手在配置时钟相位时一头雾水或在处理多字节传输时遭遇数据覆盖问题往往就出在对控制器工作机理的理解不够透彻。今天我们就抛开手册的冰冷描述从实际开发的角度把LPC21xx/22xx的SPI和SSP接口掰开揉碎讲清楚每一个配置位的意义以及如何避开那些手册里可能一笔带过、但实际调试中却频频出现的“坑”。2. 核心概念与硬件架构解析2.1 SPI基础四线制下的同步对话SPI通信的本质是一场由主设备Master严格指挥的同步对话。它依靠四根信号线构建起通信链路SCK (Serial Clock)时钟信号由主设备产生是所有数据收发的节拍器。时钟的极性和相位是可配置的这是SPI适配不同外设的关键。MOSI (Master Out Slave In)主设备输出、从设备输入的数据线。主设备通过这根线向从设备发送指令或数据。MISO (Master In Slave Out)主设备输入、从设备输出的数据线。从设备通过这根线回应主设备。SSEL (Slave Select)从设备选择信号低电平有效。主设备通过拉低对应从设备的SSEL线来“点名”通知它准备参与通信。一个主设备可以连接多个从设备通过不同的GPIO控制各自的SSEL。通信过程可以想象成工厂的装配流水线SCK是传送带的节拍主设备在某个节拍将数据位一个零件放到MOSI传送带上送出同时从设备也必须在这个节拍将其要回应的数据位放到MISO传送带上。主设备在下一个节拍从MISO线上读取这个位。整个过程是全双工的即收发同时进行。SSEL信号则像是流水线的总开关只有被选中的从设备流水线才会通电工作。2.2 LPC21xx/22xx SPI控制器内部探秘LPC21xx/22xx的SPI控制器SPI0和SPI1是一个相对简洁但功能完整的硬件模块。理解其内部结构对编程至关重要。其核心是一个移位寄存器和一个数据寄存器SPDR。这里有一个关键细节常被忽略写入SPDR的数据是直接进入内部移位寄存器的中间没有缓冲。这意味着如果你在移位寄存器正在忙碌地向外移位数据时即一次传输正在进行中向SPDR写入新数据新数据会直接覆盖正在移出的数据导致本次传输混乱并触发“写冲突WCOL”状态。反之接收数据则有缓冲当一次传输完成接收到的数据从移位寄存器移入一个独立的接收数据缓冲区然后我们通过读取SPDR来获取这个缓冲区的值。这种“写无缓冲读有缓冲”的设计要求我们的软件必须严格遵循“等待一次传输完成再发起下一次传输”的顺序。控制器的状态由几个关键寄存器掌控SPCR (控制寄存器)决定SPI的工作模式主/从、数据帧格式MSB/LSB先行、时钟极性CPOL与相位CPHA、传输位宽8位或8-16位以及是否使能中断。SPSR (状态寄存器)反映控制器当前状态。最重要的位是SPIF传输完成标志它在一帧数据发送完成的那个时钟边沿被硬件置位。此外它还报告异常状态如模式错误MODF、从机中止ABRT、读溢出ROVR和写冲突WCOL。SPCCR (时钟计数器寄存器)仅在主模式下有效用于设置SCK时钟分频计算公式为SCK频率 PCLK / SPCCR。手册强制要求此值必须为大于等于8的偶数否则行为不可预测。SPINT (中断标志寄存器)当控制寄存器中的SPIE中断使能位为1且SPSR中的SPIF或MODF位从0变为1时此寄存器的中断标志位会被置1。注意清除SPIF状态位和清除SPINT中断标志是两回事需要分别操作。2.3 SSP接口SPI的增强版兄弟在LPC2109/01、LPC2294/01等增强型芯片上SPI1的引脚可以被复用为功能更强大的SSP接口。你可以通过电源控制寄存器PCONP中的PSSP位来选择启用SSP。SSP相对于基础SPI主要有三大增强协议兼容性更广不仅支持标准SPI还支持TI的SSI4线制和National Semiconductor的Microwire总线协议适应性更强。帧长度灵活支持4位到16位任意长度的数据帧而基础SPI通常固定为8位或通过特定配置支持8-16位。硬件FIFO这是最实用的改进。SSP内置了深度为8的独立发送和接收FIFO。这意味着你可以连续写入最多8个数据帧到发送FIFO控制器会自动按顺序发送同样可以连续从接收FIFO读取最多8个数据帧。这极大地减轻了CPU的中断负担特别适合高速或批量数据传输场景。重要提示SPI1和SSP共享同一组物理引脚但绝不能同时使能。切换时务必先禁用当前活动外设的中断在外设本身和VIC中清除所有挂起的中断标志最后再修改PCONP寄存器进行切换。如果先禁用外设时钟PCONP清零其寄存器将无法访问可能导致无法清除的中断标志持续引发中断造成系统死锁。3. 寄存器配置详解与实战编程3.1 时钟极性(CPOL)与相位(CPHA)时序匹配的灵魂SPI通信中最容易配置出错的就是CPOL和CPHA它们共同定义了数据与时钟的相对时序关系共有四种模式模式0-3。LPC21xx/22xx的SPCR寄存器中的CPOL和CPHA位直接对应这些模式。CPOL (Clock Polarity)决定SCK空闲时的电平。CPOL0SCK空闲时为低电平。CPOL1SCK空闲时为高电平。CPHA (Clock Phase)决定数据在时钟的哪个边沿被采样捕获在哪个边沿被切换更新。CPHA0数据在第一个时钟边沿被采样。对于主设备数据在SCK边沿之前就必须准备好输出到MOSI。在此模式下SSEL信号必须在每次传输前后跳变有效-无效。CPHA1数据在第二个时钟边沿被采样。数据在第一个时钟边沿切换。在此模式下SSEL信号可以在多次传输期间保持有效。如何选择模式这完全取决于你的外设芯片数据手册的要求。例如很多SPI Flash芯片工作在模式0CPOL0 CPHA0或模式3CPOL1 CPHA1。一个黄金法则主设备和所有从设备的CPOL、CPHA设置必须完全一致否则通信必然失败。配置示例模式0主机8位数据MSB先行// 假设使用SPI0 #define SPI0_SPCR (*(volatile unsigned short *)0xE0020000) #define SPI0_SPSR (*(volatile unsigned char *)0xE0020004) #define SPI0_SPDR (*(volatile unsigned short *)0xE0020008) #define SPI0_SPCCR (*(volatile unsigned char *)0xE002000C) void SPI0_Master_Init(void) { // 1. 设置时钟分频。假设PCLK60MHz目标SCK1MHz则SPCCR 60MHz/1MHz 60。 // 必须为偶数且8我们取60。 SPI0_SPCCR 60; // 2. 配置控制寄存器SPCR: // BitEnable0 (8位传输), CPHA0, CPOL0, MSTR1 (主机模式), LSBF0 (MSB先行), SPIE0 (先禁用中断) // BITS字段在BitEnable0时无效 SPI0_SPCR (0 2) | (0 3) | (0 4) | (1 5) | (0 6) | (0 7); // 或直接赋值 SPI0_SPCR 0x20; // 二进制0010 0000即MSTR位为1 }3.2 主从模式操作流程与代码实现主机模式发送/接收单字节查询方式这是最基础的操作。关键在于严格遵循“写数据启动传输 - 等待SPIF标志 - 读状态清标志 - 读数据”的顺序。unsigned char SPI0_Master_TransferByte(unsigned char data_out) { unsigned char data_in 0; // 1. 写入要发送的数据此操作会启动SPI传输主机模式下 SPI0_SPDR data_out; // 2. 等待传输完成。轮询SPSR的SPIF位第7位。 while (!(SPI0_SPSR 0x80)); // 等待SPIF位变为1 // 3. 读取状态寄存器。这一步会清除ABRT、MODF、ROVR、WCOL等异常标志位。 volatile unsigned char status SPI0_SPSR; // 4. 读取接收到的数据。读取SPDR会清除SPIF状态位。 data_in SPI0_SPDR; return data_in; }注意第3步读取状态寄存器SPSR是必须的它不仅用于检查错误其读取动作本身会清除ABRT、MODF、ROVR标志。而SPIF标志的清除需要第4步的读或写SPDR操作。顺序错误可能导致标志无法清除程序死循环。从机模式接收数据查询方式从机模式下的传输由主机发起从机被动响应。void SPI0_Slave_Init(void) { // 配置为从机模式MSTR0, 其他配置CPOL, CPHA必须与主机一致 SPI0_SPCR (0 5); // MSTR位为0其他位根据主机设置例如模式0: CPHA0, CPOL0 } unsigned char SPI0_Slave_ReceiveByte(void) { unsigned char received_data 0; // 从机模式下传输由主机的SCK和SSEL信号启动。 // 我们只需要等待SPIF标志表示一帧数据接收完成。 while (!(SPI0_SPSR 0x80)); // 读取状态寄存器以清除可能的错误标志 volatile unsigned char status SPI0_SPSR; // 读取接收到的数据 received_data SPI0_SPDR; // 如果需要回应数据可以在此处将数据写入SPDR供主机下一帧读取。 // SPI0_SPDR data_to_send; // 注意必须在下次传输开始前写入 return received_data; }关键点在从机模式下你写入SPDR的数据会被加载到移位寄存器等待主机时钟到来时移出。因此如果你想回应主机必须在主机发起下一帧传输之前将回应数据写入SPDR。通常的做法是在本次接收完成的中断服务程序里立即准备好下一帧要发送的数据并写入SPDR。3.3 多字节传输与FIFO使用SSP对于基础SPI多字节传输就是循环调用单字节传输函数。但对于SSP我们可以利用其硬件FIFO大幅提升效率。SSP初始化与FIFO操作示例// SSP寄存器基址假设使用SSP地址与SPI1相同 #define SSP_CR0 (*(volatile unsigned short *)0xE0030000) #define SSP_CR1 (*(volatile unsigned short *)0xE0030004) #define SSP_DR (*(volatile unsigned short *)0xE0030008) #define SSP_SR (*(volatile unsigned short *)0xE003000C) #define SSP_CPSR (*(volatile unsigned short *)0xE0030010) void SSP_Master_Init(void) { // 1. 在PCONP寄存器中使能SSP禁用SPI1 (具体地址请查手册) // *(volatile unsigned long *)0xE01FC0C4 | (1 21); // 设置PSSP位 // *(volatile unsigned long *)0xE01FC0C4 ~(1 10); // 清除PSPI1位 // 2. 设置时钟预分频 (CPSR)例如PCLK60MHz, CPSR2则SSP时钟30MHz SSP_CPSR 2; // 3. 配置控制寄存器CR0: 8位数据帧SPI模式CPOL0, CPHA0 SSP_CR0 0x0007; // SCR0, SPH0, SPO0, FRF0(SPI), DSS0x7(8-bit) // 4. 配置控制寄存器CR1: 使能SSP设置为主机模式 SSP_CR1 (1 1); // SSPEN1, MS0(Master) } // 使用FIFO发送多个字节 void SSP_Send_MultipleBytes(unsigned char *data, unsigned int length) { unsigned int i 0; while (i length) { // 等待发送FIFO未满 (TNF位为1) while (!(SSP_SR 0x02)); // 向数据寄存器写入一个字节数据会自动进入发送FIFO SSP_DR data[i]; i; // 可以连续写入直到FIFO满深度8或数据写完 } // 可选等待所有数据发送完成BSY位为0 while (SSP_SR 0x04); } // 使用FIFO接收多个字节 void SSP_Receive_MultipleBytes(unsigned char *buffer, unsigned int length) { unsigned int i 0; while (i length) { // 等待接收FIFO非空 (RNE位为1) while (!(SSP_SR 0x04)); // 从数据寄存器读取一个字节 buffer[i] SSP_DR; i; } }使用FIFO后CPU无需在每字节传输后都等待并处理中断只需在FIFO快空或快满时进行批量填充或读取极大提高了效率降低了中断频率。4. 异常处理与调试技巧实录4.1 常见异常状态诊断与恢复SPI状态寄存器SPSR中的几个错误标志是调试的宝贵线索。写冲突 (WCOL)现象数据发送不正确读取状态寄存器发现WCOL位被置1。原因在SPI传输正在进行时即从写入SPDR启动传输开始到SPIF置位且状态寄存器被读取之前向SPDR寄存器执行了写操作。基础SPI的写操作无缓冲直接破坏当前传输。解决严格遵守操作流程。在查询方式下确保while(!(SPIF));之后先读状态寄存器再读/写数据寄存器。在中断服务程序中避免在传输未完成时再次触发发送。清除方法先读SPSR再访问SPDR。模式故障 (MODF)现象主机突然无法产生SCK时钟MODF位置1。原因当SPI配置为主机模式时其SSEL输入引脚被拉低变为有效。这通常意味着总线上有另一个设备试图将本设备作为从机在多主系统中可能发生更常见的是硬件连接错误或软件误配置——将主机模式的SSEL引脚配置成了输入且被外部拉低。解决检查硬件连接确保主机设备的SSEL引脚即使不用被上拉到高电平或在软件中将其配置为GPIO输出并置高。对于LPC21xx/22xx手册特别指出无Flash的LPC22xx及所有旧版芯片/00 /01 无后缀在作为SPI主机时必须将SSEL引脚功能选中并保持高电平。清除方法先读SPSR再写SPCR。从机中止 (ABRT)现象从机通信中断ABRT位置1。原因在从机传输过程中SSEL信号在传输完成前变为了无效高电平。这可能是主机端软件错误地提前释放了片选或者是信号干扰。解决检查主机端的代码确保SSEL信号在整帧数据传输期间保持有效。对于CPHA0的模式SSEL在每帧之间可以无效对于CPHA1SSEL可以在多帧传输期间保持有效。清除方法读取SPSR寄存器。读溢出 (ROVR)现象数据丢失ROVR位置1。原因接收数据缓冲区读缓冲中的数据尚未被CPU读取SPIF仍为1一次新的传输又完成了。新数据无处存放被丢弃。解决提高CPU读取数据的速度。使用中断而非查询方式确保中断服务程序及时读取SPDR。或者检查程序逻辑避免错过SPIF标志。清除方法读取SPSR寄存器。4.2 实战调试心得与避坑指南时钟配置是第一步也是最重要的一步在初始化任何SPI操作前务必正确配置SPCCR主机并确认PCLK时钟源正确。一个计算错误的SPCCR值如奇数或小于8会导致SCK波形畸变通信完全失败。用示波器测量SCK频率验证F_sck PCLK / SPCCR。“隐形的”从机选择即使你的系统只有一个从设备也不要在硬件上简单地将从机的SSEL引脚永久接地。虽然这样从机一直处于被选中的状态但在CPHA0模式下SPI协议要求SSEL在每帧之间跳变。永久拉低可能导致某些外设尤其是CPHA0的工作异常。正确的做法是主机用一个GPIO来模拟SSEL信号并在通信前后正确控制它。中断服务程序ISR的清理工作如果使用SPI中断SPIE1在ISR中必须完成两件事1) 读取SPSR以清除MODF等错误标志2) 读取或写入SPDR以清除SPIF标志。此外还必须向SPINT寄存器写入1来清除SSP的中断标志。忘记任何一步都会导致中断持续触发系统卡死。电平转换与上拉电阻如果SPI总线需要连接3.3V和5V设备必须使用电平转换器。对于开漏输出的MISO线多个从设备共用时必须在主机端加上拉电阻通常4.7kΩ-10kΩ以确保总线在无人驱动时处于确定的高电平状态。示波器是你的好朋友当通信失败时第一时间用示波器同时抓取SCK、MOSI、MISO和SSEL四路信号。对照数据手册的时序图检查CPOL和CPHA设置是否正确第一个数据位是在SCK第一个边沿还是第二个边沿出现SSEL信号时序是否符合CPHA模式要求MOSI/MISO上的数据是否稳定有无毛刺数据位是MSB先行还是LSB先行通过系统地分析这些信号绝大多数SPI通信问题都能被快速定位和解决。SPI接口看似简单但其稳定可靠运行依赖于对硬件特性和协议细节的精准把握。理解LPC21xx/22xx的寄存器行为遵循正确的配置与操作流程并善用状态寄存器进行诊断你就能让这条高速数据通道流畅地运转起来。