~9.8 study 1

Nitroblue 1·2025년 9월 7일

App 2개 Capsulation + mini OS화

// mini_os_app_framework.rs
#![no_std]
#![no_main]

use cortex_m_rt::entry;
use panic_halt as _;

// ------------------------- OS Core -------------------------
mod os {
    /// Logical pins that apps are allowed to use.
    /// Apps never see concrete MCU registers; they just reference logical pins.
    #[derive(Copy, Clone)]
    pub enum GpioPin {
        Led1,
        Led2,
    }
    /// 앱 입장에선 메모리 주소를 몰라도 sys.gpio_write(GpioPin::Led1, true)처럼 간접 호출만 하면 되게 설계.

    /// Minimal syscall surface that apps can use.
    pub trait Syscalls {
        fn gpio_write(&mut self, pin: GpioPin, high: bool);
        fn gpio_toggle(&mut self, pin: GpioPin);
        fn sleep_ms(&mut self, ms: u32);
        fn now_ms(&self) -> u64; // (optional) coarse time
    }

    /// App contract: init() runs once, then tick() is called by the OS.
    pub trait App {
        fn name(&self) -> &'static str;
        fn init(&mut self, _sys: &mut dyn Syscalls) {}
        fn tick(&mut self, sys: &mut dyn Syscalls);
    }

    /// How to choose which app(s) to run.
    pub enum AppCall<'a> {
        ByName(&'a str),
        ByIndex(usize),
        All, // simple round-robin
    }

    /// Tiny OS that holds apps + the board-specific syscalls implementation.
    pub struct Os<'a> {
        apps: &'a mut [&'a mut dyn App],
        sys: &'a mut dyn Syscalls,
        started: bool,
    }

    impl<'a> Os<'a> {
        pub fn new(apps: &'a mut [&'a mut dyn App], sys: &'a mut dyn Syscalls) -> Self {
            Self { apps, sys, started: false }
        }

        /// Run the requested app(s) forever.
        pub fn run(&'a mut self, call: AppCall<'a>) -> ! {
            if !self.started {
                for app in self.apps.iter_mut() {
                    app.init(self.sys);
                }
                self.started = true;
            }

            match call {
                AppCall::ByIndex(mut idx) => {
                    idx %= self.apps.len();
                    loop {
                        self.apps[idx].tick(self.sys);
                    }
                }
                AppCall::ByName(name) => {
                    let mut idx = 0;
                    for (i, a) in self.apps.iter().enumerate() {
                        if a.name() == name { idx = i; break; }
                    }
                    loop { self.apps[idx].tick(self.sys); }
                }
                AppCall::All => {
                    loop {
                        for app in self.apps.iter_mut() {
                            app.tick(self.sys);
                        }
                    }
                }
            }
        }
    }

    pub use {App, AppCall, GpioPin, Syscalls, Os};
}

// ------------------------- Apps ----------------------------
mod apps {
    use super::os::{App, Syscalls, GpioPin};

    /// App #1: Simple blink on LED1
    pub struct LedBlinkApp {
        period_ms: u32,
        last: u64,
        on: bool,
    }
    impl LedBlinkApp {
        pub const fn new(period_ms: u32) -> Self {
            Self { period_ms, last: 0, on: false }
        }
    }
    impl App for LedBlinkApp {
        fn name(&self) -> &'static str { "led_blink" }
        fn init(&mut self, _sys: &mut dyn Syscalls) {}
        fn tick(&mut self, sys: &mut dyn Syscalls) {
            let now = sys.now_ms();
            if now.wrapping_sub(self.last) >= self.period_ms as u64 {
                self.on = !self.on;
                sys.gpio_write(GpioPin::Led1, self.on);
                self.last = now;
            }
            // Yield a little to avoid a hot loop
            sys.sleep_ms(1);
        }
    }

