项目概述

本项目以 ESP32 为主控,通过 I2C 接口扩展芯片 PCF8575,将有限的主控引脚扩展为 16 路数字 I/O,并在其上挂载:

模块 作用
4×4 矩阵键盘 人机输入,控制灯效与工作模式
2 路高电平 LED 状态指示 / 感应联动照明
RGB 三色灯模块 多色彩显示与颜色轮播
SR505 人体感应 有人自动开灯、无人自动灭灯
ST7789 1.3 寸屏(240×240) 实时显示模式、灯态、感应状态

系统上电后默认进入 AUTO(人体感应)模式:有人靠近时两路 LED 点亮,人离开后熄灭。同时可通过矩阵键盘切换到 MANUAL(手动)模式,独立控制 LED、RGB 颜色与轮播。

核心目标:在一块扩展芯片上同时演示 I2C 通信、数字输出驱动、数字输入采集、矩阵键盘扫描、传感器联动与图形界面刷新


项目亮点

  1. 一芯多用:键盘扫描(输入)、LED/RGB 驱动(输出)、人体感应采集(输入)全部走 PCF8575,充分展示 I/O 扩展器的双向能力。
  2. AUTO / MANUAL 双模式:感应自动控灯与按键手动控灯可一键切换,互不抢占 RGB。
  3. 矩阵键盘完整消抖 + 边沿触发:按一下只触发一次,不会连发。
  4. RGB 独立轮播:按键 C 控制颜色循环,与感应模式解耦,避免彩灯干扰 PIR。
  5. ST7789 卡片式 UI:240×240 方形屏上分区显示 MODE / LED / RGB / PIR,状态变化即时刷新、不整屏闪烁。
  6. 接线容错:键盘排线反向(P17→P10)时,仅改掩码数组即可适配,无需重插线。

项目难点及解决方案

