
ESP-IDF의 샘플 프로젝트는 주로 칩의 다양한 기능을 사용하는 방법을 보여줍니다. 공식 GitHub 저장소의 examples 디렉토리에서 수많은 샘플들을 찾아볼 수 있습니다. 이 샘플들은 개발자가 자신의 프로젝트를 시작하는 데 필요한 기초적인 코드를 제공합니다.
깃허브 참고: https://github.com/espressif/esp-idf/tree/master/examples/
ESP-IDF 샘플 프로젝트는 크게 세 가지 범주로 나눌 수 있습니다.
1. System Examples: 칩의 기본적인 기능을 다룹니다.
2. Protocol Examples: 통신 프로토콜 관련 기능을 다룹니다.
3. Peripheral Examples: 칩에 내장된 다양한 주변 장치를 사용하는 방법을 다룹니다.
VS Code에서 Ctrl+Shift+P (macOS에서는 Cmd+Shift+P)를 눌러 명령 팔레트를 엽니다. 명령 팔레트에 "ESP-IDF: Show Examples Projects"를 입력하고 선택합니다.

hello_world, blink, wifi, ble 등 다양한 샘플 프로젝트 목록이 나타납니다. 원하는 샘플을 선택하고, 프로젝트를 저장할 폴더를 지정하면 자동으로 새로운 프로젝트가 생성됩니다.
가장 처음으로는 get-started 에 있는 blink 예제를 선택해서 Create project를 눌러서 프로젝트를 생성하겠습니다. 이 프로젝트는 ESP32 개발 보드에서 LED를 깜빡이는 "Blink" 예제입니다. (깃허브 참고)

프로젝트 구성은 어떤 식으로 되어 있는지, 추가적으로 어떤 설정이 필요한지 알아보도록 하겠습니다.
ESP-IDF에서 타겟 칩(예: ESP32, ESP32-S2, ESP32-C3)을 설정하는 방법은 CMake 기반의 빌드 시스템에서 idf.py set-target 명령어를 사용하는 것입니다.
$ idf.py set-target <target_name>
<target_name>에는 다음 중 하나를 입력합니다.
이 명령어를 실행하면, sdkconfig 파일과 CMake 빌드 시스템이 자동으로 업데이트되어 지정된 타겟 칩에 최적화된 컴파일 환경을 구성합니다.
VS Code 확장 프로그램을 설치했으면 창 하단의 상태 표시줄(파란색 막대)에서도 변경이 가능합니다. 아니면 명령 팔레트 열기(Cmd+Shift+P)를 눌러 명령 팔레트를 열고나서 "ESP-IDF: Set Espressif device target"을 입력하고 선택할 수 있습니다.
타겟을 변경한 후에는 기존 빌드 파일을 삭제하고 다시 빌드하는 것이 좋습니다.
$ idf.py fullclean
$ idf.py build
fullclean 명령어는 이전 타겟에서 생성된 모든 빌드 파일을 깨끗하게 지워, 새로운 타겟 설정에 맞춰 빌드 과정에 오류가 발생하지 않도록 합니다.
🤔 타겟 칩을 설정하는 구체적인 이유와 의미에 대해 궁금한데...?
타겟 칩에 최적화된 컴파일 환경을 구성한다는 것은, 빌드 시스템이 지정된 칩의 하드웨어 아키텍처와 내장된 기능에 맞춰서 컴파일러와 링커 설정을 변경하는 것을 의미합니다.
1. 칩 아키텍처 및 명령어 세트 최적화
모든 ESP32 칩셋이 동일한 CPU 코어를 사용하는 것은 아닙니다. 예를 들어, ESP32-S2/S3는 Tensilica Xtensa LX7 코어를 사용하고, ESP32-C3/C6는 RISC-V 코어를 사용합니다.
이렇게 하면, 컴파일러가 특정 칩의 아키텍처에 맞는 효율적인 기계어 코드를 생성하여 성능을 최적화할 수 있습니다.
2. 하드웨어 기능 활성화/비활성화
각 칩은 내장된 하드웨어 주변 장치(Wi-Fi, Bluetooth, USB OTG, GPIO 등)의 구성이 다릅니다.
예를 들어, ESP32-S2는 Wi-Fi는 지원하지만 Bluetooth 기능은 없습니다. esp32s2를 타겟으로 설정하면 빌드 시스템은 Bluetooth 관련 코드가 컴파일되지 않도록 자동으로 설정합니다. 불필요한 코드를 제외함으로써 펌웨어 크기를 줄이고 메모리 사용량을 최적화할 수 있습니다.
3. 메모리 맵 및 링커 스크립트 설정
칩마다 RAM, 플래시 메모리, ROM의 주소와 크기가 다릅니다.
타겟 설정을 하면 링커가 해당 칩의 실제 메모리 맵에 맞춰 변수, 함수, 라이브러리 코드를 배치하도록 링커 스크립트를 변경합니다. 이 과정은 프로그램이 실행될 때 메모리 주소 충돌을 방지하고, 효율적으로 자원을 할당하는 데 필수적입니다.
✍️ 타겟 칩을 설정하는 것은 칩의 종류에 따라 최적의 성능을 끌어내고 불필요한 코드를 제거하며, 하드웨어에 맞는 정확한 빌드 환경을 구축하는 핵심 과정입니다.
menuconfig는 ESP-IDF 프로젝트의 빌드 및 실행 환경, 다양한 설정 옵션을 쉽게 변경할 수 있는 메뉴 기반의 환경 설정 도구입니다. 리눅스 커널 설정과 유사한 인터페이스를 제공하며, 터미널에서 idf.py menuconfig 명령어로 실행합니다. 혹은 하단 표시줄 > 톱니바퀴 선택하면 됩니다.
여러 가지 설정 옵션이 많은데 그 중에서 Example Configuration 를 선택합니다.

