운영체제 없이 하드웨어 위에서 직접 동작하는 펌웨어(bare metal).
기본 구조 — 슈퍼루프
int main(void) {
hw_init();
while (1) {
task_a();
task_b();
task_c();
}
}
단순함이 최대 강점이다. 실행 순서가 코드에 그대로 보인다.
장점
| 장점 | 설명 |
|---|---|
| 완전한 제어 | 모든 사이클이 내 코드. 숨은 동작이 없다 |
| 최소 자원 | 커널 오버헤드 0. RAM·플래시 절약 |
| 예측 가능 | 스케줄러가 없으니 언제 무엇이 실행되는지 명확 |
| 디버깅 쉬움 | 콜 스택이 하나. 경쟁 조건이 인터럽트로 한정 |
| 인증 용이 | 검증할 코드가 적다 |
한계 — 응답 시간이 루프에 묶인다
task_a 10ms
task_b 50ms ← 여기 실행 중에는 아무것도 못 한다
task_c 5ms
────────────
최악 응답 = 65ms
"5ms 안에 반응해야 하는 일"이 생기면 구조가 무너진다.
슈퍼루프를 개선하는 기법들
① 상태 머신으로 긴 작업 쪼개기
void display_task(void) {
switch (state) {
case CLEAR: clear_line(row++); if (row > 8) state = DRAW; break;
case DRAW: draw_item(idx++); if (idx > n) state = DONE; break;
case DONE: state = CLEAR; row = idx = 0; break;
}
return; // 한 번에 조금씩만 하고 즉시 반환
}
블로킹하지 않고 조금씩 진행한다. 루프 주기가 짧게 유지된다. 사실상 협조형 멀티태스킹을 손으로 구현하는 것이다.
② 시간 기반 스케줄링
while (1) {
uint32_t now = HAL_GetTick();
if (now - t_sensor >= 10) { t_sensor = now; read_sensor(); }
if (now - t_display >= 100){ t_display = now; update_lcd(); }
if (now - t_comm >= 5) { t_comm = now; handle_comm(); }
}
주기가 다른 작업을 하나의 루프에서 관리한다. 아주 널리 쓰이는 패턴이며, 이것만으로도 상당히 복잡한 시스템을 만들 수 있다.
③ 인터럽트로 급한 일 처리
급한 일 → ISR에서 즉시 (또는 플래그만 세우고 루프에서) 느긋한 일 → 슈퍼루프
결국 "우선순위 2단계" 를 만드는 셈이다. 인터럽트 우선순위를 활용하면 더 세분화할 수 있다.
④ 이벤트 큐
while (1) {
Event e;
if (queue_pop(&e)) dispatch(e);
else __WFI(); // 할 일 없으면 잠든다
}
ISR이 큐에 이벤트를 넣고 루프가 처리한다. 저전력에도 유리하다.
결코 하면 안 되는 것 — 블로킹 지연
❌ HAL_Delay(500); // 500ms 동안 아무것도 못 한다
✅ if (now - last >= 500) { last = now; … }
슈퍼루프에서 Delay는 금지어라고 생각하는 것이 좋다.
초기화 코드에서만 허용된다.
RTOS와의 선택 기준
베어메탈로 충분
- 작업이 몇 개 안 되고 주기가 비슷하다
- 가장 긴 작업이 응답 요구보다 짧다
- 인터럽트만으로 급한 일이 해결된다
- RAM이 극도로 부족하다 (8~16KB)
RTOS 검토
- 작업의 응답 요구가 크게 다르다 (1ms vs 1s)
- 블로킹 대기가 많다 (통신 응답 대기 등)
- 상태 머신이 관리 불가능할 만큼 복잡해졌다
- 여러 프로토콜 스택을 동시에 (TCP/IP + BLE + USB)
"상태 머신이 감당이 안 된다" 는 것이 가장 현실적인 전환 신호다. 블로킹 코드를 상태 머신으로 쪼개는 작업 자체가 큰 비용이 되기 때문이다.
실무의 현실
대부분의 양산 임베디드 제품이 여전히 베어메탈이다. 가전, 센서 노드, 모터 컨트롤러, 배터리 관리 등 단일 목적 기기는 슈퍼루프 + 인터럽트로 충분하고, 그것이 더 검증하기 쉽다.
RTOS는 필요할 때 쓰는 도구이지 발전된 형태가 아니다.