서론
Embedded 분야는 기술과 디바이스의 발전과 함께 적용 범위를 지속적으로 넓혀가고 있습니다.
과거에는 단순한 제어 중심이었던 Embedded 시스템이 이제는 Network 연결은 물론, High Level Computing 기능까지 요구받는 시대로 발전하고 있습니다.
이와 함께 시스템의 규모와 복잡성이 증가하면서 운영체제(OS)의 중요성도 점점 커지고 있습니다.
이전 시리즈에서는 이러한 요구를 충족하기 위한 대표적인 RTOS인 FreeRTOS를 중심으로 Task, Semaphore, Queue, Mutex 등의 핵심 기능을 실습해 보았습니다.
이번 시리즈에서는 FreeRTOS보다 한 단계 확장된 개념을 제공하는 Zephyr RTOS를 다루어 보겠습니다. Zephyr는 RTOS 기능뿐만 아니라 DeviceTree, Driver Framework, Build System 등을 포함하는 완전한 Embedded Software Platform으로, 다양한 MCU와 SoC를 하나의 프레임워크에서 개발할 수 있도록 지원합니다.
첫 번째 실습에서는 STM32F103 기반의 Custom Board를 직접 생성하고 Zephyr를 수동으로 포팅하여 LED Blink 프로젝트를 구현하는 과정을 살펴보겠습니다. 이를 통해 Zephyr 프로젝트의 기본 구조와 Build 과정을 이해하는 것이 이번 글의 목표입니다.
Zephyr RTOS 소개
Zephyr RTOS는 Linux Foundation이 주관하는 오픈소스 실시간 운영체제(RTOS)입니다. 다양한 MCU와 SoC를 지원하며, 단순한 RTOS를 넘어 Embedded Software Platform으로 발전하고 있습니다.
Zephyr는 ARM Cortex-M 계열뿐만 아니라 RISC-V, Xtensa 등 다양한 아키텍처를 지원하며, STM32, NXP, Nordic, Espressif 등 여러 반도체 제조사의 개발 보드를 하나의 프레임워크에서 개발할 수 있도록 설계되어 있습니다.
기존의 RTOS는 주로 Task, Semaphore, Queue와 같은 실시간 기능을 제공하는 데 중점을 두었다면, Zephyr는 여기에 DeviceTree(DTS), Driver Framework, Kconfig, CMake 기반 빌드 시스템(Build System) 등을 함께 제공하여 프로젝트 전체를 체계적으로 관리할 수 있도록 지원합니다.
또한 UART, SPI, I2C, ADC, PWM과 같은 다양한 주변장치 드라이버를 공통 API로 제공하므로, MCU가 변경되더라도 애플리케이션 코드를 크게 수정하지 않고 재사용할 수 있다는 장점이 있습니다.
Zephyr의 주요 특징
- Linux Foundation이 주관하는 오픈소스 RTOS
- 다양한 MCU와 SoC 지원
- DeviceTree 기반 하드웨어 구성
- Kconfig를 이용한 기능 선택
- CMake와 West 기반 Build 시스템
- 표준 Driver API(GPIO, UART, SPI, I2C, ADC 등) 제공
- Thread, Semaphore, Mutex, Message Queue 등 다양한 RTOS 기능 제공
Zephyr는 단순한 RTOS가 아니라 Embedded Software Platform이라는 점이 가장 큰 특징입니다.
Zephyr에 대한 자세한 내용은 공식 사이트에서 확인할 수 있습니다.
이번 시리즈에서는 STM32F103용 Custom Board를 직접 생성하고 Zephyr를 포팅하는 과정을 단계별로 살펴보겠습니다. 이를 통해 Zephyr의 프로젝트 구조와 Build 시스템, DeviceTree의 역할을 자연스럽게 이해하는 것을 목표로 합니다.
Toolchain 준비
Zephyr 프로젝트를 개발하기 위해서는 몇 가지 필수 도구가 필요합니다. 이번 실습에서는 아래와 같은 Toolchain을 사용하였습니다.
| Tool | 역할 |
|---|---|
| West | Zephyr 프로젝트 관리 및 Build 도구 프로젝트 생성, Build, Flash 등을 수행하는 Zephyr 명령행 도구 |
| CMake | Build 설정 생성 및 프로젝트 구성 |
| Python | West 및 Zephyr Build 스크립트 실행 |
| Ninja | 실제 프로젝트 Build 수행 |
| STM32CubeProgrammer | STM32 MCU에 프로그램 다운로드 |
| Visual Studio Code | 소스 코드 편집(Source Code Editor) |
Zephyr는 하나의 IDE에 의존하지 않습니다. 각 도구는 서로 역할을 분담하며, West가 이들을 하나의 개발 환경으로 통합하여 사용합니다.
위 도구들은 Zephyr 프로젝트를 개발하기 위한 기본 개발 환경이며, Zephyr SDK는 별도로 설치 및 확인 과정이 필요하므로 다음 절에서 자세히 살펴보겠습니다.
각 Toolchain의 설치 과정은 운영체제와 버전에 따라 달라질 수 있으므로 이번 글에서는 자세히 다루지 않겠습니다. 최신 설치 방법은 Zephyr 공식 사이트의 Getting Started Guide를 참고하시기 바랍니다.
Zephyr 공식 사이트의 Getting Started Guide에는 Toolchain 설치부터 개발 환경 구성까지 자세히 설명되어 있습니다.
아래는 Visual Studio Code의 Powershell로 확인한 필자의 컴퓨터에 설치된 Toolchain입니다.

