AEGLOSLAB Arduino UNO Q 완전 가이드

제3부 소프트웨어 스택

3.2 Zephyr 기반 Arduino Core — MCU 측 실시간 펌웨어

제3부 소프트웨어 스택. 앞 장(3.1)이 MPU 쪽 Debian을 다뤘다면, 이 장은 MCU(STM32U585) 쪽이 스케치를 어떻게 실행하는지 다룬다. 목표는 아키텍처 이해다 — "실시간성에 실제로 어떤 영향을 주는가"라는 더 깊은 질문은 제8부 8.1(MCU의 실시간성 이해)에서 다룬다. 여기서는 그 논의를 이해하는 데 필요한 구조만 정리한다.

3.2.1 MCU도 "단순한 아두이노"가 아니다

UNO Q의 MCU는 STMicroelectronics STM32U585 — Arm Cortex-M33 코어, 최대 160 MHz, 플래시 2 MB, SRAM 786 KB다(2.1에서 하드웨어를 더 다룬다). 스케치를 업로드하고 Serial.println()을 쓰는 개발자 경험은 클래식 UNO와 크게 다르지 않다. 하지만 그 밑에서 실행되는 것은 완전히 다르다.

"the real-time responsiveness of a dedicated STM32U585 Microcontroller (MCU) running Arduino sketches over Zephyr OS" — UNO Q User Manual

클래식 UNO(ATmega328P)나 심지어 Portenta 계열이 써 온 mbed OS 기반 코어와 달리, UNO Q의 MCU 코어는 Zephyr RTOS 위에 구축된 Arduino Core다. 이 장은 그 구조를 분석한다.

3.2.2 Zephyr RTOS란 무엇인가

Zephyr Project는 Linux Foundation 산하의 오픈소스 실시간 운영체제(RTOS)다. 공식 설명을 그대로 옮기면:

"Zephyr RTOS is an open-source, real-time operating system designed for low-power, resource-constrained devices. It's modular, scalable, and supports multiple architectures." — ArduinoCore-zephyr README

몇 가지 핵심 특징:

  • 범용성: ARM Cortex-M뿐 아니라 RISC-V, x86, ARC, Xtensa 등 다양한 아키텍처와 수백 종의 보드를 지원한다. 특정 실리콘 벤더에 종속되지 않은 커뮤니티 표준 RTOS다.
  • 커널 기능: 선점형(preemptive) 우선순위 스케줄러, 스레드(태스크), 세마포어·뮤텍스·메시지 큐 같은 동기화 프리미티브, 인터럽트 처리, 전력 관리 서브시스템을 갖춘 완전한 커널이다. 마이크로컨트롤러용 "슈퍼루프 + 인터럽트"가 아니라, 여러 실행 흐름을 스케줄러가 시분할·우선순위 기반으로 실행한다.
  • 모듈성: 필요한 서브시스템만 컴파일에 포함하는 Kconfig 기반 설정(.conf 파일)을 쓴다 — 리눅스 커널의 menuconfig와 철학이 비슷하다.
  • 폭넓은 채택: Nordic, NXP, ST 등 여러 실리콘 벤더가 자사 SDK의 기반으로 Zephyr를 채택하고 있다.

3.2.3 아키텍처 — 로더(loader)와 LLEXT

여기서부터가 클래식 Arduino와 가장 크게 갈라지는 지점이다. 전통적인 Arduino는 스케치를 컴파일해 독립 실행 바이너리를 만들고, 부트로더가 그 바이너리를 플래시에 써 넣은 뒤 곧바로 실행한다. UNO Q(및 최근 Portenta/Giga 계열)의 Zephyr 기반 코어는 다르다.

"Unlike traditional Arduino implementations, where the final output is a standalone binary loaded by a bootloader, this core generates a freestanding elf file. This file is dynamically loaded by a precompiled Zephyr firmware, referred to as the loader." — ArduinoCore-zephyr README, "Under the hood"

구조를 나누면 다음과 같다.

  • 로더(loader): MCU에 미리 탑재되어 있는, 사전 컴파일된 Zephyr 기반 펌웨어. Zephyr 커널·드라이버·통신 스택(Bridge 포함, 3.4)이 모두 여기 포함된다.
  • 스케치: 로더와 별도로 컴파일되는 freestanding ELF 파일. 로더가 이 ELF를 런타임에 동적으로 적재해 실행한다.
  • LLEXT(Linkable Loadable Extensions): 스케치와 로더 사이를 잇는 "접착제" 역할을 하는 Zephyr의 공식 서브시스템(Zephyr LLEXT 문서). 리눅스의 커널 모듈 로딩과 개념적으로 비슷하다 — 미리 빌드된 커널(로더)에 별도로 빌드된 코드(스케치)를 나중에 링크·적재한다.
