#include "HermitCrab.h" #include "Config.h" #include "AHT2x.h" #include "NTC_10K.h" #include "ZCD.h" #include "History.h" #include "ConnectWiFi.h" #include "WiFiHost.h" #include "TimeManager.h" #include "OTA.h" #include "UI.h" #include "BLEScan.h" #include #include "esp_coexist.h" // GPIO Headers #include "driver/gpio.h" #include "driver/timer.h" #include "driver/adc.h" #define TAG_SETUP "TAG_SETUP" // Task handle TaskHandle_t TaskHandle_0; bool g_bWiFiSetupExecuted = false; bool g_bWiFiHasBeenConnected = false; extern STATUS_TYPE status; extern CHistory history; // Hardware First void setupGPIO1(); void setupGPIO2(); void setupPWM(); void setupZCD(); void setupTimers(); void setupConfig(); void setupStatus(); void restoreStatus(); void setupWiFi(); void setupPostWiFi(bool bBoot); void setupSensor(); void setup_BLE(); void scanI2C(); void setup() { // put your setup code here, to run once: #ifdef DEBUG Serial.begin(115200); #endif //esp_log_level_set("*", ESP_LOG_INFO); // Global log level //esp_log_level_set("BLE_POLL", ESP_LOG_INFO); // Module-specific level //esp_coex_preference_set(ESP_COEX_PREFER_BT); DPRINTLN("**********************"); DPRINTF(" SETUP - Start - ver. %s type: %d\n", HC__VERSION, THIS_DEVICE_TYPE); DPRINTLN("**********************"); g_bWiFiHasBeenConnected = false; g_bWiFiHasBeenConnected = false; g_nYear = 2024; g_nMonth = 10; g_nDay = 15; g_nHour = 0; g_nMinute = 0; g_nSecond = 0; bShowSensor = false; // Hardware setupPWM(); setupGPIO1(); setupConfig(); setupStatus(); scanI2C(); setupSensor(); ui.setup(); setupWiFi(); if (aht25.sensor() || aht10_0x39.sensor()) { ui.message(4, (char *) "Sensor... OK!"); } else { ui.message(4, (char *) "Sensor... None!"); } ui.message(5, (char *) "ZCD..."); setupZCD(); ui.message(5, (char *) "ZCD... OK!"); ui.message(6, (char *) "Setup OK!"); //if (!isWiFiConnected) delay(3000); ble.setupConnect(config.nBLESensorAddr, config.nBLESensorAddr2); // Restore Status restoreStatus(); // Create a task pinned to core 0 xTaskCreatePinnedToCore( core0Task, // Function to run as a task "Task0", // Task name 20480, // Stack size in words NULL, // Task input parameter 1, // Priority of the task &TaskHandle_0, // Task handle 0 // Core 0 ); setupTimers(); setupGPIO2(); DPRINTLN("Setup Completed\n========================\n"); } // ====================================================================== // // Setup // // ====================================================================== void setupGPIO1() { // Set PIN Direction & Status // ========================= // 1. NTC (ADC 설정) // ========================= // adc1_config_channel_atten에서 내부적으로 핀 설정을 하므로 방향 설정은 생략 가능합니다. adc1_config_width(ADC_WIDTH_BIT_12); // 0~4095 adc1_config_channel_atten(ADC1_CHANNEL_3, ADC_ATTEN_DB_11); // ========================= // 2. UI Buttons 설정 // ========================= gpio_config_t io_conf_ui = {}; io_conf_ui.intr_type = GPIO_INTR_ANYEDGE; io_conf_ui.mode = GPIO_MODE_INPUT; io_conf_ui.pin_bit_mask = (1ULL << PIN_SW_SET) | (1ULL << PIN_SW_UP) | (1ULL << PIN_SW_DOWN); io_conf_ui.pull_up_en = GPIO_PULLUP_ENABLE; io_conf_ui.pull_down_en = GPIO_PULLDOWN_DISABLE; gpio_config(&io_conf_ui); // ========================= // 3. ZCD Inputs 설정 // ========================= gpio_config_t io_conf_zcd = {}; io_conf_zcd.intr_type = GPIO_INTR_ANYEDGE; io_conf_zcd.mode = GPIO_MODE_INPUT; io_conf_zcd.pin_bit_mask = (1ULL << PIN_ZCD_AC) | (1ULL << PIN_ZCD_LOAD); io_conf_zcd.pull_up_en = GPIO_PULLUP_DISABLE; io_conf_zcd.pull_down_en = GPIO_PULLDOWN_DISABLE; gpio_config(&io_conf_zcd); // ========================= // 4. Heater Outputs 설정 // ========================= gpio_config_t io_conf_heater = {}; io_conf_heater.intr_type = GPIO_INTR_DISABLE; io_conf_heater.mode = GPIO_MODE_OUTPUT; io_conf_heater.pin_bit_mask = (1ULL << PIN_HEATER1) | (1ULL << PIN_HEATER2); io_conf_heater.pull_up_en = GPIO_PULLUP_DISABLE; io_conf_heater.pull_down_en = GPIO_PULLDOWN_DISABLE; gpio_config(&io_conf_heater); // 초기 상태 OFF 설정 gpio_set_level((gpio_num_t)PIN_HEATER1, HEATER_OFF); gpio_set_level((gpio_num_t)PIN_HEATER2, HEATER_OFF); } void setupGPIO2() { // ========================= // ISR 서비스 및 핸들러 등록 // ========================= // LEVEL2 플래그를 사용하여 타이밍 정밀도를 높입니다. esp_err_t err = gpio_install_isr_service(ESP_INTR_FLAG_IRAM); if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) { DPRINTLN("[GPIO] Error on gpio_install_isr_service"); } // 버튼 핸들러 gpio_isr_handler_add((gpio_num_t)PIN_SW_SET, buttonSetISR, NULL); gpio_isr_handler_add((gpio_num_t)PIN_SW_UP, buttonUpISR, NULL); gpio_isr_handler_add((gpio_num_t)PIN_SW_DOWN, buttonDownISR, NULL); // ZCD 핸들러 (이제 LEVEL2 우선순위로 동작) gpio_isr_handler_add((gpio_num_t)PIN_ZCD_AC, zcdACISR, NULL); gpio_isr_handler_add((gpio_num_t)PIN_ZCD_LOAD, zcdLoadISR, NULL); } void setupTimers() { //=============================================================================== // // TIMER 설정 구조체 준비 // static timer_config_t tcfg0t1 = { .alarm_en = TIMER_ALARM_DIS, .counter_en = TIMER_START, .intr_type = TIMER_INTR_LEVEL, .counter_dir = TIMER_COUNT_UP, .auto_reload = (timer_autoreload_t) TIMER_AUTORELOAD_DIS, .divider = 8000 // 10KHz (100us tick) }; esp_err_t err; #ifdef DEBUG bool bErr = false; #endif // ========================= // TG0 TIMER1 (millis replacement) // ========================= if ((err = timer_init(TIMER_GROUP_0, TIMER_1, &tcfg0t1)) == ESP_OK ) { timer_set_counter_value(TIMER_GROUP_0, TIMER_1, 0); // init counter value timer_start(TIMER_GROUP_0, TIMER_1); } #ifdef DEBUG else { DPRINTLN("[Timer] Failed to init timer TG0 Timer1"); bErr = true; } #endif static timer_config_t tcfg1 = { .alarm_en = TIMER_ALARM_EN, .counter_en = TIMER_START, .intr_type = TIMER_INTR_LEVEL, .counter_dir = TIMER_COUNT_UP, .auto_reload = (timer_autoreload_t) TIMER_AUTORELOAD_EN, .divider = 80 // 1MHz (1us tick) }; // ========================= // TG1 TIMER0 (heater1) // ========================= if ((err = timer_init(TIMER_GROUP_1, TIMER_0, &tcfg1)) == ESP_OK ) { timer_set_counter_value(TIMER_GROUP_1, TIMER_0, 0); timer_set_alarm_value(TIMER_GROUP_1, TIMER_0, (uint64_t) 0xFFFFFFF0); err = timer_isr_register(TIMER_GROUP_1, TIMER_0, timer0ISR, NULL, ESP_INTR_FLAG_IRAM, NULL ); DPRINTF("TimerG1T0 ISR Register Result: %d\n", err); timer_enable_intr(TIMER_GROUP_1, TIMER_0); timer_start(TIMER_GROUP_1, TIMER_0); } #ifdef DEBUG else { DPRINTLN("[Timer] Failed to init timer TG1 Timer0"); bErr = true; } #endif // ========================= // TG1 TIMER1 (heater2) // ========================= if ((err = timer_init(TIMER_GROUP_1, TIMER_1, &tcfg1)) == ESP_OK ) { timer_set_counter_value(TIMER_GROUP_1, TIMER_1, 0); timer_set_alarm_value(TIMER_GROUP_1, TIMER_1, (uint64_t) 0xFFFFFFF0); err = timer_isr_register(TIMER_GROUP_1, TIMER_1, timer1ISR, NULL, ESP_INTR_FLAG_IRAM, NULL ); DPRINTF("TimerG1T1 ISR Register Result: %d\n", err); timer_enable_intr(TIMER_GROUP_1, TIMER_1); timer_start(TIMER_GROUP_1, TIMER_1); } #ifdef DEBUG else { DPRINTLN("[Timer] Failed to init timer TG1 Timer1"); bErr = true; } if (bErr == false) { DPRINTLN("[Timer] All Timers create and registered successfully!"); } #endif } void setupConfig() { config.load(); history.loadPID(); config.m_nDeviceType = THIS_DEVICE_TYPE; } void setupWiFi() { // Set WiFiEvent WiFi.onEvent(WiFiEvent); strncpy(BLE_SSID, config.ssid, sizeof(BLE_SSID)); strncpy(BLE_PW, config.pw, sizeof(BLE_PW)); // Connect WiFi for OTA if (config.ssid[0] && config.pw[0]) { esp_wifi_set_max_tx_power(74); ui.message(0, (char *) "WiFi..."); DPRINTF("BOOT: Connecting to WiFi: SSID: '%s', PW: '%s'\n", config.ssid, config.pw); WiFi.mode(WIFI_STA); WiFi.begin(config.ssid, config.pw); unsigned long beginTime = millis(); while (WiFi.status() != WL_CONNECTED) { delay(250); DPRINT('.'); if (millis() - beginTime > 30000) break; } DPRINTLN(); if (WiFi.status() == WL_CONNECTED) { ledcWrite(PIN_LED_WIFI, PWM_FULL * 9 / 10); // LED_OFF ui.message(0, (char *) "WiFi...OK!"); DPRINTLN("WiFi - Connected at SETUP"); DPRINTF("WiFi - SSID(%s) PW(%s) IP(%s)\n", config.ssid, config.pw, WiFi.localIP().toString().c_str()); g_bWiFiHasBeenConnected = true; setupPostWiFi(true); } else { DPRINTLN("WiFi - ** NOT ** Connected at SETUP."); //WiFi.disconnect(false, true, 500); } } } void setupPostWiFi(bool bBoot = false) { if (WiFi.status() == WL_CONNECTED) { // Time if (bBoot) ui.message(1, (char *) "Time..."); timeManager.begin(); vTaskDelay((bBoot ? 500 : 250)/portTICK_PERIOD_MS); timeManager.checkNTPResponse(); if (bBoot) { if (timeManager.hasNTPUpdate()) { ui.message(1, (char *) "Time...OK!"); // OTA DPRINTLN("Setup - TimeManager.begin()"); DPRINTLN("\n===============================\n"); DPRINTLN(" Trying OTA"); ui.message(2, (char *) "Update..."); ledcWrite(PIN_LED_WIFI, PWM_FULL / 20); checkOTA(true); ledcWrite(PIN_LED_WIFI, PWM_FULL * 39 / 40); ui.message(2, (char *) "Update OK!"); DPRINTLN(" OTA Process completed!"); DPRINTLN("===============================\n"); } else { ui.message(2, (char *) "Update check SKIPPED!"); timeManager.sendNTPRequest(); } } // Host if (bBoot) ui.message(3, (char *) "Server..."); host.Setup(); DPRINTLN("Setup - host.Setup()"); if (bBoot) ui.message(3, (char *) "Server...OK!"); g_bWiFiSetupExecuted = true; } } void setupPWM() { // --- 1. High Speed Group: Motor and Fan (~26kHz) --- // Core 2.0.17 uses APB (80MHz). 80MHz / (3 * 1024) ≈ 26kHz. ledcAttachChannel(PIN_MOTOR, PWM_26KHZ_FREQ, PWM_RESOLUTION, PWM_MOTOR_CHANNEL); ledcAttachChannel(PIN_FAN, PWM_26KHZ_FREQ, PWM_RESOLUTION, PWM_FAN_CHANNEL); // --- 2. Medium Speed Group: LED & Heaters (1221Hz) --- // 80MHz / (64 * 1024) = 1220.7Hz. ledcAttachChannel(PIN_LIGHT, PWM_1KHZ_FREQ, PWM_RESOLUTION, PWM_LIGHT_CHANNEL); ledcAttachChannel(PIN_LED_HEATER1, PWM_1KHZ_FREQ, PWM_RESOLUTION, PWM_HEATER1_CHANNEL); ledcAttachChannel(PIN_LED_HEATER2, PWM_1KHZ_FREQ, PWM_RESOLUTION, PWM_HEATER2_CHANNEL); // --- 3. Low Speed Group: Mist & WiFi LED (1.19Hz) --- // Initialized at 1Hz to bypass the slow divider calculation/WDT panic. // Must be ESP32 coer version 3.0.2 in ordre to set 1Hz ledcAttachChannel(PIN_MIST, PWM_1HZ_FREQ, PWM_RESOLUTION, PWM_MIST_CHANNEL); ledcAttachChannel(PIN_LED_WIFI, PWM_1HZ_FREQ, PWM_RESOLUTION, PWM_WIFI_LED_CHANNEL); // --- Initial State Writes --- ledcWrite(PIN_MOTOR, PWM_OFF); ledcWrite(PIN_FAN, PWM_OFF); ledcWrite(PIN_LIGHT, PWM_OFF); ledcWrite(PIN_MIST, PWM_OFF); ledcWrite(PIN_LED_WIFI, PWM_FULL / 20); // 80% Initial Blink // Heaters (Inverted logic for MOSFET safety) ledcWrite(PIN_LED_HEATER1, PWM_FULL - PWM_OFF); ledcWrite(PIN_LED_HEATER2, PWM_FULL - PWM_OFF); } void setupStatus() { if (config.bStatusSaved) { status = config.statusSave; config.bStatusSaved = false; } // init sensor and counter status.nTemp1 = 0; status.nTemp2 = 0; status.nTemp3 = 0; status.nHumid1 = 0; status.nHumid2 = 0; status.zcdAC = 0; status.zcdLoad = 0; status.nFlags = 0x00; } void restoreStatus() { if (isWiFiConnected()) { if (timeManager.hasNTPUpdate()) { time_t now; time(&now); uint32_t gap = (uint32_t)now - config.statusSave.now; DPRINTF("Reboot within %.1f seconds after last reset... \n", gap / 1000.0f); if (gap < 60000 && config.bStatusSaved) { status = config.statusSave; status.nFlags |= (uint16_t)(config.statusSave.nFlags & 0xFF); status.nLightDuty = 0; config.bStatusSaved = false; DPRINTLN(" Status Restored!"); } else { DPRINTLN(" Status NOT Restored!"); } } } } void scanI2C() { Wire.begin(); DPRINTLN("I2C - Scanning..."); for (byte addr = 1; addr < 127; addr++) { Wire.beginTransmission(addr); if (Wire.endTransmission() == 0) { DPRINTF("I2C - Found device at address: 0x%02X\n", addr); } } DPRINTLN(" Scanning Done."); } void setupSensor() { // AHTx0 if (aht25.setup()) { delay(82); aht25.readSensor(millis()); DPRINTF("AHT25 initialized successfully at 0x38. Temp: %.2f, Humid: %.2f%%\n", aht25.getTemperature() / 100.0f, aht25.getHumidity() / 100.0f); } delay(10); if (aht10_0x39.setup(0x39)) { //begin(PIN_SCL, PIN_SDA, AHT10_ADDRESS_0X39)) { delay(82); if (aht10_0x39.readSensor(millis())) { //aht10_0x39.initBuffer(); } DPRINTF("AHTx0 initialized successfully at 0x39. Temp: %.2f, Humid: %.2f%%\n", aht10_0x39.getTemperature() / 100.0f , aht10_0x39.getHumidity() / 100.0f ); } delay(10); history.init(status.nTemp1, status.nHumid1); if (!aht25.sensor() && !aht10_0x39.sensor()) { DPRINTF("AHTx0 initialization failed. SCL:%d SDA:%d\n", PIN_SCL, PIN_SDA ); } // Temp3 - NTC ntc.setup(config.bNTCNegativePolarity); status.nTemp3 = ntc.getTemp(); }