From 5800a8d5dc40e76f0aafe2b43c01adf677565cea Mon Sep 17 00:00:00 2001 From: byun Date: Tue, 10 Mar 2026 23:20:32 +0000 Subject: [PATCH] =?UTF-8?q?=EB=8B=A4=EC=9A=B4=20=EC=88=98=EC=A0=95=201bit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ESP32_CAN_Logger-a.ino | 125 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 112 insertions(+), 13 deletions(-) diff --git a/ESP32_CAN_Logger-a.ino b/ESP32_CAN_Logger-a.ino index fefb2d6..dcef3dd 100644 --- a/ESP32_CAN_Logger-a.ino +++ b/ESP32_CAN_Logger-a.ino @@ -2397,10 +2397,12 @@ void setup() { initRTC(); + // ★ 로깅: SDIO 4-bit (D0~D3) - 쓰기 속도 우선 + // 다운로드: SDIO 1-bit (D0만) - WiFi RF 간섭 방지 Serial.println("SD 카드 초기화 (SDIO 4-bit)..."); if (!SD_MMC.setPins(SDIO_CLK, SDIO_CMD, SDIO_D0, SDIO_D1, SDIO_D2, SDIO_D3)) { sdCardReady = false; - } else if (SD_MMC.begin("/sdcard", false)) { + } else if (SD_MMC.begin("/sdcard", false, false, 10000000)) { // false = 4-bit sdCardReady = true; Serial.printf("✓ SD 카드 초기화 완료: %llu MB\n", SD_MMC.cardSize()/(1024*1024)); loadFileComments(); @@ -2472,6 +2474,22 @@ void setup() { server.send(404, "text/plain", "File not found"); return; } + // ★ 다운로드 중 1-bit 모드 전환 (WiFi TX와 SD DMA 간섭 방지) + // 4-bit: D0~D3 동시 → WiFi RF 노이즈에 취약 → 0x109 DMA 타임아웃 + // 1-bit: D0만 사용 → 노이즈 내성 대폭 향상 + // 전환 절차: end() → setPins(D0만) → begin(1-bit) + { + SD_MMC.end(); + delay(50); + SD_MMC.setPins(SDIO_CLK, SDIO_CMD, SDIO_D0); + if (!SD_MMC.begin("/sdcard", true, false, 10000000)) { + xSemaphoreGive(sdMutex); + if (wasLogging) loggingEnabled = true; + server.send(500, "text/plain", "SD 1-bit init failed"); return; + } + Serial.println("✓ SD 1-bit 모드 전환 (다운로드용)"); + } + File file = SD_MMC.open(filename, FILE_READ); if (!file) { xSemaphoreGive(sdMutex); @@ -2487,39 +2505,120 @@ void setup() { server.sendHeader("Connection", "close"); server.send(200, "application/octet-stream", ""); - // ★ 청크 크기 8KB (512 → 8192): 전송 횟수 대폭 감소, Watchdog 여유 - const size_t CHUNK = 8192; + // ★ CHUNK 4KB: DMA 전송 크기를 줄여 SD 내부 타이밍 부담 감소 + const size_t CHUNK = 4096; uint8_t* buf = (uint8_t*)heap_caps_malloc(CHUNK, MALLOC_CAP_DMA | MALLOC_CAP_8BIT); if (!buf) buf = (uint8_t*)malloc(CHUNK); if (buf) { size_t total = 0; WiFiClient client = server.client(); + client.setNoDelay(true); - while (file.available() && total < fileSize && client.connected()) { - size_t r = file.read(buf, CHUNK); - if (!r) break; + // SD 읽기 실패 복구 람다 (SDMMC 페리페럴 완전 리셋) + // 0x109(ESP_ERR_TIMEOUT) 발생 후 seek+read 재시도는 무의미: + // SDMMC 하드웨어가 에러 상태로 굳어 있어서 end()+begin() 필수 + auto sdReinit = [&]() -> bool { + Serial.println("⚠️ SDMMC 페리페럴 리셋 시도..."); + file.close(); + xSemaphoreGive(sdMutex); // 뮤텍스 해제 후 reinit + delay(200); + SD_MMC.end(); + delay(100); + SD_MMC.setPins(SDIO_CLK, SDIO_CMD, SDIO_D0); // 1-bit + if (!SD_MMC.begin("/sdcard", true, false, 10000000)) { // true = 1-bit + Serial.println("✗ SDMMC 재초기화 실패"); + sdCardReady = false; + return false; + } + sdCardReady = true; + // 뮤텍스 재획득 + if (xSemaphoreTake(sdMutex, pdMS_TO_TICKS(3000)) != pdTRUE) { + Serial.println("✗ 뮤텍스 재획득 실패"); + return false; + } + // 파일 재오픈 후 이어받기 위치로 seek + file = SD_MMC.open(filename, FILE_READ); + if (!file) { + Serial.println("✗ 파일 재오픈 실패"); + xSemaphoreGive(sdMutex); + return false; + } + if (!file.seek(total)) { + Serial.println("✗ 파일 seek 실패"); + file.close(); + xSemaphoreGive(sdMutex); + return false; + } + Serial.printf("✓ SDMMC 리셋 완료, offset=%u 에서 재개", (unsigned)total); + return true; + }; + + int reinitCount = 0; + while (total < fileSize && client.connected()) { + size_t toRead = min((size_t)(fileSize - total), CHUNK); + size_t r = file.read(buf, toRead); + + if (r == 0) { + // SDMMC 에러 상태 → 페리페럴 완전 리셋 + if (reinitCount >= 3) { + Serial.println("✗ Download: SDMMC 리셋 3회 실패, 중단"); + goto download_done; + } + reinitCount++; + Serial.printf("⚠️ SD 읽기 실패 (reinit %d), offset=%u", + reinitCount, (unsigned)total); + if (!sdReinit()) goto download_done; + continue; // 리셋 성공 → 같은 offset에서 재시도 + } + reinitCount = 0; // 읽기 성공 시 카운터 리셋 size_t w = 0; - uint32_t chunkStart = millis(); - while (w < r && client.connected()) { + uint32_t writeStart = millis(); + while (w < r) { + if (!client.connected()) { + Serial.println("⚠️ Download: 클라이언트 연결 끊김"); + goto download_done; + } size_t wr = client.write(buf + w, r - w); if (wr > 0) { w += wr; + writeStart = millis(); } else { - // 클라이언트 버퍼 가득 찬 경우 짧게 대기 - if (millis() - chunkStart > 5000) break; // 5초 타임아웃 + if (millis() - writeStart > 30000) { + Serial.println("⚠️ Download: 30초 타임아웃"); + goto download_done; + } + yield(); delay(1); } } total += r; - yield(); // WiFi 스택에 처리 시간 양보 + yield(); } + download_done: free(buf); - Serial.printf("Download OK: %s (%u bytes)\n", filename.c_str(), total); + Serial.printf("Download %s: %s (%u / %u bytes)\n", + (total == fileSize) ? "OK" : "PARTIAL", + filename.c_str(), (unsigned)total, (unsigned)fileSize); + } + + if (file) file.close(); + + // ★ 다운로드 완료 후 4-bit 모드 복원 (로깅용) + { + SD_MMC.end(); + delay(50); + SD_MMC.setPins(SDIO_CLK, SDIO_CMD, SDIO_D0, SDIO_D1, SDIO_D2, SDIO_D3); + if (SD_MMC.begin("/sdcard", false, false, 10000000)) { + sdCardReady = true; + Serial.println("✓ SD 4-bit 모드 복원 (로깅용)"); + } else { + sdCardReady = false; + Serial.println("✗ SD 4-bit 복원 실패"); + } } - file.close(); xSemaphoreGive(sdMutex); // 로깅 재개