资讯动态

STM32F103软件IIC驱动PCA9555:16路IO扩展与排查

发布时间:2026/10/26 17:30:54 来源:尧图企业网站定制
简介面向嵌入式开发者与STM32初学者这是一套围绕STM32F103软件模拟IIC并访问PCA9555 IO扩展芯片的完整工程资料可用于GPIO资源不足时的端口扩展实验也可作为裸机IIC时序学习与移植参考。压缩包共127个文件、约2MB以32个c源文件和33个h头文件为主体保留Keil工程配置及hex、axf等编译输出并含有少量脚本与说明文件便于直接打开对照。已有4838人学习。资料完整呈现GPIO推挽输出与上拉配置、起始与停止条件、7位地址及读写位、应答处理以及对输入、输出、中断使能等寄存器的操作思路读者可据此理解时序调试、寄存器读写和常见通信故障排查并快速将模拟IIC与PCA9555驱动迁移到自己的项目中。1. 从两根线到十六个IOPCA9555 与软件 IIC 的适用边界STM32F103 的硬件 IIC 外设不是不能用但在引脚被占死、总线设备地址固定、时序需要微调的场合用 GPIO 模拟 IIC 访问 PCA9555 更省心。PCA9555 是 16 位 I/O 扩展器IIC 接口7 位地址由 A2/A1/A0 决定两组 8 位端口可独立配置输入或输出适合按键面板、LED 指示、继电器阵列这类“IO 不够用”的场景。模拟 IIC 的价值不在替代硬件外设而在于把 SCL/SDA 挪到任意 GPIO、把半周期延时调到自己能接受的档位并且出问题时能逐位看波形。这套代码面向 STM32F103 最小系统板和 3.3V 供电的 PCA9555 模块新手能按步骤点亮端口熟手能对照时序参数和地址算法检查边界。2. 硬件与地址先对齐PCA9555 最小系统与 7 位地址算法2.1 PCA9555 引脚、上拉与供电的底线PCA9555 的接线不复杂但先要把几个“底线”钉死。VDD 和 GND 必须和 MCU 共地模块供电优先用 3.3V和 STM32F103 的 GPIO 电平一致。SCL、SDA 各接一只 4.7k 上拉到 VDDINT 是开漏输出不用中断也要上拉或明确悬空处理。A0、A1、A2 不能悬空必须接 VDD 或 GND否则地址会漂扫描时能看到“有时应答、有时不应答”的玄学现象。P0_0 到 P0_7、P1_0 到 P1_7 是 16 个 I/O上电默认全部为输入配置寄存器默认 0xFF这一点后面写输出时还会碰到。引脚方向接法说明VDD电源3.3V与 MCU 同电平5V 供电要确认总线电平GND地共地不共地时读到的全是假 ACKSCL输入MCU GPIO 4.7k 上拉开漏驱动SDA双向MCU GPIO 4.7k 上拉开漏驱动读时释放A0/A1/A2输入接 VDD 或 GND决定 7 位地址低三位INT输出开漏需上拉不用中断可不接但别让它拉低总线P0/P1I/O按负载接输入高阻输出锁存PCA9555 内部寄存器按命令字节寻址常用的是下面这几个。写配置之前先读一遍能排除“模块默认状态和你想的不一样”的低级翻车。命令字节寄存器读/写上电默认作用0x00输入端口 0读反映引脚P0 引脚实际电平0x01输入端口 1读反映引脚P1 引脚实际电平0x02输出端口 0读/写0xFFP0 输出锁存0x03输出端口 1读/写0xFFP1 输出锁存0x04极性反转 0读/写0x00输入极性反转0x05极性反转 1读/写0x00输入极性反转0x06配置 0读/写0xFF0输出1输入0x07配置 1读/写0xFF0输出1输入注意PCA9555 上电默认 16 个口全是输入。只写输出寄存器 0x02/0x03 不会让引脚变输出必须先把 0x06/0x07 对应位清 0。2.2 地址字节怎么算A2/A1/A0 与读写位PCA9555 的 7 位地址固定高四位是 0x20 的高位部分低三位由 A2、A1、A0 决定。公式是addr7 0x20 | (A2 2) | (A1 1) | A0。A0 到 A2 全接地时是 0x20全接 VDD 时是 0x27一条总线上最多挂 8 片。IIC 实际发出去的是 8 位地址字节写操作是addr7 1读操作是(addr7 1) | 1。很多人在这里翻车是把模块资料里的“写地址 0x40”又左移了一次结果发成 0x80扫描当然全无应答。A2A1A07 位地址写字节读字节0000x200x400x410010x210x420x430100x220x440x450110x230x460x471000x240x480x491010x250x4A0x4B1100x260x4C0x4D1110x270x4E0x4F读 PCA9555 时不是直接发读地址就完事标准流程是“写地址 命令字节 重复起始 读地址 读数据 NACK STOP”。命令字节告诉器件从哪个寄存器开始读读地址里的最低位为 1 表示主机要收数据。写流程则是“写地址 命令字节 数据 STOP”。这两个流程在后面的驱动函数里会各出现一次先把地址字节算对后面只是时序问题。2.3 用扫描代码确认总线0x20 到 0x27 的逐地址探测在写完整驱动之前先写一个只发地址字节的探测函数。它不读寄存器也不写数据只判断第 9 个时钟上从机有没有把 SDA 拉低。这个动作能把“接线错、地址错、供电错”三类问题先筛掉。/* 返回 1 表示该 7 位地址上有器件应答0 表示无应答 */ uint8_t pca9555_probe(uint8_t addr7) { uint8_t ack; iic_start(); /* 起始条件 */ ack iic_write_byte((addr7 1) | 0); /* 只发写地址字节 */ iic_stop(); /* 停止条件 */ return (ack 0) ? 1 : 0; /* iic_write_byte 返回 0 表示收到 ACK */ } void pca9555_scan(void) { uint8_t addr; for (addr 0x20; addr 0x27; addr) { if (pca9555_probe(addr)) { printf(PCA9555 found at 0x%02X\r\n, addr); } } }pca9555_probe的入参是 7 位地址函数内部左移并补读写位避免调用者把 8 位地址和 7 位地址混着传。iic_write_byte返回 0 表示从机在第 9 个时钟拉低 SDA返回 1 表示超时或无应答。扫描范围固定 0x20 到 0x27正好覆盖 A0-A2 的 8 种组合。printf需要工程里已经重定向串口没有串口时把打印换成点亮 LED 或置一个全局变量也行。扫描到地址只是第一步总线上别的器件也可能占 0x20 到 0x27。更稳的确认方式是接着读 0x06 配置寄存器上电默认应该是 0xFF读 0x00 输入寄存器值会随引脚电平变化。如果扫描有 ACK、读寄存器却全 0x00 或全 0xFF先别怀疑代码回头量 SDA/SCL 的静态电平和上拉电阻。提示扫描前把 SCL、SDA 都释放为高电平静态量到 3.3V 再上电。静态不是高电平后面所有时序都无从谈起。3. GPIO 模拟 IIC 的时序骨架从 START 到 STOP 的六个原子操作3.1 开漏输出与释放 SDA为什么推挽会翻车模拟 IIC 的 GPIO 必须配成开漏输出外部加上拉。开漏的“高”是释放总线由上拉电阻把线拉高“低”才是真正把线拉低。如果配成推挽输出主机输出高电平时从机想把 SDA 拉低两个输出对打轻则波形畸形重则引脚发热。STM32F103 的 HAL 里用GPIO_MODE_OUTPUT_OD同时把内部上拉打开作为兜底但外部 4.7k 不能省。SCL 只由主机驱动SDA 在写字节时由主机驱动在读字节和等 ACK 时主机要写 1 释放让从机控制。读 SDA 有两种做法一是把 SDA 临时切成输入模式读完再切回输出二是保持开漏输出先写 1 释放再直接读 IDR。后者切换少时序更干净我一般用后者。前提是输出寄存器对应位确实写成了 1否则读到的还是自己拉低的电平。#define IIC_SCL_PORT GPIOB #define IIC_SCL_PIN GPIO_PIN_6 #define IIC_SDA_PORT GPIOB #define IIC_SDA_PIN GPIO_PIN_7 #define SCL_H() HAL_GPIO_WritePin(IIC_SCL_PORT, IIC_SCL_PIN, GPIO_PIN_SET) #define SCL_L() HAL_GPIO_WritePin(IIC_SCL_PORT, IIC_SCL_PIN, GPIO_PIN_RESET) #define SDA_H() HAL_GPIO_WritePin(IIC_SDA_PORT, IIC_SDA_PIN, GPIO_PIN_SET) #define SDA_L() HAL_GPIO_WritePin(IIC_SDA_PORT, IIC_SDA_PIN, GPIO_PIN_RESET) #define SDA_READ() HAL_GPIO_ReadPin(IIC_SDA_PORT, IIC_SDA_PIN) void iic_gpio_init(void) { GPIO_InitTypeDef gpio {0}; __HAL_RCC_GPIOB_CLK_ENABLE(); gpio.Pin IIC_SCL_PIN | IIC_SDA_PIN; gpio.Mode GPIO_MODE_OUTPUT_OD; /* 开漏外部 4.7k 上拉 */ gpio.Pull GPIO_PULLUP; /* 内部弱上拉兜底 */ gpio.Speed GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOB, gpio); SCL_H(); SDA_H(); }这段初始化把 SCL、SDA 都置于释放状态。GPIO_MODE_OUTPUT_OD是关键GPIO_PULLUP只是内部弱上拉不能替代外部 4.7k。引脚宏单独抽出来换板子只改端口和引脚号。初始化最后两行把总线拉高给起始条件一个干净的静态起点。3.2 半周期延时与 100kHz 参数表模拟 IIC 的速度由半周期延时决定。标准模式 100kHz 对应 5us 半周期快速模式 400kHz 对应 1.25us 半周期。STM32F103 跑 72MHz 时用 DWT 周期计数器做微秒延时比较稳没有 DWT 就用定时器或校准过的循环。下面给的是可调宏先把IIC_DELAY_US设成 5跑通后再往下压。参数典型值作用调整建议IIC_DELAY_US5半周期延时约 100kHz先 5us 跑通再试 2us上拉电阻4.7k上升沿线长可降到 2.2k功耗会增大总线电容小于 400pF上升沿线越长越要缩短或降低速率ACK 超时200 次循环防从机拉死按主频调整别写成死等供电3.3V电平匹配5V 器件要确认 MCU 引脚耐受注意延时不是越小越好。PCA9555 支持 400kHz但模拟 IIC 的上升沿由外部上拉和线电容决定延时压到 1us 时如果上升沿还没到高电平从机可能把高位读错。3.3 起始、停止、写字节、读字节与 ACK 超时IIC 的原子操作就六个起始、停止、写一个字节、读一个字节、等 ACK、给 ACK。起始条件是 SCL 高时 SDA 由高变低停止条件是 SCL 高时 SDA 由低变高。写字节时先拉低 SCL再放 SDA然后拉高 SCL 让从机采样。读字节时主机先释放 SDA再拉高 SCL在 SCL 高期间读 SDA。static void iic_delay(void) { delay_us(5); /* 约 100kHz按需改成 2 试 250kHz */ } void iic_start(void) { SDA_H(); SCL_H(); iic_delay(); SDA_L(); /* SCL 高时 SDA 下降起始 */ iic_delay(); SCL_L(); iic_delay(); } void iic_stop(void) { SDA_L(); SCL_L(); iic_delay(); SCL_H(); iic_delay(); SDA_H(); /* SCL 高时 SDA 上升停止 */ iic_delay(); } /* 返回 0收到 ACK返回 1无应答或超时 */ uint8_t iic_write_byte(uint8_t dat) { uint8_t i; uint8_t ack; uint16_t timeout 0; for (i 0; i 8; i) { SCL_L(); iic_delay(); if (dat 0x80) { SDA_H(); } else { SDA_L(); } dat 1; iic_delay(); SCL_H(); iic_delay(); SCL_L(); } SDA_H(); /* 释放 SDA准备读 ACK */ iic_delay(); SCL_H(); iic_delay(); while (SDA_READ() GPIO_PIN_SET) { if (timeout 200) { SCL_L(); return 1; /* 从机没拉低超时 */ } iic_delay(); } ack 0; SCL_L(); iic_delay(); return ack; } /* ack0 主机回 ACKack1 主机回 NACK返回值是读到的字节 */ uint8_t iic_read_byte(uint8_t ack) { uint8_t i; uint8_t dat 0; SDA_H(); /* 释放 SDA交给从机驱动 */ for (i 0; i 8; i) { SCL_L(); iic_delay(); SCL_H(); iic_delay(); dat 1; if (SDA_READ() GPIO_PIN_SET) { dat | 0x01; } SCL_L(); iic_delay(); } if (ack 0) { SDA_L(); /* 主机回 ACK */ } else { SDA_H(); /* 主机回 NACK */ } iic_delay(); SCL_H(); iic_delay(); SCL_L(); SDA_H(); iic_delay(); return dat; }iic_start和iic_stop里的顺序不能换SCL 高电平期间 SDA 的跳变才被识别为起始或停止SCL 低电平期间 SDA 随便变。iic_write_byte先发最高位发完 8 位后释放 SDA再给第 9 个时钟读 ACK超时 200 次是为了防止从机把 SDA 拉死时程序卡在while里。iic_read_byte每读一位都先拉低 SCL 再释放 SDA读完后根据ack参数决定主机拉低还是释放最后一个字节必须传ack1发 NACK否则从机会继续占用总线。这套函数没有关中断也没有做时钟拉伸处理。PCA9555 一般不会拉低 SCL 做时钟拉伸但在多设备总线上如果某个从机拉低 SCL主机读 SDA 之前最好加超时。量产代码里我一般把iic_delay换成 DWT 延时并在iic_write_byte的 ACK 等待里保留超时返回别让一个坏器件把整机拖死。4. 读写 PCA9555 寄存器配置、输出、输入三步闭环4.1 命令字节与自动递增一次写两个端口PCA9555 的寄存器访问靠命令字节写操作是“START 写地址 命令字节 数据 STOP”读操作是“START 写地址 命令字节 重复 START 读地址 数据 NACK STOP”。命令字节写完以后器件内部地址指针会指向对应寄存器如果继续发数据指针自动递增所以可以一次写 0x02 和 0x03 两个输出端口也可以从 0x00 开始连续读两个输入端口。/* 写一个寄存器返回 0 成功1 失败 */ uint8_t pca9555_write_reg(uint8_t addr7, uint8_t reg, uint8_t val) { iic_start(); if (iic_write_byte((addr7 1) | 0)) { iic_stop(); return 1; } if (iic_write_byte(reg)) { iic_stop(); return 1; } if (iic_write_byte(val)) { iic_stop(); return 1; } iic_stop(); return 0; } /* 从 reg 开始连续写 len 个字节利用地址自动递增 */ uint8_t pca9555_write_regs(uint8_t addr7, uint8_t reg, const uint8_t *buf, uint8_t len) { uint8_t i; iic_start(); if (iic_write_byte((addr7 1) | 0)) { iic_stop(); return 1; } if (iic_write_byte(reg)) { iic_stop(); return 1; } for (i 0; i len; i) { if (iic_write_byte(buf[i])) { iic_stop(); return 1; } } iic_stop(); return 0; } /* 从 reg 开始连续读 len 个字节最后一个字节回 NACK */ uint8_t pca9555_read_regs(uint8_t addr7, uint8_t reg, uint8_t *buf, uint8_t len) { uint8_t i; iic_start(); if (iic_write_byte((addr7 1) | 0)) { iic_stop(); return 1; } if (iic_write_byte(reg)) { iic_stop(); return 1; } iic_start(); /* 重复起始 */ if (iic_write_byte((addr7 1) | 1)) { iic_stop(); return 1; } for (i 0; i len; i) { buf[i] iic_read_byte((i (len - 1)) ? 1 : 0); } iic_stop(); return 0; }pca9555_write_reg是最小写单元三个 ACK 任意一个失败都直接发 STOP 退出避免总线停在半截。pca9555_write_regs用len控制连续写写输出端口时传buf[0]到 P0、buf[1]到 P1。pca9555_read_regs里重复起始之后发读地址最后一个字节传ack1发 NACK再 STOP。addr7是 7 位地址函数内部统一左移调用时不要传 0x40 这种 8 位写地址。4.2 配置寄存器先写 0把默认输入改成输出配置寄存器 0x06 和 0x07 的每一位对应一个引脚写 0 是输出写 1 是输入。上电默认 0xFF所以第一次用必须写配置。只把 P0 全设输出、P1 全设输入时0x06 写 0x000x07 写 0xFF。如果只想改 P0 的低四位就先读 0x06改低四位再写回去避免把其他位误设成输出。/* port: 0 或 1dir: 0 输出1 输入 */ uint8_t pca9555_set_direction(uint8_t addr7, uint8_t port, uint8_t dir) { uint8_t reg (port 0) ? 0x06 : 0x07; return pca9555_write_reg(addr7, reg, dir); } /* 写一个端口输出锁存port 0/1val 8 位 */ uint8_t pca9555_write_port(uint8_t addr7, uint8_t port, uint8_t val) { uint8_t reg (port 0) ? 0x02 : 0x03; return pca9555_write_reg(addr7, reg, val); } /* 读一个端口输入寄存器port 0/1 */ uint8_t pca9555_read_port(uint8_t addr7, uint8_t port, uint8_t *val) { uint8_t reg (port 0) ? 0x00 : 0x01; return pca9555_read_regs(addr7, reg, val, 1); }pca9555_set_direction的dir参数是整字节0x00 全输出0xFF 全输入0x0F 表示低四位输出、高四位输入。pca9555_write_port只写输出锁存不改变方向所以调用前必须先写配置寄存器。pca9555_read_port读的是输入寄存器反映引脚实际电平如果端口被配置成输出读输入寄存器也能看到引脚电平但受外部负载影响不能完全代替输出锁存回读。4.3 完整驱动函数与主循环示例把上面的函数拼起来初始化顺序是“配置 P0 输出、P1 输入写 P0 初始电平再读 P1”。下面这段主循环每 500ms 翻转一次 P0同时把 P1 的按键状态读回来。LED 接 P0_0 到 P0_7 时注意限流电阻按键接 P1 时用 10k 上拉、按下接地。#define PCA_ADDR7 0x20 /* A2A1A00 */ void pca9555_init(void) { uint8_t cfg[2]; iic_gpio_init(); /* P0 全输出P1 全输入 */ cfg[0] 0x00; /* 配置 00输出 */ cfg[1] 0xFF; /* 配置 11输入 */ pca9555_write_regs(PCA_ADDR7, 0x06, cfg, 2); /* 输出端口先给一个安全电平 */ pca9555_write_port(PCA_ADDR7, 0, 0x00); pca9555_write_port(PCA_ADDR7, 1, 0xFF); } int main(void) { uint8_t in0, in1; uint8_t out 0x00; HAL_Init(); SystemClock_Config(); delay_init(); uart_init(115200); pca9555_init(); while (1) { out ^ 0xFF; pca9555_write_port(PCA_ADDR7, 0, out); if (pca9555_read_port(PCA_ADDR7, 0, in0) 0 pca9555_read_port(PCA_ADDR7, 1, in1) 0) { printf(IN00x%02X IN10x%02X\r\n, in0, in1); } else { printf(PCA9555 read fail\r\n); } HAL_Delay(500); } }pca9555_init先写两个配置字节利用自动递增一次写完 0x06 和 0x07再写输出端口给安全电平。主循环里out ^ 0xFF翻转 P0 的 8 个 LED读 P1 的按键状态。读失败时打印错误不继续用旧值避免把通信失败误判成按键状态。PCA_ADDR7按实际 A0-A2 改0x20 到 0x27 对应前面表格。提示写输出后想确认有没有写进去读 0x02/0x03 输出寄存器回读不要读 0x00/0x01 输入寄存器判断。输入寄存器受外部电路影响读回的值和输出锁存不是一回事。5. 避坑与排查模拟 IIC 访问 PCA9555 的五个高频翻车点5.1 总线无应答先查上拉、地址和供电现象扫描 0x20 到 0x27 全无 ACK或者偶尔能扫到、复位后又扫不到。原因通常不是代码而是 SDA/SCL 接反、上拉电阻缺失、PCA9555 的 VDD 没供上、A0-A2 悬空导致地址漂移或者 MCU 引脚配成了推挽输出。解决顺序是先断电量线SCL 和 SDA 对 VDD 的电阻应该在 4.7k 左右静态电压接近 3.3V再确认 A0-A2 各自接固定电平最后把 GPIO 改成开漏扫描代码只发地址字节不读寄存器。如果静态电平都对、扫描仍无 ACK用示波器看第 9 个时钟 SDA 有没有被从机拉低拉低说明地址对了但后续流程有问题没拉低就继续查地址和供电。5.2 写成功但端口不动配置寄存器与输出寄存器混淆现象pca9555_write_port返回 0串口打印成功但 P0 的 LED 不亮。原因多半是配置寄存器 0x06 还是默认 0xFF端口仍是输入或者写的是输入寄存器 0x00 而不是输出寄存器 0x02也有可能是只写了配置没写输出或者写完输出立刻读输入寄存器把外部电平当成写失败。解决是初始化先写 0x06/0x07再写 0x02/0x03写完回读 0x02/0x03 确认锁存值。LED 不亮时用万用表量 P0 引脚不要只看串口返回值。5.3 输入读数抖动上下拉、去抖与扫描节奏现象P1 接按键读到的值随机跳或者未接引脚全 1。原因是 PCA9555 配置成输入后引脚是高阻外部没有上拉或下拉线一长就拾取干扰按键没有硬件消抖软件又读得太快。解决是按键一端接 10k 上拉到 3.3V、另一端接地按下为低软件连续读两次间隔 10ms 一致才认IIC 速率先降到 100kHz电源脚加 0.1uF 去耦。未用引脚不要悬空配置成输出并给固定电平或者外部加下拉。5.4 多设备地址冲突与总线电容现象一条总线上挂两片 PCA9555只有一片响应或者波形上升沿变圆读数据偶尔错位。原因是 A0-A2 没有错开两片地址相同上拉电阻太大或总线电容超过 400pF。解决是把第二片的 A0 接 VDD地址改成 0x21上拉保持 4.7k线长超过 30cm 时缩短或降低速率多设备分两条总线不要把所有模块串在同一组排线上。地址冲突时扫描能扫到地址但读写数据会互相干扰表现像“时好时坏”。5.5 总线死锁与上电顺序9 个时钟恢复现象程序复位后扫描不到 PCA9555量到 SCL 是高、SDA 被拉低。原因是从机正在输出数据时主机复位时钟停了从机一直等下一个时钟或者上电顺序不对PCA9555 先上电并拉了 INT/SDA。解决是上电初始化时先发 9 个 SCL 时钟再发 STOP让从机把剩余字节吐完。下面这个恢复函数放在iic_gpio_init之后、正式扫描之前调用。void iic_bus_recover(void) { uint8_t i; SDA_H(); for (i 0; i 9; i) { SCL_L(); iic_delay(); SCL_H(); iic_delay(); } iic_stop(); /* 补一个停止条件把总线拉回空闲 */ }iic_bus_recover只做一件事在 SCL 上打 9 个时钟让从机把可能挂住的字节发完最后补 STOP。调用前 SDA 要先释放为高否则从机看到 SDA 一直低可能不退出。量产代码里还要加超时任何一次 ACK 等待超过 200 次循环就返回错误主循环重新初始化总线不要死等。上电顺序上PCA9555 和 MCU 尽量同时上电或者先给 MCU 上电再给扩展芯片上电减少从机拉死总线的概率。6. 进阶验证与量产习惯用回读、示波器和自检表把 IIC 锁死6.1 上电自检表模拟 IIC 没有硬件错误标志通信是否可信要靠回读和自检。我一般在上电初始化后跑一张小表读 0x06/0x07 配置寄存器确认等于写入值写 0x02 输出 0x55回读 0x02 确认锁存写 0x03 输出 0xAA回读 0x03读 0x00/0x01 输入寄存器记录当前引脚状态。任何一项失败就点亮错误指示不进入主循环。这样能把“偶发 ACK 失败”和“配置没写进去”分开省掉很多黑匣子式的猜。自检项操作期望失败处理配置回读读 0x06/0x07等于写入的 cfg重写一次仍失败报错输出回读写 0x020x55读 0x020x55检查写时序和地址第二端口回读写 0x030xAA读 0x030xAA检查自动递增输入快照读 0x00/0x01随引脚变化检查外部上下拉6.2 自检代码与波形检查uint8_t pca9555_self_test(uint8_t addr7) { uint8_t v; if (pca9555_read_regs(addr7, 0x06, v, 1) || v ! 0x00) return 1; if (pca9555_write_reg(addr7, 0x02, 0x55)) return 2; if (pca9555_read_regs(addr7, 0x02, v, 1) || v ! 0x55) return 3; if (pca9555_write_reg(addr7, 0x03, 0xAA)) return 4; if (pca9555_read_regs(addr7, 0x03, v, 1) || v ! 0xAA) return 5; return 0; }返回值直接对应失败步骤串口打印一个数字就能定位。示波器或逻辑分析仪看两处起始条件之后第 9 个时钟 SDA 是否被拉低以及 STOP 之后 SCL/SDA 是否都回到高电平。波形上如果 ACK 位是高的说明地址或器件没响应如果 STOP 后 SDA 一直低查从机是否挂死用前面的 9 时钟恢复。从那以后我每次给新板子上电都先跑一遍地址扫描和这张自检表再谈业务逻辑。模拟 IIC 的坑大多不在函数本身而在上拉、地址和配置顺序先把这三样锁死后面改引脚、换模块都只是改宏的事。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