현재 개발 환경은 Zephyr 공식 사이트의 Getting Started Guide에서 제시하는 최소 Toolchain 요구 사항을 모두 만족합니다.

DeviceTree Compiler(DTC)는 Zephyr Build 과정에서 내부적으로 자동 실행되므로 별도의 확인 과정은 생략하였습니다. 프로젝트가 정상적으로 Build된다면 DTC 역시 정상적으로 설치되어 동작하는 것으로 볼 수 있습니다.
기본 Toolchain 준비가 완료되었다면, 다음은 이번 글의 핵심인 Zephyr SDK를 설치하고 정상적으로 동작하는지 확인해 보겠습니다.
Zephyr SDK 설치
이제 Zephyr SDK를 다운로드하여 설치해 보겠습니다.
이 장에서는 7-Zip 설치, Zephyr SDK 다운로드, SDK 설치까지의 과정을 차례대로 진행합니다.
SDK가 정상적으로 설치되었는지는 다음 장에서 Workspace를 구성한 후 실제 Build를 수행하면서 확인하겠습니다.
7-Zip 설치
Zephyr SDK는 압축 파일(Archive) 형태로 제공됩니다. 따라서 설치를 진행하기 전에 압축을 해제할 수 있는 프로그램이 필요합니다.
Windows에서는 다양한 압축 프로그램을 사용할 수 있지만, Zephyr 공식 문서에서도 많이 사용되는 7-Zip을 사용하는 것을 권장합니다.
7-Zip 프로그램은 아래 링크에서 다운로드 할 수 있습니다.
운영체제에 맞는 버전을 선택하여 다운로드합니다. 대부분의 PC에서는 64-bit Windows x64 버전을 선택하면 됩니다.

설치 파일을 실행한 후 Install 버튼을 클릭하면 기본 설정으로 설치가 진행됩니다. 특별히 변경해야 할 옵션은 없으며, 설치가 완료되면 Close 버튼을 눌러 종료합니다.
설치가 완료되면 Zephyr SDK 설치 프로그램이 7-Zip을 사용할 수 있도록 7-Zip 실행 파일이 시스템의 PATH 환경 변수에 등록되어 있어야 합니다.
PATH에 등록되어 있지 않으면 Zephyr SDK 설치 과정에서 7-Zip을 실행할 수 없어 설치가 정상적으로 진행되지 않을 수 있습니다.
Windows에서 PATH 환경 변수는 다음 순서로 접근할 수 있습니다.
제어판 → 시스템 → 고급 시스템 설정 → 환경 변수
또는
설정 → 시스템 → 정보 → 고급 시스템 설정 → 환경 변수
환경 변수 편집 창에서 Path를 선택한 후 편집(Edit) 버튼을 클릭합니다.
7-Zip이 설치된 폴더(예: C:\Program Files\7-Zip)가 목록에 등록되어 있는지 확인합니다. 등록되어 있지 않다면 새로 만들기(New)를 클릭하여 해당 경로를 추가한 후 확인을 눌러 저장합니다.

<참고>
최신 버전의 7-Zip은 설치 시 PATH 환경 변수가 자동으로 등록되는 경우가 많습니다.
그러나 설치 환경에 따라 자동으로 등록되지 않을 수도 있으므로, Zephyr SDK를 설치하기 전에 한 번 확인해 두는 것이 좋습니다.
이제 7-Zip 설치와 환경 변수 설정이 완료되었습니다. 다음으로 Zephyr SDK를 다운로드하여 설치해 보겠습니다.
Zephyr SDK 다운로드
이제 Zephyr SDK를 다운로드해 보겠습니다.
Zephyr SDK는 Zephyr 공식 홈페이지에서 설치 방법을 안내하고 있으며, 실제 SDK 파일은 GitHub Releases를 통해 배포됩니다.
아래 링크를 클릭하여 최신 SDK Release 페이지로 이동합니다.


다운로드 할 파일 이름은 zephyr-sdk-1.0.1_windows-x86_64_gnu.7z 입니다.
SDK Bundle은 Minimal, GNU, LLVM의 세 가지 버전으로 제공됩니다. STM32를 비롯한 대부분의 Zephyr 예제에서는 GNU Toolchain을 사용하므로, 이 글에서도 GNU 버전을 기준으로 설명하겠습니다.
다운로드가 완료되면 다음 장에서 Zephyr SDK를 설치하고, 정상적으로 설치되었는지 확인하는 과정까지 살펴보겠습니다.
다운로드한 SDK 파일은 관리하기 쉽도록 Zephyr 작업 폴더에 보관하는 것을 권장합니다.
필자는 다음과 같이 Zephyr 전용 폴더를 구성하였습니다.

