拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

PCA9698 驱动实战:40 路 I2C GPIO 扩展的寄存器、代码与避坑指南

PCA9698 驱动实战:40 路 I2C GPIO 扩展的寄存器、代码与避坑指南

简介:这份资源是面向Linux内核驱动开发者的PCA9698 GPIO扩展芯片驱动参考实现,针对内核2.6.28版本编写,解决嵌入式系统与物联网设备中I2C接口GPIO扩展的通信与控制问题。压缩包共2个文件,包含1个c源文件与1个h头文件,整体约3KB,其中c文件实现设备注册、I2C读写、中断处理及用户空间控制接口,h文件定义寄存器、错误码与API函数原型,结构精简便于快速移植。已有172人学习下载,适合具备一定内核基础、需要为其他内核版本或应用场景定制驱动的开发者参考。读者可从中掌握PCA9698的初始化配置、GPIO方向与中断触发设置、寄存器读写流程,并理解如何将芯片接入Linux I2C子系统,为工业自动化、智能家居等场景的远程控制与监控提供可复用的驱动骨架与排错思路。

1. PCA9698 扩展 GPIO 到底解决了什么:从 pca9698.c 看 40 路 IO 的落地价值

如果你手上有块板子,MCU 的 GPIO 已经被按键、LED、继电器、传感器片选占得七七八八,再想加一路风扇控制或者多接几个限位开关,就会发现引脚不够用了。PCA9698 就是在这种场景下被翻出来的:它是一颗 40 位(5 组 × 8 位)I2C 转并行 GPIO 扩展芯片,通过两根线就能把 40 路准双向 IO 挂到主控上。标题里的pca9698_gpio.rar_PCA9698_pca9698.c指向的正是这类驱动源码包——一个以pca9698.c为核心的 GPIO 扩展驱动实现。它适合谁?适合那些主控引脚吃紧、又不想换更大封装 MCU 的嵌入式工程师,尤其是做工业控制板、多路继电器板、矩阵按键面板的人。这一章先把这颗芯片和驱动的关系讲清楚,后面再落到寄存器、代码和踩坑。

2. PCA9698 的寄存器模型与 pca9698.c 驱动骨架

2.1 先搞懂 PCA9698 的 5 组 IO 和寄存器映射

PCA9698 内部把 40 路 IO 分成 Bank0 到 Bank4,每组 8 位,对应一个可读写的输出寄存器。它和常见的 PCA9555(16 位)不同,多了方向控制、输出驱动强度、输入锁存这些配置。核心寄存器大致分四类:器件控制、输出端口、输入端口、配置寄存器。每组 IO 有独立的输出寄存器地址,写入即改变引脚电平;输入寄存器反映当前引脚实际电平;配置寄存器决定该组是输入还是输出。I2C 从机地址由 A0~A2 三个引脚决定,最多挂 8 片,理论上能扩到 320 路 IO。

pca9698.c这类驱动的骨架通常包含:I2C 读写封装、寄存器地址宏定义、初始化函数、单引脚读写函数、整组读写函数。驱动不会替你决定业务逻辑,它只负责把「第几路 IO 设成什么电平」翻译成 I2C 时序。理解这一点,后面看代码就不会迷路。

2.2 从 pca9698.c 抽出的最小驱动框架

下面这段代码是这类驱动最常见的写法,用伪寄存器地址演示,实际地址以你手上的数据手册为准。它展示了初始化、写单引脚、读单引脚三个核心动作。

#include <stdint.h> #include <stdbool.h> /* PCA9698 从机地址,A2 A1 A0 全接地时为 0x20(7 位地址) */ #define PCA9698_ADDR 0x20 /* 输出寄存器基址,每组偏移 1,实际以数据手册为准 */ #define PCA9698_OUT_BASE 0x02 /* 配置寄存器基址,决定每组方向 */ #define PCA9698_CFG_BASE 0x0A /* 底层 I2C 写,返回 0 成功 */ extern int i2c_write(uint8_t addr, uint8_t reg, uint8_t val); extern int i2c_read(uint8_t addr, uint8_t reg, uint8_t *val); /* 初始化:把 5 组全部设为输出,默认输出高电平 */ int pca9698_init(void) { for (int bank = 0; bank < 5; bank++) { /* 配置寄存器写 0x00 表示该组全部为输出 */ if (i2c_write(PCA9698_ADDR, PCA9698_CFG_BASE + bank, 0x00) != 0) return -1; /* 输出寄存器写 0xFF,上电默认高,避免误触发 */ if (i2c_write(PCA9698_ADDR, PCA9698_OUT_BASE + bank, 0xFF) != 0) return -1; } return 0; } /* 写单路 IO,pin 范围 0~39 */ int pca9698_write_pin(uint8_t pin, bool level) { uint8_t bank = pin / 8; uint8_t bit = pin % 8; uint8_t reg = PCA9698_OUT_BASE + bank; uint8_t cur; if (i2c_read(PCA9698_ADDR, reg, &cur) != 0) return -1; if (level) cur |= (1 << bit); else cur &= ~(1 << bit); return i2c_write(PCA9698_ADDR, reg, cur); } /* 读单路 IO 实际电平 */ int pca9698_read_pin(uint8_t pin, bool *level) { uint8_t bank = pin / 8; uint8_t bit = pin % 8; uint8_t reg = PCA9698_OUT_BASE + bank; /* 读回输出寄存器即当前驱动电平 */ uint8_t cur; if (i2c_read(PCA9698_ADDR, reg, &cur) != 0) return -1; *level = (cur >> bit) & 0x01; return 0; }

