1. 项目概述PIR8575 是一个面向嵌入式场景的 Arduino 兼容库专为构建 16 路并行被动红外PIR运动检测系统而设计其核心硬件依托于 TI现为 NXP推出的 PCF8575 16 位 I²C 双向 I/O 扩展器。该库并非简单封装 I/O 读取功能而是针对安防监控、智能空间感知等实际工程需求提出了一种可扩展、低引脚占用、支持中断驱动的多路 PIR 集成方案。传统单片机直接连接 PIR 传感器存在显著瓶颈典型 PIR 模块如 HC-SR501每路需占用 1 个 GPIO 引脚用于信号输出若需部署 16 路则需 16 个独立输入引脚。以主流 STM32F103C8T6Blue Pill为例其可用 GPIO 总数约 37 个但需预留 UART、I²C、SPI、ADC、PWM 等外设资源后实际可用于通用数字输入的引脚往往不足 10 个Arduino Uno 更仅有 20 个数字引脚其中部分复用为 PWM/Serial难以支撑大规模 PIR 部署。PIR8575 库通过引入 PCF8575将 16 路 PIR 输入信号统一汇聚至 I²C 总线仅需 SDA/SCL 两根信号线彻底解耦了传感器数量与 MCU 引脚资源的强绑定关系使单 MCU 控制数十路甚至上百路 PIR 成为可能通过级联多个 PCF8575。该库的设计哲学强调“工程实用性优先”它不追求极致性能如纳秒级响应而聚焦于在典型室内安防场景响应时间 100ms~2s 级别下提供稳定、可配置、易集成的多路检测能力。其底层复用了经过充分验证的 PCF8575 库 确保 I/O 扩展器通信的鲁棒性同时为兼容性考虑明确指出在特定配置下亦可适配引脚更少、功能精简的 PCF85748 位 I/O 扩展器为小规模部署提供成本优化路径。2. 硬件架构与接口设计2.1 核心芯片PCF8575 的角色与特性PCF8575 是一款 CMOS 工艺的 16 位 I²C 总线 I/O 扩展器其核心价值在于将 I²C 协议转换为并行的 16 位双向数据总线。在 PIR8575 系统中它被配置为纯输入模式所有 16 个端口P00–P07, P10–P17均作为高阻抗输入用于采集外部 PIR 传感器的 TTL 电平输出信号。关键电气特性决定了其在本项目中的适用性输入电平兼容性PCF8575 的逻辑高电平输入阈值VIH典型值为 2.0V低电平阈值VIL典型值为 0.8V完全兼容标准 3.3V/5V TTL 电平可无缝对接 HC-SR501、AM312 等主流 PIR 模块。内部上拉结构每个 I/O 引脚内部集成约 100kΩ 上拉电阻至 VCC。当 PIR 无触发时输出为高阻态引脚被上拉至高电平逻辑 1触发时PIR 输出低电平逻辑 0从而在 PCF8575 输入端形成明确的电平跳变。此设计免除了外部上拉电阻简化了硬件。中断输出INT引脚这是 PIR8575 实现高效轮询替代方案的关键。当任意一个输入引脚状态发生改变高→低或低→高PCF8575 会立即将 INT 引脚拉低并保持该状态直至 MCU 通过 I²C 读取一次端口数据。该特性使得 MCU 可以进入低功耗休眠状态仅在 INT 中断触发时才唤醒处理极大降低系统平均功耗。2.2 系统物理连接拓扑一个完整的 16 路 PIR 检测系统包含以下硬件组件组件规格/说明连接要点MCU 主控Arduino Uno/Nano/ESP32/STM32 等SDA → PCF8575 PIN 14 (SDA), SCL → PCF8575 PIN 13 (SCL)PCF8575 I/O 扩展器TSSOP-24 或 DIP-24 封装A0/A1/A2 接地或 VCC 决定 I²C 地址INT 引脚接 MCU 任一外部中断引脚如 Arduino UNO 的 D2VCC5V与 PIR 匹配GND 共地16 路 PIR 传感器HC-SR501推荐带电位器调节、AM312 等VCC→ 独立 5V 电源严禁直接接 MCU 5VGND→ 与 PCF8575、MCU 共地OUT→ PCF8575 的 P00–P07, P10–P17共 16 路电源设计警示HC-SR501 单个模块静态电流约 50μA但触发瞬间峰值电流可达 60mA。16 路同时触发时瞬时电流需求超过 900mA。Arduino 板载 5V 稳压器如 NCP1117通常仅能提供 1A 持续电流且散热能力有限极易导致电压跌落、MCU 复位或 PCF8575 通信异常。因此必须使用独立的、额定电流 ≥2A 的 5V 开关电源为所有 PIR 供电PCF8575 的 VCC 可由该电源供给或由 MCU 板载稳压器供给因其负载极轻。2.3 I²C 地址配置与多设备扩展PCF8575 的 7 位 I²C 从机地址由其三个硬件地址引脚 A0、A1、A2 的电平决定计算公式为I²C_Address 0x20 (A2 2) | (A1 1) | A0A2A1A07-bit Address8-bit Write Address8-bit Read AddressGNDGNDGND0x200x400x41GNDGNDVCC0x210x420x43GNDVCCGND0x220x440x45..................VCCVCCVCC0x270x4E0x4F这意味着单条 I²C 总线上最多可挂载 8 个 PCF8575从而支持128 路 PIR 传感器。在 PIR8575 库中用户通过构造函数PIR8575(uint8_t address, TwoWire * wire)显式指定目标设备地址。对于多设备系统需为每个 PCF8575 创建独立的PIR8575对象实例并分别调用begin()初始化。3. 软件架构与 API 详解3.1 核心类与初始化流程PIR8575类是整个库的入口其设计遵循嵌入式 C 的轻量级原则所有成员函数均为内联或短小实现避免动态内存分配。#include PIR8575.h #include Wire.h // 创建 PIR8575 对象地址为 0x20使用默认 Wire 总线 PIR8575 pirSensor(0x20); void setup() { Serial.begin(115200); // 初始化 I²C 总线对 Arduino 默认 Wire 是必需的 Wire.begin(); // 初始化 PIR8575 设备 if (!pirSensor.begin()) { Serial.println(ERROR: PIR8575 not found on I2C bus!); while (1); // 硬件故障死循环 } Serial.println(PIR8575 initialized successfully.); }begin()函数执行两项关键操作I²C 设备探测向指定地址发送 STARTADDRREAD 信号检查是否收到 ACK。若未应答返回false。输入模式配置向 PCF8575 的输出寄存器地址 0x00写入0xFFFF。由于 PCF8575 的 I/O 结构为“准双向”写入1会使对应引脚进入高阻抗输入模式写入0则强制输出低电平。因此0xFFFF确保所有 16 个引脚均被配置为输入。isConnected()是begin()的轻量级版本仅执行第 1 步探测适用于运行时的设备在线状态轮询。3.2 数据读取 API单路与全量库提供了两种读取方式适应不同应用场景3.2.1 全量读取uint16_t read16()此函数一次性读取 PCF8575 的全部 16 位输入状态返回一个uint16_t整数其 bit0–bit15 分别对应 P00–P07, P10–P17 的当前电平0低电平/触发1高电平/未触发。这是最高效的读取方式尤其适合需要分析多路关联逻辑如区域联动的场景。void loop() { uint16_t allStates pirSensor.read16(); // 检查第 0 路P00是否触发 if ((allStates 0x0001) 0) { Serial.println(PIR 0 triggered!); } // 检查第 15 路P17是否触发 if ((allStates 0x8000) 0) { Serial.println(PIR 15 triggered!); } delay(100); }3.2.2 单路读取uint8_t read(uint8_t pir)此函数按需读取指定编号0–15的单路 PIR 状态返回0触发或1未触发。其实现并非简单地从read16()结果中提取某一位而是通过两次 I²C 通信先读取高字节P10–P17再读取低字节P00–P07然后根据pir参数选择相应字节和位进行判断。虽然效率低于read16()但代码语义更清晰便于调试和单点故障排查。// 读取第 5 路 PIR对应 P05 uint8_t state5 pirSensor.read(5); if (state5 0) { Serial.println(PIR 5 is active.); } else { Serial.println(PIR 5 is idle.); }3.3 错误处理机制PIR8575 库采用简洁的错误码体系所有错误信息通过lastError()函数获取返回值定义在头文件中错误码宏十六进制值含义常见原因PIR8575_OK0x00无错误操作成功PIR8575_PIN_ERROR0x81引脚编号越界read()函数参数pir不在 0–15 范围内PIR8575_I2C_ERROR0x82I²C 通信失败总线被占用、设备掉线、地址错误、接线松动在关键应用中应始终检查lastError()uint8_t state pirSensor.read(3); if (pirSensor.lastError() ! PIR8575_OK) { switch (pirSensor.lastError()) { case PIR8575_PIN_ERROR: Serial.println(Invalid PIR number!); break; case PIR8575_I2C_ERROR: Serial.println(I2C communication failed! Check wiring.); break; } }4. 中断驱动模式告别轮询拥抱高效4.1 中断原理与硬件连接PCF8575 的INT引脚是实现事件驱动架构的核心。其工作逻辑如下初始状态下所有 PIR 未触发PCF8575 输入为全1INT引脚为高电平开漏输出需外部上拉。当任一 PIR 触发输出低电平PCF8575 检测到对应输入引脚由1变0立即拉低INT引脚。INT引脚保持低电平直到 MCU 通过 I²C 读取一次 PCF8575 的输入寄存器即调用read16()或read()。读取动作会清零内部的“变化标志”从而使INT恢复高电平。在硬件上需将 PCF8575 的INT引脚连接至 MCU 的一个外部中断引脚如 Arduino Uno 的 D2 或 D3并在setup()中启用中断volatile bool pirInterrupted false; void IRAM_ATTR onPirInterrupt() { pirInterrupted true; } void setup() { // ... 其他初始化 pinMode(2, INPUT); // D2 作为中断引脚 attachInterrupt(digitalPinToInterrupt(2), onPirInterrupt, FALLING); } void loop() { if (pirInterrupted) { pirInterrupted false; // 必须在此处读取以清除 INT 信号 uint16_t states pirSensor.read16(); // 处理状态变化... for (int i 0; i 16; i) { if ((states (1 i)) 0) { Serial.print(PIR ); Serial.print(i); Serial.println( triggered!); } } } }4.2 中断模式下的软件设计考量采用中断模式带来显著优势但也引入了新的设计约束最小化中断服务程序ISRonPirInterrupt()中仅设置一个volatile标志位所有繁重的数据处理如read16()、串口打印、网络上报必须在loop()的主循环中完成。这是因为 ISR 应尽可能短且 Arduino 的Wire库在 ISR 中调用是不安全的。状态去抖与防误触发PIR 传感器本身存在光学和电气噪声可能导致INT引脚产生毛刺。应在主循环中加入软件消抖例如记录上次读取时间仅在间隔 50ms 后再次读取并确认状态。中断丢失风险若 MCU 在INT拉低后、尚未执行read16()前另一路 PIR 又发生状态变化INT信号不会再次拉低PCF8575 仅报告“有变化”不报告“哪路变化”。因此每次中断都必须执行一次完整的read16()以捕获所有已发生的变更。5. 高级应用与工程实践5.1 极简 360° 扫描系统实现利用 16 路 PIR 的空间分布能力可构建低成本的 360° 环境扫描系统。典型布局是将 16 个 PIR 模块沿圆周均匀安装每路覆盖约 22.5° 视角。软件层面可通过read16()获取的 16 位状态字快速定位活动区域// 定义 16 路 PIR 的物理方位角0°-359° const int azimuth[16] {0, 22, 45, 67, 90, 112, 135, 157, 180, 202, 225, 247, 270, 292, 315, 337}; void scanEnvironment(uint16_t states) { bool anyActive false; int firstActive -1, lastActive -1; for (int i 0; i 16; i) { if ((states (1 i)) 0) { // PIR i is triggered anyActive true; if (firstActive -1) firstActive i; lastActive i; } } if (anyActive) { // 计算活动扇区的中心角度和宽度 int centerIndex (firstActive lastActive) / 2; int widthDegrees (lastActive - firstActive 1) * 22; Serial.print(Activity detected from ); Serial.print(azimuth[firstActive]); Serial.print(° to ); Serial.print(azimuth[lastActive]); Serial.print(° (center: ); Serial.print(azimuth[centerIndex]); Serial.print(°, width: ); Serial.print(widthDegrees); Serial.println(°)); } }5.2 与 FreeRTOS 的协同集成在基于 ESP32 或 STM32 的复杂系统中可将 PIR 事件作为 FreeRTOS 任务的同步信号源。一个典型的架构是创建一个高优先级的“PIR 中断处理任务”它通过二进制信号量Binary Semaphore接收中断通知#include freertos/FreeRTOS.h #include freertos/task.h #include freertos/semphr.h SemaphoreHandle_t pirSem NULL; void IRAM_ATTR onPirInterrupt() { BaseType_t xHigherPriorityTaskWoken pdFALSE; xSemaphoreGiveFromISR(pirSem, xHigherPriorityTaskWoken); portYIELD_FROM_ISR(xHigherPriorityTaskWoken); } void pirTask(void *pvParameters) { pirSem xSemaphoreCreateBinary(); xSemaphoreGive(pirSem); // 初始化信号量 for (;;) { if (xSemaphoreTake(pirSem, portMAX_DELAY) pdTRUE) { // 在此处执行 read16() 和业务逻辑 uint16_t states pirSensor.read16(); processPirEvents(states); } } } // 在 app_main() 中创建任务 xTaskCreate(pirTask, PIR_Task, 2048, NULL, 5, NULL);此模式将中断处理与业务逻辑解耦充分利用了 RTOS 的调度能力确保 PIR 事件得到及时响应同时不影响其他任务如 Wi-Fi 连接、传感器融合的执行。5.3 电源管理与低功耗优化对于电池供电的长期部署场景如野外监测可结合INT中断与 MCU 的深度睡眠模式。以 ESP32 为例#include driver/rtc_io.h #include esp_sleep.h void setup() { // ... 初始化 // 配置 GPIO2 为 RTC IO并启用中断唤醒 rtc_gpio_init(GPIO_NUM_2); rtc_gpio_set_direction(GPIO_NUM_2, RTC_GPIO_MODE_INPUT_ONLY); rtc_gpio_pullup_dis(GPIO_NUM_2); rtc_gpio_pulldown_en(GPIO_NUM_2); esp_sleep_enable_ext0_wakeup(GPIO_NUM_2, 0); // 低电平唤醒 } void loop() { // 进入深度睡眠等待 PIR 触发 esp_light_sleep_start(); // 唤醒后执行一次 read16() uint16_t states pirSensor.read16(); handleWakeUpEvent(states); // 可选延时后再次睡眠避免连续触发 vTaskDelay(5000 / portTICK_PERIOD_MS); }在此模式下ESP32 的待机电流可降至 10μA 量级配合大容量锂电池可实现数月的续航。6. 兼容性与未来演进方向6.1 PCF8574 兼容性分析文档中提及“预期可在一定程度上兼容 PCF8574”。PCF8574 是 PCF8575 的 8 位版本其寄存器结构与通信协议高度一致主要差异在于寄存器宽度PCF8574 仅有一个 8 位端口P0–P7而 PCF8575 有两个 8 位端口Port0, Port1。读取指令PCF8574 读取时只需发送 STARTADDRREAD即可获得 8 位数据PCF8575 则需在读取第一个字节Port1后自动递增地址读取第二个字节Port0。PIR8575 库的read16()函数在底层会尝试读取两个字节。若连接的是 PCF8574第二次读取将因地址越界而失败导致lastError()返回PIR8575_I2C_ERROR。因此要实现兼容需在begin()中增加芯片型号探测逻辑或提供一个setMode8Bit()配置函数使库在检测到单字节响应时自动切换为 8 位模式。6.2 社区驱动的演进路线根据作者在 README 中列出的 “Future” 计划该库的持续演进将围绕以下工程痛点展开极性配置不同 PIR 模块的输出极性可能不同高有效/低有效。begin()函数可增加一个polarityMask参数允许用户为每一路指定是否需要异或翻转避免在应用层做位运算。动态通道数配置begin()可增加uint8_t channelCount参数使库在初始化时只启用前 N 路节省内存并加速read16()仅读取必要字节。内置扫描算法提供scan360()、scanCorridor()等高级 API封装方位计算、轨迹跟踪等逻辑降低上层应用开发门槛。这些演进方向均源于真实项目反馈体现了开源库“从实践中来到实践中去”的生命力。对于工程师而言理解其当前能力边界与未来潜力是评估其是否适配自身项目的基石。