스케치 소스 (sketch.ino) arduino-cli 컴파일 freestanding ELF zephyr-sketch-tool 패키징 MCU로 업로드 Zephyr 로더 (사전컴파일 펌웨어) LLEXT가 ELF를 런타임에 동적 로드 스케치가 Zephyr 스레드로 실행

이 구조 덕분에 얻는 실용적 이점 하나: 스케치를 새로 업로드할 때마다 전체 펌웨어를 재플래시하지 않는다. 로더는 그대로 두고 ELF만 교체하면 되므로 업로드 부담이 작다. 대신 로더 자체 업데이트가 필요한 경우(보드를 처음 쓸 때, 혹은 다른 버전의 코어로 전환할 때)는 "Burn Bootloader" 같은 별도 절차를 한 번 거친다 — README는 이를 "release 0.90.0부터 첫 스케치 업로드 시 자동화됐다"고 밝힌다.

로더의 동작 모드는 IDE의 Mode 메뉴에서 고른다.

  • Standard: 스케치가 부팅 시 자동으로 로드·실행된다.
  • Debug: 로더가 Zephyr 셸에서 대기하고, 사용자가 기본 Serial로 접속해 sketch 명령을 입력해야 실행된다 — 초기 크래시를 진단할 때 유용하다.

3.2.4 클래식 Arduino(베어메탈)와 무엇이 다른가

"베어메탈"이라는 말은 보통 운영체제 없이 setup()/loop() 두 함수만으로 동작하는 실행 모델을 가리킨다. Zephyr 기반 코어는 API 표면(setup()/loop(), digitalWrite(), Serial 등)은 그대로 유지하면서 그 밑을 RTOS 커널로 교체했다.

구분클래식 Arduino(베어메탈)UNO Q MCU(Zephyr 기반)
실행 모델setup() 한 번 + loop() 무한 반복, 인터럽트로만 개입Zephyr 스케줄러가 여러 스레드(태스크)를 우선순위·시분할로 관리, 스케치는 그중 한 스레드
산출물독립 실행 바이너리, 부트로더가 전체를 플래시로더(사전컴파일 RTOS 펌웨어) + 스케치(freestanding ELF), LLEXT로 동적 결합
동시성없음(사용자가 직접 상태 머신으로 구현)커널 제공 스레드·세마포어·뮤텍스·메시지 큐
통신 스택라이브러리가 직접 레지스터 제어Bridge(RPC)가 로더 안의 Zephyr 스레드로 백그라운드에서 폴링(3.4)
이식성보드별 코어가 각자 구현Zephyr가 지원하는 어떤 칩·보드로도 코어를 옮기기 쉬움(HAL 추상화)
디버깅Serial 프린트 위주Zephyr 셸(Debug 모드), journalctl 격 로그, OS 크래시 리포트(USAGE FAULT 등)

가장 실질적인 차이는 "스케줄러와 태스크 개념이 존재한다"는 점이다. 클래식 아두이노에서 delay(1000)은 그 1초 동안 프로세서 전체를 멈춘다. Zephyr 커널 위에서는 원칙적으로 다른 스레드(예: Bridge의 백그라운드 RPC 스레드)가 그 사이에도 실행될 수 있는 여지가 생긴다 — 다만 사용자 스케치 코드 자체는 여전히 loop() 모델로 작성하므로, 스케치 작성자 입장에서 당장 드러나는 프로그래밍 모델의 변화는 크지 않다. 실제로 이것이 타이밍·지터에 어떤 의미를 갖는지, 즉 "이것이 실시간성에 유리한가 불리한가"는 겉보기만으로 답할 수 없는 질문이다 — RTOS 스케줄링 오버헤드, 인터럽트 지연, Bridge 스레드와의 자원 경합까지 따져야 한다. 이 절충 판단은 8.1(MCU의 실시간성 이해)에서 본격적으로 다룬다.

3.2.5 왜 전환했나 — mbed OS에서 Zephyr로

Arduino는 이전까지 Portenta 계열 등에서 ARM mbed OS 기반 코어(ArduinoCore-mbed)를 써 왔다. README는 새 Zephyr 코어의 위치를 이렇게 밝힌다.

"This core is designed to replace the mbed OS-based Arduino Core on all the devices it supported, and to provide a more modern and flexible foundation for current and future Arduino boards by allowing the use of Zephyr RTOS features and APIs."

