ota를 위한 ESP32 partion table 이해하기

  • ESP32 파티션 테이블 (factory 이하 부분은 다를수 있음)
    • esp32 c3 supermini oled 보드로 수행해봤음.
    파티션 이름 (Name) 타입 (Type) 서브타입 (SubType) 오프셋 (Offset) 크기 (Size) (예시) 용도 (Purpose)
    Bootloader (숨김) (숨김) 0x1000 0x7000 (28KB) 칩 부팅 시 가장 먼저 실행되는 2단계 부트 로더 코드 저장
    Partition Table (숨김) (숨김) 0x8000 0x1000 (4KB) 플래시 내의 모든 파티션 정보(이름, 타입, 오프셋, 크기)를 정의
    nvs data nvs 0x9000 0x5000 (20KB) Wi-Fi 설정, Bluetooth 페어링 정보, 장치 보정 데이터 등 비휘발성 데이터 저장 (NVS 라이브러리 사용)
    otadata data ota 0xE000 0x2000 (8KB) OTA(Over-The-Air) 업데이트를 위한 데이터 저장 (현재 활성 앱 슬롯 정보 등)
    factory app factory 0x10000 0x140000 (1.25MB) 기본 공장 펌웨어 또는 단일 애플리케이션 펌웨어 저장
    ota_0 app ota_0 0x150000 0x140000 (1.25MB) OTA 업데이트 시 새 펌웨어를 저장하는 공간 (첫 번째 OTA 슬롯)
    ota_1 app ota_1 0x290000 0x140000 (1.25MB) OTA 업데이트 시 새 펌웨어를 저장하는 공간 (두 번째 OTA 슬롯)
    spiffs data spiffs 0x3D0000 0x190000 (1.5MB) 파일 시스템 (SPIFFS, LittleFS 또는 FATFS) 데이터 저장 (웹 페이지, 이미지 등)

    #include  // ESP-IDF 파티션 관련 API 헤더
    // #include   // Not needed if using ESP.getFlashChipSize()
    #include    // For esp_ota_get_running_partition()
    #include            // For ESP.getFlashChipSize()
    
    // **NOTE**: The Wi-Fi and OTA update related code from previous examples
    // is not included here to keep this snippet focused on the partition info.
    // If you're combining, ensure you include WiFi.h, HTTPClient.h, and Update.h
    // in your main sketch.
    
    /**
     * @brief 현재 ESP32 장치의 플래시 파티션 정보를 조회하여 출력합니다.
     * 주로 4MB 플래시 보드에서 사용되는 파티션 스키마를 가정하고 있습니다.
     */
    void printFlashPartitionInfo() {
      Serial.println("n--- ESP32 Flash Partition Info ---");
    
      // 플래시 칩 크기 조회
      // CORRECTED: Use ESP.getFlashChipSize()
      uint32_t flash_chip_size = ESP.getFlashChipSize();
      Serial.printf("Flash Chip Size: %u MBn", flash_chip_size / (1024 * 1024));
      Serial.println("------------------------------------");
    
      // 모든 파티션을 찾기 위한 이터레이터
      esp_partition_iterator_t it;
    
      // 'app' (애플리케이션) 타입 파티션 조회
      it = esp_partition_find(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_ANY, NULL);
      if (it) {
        Serial.println("### Application Partitions (APP) ###");
        do {
          const esp_partition_t* part = esp_partition_get(it);
          Serial.printf("  Label: %-15s, Type: %2d, SubType: %2d, Address: 0x%06x, Size: %6u KBn",
                        part->label,
                        part->type,
                        part->subtype,
                        part->address,
                        part->size / 1024);
          it = esp_partition_next(it);
        } while (it != NULL);
        esp_partition_iterator_release(it); // 이터레이터 해제
      } else {
        Serial.println("  No APP partitions found.");
      }
      Serial.println("------------------------------------");
    
      // 'data' 타입 파티션 조회 (NVS, OTA Data, SPIFFS/LittleFS 등)
      it = esp_partition_find(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, NULL);
      if (it) {
        Serial.println("### Data Partitions ###");
        do {
          const esp_partition_t* part = esp_partition_get(it);
          // 서브타입에 따라 더 구체적인 이름 출력
          const char* subTypeStr;
          if (part->subtype == ESP_PARTITION_SUBTYPE_DATA_NVS) {
            subTypeStr = "NVS";
          } else if (part->subtype == ESP_PARTITION_SUBTYPE_DATA_OTA) {
            subTypeStr = "OTA_DATA";
          } else if (part->subtype == ESP_PARTITION_SUBTYPE_DATA_PHY) {
            subTypeStr = "PHY_INIT";
          } else if (part->subtype == ESP_PARTITION_SUBTYPE_DATA_SPIFFS) {
            subTypeStr = "SPIFFS";
          } else if (part->subtype == ESP_PARTITION_SUBTYPE_DATA_FAT) {
            subTypeStr = "FATFS";
          } else {
            subTypeStr = "UNKNOWN";
          }
    
          Serial.printf("  Label: %-15s, Type: %2d, SubType: %-10s, Address: 0x%06x, Size: %6u KBn",
                        part->label,
                        part->type,
                        subTypeStr, // 문자열 서브타입 사용
                        part->address,
                        part->size / 1024);
          it = esp_partition_next(it);
        } while (it != NULL);
        esp_partition_iterator_release(it); // 이터레이터 해제
      } else {
        Serial.println("  No Data partitions found.");
      }
      Serial.println("------------------------------------");
    
      // 현재 실행 중인 애플리케이션 파티션 정보
      const esp_partition_t* running_partition = esp_ota_get_running_partition();
      if (running_partition) {
        Serial.printf("Currently Running App: %s (Address: 0x%06x)n",
                      running_partition->label, running_partition->address);
      }
      Serial.println("------------------------------------");
    }
    
    void setup() {
      Serial.begin(115200);
      delay(100); // 시리얼 초기화 대기
    
      printFlashPartitionInfo(); // 파티션 정보 출력 함수 호출
    }
    
    void loop() {
      // Simple serial command handler for demonstration
      if (Serial.available()) {
        String command = Serial.readStringUntil('n');
        command.trim();
    
        if (command.equalsIgnoreCase("info") || command.startsWith("table")) {
          Serial.println("Executing printFlashPartitionInfo...");
          printFlashPartitionInfo();
        } else {
          Serial.println("Unknown command. Type 'info' or 'table' for partition info.");
        }
      }
      delay(100);
    }
    17:06:52.917 -> ### Application Partitions (APP) ###
    
    17:06:52.917 -> Label: app0 , Type: 0, SubType: 16, Address: 0x010000, Size: 1920 KB
    
    17:06:52.917 -> Label: app1 , Type: 0, SubType: 17, Address: 0x1f0000, Size: 1920 KB
    
    17:06:52.917 -> ------------------------------------
    
    17:06:52.917 -> ### Data Partitions ###
    
    17:06:52.917 -> Label: nvs , Type: 1, SubType: NVS , Address: 0x009000, Size: 20 KB
    
    17:06:52.917 -> Label: otadata , Type: 1, SubType: OTA_DATA , Address: 0x00e000, Size: 8 KB
    
    17:06:52.917 -> Label: spiffs , Type: 1, SubType: SPIFFS , Address: 0x3d0000, Size: 128 KB
    
    17:06:52.917 -> Label: coredump , Type: 1, SubType: UNKNOWN , Address: 0x3f0000, Size: 64 KB
    
    모두 합하면 4M 가 아니다

    시리얼 출력에서 각 파티션의 크기를 합산해보면:

    • APP 파티션:
      • app0: 1920 KB
      • app1: 1920 KB
    • Data 파티션:
      • nvs: 20 KB
      • otadata: 8 KB
      • spiffs: 128 KB
      • coredump: 64 KB

    이들을 모두 더하면:
    1920 + 1920 + 20 + 8 + 128 + 64 = 4060 KB

    1MB = 1024KB이므로, 4060 KB는 약 3.965 MB입니다. 실제로 4MB보다 작습니다.


    왜 합이 정확히 4MB가 아닐까요? 🧐

    리스트에 나온 파티션들의 합이 플래시 전체 크기(예: 4MB)와 정확히 일치하지 않는 데에는 몇 가지 주요 이유가 있습니다.

    1. 숨겨진/예약된 영역: 파티션 테이블 자체와 2차 부트로더(ESP32가 부팅하는 데 필수)는 플래시 맨 앞부분에 별도의 공간을 차지합니다.

      • 2차 부트로더: 일반적으로 0x1000(4KB 오프셋)에 위치하며 약 0x7000(28KB) 정도를 사용합니다.
      • 파티션 테이블: 0x8000(32KB 오프셋)에 위치하며 보통 0x1000(4KB) 크기입니다.
        이 영역들은 전체 플래시 메모리의 일부이지만, esp_partition_find로 나오는 사용자 정의 파티션 목록에는 나타나지 않습니다. 이들은 고정된 역할을 가지며, 사용자가 임의로 조정할 수 없습니다.
    2. 할당되지 않은 공간/패딩: 파티션 사이 또는 플래시 끝에 작은 빈 공간이 남아 있을 수 있습니다. 이는 정렬(alignment) 목적이거나, 단순히 명시적으로 정의되지 않은 미사용 공간일 수 있습니다.

    3. 플래시 칩 크기 vs. 실제 사용 가능 공간: 플래시 칩이 "4MB"라고 해도, 내부 구조나 주소 지정 방식에 따라 실제 사용 가능한 공간에 약간의 차이가 있을 수 있습니다.

    제공하신 주소를 바탕으로 숨겨진 영역을 계산해보면:

    • 첫 번째 APP 파티션(app0)은 0x010000(64KB)에서 시작합니다.
    • 그 전에는:

      • 부트로더: 0x1000(4KB)에서 시작해 0x8000(32KB)까지, 약 28KB 사용
      • 파티션 테이블: 0x8000(32KB)에서 시작해 0x9000(36KB)까지, 4KB 사용
      • NVS: 0x9000(36KB)에서 시작해 20KB, 0x0E000(56KB)까지
      • OTA Data: 0x0E000(56KB)에서 시작해 8KB, 0x10000(64KB)까지

      따라서, app0가 시작되는 이전 공간(0x10000)은:
      0x10000(app0 시작) - 0x0(플래시 시작) = 65536 바이트 = 64 KB입니다.

      여러분이 나열한 파티션들(NVS, OTA Data)은 20KB + 8KB = 28KB를 차지합니다.
      즉, 부트로더와 파티션 테이블이 암묵적으로 64 KB - 28 KB = 36 KB를 사용하고 있다는 뜻입니다. 이 값은 실제로 이 구성요소들의 일반적인 크기와 잘 일치합니다.

      이렇게 "숨겨진" 또는 암묵적으로 사용되는 부분을 더해보면:
      4060 KB(나열된 파티션 합) + 36 KB(부트로더 + 파티션 테이블) = 4096 KB가 됩니다.

      그리고 4096 KB = 4 MB! 🎉

      즉, "사라진" 공간은 바로 ESP32의 동작에 필수적인 부트로더와 파티션 테이블이 차지하고 있으며, 이들은 사용자 관리 파티션 목록에는 나타나지 않는다는 점을 알 수 있습니다.


    댓글 남기기