逻辑说明:pca9698_init先把 5 组配置成输出,再把输出寄存器全置 1,这是为了防止上电瞬间继电器或 LED 被误触发。pca9698_write_pin采用「读-改-写」方式,只动目标位,不影响同组其他引脚。pca9698_read_pin这里读的是输出寄存器,反映的是你写下去的电平;如果要读外部输入的实际电平,需要把该组配置成输入,再读输入寄存器。

参数说明:PCA9698_ADDR由硬件 A0~A2 决定,常见 0x20~0x27;PCA9698_OUT_BASE和PCA9698_CFG_BASE必须对照数据手册,不同批次或封装可能不同;pin的 0~39 映射到 Bank0~Bank4,跨组操作要重新计算 bank 和 bit。

2.3 初始化顺序为什么不能随便调

很多翻车现场出在初始化顺序上。正确顺序是:先配置方向寄存器,再写输出寄存器,最后如果需要中断功能再配置中断相关寄存器。如果先写输出再配方向,某些批次芯片会在方向切换瞬间把输出寄存器内容打到引脚上,导致继电器「啪」一声吸合。我一般会在pca9698_init里先把所有输出寄存器写成安全电平,再统一配方向,最后再写一次输出寄存器确认。这个顺序在pca9698.c里往往被简化成两行,但实际项目里值得多写几行。

3. 把 pca9698.c 跑起来:I2C 对接、地址确认与最小验证

3.1 硬件连接与 I2C 地址确认

PCA9698 的 SDA、SCL 接主控 I2C,A0~A2 决定地址,RESET 引脚建议接主控 GPIO 或上拉,INT 引脚如果不用可以悬空但最好上拉。上电后先别急着写驱动,用 I2C 扫描工具确认地址。Linux 下可以用i2cdetect,裸机环境可以写个简单扫描循环。

# Linux 下确认 PCA9698 是否在 I2C-1 总线上 i2cdetect -y 1 # 预期看到 0x20~0x27 中某个地址出现

如果扫描不到,先查上拉电阻。I2C 总线没有上拉,或者上拉阻值太大(比如 10k 以上),在 400kHz 下波形会塌,芯片根本不应答。常见做法是 2.2k~4.7k 上拉到 3.3V。另一个坑是 A0~A2 悬空,悬空电平不确定,地址会飘,必须明确接高或接低。

3.2 用最小验证代码确认读写通路

地址确认后,写一段最小验证:把 Bank0 全部置高,再全部置低,用万用表或 LED 看电平变化。这一步能排除 90% 的硬件问题。

/* 最小验证:Bank0 交替闪烁,确认 I2C 通路和输出功能 */ void pca9698_blink_test(void) { pca9698_init(); while (1) { i2c_write(PCA9698_ADDR, PCA9698_OUT_BASE + 0, 0xFF); /* Bank0 全高 */ delay_ms(500); i2c_write(PCA9698_ADDR, PCA9698_OUT_BASE + 0, 0x00); /* Bank0 全低 */ delay_ms(500); } }

逻辑说明:直接写整组输出寄存器,不经过读-改-写,减少变量。如果 LED 不闪,先量 SDA/SCL 波形,再看 RESET 是否被拉低。参数说明:delay_ms按你的平台实现;如果 Bank0 接的是继电器,建议把周期拉长到 1 秒以上,避免机械触点频繁动作。

3.3 输入模式下的读回验证

把某一组配置成输入,外部给电平,读输入寄存器。注意 PCA9698 的输入寄存器地址和输出寄存器不同,别读错。

