尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

树莓派GPIO扩展板Pi-Face设计实战:从飞线到PCB

树莓派GPIO扩展板Pi-Face设计实战:从飞线到PCB 攒了一年的树莓派配件最后发现最缺的居然是一张“脸”。别误会我说的不是给它装个显示屏做人脸识别而是一块能把树莓派那些裸奔的GPIO引脚变成真正可用接口的扩展板。这个项目我取名Pi-Face前后做了三版从最初的面包板飞线到现在的PCB成品踩了不少坑也总结了一些值得记录的经验。这篇文章就把完整过程摊开来讲从硬件选型、原理图设计、CM4引脚映射到软件适配和实测调试给想自己动手做树莓派配件或GPIO扩展板的朋友一个参考。1. 为什么树莓派GPIO需要一张“脸”从一场飞线灾难说起1.1 一次失控的原型验证起因是我给环境监测项目做的一套采集装置。传感器、继电器、指示灯、物理按键全都要接我在面包板上用杜邦线把它们接到树莓派的40Pin排针上。头几天还能靠颜色分辨线序到第六天接上第七个传感器之后整个面包板已经成了一团需要靠照片才能回忆的“毛线球”。有一次我不小心把一根5V线插到了3.3V的GPIO脚上虽然树莓派侥幸没烧但那个传感器直接报废。那晚我把整捆线拆掉对着树莓派的GPIO引脚图看了很久意识到问题不在我粗心而在于树莓派的GPIO本身设计得太“原始”了引脚只是把SoC的I/O直接引出来既没有针对传感器的统一供电接口也没有带电平转换的端子排更没有状态指示。于是我开始琢磨要是能给树莓派做一张“脸”——把常用接口、指示灯、按键、扩展I/O都标准化地装在一块固定配件上以后做原型就不用再碰飞线了。1.2 Pi-Face的定义它不是HAT而是一张“可插拔的脸”大部分树莓派玩家第一时间会想到HAT也就是那种40Pin全脚对插、带EEPROM的扩展板。HAT确实标准但我并不想做一个HAT原因是HAT直接压在树莓派上方占掉了所有排针而且尺寸被板型限制住。我更想要的是一个通过40Pin排线或插接头连接、可以竖立在树莓派旁边的小面板这样它既能露出树莓派本身的接口又能把传感器端子、按钮、LED、继电器整齐地排列在一起。Pi-Face定位就是这样一个配件一张带扩展I/O、状态灯、物理按键和隔离端子的“脸”。它不替代树莓派的任何既有功能而是把GPIO的使用体验补完。简单说树莓派是主机Pi-Face是交互面板。这个定位决定了后面的所有选型比如要不要用MCP23017扩展、要不要做电平转换、要不要保留标准40Pin接口全都要跟着这个定位走。1.3 需求清单当初我列了七条后来只保留五条第一版设计之前我列了一个需求清单现在看其中两条纯属给自己画饼。原始清单是至少扩展16路数字I/O方便挂多个传感器和继电器板载4颗状态LED能通过软件控制板载2个物理按键可自定义功能3路5V传感器端子带独立的电平转换保留标准40Pin接口直通方便再接其它HAT板载实时时钟RTC断网也能有准确时间自带OLED显示屏显示系统状态后来我砍掉了RTC和OLED原因是树莓派正常联网时NTP已经够用RTC只在极端离线场景有意义OLED则会让板的尺寸和成本都明显上升软件上还要额外处理显示驱动的复杂度。真正让我下决心砍掉的是我意识到第一版的目标应该是“验证核心链路”而不是做多功能全家桶。核心链路就是I2C扩展I/O、按键输入、LED输出、5V传感器接入。这就是Pi-Face真正要解决的四件事。2. Pi-Face硬件架构与核心器件选型为什么最终选了MCP230172.1 核心扩展芯片MCP23017与PCF8574的取舍第一件事是确定“扩展I/O用什么方案”。在树莓派生态里最流行的I/O扩展芯片有两颗Microchip的MCP23017和NXP的PCF8574。两者都走I2C总线都支持中断输出但设计逻辑完全不同。PCF8574的特点是极简8个I/O引脚不需要配置方向寄存器靠准双向结构工作写1就是输入写0就是输出。MCP23017则是16个I/O引脚使用A0、A1、A2三根地址线可配置为最多8个不同I2C地址每个引脚都能独立配置为输入或输出还有内部上拉电阻和两个独立的中断输出。我做了一张对比表方便看清楚差别项目MCP23017PCF8574I/O数量16路8路方向配置每个引脚独立可配置准双向写1为输入内部上拉有每位可独立使能弱上拉约100uAI2C地址范围0x20-0x27最多8个0x20-0x27最多8个中断输出2路INTA/INTB1路INT输入读取可读真实引脚状态可读端口值价格约3-5元约1-2元我选MCP23017的核心原因不是I/O多而是它支持“独立方向控制”。Pi-Face要同时接传感器输入和继电器输出如果只靠写1/写0区分方向电路设计时就必须固定好哪些脚输入、哪些脚输出一旦后续改需求焊盘就得飞线。MCP23017用寄存器按位设置方向软件就能随时调整开发期灵活性高得多。另外I/O多一倍第二版扩展功能时不用换主控板。2.2 电平转换5V传感器接入3.3V系统的三种方案树莓派所有GPIO都是3.3V电平但市面上大量传感器、继电器模块还是5V逻辑。如果直接把5V信号送到GPIO树莓派的SoC存在损坏风险。Pi-Face必须把“接5V外设”和“保护3.3V主机”两件事同时解决。常见方案有三种第一分压电阻。用两个电阻把5V信号分压成3.3V比如1k和2k串联5V到中间抽头就是约3.33V。优点便宜缺点是不能双向转换只适合单向信号输入而且电阻值会带来一定的信号上升沿变缓。第二MOS管电平转换。典型电路用BSS138配合上拉电阻可以自动双向转换适合I2C这种开漏总线比如常见的Adafruit电平转换模块就是这么做的。缺点是BSS138的导通阈值和速度在高频下有局限1MHz以上I2C需要重点验证。第三专用电平转换芯片。比如TXS0108E、TXB0108这类芯片内部带自动方向检测适合并行多路双向传输。缺点是芯片成本高静态功耗也更大。Pi-Face这里我采用了混合方案I2C和UART这类双向总线走BSS138转换普通传感器输入走电阻分压继电器输出则直接由MCP23017通过三极管驱动不需要做电平转换。这样既控制了成本也把关键信号的双向性保证好。2.3 电源设计与电流预算别让小面板拖垮树莓派很多新手做扩展板时只关注信号引脚忽略电源结果就是一接上继电器树莓派的5V直接跌落WiFi模块跟着重启。Pi-Face的电源设计我按三个层级处理。树莓派的5V电源来自USB Type-C口Pi 5为PD供电Pi 4及以前为5V/3A直供经过板载保护后40Pin排针的2、4脚可以提供5V输出。Pi-Face从5V脚取电然后分两条路一路直接给5V传感器端子供能另一路通过LDO降到3.3V给MCP23017、LED逻辑和电平转换芯片供电。继电器模块是重点。单个5V继电器线圈的电流通常有70mA左右如果Pi-Face板载两颗继电器峰值电流就有140mA。树莓派本身的5V还要喂USB外设和HDMI转接余量并不大。我的做法是继电器线圈不从树莓派5V取电而是从Pi-Face自带的DC 5V输入端子供电树莓派只提供控制信号。这样即使继电器全部吸合也不会影响树莓派主系统供电。LED限流电阻的计算也要写清楚。我用的LED压降红绿大约2.0V蓝色2.8V3.3V供电串联1k电阻时电流大约是(3.3-2.0)/10001.3mA够做状态指示但不会刺眼。如果换成220欧电流就超过5mA视觉上会很亮但也会在MCP23017的输出脚上叠加额外负载大规模点灯时需要注意总电流别超过芯片的规格。3. 对着CM4的GPIO引脚图逐个确认引脚分配与原理图设计3.1 为什么标题里要特意写“GPIO引脚图”做任何树莓派配件第一件事不是画原理图而是把目标平台的GPIO引脚图吃透。尤其当我准备兼容Compute Module 4时这件事就变得更有意思。CM4和普通树莓派主板不同它没有预焊排针而是通过板上两块100Pin的DDR4 SO-DIMM连接器引出信号。GPIO信号分布在J2连接器上物理位置和标准40Pin排针完全不是一回事如果不把CM4的引脚图搞清楚设计出的Pi-Face就只能兼容普通树莓派接不了CM4。CM4的J2连接器上GPIO0到GPIO27、以及电源、地、I2C、UART、SPI等信号都按照特定的引脚编号排列。比如I2C1的SDA和SCL在CM4上是引脚49和51而普通树莓派40Pin排针上是3和5。这就需要在Pi-Face板子上做一个“桥接转换”让标准40Pin接口和CM4的J2接口都能用同一套扩展逻辑。3.2 从CM4 J2排针到标准40Pin母座的映射建议先看CM4的官方原理图和数据手册中的引脚表。CM4有两个连接器J1承载PCIe、USB、HDMI、DSI/CSI等高速信号J2承载GPIO、I2C、UART、SPI、PWM和电源。Pi-Face需要用到的所有信号都在J2上。我在设计Pi-Face的第二版时特意加了一个“CM4适配子板”的位置。子板一端是SO-DIMM连接器对接CM4的J2另一端引出一排2.54mm排针排针定义与标准40Pin排针保持一致。这样CM4通过子板就变成了一个带标准40Pin接口的核心板之后接Pi-Face就完全一样了。关于CM4的GPIO引脚图要特别注意两点第一CM4的J2上很多GPIO在内部就已经配置了特殊功能比如GPIO28/29是ID_SD和ID_SC对应EEPROM探测Pi-Face如果不需要HAT EEPROM就得避开这两个引脚或者确保它们悬空不影响启动。第二CM4的部分GPIO没有内部上拉比如GPIO0到GPIO9设计时如果需要检测“是否插入”这种信号不能依靠内部弱上拉必须在外围加上拉电阻。3.3 引脚分配表哪些给I2C、哪些给中断、哪些留作输入在原理图动笔前我把Pi-Face要用到的树莓派40Pin引脚全部列出来逐一分配了功能。这个步骤看着琐碎实际上极大避免了后续改线的麻烦。40Pin脚位功能接到Pi-Face哪里13.3VLDO输出、逻辑电源25V5V传感器电源、LDO输入3GPIO2 (I2C1 SDA)MCP23017 SDA经BSS1385GPIO3 (I2C1 SCL)MCP23017 SCL经BSS1387GPIO4板载按键KEY111GPIO17板载按键KEY213GPIO27MCP23017 INTA中断线15GPIO22预留UART或外部中断17GPIO5板载LED119GPIO6板载LED221GPIO13继电器K1控制23GPIO19继电器K2控制25GPIO26外部传感器DOUT1输入27GPIO20外部传感器DOUT2输入29-40若干GPIO全部引到带丝印的扩展排针GPIO4和GPIO17接按键我特意选了带内部上拉的GPIO这样按键电路可以省掉外部上拉电阻。MCP23017的中断线接到GPIO27当扩展I/O上的输入状态变化时树莓派能立刻被中断唤醒而不是轮询I2C。3.4 原理图中的几个关键细节上拉电阻、去耦电容、按钮消抖原理图看起来就是一堆元件符号但真正决定板子能不能稳定工作的全在细节里。I2C总线上拉电阻是最常见的坑。树莓派板载的I2C上拉已经存在但Pi-Face通过排线连接后线路长度增加寄生电容也跟着增加。如果总线上只有一个上拉组信号边沿可能变缓导致I2C通信偶发错误。我在MCP23017附近额外加了一组2.2k上拉电阻到3.3V实测在400kHz模式下波形边沿明显变陡通信稳定性提升明显。MCP23017的电源去耦我用了0.1uF陶瓷电容放在芯片电源脚旁边尽量靠近。同时在Pi-Face整体电源入口处放了一颗10uF钽电容用于吸收继电器切换瞬间的电流尖峰。别小看这两颗电容没有它们继电器吸合瞬间I2C偶发错误几乎必现。按钮消抖不在原理图层面处理而放在软件里。物理按键的机械抖动通常持续5-20ms软件读到时如果不去抖一次按压会被识别成多次触发。Pi-Face的按键引脚在硬件上只做了上拉没有加RC滤波因为RC参数很难兼顾不同按键的按压习惯软件消抖更灵活。我的经验是先用10ms延时采样连续两次读到相同电平才确认按键状态变化效果很理想。4. 软件适配从设备树Overlay到Python驱动的完整链路4.1 让内核先认识这块板编写设备树OverlayPi-Face插上树莓派之后系统并不会自动知道“这里有个MCP23017地址是0x20”。在树莓派上硬件板卡的信息通过设备树描述。标准的HAT带有EEPROM里面存了厂商信息和设备树片段树莓派启动时自动加载。Pi-Face为了兼容CM4和普通树莓派没有做EEPROM所以我选择了手动加载设备树Overlay的方式。Overlay文件是一个dts文件编译出来的dtbo文件。我的做法是在/boot/firmware/config.txt老系统是/boot/config.txt里添加一行dtoverlaypi-face然后编写对应的overlay描述。核心部分是这样的/dts-v1/; /plugin/; / { compatible brcm,bcm2835; fragment0 { target i2c1; __overlay__ { status okay; }; }; fragment1 { target-path /; __overlay__ { pi_face_mcp: mcp2301720 { compatible microchip,mcp23017; reg 0x20; gpio-controller; #gpio-cells 2; interrupt-parent gpio; interrupts 27 2; microchip,irq-mirror; }; }; }; };注意几个字段reg 0x20 表示I2C地址这个值要和原理图上A0/A1/A2引脚接地或接3.3V的设置一致。interrupt-parent和interrupts把MCP23017的中断输出接到树莓派GPIO27上2表示下降沿触发。加载完成后用gpioinfo命令能看到这颗芯片注册出的gpiochip。4.2 Python侧的操作用libgpiod还是直接操作/dev/i2c设备树生效后MCP23017在Linux里就表现为一颗gpiochip。操作它有两种主流方式。第一种是直接打开/dev/i2c-1通过ioctl读写MCP23017的寄存器控制IO方向和数据。这种方式最接近芯片底层适合需要精细控制、或者不用Linux gpio子系统的场景但代码要处理寄存器地址、位掩码这些细节。第二种是使用libgpiodgpiod库因为MCP23017已经被内核gpio子系统接管用户态可以用gpioset、gpioget或者Python的gpiod模块像操作普通GPIO一样操作扩展引脚。我推荐优先用第二种。理由有三点第一内核已经帮你管理了寄存器初始化和中断不用自己写一堆底层代码第二设备树中配置的导出的gpiochip会和树莓派原生GPIO合并到同一个用户态接口代码可以统一处理第三中断支持是现成的用poll或epoll就能等待外部事件。一个简单的LED闪烁示例import gpiod import time chip gpiod.Chip(gpiochip4) # 假设MCP23017注册为gpiochip4实际以系统为准 line chip.get_line(0) line.request(consumerpi-face-led, typegpiod.LINE_REQ_DIR_OUT) while True: line.set_value(1) time.sleep(0.5) line.set_value(0) time.sleep(0.5)需要注意不同内核版本中gpiochip编号会变化不能硬编码。我自己的做法是在启动脚本里扫描/sys/class/gpio找label为“pinctrl-mcp23017”的chip再动态获取对应行号这样兼容性会好很多。4.3 按键检测与中断驱动的完整示例按键如果只用轮询CPU占用高而且响应延迟不可控。更好的办法是利用内核gpio事件机制。将按键GPIO配置为输入并启用中断后通过libgpiod的事件监听接口就能阻塞等待上升沿或下降沿。设备树中按键引脚如果直接接树莓派原生GPIO比如GPIO4、GPIO17可以直接用gpiod的line_request_event。示例import gpiod import time def wait_for_key(consumerpi-face-key, line_offset4, timeout3): chip gpiod.Chip(gpiochip0) line chip.get_line(line_offset) line.request(consumerconsumer, typegpiod.LINE_REQ_EV_FALLING_EDGE, flagsgpiod.LINE_REQ_FLAG_BIAS_PULL_UP) # 等待事件带超时 if line.event_wait(timeout): event line.event_read() return event.type gpiod.LINE_EV_FALLING_EDGE return False我在实际测试中发现按键按下时偶尔会被识别成两次触发。原因就是这个按键引脚在CM4和普通树莓派上的默认配置不同某些系统版本中GPIO4被启用为“无上拉”状态导致按键悬空时电平不稳定。解决办法是在设备树或启动脚本中显式设置上拉或者在Python请求事件时加上LINE_REQ_FLAG_BIAS_PULL_UP标志。这个坑耗费了不少排查时间后面会专节细说。5. 焊接、调试与实测那些能让一块板子“发疯”的细节5.1 I2C扫描不到设备一次从地址线到虚焊的完整排查板子焊接好后我做的第一件事是跑i2cdetect扫描i2cdetect -y 1结果显示只有一个“--”说明MCP23017没被识别到。我当时的第一反应是地址配置错了于是用万用表测A0/A1/A2引脚的电压它们都接地地址应该是0x20。接着测I2C数据线SDA和SCL在空闲状态电压都是3.3V看起来正常。继续测MCP23017电源脚3.3V也对。排查到这里我开始怀疑是虚焊。MCP23017是SSOP28封装手工焊接很容易在相邻引脚之间产生锡桥。我用放大镜仔细检查果然发现第14脚SCL和第15脚SDA之间有一小块锡珠。用电烙铁加助焊剂清理后重新扫描0x20出现了。这个经历给我一个教训PCB打样回来焊接完成后先不要急着上电先用放大镜逐脚检查一遍。特别是SSOP、TSSOP这类小封装锡桥概率远高于直插封装。另外I2C总线上如果有某个设备地址冲突也会导致扫描不全排查顺序应该是地址线电平、供电、引脚虚焊、总线收发器方向。5.2 按键误触发的根因上拉电阻与GPIO默认状态的冲突第二版测试时我发现Pi-Face上的KEY1在按下后松开软件总是会收到两次触发。一开始我以为是延时消抖时间不够把消抖时间从10ms改成50ms问题依旧。直到我把示波器探针接到GPIO4引脚上观察波形才发现按键按下的下降沿之后波形恢复到高电平的过程非常缓慢爬升时间是正常情况的五倍以上。根源是GPIO4在树莓派上默认没有内部上拉而Pi-Face板载按键的外部上拉电阻用了100k。100k对于静态电平来说没问题但在按键引脚和地之间有分布电容、且GPIO内部还有输入电容的情况下100k上拉充电时间常数太大导致上升沿太缓。内核gpio中断检测的是电平变化事件边沿不够陡就有可能在临界区被重复触发。解决方案是把外部上拉从100k改成10k。实测上升沿明显变陡按键单次触发正常。这里也解释了最开始的疑问不是消抖时间不够而是复位到高电平太慢。这种问题只有在示波器上才能一眼看到仅靠软件改时间参数很难根治。5.3 继电器吸合时I2C偶发错误电源去耦被低估的教训第三版测试时我给Pi-Face临时加了两个继电器模块用MCP23017的PA0和PA1控制。继电器切换瞬间I2C通信时不时出现“Remote I/O error”因为继电器线圈是感性负载断开瞬间会产生较高的反向电动势在电源线上形成尖峰干扰。我在Pi-Face电源入口处原来有一枚10uF钽电容但继电器模块是从树莓派5V引脚引的线等于干扰源在I2C芯片的电源上游钽电容没法完全吸收。后来我在每个继电器线圈两端并了一颗续流二极管把线圈断电时的反向电动势直接短路掉同时在继电器控制管脚三极管基极上增加一个10k下拉电阻确保MCP23017上电瞬间不会因为引脚浮空导致继电器误动作。整改之后连续切换上千次I2C再没出现过一次错误。这个例子也说明扩展板上的“干扰源”和“敏感器件”要分开供电至少不能在同一个走线上串联。如果Pi-Face再出新版我会直接把继电器驱动部分设计为带光耦隔离的模块和主板完全电气隔离那样抗干扰能力会强很多。5.4 实测数据与后续改进方向全部调通之后我对Pi-Face做了几项基本的稳定性测试。I2C通信质量方面用i2cdetect确认设备地址稳定40cm长的排线下400kHz通信仍然可靠。按键响应延迟方面从中断产生到Python回调开始执行平均约2.8ms足以满足大多数交互场景。16路扩展I/O同时翻转时MCP23017的VDD电压波动在50mV以内符合datasheet要求。如果重新打一版我会做三个改动第一把所有按键和外部输入的上拉电阻默认改成10k避免再出现边沿过缓问题第二增加EEPROM芯片写入标准的HAT信息让树莓派开机自动识别Pi-Face不用手动加载overlay第三把继电器接口改成可插拔的端子模块接口采用3.81mm间距方便接大电流线缆。6. 如果再让我做一次Pi-Face我会在哪些地方直接抄自己的作业最后分享几个从这几次迭代里沉淀下来的实操心得都是可以直接复用的“作业”。第一硬件原型阶段首选MCP23017是稳的但优先用DIP封装或带引脚的模块别一上来就焊SSOP。等电路验证没问题再画PCB上小封装。我第一版就是用MCP23017模块加杜邦线做完整个软件验证硬件改版时才换成SSOP这样做至少把“芯片问题”和“焊接问题”分开了。第二电平转换别贪多。Pi-Face上最简单的电阻分压方案已经满足大多数传感器输入场景BSS138只保留给I2C和UART双向信号。如果所有信号都上专用电平转换芯片板子面积和成本都会翻倍而且高速信号在转换芯片上也可能引入新的信号完整性问题。第三软件上最重要的一件事是让设备树和实际硬件严格一致。MCP23017的地址、中断引脚、上拉配置任何一个不匹配都会造成难排查的怪问题。升级内核后还要重新确认overlay是否兼容树莓派系统每次大版本更新都可能改动设备树的加载方式。第四异常排查的顺序永远是电源、时钟/上拉、地址/配置、芯片自身最后才怀疑软件。我每次遇到“I2C找不到设备”这类问题都是先量电压、再查地址、再查焊点很少一上来就折腾驱动代码。Pi-Face这个项目到现在已经稳定跑了几个月日常原型验证基本都靠它。做树莓派配件这件事很多时候难的不是某一个具体功能而是如何把一堆零散的信号、电源和通信要求整理成一张清晰可复用的“脸”。希望这篇记录能帮你少走一点我走过的弯路也期待看到你的Pi-Face变得比我这个更好用。
返回列表