From e251acd6e35cdd53e8095fa69cf59962921769a2 Mon Sep 17 00:00:00 2001 From: byun Date: Fri, 6 Mar 2026 17:04:05 +0000 Subject: [PATCH] =?UTF-8?q?sd=EC=B9=B4=EB=93=9C=20=ED=94=8C=EB=9F=AC?= =?UTF-8?q?=EC=89=AC=20=EA=B4=80=EB=A0=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ESP32_CAN_Logger-a.ino | 90 +++++++++++++++++++++--------------------- 1 file changed, 46 insertions(+), 44 deletions(-) diff --git a/ESP32_CAN_Logger-a.ino b/ESP32_CAN_Logger-a.ino index 3517684..fefb2d6 100644 --- a/ESP32_CAN_Logger-a.ino +++ b/ESP32_CAN_Logger-a.ino @@ -862,20 +862,17 @@ void checkAutoTriggers(struct can_frame &frame) { } } if (anyMatch && result) { + loggingEnabled = false; // 먼저 중단 → sdFlushTask 새 flush 안 함 + int waitCount = 0; + while (flushInProgress && waitCount++ < 500) vTaskDelay(10); if (xSemaphoreTake(sdMutex, pdMS_TO_TICKS(2000)) == pdTRUE) { if (writeBufferIndex > 0 && logFile) { logFile.write(currentWriteBuffer, writeBufferIndex); - logFile.flush(); writeBufferIndex = 0; } - int waitCount = 0; - while (flushInProgress && waitCount++ < 500) vTaskDelay(10); - if ((canLogFormatCSV || canLogFormatPCAP) && logFile) logFile.flush(); - if (logFile) { logFile.close(); } - loggingEnabled = false; + if (logFile) { logFile.flush(); logFile.close(); } autoTriggerActive = false; currentFilename[0]= '\0'; - writeBufferIndex = 0; flushBufferSize = 0; Serial.println("🎯 Auto Trigger 중지"); xSemaphoreGive(sdMutex); @@ -1232,19 +1229,26 @@ void serial2RxTask(void *parameter) { // ======================================== void sdFlushTask(void *parameter) { Serial.println("✓ sdFlushTask 시작 (더블 버퍼링 + RingBuffer)"); + uint32_t flushCount = 0; // flush 횟수 카운터 (flush() 호출 빈도 조절용) while (1) { uint32_t flushSize; if (xTaskNotifyWait(0, 0xFFFFFFFF, &flushSize, portMAX_DELAY) == pdTRUE) { if (flushSize > 0 && flushSize <= FILE_BUFFER_SIZE) { flushInProgress = true; - uint32_t t0 = millis(); if (xSemaphoreTake(sdMutex, pdMS_TO_TICKS(5000)) == pdTRUE) { if (logFile && sdCardReady && loggingEnabled) { - size_t written = logFile.write(currentFlushBuffer, flushSize); - logFile.flush(); + uint32_t t0 = millis(); + logFile.write(currentFlushBuffer, flushSize); + flushCount++; + // ★ logFile.flush()는 FAT 디렉토리 엔트리 2회 섹터 쓰기 유발 + // 매번 호출하면 300ms+ 지연 → 4번(~256KB)마다 1회로 줄임 + // 전원 차단 시 최대 256KB 손실 가능하나, 로깅 완료 후 stopLogging에서 반드시 flush + if (flushCount % 4 == 0) { + logFile.flush(); + } uint32_t elapsed = millis() - t0; - if (elapsed > 100) - Serial.printf("⚠️ SD Flush 지연: %d ms (%d bytes)\n", elapsed, written); + if (elapsed > 150) + Serial.printf("⚠️ SD Flush 지연: %d ms (%d bytes)\n", elapsed, (int)flushSize); } xSemaphoreGive(sdMutex); } else { @@ -1320,13 +1324,13 @@ void sdWriteTask(void *parameter) { } else if (canLogFormatPCAP) { // ── PCAP: 32bytes/record → 더블 버퍼 (BIN과 동일 구조) ── if (writeBufferIndex + sizeof(PcapRecord) > FILE_BUFFER_SIZE) { - int waitCount = 0; - while (flushInProgress && waitCount++ < 100) vTaskDelay(1); - uint8_t* tmp = currentWriteBuffer; + // ★ flush 완전 완료까지 대기 (타임아웃 없음 - 데이터 손상 방지) + while (flushInProgress) vTaskDelay(1); + uint8_t* tmp = currentWriteBuffer; currentWriteBuffer = currentFlushBuffer; currentFlushBuffer = tmp; - flushBufferSize = writeBufferIndex; - writeBufferIndex = 0; + flushBufferSize = writeBufferIndex; + writeBufferIndex = 0; if (sdFlushTaskHandle) xTaskNotify(sdFlushTaskHandle, flushBufferSize, eSetValueWithOverwrite); } @@ -1362,18 +1366,18 @@ void sdWriteTask(void *parameter) { // writeBuffer는 sdWriteTask만 쓰므로 mutex 불필요 if (writeBufferIndex + sizeof(CANMessage) > FILE_BUFFER_SIZE) { - // 버퍼 스왑 - flushTask 완료 대기 (최대 100ms) - int waitCount = 0; - while (flushInProgress && waitCount++ < 100) vTaskDelay(1); + // ★ flush 완전 완료까지 대기 (타임아웃 없음) + // 이유: 100ms 타임아웃 후 강제 스왑하면 sdFlushTask가 읽는 중인 + // 버퍼에 sdWriteTask가 덮어써서 SD 파일 데이터 손상 발생 + // 안전성: RingBuffer 8192슬롯(~1초@8kfps)이 대기 중 메시지 흡수 + while (flushInProgress) vTaskDelay(1); - // ★ 버퍼 포인터 스왑 (즉시, 블로킹 없음) - uint8_t* tmp = currentWriteBuffer; + uint8_t* tmp = currentWriteBuffer; currentWriteBuffer = currentFlushBuffer; currentFlushBuffer = tmp; - flushBufferSize = writeBufferIndex; - writeBufferIndex = 0; + flushBufferSize = writeBufferIndex; + writeBufferIndex = 0; - // sdFlushTask에 비동기 알림 if (sdFlushTaskHandle) xTaskNotify(sdFlushTaskHandle, flushBufferSize, eSetValueWithOverwrite); } @@ -1672,20 +1676,23 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t *payload, size_t length) } else if (strcmp(cmd, "stopLogging") == 0) { if (loggingEnabled) { + // ① 로깅 중단 신호 → sdWriteTask/sdFlushTask가 새 데이터 쓰기 중단 + loggingEnabled = false; + // ② sdFlushTask가 진행 중인 flush 완료 대기 (뮤텍스 잡기 전에!) + // sdMutex를 먼저 잡으면 sdFlushTask가 뮤텍스 못 얻어 데드락 + int wc = 0; + while (flushInProgress && wc++ < 500) vTaskDelay(10); + // ③ 이제 sdMutex 안전하게 획득 if (xSemaphoreTake(sdMutex, pdMS_TO_TICKS(2000)) == pdTRUE) { + // ④ writeBuffer에 남은 데이터 마지막으로 쓰기 if (writeBufferIndex > 0 && logFile) { logFile.write(currentWriteBuffer, writeBufferIndex); - logFile.flush(); writeBufferIndex = 0; } - int wc = 0; - while (flushInProgress && wc++ < 500) vTaskDelay(10); - // CSV/PCAP 모두 최종 flush - if ((canLogFormatCSV || canLogFormatPCAP) && logFile) logFile.flush(); - if (logFile) { logFile.close(); } - loggingEnabled = false; + // ⑤ 최종 flush (4번에 1번 규칙 무관하게 반드시 호출) + if (logFile) { logFile.flush(); logFile.close(); } currentFilename[0] = '\0'; - writeBufferIndex = 0; flushBufferSize = 0; + flushBufferSize = 0; xSemaphoreGive(sdMutex); } } @@ -2442,13 +2449,15 @@ void setup() { } String filename = "/" + server.arg("file"); - // 로깅 중이면 잠시 중단 (sdWriteTask와 SD 버스 충돌 방지) + // 로깅 중이면 완전 중단 후 SD 버스 정리 bool wasLogging = loggingEnabled; if (wasLogging) { loggingEnabled = false; - // sdWriteTask가 현재 flush 중이면 완료 대기 (최대 500ms) + // ① sdWriteTask가 버퍼에 쓰던 것 완료 대기 + // ② sdFlushTask가 SD에 flush 완료 대기 (여기가 핵심 - 안 기다리면 SD 버스 충돌) int waitMs = 0; - while (flushInProgress && waitMs < 500) { delay(10); waitMs += 10; } + while (flushInProgress && waitMs < 2000) { delay(10); waitMs += 10; } + delay(50); // SD 버스 안정화 } // sdMutex 획득 (sdFlushTask와 동시 접근 차단) @@ -2486,7 +2495,6 @@ void setup() { if (buf) { size_t total = 0; WiFiClient client = server.client(); - uint32_t lastWdtFeed = millis(); while (file.available() && total < fileSize && client.connected()) { size_t r = file.read(buf, CHUNK); @@ -2505,13 +2513,7 @@ void setup() { } } total += r; - - // ★ Watchdog 피드 (매 500ms마다) - if (millis() - lastWdtFeed > 500) { - esp_task_wdt_reset(); - lastWdtFeed = millis(); - } - yield(); + yield(); // WiFi 스택에 처리 시간 양보 } free(buf); Serial.printf("Download OK: %s (%u bytes)\n", filename.c_str(), total);