ESP8266+WS2812B闹钟:触发时sunrise()函数无法运行求助
问题:ESP8266闹钟触发时sunrise()函数无法生效
使用ESP8266和WS2812B制作闹钟,期望闹钟时间触发时调用sunrise()函数实现日出灯光效果。该函数直接放入loop()中时运行正常,但在闹钟触发逻辑内调用时完全没有效果。
触发代码片段
if (AlarmData.AlarmOn[Current.Day]) { if (!AlarmActive)//do not enter this routine if alarm already active { if (Current.Hour == AlarmData.Hour[Current.Day]) { if (Current.Minute == AlarmData.Minute[Current.Day]) { if (Current.Second > 0 && Current.Second < 3) { AlarmActive = true; sunrise(); alarmTriggerTime = micros(); Serial.println("Its time for your alarm!"); } } } } }
完整项目代码
#include <ESP8266WiFi.h> #include <ArduinoOTA.h> #include <ESP8266WebServer.h> #include <WiFiManager.h> #include "EEPROMAnything.h" #include "get_time.h" #include "OTA.h" #include <stdio.h> #include <stdint.h> #include "web_portal.h" #include "ESP8266TimerInterrupt.h" #include "restore_factory_settings.h" #include "FastLED.h" #define NUM_LEDS 60 #define DATA_PIN 5 // Define the array of leds CRGB leds[NUM_LEDS]; #define timeZone 3 #define TenSecs 10000000 #define OneMin 60000000 #define TenMins 600000000 #define TIMER_INTERVAL_MS 1000 ESP8266Timer ITimer; int timer; int alarmTriggerTime; struct CurrentTime Current; bool AlarmActive; struct AlarmDataStruct AlarmData; int WiFiTimer; char factory_settings_stored [3]; bool OneSecoundPassed; void ICACHE_RAM_ATTR TimerHandler(void) { OneSecoundPassed = true; } void setup() { Serial.begin(115200); Serial.println("Booting"); EEPROM.begin(512); EEPROM_readAnything(150, factory_settings_stored); if (memcmp(&factory_settings_stored, "YES", 3) != 0) { restore_factory_settings(); } FastLED.addLeds<WS2812B, DATA_PIN, GRB>(leds, NUM_LEDS); FastLED.setBrightness(0); WiFi.mode(WIFI_STA); WiFiManager wm; bool response; response = wm.autoConnect("AutoConnectAP"); // anonymous ap if (!response) { Serial.println("Failed to connect"); // ESP.restart(); } else { //if you get here you have connected to the WiFi Serial.println("Lets Go"); } start_server(); SetupOTA(); setup_time(timeZone); Current = Current_Time(); EEPROM_readAnything(100, AlarmData); Serial.print("Alarm set for "); Serial.print(AlarmData.Hour[Current.Day]); Serial.print(":"); Serial.println(AlarmData.Minute[Current.Day]); timer = micros(); WiFiTimer = timer; // Interval in microsecs if (ITimer.attachInterruptInterval(TIMER_INTERVAL_MS * 1000, TimerHandler)) { Serial.println("Starting ITimer OK, millis() = " + String(millis())); } else { Serial.println("Can't set ITimer correctly. Select another freq. or interval"); } } void loop() { if (!AlarmActive) { ArduinoOTA.handle(); handle_client(); if ((micros() - WiFiTimer) > TenMins) // check if wifi connection lost and if so try to reconnect { if (WiFi.status() != WL_CONNECTED) { ESP.restart(); //try to reconnect rather than resetting } WiFiTimer = micros(); } if (OneSecoundPassed) { updateLocalTime(); OneSecoundPassed = false; } //update current hour from NTP server if ((micros() - timer) > TenMins) { Current = Current_Time(); timer = micros(); } } if (AlarmActive) { if ((micros() - alarmTriggerTime) > TenMins) { AlarmActive = false; //if alarm active for 10mins and no one switches it off then do it auto } } if (AlarmData.AlarmOn[Current.Day]) { if (!AlarmActive)//do not enter this routine if alarm already active { if (Current.Hour == AlarmData.Hour[Current.Day]) { if (Current.Minute == AlarmData.Minute[Current.Day]) { if (Current.Second > 0 && Current.Second < 3) { AlarmActive = true; sunrise(); alarmTriggerTime = micros(); Serial.println("Its time for your alarm!"); } } } } } else { if (!AlarmActive) { } } } void updateLocalTime () { Current.Second++; if (Current.Second >= 60) { Current.Second = 0; Current.Minute++; if (Current.Minute >= 60) { Current.Minute = 0; Current.Hour++; if (Current.Hour >= 24) { Current.Hour = 0; Current.Day++; if (Current.Day >= 7) { Current.Day = 0; } } } } char tempTime[6]; if (Current.Minute < 10 && Current.Second < 10) { sprintf(tempTime, "0%d:0%d", Current.Minute, Current.Second); } else if (Current.Minute < 10) { sprintf(tempTime, "0%d:%d", Current.Minute, Current.Second); } else if (Current.Second < 10) { sprintf(tempTime, "%d:0%d", Current.Minute, Current.Second); } else { sprintf(tempTime, "%d:%d", Current.Minute, Current.Second); } } void sunrise() { static const uint8_t sunriseLength = 30; //(min) static const uint8_t interval = (sunriseLength * 60) / 256; static const uint8_t binterval = (sunriseLength * 60) / 256; // current gradient palette color index static uint8_t heatIndex = 0; // start out at 0 static uint8_t brIndex = 0; // HeatColors_p is a gradient palette built in to FastLED // that fades from black to red, orange, yellow, white // feel free to use another palette or define your own custom one CRGB color = ColorFromPalette(HeatColors_p, heatIndex); fill_solid(leds, NUM_LEDS, color); // fill the entire strip with the current color EVERY_N_SECONDS(binterval) { if (brIndex < 255) { FastLED.setBrightness(brIndex); brIndex++; } } EVERY_N_SECONDS(interval) { if (heatIndex < 255) { heatIndex++; } } FastLED.show(); }
问题原因分析
EVERY_N_SECONDS宏的工作机制:FastLED的EVERY_N_SECONDS宏需要反复调用包含它的函数才能生效。它内部会记录上次执行的时间,每次函数被调用时检查是否达到指定间隔,只有这样宏内的代码才会触发。当前闹钟触发时只调用了一次sunrise(),之后没有再调用,导致亮度和颜色索引的递增代码完全无法执行。- 初始状态无可见效果:
sunrise()内的静态变量heatIndex和brIndex初始值都是0,第一次调用时LED会被设置为全黑,之后没有后续调用更新状态,所以看起来函数完全没生效。 - AlarmActive状态下的逻辑缺失:当
AlarmActive设为true后,loop()跳过了大部分常规逻辑,但也没有加入持续调用sunrise()的代码,导致日出效果无法持续运行。
解决方案
1. 调整变量结构与触发逻辑
将sunrise()内的静态变量改为全局变量,方便重置和状态检查;新增一个标志位控制日出效果的运行状态:
// 新增全局变量 bool isSunriseActive = false; uint8_t heatIndex = 0; uint8_t brIndex = 0; const uint8_t sunriseLength = 30; //(min) const uint8_t interval = (sunriseLength * 60) / 256; const uint8_t binterval = (sunriseLength * 60) / 256;
修改闹钟触发代码,设置运行标志位而非直接调用一次sunrise():
if (Current.Second > 0 && Current.Second < 3) { AlarmActive = true; isSunriseActive = true; // 重置日出效果的状态变量,避免上次运行的残留影响 heatIndex = 0; brIndex = 0; alarmTriggerTime = micros(); Serial.println("Its time for your alarm!"); }
2. 重构sunrise()函数
移除EVERY_N_SECONDS宏,改用millis()手动计时,确保每次调用都能检查时间间隔并更新状态:
void sunrise() { // 检查日出效果是否已完成 if (heatIndex >= 255 && brIndex >= 255) { isSunriseActive = false; return; } // 设置当前颜色并填充LED CRGB color = ColorFromPalette(HeatColors_p, heatIndex); fill_solid(leds, NUM_LEDS, color); // 手动控制亮度递增 static unsigned long lastBrUpdate = 0; if (brIndex < 255 && (millis() - lastBrUpdate) >= (binterval * 1000)) { FastLED.setBrightness(brIndex); brIndex++; lastBrUpdate = millis(); } // 手动控制颜色索引递增 static unsigned long lastHeatUpdate = 0; if (heatIndex < 255 && (millis() - lastHeatUpdate) >= (interval * 1000)) { heatIndex++; lastHeatUpdate = millis(); } // 更新LED显示 FastLED.show(); }
3. 在loop()中持续调用sunrise()
修改AlarmActive状态下的逻辑,确保日出效果持续运行,并在超时后重置状态:
if (AlarmActive) { if ((micros() - alarmTriggerTime) > TenMins) { AlarmActive = false; isSunriseActive = false; // 重置LED状态 heatIndex = 0; brIndex = 0; FastLED.setBrightness(0); FastLED.clear(); FastLED.show(); } else { // 持续运行日出效果 if (isSunriseActive) { sunrise(); } } }
内容的提问来源于stack exchange,提问作者GkNx
相关产品推荐
相关产品推荐