다운로드한 zephyr-sdk-1.0.1_windows-x86_64_gnu.7z 파일을 Zephyr 폴더 아래에 압축 해제하면 그림과 같이 zephyr-sdk-1.0.1 폴더가 생성됩니다. 이 폴더에는 Zephyr SDK 설치에 필요한 파일들이 포함되어 있습니다.
<참고>
본 글의 화면은 설치 과정을 처음부터 다시 재현하기 위해 별도의
Zephyr_Test폴더에서 캡처하였습니다. 실제 개발 환경에서는 **D:\Zephyr**와 같은 작업 폴더를 사용하여 동일한 방법으로 진행하면 됩니다.
SDK 설치
압축을 해제한 zephyr-sdk-1.0.1 폴더를 열면 다음과 같은 파일을 확인할 수 있습니다.
- setup.cmd
- setup.ps1
- sdk_toolchain_install.bat
- 기타 SDK 구성 파일
Windows에서는 setup.cmd를 실행하여 Zephyr SDK를 설치합니다.

위 화면에서 실행을 누르면 Command 화면이 나타나면서 선택을 하는 단계를 진행합니다.
아래와 같이 선택을 하면 됩니다.
| 항목 | 선택 | 설명 |
|---|---|---|
| Install GNU toolchain | Y | STM32 등 대부분의 Zephyr 프로젝트에서 사용하는 GCC Toolchain |
| Install LLVM toolchain | N | Clang 기반 Toolchain으로 이번 실습에서는 사용하지 않음 |
| Install host tools | Y | Zephyr 빌드에 필요한 Host Tools 설치 |
| Register Zephyr SDK CMake package | Y | CMake에서 Zephyr SDK를 자동으로 찾을 수 있도록 등록 |
아래 화면은 설치가 완료된 화면입니다.

설치 과정에서 다음과 같은 메시지가 출력됩니다.
SKIPPED: Windows host tools are not available yet.
이는 오류가 아니라 정상적인 동작입니다.
현재 Windows용 Host Tools는 SDK에 포함되어 있지 않으므로 설치 과정에서 자동으로 건너뛰게 됩니다.
따라서 위 메시지가 출력되어도 정상적으로 설치가 완료된 것이므로 걱정하지 않아도 됩니다.
모든 설치가 완료되면 Press any key to exit… 메시지가 출력됩니다.
아무 키나 눌러 설치를 종료합니다.
이제 Zephyr SDK 설치가 완료되었습니다.
Zephyr SDK 설치는 완료되었지만, SDK가 Zephyr 개발 환경에서 정상적으로 인식되는지는 실제 프로젝트를 빌드해 보아야 확인할 수 있습니다.
다음 장에서는 Workspace를 구성한 후 Zephyr 프로젝트를 Build하면서 SDK가 정상적으로 인식되는지 함께 확인해 보겠습니다.
Workspace 구성
Zephyr 작업 폴더 준비
Zephyr SDK 설치가 완료되었으므로 이제 Zephyr 프로젝트를 관리하기 위한 Workspace를 구성해 보겠습니다.
Zephyr는 일반적인 IDE처럼 프로젝트 하나만 생성하여 개발하는 구조가 아닙니다. Zephyr 소스 코드, 사용자 프로젝트, Custom Board 등을 하나의 Workspace에서 함께 관리하는 방식을 사용합니다.
따라서 Workspace를 체계적으로 구성해 두면 여러 프로젝트를 쉽게 관리할 수 있으며, Zephyr 버전이 변경되더라도 사용자 프로젝트를 독립적으로 유지할 수 있습니다.
필자는 다음과 같은 구조로 Workspace를 구성하였습니다.

위 그림은 Zephyr 개발 환경의 최상위 폴더입니다.
- doc : 관련 문서 및 참고 자료
- workspace : Zephyr 소스와 사용자 프로젝트를 관리하는 작업 공간
- zephyr-sdk-1.0.1 : 설치된 Zephyr SDK
- 7z2602-x64.exe : 7-Zip 설치 프로그램
- zephyr-sdk-1.0.1_windows-x86_64_gnu.7z : 다운로드한 SDK 압축 파일
이와 같이 개발 환경을 구성하면 프로젝트와 SDK를 체계적으로 관리할 수 있으며, 새로운 개발 환경을 구축하거나 다른 PC로 작업 환경을 옮길 때도 편리합니다.
이제 이 작업 폴더를 기준으로 Workspace를 생성해 보겠습니다.
Workspace 생성
Workspace는 west init workspace 명령으로 생성되며, 이후 west update 명령을 실행하면 GitHub에서 Zephyr Kernel과 Module 등 필요한 Source Code가 다운로드됩니다.
아래 그림은 Zephyr SDK와 Workspace가 어떻게 구성되고, Build 과정에서 서로 어떤 역할을 하는지를 나타낸 것입니다.

