#include <U8g2lib.h> // 引入U8g2库,用于驱动OLED显示屏
#include <Wire.h> // 用于 I2C 通信,RTC 模块和 OLED 可能会用到
#include <RTClib.h> // 引入实时时钟库,若没有硬件 RTC 模块,可注释掉相关代码改用软件模拟
// 初始化OLED显示屏:使用硬件I2C连接,屏幕型号为SSD1306 128x64,地址默认,不使用复位引脚
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE);
RTC_DS1307 rtc; // 定义 RTC 对象,若用其他 RTC 模块(如 DS3231 等),按需修改这里
// 定义引脚:
const int pirPin = 2; // PIR人体红外传感器引脚(检测人员运动)
const int lightPin = A0; // 光敏电阻引脚(检测环境光照强度)
const int ledPin = 3; // LED灯控制引脚(执行亮灯/熄灯)
const int modeBtnPin = 4; // 模式切换按键(自动/手动)
const int upBtnPin = 5; // 阈值增加/亮度增加按键
const int downBtnPin = 6; // 阈值减少/亮度减少按键
const int manualOffBtnPin = 7; // 新增:手动模式下关闭LED的按键引脚
int lightThreshold = 500; // 光照阈值:低于此值时,才会触发LED亮灯逻辑(自动模式下生效)
unsigned long lastMotionTime = 0; // 记录最后一次检测到人员的时间(用于自动模式延时熄灯)
bool isManualMode = false; // 标记是否处于手动模式
// 若没有硬件 RTC 模块,可定义以下变量用软件模拟时间
// unsigned long systemTime = 0;
// int hours = 0, minutes = 0, seconds = 0;
void setup() {
// 配置引脚模式:
pinMode(pirPin, INPUT); // PIR传感器设为输入模式
pinMode(ledPin, OUTPUT); // LED控制引脚设为输出模式
pinMode(modeBtnPin, INPUT_PULLUP); // 模式切换按键启用内部上拉电阻
pinMode(upBtnPin, INPUT_PULLUP); // 阈值增加按键启用内部上拉电阻
pinMode(downBtnPin, INPUT_PULLUP); // 阈值减少按键启用内部上拉电阻
pinMode(manualOffBtnPin, INPUT_PULLUP); // 新增:配置手动关灯按键引脚
u8g2.begin(); // 初始化OLED显示屏
digitalWrite(ledPin, LOW); // 初始状态:关闭LED
// 初始化 RTC(若有硬件 RTC 模块时执行)
if (!rtc.begin()) {
u8g2.firstPage();
do {
u8g2.setFont(u8g2_font_ncenB08_tr);
u8g2.drawStr(0, 22, "RTC Error!");
} while (u8g2.nextPage());
while (1); // 若 RTC 初始化失败,程序停止在这里
}
// 首次使用 RTC 时,可取消注释下面一行设置初始时间,之后注释掉避免每次重置
// rtc.adjust(DateTime(2024, 7, 1, 12, 0, 0));
}
void loop() {
// 1. 读取传感器数据
int lightValue = analogRead(lightPin); // 读取光敏电阻的模拟值(0-1023),反映光照强度
int pirValue = digitalRead(pirPin); // 读取PIR传感器状态(HIGH=检测到人员,LOW=无人员)
// 2. 处理按键逻辑
handleButtons();
// 3. 不同模式下的控制逻辑
if (isManualMode) {
manualModeControl();
} else {
autoModeControl(lightValue, pirValue);
}
// 4. 更新实时时间(若有硬件 RTC 模块时执行,若无则用软件模拟部分)
DateTime now = rtc.now(); // 获取当前 RTC 时间
// 若没有硬件 RTC 模块,用下面代码模拟时间(需注释掉上面一行)
// systemTime = millis();
// if (systemTime / 1000 % 60 == 0 && systemTime / 1000 != 0) {
// seconds = 0;
// minutes++;
// if (minutes >= 60) {
// minutes = 0;
// hours++;
// if (hours >= 24) hours = 0;
// }
// } else {
// seconds = systemTime / 1000 % 60;
// }
// 5. OLED显示逻辑(实时刷新屏幕内容)
u8g2.firstPage(); // 开始绘制OLED第一页(U8g2库需通过分页绘制实现刷新)
do {
u8g2.setFont(u8g2_font_ncenB08_tr); // 设置字体:使用8号英文字体(ncenB08_tr)
// 绘制标题
u8g2.drawStr(0, 10, "Smart Street Light");
// 绘制实时时间(若有硬件 RTC 模块)
char timeStr[20];
sprintf(timeStr, "Time: %02d:%02d:%02d", now.hour(), now.minute(), now.second());
u8g2.drawStr(0, 22, timeStr);
// 若没有硬件 RTC 模块,用下面代码绘制模拟时间
// sprintf(timeStr, "Time: %02d:%02d:%02d", hours, minutes, seconds);
// u8g2.drawStr(0, 22, timeStr);
// 绘制光照值
char lightStr[20];
sprintf(lightStr, "Light: %d", lightValue);
u8g2.drawStr(0, 34, lightStr);
// 绘制人员状态
char pirStr[20];
sprintf(pirStr, "Person: %s", pirValue ? "Yes" : "No");
u8g2.drawStr(0, 46, pirStr);
// 绘制阈值
char thresholdStr[20];
sprintf(thresholdStr, "Threshold: %d", lightThreshold);
u8g2.drawStr(0, 58, thresholdStr);
// 绘制当前模式
char modeStr[20];
sprintf(modeStr, "Mode: %s", isManualMode ? "Manual" : "Auto");
u8g2.drawStr(0, 70, modeStr);
} while (u8g2.nextPage()); // 循环绘制所有分页(此处只有1页,实际是完整刷新屏幕)
delay(100); // 控制主循环延时,降低刷新频率,避免过度占用资源
}
// 处理按键的函数,把按键相关逻辑封装在这里
void handleButtons() {
static bool lastModeBtn = HIGH;
static bool lastUpBtn = HIGH;
static bool lastDownBtn = HIGH;
static bool lastManualOffBtn = HIGH; // 新增:手动关灯按键消抖变量
// 模式切换按键处理(消抖)
bool modeBtn = digitalRead(modeBtnPin) == LOW;
if (modeBtn != lastModeBtn) {
delay(50); // 简单消抖
if (digitalRead(modeBtnPin) == LOW) {
isManualMode = !isManualMode;
if (isManualMode) {
digitalWrite(ledPin, LOW); // 进入手动模式时先关灯,方便后续手动控制
}
}
lastModeBtn = modeBtn;
}
// 阈值增加按键处理(消抖,自动模式下才调整阈值;手动模式下可扩展调整亮度等)
bool upBtn = digitalRead(upBtnPin) == LOW;
if (upBtn != lastUpBtn) {
delay(50);
if (digitalRead(upBtnPin) == LOW) {
if (!isManualMode) {
lightThreshold = min(lightThreshold + 50, 1000); // 阈值最大设为1000,可按需改
} else {
// 手动模式下可扩展:比如调整LED亮度,这里简单示例,可自行完善
int currentBrightness = analogRead(ledPin); // 实际应该用变量记录,这里只是示例
analogWrite(ledPin, min(currentBrightness + 51, 255)); // 每次加51,到255为止
}
}
lastUpBtn = upBtn;
}
// 阈值减少按键处理(消抖,自动模式下才调整阈值;手动模式下可扩展调整亮度等)
bool downBtn = digitalRead(downBtnPin) == LOW;
if (downBtn != lastDownBtn) {
delay(50);
if (digitalRead(downBtnPin) == LOW) {
if (!isManualMode) {
lightThreshold = max(lightThreshold - 50, 100); // 阈值最小设为100,可按需改
} else {
// 手动模式下可扩展:比如调整LED亮度,这里简单示例,可自行完善
int currentBrightness = analogRead(ledPin); // 实际应该用变量记录,这里只是示例
analogWrite(ledPin, max(currentBrightness - 51, 0)); // 每次减51,到0为止
}
}
lastDownBtn = downBtn;
}
// 新增:手动模式下关闭LED按键处理(消抖)
bool manualOffBtn = digitalRead(manualOffBtnPin) == LOW;
if (manualOffBtn != lastManualOffBtn) {
delay(50);
if (digitalRead(manualOffBtnPin) == LOW && isManualMode) { // 仅在手动模式下生效
digitalWrite(ledPin, LOW); // 关闭LED
}
lastManualOffBtn = manualOffBtn;
}
}
// 手动模式下的控制逻辑,这里简单示例:可通过按键调整亮度等,也可保持常亮等逻辑
void manualModeControl() {
// 手动模式下,你可以让用户完全自主控制LED,比如通过按键实时调整亮度
// 这里只是简单示例,你可根据需求扩展,比如:
// 持续保持当前设置的亮度(由阈值加减按键在手动模式下调整的)
// 或者做其他自定义逻辑,比如按一下开,再按一下关等更复杂交互
}
// 自动模式下的控制逻辑
void autoModeControl(int lightValue, int pirValue) {
if (lightValue <= lightThreshold && pirValue == HIGH) { // 光照弱 + 检测到人员
digitalWrite(ledPin, HIGH); // 点亮LED
lastMotionTime = millis(); // 记录当前时间(用于延时)
} else if (millis() - lastMotionTime > 10000) { // 人员离开后,延时10秒(10000毫秒)
digitalWrite(ledPin, LOW); // 关闭LED
} else if (lightValue > lightThreshold) { // 光照强时,直接关灯
digitalWrite(ledPin, LOW);
}
}