LPC21xx/22xx I2C与SPI通信实战:从寄存器操作到状态机调试
1. 项目概述深入LPC21xx/22xx的串行通信核心在嵌入式开发领域尤其是基于ARM7内核的LPC21xx/22xx系列微控制器I2C和SPI是绕不开的两大串行通信外设。无论是连接一个温湿度传感器、配置一块OLED屏幕还是与外部EEPROM或Flash存储器交换数据你几乎总会和它们打交道。官方数据手册UM10114提供了寄存器定义和状态机描述但对于初次接触或希望深入理解的开发者来说那份文档更像是一本“字典”而非“烹饪指南”。它告诉你每个寄存器位是干什么的却没告诉你炒这盘菜时什么时候该放盐火候该如何掌握。我从事嵌入式开发十多年从51单片机到ARM Cortex-M系列LPC2000系列因其稳定性和丰富的外设在工控、消费电子等领域曾是经典之选。很多朋友在调试I2C或SPI时卡住问题往往不在于看不懂寄存器而在于不理解状态机如何流转、时序如何配合、异常如何排查。本文将结合UM10114手册的核心内容以一线开发者的视角拆解LPC21xx/22xx的I2C与SPI接口。我不会照本宣科地罗列寄存器而是带你理解其工作机理并分享从实际项目中总结出的配置流程、避坑经验和调试技巧。无论你是正在评估该系列芯片还是已经深陷某个通信bug之中相信这些从实战中得来的细节都能为你提供清晰的路径。2. I2C接口深度解析与状态机实战LPC21xx/22xx的I2C接口是一个符合I2C总线标准的硬件模块它最大的特点是将复杂的总线协议状态机用硬件实现了。这意味着我们不需要用GPIO模拟时序而是通过操作一组寄存器来响应硬件产生的中断或查询状态从而完成通信。理解这一点是高效使用它的前提。2.1 核心寄存器组与工作模式I2C模块的核心是几个关键寄存器I2CONSET、I2CONCLR、I2DAT、I2STAT。手册中频繁出现的写0x04到I2CONSET设置AA位或写0x08到I2CONCLR清除SI标志等操作都是基于对这些寄存器的操作。I2CONSET/CONCLR (控制设置/清除寄存器)这是最关键的寄存器。它采用“置位-清除”机制即向I2CONSET的某位写1该位被置1向I2CONCLR的某位写1该位被清0。这种设计避免了读-修改-写操作可能带来的风险。核心控制位包括I2EN (位6)I2C接口使能。必须置1才能启动模块。STA (位5)产生起始(START)条件。在主机模式下设置此位会在总线上产生一个起始信号。STO (位4)产生停止(STOP)条件。设置此位会产生停止信号。在从机模式下此位无效。SI (位3)I2C中断标志。当I2C状态改变例如起始条件已发送、地址已接收、数据已收发完成等时硬件将此位置1。这是驱动状态机的核心信号。你必须通过读取I2STAT状态寄存器来获取当前状态码并执行相应操作后手动清除此位通过写I2CONCLR状态机才会进入下一个状态。AA (位2)应答标志(Assert Acknowledge)。此位决定在当前字节传输完成后硬件是否在SCL线上发出应答(ACK)或非应答(NACK)信号。AA1发ACKAA0发NACK。这在主机接收和从机模式下至关重要。I2DAT (数据寄存器)要发送的数据写入此寄存器接收到的数据从此寄存器读取。一个非常重要的细节向I2DAT写入数据并不会立即启动发送它只是填充了发送缓冲区。真正的发送动作是由设置STA位或清除SI位等操作触发的状态变迁来完成的。I2STAT (状态寄存器)这是一个只读寄存器其高5位包含了当前I2C接口的状态码。手册中所有的“State: 0x58”这类描述指的就是I2STAT的值。你的所有程序逻辑都应当围绕识别这个状态码并执行对应操作来展开。2.2 主机模式编程流程与状态码精讲主机模式是主动发起通信的一方。其基本流程是启动总线-发送从机地址含读写位-发送/接收数据-停止总线。每一步都对应着特定的状态码。2.2.1 启动与地址发送假设我们要作为主机向一个从机地址0xA0写操作发送数据。流程如下初始化设置I2C时钟频率通过I2SCLH和I2SCLL寄存器使能I2C接口I2CONSET 0x40。产生起始条件设置STA位I2CONSET 0x20。硬件会自动在总线空闲时产生START信号随后SI标志置起状态变为0x08“起始条件已发送”。发送从机地址写位在状态0x08下我们需要将7位地址和读写位0为写组合成一个字节本例为0xA0 1 0x40写入I2DAT然后清除SI位以继续I2CONCLR 0x08。硬件会发送这个地址字节。处理从机应答地址发送完毕后SI再次置起。此时状态可能是0x18从机已应答(ACK)。这是我们期望的状态意味着从机在线并准备接收数据。此时我们可以写入第一个要发送的数据字节到I2DAT然后清除SI位。0x20从机未应答(NACK)。这通常意味着总线上没有对应地址的从机或者从机忙。此时正确的处理是设置STO位释放总线I2CONSET 0x10然后清除SI位I2CONCLR 0x08总线进入空闲状态。2.2.2 数据发送与接收主机发送续上在0x18状态发送第一个数据字节后清除SI位。数据发送完并收到ACK后状态变为0x28。在这个状态下你可以继续写入下一个数据到I2DAT并清除SI位重复此过程。发送完最后一个字节后你需要产生停止条件在状态0x28下设置STO位然后清除SI位I2CONSET 0x10; I2CONCLR 0x08。主机接收流程类似但在发送地址时读写位应为1读。地址发送并收到ACK后状态为0x40。此时你需要提前设置AA位以决定在接收到第一个数据字节后是否发出ACK。如果你想继续接收下一个字节则设置AA1I2CONSET 0x04然后清除SI位。接收到数据后状态变为0x50此时应从I2DAT读取数据。如果你想停止接收接收最后一个字节则在状态0x40时清除AA位I2CONCLR 0x04然后清除SI位。接收到最后一个数据伴随NACK后状态变为0x58此时读取数据然后设置STO位并清除SI位以产生停止条件。关键经验手册中状态0x58对应的操作序列“Write 0x14 to I2CONSET”非常经典。0x14的二进制是0001 0100即同时设置STO(位4)和AA(位2)。这里设置AA位是为了让接口在停止条件后准备好下一次可能的通信尽管作为主机在发送停止后总线已释放。这个操作顺序先读数据再设置STO和AA最后清SI是确保状态机正确流转的黄金法则。2.3 从机模式编程要点从机模式的编程更依赖于中断服务程序(ISR)。你需要预先设置好自己的7位从机地址通过I2ADR寄存器。当总线上的主机发送的地址与你的地址匹配时SI标志置起状态码会指示是读请求还是写请求。状态0x60(从机地址写已接收)表示主机要向你写数据。此时你应设置AA位以准备接收数据I2CONSET 0x04清除SI位并初始化你的接收缓冲区和计数器。状态0x80(数据已接收ACK已返回)表示一个数据字节已收到。你应从I2DAT读取数据存入缓冲区。如果你还想接收更多数据非最后一个字节则保持AA1并清除SI位如果这是你想要的最后一个字节则清除AA位发送NACK并清除SI位。状态0xA8(从机地址读已接收)表示主机要读你的数据。你应将要发送的第一个数据字节加载到I2DAT设置AA位然后清除SI位。从机编程的关键在于你的ISR必须足够快能在下一个字节的时钟到来之前完成状态判断、数据存取和SI位清除操作否则可能错过时序。2.4 I2C调试实战心得与常见问题上拉电阻必不可少I2C总线是开漏输出SCL和SDA线必须通过上拉电阻接到正电源通常3.3V或5V。电阻值通常在4.7kΩ到10kΩ之间具体取决于总线电容和速度。没有上拉电阻总线永远都是低电平。状态机卡死排查如果程序卡在某个状态出不来首先用逻辑分析仪或示波器抓取SCL和SDA波形。确认起始、停止、地址、数据、ACK/NACK波形是否正常。其次检查你的代码是否在每个状态处理后都正确地清除了SI位I2CONCLR 0x08。这是最常见的错误。时钟速率设置I2SCLH和I2SCLL寄存器决定了SCL高电平和低电平的时间以PCLK周期为单位。两者之和决定了I2C时钟频率。例如PCLK12MHz欲设置I2C为100kHz标准模式则每个SCL周期为10us对应120个PCLK周期。通常设置I2SCLH I2SCLL 60。务必确保设置值不小于4。仲裁丢失处理在多主机系统中状态码如0x38、0x68、0x78、0xB0都表示仲裁丢失。硬件检测到总线竞争失败后会自动切换到从机模式并置起SI。此时你的程序应该执行手册中对应的操作通常涉及重新设置STA位以尝试再次成为主机并做好重发数据的准备。3. SPI接口详解与多模式配置与I2C不同SPI是全双工、同步、四线或三线的串行接口。LPC21xx/22xx通常提供两个独立的SPI控制器SPI0和SPI1。它的编程模型比I2C的状态机模型更直观但时序配置的灵活性也带来了复杂性。3.1 SPI核心概念CPOL与CPHA这是理解SPI时序的基石也是新手最容易混淆的地方。手册中的图49和表192是理解这一切的关键。CPOL (Clock Polarity时钟极性)决定SCK时钟线在空闲时的状态。CPOL 0SCK空闲时为低电平。CPOL 1SCK空闲时为高电平。CPHA (Clock Phase时钟相位)决定数据在SCK的哪个边沿被采样捕获在哪个边沿被切换输出。CPHA 0数据在SCK的第一个边沿被采样在第二个边沿切换。注意对于CPHA0从机的SSEL信号必须在每次传输前变为有效低电平并在传输间隙变为无效。CPHA 1数据在SCK的第二个边沿被采样在第一个边沿切换。CPOL和CPHA组合成四种模式这是与外设通信时必须匹配的参数。通常传感器、Flash芯片的数据手册会明确要求SPI模式。模式CPOLCPHA空闲时SCK电平数据采样边沿数据切换边沿000低电平上升沿下降沿101低电平下降沿上升沿210高电平下降沿上升沿311高电平上升沿下降沿实操口诀模式 (CPOL 1) | CPHA。例如模式2对应CPOL1 CPHA0。与外设通信前第一件事就是查手册确认其SPI模式。3.2 寄存器配置与数据传输流程SPI模块的寄存器比I2C少核心是SPCR控制寄存器、SPSR状态寄存器、SPDR数据寄存器和SPCCR时钟计数器寄存器。3.2.1 主机模式操作步骤假设我们要使用SPI0以主机模式向一个从设备发送一字节数据0x55并读取其返回数据。SPI模式设为0CPOL0 CPHA0时钟分频设为8SCK PCLK/8。引脚功能选择将对应管脚如P0.4/SCK0, P0.5/MISO0, P0.6/MOSI0, P0.7/SSEL0通过PINSELx寄存器设置为SPI功能。配置SPI控制寄存器 (SPCR)// 假设使用SPI0其控制寄存器地址为 S0SPCR (0xE0020000) // BitEnable0 (8位传输), CPHA0, CPOL0, MSTR1 (主机模式), LSBF0 (MSB先传), SPIE0 (先禁用中断) // BITS字段在BitEnable0时无效 *((volatile uint8_t *)0xE0020000) (1 5); // 设置MSTR位其他位为0即0x20更常见的做法是使用位操作或结构体映射这里用直接地址赋值是为了清晰。设置时钟速率 (SPCCR)// SPI0时钟计数器寄存器地址 S0SPCCR (0xE002000C) // 值必须为大于等于8的偶数。写入8。 *((volatile uint8_t *)0xE002000C) 8;拉低从机选择信号 (SSEL)如果从机需要硬件片选将对应的GPIO或配置为SPI SSEL功能的引脚拉低。对于LPC21xx/22xx当SPI配置为主机时其SSEL引脚是输入用于检测模式错误。因此通常用一个普通的GPIO口来作为从机片选信号。启动数据传输向数据寄存器SPDR写入要发送的数据。// SPI0数据寄存器地址 S0SPDR (0xE0020008) *((volatile uint16_t *)0xE0020008) 0x55; // 写入数据启动传输关键点在主机模式下向SPDR写入数据会立即启动一次SPI传输前提是SPI已使能且为主机模式。等待传输完成轮询状态寄存器SPSR的SPIF位位7直到其为1。// SPI0状态寄存器地址 S0SPSR (0xE0020004) while(!(*((volatile uint8_t *)0xE0020004) (1 7))); // 等待SPIF置位清除状态标志并读取数据读取状态寄存器这将清除SPIF、ROVR、MODF等标志然后读取SPDR获取从机返回的数据。uint8_t status *((volatile uint8_t *)0xE0020004); // 读取状态清除标志 uint16_t received_data *((volatile uint16_t *)0xE0020008); // 读取接收到的数据拉高片选完成本次通信后将片选GPIO拉高。3.2.2 从机模式操作要点从机模式的初始化更简单不需要设置SPCCR时钟由主机提供且将SPCR的MSTR位设为0。数据传输的触发不是由写SPDR引起而是由主机发来的SCK时钟触发。初始化SPCRMSTR0设置好CPHA/CPOL以匹配主机。预先写入要发送的数据如果需要回复主机在主机发起传输前将要发送的数据写入SPDR。从机的SPDR是一个带预装载功能的寄存器写入的数据会等待主机时钟来移出。等待传输完成SPIF置位读取状态寄存器再从SPDR中读取主机发来的数据。如果需要连续传输则在一次传输结束后立即将下一个要发送的数据写入SPDR为下一次主机时钟到来做准备。重要警告从机的系统时钟CCLK频率必须至少是SPI SCK时钟频率的8倍否则可能无法正确采样数据。这是硬件限制。3.3 SPI异常条件处理手册中提到了几种异常状态在状态寄存器SPSR中有对应的标志位。健全的驱动代码应该检查这些位。写冲突 (WCOL)当一次SPI传输正在进行时从启动到SPIF置位且状态寄存器未被读取如果再次向SPDR写入数据就会发生写冲突WCOL位被置1且这次写入的数据丢失。解决方法在写SPDR前确保SPIF为0且上次传输已完成。发生冲突后通过“读状态寄存器 - 读/写数据寄存器”的顺序来清除WCOL位。读溢出 (ROVR)当SPIF标志已经为1表示接收缓冲区有未读数据而一次新的传输又完成了新的接收数据会丢失ROVR位置1。解决方法及时读取SPDR中的数据。发生溢出后通过读取状态寄存器来清除ROVR位。模式错误 (MODF)当SPI配置为主机模式时如果其SSEL输入引脚被拉低意味着有另一个设备试图将它作为从机则发生模式错误。硬件会自动将MSTR位清零变为从机并关闭SPI输出驱动器。解决方法在主机模式下确保SSEL引脚被上拉或保持为高电平。发生错误后通过“读状态寄存器 - 写控制寄存器(SPCR)”的顺序来清除MODF位并重新配置为主机模式。从机中止 (ABRT)当SPI作为从机时如果主机在传输完成前将SSEL信号拉高传输被中止ABRT位置1。解决方法从机应丢弃本次未完成的传输数据准备下一次通信。通过读取状态寄存器清除ABRT位。3.4 SPI与SSP的区分与选型在LPC22xx的部分增强型型号如/01版本中SPI1接口可以通过PCONP外设功率控制寄存器的PSSP位切换为功能更强大的SSP (Synchronous Serial Port)接口。SSP兼容SPI、TI SSI和Microwire协议并且最关键的是它内置了8帧深的发送和接收FIFO支持4-16位的数据帧长。这对于需要高速、连续传输数据的场景如驱动TFT屏、与音频编解码器通信是巨大的优势。使用FIFO可以大大减少CPU中断频率提升效率。如果你的项目选用了支持SSP的型号并且对SPI性能有较高要求强烈建议使用SSP而非基础的SPI1。切换时需注意不能同时使能SPI1和SSP。正确的切换顺序是先禁用当前活动外设的中断在VIC和外设寄存器中清除所有挂起的中断标志然后在PCONP寄存器中关闭当前外设最后再使能目标外设。手册第14章对此有明确警告操作不当可能导致中断无法服务甚至系统死锁。4. 实战编程指南与代码框架理解了原理我们来看如何组织代码。一个健壮的驱动应该分层底层寄存器操作层、总线管理层、设备应用层。4.1 I2C驱动框架示例下面是一个基于查询非中断方式的LPC21xx I2C主机发送函数框架它处理了核心状态机typedef enum { I2C_OK 0, I2C_NAK, I2C_ARBITRATION_LOST, I2C_TIMEOUT } I2C_Result; #define I2C_TIMEOUT_VALUE 10000 // 简化寄存器定义以I2C0为例 #define I2CONSET (*((volatile uint32_t *)0xE001C000)) #define I2CONCLR (*((volatile uint32_t *)0xE001C004)) #define I2DAT (*((volatile uint32_t *)0xE001C008)) #define I2STAT (*((volatile uint32_t *)0xE001C00C)) #define I2C_START_DONE 0x08 #define I2C_SLA_W_ACK 0x18 #define I2C_SLA_W_NAK 0x20 #define I2C_DATA_TX_ACK 0x28 #define I2C_DATA_TX_NAK 0x30 #define I2C_ARB_LOST 0x38 I2C_Result I2C_MasterTransmit(uint8_t slaveAddr, const uint8_t *data, uint32_t len) { uint32_t timeout 0; // 1. 发送起始条件 I2CONSET (1 5); // 设置STA位 while (!(I2CONSET (1 3))) { // 等待SI置位 if (timeout I2C_TIMEOUT_VALUE) return I2C_TIMEOUT; } if ((I2STAT 0xF8) ! I2C_START_DONE) { // 处理错误例如仲裁丢失 I2CONCLR (1 3); // 清SI return I2C_ARBITRATION_LOST; } // 2. 发送从机地址写 I2DAT (slaveAddr 1); // 写地址 I2CONCLR (1 3) | (1 5); // 清SI和STA位 timeout 0; while (!(I2CONSET (1 3))) { if (timeout I2C_TIMEOUT_VALUE) return I2C_TIMEOUT; } uint8_t status I2STAT 0xF8; if (status I2C_SLA_W_NAK) { I2CONSET (1 4); // 设置STO位 I2CONCLR (1 3); // 清SI位 return I2C_NAK; // 从机无应答 } else if (status ! I2C_SLA_W_ACK) { // 其他错误状态处理 I2CONSET (1 4); I2CONCLR (1 3); return I2C_ARBITRATION_LOST; } // 3. 发送数据字节 for (uint32_t i 0; i len; i) { I2DAT data[i]; I2CONCLR (1 3); // 清SI位启动发送 timeout 0; while (!(I2CONSET (1 3))) { if (timeout I2C_TIMEOUT_VALUE) return I2C_TIMEOUT; } status I2STAT 0xF8; if (status ! I2C_DATA_TX_ACK) { // 发送失败终止总线 I2CONSET (1 4); I2CONCLR (1 3); return (status I2C_DATA_TX_NAK) ? I2C_NAK : I2C_ARBITRATION_LOST; } } // 4. 发送停止条件 I2CONSET (1 4); // 设置STO位 I2CONCLR (1 3); // 清SI位 // 注意STO位会在硬件产生停止条件后自动清除无需软件干预 return I2C_OK; }这个框架省略了详细的初始化设置时钟频率、使能I2C和错误恢复的完整逻辑但清晰地展示了基于状态机的编程流程。在实际项目中你还需要考虑总线忙等待、中断驱动等方式来优化。4.2 SPI驱动框架示例以下是一个SPI主机阻塞式发送/接收函数的示例typedef struct { __IO uint32_t SPCR; __IO uint32_t SPSR; __IO uint32_t SPDR; __IO uint32_t SPCCR; __IO uint32_t RESERVED[3]; __IO uint32_t SPINT; } SPI_TypeDef; #define SPI0_BASE 0xE0020000UL #define SPI0 ((SPI_TypeDef *) SPI0_BASE) void SPI_Init(SPI_TypeDef *SPIx, uint8_t mode, uint8_t clockDiv) { // 1. 确保时钟分频值为偶数且8 if (clockDiv 8) clockDiv 8; if (clockDiv 0x01) clockDiv; // 确保为偶数 // 2. 配置控制寄存器 // BitEnable0 (8位), MSTR1 (主机), 根据mode设置CPOL和CPHA uint8_t cpol (mode 1) 0x01; uint8_t cpha mode 0x01; SPIx-SPCR (1 5) | (cpol 4) | (cpha 3); // 3. 设置时钟分频 SPIx-SPCCR clockDiv; } uint8_t SPI_TransferByte(SPI_TypeDef *SPIx, uint8_t txData) { // 等待上一次传输完成SPIF标志为1同时检查错误 while (!(SPIx-SPSR (1 7))) { // 可选在此检查WCOL, MODF等错误位 if (SPIx-SPSR (1 6)) { // WCOL // 发生写冲突清除之读状态再读数据寄存器 volatile uint8_t dummy SPIx-SPSR; dummy SPIx-SPDR; } if (SPIx-SPSR (1 4)) { // MODF // 发生模式错误清除之读状态再写控制寄存器 volatile uint8_t dummy SPIx-SPSR; SPIx-SPCR | (1 5); // 重新使能主机模式 } } // 清除SPIF标志通过读状态寄存器实现 volatile uint8_t status SPIx-SPSR; // 写入要发送的数据启动新的传输 SPIx-SPDR txData; // 等待本次传输完成 while (!(SPIx-SPSR (1 7))); // 清除SPIF标志并读取接收到的数据 status SPIx-SPSR; return (uint8_t)(SPIx-SPDR); }这个SPI_TransferByte函数实现了完整的单字节全双工传输并包含了基本的错误检查。在实际使用中你需要在传输前手动控制片选GPIO拉低传输后再拉高。5. 常见问题排查与性能优化技巧即使理解了所有原理调试I2C/SPI时仍会遇到各种奇怪的问题。下面是我总结的一些常见“坑”和解决思路。5.1 I2C通信失败排查清单无任何波形检查I2C模块是否使能I2CONSET的I2EN位GPIO引脚是否已正确配置为I2C功能通过PINSEL寄存器上拉电阻是否焊接好用万用表测量SDA/SCL电压空闲时应为高电平VCC。有起始信号但地址无应答NACK检查从机地址是否正确7位地址左移1位后最低位是R/W位从设备电源是否正常从设备的I2C总线速率是否支持主机当前设置的速率从设备是否处于忙状态如EEPROM正在写内部页尝试降低I2C时钟频率。通信随机失败有时成功有时失败检查总线电容是否过大长导线或过多设备并联会导致上升沿变缓违反时序要求。尝试减小上拉电阻值如从10kΩ改为4.7kΩ以增强驱动能力。检查电源是否干净是否有大的毛刺。检查代码状态处理是否完整是否在每个状态变化后都清除了SI位中断服务程序中是否有耗时过长的操作导致错过下一个字节的响应仲裁丢失频繁发生检查是否在多主机系统中两个主机同时发起传输软件上应实现总线监听和退避机制。硬件上确保各主机的电源和地稳定避免因电源噪声导致错误的总线状态判断。5.2 SPI通信问题排查清单从机无响应首要检查CPOL和CPHA模式是否匹配这是SPI问题中最最常见的原因。用逻辑分析仪同时抓取SCK、MOSI、MISO和SSEL信号对照数据手册的时序图检查数据在哪个时钟边沿被从机采样与你配置的模式是否一致。检查片选信号SSEL是否正确是低电平有效还是高电平有效有效电平的宽度和建立/保持时间是否满足从机要求对于CPHA0的模式SSEL必须在每个数据帧前重新有效。检查时钟频率是否超过从机支持的最大频率尤其是从机模式需确保CCLK 8 * SCK。数据错位或位反转检查SPCR的LSBF位位6是否配置正确大多数设备是MSB先传。如果此位设反接收到的数据高低位会完全颠倒。检查数据帧长度是否匹配虽然LPC21xx基础SPI固定8位但部分/01型号和SSP支持8-16位。确保主机和从机约定好一次传输多少位。使用SSEL引脚作为GPIO输出时的注意事项对于LPC21xx/22xx当SPI配置为主机时其硬件SSEL引脚是输入模式用于检测模式错误。如果你想用它作为输出以控制从机片选必须先将该引脚配置为GPIO功能而不是SPI功能。然后通过GPIO寄存器控制其电平。直接使用SPI功能的SSEL引脚作为输出是无法驱动从机的。5.3 性能优化建议使用中断或DMA对于连续、大数据量的传输查询方式会大量占用CPU。I2C和SPI都支持中断。SPI的SSP模块还支持DMA可以极大解放CPU。将数据搬运工作交给DMACPU仅在传输完成时被中断处理是提升系统效率的关键。SPI时钟分频优化SPCCR寄存器的值决定了SCK PCLK / value。value必须是偶数且8。为了获得精确的波特率需要根据PCLK计算。例如PCLK60MHz需要10MHz的SCK则value6但6小于8且非偶数不可用。只能取8得到7.5MHz。此时需要评估从机是否支持此速率。I2C状态机的中断处理将I2C状态码判断和寄存器操作放在中断服务程序(ISR)中可以构建非阻塞的、高效的I2C引擎。主程序只需将“地址数据”放入队列ISR自动按状态机处理。注意ISR应尽量短小复杂的后续处理可置标志位由主循环完成。电源与噪声管理高速SPI通信10MHz对PCB布局非常敏感。确保SCK、MOSI、MISO走线尽可能短并远离高频噪声源。在信号线上串联小电阻如22Ω-100Ω有助于抑制过冲和振铃。如果通信距离稍长10cm需考虑信号完整性可能需使用缓冲器或降低速率。调试串行通信逻辑分析仪是你的最佳伙伴。它能直观地展示每一位数据、每一个时钟边沿、每一个起始停止条件比任何打印信息都管用。投资一个哪怕是最基础的逻辑分析仪也能在调试通信问题时节省你无数个小时。