尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

I2C扩展GPIO实战:PCA9555PW寄存器配置与Linux驱动详解

I2C扩展GPIO实战:PCA9555PW寄存器配置与Linux驱动详解 简介面向STM32嵌入式开发者的PCA9555PW IO扩展器配套资料主要解决单片机原生IO口不足、需外扩多位数字输入输出的场景。资源不仅包含芯片数据手册还提供了基于模拟I²C的自定义驱动源码适合在没有硬件I²C外设的STM32型号上使用。驱动覆盖16个GPIO的读写配置、中断响应等基础功能便于移植到实际项目中。压缩包共108个文件总大小10.53MB。内容以C语言源文件和头文件为主45个.c、50个.h同时包含Keil工程文件、原理图与PCB源文档以及PCA9555数据手册覆盖从硬件设计到软件调试的主要环节。目前已有681人学习下载附带工程模板、初始化配置和读写示例适合需要快速集成PCA9555或理解模拟I²C时序的开发者参考节省自行查阅手册与编码的时间。1. 主控IO不够时先在I2C总线上挂一片PCA9555PW做工控面板或者Linux单板机扩展时最常遇到的情况不是CPU不够快而是主控只剩两三个GPIO面板上却排着十几路指示灯、按键和拨码开关。常见的做法不是换主控而是在I2C总线上挂一片PCA9555PW用两根信号线换回16路可配置IO。这颗NXP的16位I2C扩展芯片上电时所有IO处于输入高阻状态写配置寄存器把它切到输出模式才真正把引脚驱动起来如果上来就写输出数据寄存器电平不会出现在引脚上。这个细节决定了调试顺序也是后面所有驱动代码的前提。适合做嵌入式裸机、Linux用户态IO扩展以及想用I2C总线替代飞线的工程师继续往下读。2. PCA9555PW内部结构与I2C地址映射寄存器决定一切2.1 8个寄存器和16位IO的对应关系PCA9555PW把16路IO分成P0和P1两个端口每个端口8位P0对应P00~P07P1对应P10~P17。所有操作都通过8个寄存器完成其中每个端口占4个寄存器寄存器地址名称位定义访问属性复位值0x00Input Port 0P07~P00只读反映引脚电平0x01Input Port 1P17~P10只读反映引脚电平0x02Output Port 0P07~P00读写0xFF0x03Output Port 1P17~P10读写0xFF0x04Polarity Inversion 0P07~P00读写0x000x05Polarity Inversion 1P17~P10读写0x000x06Configuration 0P07~P00读写0xFF0x07Configuration 1P17~P10读写0xFFConfiguration寄存器决定引脚方向写0为输出写1为输入。上电复位值全部是1也就是16路IO都是输入高阻对外既不拉高也不拉低。Output寄存器只保存要输出的电平锁存值但它能否出现在引脚上取决于Configuration寄存器对应位是否设成了0。Polarity Inversion寄存器解决的是输入极性匹配问题某一位写1的时候从Input寄存器读到的电平就被反相比如接到一个低有效信号逻辑上可以把它当成高电平处理省去软件里反复取反。2.2 PW封装引脚与A0~A2地址扩展PCA9555PW的PW后缀代表TSSOP-24封装引脚间距0.65mm比DIP版本小很多适合贴片生产。引脚排列上有几个关键信号SCL和SDA接到I2C总线INT是开漏输出的中断引脚低电平表示有输入变化A0、A1、A2是硬件地址选择脚VDD接2.3V到5.5V电源VSS接地。INT引脚本身不带内部上拉开漏输出必须外接上拉电阻才能被主控识别。A0~A2三个引脚决定7位I2C地址的低三位都接地时地址是0x20最高可配到0x27一条I2C总线上最多挂8片同样的芯片。设计多个扩展器时地址规划要在画板前做好因为TSSOP封装下飞线改A0~A2引脚电平非常痛苦。7位地址加上R/W位之后I2C总线上的实际字节是0x40加低位但Linux i2c-tools和大多数驱动里直接填7位地址0x20填0x40反而找不到设备。2.3 读时序、写时序和寄存器指针递增写PCA9555PW的过程分三段先发从机地址写方向再发命令字节指定寄存器地址最后发一个或几个数据字节。命令字节写入后寄存器指针指向目标寄存器连续写多个字节时指针自动递增所以一次I2C写事务可以把Configuration 0和Configuration 1连续写完。读的过程略微不同常见做法是先发从机地址加写方向把命令字节写进去再发重复起始条件切换成读方向这时从机把指针指向的寄存器内容放到SDA上。指针自动递增在读操作里同样有效连续读两个字节能一次拿回P0和P1的状态。但实际驱动里我一般不依赖跨寄存器组的递增行为因为递增到了0x02之后是Output寄存器如果某次读多了一个字节会把输出锁存值当输入电平拿去用。宁可每次读一个寄存器重新写命令字节多一次I2C事务换取逻辑清晰。400kHz快速模式下多出的这次寻址开销大约几十微秒对按键扫描和LED刷新完全无感。3. 最小系统接线与上拉电阻参数计算3.1 VDD电平与I2C总线上拉的匹配问题PCA9555PW的VDD范围是2.3V到5.5V它的IO电平直接跟随VDD。如果主控是3.3V芯片也接3.3V两边电平兼容如果芯片接5V而主控是3.3V那么芯片IO输出高电平接近5V可能超过主控引脚的耐受范围这种情况必须加电平转换或把PCA9555PW的IO负载设计成开漏。I2C的SCL和SDA都是开漏结构必须外接上拉电阻到VDD。上拉阻值不是随便选的它同时影响上升沿时间和灌电流。标准模式100kHz下常用4.7kΩ快速模式400kHz下推荐2.2kΩ。阻值太大总线电容会让上升沿变缓传输速度上不去阻值太小SDA被拉低时流过晶体管的电流过大VDD越高越明显。一个快速估算起点是2.2kΩ配400kHz、总线长度不超过30cmI2C器件数量不超过8个。如果总线上还有其他器件上拉电阻只需要在一处接不要把每块板子都放上拉。3.2 最小电路接线对照表做最小系统时我习惯先列一张接线表确认每一个引出脚都有去处再开始画原理图。PCA9555PW的典型接线如下引脚信号接法说明SDA接主控I2C_SDA经2.2kΩ上拉至VDD双向数据线SCL接主控I2C_SCL经2.2kΩ上拉至VDD时钟线A0/A1/A2全部接地或按需配置接地为0x20INT接主控GPIO中断脚经10kΩ上拉至VDD开漏低有效VDD接3.3V或5V与主控IO电平匹配VSS接GND必须可靠接地P00~P17接LED、按键、拨码开关等负载方向由软件配置INT引脚上拉电阻可以用10kΩ它只负责把电平恢复成高不参与I2C快速通信阻值要求不严。VDD和VSS之间建议放一个100nF去耦电容如果板子上还有其他数字器件可以再加一个10µF电解电容。PCA9555PW没有硬件复位引脚掉电重新上电是唯一的复位手段调试时遇到状态卡死优先检查VDD是否稳定而不是反复发软件复位命令。3.3 输出驱动负载的保护设计PCA9555PW的IO灌电流能力远大于拉电流驱动LED时把LED阳极接VDD阴极经限流电阻接到扩展器引脚引脚输出0时LED点亮这样由灌电流完成驱动电压余量也更好算。单个引脚的灌电流能力在20mA级别直接点一颗几毫安的LED没问题但驱动继电器线圈或者大功率灯珠必须外接三极管或MOS管不能把线圈直接接在IO口上。继电器线圈断电瞬间会产生反向电动势轻则干扰VDD重则击穿芯片IO结构常见做法是给线圈并联一个续流二极管阴极接VDD阳极接三极管集电极。输入侧同样要注意电平范围。按键一端接GND一端接扩展器引脚引脚内部没有上拉电阻必须把该引脚配成输入模式后在外部加上拉电阻或者利用极性反转和外部上拉电阻组合。直接悬空当作输入读取引脚电平飘在中间按键按下和松开都可能读到不确定值。10kΩ上拉到VDD配合按键到GND是工程上最稳的接法。4. 驱动实现Linux用户态i2c-dev读写与裸机寄存器流程4.1 用i2cset快速验证16路输出初始化拿到新板子我一般先用i2c-tools验证硬件通路不急着写C代码。先把PCA9555PW的16路全部配置成输出i2cset -y 1 0x20 0x06 0x00 i2cset -y 1 0x20 0x07 0x00 i2cset -y 1 0x20 0x02 0xAA i2cset -y 1 0x20 0x03 0x55第一条命令把Configuration 0寄存器设为0x00P0端口8位全部变成输出模式第二条同样处理P1端口。第三条给Output Port 0写0xAAP0口的奇数位输出1偶数位输出0。第四条给Output Port 1写0x55电平分配正好与P0相反。执行后用万用表量P00应该是低电平P01是高电平能快速确认16位IO的每一位和寄存器位序有没有画反。如果量到的电平完全不变先检查i2cget读回Configuration寄存器是不是0x00再回头查A0~A2地址有没有配错。4.2 用C语言在Linux用户态读写寄存器Linux环境下最直接的驱动方式是访问/dev/i2c-N设备节点用ioctl设置从机地址后通过write和read完成I2C事务。下面这段代码完成读输入端口的功能#include stdio.h #include fcntl.h #include linux/i2c-dev.h #include sys/ioctl.h int i2c_read_byte(int fd, __u8 reg, __u8 *val) { // 先写命令字节让指针指向目标寄存器 if (write(fd, reg, 1) ! 1) { return -1; } // 从机把当前指针指向的寄存器内容发回来 if (read(fd, val, 1) ! 1) { return -1; } return 0; } int main(void) { int fd open(/dev/i2c-1, O_RDWR); if (fd 0) { perror(open); return 1; } // 0x20是7位I2C地址不要带读写位 if (ioctl(fd, I2C_SLAVE, 0x20) 0) { perror(ioctl); return 1; } __u8 cfg0 0xFF; // 默认是输入模式 if (i2c_read_byte(fd, 0x06, cfg0) 0) { printf(Configuration 0 0x%02X\n, cfg0); } __u8 in0 0xFF; if (i2c_read_byte(fd, 0x00, in0) 0) { printf(Input Port 0 0x%02X\n, in0); } close(fd); return 0; }代码里write单个字节实际完成的是I2C写事务从机地址接写方向数据就是命令字节。随后read完成一次读事务从机地址自动切换成读方向把当前寄存器指针指向的数据送出来。这里不需要重复起始条件因为用户态read对驱动来说是独立的事务内核I2C子系统会在write和read之间处理总线状态。参数上I2C_SLAVE地址必须是7位最右侧的读写位由内核根据操作方向添加如果直接用0x40从机地址找不到芯片open和ioctl都不会报错但read会返回EIO。4.3 读-改-写保留未用引脚状态实际项目中很少一次把16路全占满多半用了几路按键几路LED剩下的引脚要保持原有配置。这时不能直接把配置寄存器整个覆盖得先读出原值只改目标位再写回去。以把P05配置成输出为例__u8 cfg0; __u8 cfg_new; // 读出当前配置 i2c_read_byte(fd, 0x06, cfg0); // P05对应bit5先清零再置1改成输出 cfg_new cfg0 ~(1 5); cfg_new cfg_new | (0 5); // 写回配置寄存器 if (write(fd, cfg_new, 1) ! 1) { perror(write cfg); }上面这段逻辑里先读回原值把bit5以外的位保留把bit5清0完成输出模式配置再回写0x06寄存器。这里要注意从机地址已经通过ioctl设置过所以write第一个参数只传配置值即可内核会把同一事务发到0x20地址。读-改-写操作绝不能从Input寄存器读值必须从Output寄存器读锁存值因为Input寄存器反映的是引脚实际电平可能被外部负载拉低用它去改输出锁存数据会把原本该输出的高电平改掉这是PCA9555驱动里最隐蔽的bug之一。5. 上电状态、中断清除与输出毛刺的诊断技巧5.1 用回环测试确认每一位输入正常验证输入通道最直接的方法是把每一路引脚临时接地再用i2cget读回输入状态。下面命令读取P0端口如果P00接地读出的值应当最低位是0输出0xFEi2cget -y 1 0x20 0x00对照PCF8574这类老芯片的做法很多人习惯用读到的值取反来算按键状态但PCA9555PW不推荐这样。先读回输入端口再用逻辑与判断每一位比整体取反更直观而且和Polarity Inversion配合起来不会出错。如果读回全部是0xFF多半是引脚上拉电阻没接或者引脚悬空如果读回全部是0x00检查是不是有外部器件把整组引脚拉到地了。5.2 中断不触发和误触发时的两个检查点INT引脚是开漏输出任何配置为输入的引脚状态发生变化INT都会拉低直到主控读取该输入端口寄存器后才恢复。最容易踩的坑有两个一个是没有把Configuration寄存器里对应位设成输入输出模式下电平变化不会触发中断另一个是中断服务程序里只清GPIO中断标志没有用I2C读取输入端口寄存器INT一直保持低电平后续变化无法再产生边沿。对于后一种情况无论读哪一个输入端口寄存器都会对全部输入引脚重新锁存当前电平所以在中断处理里把0x00和0x01两个寄存器都读一遍最稳妥。误触发多发生在电平缓慢变化的场合比如按键抖动或者拨码开关切换。PCA9555PW内部没有可编程滤波硬件上可以给输入引脚并联一个100nF电容滤除高频抖动软件上则要在读到输入变化后延时10ms左右再次确认电平确认稳定后再更新逻辑状态否则INT引脚会随着机械抖动连续拉低主控中断被频繁触发。5.3 从输入切输出前先锁存电平消除闪亮毛刺最后一个实际经验是把引脚从输入模式切换到输出模式的瞬间引脚电平会直接跳到Output寄存器里锁存的值。如果输出锁存值是0而外部负载接法是上一节图里的灌电流接法这个引脚就会突然点亮对应LED一次。PCA9555PW上电后Output寄存器内容可能是全1但工程上不能依赖这个状态。正确做法是在修改Configuration寄存器之前先把目标引脚要输出的电平写入Output寄存器再切换方向这样引脚电平在切换瞬间不会产生反向跳变。调试结束后如果发现某一路输出在系统启动时闪了一下优先查这一路对应的Output寄存器初始值而不是怀疑时序。类似的还有把开漏输入换成推挽输出时如果Output锁存值为0引脚会被直接拉低可能影响外接上拉电路。养成先写Output后改Configuration的习惯后这类毛刺基本可以杜绝。最后用一条命令复查配置i2cget -y 1 0x20 0x06读回结果和设计值逐位对比0x00对应输出且锁存低电平0xFF对应输入高阻对不上的位就是配置顺序写反了。本文还有配套的精品资源点击获取
返回列表