Zephyr 개발 환경은 크게 Zephyr SDK와 Workspace의 두 구성 요소로 이루어집니다.
- Zephyr SDK는 GCC 컴파일러와 Linker 등 빌드에 필요한 Toolchain을 제공합니다.
- Workspace는 Zephyr Kernel, Driver, Module, 사용자 프로젝트 등의 Source Code를 관리합니다.
west init과west update는 GitHub에서 Zephyr Source Code를 Workspace로 다운로드합니다.west build는 Workspace의 Source Code와 Zephyr SDK의 Toolchain를 사용하여 Firmware(ELF/HEX)를 생성합니다.- 생성된 Firmware는 STM32와 같은 Target MCU에 다운로드되어 실행됩니다.
다음은 west init workspace 명령을 실행한 화면입니다. 필자는 Visual Studio Code의 PowerShell 터미널에서 명령을 실행하였습니다.

west init workspace 명령을 실행하면 workspace 폴더가 생성되고, Zephyr 프로젝트를 관리하기 위한 Manifest Repository가 초기화됩니다.
또한 화면 마지막에는 다음과 같은 메시지가 출력됩니다.
Now run "west update" inside D:\Zephyr_Test\workspace.
이는 Workspace 생성이 완료되었으며, 다음 단계에서 west update 명령을 실행하여 Zephyr Kernel과 Module 등 개발에 필요한 Source Code를 다운로드하라는 의미입니다.
먼저 생성된 workspace 폴더로 이동한 후 west update 명령을 실행합니다.
Zephyr 소스를 포함한 각종 파일들이 다운로드 되는 것을 볼 수 있습니다.
인터넷 환경에 따라 다르지만 처음 실행하는 경우에는 다운로드에 수 분에서 10분 정도 소요될 수 있습니다.
west update가 완료되면 workspace 폴더에는 아래와 같이 Zephyr 개발에 필요한 Repository가 생성됩니다.

각 폴더들의 역할은 다음과 같습니다.
- .west : Workspace 정보를 관리하는 설정 폴더
- zephyr : Zephyr Kernel과 기본 소스 코드
- modules : Zephyr에서 사용하는 다양한 Module
- bootloader : MCUboot 등 Bootloader 관련 소스
- tools : 개발에 필요한 각종 도구
다음으로 Zephyr에서 사용하는 Python 패키지를 설치합니다.
![]()
이 명령은 Zephyr Build, DeviceTree 처리, Flash 등에서 사용하는 Python 라이브러리를 설치하는 과정입니다. 최초 한 번만 수행하면 되며, 이후에는 별도로 다시 실행할 필요가 없습니다.
이제 Zephyr 개발에 필요한 기본 Workspace 구성이 완료되었습니다.
다음 장에서는 Custom Board를 생성하고 Zephyr에 등록하는 방법을 살펴보겠습니다.
Custom Board 생성
지금까지 Zephyr 개발 환경을 구축하고 Workspace를 생성하였습니다.
이번 실습에서는 STM32F103VE가 탑재된 범용 개발보드를 사용합니다. 이 보드는 AliExpress에서 쉽게 구입할 수 있는 저렴한 개발보드이지만, Zephyr에서 기본적으로 지원하는 공식 Board 목록에는 포함되어 있지 않습니다.
따라서 이 보드를 사용하기 위해서는 Zephyr에 새로운 Board를 등록해야 합니다. 이러한 과정을 Custom Board 생성이라고 하며, 실제 제품 개발에서도 가장 많이 사용하는 방법입니다.
즉, Custom Board는 반드시 직접 설계한 PCB만을 의미하는 것이 아니라, Zephyr에서 기본적으로 지원하지 않는 보드를 등록하여 사용하는 모든 경우를 포함합니다.
아래는 이번 실습에서 사용할 보드의 사진 입니다.

다음 단계에서는 이 보드를 Zephyr에서 사용할 수 있도록 Workspace에 새로운 Board를 추가하고, Board 이름과 폴더 구조를 구성해 보겠습니다.
이번에 생성하는 Custom Board는 이후 직접 설계한 STM32 보드에도 동일한 방법으로 적용할 수 있습니다.
먼저 workspace 아래에 사용자 보드와 프로젝트를 관리할 폴더를 생성합니다.

새로 생성한 my_projects 폴더에는 사용자가 작성하는 Application Project를 저장합니다.
또한 my_boards 폴더에는 사용자가 추가하는 Custom Board의 정보가 저장됩니다. Zephyr는 이 폴더에 등록된 Board 정보를 읽어 사용자 보드를 인식하게 됩니다.
my_boards의 폴더에 아래 그림과 같이 하위 폴더를 만듭니다.