즉 UNO Q만을 위한 일회성 선택이 아니라, Arduino 플랫폼 전체가 향후 여러 보드(Portenta H7, Portenta C33, Giga 등)에 걸쳐 표준으로 삼으려는 방향 전환의 일부다. 이 저장소 자체도 zephyrproject-rtos/arduino-core-zephyr(Zephyr 프로젝트가 관리하는 상류 코어)의 다운스트림 포크이고, Arduino IDE·CLI·App Lab 통합을 추가한 버전이라는 점도 README가 명시한다 — 즉 Arduino 자체 발명이 아니라 Zephyr 커뮤니티가 만든 "Arduino Core API for Zephyr"를 Arduino가 도입해 자사 툴체인에 맞춘 구조다.

3.2.6 코어 설치와 첫 업로드

Arduino IDE 2.x 기준 설치 경로는 Boards Manager에서 "Zephyr" 검색 → Arduino UNO Q Board(또는 범용 Arduino Zephyr Boards) 설치다. FQBN(Fully Qualified Board Name)은 arduino:zephyr:unoq다(App Lab Settings 페이지의 Device Details에서도 이 값을 확인할 수 있다).

Tools > Board > Boards Manager... 에서 "UNO Q" 검색 → Arduino UNO Q Zephyr Core 설치

검색 결과에 코어가 표시되지 않으면, Boards Manager URL을 수동으로 추가한다.

File > Preferences > Additional Boards Manager URLs:
https://downloads.arduino.cc/packages/package_zephyr_index.json

블링크 예제는 클래식 아두이노와 똑같이 보인다 — 이것이 바로 Zephyr 기반 코어가 API 호환성을 지키려 애쓴 지점이다.

void setup() {
  Serial.begin(9600);
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
  digitalWrite(LED_BUILTIN, HIGH);
  delay(1000);
  digitalWrite(LED_BUILTIN, LOW);
  delay(1000);
  Serial.println("Hello World");
}

App Lab을 쓰면 이 스케치 컴파일·업로드·(필요시 로더 갱신)까지 자동으로 처리된다(3.3·4.1). Arduino IDE로 직접 업로드할 수도 있는데, 이 경우 IDE는 MCU 쪽만 프로그램한다 — MPU(Debian) 쪽을 다루려면 App Lab이 필요하다는 점을 UNO Q User Manual이 명시적으로 경고한다.

3.2.7 엣지케이스 — 로더가 드러내는 실패 양상

Zephyr 기반이라 클래식 아두이노에서는 안 보이던 실패 모드가 몇 가지 새로 생긴다. ArduinoCore-zephyr README의 트러블슈팅 항목 중 주목할 것들:

  • <err> llext: Undefined symbol with no entry in symbol table ... — 스케치가 로더에 아직 노출되지 않은 Zephyr 함수를 호출했을 때 발생한다. llext_exports.c에 필요한 심볼을 추가하고 로더를 재빌드해야 한다 — 즉 로더가 "화이트리스트 방식"으로 스케치에 노출할 API를 정한다는 뜻이다.
  • <err> os: ***** USAGE FAULT ***** 같은 OS 크래시 — 사용자 코드의 버퍼 오버플로 등이 원인인 경우가 많다. Serial 출력이 나타나지 않는 초기 크래시는 USB-to-UART 어댑터로 직결하거나, Debug 모드로 컴파일해 Zephyr 셸에서 원인을 진단한다.
  • 메모리 부족(out of memory) — 현재 릴리스는 버그 리포트 수집을 위해 Zephyr 셸을 기본 활성화해 두고 있어, 이것이 스택을 상당히 점유한다. RAM이 부족한 보드라면 .conf 파일에서 스택 크기를 줄여야 할 수 있다.
  • Wi-Fi 모듈 통신 실패 — MCU 쪽 문제가 아니라 무선 모듈 펌웨어 문제일 수 있다(FlashFormat 예제 스케치로 복구).

이런 에러 메시지들이 클래식 아두이노와 결이 다르다는 것 자체가, "이 MCU는 이제 RTOS 위에서 동작한다"는 사실을 가장 분명하게 드러내는 지점이다.

3.2.8 다음으로

이 장은 MCU 쪽 소프트웨어 스택의 기본 구조만 다뤘다 — Zephyr가 무엇이고, 스케치가 어떻게 적재·실행되는지, 클래식 베어메탈과 무엇이 다른지. 이 기본 구조가 실제 프로젝트에서 어떤 실시간성 특성으로 이어지는지는 제8부(8.1~8.5)에서, 특히 8.1(결정론적 타이밍)와 8.2(인터럽트·타이머·타이밍 크리티컬 태스크 설계)에서 본격적으로 다룬다. MPU와 이 MCU 로더를 잇는 통신 계층(Bridge)의 내부 구조는 다음다음 장 3.4로 이어진다.