#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <DHTesp.h>
// ==================== 引脚定义 ====================
#define OLED_SDA 41
#define OLED_SCL 42
#define DHT_PIN 40
#define MQ2_AO_PIN 19
#define LDR_AO_PIN 20 // D20
#define BUZZER_PIN 37
#define FAN_RELAY 46
#define LIGHT_RELAY 5
// ==================== 常量配置 ====================
#define TEMP_ON_THRESH 30.0f
#define TEMP_OFF_THRESH 26.0f
#define LUX_ON_THRESH 100.0f
#define SMOKE_ALARM_TH 100.0f // ppm
// LDR Gamma 校准参数
#define GAMMA 0.7
#define RL10 50.0
#define FIXED_R 2000.0
#define VCC 3.3
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
DHTesp dhtSensor;
bool autoMode = true;
bool manualFan = false;
bool manualLight = false;
bool smokeAlarmActive = false;
float temperature = 0.0f;
float humidity = 0.0f;
float realLux = 0.0f;
int realPPM = 0;
int smokeRaw = 0;
unsigned long lastSensorRead = 0;
unsigned long lastWiFiPrint = 0;
unsigned long lastDisplayUpdate = 0;
// ==================== 烟雾校准(查表插值)====================
const float ppm_real[] = {0.1, 0.2, 0.3, 0.4, 0.5, 1, 2, 10, 20, 55, 209};
const int ppm_wrong[] = {2249, 2595, 2974, 3216, 3426, 4043, 4835, 6312, 6945, 7749, 8561};
const int ppm_points = 11;
float correctPPM(int wrong) {
if (wrong <= ppm_wrong[0]) return ppm_real[0];
if (wrong >= ppm_wrong[ppm_points-1]) return ppm_real[ppm_points-1];
for (int i = 0; i < ppm_points-1; i++) {
if (wrong >= ppm_wrong[i] && wrong <= ppm_wrong[i+1]) {
float t = (float)(wrong - ppm_wrong[i]) / (ppm_wrong[i+1] - ppm_wrong[i]);
return ppm_real[i] + t * (ppm_real[i+1] - ppm_real[i]);
}
}
return ppm_real[ppm_points-1];
}
// ==================== LDR 读取(Gamma,无缩放)====================
float readLux() {
int adc = analogRead(LDR_AO_PIN);
float voltage = adc * (VCC / 4095.0);
float resistance = FIXED_R * voltage / (VCC - voltage);
float lux = pow(RL10 * 1000 * pow(10, GAMMA) / resistance, (1.0 / GAMMA))/10;
return lux;
}
int readSmokePPM() {
smokeRaw = analogRead(MQ2_AO_PIN);
int wrongPPM = map(smokeRaw, 0, 4095, 0, 10000)-30;
return (int)correctPPM(wrongPPM);
}
// ==================== 自动控制 ====================
void updateFanByTemp() {
if (temperature > TEMP_ON_THRESH) {
digitalWrite(FAN_RELAY, HIGH);
} else if (temperature < TEMP_OFF_THRESH) {
digitalWrite(FAN_RELAY, LOW);
}
}
void updateLightByLux() {
if (realLux < LUX_ON_THRESH) {
digitalWrite(LIGHT_RELAY, HIGH);
} else if (realLux > LUX_ON_THRESH) {
digitalWrite(LIGHT_RELAY, LOW);
}
}
void applyNormalControl() {
if (autoMode) {
updateFanByTemp();
updateLightByLux();
} else {
digitalWrite(FAN_RELAY, manualFan ? HIGH : LOW);
digitalWrite(LIGHT_RELAY, manualLight ? HIGH : LOW);
}
}
// ==================== 烟雾报警 ====================
void handleSmokeAlarm() {
realPPM = readSmokePPM();
bool nowAlarm = (realPPM > SMOKE_ALARM_TH);
if (nowAlarm && !smokeAlarmActive) {
smokeAlarmActive = true;
digitalWrite(FAN_RELAY, HIGH);
tone(BUZZER_PIN, 2000);
}
else if (!nowAlarm && smokeAlarmActive) {
smokeAlarmActive = false;
noTone(BUZZER_PIN);
applyNormalControl();
}
}
// ==================== OLED 显示(每行单独,行间距 16 像素)====================
void updateDisplay() {
display.clearDisplay();
display.setTextSize(2);
display.setTextColor(SSD1306_WHITE);
int y = 0;
int lineHeight = 16; // textSize(2) 时实际高度16,行距可略增
char buf[16];
// 第一行:温度 + 报警标志(右侧)
snprintf(buf, sizeof(buf), "T:%.0fC%s", temperature, smokeAlarmActive ? "alarm" : "normal");
display.setCursor(0, y);
display.print(buf);
y += lineHeight;
// 第二行:湿度
snprintf(buf, sizeof(buf), "H:%.0f%%", humidity);
display.setCursor(0, y);
display.print(buf);
y += lineHeight;
// 第三行:光照
snprintf(buf, sizeof(buf), "L:%.0fLx", realLux);
display.setCursor(0, y);
display.print(buf);
y += lineHeight;
// 第四行:烟雾
snprintf(buf, sizeof(buf), "S:%dppm", realPPM);
display.setCursor(0, y);
display.print(buf);
display.display();
}
// ==================== 辅助功能 ====================
void wifiUploadSim() {
Serial.printf("[WiFi] T=%.1f H=%.1f Lx=%.0f PPM=%d Fan=%d Light=%d Alarm=%d\n",
temperature, humidity, realLux, realPPM,
digitalRead(FAN_RELAY), digitalRead(LIGHT_RELAY), smokeAlarmActive);
}
// ==================== 串口命令处理(简化版)====================
void handleSerialCommands() {
if (!Serial.available()) return;
String cmd = Serial.readStringUntil('\n');
cmd.trim(); cmd.toLowerCase();
if (cmd == "auto") {
autoMode = true;
Serial.println("[CMD] AUTO");
if (!smokeAlarmActive) applyNormalControl();
}
else if (cmd == "manual") {
autoMode = false;
manualFan = digitalRead(FAN_RELAY);
manualLight = digitalRead(LIGHT_RELAY);
Serial.println("[CMD] MANUAL");
}
else if (cmd == "fon" && !autoMode) {
manualFan = true;
if (!smokeAlarmActive) digitalWrite(FAN_RELAY, HIGH);
Serial.println("[CMD] Fan ON");
}
else if (cmd == "foff" && !autoMode) {
manualFan = false;
if (!smokeAlarmActive) digitalWrite(FAN_RELAY, LOW);
Serial.println("[CMD] Fan OFF");
}
else if (cmd == "lon" && !autoMode) {
manualLight = true;
if (!smokeAlarmActive) digitalWrite(LIGHT_RELAY, HIGH);
Serial.println("[CMD] Light ON");
}
else if (cmd == "loff" && !autoMode) {
manualLight = false;
if (!smokeAlarmActive) digitalWrite(LIGHT_RELAY, LOW);
Serial.println("[CMD] Light OFF");
}
else if (cmd == "status") {
// 输出完整状态,包含温湿度光照等
Serial.printf("Mode:%s Temp:%.1fC Hum:%.1f%% Lux:%.0f PPM:%d Fan:%s Light:%s Alarm:%d\n",
autoMode?"AUTO":"MANUAL",
temperature, humidity, realLux, realPPM,
digitalRead(FAN_RELAY)?"ON":"OFF",
digitalRead(LIGHT_RELAY)?"ON":"OFF",
smokeAlarmActive);
}
else {
if (!autoMode) Serial.println("Use: fon, foff, lon, loff, auto, manual, status");
else Serial.println("Use: manual, status (auto mode)");
}
}
// ==================== 初始化 ====================
void setup() {
Serial.begin(115200);
pinMode(FAN_RELAY, OUTPUT);
pinMode(LIGHT_RELAY, OUTPUT);
pinMode(BUZZER_PIN, OUTPUT);
digitalWrite(FAN_RELAY, LOW);
digitalWrite(LIGHT_RELAY, LOW);
noTone(BUZZER_PIN);
Wire.begin(OLED_SDA, OLED_SCL);
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println("OLED init fail");
}
display.clearDisplay();
display.display();
dhtSensor.setup(DHT_PIN, DHTesp::DHT22);
Serial.println("System Ready");
Serial.println("Commands: auto, manual, fon, foff, lon, loff, status");
}
// ==================== 主循环 ====================
void loop() {
unsigned long now = millis();
if (now - lastSensorRead >= 2000) {
lastSensorRead = now;
TempAndHumidity data = dhtSensor.getTempAndHumidity();
if (!isnan(data.temperature) && !isnan(data.humidity)) {
temperature = data.temperature;
humidity = data.humidity;
} else {
Serial.println("DHT22 error");
}
realLux = readLux();
}
handleSmokeAlarm();
if (!smokeAlarmActive) {
if (autoMode) {
updateFanByTemp();
updateLightByLux();
} else {
digitalWrite(FAN_RELAY, manualFan ? HIGH : LOW);
digitalWrite(LIGHT_RELAY, manualLight ? HIGH : LOW);
}
} else {
// 报警时风扇强制开,灯光按模式控制
if (autoMode) updateLightByLux();
else digitalWrite(LIGHT_RELAY, manualLight ? HIGH : LOW);
}
if (now - lastDisplayUpdate >= 200) {
lastDisplayUpdate = now;
updateDisplay();
}
if (now - lastWiFiPrint >= 5000) {
lastWiFiPrint = now;
wifiUploadSim();
}
handleSerialCommands();
delay(50);
}显示屏
光线传感器
蜂鸣器
温湿度传感器
MQ-2气体传感器
照明
风扇