Zephyr는 boards 폴더 아래에서 사용자 Board를 검색합니다. 따라서 my_boards 아래에 boards 폴더를 만들고, 그 안에 my_f103ve 폴더를 생성합니다.
Board 폴더 이름은 Zephyr에 등록할 Board 이름과 동일하게 지정하는 것이 좋습니다. 본 실습에서는 my_f103ve를 Board 이름으로 사용합니다.
먼저 Board를 등록하기 위한 기본 파일을 생성합니다. 이후 DeviceTree 설정 단계에서 필요한 파일을 추가하여 보드 구성을 완성하게 됩니다.
- board.yml : Board의 기본 정보를 정의하는 파일
- Kconfig.my_f103ve : Board 전용 Kconfig 설정
- my_f103ve_defconfig : Board의 기본 Configuration
우선 위 세 개의 빈 파일을 my_f103ve 폴더에 생성합니다. 각 파일의 내용은 다음 단계에서 하나씩 작성하겠습니다.
먼저 board.yml 파일을 작성합니다.
board.yml 은 Zephyr에 새로운 Board를 등록하기 위한 기본 정보를 정의하는 파일입니다.
이 파일에는 Board 이름(name), 표시 이름(full_name), 그리고 이 Board에서 사용하는 MCU(SoC) 정보를 정의합니다.

- name : Zephyr에서 사용할 Board 이름
- full_name : 사람이 읽기 쉬운 Board 이름
- socs : 이 Board에서 사용하는 MCU(SoC) 정보
다음은 kconfig.my_f103ve 파일을 작성 합니다.
이 파일은 Zephyr에서 my_f103ve Board가 선택되었을 때 사용할 SoC(System on Chip)를 지정하는 역할을 합니다.
아래와 같이 작성합니다.

각 항목의 의미는 다음과 같습니다.
- BOARD_MY_F103VE :
my_f103veBoard를 선택하기 위한 Kconfig 항목입니다. - bool : Kconfig 메뉴에 표시될 Board 이름입니다.
- SOC_SERIES_STM32F1X : STM32F1 시리즈를 선택합니다.
- SOC_STM32F103XE : STM32F103XE MCU를 사용하도록 지정합니다.
즉, my_f103ve Board가 선택되면 STM32F103XE MCU를 사용하는 STM32F1 시리즈 Board로 동작하게 됩니다.
마지막으로 my_f103ve_defconfig 파일을 작성 합니다.
my_f103ve_defconfig는 이 Board의 기본 Kconfig 설정을 저장하는 파일입니다. 하지만 현재 단계에서는 아직 추가할 설정이 없으므로 아래와 같이 주석만 입력해 둡니다.

현재는 비어 있지만, 이후 Clock 설정이나 Console 설정 등 Board 기본 옵션이 필요하면 이 파일에 추가하게 됩니다.
이제 Custom Board를 등록하기 위한 기본 파일 준비가 완료되었습니다. 다음 단계에서는 Zephyr가 이 Board를 실제로 인식할 수 있도록 Board 등록을 확인해 보겠습니다.
Visual Studio Code의 Powershell에서 명령어를 입력하면 등록한 보드의 이름이 나타나게 됩니다. 이 이름이 나타나면 Zephyr에서 정상적으로 등록이 되었다는 것을 의미 합니다.
아래는 입력할 west 명령어 입니다.
west boards –board-root=D:\Zephyr\workspace\my_boards | findstr my_f103ve
--board-root 옵션은 Zephyr 기본 Board 이외에 사용자가 만든 Board를 검색할 위치를 지정합니다.

이 단계에서는 아직 MCU의 메모리, 클럭, LED 핀과 같은 하드웨어 정보가 모두 설정된 것은 아닙니다. 다만 Zephyr가 my_f103ve라는 Board 이름과 기본 등록 정보를 인식하는 상태까지 확인한 것입니다.
지금까지는 Zephyr가 새로운 Board의 존재를 인식하도록 등록하는 과정이었습니다.
다음 장에서는 DeviceTree(DTS)를 작성하여 이 보드의 실제 하드웨어 구성을 정의하겠습니다.
DeviceTree(DTS) 및 보드 설정
앞 장에서는 my_f103ve Custom Board를 Zephyr에 등록하고, Zephyr가 새로운 Board를 정상적으로 인식하는 것까지 확인하였습니다.
하지만 아직은 Board 이름만 등록된 상태일 뿐입니다. Zephyr는 아직 이 보드의 Flash 크기, SRAM 크기, 시스템 클럭, LED 연결 핀과 같은 실제 하드웨어 정보를 알지 못합니다.
이러한 하드웨어 정보를 정의하는 것이 DeviceTree(DTS) 입니다.
이번 장에서는 my_f103ve.dts와 관련 설정 파일을 작성하여 Zephyr가 이 보드의 하드웨어 구성을 이해할 수 있도록 하겠습니다.
DeviceTree는 DTS, DTSI, Overlay 3개의 파일로 구성됩니다.
아래는 이 파일들이 어떻게 연관이 되는지 설명하는 그림입니다.’

