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