From 2d7368679d0d35d393a164cd4b491c9ab9d2d696 Mon Sep 17 00:00:00 2001 From: byun Date: Wed, 10 Dec 2025 21:48:18 +0000 Subject: [PATCH] =?UTF-8?q?can=20logging=20=EB=A9=88=EC=B6=A4=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0,=20=ED=8C=8C=EC=9D=BC=20=EC=A0=95=EC=83=81=20?= =?UTF-8?q?=EB=A1=9C=EA=B9=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ESP32_CAN_Logger-a.ino | 147 +++++++++++++++++++++++++++++++---------- index.h | 17 +++-- 2 files changed, 125 insertions(+), 39 deletions(-) diff --git a/ESP32_CAN_Logger-a.ino b/ESP32_CAN_Logger-a.ino index 721df4e..2605b0e 100644 --- a/ESP32_CAN_Logger-a.ino +++ b/ESP32_CAN_Logger-a.ino @@ -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) { diff --git a/index.h b/index.h index 3d58165..fb0893b 100644 --- a/index.h +++ b/index.h @@ -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 연결되면 즉시 파일 목록 요청