위 그림은 Zephyr에서 DeviceTree가 처리되는 전체 과정을 나타낸 것입니다.
- Board DTS(
my_f103ve.dts)에는 사용자가 정의하는 보드의 하드웨어 정보가 들어갑니다. - SoC Include(
stm32f103Xe.dtsi)에는 STM32F103XE MCU에 대한 공통 정보가 정의되어 있습니다. - Application Overlay(
app.overlay)는 프로젝트별로 DeviceTree를 수정하거나 추가할 때 사용하는 파일입니다. - Build 과정에서 DeviceTree Compiler(DTC)가 이 파일들을 하나로 결합하여 처리합니다.
- 최종적으로
devicetree_generated.h가 생성되며, Application과 Driver는 이 파일을 통해 하드웨어 정보를 사용하게 됩니다.
따라서 개발자는 직접 DeviceTree를 해석할 필요 없이, Zephyr가 생성한 정보를 API를 통해 사용할 수 있습니다.
이번 실습에서는 my_f103ve.dts만 직접 작성합니다. stm32f103Xe.dtsi는 Zephyr에서 이미 제공하는 파일이며, app.overlay는 이번 LED Blinking 실습에서는 사용하지 않습니다.
참고로 위 그림은 아래 자료를 참고하여 본 글의 설명에 맞게 재구성하였습니다.
How Zephyr RTOS Builds and Uses the Devicetree
이제 my_f103v.dts를 작성해 보겠습니다.
가장 먼저 해야 할 일은 SoC에 대한 기본 정보를 포함하고 있는 DTSI(DeviceTree Include) 파일을 include하는 것입니다.
stm32f103Xe.dtsi에는 STM32F103XE MCU의 CPU, Flash, SRAM, GPIO, UART, Timer 등 공통 하드웨어 정보가 이미 정의되어 있습니다.
따라서 Custom Board에서는 이 파일을 그대로 사용하고, 보드마다 달라지는 LED, Clock, Pin 설정 등만 추가로 정의하면 됩니다.

위 그림에서 세가지 파일을 include 하고 있습니다. 각 파일의 설명을 아래와 같습니다.
#include <st/f1/stm32f103Xe.dtsi> 는 STM32F103XE MCU의 공통 DeviceTree 정보를 포함합니다.
#include <zephyr/dt-bindings/gpio/gpio.h> 는 GPIO 관련 상수(GPIO_ACTIVE_LOW, GPIO_ACTIVE_HIGH 등)를 사용하기 위한 헤더입니다.
#include <zephyr/dt-bindings/pinctrl/stm32f1-pinctrl.h> 는 STM32F1의 Pin Control 설정을 위해 사용하는 헤더입니다.
Root Node 설정
다음은 DeviceTree의 최상위 노드(Root Node)를 작성합니다.

Device Tree의 Root Node는 위 그림처럼 Node Property와 Child Node로 구성됩니다.
Root Node 밖에도 Node를 작성할 수 있지만, 이들은 새로운 Node를 정의하는 것이 아니라 이미 존재하는 Node를 참조하여 수정(Override)하는 역할을 합니다. 아래와 같이 구분할 수 있습니다.
DeviceTree에서 Root Node(/) 안에는 이 Board 고유의 정보를 정의합니다. 예를 들어 model, chosen, leds, aliases 등이 이에 해당합니다.
반면 Root Node 밖의 &usart1, &rcc, &pll과 같은 Node는 stm32f103Xe.dtsi에 이미 정의되어 있는 MCU의 공통 Node를 참조하여 수정하는 것입니다.
즉, Root Node 안은 Board를 정의하는 공간이고, Root Node 밖은 MCU의 공통 하드웨어를 Board에 맞게 설정하는 공간이라고 이해하면 됩니다.
현재 Root Node에는 두 개의 Property가 정의되어 있습니다.
각 property의 설명은 다음과 같습니다.
model은 사람이 읽기 쉬운 Board 이름이고, compatible은 Zephyr와 Driver가 Board를 식별하기 위한 문자열입니다.
현재 Root Node에는 chosen, leds, aliases의 세 개 Child Node가 정의되어 있습니다. 각 Child Node의 역할을 차례대로 살펴보겠습니다.
chosen Node는 Zephyr가 기본적으로 사용할 하드웨어를 지정하는 예약된(Reserved) Node입니다.
이 Node에서는 시스템에서 사용할 Flash, SRAM, Console(UART) 등 기본 장치를 선택합니다.
이번 실습에서는 Zephyr가 기본적으로 사용할 SRAM, Flash, Console(UART) 을 아래와 같이 지정합니다. 이후 Build 과정에서 Zephyr는 이 정보를 사용하여 메모리와 Console 장치를 초기화합니다.

leds Node는 Board에 연결된 LED 정보를 정의하는 Node입니다.
이 Node 안에는 LED가 연결된 GPIO 포트와 핀 번호, Active High/Low 여부, 그리고 LED 이름 등을 정의합니다.
이번 실습에서는 PB13에 연결된 LED를 led0으로 등록합니다.
Application에서는 led0이라는 이름으로 이 LED를 참조하게 됩니다.

aliases Node는 DeviceTree에서 자주 사용하는 Node에 별칭(Alias)을 부여하는 Node입니다.
Application에서는 실제 Node 이름 대신 Alias를 사용할 수 있어 코드의 이식성과 가독성이 좋아집니다.
이번 실습에서는 led0이라는 Alias를 등록하여 LED를 쉽게 참조할 수 있도록 합니다.

