다운 수정 1bit

This commit is contained in:
2026-03-10 23:20:32 +00:00
parent 314d05b15e
commit 5800a8d5dc

View File

@@ -2397,10 +2397,12 @@ void setup() {
initRTC(); initRTC();
// ★ 로깅: SDIO 4-bit (D0~D3) - 쓰기 속도 우선
// 다운로드: SDIO 1-bit (D0만) - WiFi RF 간섭 방지
Serial.println("SD 카드 초기화 (SDIO 4-bit)..."); Serial.println("SD 카드 초기화 (SDIO 4-bit)...");
if (!SD_MMC.setPins(SDIO_CLK, SDIO_CMD, SDIO_D0, SDIO_D1, SDIO_D2, SDIO_D3)) { if (!SD_MMC.setPins(SDIO_CLK, SDIO_CMD, SDIO_D0, SDIO_D1, SDIO_D2, SDIO_D3)) {
sdCardReady = false; sdCardReady = false;
} else if (SD_MMC.begin("/sdcard", false)) { } else if (SD_MMC.begin("/sdcard", false, false, 10000000)) { // false = 4-bit
sdCardReady = true; sdCardReady = true;
Serial.printf("✓ SD 카드 초기화 완료: %llu MB\n", SD_MMC.cardSize()/(1024*1024)); Serial.printf("✓ SD 카드 초기화 완료: %llu MB\n", SD_MMC.cardSize()/(1024*1024));
loadFileComments(); loadFileComments();
@@ -2472,6 +2474,22 @@ void setup() {
server.send(404, "text/plain", "File not found"); return; 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); File file = SD_MMC.open(filename, FILE_READ);
if (!file) { if (!file) {
xSemaphoreGive(sdMutex); xSemaphoreGive(sdMutex);
@@ -2487,39 +2505,120 @@ void setup() {
server.sendHeader("Connection", "close"); server.sendHeader("Connection", "close");
server.send(200, "application/octet-stream", ""); server.send(200, "application/octet-stream", "");
// ★ 청크 크기 8KB (512 → 8192): 전송 횟수 대폭 감소, Watchdog 여유 // ★ CHUNK 4KB: DMA 전송 크기를 줄여 SD 내부 타이밍 부담 감소
const size_t CHUNK = 8192; const size_t CHUNK = 4096;
uint8_t* buf = (uint8_t*)heap_caps_malloc(CHUNK, MALLOC_CAP_DMA | MALLOC_CAP_8BIT); uint8_t* buf = (uint8_t*)heap_caps_malloc(CHUNK, MALLOC_CAP_DMA | MALLOC_CAP_8BIT);
if (!buf) buf = (uint8_t*)malloc(CHUNK); if (!buf) buf = (uint8_t*)malloc(CHUNK);
if (buf) { if (buf) {
size_t total = 0; size_t total = 0;
WiFiClient client = server.client(); WiFiClient client = server.client();
client.setNoDelay(true);
while (file.available() && total < fileSize && client.connected()) { // SD 읽기 실패 복구 람다 (SDMMC 페리페럴 완전 리셋)
size_t r = file.read(buf, CHUNK); // 0x109(ESP_ERR_TIMEOUT) 발생 후 seek+read 재시도는 무의미:
if (!r) break; // 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; size_t w = 0;
uint32_t chunkStart = millis(); uint32_t writeStart = millis();
while (w < r && client.connected()) { while (w < r) {
if (!client.connected()) {
Serial.println("⚠️ Download: 클라이언트 연결 끊김");
goto download_done;
}
size_t wr = client.write(buf + w, r - w); size_t wr = client.write(buf + w, r - w);
if (wr > 0) { if (wr > 0) {
w += wr; w += wr;
writeStart = millis();
} else { } else {
// 클라이언트 버퍼 가득 찬 경우 짧게 대기 if (millis() - writeStart > 30000) {
if (millis() - chunkStart > 5000) break; // 5초 타임아웃 Serial.println("⚠️ Download: 30초 타임아웃");
goto download_done;
}
yield();
delay(1); delay(1);
} }
} }
total += r; total += r;
yield(); // WiFi 스택에 처리 시간 양보 yield();
} }
download_done:
free(buf); 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); xSemaphoreGive(sdMutex);
// 로깅 재개 // 로깅 재개