rust를 공부하면서 실제 보드에서 쓰일 때 어떤 식으로 언어가 쓰이는지 보면 더 언어 이해에 도움이 될 것 같아서 분석해보았습니다. 틀린 부분이 있을 수도 있으니 참고해주세요!
분석한 예제는 간단한 blink 동작을 하도록 하는 예제입니다.
//! Blinks an LED
#![no_std]
#![no_main]
extern crate cortex_m;
#[macro_use]
extern crate cortex_m_rt as rt;
extern crate cortex_m_semihosting as sh;
extern crate panic_semihosting;
extern crate stm32l4xx_hal as hal;
// #[macro_use(block)]
// extern crate nb;
use crate::hal::delay::Delay;
use crate::hal::prelude::*;
use crate::rt::entry;
use crate::rt::ExceptionFrame;
use crate::sh::hio;
use core::fmt::Write;
#[entry]
fn main() -> ! {
let mut hstdout = hio::hstdout().unwrap();
writeln!(hstdout, "Hello, world!").unwrap();
let cp = cortex_m::Peripherals::take().unwrap();
let dp = hal::stm32::Peripherals::take().unwrap();
let mut flash = dp.FLASH.constrain(); // .constrain();
let mut rcc = dp.RCC.constrain();
let mut pwr = dp.PWR.constrain(&mut rcc.apb1r1);
// Try a different clock configuration
let clocks = rcc.cfgr.hclk(8.MHz()).freeze(&mut flash.acr, &mut pwr);
// let clocks = rcc.cfgr
// .sysclk(64.MHz())
// .pclk1(32.MHz())
// .freeze(&mut flash.acr);
// let mut gpioc = dp.GPIOC.split(&mut rcc.ahb2);
// let mut led = gpioc.pc13.into_push_pull_output(&mut gpioc.afrh);
let mut gpiob = dp.GPIOB.split(&mut rcc.ahb2);
let mut led = gpiob
.pb3
.into_push_pull_output(&mut gpiob.moder, &mut gpiob.otyper);
let mut timer = Delay::new(cp.SYST, clocks);
loop {
// block!(timer.wait()).unwrap();
timer.delay_ms(1000_u32);
led.set_high();
// block!(timer.wait()).unwrap();
timer.delay_ms(1000_u32);
led.set_low();
}
}
#[exception]
unsafe fn HardFault(ef: &ExceptionFrame) -> ! {
panic!("{:#?}", ef);
}
문법을 보며 열심히 playground에서 따라 쳐가며 자신감을 올리고 있었지만 실제 동작하는 코드를 보니 또 어렵고 생소한 느낌입니다 ㅋㅋㅋㅠ 그래도 한 줄씩 잘 분석해보겠습니다!
#![no_std]
#![no_main]
#[...] -> rust에서는 이렇게 속성이라는 것을 표현합니다. 컴파일러에게 전달하는 정보라고 생각하면 될 것 같습니다.
#![...]와 #[]처럼 느낌표의 유무에 따라 속성 적용 범위가 달라집니다.
전자는 전체에 적용한다는 의미이고, 후자는 함수, 모듈 등 특정 항목에만 적용한다는 의미입니다.
no_std는 os에서 제공하는 standard lib을 사용하지 않는다는 의미이고, no_main은 main 함수를 사용하지 않는다는 의미입니다.
os에서 기본적으로 프로그램의 시작점을 main으로 생각하고 있는데, 현재 임베디드 환경에서는 os없이 사용하기 때문에 사용하는 속성입니다. 이 경우에는 #[entry] 속성을 사용하여 시작점을 직접 지정해줘야 합니다!
no_std를 통해 standard lib을 사용하지 않는 이유는, bare-metal 시스템에서는 OS를 사용하지 않기 때문입니다.
standard lib을 load시키려면 OS에서 제공하는 프로그램을 사용해야 합니다. 더 자세한 내용은 해당 링크를 참고해주세요
extern crate cortex_m;
extern 키워드를 사용하여 crate를 추가하는 것은 컴파일러에게 외부에 있는 crate를 사용하겠다고 알려주는 의미입니다. 이런 방식은 예전 방식이고, rust 2018 이후로는 .toml 파일의 [dependencies] 에 추가한 crate는 자동으로 포함되어 사용이 가능합니다!
#[macro_use]
extern crate cortex_m_rt as rt;
이 속성은 외부 crate에서 가져온 매크로를 사용할 때 필요한 부분인데, 마찬가지로 rust 2018 이후로는 해당 속성없이 use 키워드만으로 사용이 가능합니다. 1번 항목에서 언급된 #[entry] 속성도 여기서 가져온 속성 매크로네요!
extern crate cortex_m_semihosting as sh;
extern crate panic_semihosting;
extern crate stm32l4xx_hal as hal;
// #[macro_use(block)]
// extern crate nb;
use crate::hal::delay::Delay;
use crate::hal::prelude::*;
use crate::rt::entry;
use crate::rt::ExceptionFrame;
use crate::sh::hio;
use core::fmt::Write;
또 여러가지 blink 동작을 위해 필요한 모듈들이 추가되었습니다. 임베디드 시스템에서는 standard lib에서 제공 받는 기능을, core라는 crate를 통해 사용하는 것 같습니다. Write를 가져와서 사용하려고 하고 있네요!
이 외에도 HAL이나 패닉 처리를 위한 crate도 보입니다. 뒷 내용에서 이런 crate들이 어떻게 쓰이는 지 확인해보겠습니다.