파일 라이팅 페일 조치

This commit is contained in:
2026-01-19 19:36:57 +00:00
parent fd1696ffb2
commit 6485a349d2

View File

@@ -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();
}
}