1. 项目概述与核心价值
在嵌入式系统开发中,我们常常会遇到一个经典难题:主控微处理器(MCU)本身没有I2C硬件接口,或者已有的I2C接口数量不够用,但项目又必须连接多个I2C从设备,比如传感器、EEPROM或IO扩展芯片。早年,工程师们可能会选择用GPIO模拟I2C时序,也就是我们常说的“软件I2C”或“Bit-Banging”。这种方法虽然灵活,但会大量占用CPU时间,时序精度也受中断和主频影响,在多任务或实时性要求高的场景下捉襟见肘。另一种方案是选用带更多硬件I2C外设的MCU,但这意味着硬件选型受限,可能增加BOM成本。
Philips Semiconductors(现为NXP)推出的PCA9564,就是专门为解决这个痛点而生的“协议转换桥梁”。它本质上是一个并串转换控制器,一端通过标准的8位并行数据/地址总线与你的主处理器(无论是8051、ARM还是其他架构)对话,另一端则扮演一个完整的、支持标准模式和快速模式的I2C总线主/从设备。它的价值在于,将复杂的I2C协议时序处理、总线仲裁、时钟生成等底层任务全部硬件化,让主处理器可以像读写内存一样,通过几个简单的寄存器操作来完成复杂的I2C通信,极大地解放了CPU资源,也保证了通信的稳定性和可靠性。
我最早接触这颗芯片是在一个工业控制板上,主控是颗没有硬件I2C的老款MCU,但需要管理超过5个分布在板卡不同位置的I2C温度传感器和FRAM。布线空间紧张,软件模拟的时序在复杂电磁环境下总是不稳定。换上PCA9564后,所有问题迎刃而解,主控只需通过并口发送命令和数据,剩下的“脏活累活”全交给它,系统稳定性提升了一个数量级。下面,我就结合数据手册和实际项目经验,为你彻底拆解这颗芯片,从引脚功能、内部寄存器到实战编程,让你不仅能看懂手册,更能真正用起来。
2. 芯片深度解析:引脚、寄存器与工作原理
要驾驭PCA9564,不能只停留在“它是一个转换器”的概念上,必须深入其内部机制。这就像开车,不仅要会踩油门和刹车,还得知道发动机和变速箱是怎么工作的,遇到故障才能排查。
2.1 引脚功能与硬件连接要点
PCA9564提供DIP20、SO20、TSSOP20和HVQFN20多种封装,我们以最常见的DIP/SO/TSSOP封装为例,其引脚排列逻辑清晰,主要分为三组:并行总线侧、I2C总线侧和控制侧。
并行总线侧 (D0-D7, CE, RD, WR, A0, A1):
- D0-D7: 8位双向三态数据总线。这是主处理器与PCA9564交换数据、命令和状态的生命线。特别注意,D0是数据的最低位(LSB),这在后续组装数据字节(如从机地址)时至关重要。
- CE (Chip Enable): 片选信号,低电平有效。只有当CE为低时,PCA9564才会响应RD和WR信号。
- RD (Read Strobe) & WR (Write Strobe): 读/写选通信号,低电平有效。它们与A0、A1配合,决定当前是访问哪个内部寄存器。时序上要留意:写操作发生在WR的上升沿,读操作始于RD的下降沿。你的主处理器总线时序必须满足芯片手册中的建立和保持时间要求。
- A0, A1: 地址输入线。这两根线决定了访问哪个内部寄存器,具体映射我们稍后在寄存器部分详解。
I2C总线侧 (SDA, SCL):
- SDA & SCL: 标准的I2C串行数据和时钟线。关键点:这两根引脚是开漏(Open-Drain)输出。这意味着外部必须接上拉电阻!电阻值根据总线电容和速度选择,通常在2.2kΩ到10kΩ之间。快速模式下,电阻值应更小以确保边沿速度。
- INT (Interrupt Request): 中断请求输出,低电平有效,开漏输出。当PCA9564需要处理器介入时(如完成一次字节传输、收到自身地址等),会拉低此引脚。同样,外部需要上拉电阻。你可以选择查询或中断方式来处理PCA9564的事件,中断方式效率更高。
电源与控制侧 (VDD, VSS, RESET, DNU):
- VDD, VSS: 电源和地。工作电压范围是2.3V到3.6V,这是一个3.3V系统的典型电压。惊喜的是,其I/O引脚是5V耐受的,这意味着即使你的主处理器是5V系统,也可以直接连接(除了I2C总线需匹配电平),兼容性很好。
- RESET: 复位引脚,低电平有效。拉低会清零所有内部寄存器,I2C状态机复位。上电后或总线出现严重错误(如状态码70H, 90H, 00H)时,必须通过此引脚进行硬件复位,这是恢复通信的唯一可靠方法。
- DNU (Do Not Use): 第9脚。务必悬空!内部已下拉,切勿接地或接电源。
实操心得一:电源与上拉电阻在实际布线中,VDD的退耦电容(通常是一个100nF的陶瓷电容紧贴芯片电源引脚)必不可少,它能滤除高频噪声,防止内部振荡器工作不稳定。对于SDA、SCL和INT这三个开漏引脚的上拉电阻,我的经验是:如果总线设备少、走线短(<10cm),用4.7kΩ;如果设备多或走线长,用2.2kΩ。可以用示波器观察一下上升沿,如果太缓(超过快速模式规定的300ns),就需要减小电阻值。另外,将上拉电阻的电源连接到与PCA9564 VDD相同的3.3V网络,可以避免电平不匹配问题。
2.2 核心寄存器详解:控制芯片的“遥控器”
PCA9564内部有5个可通过A0/A1寻址的寄存器,它们是软件驱动开发的核心。理解每个比特位的含义,是编写稳定驱动代码的前提。
寄存器地址选择 (A1, A0):
A1=0, A0=0: 读操作访问状态寄存器(I2CSTA),写操作访问超时寄存器(I2CTO)。这是唯一一个地址对应两个不同功能的寄存器,读写方向决定访问对象。A1=0, A0=1: 访问数据寄存器(I2CDAT)。A1=1, A0=0: 访问自身地址寄存器(I2CADR)。A1=1, A0=1: 访问控制寄存器(I2CCON)。
1. 控制寄存器 (I2CCON) - 0x03 (当A1=1, A0=1时)这是最重要的寄存器,用于配置和操控I2C传输流程。
| 位 | 符号 | 名称与功能 |
|---|---|---|
| 7 | ENSIO | SIO使能位。1=使能I2C功能,SDA/SCL引脚受控;0=禁用,SDA/SCL高阻,忽略输入。关键点:从0置1后,内部振荡器需要最多500µs启动时间,之后才能进行I2C操作。 |
| 6 | STA | 起始标志位。1=尝试产生START条件(总线空闲时)或重复START条件(已为主机时);0=不产生START条件。 |
| 5 | STO | 停止标志位。1=产生STOP条件(主机模式下);STOP条件发出后硬件自动清零。0=不产生STOP条件。注意:STA和STO同时置1,会先发STOP再发START。 |
| 4 | SI | 串行中断标志位。只读。当I2C状态机进入一个新的有效状态(除了F8H),硬件自动置1。此时SCL线会被拉低以暂停总线,等待CPU处理。必须通过软件写0来清除此位,总线传输才会继续。 |
| 3 | AA | 应答标志位。1=在特定情况下返回ACK(低电平);0=返回NACK(高电平)。用于控制是否应答自身地址或接收到的数据。 |
| 2:0 | CR[2:0] | 时钟速率选择位。仅当芯片作为I2C主机时有效,用于设置SCL时钟频率。 |
时钟速率选择 (CR2, CR1, CR0):这是配置主机模式下SCL频率的关键。手册给出了一个参考表,但要注意标注:这些频率是近似的,受温度、电压、工艺影响。如果你的应用严格要求标准模式(SCL < 100kHz),不要使用100b(标称88kHz),因为某些条件下它可能略高于100kHz。稳妥起见,使用101b(标称59kHz)或110b(44kHz)。
| CR2 | CR1 | CR0 | 标称SCL频率 (kHz) | 备注 |
|---|---|---|---|---|
| 0 | 0 | 0 | 330 | 快速模式+ |
| 0 | 0 | 1 | 288 | 快速模式 |
| 0 | 1 | 0 | 217 | 快速模式 |
| 0 | 1 | 1 | 146 | 快速模式 |
| 1 | 0 | 0 | 88 | 慎用于标准模式 |
| 1 | 0 | 1 | 59 | 推荐用于标准模式 |
| 1 | 1 | 0 | 44 | 标准模式 |
| 1 | 1 | 1 | 36 | 标准模式 |
2. 状态寄存器 (I2CSTA) - 0x00 (读操作时)这是一个8位只读寄存器,高5位是状态码,低3位恒为0。它是整个驱动程序的“导航仪”。芯片每完成一个小的总线动作(如发出START、发送完地址、收到ACK等),就会进入一个新的状态,并将状态码写入此寄存器,同时拉低INT引脚(如果使能了中断)。你的代码必须根据这个状态码来决定下一步做什么。状态码F8H表示“无可用状态信息”,通常出现在总线空闲或发出STOP条件后,此时不会产生中断。
3. 数据寄存器 (I2CDAT) - 0x01用于存放即将发送或刚刚接收到的8位数据。特别注意其数据格式:当作为主机发送从机地址时,你需要将7位地址左移1位,并将最低位(R/W位)置0(写)或1(读),然后写入I2CDAT。例如,要向地址0x50(二进制101 0000)的设备写数据,则需要写入(0x50 << 1) | 0x00 = 0xA0。
4. 自身地址寄存器 (I2CADR) - 0x02当PCA9564作为从机时,此寄存器存放它的7位I2C从机地址。需要将地址写入bit7-bit1,bit0应写0。例如,若想设置从机地址为0x30,则写入0x30 << 1 = 0x60。
5. 超时寄存器 (I2CTO) - 0x00 (写操作时)用于使能和设置超时时间。Bit7(TE)为1使能超时功能。Bit6-bit0(TO[6:0])设置超时值,超时周期 ≈(TO[6:0] + 1) * 113.7 µs。这个功能非常有用,可以防止总线因某个设备故障(一直拉低SCL)而永久锁死。当检测到SCL被拉低超过设定时间,芯片会进入总线错误状态(状态码90H),并释放总线。
3. 实战驱动开发:从初始化到完整传输
理解了寄存器,我们就可以动手编写驱动了。驱动开发的核心是状态机编程。PCA9564内部已经实现了一个I2C协议状态机,我们的软件需要根据I2CSTA寄存器反映的状态,执行相应的操作(如写数据、读数据、设置控制位),然后清除SI标志,让状态机继续运行。
3.1 硬件初始化与基础函数
首先,我们需要实现底层的并行总线读写函数。这完全取决于你的主处理器类型。假设我们使用一个通用的8位MCU,通过GPIO模拟并行总线时序。
// 假设的硬件抽象层引脚定义 #define PCA9564_CE_PORT PORTB #define PCA9564_CE_PIN PB0 #define PCA9564_RD_PORT PORTB #define PCA9564_RD_PIN PB1 #define PCA9564_WR_PORT PORTB #define PCA9564_WR_PIN PB2 #define PCA9564_A0_PORT PORTB #define PCA9564_A0_PIN PB3 #define PCA9564_A1_PORT PORTB #define PCA9564_A1_PIN PB4 #define PCA9564_DATA_DDR DDRC #define PCA9564_DATA_PORT PORTC #define PCA9564_DATA_PIN PINC #define PCA9564_DATA_DIR_INPUT() (PCA9564_DATA_DDR = 0x00) #define PCA9564_DATA_DIR_OUTPUT() (PCA9564_DATA_DDR = 0xFF) // 写一个字节到PCA9564的指定寄存器 void PCA9564_WriteReg(uint8_t reg_addr, uint8_t data) { // 设置地址线 A1, A0 if (reg_addr & 0x01) SET_PIN_HIGH(PCA9564_A0_PORT, PCA9564_A0_PIN); else SET_PIN_LOW(PCA9564_A0_PORT, PCA9564_A0_PIN); if (reg_addr & 0x02) SET_PIN_HIGH(PCA9564_A1_PORT, PCA9564_A1_PIN); else SET_PIN_LOW(PCA9564_A1_PORT, PCA9564_A1_PIN); // 数据线设置为输出 PCA9564_DATA_DIR_OUTPUT(); PCA9564_DATA_PORT = data; // 产生写脉冲:CE=0, WR=0 -> WR=1,在WR上升沿锁存数据 SET_PIN_LOW(PCA9564_CE_PORT, PCA9564_CE_PIN); SET_PIN_LOW(PCA9564_WR_PORT, PCA9564_WR_PIN); delay_ns(50); // 等待数据建立时间,具体值查手册 SET_PIN_HIGH(PCA9564_WR_PORT, PCA9564_WR_PIN); delay_ns(50); // 等待数据保持时间 SET_PIN_HIGH(PCA9564_CE_PORT, PCA9564_CE_PIN); PCA9564_DATA_PORT = 0x00; // 释放数据线 } // 从PCA9564的指定寄存器读一个字节 uint8_t PCA9564_ReadReg(uint8_t reg_addr) { uint8_t data; // 设置地址线 A1, A0 if (reg_addr & 0x01) SET_PIN_HIGH(PCA9564_A0_PORT, PCA9564_A0_PIN); else SET_PIN_LOW(PCA9564_A0_PORT, PCA9564_A0_PIN); if (reg_addr & 0x02) SET_PIN_HIGH(PCA9564_A1_PORT, PCA9564_A1_PIN); else SET_PIN_LOW(PCA9564_A1_PORT, PCA9564_A1_PIN); // 数据线设置为输入(高阻) PCA9564_DATA_DIR_INPUT(); // 确保内部上拉关闭或外部无驱动,PCA9564会驱动数据线 // 产生读脉冲:CE=0, RD=0,在RD下降沿后数据有效 SET_PIN_LOW(PCA9564_CE_PORT, PCA9564_CE_PIN); SET_PIN_LOW(PCA9564_RD_PORT, PCA9564_RD_PIN); delay_ns(100); // 等待数据有效访问时间 data = PCA9564_DATA_PIN; // 读取数据 SET_PIN_HIGH(PCA9564_RD_PORT, PCA9564_RD_PIN); SET_PIN_HIGH(PCA9564_CE_PORT, PCA9564_CE_PIN); return data; }有了读写函数,就可以进行芯片初始化了。
// PCA9564 初始化 void PCA9564_Init(void) { // 1. 硬件复位(如果RESET引脚由MCU控制) SET_PIN_LOW(PCA9564_RESET_PORT, PCA9564_RESET_PIN); delay_ms(1); // 保持低电平至少一段时间 SET_PIN_HIGH(PCA9564_RESET_PORT, PCA9564_RESET_PIN); delay_ms(1); // 等待复位完成 // 2. 写超时寄存器(可选,但推荐使能以增加鲁棒性) // 例如:设置超时约为 (0x7F + 1) * 113.7µs ≈ 14.6ms PCA9564_WriteReg(0x00, 0xFF); // TE=1, TO[6:0]=0x7F // 3. 设置自身地址(如果作为从机使用) // PCA9564_WriteReg(0x02, (MY_SLAVE_ADDR << 1)); // 假设不作为从机,可写0 // 4. 配置控制寄存器:使能SIO,设置时钟速率,清空标志位 // CR[2:0]=101,选择约59kHz的SCL(标准模式) // AA=1 (使能应答), SI=0, STO=0, STA=0, ENSIO=1 uint8_t ctrl_val = (1 << 7) | (1 << 3) | (0x05); // ENSIO=1, AA=1, CR=101 PCA9564_WriteReg(0x03, ctrl_val); // 5. 等待内部振荡器启动(最多500µs) delay_us(600); // 留有余量 }3.2 主机模式下的完整数据传输流程
我们以实现一个最常见的“主机写数据到从机”流程为例,详细解读状态机的跳转。这个过程对应数据手册中的“Master Transmitter Mode”流程图和状态表。
假设我们要向一个I2C EEPROM(地址0xA0,写操作)的特定地址0x0100写入两个字节数据0x55和0xAA。标准I2C写序列是:START->发送设备地址+W->收到ACK->发送内存地址高字节->收到ACK->发送内存地址低字节->收到ACK->发送数据字节1->收到ACK->发送数据字节2->收到ACK->STOP。
我们的驱动代码需要围绕状态码来组织。通常我们会用一个函数来等待并处理状态码。
// 等待SI标志置位,并返回状态码 uint8_t PCA9564_WaitAndGetStatus(void) { uint8_t status; // 方式1:查询方式(简单,但占用CPU) do { status = PCA9564_ReadReg(0x00); // 读I2CSTA } while ((status & 0xF8) == 0xF8); // 状态码F8H表示无中断,继续等待 // 方式2:中断方式(更高效,需配置MCU外部中断) // 在中断服务程序里读取状态码并设置全局变量 return status; } // 主机写数据函数 uint8_t PCA9564_MasterWrite(uint8_t slave_addr, uint16_t mem_addr, uint8_t *data, uint8_t len) { uint8_t status; uint8_t ret_val = 0; // 0=成功,其他=错误 // 步骤1:发送START条件 // 设置STA=1, SI=0, 其他位保持。注意:写I2CCON会清除SI位! PCA9564_WriteReg(0x03, (1<<7) | (1<<6) | (1<<3) | (0x05)); // ENSIO=1, STA=1, AA=1, CR=101, SI=0 status = PCA9564_WaitAndGetStatus(); if (status != 0x08) { // 期望状态码 08H: START已发送 // 错误处理,可能是总线被占用 ret_val = 1; goto exit; } // 步骤2:发送从机地址 + 写位 (R/W=0) PCA9564_WriteReg(0x01, slave_addr & 0xFE); // 确保最低位是0 // 清除SI位以继续传输。写I2CCON时,STA位会自动清零(如果START已发出)。 PCA9564_WriteReg(0x03, (1<<7) | (1<<3) | (0x05)); // ENSIO=1, AA=1, CR=101, SI=0, STA=0 status = PCA9564_WaitAndGetStatus(); if (status == 0x18) { // 0x18: SLA+W 已发送,收到ACK。很好,继续。 } else if (status == 0x20) { // 0x20: SLA+W 已发送,收到NACK。从机无应答,失败。 ret_val = 2; goto exit; } else if (status == 0x38) { // 0x38: 仲裁丢失。在多主系统中可能发生。 ret_val = 3; goto exit; } else { // 其他未预期的状态 ret_val = 4; goto exit; } // 步骤3:发送内存地址高字节 PCA9564_WriteReg(0x01, (mem_addr >> 8) & 0xFF); PCA9564_WriteReg(0x03, (1<<7) | (1<<3) | (0x05)); // 清除SI status = PCA9564_WaitAndGetStatus(); if (status != 0x28) { // 0x28: 数据字节已发送,收到ACK ret_val = 5; goto exit; } // 步骤4:发送内存地址低字节 PCA9564_WriteReg(0x01, mem_addr & 0xFF); PCA9564_WriteReg(0x03, (1<<7) | (1<<3) | (0x05)); // 清除SI status = PCA9564_WaitAndGetStatus(); if (status != 0x28) { ret_val = 6; goto exit; } // 步骤5:循环发送数据字节 for (uint8_t i = 0; i < len; i++) { PCA9564_WriteReg(0x01, data[i]); PCA9564_WriteReg(0x03, (1<<7) | (1<<3) | (0x05)); // 清除SI status = PCA9564_WaitAndGetStatus(); if (status != 0x28) { ret_val = 7 + i; goto exit; } } // 步骤6:发送STOP条件 // 设置STO=1, SI=0。STO会在STOP条件发出后自动清零。 PCA9564_WriteReg(0x03, (1<<7) | (1<<5) | (1<<3) | (0x05)); // ENSIO=1, STO=1, AA=1, CR=101, SI=0 // 发送STOP后,状态会变为F8H,且不会产生SI中断。我们可以直接返回。 // 等待一小段时间确保STOP完成 delay_us(10); exit: // 如果中途出错,最好强制发送STOP并复位总线 if (ret_val != 0) { PCA9564_WriteReg(0x03, (1<<7) | (1<<5) | (1<<3) | (0x05)); // 发STOP delay_us(10); // 可以考虑进行一次硬件复位 // PCA9564_Init(); } return ret_val; }实操心得二:状态处理与错误恢复上面的代码框架展示了最基本的成功路径处理。在实际项目中,错误处理必须更健壮。例如,状态
0x38(仲裁丢失)在多主系统中是正常现象,你的代码不应该直接报错退出,而应该回到空闲状态,等待随机时间后重试发送START。此外,任何非预期状态都应触发复位序列。一个稳健的驱动,其错误处理代码量可能比正常流程还要多。
3.3 主机读数据流程与从机模式配置
主机读流程(Master Receiver Mode)与写流程类似,但需要在发送从机地址(带R/W=1)后,切换为接收模式,并通过设置AA位来控制是否发送最后一个NACK。
从机模式(Slave Receiver/Transmitter Mode)的配置则更简单。主要步骤是:
- 在I2CADR寄存器中写入自身的7位从机地址(左移1位)。
- 在I2CCON寄存器中,确保ENSIO=1,AA=1(使能应答自身地址)。
- 之后,PCA9564就会在总线上监听自己的地址。当被寻址时,会产生中断(SI置位),状态码会是
0x60(被写)或0xA8(被读)。你的中断服务程序需要根据状态码,执行相应的数据收发操作,流程同样遵循状态表(表4和表5)。
从机模式的一个高级用法是利用AA位实现动态地址响应。当你将AA位清零后,PCA9564将不再应答自己的从机地址,相当于暂时从总线“隐身”,但仍在监听总线。这可以用于实现“广播地址”监听或复杂的多主从协议。
4. 高级应用、调试技巧与常见问题排查
掌握了基本读写,我们来看看一些进阶用法和实战中必然遇到的“坑”。
4.1 多主系统与仲裁丢失处理
PCA9564支持多主能力。当多个主机同时发起传输时,I2C总线通过“线与”机制进行仲裁。PCA9564在仲裁中失败时,会进入状态0x38,并自动从主机模式切换到从机模式(如果AA=1,还可能被寻址)。
处理策略:
- 检测到状态
0x38。 - 立即读取I2CDAT寄存器,以获取仲裁丢失时总线上的数据(可能是其他主机的地址)。
- 根据你的应用逻辑决定下一步:通常是放弃当前传输,等待一个随机时间(如用随机数生成延时)后重试,以避免再次冲突。
- 在重试前,务必确保STA和STO位已正确设置。通常的做法是重新初始化I2CCON寄存器(设置STA=1, SI=0),然后等待新的START完成。
4.2 超时功能与总线锁死恢复
总线锁死是I2C系统中最令人头疼的问题之一,通常是由于某个从设备故障,持续拉低SCL或SDA线导致的。PCA9564的超时寄存器(I2CTO)是应对此问题的利器。
配置与原理:
- 使能:向I2CTO寄存器写入
0xFF(TE=1,超时值最大)。 - 作用:当PCA9564作为主机试图发送START,但SCL线被长期拉低时;或在任何模式下,SCL线被拉低超过设定时间,芯片会判定为总线错误。
- 表现:状态寄存器会变为
0x90(SCL stuck LOW)。 - 恢复:进入
0x90或0x70(SDA stuck LOW)状态后,仅靠软件操作寄存器无法恢复!必须给RESET引脚一个低电平脉冲进行硬件复位。因此,在设计电路时,最好用MCU的一个GPIO来控制PCA9564的RESET引脚,以便在软件中执行强制复位。
void PCA9564_RecoverFromBusError(void) { // 1. 尝试发送STOP(可能无效,但尝试一下) PCA9564_WriteReg(0x03, (1<<7) | (1<<5) | (0x05)); // ENSIO=1, STO=1, CR=101 delay_ms(1); // 2. 禁用SIO(释放总线) PCA9564_WriteReg(0x03, (0<<7) | (0x05)); // ENSIO=0, CR=101 delay_ms(1); // 3. 硬件复位(如果RESET引脚可控) SET_PIN_LOW(PCA9564_RESET_PORT, PCA9564_RESET_PIN); delay_ms(10); // 保持低电平足够时间 SET_PIN_HIGH(PCA9564_RESET_PORT, PCA9564_RESET_PIN); delay_ms(1); // 4. 重新初始化 PCA9564_Init(); }4.3 调试技巧与实战问题排查
调试I2C问题,逻辑分析仪或带I2C解码功能的示波器是必备工具。以下是一些常见问题的排查思路:
问题1:通信完全无响应,读回的状态码一直是0xF8。
- 检查硬件:首先用万用表测量VDD、VSS。用示波器查看SDA、SCL线上是否有上拉电压(应为3.3V高电平)。检查RESET引脚是否为高电平。
- 检查初始化:确认已正确写入I2CCON寄存器使能ENSIO,并等待了足够的振荡器启动时间(>500µs)。可以尝试读回I2CCON寄存器,确认写入的值是否正确。
- 检查并行接口:用示波器检查CE、WR、RD、A0、A1和数据线D0-D7的时序,确保满足芯片手册中的建立和保持时间要求。一个常见的错误是读写脉冲宽度太窄。
问题2:能发送START,但发送地址后收到NACK(状态码0x20或0x48)。
- 检查从机地址:确认写入I2CDAT的地址格式是否正确(7位地址左移1位,最低位是R/W)。确认从设备物理地址是否正确,许多设备有地址选择引脚。
- 检查从设备:确认从设备已上电,工作正常。用逻辑分析仪抓取总线波形,看从机是否在ACK时钟脉冲期间拉低了SDA线。
- 检查总线负载:总线上设备是否过多?总线电容是否过大导致边沿太慢?尝试减小上拉电阻值。
问题3:通信间歇性失败,偶尔成功。
- 检查电源噪声:在PCA9564的VDD引脚附近增加一个10µF的钽电容并联一个100nF的陶瓷电容。
- 检查时序:可能是MCU并行总线操作太快,PCA9564来不及响应。在读写操作间增加微小延时。
- 检查软件状态机:确保在每个状态处理后都正确清除了SI位。中断服务程序是否过长,导致错过了某些状态处理?状态判断逻辑是否有遗漏的分支?
问题4:作为从机时,无法被主机寻址。
- 检查I2CADR寄存器:确认写入的地址值正确(7位地址左移1位)。
- 检查AA位:在从机模式下,I2CCON中的AA位必须为1,否则不会应答自身地址。
- 检查总线冲突:总线上是否有其他设备使用了相同的地址?
实操心得三:状态机调试法最有效的调试方法是打印状态码。在每个
PCA9564_WaitAndGetStatus()函数后,将获取到的状态码通过串口打印出来。对照数据手册中的状态表(表2-6),你可以精确知道I2C总线进行到了哪一步,是在发START、发地址、发数据还是等待ACK。这比单纯看波形要直观得多。例如,如果卡在0x08,说明START已发出但没执行下一步,问题可能在你的代码没有及时写地址数据到I2CDAT并清除SI。如果频繁出现0x38,说明总线存在仲裁,可能是你的代码在多主环境中处理不当。