/* 把 Bank1 设为输入,读回外部电平 */ int pca9698_read_bank1(uint8_t *val) { /* 配置 Bank1 为输入:对应配置位写 1 */ if (i2c_write(PCA9698_ADDR, PCA9698_CFG_BASE + 1, 0xFF) != 0) return -1; /* 读输入寄存器,地址以数据手册为准 */ return i2c_read(PCA9698_ADDR, PCA9698_IN_BASE + 1, val); }

逻辑说明:配置寄存器写 1 表示输入,写 0 表示输出,这点和 PCA9555 相反,容易记混。参数说明:PCA9698_IN_BASE需要查手册;输入引脚不要悬空,否则读回值随机,必须外部上拉或下拉。

4. 避坑与排查:PCA9698 驱动最常见的 5 个翻车点

4.1 现象:I2C 扫描不到地址,或时有时无

原因:A0~A2 悬空、上拉电阻缺失或阻值过大、RESET 引脚被拉低、电源未上电。解决:A0~A2 明确接高或接低;SDA/SCL 加 2.2k~4.7k 上拉;RESET 接高或由主控释放;用示波器看电源和总线波形。

4.2 现象:写进去的电平不对,或者同组其他引脚被带动

原因:用了「读-改-写」但读回的是输入寄存器而不是输出寄存器,或者多任务同时操作同一组 IO 没有互斥。解决:确认读的是输出寄存器;在 RTOS 里给每组 IO 加互斥锁,或者把整组操作封装成原子函数。

4.3 现象:输入引脚读回值一直是 0 或一直是 1

原因:配置寄存器方向设错(把输入设成了输出),或者输入引脚悬空。解决:对照手册确认配置位含义;输入引脚外部加上拉或下拉;读输入寄存器而不是输出寄存器。

4.4 现象:上电瞬间继电器误动作

原因:初始化顺序不对,方向切换时输出寄存器内容被打到引脚。解决:先写输出寄存器为安全电平,再配方向,最后再写一次输出寄存器;硬件上继电器驱动加下拉或 RC 延时。

4.5 现象:多片 PCA9698 挂同一总线,只有一片能工作

原因:地址冲突,A0~A2 设置重复。解决:每片 A0~A2 组合唯一;如果地址不够,用 I2C 多路复用器扩展总线,而不是硬改地址。

5. 进阶用法:用 pca9698.c 做 40 路 IO 的批量操作与中断读回

5.1 批量写整组,减少 I2C 事务

单引脚读-改-写每次要两次 I2C 事务,40 路轮询一遍开销不小。如果业务允许,直接按组写,一次事务搞定 8 路。下面这个函数把 40 路状态一次性刷下去。

/* 一次性写 40 路,state 是 5 字节数组,每字节对应一组 */ int pca9698_write_all(const uint8_t state[5]) { for (int bank = 0; bank < 5; bank++) { if (i2c_write(PCA9698_ADDR, PCA9698_OUT_BASE + bank, state[bank]) != 0) return -1; } return 0; }

逻辑说明:把 5 组输出寄存器连续写一遍,适合 LED 矩阵、继电器组这类整组刷新的场景。参数说明:state数组长度必须为 5,顺序对应 Bank0~Bank4;如果 I2C 速率只有 100kHz,5 次写大约 1ms 左右,实时性要求高的场景要评估。

5.2 用 INT 引脚做输入变化捕获

PCA9698 支持中断输出,输入引脚电平变化时 INT 拉低。配置好中断使能寄存器后,主控可以用外部中断接 INT,在中断里读输入寄存器,避免轮询。常见做法是:中断触发后读一次输入寄存器,清中断标志,再处理业务。注意 INT 是开漏输出,需要上拉。

5.3 验证方法:用回读比对确认写入一致性

写完一组 IO 后,立刻读回输出寄存器比对。如果读回值和写入值不一致,说明 I2C 通信有丢包或芯片未应答。这个习惯能帮你在早期发现总线稳定性问题。

验证项方法通过标准
地址确认i2cdetect 扫描出现预期地址
输出功能整组写 0xFF/0x00万用表或 LED 跟随变化
输入功能配置为输入后给电平读回值与外部电平一致
回读一致性写后立即读输出寄存器读回值等于写入值
中断功能触发输入变化INT 拉低,读输入寄存器后恢复

我自己的习惯是,每拿到一颗新的 IO 扩展芯片,先花半小时把上面这张表跑一遍,再往业务代码里集成。这样后面出问题,能立刻判断是驱动问题还是业务逻辑问题。PCA9698 这类芯片不复杂,但寄存器细节和初始化顺序上的坑,往往要烧一次继电器才记得住。希望帮到你。

本文还有配套的精品资源,点击获取

返回列表