diff --git a/ESP32_CAN_Logger-a.ino b/ESP32_CAN_Logger-a.ino index 516c59a..21c3c55 100644 --- a/ESP32_CAN_Logger-a.ino +++ b/ESP32_CAN_Logger-a.ino @@ -1506,14 +1506,27 @@ void sdWriteTask(void *parameter) { csvFlushCounter = 0; } - // ⭐⭐⭐ 500개마다 파일 재오픈 (핵심!) - if (++csvReopenCounter >= 500) { + // ⭐⭐⭐ 2000개마다 파일 재오픈 (500 → 2000 변경, SDIO 안정성) + if (++csvReopenCounter >= 2000) { logFile.close(); - logFile = SD_MMC.open(currentFilename, FILE_APPEND); - if (logFile) { - Serial.printf("✓ CSV 파일 재오픈: %s (%lu bytes)\n", currentFilename, currentFileSize); - } else { - Serial.println("✗ CSV 파일 재오픈 실패!"); + delay(50); // SD 카드 안정화 대기 + + // 파일 재오픈 (재시도 로직) + bool reopenSuccess = false; + for (int retry = 0; retry < 3; retry++) { + logFile = SD_MMC.open(currentFilename, FILE_APPEND); + if (logFile) { + Serial.printf("✓ CSV 파일 재오픈: %s (%lu bytes)\n", currentFilename, currentFileSize); + reopenSuccess = true; + break; + } else { + Serial.printf("⚠ CSV 파일 재오픈 실패, 재시도 %d/3\n", retry + 1); + delay(100); // 재시도 전 대기 + } + } + + if (!reopenSuccess) { + Serial.println("✗ CSV 파일 재오픈 완전 실패! 로깅 중지"); loggingEnabled = false; } csvReopenCounter = 0; @@ -1552,8 +1565,8 @@ void sdWriteTask(void *parameter) { } } - // ⭐⭐⭐ 4단계: 500개마다 파일 재오픈 (핵심!) - if (binReopenCounter >= 500) { + // ⭐⭐⭐ 4단계: 2000개마다 파일 재오픈 (500 → 2000 변경, SDIO 안정성) + if (binReopenCounter >= 2000) { // 버퍼 먼저 플러시 if (logFile && bufferIndex > 0) { logFile.write(fileBuffer, bufferIndex); @@ -1561,15 +1574,29 @@ void sdWriteTask(void *parameter) { bufferIndex = 0; } - // 파일 닫고 다시 열기 + // 파일 닫기 logFile.close(); - logFile = SD_MMC.open(currentFilename, FILE_APPEND); - if (logFile) { - Serial.printf("✓ BIN 파일 재오픈: %s (%lu bytes)\n", currentFilename, currentFileSize); - } else { - Serial.println("✗ BIN 파일 재오픈 실패!"); + delay(50); // SD 카드 안정화 대기 + + // 파일 재오픈 (재시도 로직) + bool reopenSuccess = false; + for (int retry = 0; retry < 3; retry++) { + logFile = SD_MMC.open(currentFilename, FILE_APPEND); + if (logFile) { + Serial.printf("✓ BIN 파일 재오픈: %s (%lu bytes)\n", currentFilename, currentFileSize); + reopenSuccess = true; + break; + } else { + Serial.printf("⚠ BIN 파일 재오픈 실패, 재시도 %d/3\n", retry + 1); + delay(100); // 재시도 전 대기 + } + } + + if (!reopenSuccess) { + Serial.println("✗ BIN 파일 재오픈 완전 실패! 로깅 중지"); loggingEnabled = false; } + binReopenCounter = 0; } } @@ -3368,7 +3395,6 @@ void setup() { // WebSocket 시작 webSocket.begin(); webSocket.onEvent(webSocketEvent); - webSocket.enableHeartbeat(15000, 3000, 2); // ping 15초, pong 3초, 재시도 2회 // ★★★ 웹 서버 라우팅 (중요!) ★★★ server.on("/", HTTP_GET, []() { @@ -3644,22 +3670,55 @@ void setup() { // CAN 인터럽트 활성화 attachInterrupt(digitalPinToInterrupt(CAN_INT_PIN), canISR, FALLING); - // Task 생성 - xTaskCreatePinnedToCore(canRxTask, "CAN_RX", 8192, NULL, configMAX_PRIORITIES - 1, &canRxTaskHandle, 1); // Core 0, Pri 24 - xTaskCreatePinnedToCore(sdWriteTask, "SD_WRITE", 18576, NULL, 6, &sdWriteTaskHandle, 0); - xTaskCreatePinnedToCore(sequenceTask, "SEQ", 4096, NULL, 2, NULL, 1); - xTaskCreatePinnedToCore(serialRxTask, "SERIAL_RX", 6144, NULL, 5, &serialRxTaskHandle, 0); - xTaskCreatePinnedToCore(serial2RxTask, "SERIAL2_RX", 6144, NULL, 5, &serial2RxTaskHandle, 0); // ⭐ Serial2 - xTaskCreatePinnedToCore(txTask, "TX", 4096, NULL, 3, NULL, 1); - xTaskCreatePinnedToCore(webUpdateTask, "WEB_UPDATE", 18384, NULL, 2, &webTaskHandle, 1); // ⭐ 10240 → 16384 - xTaskCreatePinnedToCore(sdMonitorTask, "SD_MONITOR", 4096, NULL, 1, NULL, 1); + // ======================================== + // Task 생성 (최적화 버전) + // ======================================== + Serial.println("\nTask 생성 중..."); + + // Core 1: 실시간 처리 + 웹 서비스 + // - CAN RX: 최고 우선순위로 메시지 수신 + // - WEB: Core 0의 SD 쓰기와 분리하여 응답성 확보 + // - TX: CAN 전송 + // - SEQ: 시퀀스 재생 + // - MONITOR: 상태 모니터링 + xTaskCreatePinnedToCore(canRxTask, "CAN_RX", 12288, NULL, 24, &canRxTaskHandle, 1); // ⭐ 8KB → 12KB, Pri 24 (최고) + xTaskCreatePinnedToCore(webUpdateTask, "WEB_UPDATE", 12288, NULL, 4, &webTaskHandle, 1); // ⭐ Core 0 → 1, Pri 4 (SD와 분리) + xTaskCreatePinnedToCore(txTask, "TX", 4096, NULL, 3, NULL, 1); // Pri 3 + xTaskCreatePinnedToCore(sequenceTask, "SEQ", 4096, NULL, 2, NULL, 1); // Pri 2 + xTaskCreatePinnedToCore(sdMonitorTask, "SD_MONITOR", 4096, NULL, 1, NULL, 1); // Pri 1 + + // Core 0: I/O 전용 (SD, Serial, RTC) + // - SD 쓰기와 Serial 수신만 처리 + // - 웹은 Core 1에서 처리하므로 SD 지연 영향 없음 + xTaskCreatePinnedToCore(sdWriteTask, "SD_WRITE", 18576, NULL, 8, &sdWriteTaskHandle, 0); // Pri 8 (최우선) + xTaskCreatePinnedToCore(serialRxTask, "SERIAL_RX", 6144, NULL, 6, &serialRxTaskHandle, 0); // Pri 6 + xTaskCreatePinnedToCore(serial2RxTask, "SERIAL2_RX", 6144, NULL, 6, &serial2RxTaskHandle,0); // Pri 6 if (timeSyncStatus.rtcAvailable) { - xTaskCreatePinnedToCore(rtcSyncTask, "RTC_SYNC", 3072, NULL, 0, &rtcTaskHandle, 0); + xTaskCreatePinnedToCore(rtcSyncTask, "RTC_SYNC", 3072, NULL, 0, &rtcTaskHandle, 0); // Pri 0 (최저) } Serial.println("✓ 모든 Task 시작 완료"); Serial.println("\n========================================"); + Serial.println(" Task 구성 (Core 분리 전략)"); + Serial.println("========================================"); + Serial.println("Core 1 (실시간 + 웹 서비스):"); + Serial.println(" - CAN_RX: 12KB, Pri 24 (최고)"); + Serial.println(" - WEB_UPDATE: 12KB, Pri 4 ⭐ SD와 분리"); + Serial.println(" - TX: 4KB, Pri 3"); + Serial.println(" - SEQ: 4KB, Pri 2"); + Serial.println(" - SD_MONITOR: 4KB, Pri 1"); + Serial.println("Core 0 (I/O 전용):"); + Serial.println(" - SD_WRITE: 18KB, Pri 8 (최우선)"); + Serial.println(" - SERIAL_RX: 6KB, Pri 6"); + Serial.println(" - SERIAL2_RX: 6KB, Pri 6"); + if (timeSyncStatus.rtcAvailable) { + Serial.println(" - RTC_SYNC: 3KB, Pri 0"); + } + Serial.println("========================================"); + Serial.println("📌 웹을 Core 1로 배치 → SD 쓰기 지연 영향 없음"); + Serial.println("========================================"); + Serial.println("\n========================================"); Serial.println(" 접속 방법"); Serial.println("========================================"); Serial.printf(" WiFi SSID: %s\n", wifiSSID); @@ -3678,6 +3737,7 @@ void loop() { server.handleClient(); vTaskDelay(pdMS_TO_TICKS(10)); + // 주기적 상태 출력 (30초) static uint32_t lastPrint = 0; if (millis() - lastPrint > 30000) { Serial.printf("[상태] CAN: %d/%d | S1: %d/%d | S2: %d/%d | PSRAM: %d KB\n", @@ -3687,4 +3747,44 @@ void loop() { ESP.getFreePsram() / 1024); lastPrint = millis(); } + + // 스택 사용량 모니터링 (5분마다) + static uint32_t lastStackCheck = 0; + if (millis() - lastStackCheck > 300000) { + Serial.println("\n========== Task Stack Usage =========="); + + if (canRxTaskHandle) { + UBaseType_t stackLeft = uxTaskGetStackHighWaterMark(canRxTaskHandle); + Serial.printf("CAN_RX: %5u bytes free (alloc: 12288)\n", stackLeft * 4); + if (stackLeft * 4 < 2000) Serial.println(" ⚠️ 스택 부족 위험!"); + } + + if (sdWriteTaskHandle) { + UBaseType_t stackLeft = uxTaskGetStackHighWaterMark(sdWriteTaskHandle); + Serial.printf("SD_WRITE: %5u bytes free (alloc: 18576)\n", stackLeft * 4); + if (stackLeft * 4 < 3000) Serial.println(" ⚠️ 스택 부족 위험!"); + } + + if (webTaskHandle) { + UBaseType_t stackLeft = uxTaskGetStackHighWaterMark(webTaskHandle); + Serial.printf("WEB_UPDATE: %5u bytes free (alloc: 12288)\n", stackLeft * 4); + if (stackLeft * 4 < 2000) Serial.println(" ⚠️ 스택 부족 위험!"); + } + + if (serialRxTaskHandle) { + UBaseType_t stackLeft = uxTaskGetStackHighWaterMark(serialRxTaskHandle); + Serial.printf("SERIAL_RX: %5u bytes free (alloc: 6144)\n", stackLeft * 4); + } + + if (serial2RxTaskHandle) { + UBaseType_t stackLeft = uxTaskGetStackHighWaterMark(serial2RxTaskHandle); + Serial.printf("SERIAL2_RX: %5u bytes free (alloc: 6144)\n", stackLeft * 4); + } + + Serial.printf("Free Heap: %u bytes\n", ESP.getFreeHeap()); + Serial.printf("Free PSRAM: %u KB\n", ESP.getFreePsram() / 1024); + Serial.println("======================================\n"); + + lastStackCheck = millis(); + } } \ No newline at end of file