<참고>
DeviceTree에는
chosen,leds,aliases외에도buttons,gpio_keys,pwmleds등 다양한 Child Node를 사용할 수 있습니다. 이번 LED Blinking 실습에서는 가장 기본적인 세 가지 Child Node만 사용하며, 이후 UART, Button, PWM 등의 실습에서 다른 Child Node들도 차례대로 살펴보겠습니다.
Clock 설정
STM32F103VE 보드가 정상적으로 동작하려면 먼저 시스템 Clock을 설정해야 합니다.
stm32f103Xe.dtsi에는 Clock 관련 Node가 이미 정의되어 있으며, Custom Board에서는 이를 참조하여 필요한 설정만 수정합니다.
이번 실습에서는 다음 세 가지 Node를 설정합니다.
- &clk_hse : 외부 8MHz Crystal(HSE) 사용
- &pll : HSE를 9배로 증폭하여 72MHz 생성
- &rcc : 시스템 Clock과 Bus Clock 설정
설정 내용은 다음과 같습니다.

지금까지 DeviceTree를 작성하여 Custom Board의 기본 하드웨어 구성을 완료하였습니다. 이후 UART, SPI, I2C 등의 실습에서는 필요한 Node를 추가하면서 DeviceTree를 계속 확장해 나갈 예정입니다.
이제 DeviceTree 설정은 마쳤으므로 실제 LED를 점멸하는 Application을 작성해 보겠습니다.
LED Blinking Application 작성
지금까지 Custom Board와 DeviceTree 설정을 완료하였습니다.
이제 Zephyr GPIO API를 사용하여 PB13에 연결된 LED를 점멸하는 Application을 작성해 보겠습니다.
먼저 이번 실습에서 사용할 Application Project의 구조는 다음과 같습니다.

이번 실습에서는 LED 점멸 코드를 src/main.c에 작성합니다.
아래와 같이 main.c를 작성합니다.

코드의 내용을 하나씩 설명하겠습니다.
Header File
![]()
Zephyr Kernel API와 GPIO API를 사용하기 위한 헤더 파일입니다.
k_msleep() 함수는 kernel.h에서 제공되며, GPIO 관련 함수는 gpio.h에서 제공합니다.
DeviceTree에서 LED 정보 가져오기

앞에서 DeviceTree의 aliases Node에 등록한 led0 Alias를 이용하여 LED 정보를 가져옵니다.
GPIO_DT_SPEC_GET()은 DeviceTree에 정의된 GPIO 포트, 핀 번호, Active High/Low 정보를 하나의 구조체로 생성합니다.
따라서 Application은 GPIO 포트와 핀 번호를 직접 사용할 필요 없이 DeviceTree의 정보를 그대로 사용할 수 있습니다.
GPIO 초기화

LED가 연결된 GPIO 장치가 준비되었는지 확인한 후 출력으로 설정합니다.
초기 상태는 GPIO_OUTPUT_INACTIVE로 설정하여 LED를 끈 상태에서 시작합니다.
LED 점멸

무한 루프에서 LED를 계속 Toggle합니다.
k_msleep()은 Zephyr Kernel이 제공하는 Sleep 함수이며,
현재 실행 중인 코드를 지정한 시간(ms) 동안 일시적으로 대기시킵니다.
지금까지 LED를 점멸하는 Application을 작성하였습니다. 다음 단계에서는 이 프로젝트를 Build하여 Firmware를 생성하고 STM32 보드에 다운로드해 보겠습니다.
Build 및 Download
LED Blinking Application 작성이 완료되었으므로 프로젝트를 Build하여 Firmware를 생성해 보겠습니다.
먼저 Visual Studio Code의 PowerShell 터미널에서 해당 프로젝트 폴더로 이동합니다.
![]()
다음 명령을 실행하여 프로젝트를 빌드 합니다.
![]()
west build의 각 option에 대한 설명은 다음과 같습니다.

Build가 완료되면 터미널 마지막에 다음과 같은 메시지가 출력됩니다.

