flush삭제

This commit is contained in:
2026-01-27 19:17:06 +00:00
parent 643b84fe84
commit 247a695c6b

View File

@@ -305,7 +305,17 @@ File serial2LogFile; // ⭐ Serial2 추가
char currentFilename[MAX_FILENAME_LEN];
char currentSerialFilename[MAX_FILENAME_LEN];
char currentSerial2Filename[MAX_FILENAME_LEN]; // ⭐ Serial2 추가
uint32_t bufferIndex = 0; // ⭐ uint16_t → uint32_t (오버플로우 방지)
// ⭐⭐⭐ 더블 버퍼링 변수
uint8_t* fileBuffer1 = NULL; // 쓰기 버퍼
uint8_t* fileBuffer2 = NULL; // flush 버퍼
uint8_t* currentWriteBuffer = NULL; // 현재 쓰기용
uint8_t* currentFlushBuffer = NULL; // 현재 flush용
uint32_t writeBufferIndex = 0; // 쓰기 버퍼 인덱스
uint32_t flushBufferSize = 0; // flush할 크기
volatile bool flushInProgress = false; // flush 진행중 플래그
TaskHandle_t sdFlushTaskHandle = NULL; // flush 전용 태스크
uint32_t serialCsvIndex = 0; // ⭐ uint16_t → uint32_t
uint32_t serial2CsvIndex = 0; // ⭐ uint16_t → uint32_t // ⭐ Serial2 추가
volatile uint32_t currentFileSize = 0;
@@ -491,13 +501,27 @@ bool initPSRAM() {
Serial.printf("✓ PSRAM 총 용량: %d MB\n", ESP.getPsramSize() / 1024 / 1024);
Serial.printf("✓ PSRAM 여유: %d KB\n\n", ESP.getFreePsram() / 1024);
// 버퍼 할당
fileBuffer = (uint8_t*)ps_malloc(FILE_BUFFER_SIZE);
if (!fileBuffer) {
Serial.println("✗ fileBuffer 할당 실패");
// ⭐⭐⭐ 더블 버퍼 할당
fileBuffer1 = (uint8_t*)ps_malloc(FILE_BUFFER_SIZE);
if (!fileBuffer1) {
Serial.println("✗ fileBuffer1 할당 실패");
return false;
}
Serial.printf("✓ fileBuffer: %d KB\n", FILE_BUFFER_SIZE / 1024);
Serial.printf("✓ fileBuffer1: %d KB\n", FILE_BUFFER_SIZE / 1024);
fileBuffer2 = (uint8_t*)ps_malloc(FILE_BUFFER_SIZE);
if (!fileBuffer2) {
Serial.println("✗ fileBuffer2 할당 실패");
return false;
}
Serial.printf("✓ fileBuffer2: %d KB\n", FILE_BUFFER_SIZE / 1024);
// 초기 버퍼 포인터 설정
currentWriteBuffer = fileBuffer1;
currentFlushBuffer = fileBuffer2;
writeBufferIndex = 0;
flushBufferSize = 0;
Serial.println("✓ 더블 버퍼링 초기화 완료");
serialCsvBuffer = (char*)ps_malloc(SERIAL_CSV_BUFFER_SIZE);
if (!serialCsvBuffer) {
@@ -844,7 +868,12 @@ void checkAutoTriggers(struct can_frame &frame) {
if (logFile) {
loggingEnabled = true;
autoTriggerActive = true;
bufferIndex = 0;
// ⭐⭐⭐ 더블 버퍼 초기화
writeBufferIndex = 0;
flushBufferSize = 0;
flushInProgress = false;
currentFileSize = logFile.size();
Serial.println("🎯 Auto Trigger: 로깅 시작!");
@@ -889,12 +918,19 @@ void checkAutoTriggers(struct can_frame &frame) {
if (anyMatch && result) {
// 🎯 파일 닫기 로직 추가
if (xSemaphoreTake(sdMutex, pdMS_TO_TICKS(2000)) == pdTRUE) {
// BIN 형식: 버퍼 남은 데이터 강제 플러시
if (bufferIndex > 0 && logFile) {
size_t written = logFile.write(fileBuffer, bufferIndex);
// ⭐⭐⭐ 더블 버퍼: 현재 쓰기 버퍼 남은 데이터 flush
if (writeBufferIndex > 0 && logFile) {
size_t written = logFile.write(currentWriteBuffer, writeBufferIndex);
logFile.flush();
Serial.printf("✓ Auto Trigger 최종 플러시: %d bytes\n", written);
bufferIndex = 0;
Serial.printf("✓ Auto Trigger 최종 쓰기 버퍼 플러시: %d bytes\n", written);
writeBufferIndex = 0;
}
// ⭐ flush 태스크가 작업 중이면 완료 대기
int waitCount = 0;
while (flushInProgress && waitCount < 500) {
vTaskDelay(10);
waitCount++;
}
// CSV 형식: 최종 플러시
@@ -911,7 +947,8 @@ void checkAutoTriggers(struct can_frame &frame) {
loggingEnabled = false;
autoTriggerActive = false;
currentFilename[0] = '\0';
bufferIndex = 0;
writeBufferIndex = 0;
flushBufferSize = 0;
Serial.println("🎯 Auto Trigger: 로깅 중지!");
Serial.printf(" 조건: ID 0x%03X 만족\n", frame.can_id);
@@ -1467,6 +1504,55 @@ void canRxTask(void *parameter) {
}
}
// ========================================
// SD Write Task
// ========================================
// ========================================
// ⭐⭐⭐ SD Flush 전용 태스크 (더블 버퍼링)
// ========================================
void sdFlushTask(void *parameter) {
Serial.println("✓ sdFlushTask 시작 (더블 버퍼링 모드)");
while (1) {
uint32_t notifyValue;
// flush 알림 대기 (무한 대기)
if (xTaskNotifyWait(0, 0xFFFFFFFF, &notifyValue, portMAX_DELAY) == pdTRUE) {
uint32_t flushSize = notifyValue;
if (flushSize > 0 && flushSize <= FILE_BUFFER_SIZE) {
flushInProgress = true;
uint32_t startTime = millis();
// ⭐ SD 뮤텍스 획득 (최대 5초 대기)
if (xSemaphoreTake(sdMutex, pdMS_TO_TICKS(5000)) == pdTRUE) {
if (logFile && sdCardReady && loggingEnabled) {
// ⭐ flush 버퍼를 SD에 쓰기
size_t written = logFile.write(currentFlushBuffer, flushSize);
// ⭐ 여기서 flush 호출 (200ms 블로킹되어도 CAN 수신은 계속!)
logFile.flush();
uint32_t elapsed = millis() - startTime;
Serial.printf("✓ SD Flush 완료: %d bytes, %d ms\n", written, elapsed);
// 통계
if (elapsed > 100) {
Serial.printf("⚠️ Flush 지연: %d ms (하지만 CAN은 정상!)\n", elapsed);
}
}
xSemaphoreGive(sdMutex);
} else {
Serial.println("✗ SD Flush: 뮤텍스 타임아웃");
}
flushInProgress = false;
}
}
}
}
// ========================================
// SD Write Task
// ========================================
@@ -1528,7 +1614,7 @@ void sdWriteTask(void *parameter) {
currentFileSize += lineLen;
static int csvFlushCounter = 0;
if (++csvFlushCounter >= 500) { // ⭐ 200 → 500 (최대 성능)
if (++csvFlushCounter >= 2000) { // ⭐ 500 → 2000 (SD 블로킹 최소화)
logFile.flush();
csvFlushCounter = 0;
}
@@ -1539,41 +1625,40 @@ void sdWriteTask(void *parameter) {
}
} else {
// BIN 형식
static uint32_t binMsgCounter = 0;
static uint32_t binReopenCounter = 0;
// ⭐⭐⭐ BIN 형식 - 더블 버퍼링
// ⭐⭐⭐ 1단계: 버퍼 가득 찼으면 먼저 플러시
if (bufferIndex + sizeof(CANMessage) > FILE_BUFFER_SIZE) {
if (logFile) {
size_t written = logFile.write(fileBuffer, bufferIndex);
logFile.flush();
Serial.printf("✓ BIN 버퍼 플러시 (FULL): %d bytes\n", written);
bufferIndex = 0;
}
// ⭐ 1단계: 버퍼 가득 찼으면 스왑
if (writeBufferIndex + sizeof(CANMessage) > FILE_BUFFER_SIZE) {
// flush가 진행중이면 잠시 대기 (매우 짧음, 버퍼 스왑만 기다림)
int waitCount = 0;
while (flushInProgress && waitCount < 100) {
vTaskDelay(1);
waitCount++;
}
// ⭐⭐⭐ 2단계: 이제 공간 확보됨, 데이터 추가
memcpy(&fileBuffer[bufferIndex], &canMsg, sizeof(CANMessage));
bufferIndex += sizeof(CANMessage);
if (waitCount >= 100) {
Serial.println("⚠️ Flush 대기 타임아웃 (버퍼 손실 방지)");
}
// ⭐ 버퍼 스왑 (즉시, 블로킹 없음!)
uint8_t* temp = currentWriteBuffer;
currentWriteBuffer = currentFlushBuffer;
currentFlushBuffer = temp;
flushBufferSize = writeBufferIndex;
writeBufferIndex = 0;
// ⭐ flush 태스크에 알림 (비동기)
if (sdFlushTaskHandle != NULL) {
xTaskNotify(sdFlushTaskHandle, flushBufferSize, eSetValueWithOverwrite);
}
Serial.printf("✓ 버퍼 스왑: %d bytes → flush 태스크\n", flushBufferSize);
}
// ⭐ 2단계: 현재 쓰기 버퍼에 데이터 추가
memcpy(&currentWriteBuffer[writeBufferIndex], &canMsg, sizeof(CANMessage));
writeBufferIndex += sizeof(CANMessage);
currentFileSize += sizeof(CANMessage);
binMsgCounter++;
binReopenCounter++;
// ⭐⭐⭐ 3단계: 1000개 메시지마다 주기적 플러시 (500→1000, 최대 성능)
if (binMsgCounter % 1000 == 0) {
if (logFile && bufferIndex > 0) {
size_t written = logFile.write(fileBuffer, bufferIndex);
logFile.flush();
Serial.printf("✓ BIN 주기 플러시: %d bytes (메시지: %d)\n", written, binMsgCounter);
bufferIndex = 0;
}
}
// ⭐⭐⭐ 4단계: 파일 재오픈 제거 (고부하 환경 최적화)
// 재오픈 시 SD 카드 블록으로 인한 오버플로우 방지
// 대신 주기적 flush로 데이터 안정성 확보
// if (binReopenCounter >= 2000) { ... } // 제거됨
}
xSemaphoreGive(sdMutex);
@@ -1986,9 +2071,14 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t *payload, size_t length)
if (logFile) {
loggingEnabled = true;
bufferIndex = 0;
// ⭐⭐⭐ 더블 버퍼 초기화
writeBufferIndex = 0;
flushBufferSize = 0;
flushInProgress = false;
currentFileSize = logFile.size();
Serial.printf("✅ 로깅 시작!\n");
Serial.printf("✅ 로깅 시작! (더블 버퍼링 모드)\n");
Serial.printf(" 파일: %s\n", currentFilename);
Serial.printf(" 형식: %s\n", canLogFormatCSV ? "CSV" : "BIN");
Serial.printf(" 초기 크기: %lu bytes\n", currentFileSize);
@@ -2010,12 +2100,22 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t *payload, size_t length)
Serial.println("🛑 로깅 중지 요청...");
if (xSemaphoreTake(sdMutex, pdMS_TO_TICKS(2000)) == pdTRUE) {
// ⭐⭐⭐ BIN 형식: 버퍼 남은 데이터 강제 플러시
if (bufferIndex > 0 && logFile) {
size_t written = logFile.write(fileBuffer, bufferIndex);
// ⭐⭐⭐ 더블 버퍼: 현재 쓰기 버퍼 남은 데이터 flush
if (writeBufferIndex > 0 && logFile) {
size_t written = logFile.write(currentWriteBuffer, writeBufferIndex);
logFile.flush();
Serial.printf("✓ 최종 플러시: %d bytes\n", written);
bufferIndex = 0;
Serial.printf("✓ 최종 쓰기 버퍼 플러시: %d bytes\n", written);
writeBufferIndex = 0;
}
// ⭐ flush 태스크가 작업 중이면 완료 대기
int waitCount = 0;
while (flushInProgress && waitCount < 500) {
vTaskDelay(10);
waitCount++;
}
if (waitCount > 0) {
Serial.printf("✓ Flush 태스크 완료 대기: %d ms\n", waitCount * 10);
}
// ⭐⭐⭐ CSV 형식: 최종 플러시
@@ -2032,7 +2132,8 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t *payload, size_t length)
loggingEnabled = false;
currentFilename[0] = '\0';
bufferIndex = 0;
writeBufferIndex = 0;
flushBufferSize = 0;
xSemaphoreGive(sdMutex);
} else {
Serial.println("✗ sdMutex 획득 실패!");
@@ -3700,8 +3801,8 @@ void setup() {
// - TX: CAN 전송
// - SEQ: 시퀀스 재생
// - MONITOR: 상태 모니터링
xTaskCreatePinnedToCore(canRxTask, "CAN_RX", 12288, NULL, 20, &canRxTaskHandle, 1); // ⭐ 8KB → 12KB, Pri 24 (최고)
xTaskCreatePinnedToCore(webUpdateTask, "WEB_UPDATE", 12288, NULL, 4, &webTaskHandle, 1); // ⭐ Core 0 → 1, Pri 4 (SD와 분리)
xTaskCreatePinnedToCore(canRxTask, "CAN_RX", 12288, NULL, 24, &canRxTaskHandle, 1); // ⭐ 8KB → 12KB, Pri 24 (최고)
xTaskCreatePinnedToCore(webUpdateTask, "WEB_UPDATE", 12288, NULL, 8, &webTaskHandle, 0); // ⭐ 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
@@ -3709,7 +3810,8 @@ void setup() {
// Core 0: I/O 전용 (SD, Serial, RTC)
// - SD 쓰기와 Serial 수신만 처리
// - 웹은 Core 1에서 처리하므로 SD 지연 영향 없음
xTaskCreatePinnedToCore(sdWriteTask, "SD_WRITE", 18576, NULL, 8, &sdWriteTaskHandle, 0); // Pri 8 (최우선)
xTaskCreatePinnedToCore(sdFlushTask, "SD_FLUSH", 4096, NULL, 9, &sdFlushTaskHandle, 0); // Pri 9 (최우선, flush 전용)
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