难点 现象 解决方案
PCF8575 高电平驱动能力弱 LED 极暗甚至几乎不亮 输出引脚与 VCC 之间加 330Ω~1kΩ 上拉电阻,写 1 时由电阻供电流,写 0 时芯片灌电流关灯
矩阵键盘与 LED 同芯片 扫描键盘时误把 LED 熄灭 所有写端口统一走 writePort(),始终保留当前 ledState / rgbIndex
RGB 点亮干扰 SR505 开 RGB 后 PIR 一直判“有人” 感应模式 只控制两路 LED,不驱动 RGB;RGB 由按键 C / 0~7 独立控制
按键连发 / 抖动 一次按下触发多次 20ms 软件消抖 + 仅在“按下边沿”调用 onKeyPressed()
键盘排线方向反了 按键识别错位 用 ROW_MASKS / COL_MASKS 映射实际引脚,代码适配硬件
屏幕频繁全刷闪烁 UI 闪、观感差 静态背景只画一次(drawStaticUI),状态区脏标记刷新(displayDirty
SR505 供电不稳 亮灯后 OUT 粘高、关不掉 保证模块 VCC 接牢 5V,GND 共地;必要时电源脚加滤波电容

一、硬件系统部分

1.1 硬件清单

名称 规格 / 说明 数量
零知ESP32 开发板 带 USB 下载,I2C 默认 GPIO21/22 1
零知ESP32 扩展板 带屏幕接口,方便接线 1
PCF8575 模块 I2C 16 位 I/O 扩展,默认地址 0x20 1
4×4 薄膜矩阵键盘 8 线排针,无源 1
高电平触发 LED 模块 单路信号 + VCC/GND 2
RGB LED 模块 R/G/B 三路独立,共阴或模块 GND 1
SR505 人体感应模块 工作电压约 4.5~20V,OUT 数字输出 1
ST7789 彩屏 240×240,SPI,TFT_eSPI 驱动 1
杜邦线、面包板 若干

1.2 接线方案

(1)ESP32 ↔ PCF8575(I2C)

ESP32 PCF8575
GPIO21(SDA) SDA
GPIO22(SCL) SCL
5V VCC
GND GND

(2)PCF8575 外设分配

PCF8575 引脚 外设 方向
P00 LED1 信号 输出
P01 LED2 信号 输出
P02 RGB-R 输出
P03 RGB-G 输出
P04 RGB-B 输出
P05 SR505 OUT 输入
P06、P07 未用
P17~P14 键盘行 R1~R4 输出扫描
P13~P10 键盘列 C1~C4 输入读取

(3)4×4 键盘排线对应关系(本工程实际接法)

键盘正面朝上、排线朝下,从左到右 8 根线:

排线左 → 右:R1 R2 R3 R4 C1 C2 C3 C4
接 PCF8575:  P17 P16 P15 P14 P13 P12 P11 P10

1.3 硬件连接图

                    ┌─────────────┐
                    │    ESP32    │
                    │  SDA=GPIO21 ├──┐
                    │  SCL=GPIO22 ├──┼── I2C 总线
                    │  SPI → TFT  │  │
                    └──────┬──────┘  │
                           │         │
              ┌────────────┴───┐     │
              │  ST7789 240²   │     │
              │  (TFT_eSPI)    │     │
              └────────────────┘     │
                                     │
                    ┌────────────────┴──────┐
                    │      PCF8575 0x20     │
                    │                       │
     P00 ──[上拉]── LED1                    │
     P01 ──[上拉]── LED2                    │
     P02 ──[上拉]── RGB R                   │
     P03 ──[上拉]── RGB G                   │
     P04 ──[上拉]── RGB B                   │
     P05 ────────── SR505 OUT   (+→5V)      │
     P17..P14 ───── 键盘行 R1..R4           │
     P13..P10 ───── 键盘列 C1..C4           │
                    └───────────────────────┘

系统数据流示意:

1.4 实物连接图


二、软件架构设计

2.1 系统初始化(setup

初始化顺序与代码一致:

  1. 打开串口 115200
  2. tft.init() + setRotation(),调用 drawStaticUI() 画静态背景
  3. Wire.begin(21, 22),I2C 时钟 100kHz
  4. 探测 PCF8575 地址 0x20 是否应答
  5. writePort(0xFF00):LED/RGB 灭,行列写 1,P05~P07 保持输入
  6. 读取一次 PIR 电平,置 displayDirty,等待主循环刷新动态区

2.2 主循环逻辑(loop

每个循环周期(约 5ms 间隔)依次执行:


  A[scanMatrix 扫描键盘] --> B[消抖 + 边沿检测]
  B -->|确认按下| C[onKeyPressed]
  B --> D{autoMode?}
  D -->|是| E[handleMotion 有人开灯/无人灭灯]
  D -->|否| F[跳过感应]
  E --> G[handleRgbCycle]
  F --> G
  G --> H[每 200ms 采样 PIR 电平]
  H --> I{displayDirty?}
  I -->|是| J[updateDisplay]
  I -->|否| K[delay 5ms]
  J --> K
 

设计要点:

  • 键盘优先:先扫键再处理感应,保证按键响应及时
  • 脏标记刷新:只有状态变化才重画动态卡片,降低 SPI 负担
  • 感应与 RGB 解耦handleMotion 只改 ledState,不改 rgbIndex

三、代码拆分讲解

3.1 底层读写 PCF8575

void writePCF8575(uint16_t value);  // 先低字节 P00-P07,再高字节 P10-P17
uint16_t readPCF8575();             // requestFrom 2 字节拼成 16 位

writePort(rowColBits) 是关键写出口:高字节负责行列扫描,低字节始终合并:

  • ledBits():LED1/2 + RGB 三通道
  • UNUSED_LOW:P05~P07 写 1,保证 PIR 可被外部驱动

3.2 矩阵键盘扫描 scanMatrix

  1. 逐行将对应行掩码拉低,其余行/列写 1
  2. 延时约 500µs 等待稳定
  3. 读端口,列位为 0 表示该键按下
  4. 扫完后恢复行列全 1,并保持 LED/RGB 状态不变

消抖在 loop 中完成:电平变化后稳定 ≥20ms,且仅在 按下瞬间 触发事件。

3.3 按键事件 onKeyPressed

按键 行为
0~7 直接设 RGB 颜色,关闭轮播
A / B 切换 LED1 / LED2,并退出 AUTO
C 开关 RGB 轮播(开:从红色起每秒换色;关:RGB 熄灭)
D 切换 AUTO / MANUAL
# LED 全亮 + RGB 白,退出 AUTO,停轮播
* 全灭,退出 AUTO,停轮播

3.4 人体感应 handleMotion

  • 读 P05 → 50ms 消抖得到 motionStable
  • trueledState = 0x03(两灯亮)
  • falseledState = 0x00(两灯灭)
  • 不修改 RGB

进入 AUTO 时,setAutoMode(true) 会故意翻转一次稳定态,强制按当前传感器电平立刻同步灯态。

3.5 RGB 轮播 handleRgbCycle

rgbCycle == true 时,每 1000ms:rgbIndex 在 1~7 循环(红→绿→黄→蓝→紫→青→白)。

3.6 屏幕显示

  • drawStaticUI():背景、顶栏、分区标签(只跑一次)
  • updateDisplay():刷新 MODE / LED 圆点 / RGB 色点与名称 / PIR 状态与 OUT:H/L

四、操作过程及数据展示

4.1 操作步骤

  1. 接线检查

    • I2C:SDA/SCL、共地
    • LED/RGB:IN→P00/P01/P02/P03/P04
    • SR505:+→5V,OUT→P05,-→GND
    • 键盘:左→右接 P17→P10
  2. 编译烧录

    • 开发板选 零知ESP32,波特率 115200
    • 打开串口监视器,应看到 PCF8575 OK. 与 Ready.
  3. 功能验证

步骤 操作 预期现象
上电默认 AUTO 屏显 MODE=AUTO;挥手 LED 亮,离开后灭;PIR 显示 MOTION/IDLE
按 A / B 进入 MANUAL;对应 LED 翻转
按 1~7 RGB 变为对应颜色;屏上颜色名同步
按 C RGB 开始轮播,屏显 CYCLE;再按 C 停止并熄灭
按 D 在 AUTO / MANUAL 间切换
按 # / * 全开(白)/ 全关
  1. 屏幕与串口对照
    • 屏右上角显示最近按键字符
    • 串口打印:Key: x LED1:.. LED2:.. RGB:.. Auto:ON/OFF
    • 感应动作打印:[Auto] Motion -> LEDs ON / Idle -> LEDs OFF

按键速查表

[1] [2] [3] [A]     0~7 : RGB 定色(停轮播)
[4] [5] [6] [B]     A/B : LED1 / LED2 切换(进 MANUAL)
[7] [8] [9] [C]     C   : RGB 轮播开关
[*] [0] [#] [D]     D   : AUTO / MANUAL
                    #/* : 全开 / 全关

4.2 演示视频

零知派ESP32--通过PCF8575使用矩阵键盘控制LED


五、技术原理

5.1 工作原理

(1)PCF8575 准双向口

  • 写 1:引脚为弱上拉/高阻输入态,可被外部拉低,也可外加上拉电阻对外供电
  • 写 0:强下拉,灌电流能力强(适合关断高电平 LED)
  • 因此“高电平点亮 LED”必须外加电阻,不能指望芯片内部弱上拉直接点灯

(2)矩阵键盘扫描

行列交叉处按下时,被拉低的行会把对应列线拉低。MCU 逐行输出低电平并读列,即可定位键位,再经 KEYMAP 映射为 '0'`‘9’`、`‘A’`'D''*''#'

(3)SR505 人体感应

热释电传感器检测人体红外变化。检测到运动时 OUT 输出高电平,并保持约数秒(硬件固定,SR505 一般不可调电位器)。本工程以该数字电平作为“有人/无人”依据。
(4)I2C 多设备与 SPI 屏

  • PCF8575 独占 I2C(本工程地址 0x20)
  • ST7789 走 SPI,与 I2C 总线分离,互不抢总线

5.2 工作模式配置

模式 进入方式 LED RGB 说明
AUTO 上电默认 / 按 D 切入 由 SR505 自动控制 不联动;仍可用 C/0~7 手动控 屏显绿色 AUTO
MANUAL 按 A/B/#/* 或 D 切出 按键手动 按键手动 屏显橙色 MANUAL

相关宏配置(可按硬件微调):

#define PCF8575_ADDR       0x20  // I2C 地址,A0/A1/A2 接地时通常为 0x20
#define LED_ACTIVE_LOW     0     // 0=高电平亮(上拉接法);1=低电平亮(灌电流接法)
#define MOTION_ACTIVE_HIGH 1     // 1=OUT 高表示有人;模块相反则改 0
const uint32_t MOTION_DEBOUNCE_MS = 50;
const uint32_t DEBOUNCE_MS = 20;

键盘引脚映射(本工程反向排线):

ROW_MASKS = {P17, P16, P15, P14};
COL_MASKS = {P13, P12, P11, P10};

六、常见问题指引

Q1:串口提示 PCF8575 not found at 0x20

检查 SDA/SCL 是否接 GPIO21/22、模块供电与共地;用 I2C 扫描确认实际地址(若跳线改地址,同步改 PCF8575_ADDR)。

Q2:按键识别错位(按 1 变成别的键)

排线方向或行列对调。对照本文 1.2 节改线,或只改 ROW_MASKS / COL_MASKS / KEYMAP

Q3:按一下键触发很多次

确认使用的是边沿触发消抖逻辑;机械抖动过大时可把 DEBOUNCE_MS 提到 30~50。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