    /// App #2: SOS pattern on LED2 (· · · – – – · · ·)
    pub struct LedSosApp;
    impl LedSosApp { pub const fn new() -> Self { Self } }
    impl App for LedSosApp {
        fn name(&self) -> &'static str { "led_sos" }
        fn tick(&mut self, sys: &mut dyn Syscalls) {
            // dot = 200 ms on, dash = 600 ms on, 200 ms off between pulses
            const DOT: u32 = 200; const DASH: u32 = 600; const GAP: u32 = 200; const WORD: u32 = 700;
            let mut pulse = |dur: u32| {
                sys.gpio_write(GpioPin::Led2, true);  sys.sleep_ms(dur);
                sys.gpio_write(GpioPin::Led2, false); sys.sleep_ms(GAP);
            };
            for _ in 0..3 { pulse(DOT); }
            for _ in 0..3 { pulse(DASH); }
            for _ in 0..3 { pulse(DOT); }
            sys.sleep_ms(WORD);
        }
    }

    pub use {LedBlinkApp, LedSosApp};
}

// --------------------- Board Syscalls ----------------------
mod board {
    use embedded_hal::digital::OutputPin;
    use cortex_m::asm::nop;
    use super::os::{GpioPin, Syscalls};

    /// Simple trait to abstract a LED pin.
    pub trait LedDev { fn set(&mut self, high: bool); fn toggle(&mut self) { /* default */ } }

    impl<T> LedDev for T where T: OutputPin {
        fn set(&mut self, high: bool) {
            if high { let _ = self.set_high(); } else { let _ = self.set_low(); }
        }
        fn toggle(&mut self) {
            // If HAL has `toggle()`, you can implement via that. Fallback:
            // Not universally available via embedded-hal, so we no-op here.
        }
    }

    /// Board-specific implementation of Syscalls.
    pub struct BoardSyscalls<LED1: LedDev, LED2: LedDev> {
        led1: LED1,
        led2: LED2,
        // Extremely coarse monotonic time in ms (from busy spins).
        time_ms: u64,
        // CPU frequency hint (for crude delay) — tune for your board.
        cycles_per_ms: u32,
    }

    impl<LED1: LedDev, LED2: LedDev> BoardSyscalls<LED1, LED2> {
        pub fn new(led1: LED1, led2: LED2, cycles_per_ms: u32) -> Self {
            Self { led1, led2, time_ms: 0, cycles_per_ms }
        }

        /// Busy-wait for ~ms; updates time_ms.
        fn spin_delay(&mut self, ms: u32) {
            // NOTE: Replace with a proper timer / systick for accuracy.
            // `cycles_per_ms` is just a rough multiplier of loop iterations.
            for _ in 0..ms {
                for _ in 0..self.cycles_per_ms { unsafe { nop() }; }
                self.time_ms = self.time_ms.wrapping_add(1);
            }
        }
    }

    impl<LED1: LedDev, LED2: LedDev> Syscalls for BoardSyscalls<LED1, LED2> {
        fn gpio_write(&mut self, pin: GpioPin, high: bool) {
            match pin {
                GpioPin::Led1 => self.led1.set(high),
                GpioPin::Led2 => self.led2.set(high),
            }
        }
        fn gpio_toggle(&mut self, pin: GpioPin) {
            match pin {
                GpioPin::Led1 => self.led1.toggle(),
                GpioPin::Led2 => self.led2.toggle(),
            }
        }
        fn sleep_ms(&mut self, ms: u32) { self.spin_delay(ms); }
        fn now_ms(&self) -> u64 { self.time_ms }
    }

    pub use BoardSyscalls;
}

// -------------- Choose ONE board backend below -------------

// ---------- Backend A: STM32F446 (F4) example (PC13 + PA5) ----------
#[cfg(feature = "stm32f4")] 
mod backend {
    use super::board::BoardSyscalls;
    use super::os::{AppCall, Os};
    use super::apps::{LedBlinkApp, LedSosApp};
    use stm32f4xx_hal as hal;
    use hal::{pac, prelude::*};

