[Rust][STM32] - STM32L4 Blink 분석 - 2

김보성·2025년 3월 4일
0
post-thumbnail

이제 익숙한 main문을 볼 차례네요! main이라는 단어 말고는 아직 익숙한건 없긴 합니다ㅋㅋㅋ

//! 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);
}

#[entry]

#[entry]
fn main() -> ! {
.
.
.
}

앞에서 언급했듯이, #![no_main] 속성이 있는 경우에는 #[entry] 속성을 이용하여 main의 역할을 할 프로그램 진입점을 알려주어야 합니다. 여기서는 main() 함수가 진입점이 되겠네요.

! 느낌표는 해당 함수는 return하는 일이 절대 없다는 의미입니다. 지금 처럼 loop문을 사용하는 등의 함수에서 사용합니다. 임베디드 시스템에서는 loop문이 많기 때문에 자주 사용될 것 같습니다.

take()과 unwrap()

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();

여기서는 take()이나 unwrap()이라는 메소드를 통해 인스턴스를 가지고 오는 부분입니다. 먼저 take()에 대해 알아보겠습니다.


위 내용에 따르면 peripheral을 이용하기 위해서는 반드시 take() 메소드를 사용해야 하고, 모든 peripheral은 singleton 모델의 형태라고 하네요! 인스턴스는 하나만 존재하겠구나라고 생각하고 있겠습니다. rust의 소유권 개념을 통해 이동되며 사용될 것으로 추측 중입니다.

take()의 반환값은 Option 타입입니다. 값이 있을 수도, 없을 수도 있다는 뜻인데 이 Option 타입에 사용되는 unwrap() 메소드는 해당 타입의 Some값을 반환시켜주고, None일 경우 panic을 유발하도록 되어 있습니다.

take()과 unwrap()의 사용으로 peripheral의 인스턴스가 중복으로 생성되는 것을 방지하고 있습니다.

정리하면, 이 코드에서는 아래와 같은 행위가 이루어지고 있습니다.

  1. 출력에 사용될 hstdout 인스턴스 생성
  2. cortex M의 peripheral 인스턴스 생성
  3. stm32 hal peripheral 인스턴스 생성

constrain()

let mut flash = dp.FLASH.constrain(); // .constrain();
let mut rcc = dp.RCC.constrain();
let mut pwr = dp.PWR.constrain(&mut rcc.apb1r1);

constrain() 메소드는 특정 trait가 구현되었을 때 사용하는 것이라고 합니다. peripheral이 가지고 있는 뭔가를 반환해주는 것 같은데 뭘까요 ?

문서에서는 nice하게 peripheral을 사용하기위해 제한을 둔다고 설명이 되어 있습니다. 아직은 모호합니다..

어쨌든 Parts라는 구조체를 반환하고 있습니다. FLASH도 그렇고, RCC, PWR 모두 constrain() 메소드를 사용하고 있습니다. 여기서는 FLASH를 대표로 설명하겠습니다.

FLASH는 constrain()을 통해 아래와 같은 구조체를 반환합니다.

pub struct Parts {
  pub acr: ACR,
  pub pdkeyr: PDKEYR,
  pub keyr: KEYR,
  pub optkeyr: OPTKEYR,
  pub sr: SR,
  pub cr: CR,
  pub eccr: ECCR,
  pub pcrop1sr: PCROP1SR,
  pub pcrop1er: PCROP1ER,
  pub wrp1ar: WRP1AR,
  pub wrp1br: WRP1BR,
}

FLASH peripheral이 가지고 있는 레지스터로 이루어져 있는 구조체지만, 몇가지 없는 레지스터들이 있습니다..! 이 처럼 안정적인 peripheral 사용을 위해 모든 레지스터를 사용하도록 하는 것이 아니라 제한적으로 반환하는 것이 constrain() 메소드입니다 !

constrain()은 split() 메소드와 아주 비슷한데, split() 메소드는 constrain()과 다르게 peripheral의 모든 레지스터를 반환합니다. 처음에 constrain()과 split()이 너무 비슷하고 차이를 못느껴서 헤매고 있었습니다 ㅋㅋㅋㅠ 혹시 제가 잘못 알고 있거나 틀린 부분이 있다면 알려주세요 ! 일단 split()과 constrain()의 차이는 이 정도로 짚고 넘어가겠습니다.

이번 글에서는 여기까지 !

profile
Embedded Software Engineer

0개의 댓글