board : STM32F446 내장 레지스터에 직접 접근(MMIO)하여 GPIO/버튼/타이밍 기능 사용
OS : 앱 인터페이스(App), 시스템콜 경계(Syscalls trait), 앱 실행자(Os)
apps : 실제 동작(Heartbeat, SOS) - Syscalls만 사용해서 하드웨어를 건드린다.
main() : 보드 초기화 -> 앱 배열 구성 -> Os::run(AppCall::SwitchOnButton{...})로 실행한다.
#[derive(copy, Clone, Debug)]
pub enum GpioPin { Led1, Led2 }
실제 보드에 매핑하기 전에 logical하게 매핑하는 과정으로, 내 코드에서는 Led 2개 다 PA5으로 매핑해줄 예정.
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;
fn user_button_pressed(&self) -> bool; // B1(PC13)
}
실제 SVC로 바꿀 때 이 trait 호출부를 SVC 클라이언트로 대체하고, 현재의 보드 구현은 커널(SVC Handler)쪽으로 옮기면 된다. 앱/OS 코드는 거의 그대로 둬도 될듯.
pub trait App {
fn name(&self) -> &`static str;
fn init(&mut self, _sys: &mut dyn Syscalls) {}
fn tick(&mut self, sys: &mut dyn Stscalls); // &mut dyn : 런타임에 실제 타입이 결정되는 동적 디스패치 의미.
}
pub enum AppCall<'a> {
ByName(&'a str),
ByIndex(usize),
All,
SwitchOnButton { primary: usize, secondary: usize },
}
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 }
}
pub fn run(&'a mut self, call: AppCall<'a>) -> ! {
// One-time init for all apps
if !self.started { for a in self.apps.iter_mut() { a.init(self.sys); } self.started = true; }
assert!(!self.apps.is_empty(), "no apps to run");
match call {
AppCall::ByIndex(mut i) => {
i %= self.apps.len();
loop { self.apps[i].tick(self.sys); }
}
AppCall::ByName(name) => {
let mut idx = 0usize;
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 a in self.apps.iter_mut() { a.tick(self.sys); } }
}
AppCall::SwitchOnButton { mut primary, mut secondary } => {
let len = self.apps.len();
primary %= len; secondary %= len;
loop {
if self.sys.user_button_pressed() {
self.apps[secondary].tick(self.sys);
} else {
self.apps[primary].tick(self.sys);
}
self.sys.sleep_ms(1); // debounce / cooperative yield
}
}
}
}
}