ghlee.dev
Rust로 임베디드 맛보기
2024. 5. 4.취미로 Rust를 공부하고 있던 차에 The Rusty Bits
라는 유튜브 채널을 발견했다.
Rust를 이용한 임베디드 프로그래밍에 대한 소개를 하는 채널로 처음 발견했을 때는 영상이 두 개뿐이었지만 퀄리티가 굉장해서 기대하고 있던 채널이었다.
마침 새로운 영상이 올라온 김에 영상을 따라서 실습해보며 그 과정에 대해 이야기하면서 영상에서는 생략된 자잘한 설명도 남겨보고자 한다.
Rust 튜토리얼을 해봤다면 실습하는데는 무리가 없으며 Cargo, 크레이트 등 Rust 개발 환경의 기초적인 지식은 있어야한다. 그리고 학부 수준의 어셈블리어 프로그래밍을 해봤다면 실습이 굉장히 수월하겠지만 안해봤어도 크게 문제는 없다.
구체적인 실습 과정은 유튜브 영상 컨텐츠를 직접 확인해보길 바란다. 참고한 영상의 링크는 글 최하단에 남겨두겠다.
Setup
임베디드 프로그래밍을 하려면 우선 하드웨어(마이크로 컨트롤러)부터 구입해야한다. 영상에서 쓰인 마이크로 컨트롤러는 Micro:bit에서 만든 nRF52833
라는 모델이며 검색해보니 교육용으로 만들어진 모델로 한국에서도 쉽게 구매할 수 있었다.
내가 실제 구매한 마이크로 컨트롤러이다. micro B to USB C 케이블도 별도로 쿠팡에서 3천 원 주고 구매했다.
마이크부터 스피커, 버튼 및 터치 센서까지 다양한 모듈이 있지만 실제 사용할 모듈은 노트북과 연결하여 통신하기 위한 마이크로 5핀 커넥터 정도이고 추후에 5X5 LED 매트릭스를 이용해 볼 예정이다.
다음으로 할 일은 해당 모델이 어떤 아키텍처와 명령어 셋을 사용하는지 알아야 하는데 구글링을 해보면 Micro:bit 홈페이지에서 모델에 대해 소개하는 페이지를 찾을 수 있다.
https://tech.microbit.org/hardware/#nrf52-application-processor
페이지를 보면 Arm의 Cortex-M4 프로세서를 사용하는 것을 찾을 수 있으며 친절하게도 링크가 있다. 또한 Flash ROM과 RAM의 크기에 대해서도 나와있는데 이는 나중에 쓰일 정보이니 일단 킵해두자.
여담으로 Arm이나 Cortex 같은 용어는 전자기기에 관심이 있다면 들어봤을 법도 한데, 삼성의 갤럭시 스마트폰에 탑재되는 퀄컴의 스냅드래곤과 삼성의 엑시노스 같은 AP는 Arm에서 설계한 아키텍처(Cortex-M, Cortex-X 등)를 가져와서 자기들 입맛에 맞게 조합하여 만든 거다. 마찬가지로 Micro:bit에서 만든 nRF52833
모델도 Arm의 Cortex-M4 설계를 가져와서 사용했다고 보면 된다.
갤럭시 S24에 탑재된 스냅드래곤 8 GEN 3의 스펙 문서 중 일부이다. 우측의 CPU 설명을 보면
Arm Cortex-X4 technology
라고 작게 적혀있다.
아까 발견한 링크를 타고 들어가면 Cortex-M4에 대해 자세히 나오는데 CPU 아키텍처는 ARMv7E-M
을 사용하며 명령어셋(ISA)은 Thumb or Thumb-2
을 사용하고 있는 것을 알 수 있다.
또한 IEEE 754 명세 기반의 부동소수점 연산을 위한 FPU
가 포함되어 있으며 효율적인 벡터 연산을 위한 SIMD
를 지원한다.
아키텍처나 명령어셋 같은 스펙은 하드웨어의 종류마다 다르기 때문에 이러한 방식으로 하드웨어 제조사가 공개해놓은 정보를 직접 찾아보아야 한다.
다음으로 할 일은 ARMv7E-M
이라는 CPU 아키텍처를 Rust에서 지원하는지 찾아야 하는데 이는 The rustc book의 플랫폼 지원 현황에서 찾아볼 수 있다.
https://doc.rust-lang.org/nightly/rustc/platform-support.html
ARMv7E-M
에 해당하는 타겟으로 두 가지가 나오는데 우리가 가진 하드웨어는 FPU
가 존재하는 모델이라서 thumbv7em-none-eabihf
를 rustup에 추가해 주면 된다. 이에 대한 내용은 후술할 cortex-m-rt
크레이트 문서에서 알려준다. 이제 아래 명령어를 실행해서 rustup에 호스트 도구를 추가해 보자.
제대로 추가됐는지 rustup show
명령어로 확인할 수 있으며 아래와 같이 thumbv7em-none-eabihf
이 추가된 것을 볼 수 있다.
rustup을 처음 설치하면 이미지에 있는 aarch64-apple-darwin
처럼 하드웨어에 맞는 기본 호스트 도구가 추가된다. 나 같은 경우 M3 맥북에어를 사용하고 있으므로 애플 실리콘 아키텍처에 맞는 호스트 도구가 추가되어 있었다.
Setup.. 아직 Setup..
이제까지 한 일을 정리하면 실제 하드웨어를 구매하고 마이크로 컨트롤러 모델에 대한 정보를 찾았으며 거기서 CPU 아키텍처와 명령어 셋을 알아냈고 rustup에 필요한 호스트 도구를 추가해 주었다.
뭔가 많이 한거 같지만 여전히 세팅할 것이 잔뜩 남아있다..😢
드디어 Rust 프로젝트를 만들 때가 왔다. 원하는 경로에 아래 명령어를 실행해서 새 프로젝트를 만들어주자.
그리고 새로 만든 Rust 프로젝트에서 main.rs
로 가서 아래처럼 코드를 추가해 준다.
파일의 최상단에 특별한 코드를 추가해 주었는데 이 둘의 용도는 다음과 같다.
#![no_std]
는 Rust가 제공하는 표준 라이브러리를 사용하지 않겠다는 의미이며 사실상 표준 라이브러리를 사용할 수 없는 임베디드 프로그래밍에서는 필수이다.#![no_main]
는 프로그램의 시작점으로 강제되던main()
함수를 강제하지 않겠다는 의미이다.
이제 Cortex-M4 프로세서를 사용하기 위한 런타임 환경을 구성해 주어야 하는데 Rust에서는 cortex-m-rt
라는 크레이트가 있다. Cortex-M 팀이 직접 관리하고 있는 크레이트로 초기 부팅 시 static 변수를 초기화하고 인터럽트(interrupt) 처리에 필요한 데이터를 벡터 테이블에 채우는 등 런타임 환경을 구성하는데 필요한 최소한의 코드로 구성되어 있는 크레이트이다.
또한 아래 경로로 파일을 만들어서 cargo build
실행 시 사용될 설정을 미리 정의해둔다.
아래처럼 #[entry]
속성을 main()
위에 선언해 주고 함수도 조금 수정하자.
main()
함수의 반환 값은 없으므로!
를 반환 타입으로 지정해준다.
이제 빌드 시 런타임 코드가 들어갈 주소를 지정해야 하는데 우리가 사용하는 하드웨어의 FLASH
와 ROM
의 메모리 시작 주소와 용량을 알아야 한다. 아까 보았던 하드웨어 스펙을 기억하는가? 다시 페이지로 가서 보면 각각 FLASH는 512KB
, ROM은 128KB
용량인 것을 확인할 수 있다.
메모리 시작 주소는 어떻게 알 수 있을까? 이는 메모리 맵이라는 것을 확인해야 하는데 일반적으로 하드웨어 제조사가 제공하는 스펙 문서에 나와있다. 하드웨어 스펙 페이지에서 nRF52833
모델 관련 링크를 여러번 타고 들어가면 PDF로 만들어진 스펙 문서를 확인할 수 있는데 이 곳에서 Memory map
키워드로 검색하면 찾을 수 있다.
여기서 Flash와 Data RAM(SRAM)의 시작 주소를 확인하면 되며 각각 0x00000000
, 0x20000000
인 것을 알 수 있다. 이제 알아낸 메모리 용량과 시작 주소를 가지고 루트 경로에 memory.x
파일을 만들어서 아래처럼 정의해 주자.
참고로 홈페이지나 문서에서 SRAM과 ROM 용어를 혼용해서 사용되고 있는데, 둘 다 실행에 필요한 데이터를 미리 저장하는 의미로 큰 차이가 없어서 그런 게 아닐까 추측해 본다. 🤔
아직 빌드를 시도해 보기 전에 지금 쯤이면 main.rs
파일에서 컴파일러가 에러를 보여주고 있을 거라 생각되는데 이는 rust-analyzer
의 타겟을 정해주지 않아서 발생한다. 아래처럼 설정 파일을 만들어서 타겟을 지정해 주고 VSCode를 재시작해 주자.
드디어 cargo build
명령어로 빌드를 시도해 보면 다음과 같은 에러를 마주할 수 있다. 😬
예기치 못한 에러가 발생했을 때 프로그램을 중지시키거나 할 수 있는 코드가 필요하다는 에러 메시지인데 panic_halt
라는 크레이트를 추가해 주고 main.rs
를 살짝 바꿔주자.
이제 다시 cargo build
명령어를 실행하면 정상적으로 빌드가 진행될 것이다.
Binary size and address
이번엔 우리가 작성한 코드들의 바이너리 크기와 시작 주소를 알고 싶을 때 사용할 도구를 설치해 보자.
Rust는 컴파일러 백엔드로 LLVM을 사용하고 있으므로 자세한 디버깅을 하려면 llvm-tools
가 필요하다. 또한 Cargo에서 이를 편리하게 사용할 수 있게 해주는 cargo-binutils
를 설치해 준다. 모두 설치 후 아래 명령어를 실행해 보자.
자세히 보면 아까 memory.x
파일을 통해 정의해 주었던 FLASH
와 Data RAM
의 시작 주소에 맞춰서 .vector_table
과 .data
의 시작 주소도 정해진 것을 알 수 있다. 주소가 바뀌는 것이 궁금하다면 memory.x
에서 시작 주소를 변경한 후 기존에 빌드 되어있던 target
폴더를 지우고 다시 빌드 해보면 바뀌는 것을 확인할 수 있다.
디버거 설치와 코드 실행
디버깅을 위해 실습 코드를 변경해야 하는데 우선 필요한 크레이트를 추가해 주자.
rtt-target
은 마이크로 컨트롤러에서 디버그 로깅을 가능하게 해준다. 다르게 표현하면 내 콘솔에 메시지를 띄울 수 있도록 실시간 통신 기능이 있다.cortex-m
은 Cortex-M 프로세서에 대한 조작을 가능하게 해주는 코드가 있다.critical-section-single-core
은nRF52833
모델이 단일 코어이기 때문에 해당 기능을 넣어준다.
다음으로 main.rs
의 코드를 아래처럼 변경하자.
이제 작성한 코드를 우리가 구입한 하드웨어에 넣어주고 실행해야 한다. 이를 위해 probe-rs
패키지를 설치한다.
이 부분은 유튜브의 실습 영상과는 다른 방법인데, 영상의 댓글을 보다 보니 해당 패키지를 추천하는 사람들이 있었고 나 또한 사용해 보니 괜찮아 보여서 이걸로 진행했다. 영상 제작자도 probe-rs
에 대한 평가를 고정 댓글로 남겨두었으니 확인해 보면 도움이 될 것이다.
기존에 작성했던 config.toml
파일도 일부 수정하자.
드디어 처음으로 코드를 실행해 볼 순간이 왔다. 😁
우선 구입한 마이크로 컨트롤러를 컴퓨터(혹은 노트북)에 케이블을 이용하여 연결한다. 그리고 기존 빌드 된 파일을 없애기 위해 target
폴더를 지운 뒤 cargo run
명령어를 실행해 보자.
우리가 입력한 Echo...
메시지가 터미널에 순차적으로 나온다면 성공이다. 🎉
probe-rs debugger extension
이제까지 우리가 구입한 하드웨어의 스펙에 맞게 코드를 작성하고 이를 빌드 하여 하드웨어에 옮겨준 후 실행까지 무사히 성공했다. 마지막으로 VSCode의 디버깅 도구를 이용할 수 있도록 세팅해 주고 마무리하겠다.
VSCode의 확장 프로그램 중에서 Debugger for probe-rs
을 검색하여 설치하자.
https://marketplace.visualstudio.com/items?itemName=probe-rs.probe-rs-debugger
다음으로 .vscode
폴더에 launch.json
파일을 만들어서 아래처럼 작성해 준다.
참고로 해당 세팅에 대한 내용은 probe-rs 문서에 잘 나와있으니 필요하다면 참고하면 된다.
이제 디버거 도구를 이용해서 디버깅을 할 수 있는데, rprint!("Echo...\n");
부분에 중단점을 찍고 디버깅을 해보았다.
Call Stack에서 함수를 우측 클릭해서 디스어셈블을 하면 함수 위치로 바로 갈 수 있다. 물론 코드를 직접 우측 클릭해서 볼 수도 있다.
좌측 디버거 패널에서 정적 변수(Static), 레지스터(Registers), 변수(Variables) 등의 정보를 확인할 수 있다. 정적 변수에는 우리가 작성해서 마이크로 컨트롤러로 플러시 했던 코드가 있고 변수에는 RTT의 채널에 대한 데이터나 이터레이션 등의 데이터가 있다. 레지스터의 값이 바뀌는 것도 확인할 수 있으니 중단점을 바꿔가면서 디버깅해보길 바란다.
마치며
기나긴 세팅 끝에 마이크로 컨트롤러와 아주 간단한 통신을 해보았다. 비록 콘솔에 메시지를 남기는 정도였지만 임베디드 혹은 어셈블리어 프로그래밍을 처음 접한다면 실습을 이해하는 게 간단하지는 않았을 것으로 생각된다.
영상에서는 언급하지 않고 넘어간 몇몇 부분을 초심자도 알법한 쉬운 용어로 설명을 덧붙여 보았는 데 도움이 됐길 바란다.
다음 단계는 마이크로 컨트롤러가 가지고 있는 25개의 LED와 통신하는 실습인데 궁금한 사람은 직접 유튜브 채널로 가서 영상을 보며 실습해 보길 바란다. 이 글에서 디버깅까지 무사히 실습했다면 다음 단계도 그리 어렵지 않을 것이다. 또한 글에서 작성한 코드는 깃허브 링크로 남겨두겠으니 필요하다면 참고하길 바란다.
"I ♥️ RUST" 🤭
개인적으로는 취미로 공부하는 Rust를 사용할 만한 분야가 많지 않아서 (그동안은 Leetcode 문제를 풀거나 블록체인의 스마트 컨트랙트 작성하는 정도였다.) 이번 기회에 소형 하드웨어에서의 프로그래밍을 경험해 볼 수 있어서 재미있었다.
참고한 유튜브 실습 영상
코드 샘플