    #[cortex_m_rt::entry]
    fn main() -> ! {
        let dp = pac::Peripherals::take().unwrap();
        let cp = cortex_m::Peripherals::take().unwrap();

        // Clocks
        let rcc = dp.RCC.constrain();
        let clocks = rcc.cfgr.sysclk(84.MHz()).freeze();

        // GPIO
        let gpioc = dp.GPIOC.split();
        let gpioa = dp.GPIOA.split();
        let mut led1 = gpioc.pc13.into_push_pull_output(); // on many Nucleo boards
        let mut led2 = gpioa.pa5.into_push_pull_output();   // user LED (e.g., Nucleo-F446RE)

        // Build syscalls with a rough loop-calibration (tune this value!)
        let mut syscalls = BoardSyscalls::new(led1, led2, 20_000);

        // Apps
        let mut app1 = LedBlinkApp::new(500);
        let mut app2 = LedSosApp::new();
        let mut app_list: [&mut dyn super::os::App; 2] = [ &mut app1, &mut app2 ];

        // Tiny OS
        let mut os = Os::new(&mut app_list, &mut syscalls);

        // Pick which app(s) to run:
        // Options: AppCall::ByIndex(0), AppCall::ByName("led_sos"), AppCall::All
        os.run(AppCall::All)
    }
}

// ---------- Backend B: STM32F103C8 (F1) example (PC13 + PA1) ----------
#[cfg(feature = "stm32f1")] 
mod backend {
    use super::board::BoardSyscalls;
    use super::os::{AppCall, Os};
    use super::apps::{LedBlinkApp, LedSosApp};
    use stm32f1xx_hal as hal;
    use hal::{pac, prelude::*};

    #[cortex_m_rt::entry]
    fn main() -> ! {
        let dp = pac::Peripherals::take().unwrap();
        let cp = cortex_m::Peripherals::take().unwrap();

        // Clocks
        let mut flash = dp.FLASH.constrain();
        let mut rcc = dp.RCC.constrain();
        let clocks = rcc.cfgr.sysclk(48.MHz()).freeze(&mut flash.acr);

        // GPIO
        let mut gpioa = dp.GPIOA.split();
        let mut gpioc = dp.GPIOC.split();
        let mut led1 = gpioc.pc13.into_push_pull_output(&mut gpioc.crh);
        let mut led2 = gpioa.pa1.into_push_pull_output(&mut gpioa.crl);

        // Syscalls (tune cycles_per_ms for your board)
        let mut syscalls = BoardSyscalls::new(led1, led2, 12_000);

        // Apps
        let mut app1 = LedBlinkApp::new(300);
        let mut app2 = LedSosApp::new();
        let mut app_list: [&mut dyn super::os::App; 2] = [ &mut app1, &mut app2 ];

        // Tiny OS
        let mut os = Os::new(&mut app_list, &mut syscalls);
        os.run(AppCall::All)
    }
}

// ---------- Backend C: Mock (builds even without a HAL; does nothing) ----------
#[cfg(all(not(feature = "stm32f4"), not(feature = "stm32f1")))]
mod backend {
    use super::board::{BoardSyscalls, LedDev};
    use super::os::{AppCall, Os};
    use super::apps::{LedBlinkApp, LedSosApp};

    // Dummy LED pins so you can compile without a HAL.
    struct DummyLed;
    impl LedDev for DummyLed { fn set(&mut self, _high: bool) {} }

    #[cortex_m_rt::entry]
    fn main() -> ! {
        let mut syscalls = BoardSyscalls::new(DummyLed, DummyLed, 10_000);
        let mut app1 = LedBlinkApp::new(200);
        let mut app2 = LedSosApp::new();
        let mut app_list: [&mut dyn super::os::App; 2] = [ &mut app1, &mut app2 ];
        let mut os = Os::new(&mut app_list, &mut syscalls);
        os.run(AppCall::All)
    }
}

Study