Blink 예제는 단순히 LED를 켜고 끄는 것이기 때문에, 기본 설정 외에 특별히 변경할 사항은 많지 않습니다. LED가 연결된 GPIO 핀 번호를 설정하는 것이 가장 중요합니다. 이렇게 설정하면, 코드를 직접 수정하지 않고도 다른 GPIO 핀에 연결된 LED를 제어할 수 있습니다.
정확히는 menuconfig에서 GPIO 핀을 설정하면, 그 정보가 sdkconfig 파일에 저장되고, 이 파일을 기반으로 자동 생성된 헤더 파일이 코드에 포함되어 실제 핀 번호를 사용하게 됩니다.
실제로 sdkconfig 파일을 보면 위에서 설정한 내용이 적용되어 있습니다.
// sdkconfig 파일
CONFIG_BLINK_LED_GPIO=y
# CONFIG_BLINK_LED_STRIP is not set
CONFIG_BLINK_GPIO=5
CONFIG_BLINK_PERIOD=1000
참고로, 제가 가진 ESP32 개발 보드에는 내장 LED가 없고, 5V Power On LED만 존재합니다. 따라서 GPIO5에다가 LED를 연결해서 테스트를 진행했습니다. (회로도 참고)
sdkconfig는 ESP-IDF 프로젝트의 환경 설정 파일입니다. 프로젝트의 모든 빌드 옵션과 컴포넌트 설정을 저장하는 텍스트 파일입니다.
sdkconfig의 주요 역할
어떻게 적용이 되는지 순서대로 설명하면 다음과 같습니다.
1단계. idf.py menuconfig를 실행하고 설정을 변경한 뒤 저장하면, 프로젝트 루트 디렉토리에 있는 sdkconfig 파일이 업데이트됩니다. 이 파일은 프로젝트의 모든 설정 옵션을 키-값 쌍으로 저장하는 텍스트 파일입니다.
CONFIG_BLINK_GPIO=2
2단계. 빌드 과정이 시작될 때, CMake 빌드 시스템은 이 sdkconfig 파일의 내용을 읽어 sdkconfig.h라는 헤더 파일을 자동으로 생성합니다. 이 헤더 파일은 C/C++ 소스 코드가 사용할 수 있는 #define 매크로를 포함합니다.
#define CONFIG_BLINK_GPIO_NUM 2
3단계. Blink 예제의 C 소스 코드(main/blink.c)는 이 sdkconfig.h 파일을 #include하여 설정 값을 가져옵니다.
#include "sdkconfig.h"
...
int blink_gpio = CONFIG_BLINK_GPIO_NUM;
...
이렇게 하면 CONFIG_BLINK_GPIO_NUM이라는 매크로가 컴파일 시점에 sdkconfig 파일에서 설정한 값으로 대체되어, GPIO 제어 함수가 올바른 핀 번호를 사용하게 됩니다.
이제 blink_example_main.c 파일 소스 코드를 살펴보겠습니다.
1. 헤더 파일 포함
#include <stdio.h>
// FreeRTOS 헤더 파일, Task 관련 함수를 사용하기 위해서 선언. 여기서는 vTaskDelay() 사용
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
// gpio 관련 함수들을 사용하기 위해 선언
#include "driver/gpio.h"
// 그 외 로그, led strip 라이브러리(사용 안함), 설정 파일
#include "esp_log.h"
#include "led_strip.h"
#include "sdkconfig.h"
🤔 근데, gpio 관련 함수들은 누가, 어디서 제공하는걸까?
driver/gpio.h는 ESP-IDF의 GPIO 드라이버 컴포넌트에서 제공하는 헤더 파일입니다. ESP-IDF 프레임워크 안에 내장되어 있으며, ESP32 칩의 GPIO 핀을 제어하는 데 필요한 함수 선언과 매크로 정의를 포함하고 있습니다. 👉 라이브러리 파일: /Users/ckstn0777/esp/v5.4.1/esp-idf/components/esp_driver_gpio/src/gpio.c
🎯 ESP-IDF 컴포넌트 구조
ESP-IDF는 여러 개의 컴포넌트(components)로 구성되어 있습니다. 각 컴포넌트는 특정 기능(예: Wi-Fi, 블루투스, GPIO, I2C 등)을 담당하는 모듈이며, 관련된 소스 코드와 헤더 파일을 가지고 있습니다.
Blink 예제가 이 헤더 파일을 사용하는 과정:
<컴포넌트_이름>/<헤더_파일_이름> 형식으로 헤더 파일을 간단하게 포함할 수 있습니다.따라서 driver/gpio.h는 ESP-IDF가 제공하는 드라이버 라이브러리의 일부이며, 사용자가 직접 가져올 필요 없이 프레임워크 내에서 자동으로 관리됩니다.
2. GPIO 핀 정의, 상태 변수
#define BLINK_GPIO CONFIG_BLINK_GPIO
static uint8_t s_led_state = 0;
menuconfig에서 설정한 CONFIG_BLINK_GPIO_NUM 매크로 값을 BLINK_GPIO라는 더 직관적인 이름으로 다시 정의합니다.
참고로, static 키워드는 변수의 유효 범위를 현재 파일로 제한하는 역할을 합니다. static으로 선언된 변수는 이 변수가 선언된 .c 파일 내에서만 접근 가능하며, 다른 파일에서는 이 변수를 사용할 수 없습니다. 코드의 모듈성을 높이고, 안정성과 유지보수성을 향상시키는 데 매우 중요한 역할을 합니다.
3. LED 함수 구현
#ifdef CONFIG_BLINK_LED_STRIP
...(실행 안됨)...
#elif CONFIG_BLINK_LED_GPIO
static void blink_led(void)
{
// led_state에 따라 GPIO 핀의 전압 레벨 설정
gpio_set_level(BLINK_GPIO, s_led_state);
}
static void configure_led(void)
{
ESP_LOGI(TAG, "Example configured to blink GPIO LED!");
// 사용할 GPIO 핀 초기화
gpio_reset_pin(BLINK_GPIO);
// 해당 핀의 모드를 출력으로 설정
gpio_set_direction(BLINK_GPIO, GPIO_MODE_OUTPUT);
}
#else
#error "unsupported LED type"
#endif
#ifdef, #elif, #else, #endif 전처리기를 사용하여 조건부 컴파일을 수행합니다. 이는 menuconfig에서 사용자가 어떤 LED 타입을 선택했는지에 따라 컴파일될 코드를 결정합니다. 저희는 LED_GPIO 타입을 사용하기 때문에 LED_STRIP 관련 코드 부분은 실행되지 않습니다.
4. 메인 애플리케이션 함수
void app_main(void)
{
/* Configure the peripheral according to the LED type */
configure_led();
while (1) {
ESP_LOGI(TAG, "Turning the LED %s!", s_led_state == true ? "ON" : "OFF");
blink_led();
/* Toggle the LED state */
s_led_state = !s_led_state;
vTaskDelay(CONFIG_BLINK_PERIOD / portTICK_PERIOD_MS); // 1초 동안 딜레이
}
}
CMake는 C, C++와 같은 소스 코드를 컴파일하고 실행 파일을 만드는 과정을 제어하는 크로스 플랫폼 빌드 시스템 생성기 입니다. 다양한 운영 체제와 개발 환경에서 일관된 방식으로 프로젝트를 빌드할 수 있도록 돕습니다.

