Rust 기반 임베디드 개발 소프트웨어 생태계와 마이크로컨트롤러 선택지를 살펴봅니다.
몇 년 전, ESP32 마이크로컨트롤러와 Rust를 사용해 하드웨어 프로젝트를 만들려고 시도했지만, 이상한 프로세서, 맞춤형 툴체인, 불완전한 라이브러리 때문에 경험이 너무 고통스러워서 금방 포기했습니다. 단지 "Hello World"를 실행하려고 그렇게까지 고생하기에는 인생이 너무 짧습니다.
2025년으로 빠르게 넘어오면 상황은 완전히 달라졌습니다. 가장 널리 사용되는 마이크로컨트롤러 대부분이 안정 버전 Rust를 지원하고, 임베디드 개발에 Rust를 프로덕션 환경에서 사용하는 기업도 늘어나고 있습니다. 또한 몇몇 조직과 더 넓은 커뮤니티의 꾸준한 노력 덕분에 Rust로 임베디드 프로젝트를 구축하는 일은 이제 가능할 뿐 아니라 C, Arduino IDE, MicroPython을 포함한 다른 어떤 언어 / 프레임워크보다도 더 즐거워졌습니다. 여러분의 노고에 진심으로 감사드립니다. 결과가 놀랍습니다 🙏
임베디드 소프트웨어 스택의 이러한 발전이 취미와 산업 분야의 사용 사례 모두에 가져올 변화가 정말, 정말 기대됩니다. 그래서 오늘은 Rust를 이용한 임베디드 개발의 소프트웨어 생태계를 개괄하고, 다음 연결형 식기세척기, 드론 방지 센서 또는 wifi 해킹 스마트워치를 시작하기 위해 어떤 마이크로컨트롤러를 선택해야 하는지 알려드리겠습니다.
임베디드 개발에 대한 정의는 많이 찾을 수 있지만, 특히 David O'Connor의 설명이 마음에 듭니다: 임베디드 컴퓨터의 본질.
일반적으로 임베디드 개발은 자원이 제한적이고 하드웨어와 소프트웨어가 밀접하게 결합된 특수 목적 컴퓨터용 소프트웨어를 만드는 일입니다.
요즘 마이크로컨트롤러는 RAM이 300~500 KB이고, 간혹 더 많기도 하지만 흔하지는 않으며, 250~500 MHz 사이의 주파수로 동작합니다. 이 역시 간혹 더 높지만 흔하지는 않습니다.
이는 몇 가지 의미를 가집니다. 첫째, 셸, 파일시스템 등의 추상화를 제공하는 Linux 같은 범용 운영체제(OS)를 실행할 수 없습니다.
둘째, 자원 고갈은 큰 위험이므로 동적으로 메모리를 할당하는 일은 거의 없습니다. 대신 가능한 한 스택에 할당된 변수와 아레나(풀)를 사용합니다. 이를 위해 알아야 할 가장 중요한 Rust 패키지 중 하나는 heapless입니다. 크기가 컴파일 시점에 알려지고 메모리 할당이 필요 없는 데이터 구조를 제공합니다.
대부분의 경우 USB 장치는 무엇이든 노트북에 연결하거나 분리해도 바로 작동합니다. 반면 마이크로컨트롤러는 PCB에 장착된 후에는 수명 내내 센서, 모터 등 동일한 장치에 연결됩니다.
따라서 Linux 같은 범용 운영체제(OS)를 쓸 여유가 없습니다. 자체 코드에 마이크로컨트롤러와 모든 주변 장치에 정확히 맞는 드라이버를 내장하고 연동해야 합니다.
운영체제가 없다는 것은 일반적으로 Rust의 표준 라이브러리에도 접근할 수 없다는 뜻입니다. 하지만 걱정하지 마세요. 아래에서 살펴보겠지만 삶을 편하게 해 주는 프레임워크가 있으며, 널리 쓰이는 패키지 중 상당수는 #[no_std]와 호환됩니다.
대기질을 측정하는 센서를 만들었다고 해 봅시다. 기본적으로 다음과 같은 루프입니다:
loop {
let data = measure_air_quality();
send_data(data);
sleep(5000);
}
현장 테스트 중 센서가 때때로 Wifi 네트워크에서 연결 해제된다는 사실을 알게 됩니다. 예를 들어 라우터의 전원이 꺼질 수 있습니다. 따라서 네트워크에 자동으로 다시 연결할 방법이 필요합니다. 또한 센서가 wifi에 연결되지 않았을 때 사용자에게 센서 상태를 알리도록 led가 빠르게 깜박이게 하고 싶습니다.
따라서 세 가지 서로 다른 하위 시스템(태스크)이 필요합니다. led용 루프, wifi 연결 상태를 확인하고 필요할 경우 다시 연결하는 루프, 그리고 데이터를 감지하고 처리하는 루프입니다.
바로 이 지점에서 동시성이 필요해집니다.
전통적으로 임베디드 세계는 코드 아래에서 실행되며 태스크를 관리하는 런타임인 실시간 운영체제(RTOS)로 동시성을 해결해 왔습니다. RTOS는 선점형 런타임입니다. 태스크는 스레드처럼 동작하며, 동적으로 할당되고 언제든 중단될 수 있습니다. 런타임은 프로세서의 여러 레지스터 값을 저장하고 다른 태스크를 실행합니다.
반면 async Rust는 아래에서 보겠지만 대부분 협력형입니다. 태스크는 코드의 각 await에서 다른 태스크가 실행되도록 양보합니다.
협력형 스케줄링과 선점형 스케줄링을 자세히 비교한 이전 글 Async Rust: 협력형 대 선점형 스케줄링을 참고하세요.
Rust 임베디드 개발을 위한 주요 async 런타임은 Embassy입니다. 마이크로컨트롤러에서 async Rust로 작업하는 데 필요한 모든 것을 제공합니다. 공통 Trait, smoltcp 기반 async 네트워크 스택, 심지어 일부 칩용 async 드라이버(HAL)도 제공합니다.
동시 코드 작성을 정말 쉽고 깔끔하게 만들어 줍니다:
static WIFI_CONNECTED: AtomicBool = AtomicBool::new(false);
#[embassy_executor::main]
fn main() {
spawn(led_controller_task);
spawn(wifi_controller_task);
while !WIFI_CONNECTED.load(Ordering::Relaxed) {
sleep(100).await;
}
loop {
let data = measure_air_quality().await;
send_data(data).await;
sleep(5000).await;
}
}
#[embassy_executor::task]
fn wifi_controller_task() {
// automatically (re-)connect to wifi and update WIFI_CONNECTED
}
#[embassy_executor::task]
fn led_controller_task() {
// manage led blinking depending on WIFI_CONNECTED
}
Embassy와 FreeRTOS를 비교한 Tweede Golf의 이 글을 꼭 읽어 보기를 권합니다. Embassy는 FreeRTOS보다 빠를 뿐 아니라 코드도 수십억 배는 더 깔끔합니다. 어쩌면 그 이상일지도 모릅니다!
ARM Cortex-M 마이크로컨트롤러를 위해 특별히 설계된 RTOS인 RTIC도 있습니다. 하지만 매크로 기반 설정은 자연스럽지도 않고 사용하기 편하지도 않다고 생각합니다.
메모리와 전력 소비가 적은 마이크로컨트롤러에서 여러 개의 동시 실행되며 서로 신뢰하지 않는 애플리케이션을 구동하는 범용 임베디드 운영체제 Tock도 있습니다.
마지막으로 보안에 중점을 둔 것으로 보이고 훌륭한 문서를 제공하는 Oxide의 Hubris가 있습니다.
다른 선택지에 시간을 낭비하지 말고 Embassy에 집중하기를 권합니다. 함께 작업하기 즐겁고, 안정 버전 Rust 및 가장 흔한 마이크로컨트롤러와 함께 작동하기 때문입니다.
임베디드 개발 경험이 있다면 Embassy가 FreeRTOS 같은 RTOS와 어떻게 비교되는지 궁금할 수 있습니다.
RTOS는 흔히 운영체제 기능, 즉 멀티태스킹을 위해 사용되지만, 때로는 RT(실시간) 기능도 필요합니다.
RT가 필요한 전형적인 사례는 다음과 같습니다. 누군가 빨간 버튼을 누르면 사람이 다치기 전에 즉시 큰 기계를 멈춰야 합니다.
이 경우 런타임은 현재 실행 중인 모든 태스크를 선점하여 중단하고, 우선순위가 높은 태스크, 즉 큰 기계를 멈추는 태스크를 즉시 실행해야 합니다.
이 GitHub 이슈에서 설명하듯이, 여러 Embassy 실행기를 만들어 서로 다른 우선순위 수준의 태스크를 생성할 수 있습니다. 필요할 때 높은 우선순위 태스크가 낮은 우선순위 태스크를 선점합니다.
Tasks scheduled on the same executor will block each other, so if one task is refusing to await/yield, the other task will never run. There is no preemption of tasks.
If you need real time, what you're looking for is to create multiple executors where you configure them to run on different priority levels and schedule high priority tasks on the high priority executor etc.
...
You spawn tasks at higher priorities, and they preempt lower priority tasks whenever they need to run (e.g. when a timer expired, when some byte arrived over an uart, etc...)
따라서 타임아웃과 기타 async 조합자를 사용하면, 엄격한 시간 경계 안에서 태스크를 선점하고 실행할 수 있으므로 Embassy는 실제로 RTOS로 분류할 수 있다고 이해하고 있습니다.
Embassy가 중요 시스템에서 실행되려면 일부 인증이 필요할 수 있지만, 인증이 요구되지 않을 때는 모든 RTOS 요구 사항을 충족할 것이라고 생각합니다.
마이크로컨트롤러에서 실행되는 소프트웨어를 펌웨어라고 합니다. 컴파일한 후에는 펌웨어를 마이크로컨트롤러에 플래시해야 합니다.
이를 위해 probe.rs를 사용하세요. 너무 훌륭한 도구라 더 설명할 필요가 없습니다. 그냥 사용하세요.
$ probe-rs run --chip [YOUR_MICROCONTROLLER_MODEL] ./my_firmware
이것으로 끝입니다.
임베디드 소프트웨어가 무엇인지 잘 이해했으니 이제 하드웨어에 집중해 봅시다. 다음 프로젝트에 사용할 수 있도록 Rust 지원이 뛰어난 마이크로컨트롤러를 골라 보았습니다.
비용을 제외하면 마이크로컨트롤러 선택은 네 가지 특성 사이의 균형을 맞추는 일로 볼 수 있습니다:
임베디드 개발을 단지 체험해 보고 싶다면 칩만 단독으로 구매하지 말고 개발 보드를 구매해야 합니다. 개발 보드는 마이크로컨트롤러, 마이크로컨트롤러를 플래시하고 전원을 공급할 USB 포트, 몇 개의 버튼, led, 외부 주변 장치를 연결하기 위한 하드웨어 핀이 포함된 작은 PCB입니다.
개당 3€~15€ 정도이며, 하나를 태우거나 망가뜨리거나 부술 경우에 대비해 적어도 2~3개를 구매하기를 권합니다.
서로 다른 마이크로컨트롤러의 대략적인 벤치마크는 여기에서 확인할 수 있습니다: https://www.oryx-embedded.com/benchmark
ESP32 칩 제품군은 제조사의 공식 지원과 투자, 훌륭한 공식 문서 덕분에 Rust와 함께 사용하기 가장 쉬운 제품군일 것입니다.
RISC-V 명령어 집합을 기반으로 하며 문제없이 Rust와 동작하는 ESP32-C3, ESP32-C6, ESP32-C5 칩 중에서 선택해야 합니다(rustup target add riscv32imc-unknown-none-elf / rustup target add riscv32imac-unknown-none-elf). Xtensa 프로세서를 기반으로 하며 출처가 불분명한 맞춤형 툴체인을 설치해야 하므로 작업하기 매우 고통스러운 ESP32-S3는 절대 사용하지 마세요.
제 관점에서 ESP32 칩의 유일한 단점은 다른 마이크로컨트롤러보다, 특히 wifi가 실행 중일 때 조금 더 많은 에너지를 소비한다는 점입니다. 몇 mA 정도를 말하는 것입니다. 그리고 STM32 칩보다 느립니다.
하지만 너무 저렴하고 사용하기 쉬워서 불평할 수가 없습니다.
RISC-V 기반 ESP-32 마이크로컨트롤러는 Rust 및 Embassy와 그냥 작동합니다. 임베디드 세계에서는 좀처럼 듣기 어려운 말입니다.
임베디드 여정을 시작하려면 ESP32-C3 / ESP32-C6 / ESP32-C5 보드를 강력히 권합니다. 실제로 너무 저렴해서, 하나를 태웠을 때를 대비하거나 그 성능을 맛본 뒤 너무 많은 것을 만들고 싶어질 테니 보드 10개를 사기를 권합니다.
ESP32 칩을 지원하는 Rust 라이브러리는 여기에서 찾을 수 있습니다: https://github.com/esp-rs
BLE 연결성과 매우 낮은 전력 소비 덕분에 nRF 칩은 스마트워치 및 유사 장치에 자주 사용됩니다. 예를 들어 PebbleOS를 실행하는 새로운 Core 2 Duo watch가 있습니다.
nRF 칩을 지원하는 Rust 라이브러리는 여기에서 찾을 수 있습니다: https://github.com/nrf-rs
Raspberry Pi의 마이크로컨트롤러는 멋진 개발 보드(Raspberry pi pico / pico 2 w)를 갖추고 있지만, 안타깝게도 그 외에는 그다지 훌륭하지 않습니다.
이전 모델인 RP2040은 느리고 RAM이 적습니다. 더 최신 모델은 듀얼 코어 프로세서와 520 KB 온칩 RAM을 갖춰 사양상으로는 훌륭해 보이지만, 안타까운 하드웨어 문제가 있습니다. 특정 구성에서 GPIO 포트를 사용할 때 전류가 누설되므로 문제가 해결될 때까지 추천하기 어렵습니다. Raspberry pi는 2045년까지 RP2350을 생산하겠다고 약속했으므로, 하드웨어 문제가 곧 해결되기를 바랍니다.
Raspberry pi 마이크로컨트롤러를 지원하는 Rust 라이브러리는 여기에서 찾을 수 있습니다: https://github.com/rp-rs
마지막으로 FPV 드론의 비행 컨트롤러에 특히 많이 사용되는 STM32 칩이 있습니다.
STM32는 일반적으로 대안보다 강력하지만 가격도 더 비쌉니다.
서로 다른 STM32 칩(STM32F4 / STM32F7 / STM32H7 / STM3G4...)의 비교는 여기를 참고하세요.
STM32 칩을 지원하는 Rust 라이브러리는 여기에서 찾을 수 있습니다: https://github.com/stm32-rs
이제 임베디드 개발이 무엇인지 잘 이해했으니 직접 시작해 볼 시간입니다!
Rust로 첫 번째 임베디드 프로젝트를 만드는 몇 가지 튜토리얼을 생각해 두었지만, 언제 작성하고 게시할 시간이 날지는 확실하지 않습니다. 그러니 놓치지 않도록 이 블로그 / 뉴스레터를 구독하세요!
그동안 Rust로 다음 IoT 프로젝트를 시작하기 위한 최고의 자료를 간단히 정리했습니다: