
多传感器代码,默认生成固件,电容式触摸开关,针脚IO1,指示灯IO48,3V3/GND供电
/*
* t_touch.cpp — TTP223B 电容触摸 自锁开关(软件双稳态)
* 烧录: platformio run -e touch -t upload
*
* 硬件: TTP223B 模块 VCC / GND / SIG; SIG -> IO1; 板载 WS2812(IO48) 红灯作输出指示。
* 行为: 模块本身为【点动型】(常态低, 触摸高, ~12s 无触摸自动转低功耗模式)。
* 本固件把"点动"变成【自锁/双稳态】: 每触摸一下翻转一次,
* ON -> 红灯亮; OFF -> 红灯灭; 再触摸一下反转。
* 自锁状态存 NVS, 断电不丢(像真正的自锁开关)。
*
* 接线安全: TTP223B 的 VCC 务必接 3.3V(不要 5V)! ESP32-S3 的 IO 非 5V 耐压,
* 若模块由 5V 供电, SIG 输出 5V 会烧 IO1。模块支持 2.0~5.5V, 接 3.3V 即可稳定工作。
*
* 串口命令:
* help 显示帮助
* state 查询当前自锁状态 / 武装标志 / 原始电平
* led on|off 手动置位 / 复位(并保存到 NVS)
* level high|low 翻转触发电平(模块极性接反时免重烧)
* restart 软重启
*/
#include <Arduino.h>
#include <Preferences.h>
#include "pins.h"
// ====================== 硬件与参数 ======================
#define TOUCH_PIN 1 // IO1:全板唯一空闲 GPIO(J2 排针引出)
#define TOUCH_DEBOUNCE_MS 40 // 软件去抖(TTP223 内部已 ~80ms,这里再保险一点)
// 板载 WS2812 单像素:复用核心 neopixelWrite,红灯=开,灭=关(见 pins.h PIN_WS2812=48)
static const char *NS = "touchlock";
static bool g_on = false; // 自锁输出:true = 红灯亮
static bool g_armed = true; // 边沿武装:手指离开后重新武装,防止一次触摸多次翻转
static int g_level = -1; // 去抖后的稳定电平(-1 = 未初始化)
static unsigned long g_levelSince = 0;
static int g_active = HIGH; // 触发电平(TTP223 默认触摸=高;接反用 level 命令翻)
// ====================== LED 反馈(板载 WS2812 单像素)======================
static void setLed(bool on)
{
if (on)
neopixelWrite(PIN_WS2812, 255, 0, 0); // 红灯
else
neopixelWrite(PIN_WS2812, 0, 0, 0); // 灭
delayMicroseconds(80); // WS2812 帧后 LOW>50us 才锁存,补余量确保可靠
}
// 设定自锁状态(可选保存 NVS:断电保持,符合"自锁开关"语义)
static void applyState(bool on, bool save)
{
g_on = on;
setLed(g_on);
if (save)
{
Preferences p;
p.begin(NS, false);
p.putBool("on", g_on);
p.putInt("active", g_active);
p.end();
}
Serial.printf("[STATE] 自锁 %s -> 红灯%s\n", g_on ? "ON " : "OFF", g_on ? "亮" : "灭");
}
// ====================== 串口命令 ======================
static void handleCmd(const String &s)
{
String c = s;
c.toLowerCase();
c.trim();
if (c == "help" || c == "?")
{
Serial.println("===== TTP223B 触摸自锁开关 串口命令 =====");
Serial.println(" help 显示本帮助");
Serial.println(" state 查询当前自锁状态/武装标志/原始电平");
Serial.println(" led on|off 手动置位/复位(并保存到 NVS)");
Serial.println(" level high|low 翻转触发电平(接线极性接反免重烧)");
Serial.println(" restart 软重启");
}
else if (c == "state")
{
Serial.printf("[STATE] on=%s armed=%s active=%s raw_pin=%d\n",
g_on ? "true" : "false", g_armed ? "true" : "false",
g_active == HIGH ? "HIGH" : "LOW", (int)digitalRead(TOUCH_PIN));
}
else if (c == "led on")
{
applyState(true, true);
}
else if (c == "led off")
{
applyState(false, true);
}
else if (c.startsWith("level "))
{
String m = c.substring(6);
m.trim();
if (m == "high")
g_active = HIGH;
else if (m == "low")
g_active = LOW;
else
{
Serial.println("level usage: level high|low");
return;
}
applyState(g_on, true); // 顺带保存极性
Serial.printf("[LEVEL] active=%s\n", g_active == HIGH ? "HIGH" : "LOW");
}
else if (c == "restart" || c == "reboot")
{
Serial.println("rebooting...");
delay(200);
ESP.restart();
}
else
{
Serial.printf("unknown: '%s' (send help)\n", s.c_str());
}
}
// ====================== setup / loop ======================
void setup()
{
Serial.begin(115200);
Serial.setTxTimeoutMs(0); // 监视器关闭后不阻塞
delay(1000);
pinMode(TOUCH_PIN, INPUT); // TTP223B 推挽输出,无需上/下拉
pinMode(PIN_WS2812, OUTPUT);
// 恢复上次自锁状态(断电不丢,符合自锁开关语义)
Preferences p;
p.begin(NS, true);
g_on = p.getBool("on", false);
g_active = p.getInt("active", HIGH);
p.end();
setLed(g_on);
Serial.println("=== TTP223B 触摸自锁开关 ===");
Serial.printf("TOUCH_PIN=IO%d ACTIVE=%s WS2812=IO%d(红)\n",
TOUCH_PIN, g_active == HIGH ? "HIGH" : "LOW", PIN_WS2812);
Serial.printf("恢复上次自锁状态: %s\n", g_on ? "ON(红灯亮)" : "OFF(红灯灭)");
Serial.println("每触摸一次翻转状态;串口 help 看命令");
}
void loop()
{
// ---- 串口命令:行尾或 80ms 静默判为一条 ----
static char buf[48];
static int len = 0;
static unsigned long last = 0;
while (Serial.available() > 0)
{
char c = (char)Serial.read();
last = millis();
if (c == '\n' || c == '\r')
{
if (len > 0)
{
buf[len] = 0;
handleCmd(String(buf));
len = 0;
}
}
else if (len < (int)sizeof(buf) - 1)
{
buf[len++] = c;
}
}
if (len > 0 && (millis() - last) > 80)
{
buf[len] = 0;
handleCmd(String(buf));
len = 0;
}
// ---- 自锁核心:稳定上升沿 + 已武装 -> 翻转一次;电平回低 -> 重新武装 ----
bool raw = (digitalRead(TOUCH_PIN) == g_active);
unsigned long now = millis();
if (g_level < 0)
{
// 首帧:建立基线,不翻转、不打印
g_level = raw ? 1 : 0;
g_levelSince = now;
}
else
{
if ((raw ? 1 : 0) != g_level)
{
g_level = raw ? 1 : 0;
g_levelSince = now;
}
bool stable = (now - g_levelSince) >= TOUCH_DEBOUNCE_MS;
if (g_level == 1 && stable && g_armed)
{
g_armed = false; // 一次触摸只翻转一次,直到手指离开
applyState(!g_on, true);
}
else if (g_level == 0)
{
g_armed = true; // 手指离开后重新武装,准备下一次触摸
}
}
delay(5);
}