CMake의 역할
CMake는 직접 소스 코드를 컴파일하는 컴파일러가 아니라, 컴파일을 위한 빌드 스크립트(Makefile, Visual Studio 프로젝트 파일 등)를 생성하는 도구입니다.
ESP-IDF에서 CMake를 사용하는 이유
ESP-IDF는 CMake를 사용하여 복잡한 임베디드 프로젝트의 빌드 과정을 단순화하고, 개발 환경에 구애받지 않는 유연성을 제공합니다.
✍️ CMake는 개발자가 각기 다른 환경의 복잡한 빌드 시스템을 직접 다룰 필요 없이, 하나의 CMakeLists.txt 파일만으로 프로젝트의 빌드 과정을 통일하고 자동화할 수 있게 해주는 핵심 도구입니다.
Make와 CMake 차이
이미지 출처: https://earthly.dev/blog/cmake-vs-make-diff/

Make는 프로그램의 소스 파일로부터 실행 파일과 기타 비(非) 소스 파일을 생성하는 과정을 제어하는 도구입니다. 이 도구는 Makefile이라는 파일로부터 프로그램을 빌드하는 방법을 지시받습니다.
반면, CMake는 CMakeLists.txt 파일이 필요하며, 크로스 플랫폼(cross-platform)을 지원하는 Make입니다. 즉, 여러 운영 체제에서 작동합니다. 이를 통해 컴파일러에 독립적인 빌드, 테스트, 패키징 및 소프트웨어 설치가 가능합니다.
중요한 점은 CMake가 다른 시스템을 위한 빌드 파일을 생성할 뿐, 그 자체가 빌드 시스템은 아니라는 것입니다. CMake는 Makefile을 생성할 수 있으며, 생성된 Makefile은 작업 중인 플랫폼에서 Make와 함께 사용될 수 있습니다.
CMakeLists.txt는 CMake가 프로젝트를 빌드하는 데 사용하는 스크립트 파일입니다. 이 파일을 작성함으로써 소스 파일, 헤더 파일, 라이브러리 등을 어떻게 컴파일하고 링크할지 CMake에 지시할 수 있습니다.
기본적인 CMakeLists.txt 파일 구성 요소는 다음과 같습니다.
1. 최소 CMake 버전 지정
cmake_minimum_required() 명령은 이 프로젝트를 빌드하는 데 필요한 최소 CMake 버전을 지정합니다. 이는 하위 호환성을 보장하는 데 중요합니다.
cmake_minimum_required(VERSION 3.16)
ESP-IDF의 경우, 각 버전별로 요구하는 CMake의 최소 버전이 다릅니다. 따라서 idf.py가 자동으로 생성하는 CMakeLists.txt에는 이 명령어가 항상 포함되어 있습니다.
2. 프로젝트 정의: project() 명령어를 사용하여 프로젝트 이름과 언어를 정의합니다.
# 프로젝트 이름과 언어 정의
project(MyProject C)
3. 실행 파일 생성: add_executable() 명령어를 사용하여 소스 파일들을 묶어 실행 파일을 만듭니다.
# my_executable이라는 실행 파일 생성
# 소스 파일: main.c, my_module.c
add_executable(my_executable main.c my_module.c)
4. 라이브러리 연결: target_link_libraries() 명령어를 사용하여 생성된 실행 파일에 필요한 라이브러리를 연결합니다. → 타겟에 라이브러리 링크
# my_executable에 math 라이브러리 연결
target_link_libraries(my_executable PRIVATE math)
그 외 주요 CMake 명령어 정리
ESP-IDF는 일반 CMake 프로젝트보다 살짝 구조가 다릅니다. 기본적으로 프로젝트 단위 CMakeLists.txt와 컴포넌트 단위 CMakeLists.txt가 있어요.
1. 프로젝트 최상위 CMakeLists.txt
이 파일은 전체 프로젝트의 빌드 방법을 정의합니다. 모든 ESP-IDF 프로젝트의 최상위 CMakeLists.txt는 다음의 필수적인 내용을 포함해야 합니다.
# 최상위 CMakeLists.txt (프로젝트 루트)
cmake_minimum_required(VERSION 3.16)
# IDF_PATH: ESP-IDF가 설치된 경로를 가리키는 환경 변수
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(my_app)
2. 컴포넌트 단위 CMakeLists.txt
ESP-IDF에서는 컴포넌트 기반의 빌드 시스템을 사용하기 때문에, 각 컴포넌트 폴더에 CMakeLists.txt 파일을 두어 독립적으로 관리합니다. idf_component_register() 명령어를 사용하여 소스 파일, 헤더 파일 경로, 그리고 다른 컴포넌트에 대한 종속성을 정의합니다.
예시 1: main 컴포넌트의 CMakeLists.txt (main 컴포넌트는 프로젝트의 시작점인 app_main() 함수를 포함하는 특수한 컴포넌트입니다.)
# main 컴포넌트의 소스 파일과 포함 디렉터리를 등록합니다.
idf_component_register(SRCS "main.c"
INCLUDE_DIRS ".")
예시 2: 사용자 정의 컴포넌트의 CMakeLists.txt
components 디렉터리에 my_component라는 컴포넌트를 추가할 경우, my_component 디렉터리 내부에 CMakeLists.txt를 생성합니다.
# 프로젝트 구조
myProject/
├── CMakeLists.txt # 최상위 프로젝트 CMakeLists.txt
├── main/
│ ├── CMakeLists.txt # main 컴포넌트의 CMakeLists.txt
│ └── main.c
├── components/
│ └── my_component/
│ ├── CMakeLists.txt # 사용자 정의 컴포넌트의 CMakeLists.txt
│ ├── my_component.c
│ └── my_component.h
└── build/
my_component/CMakeLists.txt
# my_component의 소스 파일과 포함 디렉터리를 등록합니다.
idf_component_register(SRCS "my_component.c"
INCLUDE_DIRS ".")
main/CMakeLists.txt
# main 컴포넌트가 my_component를 사용하려면 종속성을 추가해야 합니다.
idf_component_register(SRCS "main.c"
INCLUDE_DIRS "."
REQUIRES my_component)
🤔 include($ENV{IDF_PATH}/tools/cmake/project.cmake) 구체적 의미
이 명령어는 ESP-IDF 프로젝트의 CMake 빌드 시스템을 로드하는 핵심 명령입니다. 이 한 줄을 통해 ESP-IDF의 모든 빌드 규칙과 환경 설정이 활성화됩니다. /tools/cmake/project.cmake 는 ESP-IDF의 빌드 시스템과 관련된 모든 핵심 함수와 매크로가 정의된 파일의 경로입니다.
project.cmake 파일에는 다음과 같은 중요한 내용이 담겨 있습니다.
결론적으로, 이 명령어는 ESP-IDF가 제공하는 특별한 CMake 빌드 기능을 가져와 프로젝트에 적용하는 역할을 합니다. 이 한 줄이 없으면 ESP-IDF의 편리한 빌드 시스템을 전혀 사용할 수 없게 됩니다.
덕분에 사용자는 간단한 CMakeLists.txt만 작성해도 복잡한 빌드 환경을 쉽게 사용할 수 있습니다.
🤔 CMake의 의존성 관리
REQUIRES는 ESP-IDF의 idf_component_register() 함수에서 사용되며, 한 컴포넌트가 다른 컴포넌트에 의존함을 선언하는 데 사용됩니다. 이 의존성은 CMake가 빌드 과정을 자동화하고 종속성 문제를 해결하는 핵심 메커니즘입니다.
아래 예시는 main 컴포넌트가 driver와 freertos 컴포넌트에 의존함을 CMake에게 알려줍니다.
idf_component_register(
SRCS "main.c"
REQUIRES "driver" "freertos"
)
CMake는 프로젝트의 모든 CMakeLists.txt 파일을 분석하여 종속성 트리를 구축합니다. 이 트리는 "A는 B를 필요로 하고, B는 C를 필요로 한다"와 같은 관계를 파악합니다. REQUIRES를 통해 명시된 모든 컴포넌트와 그들의 종속성을 재귀적으로 탐색하여 최종적인 컴포넌트 목록을 확정합니다.
종속성이 선언되면, CMake는 의존하는 컴포넌트의 헤더 파일 경로를 자동으로 컴파일러에 전달합니다. 따라서, main 컴포넌트의 소스 코드에서 <driver/gpio.h>와 같은 헤더 파일을 #include해도 컴파일러가 해당 파일을 찾을 수 있습니다.
🤔 근데 Blink 예시에서 mian 폴더의 CMakeLists.txt 파일을 보면 REQUIRES 이 없습니다. 그런데 어떻게 main.c 에서 driver/gpio, esp_log 등을 불러와서 사용할 수 있는 걸까요?
idf_component_register(SRCS "blink_example_main.c"
INCLUDE_DIRS ".")
main 컴포넌트는 사실상 루트 실행 대상이라, 특별히 의존성을 안 적어도 빌드 시스템이 기본 컴포넌트 세트를 자동으로 연결해줍니다.
ESP-IDF는 project() 호출 시, 내부적으로 freertos, esp_system, esp_common, esp_event, driver, esp_log 등 기본 컴포넌트를 항상 로드합니다. 즉, gpio.h (driver 컴포넌트)나 esp_log.h (log 컴포넌트)는 이미 기본 dependency chain에 들어 있기 때문에, REQUIRES에 따로 안 써줘도 그냥 불러올 수 있는 거예요.
ESP-IDF 컴포넌트 시스템은 재사용 가능한 코드 블록을 체계적으로 구성하고 관리하는 핵심적인 빌드 개념입니다. 각 컴포넌트는 자체 소스 코드, 헤더 파일, 그리고 컴포넌트의 빌드 방법을 정의하는 CMakeLists.txt 파일을 포함하는 독립적인 디렉터리입니다.
프로젝트/
├── main/ # 메인 애플리케이션 컴포넌트
│ ├── CMakeLists.txt # 컴포넌트 설정
│ └── blink_example_main.c
├── components/ # 사용자 정의 컴포넌트들
│ ├── my_sensor/
│ │ ├── CMakeLists.txt
│ │ ├── include/
│ │ │ └── my_sensor.h
│ │ └── my_sensor.c
│ └── my_display/
│ ├── CMakeLists.txt
│ └── my_display.c
└── managed_components/ # 의존성 관리자로 설치된 컴포넌트 (외부 컴포넌트)
└── espressif__led_strip/
그리고 ESP-IDF 내장 컴포넌트도 있습니다.
esp-idf/components/
├── esp_driver_gpio/ # GPIO 드라이버
├── esp_common/ # 공통 라이브러리
├── freertos/ # FreeRTOS 운영체제
├── newlib/ # C 표준 라이브러리
├── esp_wifi/ # Wi-Fi 스택
├── esp_netif/ # 네트워크 인터페이스
└── ...
외부 컴포넌트(managed_components)도 있습니다.
# main/idf_component.yml
dependencies:
espressif/led_strip: "^2.4.1" # 자동 다운로드
ESP-IDF는 다음 순서로 컴포넌트를 찾습니다:
✍️ 이처럼 ESP-IDF 컴포넌트 시스템은 프로젝트 코드를 논리적인 모듈로 나누어 재사용성과 유지보수성을 크게 향상시킵니다.
하단 작업표시줄에 빌드 아이콘을 선택해서 빌드를 진행해보겠습니다. 빌드는 말그대로 소스 코드를 ESP32가 실행할 수 있는 기계어로 변환하는 과정입니다. (컴파일, 링크, 최적화, 바이너리 생성)
*.c, *.cpp)을 컴파일하여 개별 라이브러리 파일(*.a)을 생성합니다.🤔 Ninja 가 뭐지?
Ninja는 빠르고 효율적인 빌드 시스템으로, CMake와 같은 상위 빌드 시스템에서 생성된 빌드 파일을 실행하는 데 최적화되어 있습니다. ESP-IDF 빌드 과정에서 CMake가 프로젝트를 구성하고 빌드 스크립트를 생성하면, Ninja가 이 스크립트를 실제로 실행하여 프로젝트를 컴파일하고 링크합니다.
CMakeLists.txt → CMake → Ninja → 실제 빌드
Ninja의 주요 특징
플래시는 빌드된 결과물을 보드에 반영하는 단계입니다. 플래시 방법에는 UART, JTAG, DFU를 지원하며, 각 방법은 사용 편의성, 속도, 디버깅 기능 등에서 차이가 있습니다.
1. UART (시리얼 통신) 플래싱
가장 일반적이고 기본적인 플래싱 방법입니다. USB-UART 변환 칩(예: CP210x, FTDI)이 내장된 개발 보드를 사용합니다. idf.py flash 명령어를 실행하면, esptool.py가 빌드된 펌웨어를 시리얼 포트를 통해 칩에 업로드합니다.
대부분의 최신 ESP32 보드는 자동 플래싱 회로를 내장하여 사용자가 부트 버튼을 누를 필요 없이 자동으로 플래싱 모드로 진입합니다.
별도의 하드웨어 없이 USB 케이블만으로 플래싱이 가능하다는 장점이 있습니다. 다만 다른 방법에 비해 속도가 느릴 수 있꼬, 플래싱 후 칩의 동작을 직접 디버깅할 수 없습니다.
2. JTAG 플래싱
JTAG 디버그 어댑터를 사용하여 펌웨어를 플래시하고, 하드웨어 디버깅을 수행합니다. OpenOCD와 JTAG 디버거(예: ESP-PROG)를 사용하여 플래시합니다. idf.py flash 또는 openocd 명령어를 직접 사용합니다.
중단점(breakpoint) 설정, 변수 검사 등 고급 디버깅 기능이 가능하며, 시리얼 통신보다 안정적인 플래싱을 제공합니다. 다만, 전용 JTAG 어댑터(예: J-Link, ESP-Prog)가 필요하고 OpenOCD를 설정하는 과정이 필요합니다.
3. DFU (Device Firmware Update) 플래싱
USB-OTG 기능을 지원하는 칩(ESP32-S2, ESP32-S3)에서 USB 케이블을 통해 직접 펌웨어를 업데이트하는 방식입니다. idf.py dfu-flash 명령을 사용하며, 칩의 USB-OTG 포트에 직접 연결합니다.
UART보다 훨씬 빠른 속도로 플래싱이 가능하며, 별도의 USB-UART 변환 칩이 필요 없습니다. 다만 ESP32-S2, ESP32-S3 등 특정 칩에서만 지원되며 플래시 암호화(flash encryption) 또는 보안 부팅(secure boot)이 활성화된 경우 DFU 기능이 비활성화될 수 있습니다.
저는 UART (시리얼 통신)를 선택해서 펌웨어 플래시를 진행하겠습니다. 그리고 시리얼 포트랑 타켓 보드를 잘 지정했는지 확인합니다. 작업 표시줄에 이렇게 있고, 변경이 가능합니다.

모니터는 ESP32에서 실행 중인 프로그램의 출력을 실시간으로 확인하는 도구입니다. printf 출력, 로그 메시지, 에러 메시지, 디버그 정보를 볼 수 있습니다.
LED 깜빡임 실행, GPIO 설정 성공, LED 깜빡임 패턴이 정확히 출력되는 것을 알 수 있습니다.

참고로, 빌드, 플래시 디바이스, 모니터 디바이스를 한번에 실행할 수 있는 명령이 있는데 다음과 같습니다.