#[...], #![...] ? Attribute?

  • Basic : Rust에서 #[...] 이건 어떤 역할을 하는가?
    • Attribute라고 하며, 컴파일러에게 '이 선언을 이렇게 취급해달라'는 메타디이터를 붙이는 방식이다.
    • 크게 두 가지 문법이 존재한다.
    1. #[attr(...)] : Outer attribute - 바로 뒤의 아이템 하나(fn/struct/enum/mod)에 적용.
    2. #![attr(...)] : Inner attribute - 현재 스코프 전체(주로 crate/module)에 적용.
      ex) crate 상단의 #![no_std], #![no_main] -> 참고로 이 att는 베어메탈 전용이라고 한다.
    • Attribute 대표적인 예시
      1. 자동 파생(트레이트 구현 생성) - #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)], #[derive(Serialize, Deserialize)] 등 가능
      2. 조건부 컴파일 - #[cfg(...)] : 조건이 참일 때만 해당 아이템 컴파일, #[cgf_attr(cond, attr)] : cond면 attr을 적용.
        약간 CSS나 Html같은 프론트엔드 언어 느낌이 강하게 든다.
      3. 린트/경고 레벨 제어 - #[allow(...)], #[warn(...)], #[deny(...)], #[forbid(...)], #[expect(...)]
      4. 문서/rustDoc - #[doc = "..."], #[doc(hidden)], ...
      5. 코드 생성/최적화 힌트 - #[inline], #[inline(always)], #[inline(never)] ...
      6. crate/module level - #[test], #[ignore], #[should_panic], #[cfg(test)]
        등이 있다

mod?

  • Rust에서 mod는 다른 언어에서의 함수와 비슷한 종류. 파이썬의 def, java의 functional class 단위 등.
    • Rust에서 mod는 모듈(module)을 선언하는 키워드이며, 관련된 데이터와 함수를 묶어 하나의 이름 공간으로 관리하는 코드 구성 단위 랍니다.

그래서 #[derive(Copy, Clone)]이 무엇인가?
- 해당 타입에 대해 Copy와 Clone trait를 자동으로 구현해준다.
- Copy : 값이 이동되는 대신, 복사가 된다.

trait?

pub trait App {
	fn name(&self) -> &`static str;
    fn init(&mut self, _sys: &mut dyn Syscalls) {}
    fn tick(&mut self, sys: &mut dyn Syscalls);
}

이 예시에서 알 수 있듯, trait는 '특성'이라는 의미에 충실하게 추상화를 하고 있다. 자바에서 내가 원하는 타입을 선언하는 것과 비슷한 원리인 것 같다.

' ??

        ByName(&'a str),
        ByIndex(usize),
        All,
    }

뭔가 어려운 코드같아보인다. 일단 public 접근 권한에 enum 자료형을 갖고 있고... 이름은 AppCall인데 옆에 <`a>이게 뭘까. 일단 <> 이거는 제네릭 타입임을 보여주기 위한 도구인 것 같은데, 이 앞에 \'이게 궁금하다.
-> \' 이거는 보통 Lifetime을 의미한다! 따라서 AppCall<'a>를 들고 다니는 동안 &'a str도 유효해야 한다는 뜻이다.
-> 컴파일러가 참조가 유효 범위 내에서만 쓰이도록 보장하기 위해 쓰인다. 특히 구조체/열거형이 참조를 보관할 때는 수명을 명시해야 한다.
-> 이런 식으로 컴파일러가 참조값을 볼 수 있는 유효 시간을 한정지으면서 보안을 설계하는구나!

Struct, Enum ...

  • struct : 구조체. 이름 붙은 필드들을 묶어 하나의 타입으로 만든 것. 즉, '이 데이터들은 항상 같이 다닌다.'일 때 사용한다. 마치 케이블들을 용도별로 묶어서 관리하는 느낌.
  • Enum : 열거형. 서로 다른 형태중 하나를 담는 타입. '여러 경우의 수 중 하나'를 표현할 때 사용한다.

0개의 댓글