위 메시지는 프로젝트가 정상적으로 Build되었음을 의미합니다.
특히 마지막 부분에는 생성된 Firmware 파일의 위치와 대상 Board 정보가 함께 출력됩니다.
이 메시지를 통해 다음 사항을 확인할 수 있습니다.
- blinky_uart_v1_0.elf 파일이 정상적으로 생성되었습니다.
- Build 대상 Board가 my_f103ve임을 확인할 수 있습니다.
- 사용하는 MCU가 STM32F103XE임을 확인할 수 있습니다.
이제 Build가 완료되었으므로 생성된 Firmware를 STM32F103VE 보드에 Download해 보겠습니다.
Zephyr에서는 일반적으로 아래와 같이 west flash 명령으로 Firmware를 다운로드할 수 있습니다.
![]()
공식적으로 지원되는 Board에서는 west flash만으로 Build한 Firmware를 바로 다운로드할 수 있습니다.
그러나 이번 실습에서 사용하는 STM32F103VE Custom Board에서는 west flash를 정상적으로 사용할 수 없었습니다.
ST-Link를 통해 MCU는 인식되었지만 Reset 과정에서 오류가 발생하여 Firmware 다운로드가 완료되지 않았습니다.
따라서 이번 실습에서는 STM32CubeProgrammer를 사용하여 Firmware를 다운로드하였습니다.
Build가 완료되면 다음 파일이 생성됩니다.
![]()
STM32CubeProgrammer에서 위 ELF 파일을 선택한 후 Download 버튼을 눌러 Firmware를 다운로드합니다.
주의
Custom Board에서 Hardware Reset을 사용하려면 ST-Link의 NRST 핀을 MCU의 NRST 핀에 연결해야 합니다. 또한 STM32CubeProgrammer의 ST-LINK 설정에서 Reset mode를 Hardware reset으로 변경해야 Reset 버튼을 누르지 않고도 안정적으로 다운로드할 수 있습니다.
필요한 경우 Full chip erase를 먼저 수행한 후 Download하면 정상적으로 동작합니다.
이번 실습에서는 Run after programming 옵션을 선택하더라도 프로그램이 자동으로 실행되지 않았습니다. 따라서 다운로드가 완료되면 Disconnect를 클릭한 후 보드의 Reset 버튼을 눌러 프로그램을 실행하였습니다.
정상적으로 동작하면 PB13에 연결된 LED가 일정한 주기로 반복하여 점멸하는 것을 확인할 수 있습니다.
<참고>
west flash는 Zephyr에서 권장하는 다운로드 방법이며, Nucleo와 같이 공식 지원되는 Board에서는 정상적으로 사용할 수 있습니다.이번 실습에서는 Zephyr에 등록되지 않은 Custom Board를 사용하였기 때문에
west flash를 이용한 다운로드는 생략하고 STM32CubeProgrammer를 사용하였습니다.
실행 결과
Build 및 Download가 완료되면 보드를 Reset하여 프로그램을 실행합니다.
정상적으로 실행되면 PB13에 연결된 LED가 main.c에서 설정한 주기에 따라 반복해서 점멸하는 것을 확인할 수 있습니다.
이번 실습에서는 다음 코드로 LED를 제어하였습니다.

gpio_pin_toggle_dt() 함수는 LED의 현재 상태를 반전(Toggle)시키며, k_msleep(100)은 100ms 동안 대기합니다.
따라서 LED는 약 100ms 간격으로 ON/OFF를 반복하며 계속 점멸하게 됩니다.
결론
이번 실습을 통해 Zephyr 개발 환경을 구축하고, Zephyr에서 지원하지 않는 STM32F103VE 개발보드를 Custom Board로 등록하여 첫 번째 Application을 실행해 보았습니다.
특히 DeviceTree를 이용하여 Board의 하드웨어 정보를 정의하고, Zephyr GPIO API를 사용하여 LED를 제어하는 전체 과정을 직접 확인할 수 있었습니다.
이번 실습을 통해 다음 사항을 확인하였습니다.
- Custom Board가 Zephyr에 정상적으로 등록되었습니다.
- DeviceTree를 이용하여 PB13 LED 정보를 정상적으로 가져왔습니다.
- Zephyr GPIO API를 사용하여 LED를 제어할 수 있음을 확인하였습니다.
- Build한 Firmware가 STM32F103VE 보드에서 정상적으로 실행됨을 확인하였습니다.
LED가 정상적으로 점멸하였다면 Zephyr 개발 환경 구축부터 Custom Board 등록, DeviceTree 설정, GPIO Driver 설정, Application 작성까지 모든 과정이 성공적으로 완료된 것입니다.
또한 Zephyr는 Visual Studio Code Extension을 이용하여 보다 간편하게 개발 환경을 구축할 수도 있습니다. 하지만 이번 실습에서는 이러한 자동 설치 방식을 사용하지 않고, Zephyr SDK 설치부터 Workspace 구성, Custom Board 등록, Build까지 모든 과정을 직접 수행하는 수동 설치 방식을 선택하였습니다.
수동 설치는 초기 과정이 다소 복잡하지만, Zephyr의 내부 구조와 Build 과정을 이해하는 데 큰 도움이 됩니다. 특히 Workspace 구성, west 명령, DeviceTree, Custom Board의 개념을 직접 경험함으로써 이후 UART, Thread, Semaphore 등 다양한 기능을 보다 쉽게 이해할 수 있는 기반을 마련할 수 있습니다.
이번 프로젝트는 앞으로 진행할 UART, Thread, Semaphore 등 다양한 Zephyr RTOS 실습의 기본 프로젝트로 계속 확장해 나갈 예정입니다. 이 시리즈를 통해 Zephyr의 구조를 하나씩 이해하고, 최종적으로는 자신만의 Custom Board에서 다양한 기능을 구현할 수 있는 기반을 함께 만들어 가겠습니다.