can logging 멈춤 해결, 파일 정상 로깅

This commit is contained in:
2025-12-10 21:48:18 +00:00
parent f4a41a0414
commit 2d7368679d
2 changed files with 125 additions and 39 deletions

View File

@@ -250,9 +250,9 @@ File serial2LogFile; // ⭐ Serial2 추가
char currentFilename[MAX_FILENAME_LEN];
char currentSerialFilename[MAX_FILENAME_LEN];
char currentSerial2Filename[MAX_FILENAME_LEN]; // ⭐ Serial2 추가
uint16_t bufferIndex = 0;
uint16_t serialCsvIndex = 0;
uint16_t serial2CsvIndex = 0; // ⭐ Serial2 추가
uint32_t bufferIndex = 0; // ⭐ uint16_t → uint32_t (오버플로우 방지)
uint32_t serialCsvIndex = 0; // ⭐ uint16_t → uint32_t
uint32_t serial2CsvIndex = 0; // ⭐ uint16_t → uint32_t // ⭐ Serial2 추가
volatile uint32_t currentFileSize = 0;
volatile uint32_t currentSerialFileSize = 0;
volatile uint32_t currentSerial2FileSize = 0; // ⭐ Serial2 추가
@@ -837,6 +837,28 @@ void canRxTask(void *parameter) {
struct can_frame frame;
CANMessage msg;
Serial.println("✓ CAN RX Task 시작 (Core 0, Priority 24 - 절대 최고!)");
// ⭐⭐⭐ 초기 버퍼 확인
if (digitalRead(CAN_INT_PIN) == LOW) {
Serial.println("⚠️ 초기 CAN 인터럽트 핀 LOW - 버퍼 클리어");
int readCount = 0;
while (mcp2515.readMessage(&frame) == MCP2515::ERROR_OK && readCount < 100) {
struct timeval tv;
gettimeofday(&tv, NULL);
msg.timestamp_us = (uint64_t)tv.tv_sec * 1000000ULL + tv.tv_usec;
msg.id = frame.can_id & 0x1FFFFFFF;
msg.dlc = frame.can_dlc;
memcpy(msg.data, frame.data, 8);
if (xQueueSend(canQueue, &msg, 0) == pdTRUE) {
totalMsgCount++;
readCount++;
}
}
Serial.printf("✓ 초기 버퍼에서 %d개 읽음\n", readCount);
}
while (1) {
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
@@ -848,7 +870,7 @@ void canRxTask(void *parameter) {
msg.dlc = frame.can_dlc;
memcpy(msg.data, frame.data, 8);
if (xQueueSend(canQueue, &msg, 0) == pdTRUE) {
if (xQueueSend(canQueue, &msg, pdMS_TO_TICKS(10)) == pdTRUE) {
totalMsgCount++;
}
}
@@ -892,7 +914,7 @@ void sdWriteTask(void *parameter) {
// CAN 로깅
if (loggingEnabled && sdCardReady) {
if (xSemaphoreTake(sdMutex, pdMS_TO_TICKS(100)) == pdTRUE) {
if (xSemaphoreTake(sdMutex, pdMS_TO_TICKS(50)) == pdTRUE) {
if (canLogFormatCSV) {
char csvLine[128];
uint64_t relativeTime = canMsg.timestamp_us - canLogStartTime;
@@ -913,26 +935,27 @@ void sdWriteTask(void *parameter) {
currentFileSize += lineLen;
static int csvFlushCounter = 0;
if (++csvFlushCounter >= 100) {
if (++csvFlushCounter >= 20) { // 50 → 20으로 더 자주 플러시
logFile.flush();
csvFlushCounter = 0;
}
}
} else {
// BIN 형식
if (bufferIndex + sizeof(CANMessage) <= FILE_BUFFER_SIZE) {
memcpy(&fileBuffer[bufferIndex], &canMsg, sizeof(CANMessage));
bufferIndex += sizeof(CANMessage);
currentFileSize += sizeof(CANMessage);
}
if (bufferIndex >= FILE_BUFFER_SIZE - sizeof(CANMessage)) {
// ⭐⭐⭐ 1단계: 버퍼 가득 찼으면 먼저 플러시
if (bufferIndex + sizeof(CANMessage) > FILE_BUFFER_SIZE) {
if (logFile) {
logFile.write(fileBuffer, bufferIndex);
size_t written = logFile.write(fileBuffer, bufferIndex);
logFile.flush();
Serial.printf("✓ BIN 버퍼 플러시: %d bytes written\n", written);
bufferIndex = 0;
}
}
// ⭐⭐⭐ 2단계: 이제 공간 확보됨, 데이터 추가
memcpy(&fileBuffer[bufferIndex], &canMsg, sizeof(CANMessage));
bufferIndex += sizeof(CANMessage);
currentFileSize += sizeof(CANMessage);
}
xSemaphoreGive(sdMutex);
}
@@ -946,7 +969,7 @@ void sdWriteTask(void *parameter) {
}
void sdMonitorTask(void *parameter) {
const TickType_t xDelay = pdMS_TO_TICKS(1000);
const TickType_t xDelay = pdMS_TO_TICKS(500);
while (1) {
uint32_t currentTime = millis();
@@ -1182,7 +1205,7 @@ void sequenceTask(void *parameter) {
// ========================================
void webSocketEvent(uint8_t num, WStype_t type, uint8_t *payload, size_t length) {
if (type == WStype_TEXT) {
DynamicJsonDocument doc(2048);
DynamicJsonDocument doc(44384);
DeserializationError error = deserializeJson(doc, payload);
if (error) return;
@@ -1268,16 +1291,29 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t *payload, size_t length)
timeinfo.tm_year + 1900, timeinfo.tm_mon + 1, timeinfo.tm_mday,
timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec, ext);
// ⭐⭐⭐ 파일 생성 (헤더 쓰기)
logFile = SD.open(currentFilename, FILE_WRITE);
if (logFile) {
if (canLogFormatCSV) {
logFile.println("Time_us,CAN_ID,DLC,Data");
logFile.flush();
}
logFile.close(); // ⭐ 헤더 쓰고 닫기
loggingEnabled = true;
bufferIndex = 0;
currentFileSize = logFile.size();
// ⭐⭐⭐ APPEND 모드로 다시 열기
logFile = SD.open(currentFilename, FILE_APPEND);
if (logFile) {
loggingEnabled = true;
bufferIndex = 0;
currentFileSize = logFile.size();
Serial.printf("✓ 로깅 파일 열림 (APPEND): %s\n", currentFilename);
} else {
Serial.println("✗ APPEND 모드로 파일 열기 실패");
}
} else {
Serial.println("✗ 파일 생성 실패");
}
xSemaphoreGive(sdMutex);
@@ -1410,10 +1446,20 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t *payload, size_t length)
int speedIndex = doc["speed"];
if (speedIndex >= 0 && speedIndex < 4) {
currentCanSpeed = canSpeedValues[speedIndex];
mcp2515.reset();
mcp2515.setBitrate(currentCanSpeed, MCP_8MHZ);
setMCP2515Mode(currentMcpMode);
// ⭐⭐⭐ MCP2515 리셋 제거! (CAN 수신 중단 방지)
// 속도 변경은 로깅 중지 후 수동으로만 가능하도록 변경
// mcp2515.reset();
// mcp2515.setBitrate(currentCanSpeed, MCP_8MHZ);
// setMCP2515Mode(currentMcpMode);
saveSettings();
// 사용자에게 안내 메시지
StaticJsonDocument<256> response;
response["type"] = "info";
response["message"] = "CAN speed saved. Stop logging and restart to apply.";
String json;
serializeJson(response, json);
webSocket.sendTXT(num, json);
}
}
else if (strcmp(cmd, "startSerial2Logging") == 0) {
@@ -1525,10 +1571,20 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t *payload, size_t length)
int speedIndex = doc["speed"];
if (speedIndex >= 0 && speedIndex < 4) {
currentCanSpeed = canSpeedValues[speedIndex];
mcp2515.reset();
mcp2515.setBitrate(currentCanSpeed, MCP_8MHZ);
setMCP2515Mode(currentMcpMode);
// ⭐⭐⭐ MCP2515 리셋 제거! (CAN 수신 중단 방지)
// 속도 변경은 로깅 중지 후 수동으로만 가능하도록 변경
// mcp2515.reset();
// mcp2515.setBitrate(currentCanSpeed, MCP_8MHZ);
// setMCP2515Mode(currentMcpMode);
saveSettings();
// 사용자에게 안내 메시지
StaticJsonDocument<256> response;
response["type"] = "info";
response["message"] = "CAN speed saved. Stop logging and restart to apply.";
String json;
serializeJson(response, json);
webSocket.sendTXT(num, json);
}
}
else if (strcmp(cmd, "setMcpMode") == 0) {
@@ -1735,7 +1791,7 @@ void webUpdateTask(void *parameter) {
webSocket.loop();
if (webSocket.connectedClients() > 0) {
DynamicJsonDocument doc(8192); // ⭐ 4096 → 8192로 증가
DynamicJsonDocument doc(16384); // ⭐ 4096 → 8192로 증가
doc["type"] = "update";
doc["logging"] = loggingEnabled;
doc["serialLogging"] = serialLoggingEnabled;
@@ -1837,7 +1893,7 @@ void webUpdateTask(void *parameter) {
serialMsg.isTx ? "TX" : "RX",
dataStr);
if (serialCsvIndex + lineLen < SERIAL_CSV_BUFFER_SIZE) {
if (serialCsvIndex + lineLen <= SERIAL_CSV_BUFFER_SIZE) { // < → <=
memcpy(&serialCsvBuffer[serialCsvIndex], csvLine, lineLen);
serialCsvIndex += lineLen;
currentSerialFileSize += lineLen;
@@ -1898,7 +1954,7 @@ void webUpdateTask(void *parameter) {
serial2Msg.isTx ? "TX" : "RX",
dataStr);
if (serial2CsvIndex + lineLen < SERIAL2_CSV_BUFFER_SIZE) {
if (serial2CsvIndex + lineLen <= SERIAL2_CSV_BUFFER_SIZE) { // < → <=
memcpy(&serial2CsvBuffer[serial2CsvIndex], csvLine, lineLen);
serial2CsvIndex += lineLen;
currentSerial2FileSize += lineLen;
@@ -1951,7 +2007,7 @@ void webUpdateTask(void *parameter) {
void setup() {
Serial.begin(115200);
delay(1000);
WiFi.setSleep(false);
Serial.println("\n========================================");
Serial.println(" Byun CAN Logger + Serial Terminal");
Serial.println(" Version 2.3 - PSRAM Optimized");
@@ -1993,10 +2049,33 @@ void setup() {
// MCP2515 초기화
Serial.println("MCP2515 초기화...");
mcp2515.reset();
// ⭐⭐⭐ 하드 리셋
digitalWrite(HSPI_CS, LOW);
delay(10);
digitalWrite(HSPI_CS, HIGH);
delay(50);
mcp2515.reset();
delay(100);
mcp2515.setBitrate(currentCanSpeed, MCP_8MHZ);
delay(10);
mcp2515.setNormalMode();
delay(50);
// 버퍼 클리어
Serial.println("CAN 버퍼 클리어...");
struct can_frame dummyFrame;
int clearCount = 0;
while (mcp2515.readMessage(&dummyFrame) == MCP2515::ERROR_OK) {
clearCount++;
if (clearCount > 100) break;
}
if (clearCount > 0) {
Serial.printf("✓ 버퍼 클리어 완료 (%d개)\n", clearCount);
}
mcp2515.clearRXnOVRFlags();
Serial.println("✓ MCP2515 초기화 완료");
// Serial 통신 초기화
@@ -2132,13 +2211,13 @@ void setup() {
attachInterrupt(digitalPinToInterrupt(CAN_INT_PIN), canISR, FALLING);
// Task 생성
xTaskCreatePinnedToCore(canRxTask, "CAN_RX", 4096, NULL, 6, &canRxTaskHandle, 1);
xTaskCreatePinnedToCore(sdWriteTask, "SD_WRITE", 24576, NULL, 4, &sdWriteTaskHandle, 1);
xTaskCreatePinnedToCore(canRxTask, "CAN_RX", 8192, NULL, configMAX_PRIORITIES - 1, &canRxTaskHandle, 1); // Core 0, Pri 24
xTaskCreatePinnedToCore(sdWriteTask, "SD_WRITE", 12576, 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, 0);
xTaskCreatePinnedToCore(webUpdateTask, "WEB_UPDATE", 16384, NULL, 2, &webTaskHandle, 0); // ⭐ 10240 → 16384
xTaskCreatePinnedToCore(txTask, "TX", 4096, NULL, 3, NULL, 1);
xTaskCreatePinnedToCore(webUpdateTask, "WEB_UPDATE", 16384, NULL, 5, &webTaskHandle, 0); // ⭐ 10240 → 16384
xTaskCreatePinnedToCore(sdMonitorTask, "SD_MONITOR", 4096, NULL, 1, NULL, 0);
if (timeSyncStatus.rtcAvailable) {

17
index.h
View File

@@ -26,7 +26,7 @@ const char index_html[] PROGMEM = R"rawliteral(
}
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #666;
color: white;
padding: 20px;
text-align: center;
}
@@ -883,7 +883,7 @@ const char index_html[] PROGMEM = R"rawliteral(
const modeNames = {0: 'NORMAL (TX/RX+ACK)', 1: 'LISTEN-ONLY (RX)', 2: 'LOOPBACK', 3: 'TRANSMIT-ONLY (TX)'};
let currentLoggingFile = '';
let commentingFile = '';
let hasInitialSync = false;
// hasInitialSync 제거 - 매번 자동 동기화
function updateCurrentTime() {
const now = new Date();
@@ -943,11 +943,18 @@ const char index_html[] PROGMEM = R"rawliteral(
document.getElementById('sync-status').textContent = '';
document.getElementById('sync-status').style.color = '#38ef7d';
if (!hasInitialSync) {
// ⭐⭐⭐ 자동 시간 동기화 (1분에 1회로 제한)
const now = Date.now();
const lastSync = parseInt(localStorage.getItem('lastTimeSync') || '0');
if (now - lastSync > 60000) { // 1분 이상 경과
setTimeout(function() {
syncTimeFromPhone();
hasInitialSync = true;
}, 500);
localStorage.setItem('lastTimeSync', now.toString());
console.log('Auto time sync on connect');
}, 100);
} else {
console.log('Skipping time sync (last sync was recent)');
}
// ⭐ WebSocket 연결되면 즉시 파일 목록 요청