sleep vs delay 그리고 Hz와 ms의 관계

개요

로봇 제어 코드를 작성하다 보면 **"일정 시간 대기"**가 반드시 필요합니다. Arduino에서는 delay(), ROS2에서는 sleep()을 사용하는데, 이름은 비슷해도 동작 방식이 완전히 다릅니다. 이 글에서는 두 함수의 차이점과, 제어 루프의 핵심 개념인 **Hz(주파수)**와 **ms(주기)**의 관계를 정리합니다.


sleep vs delay — 블로킹과 양보의 차이

  • delay() (Arduino): CPU를 점유한 채 대기 (블로킹)
  • sleep() (ROS2/Linux): OS에 CPU를 반납하고 대기 (논블로킹)

한 줄 요약: delay는 CPU를 멈추고, sleep은 CPU를 양보합니다.

항목delay() (Arduino)sleep() (ROS2/Linux)
CPU 사용붙잡고 있음 (독점)반납 (다른 일 가능)
멀티태스킹불가가능
콜백 처리멈춤 (인터럽트 제외)계속 동작
OS 존재없음 (베어메탈)있음 (Linux)
코어싱글 코어멀티 코어 가능

sequenceDiagram
    participant A as Arduino (delay)
    participant CPU_A as CPU
 
    A->>CPU_A: delay(33) 호출
    Note over CPU_A: 33ms 동안 아무것도 못함
    Note over CPU_A: 인터럽트 외 모든 처리 중단
    CPU_A-->>A: 33ms 후 다음 줄 실행
 
    participant R as ROS2 Node (sleep)
    participant CPU_R as CPU
    participant OS as Linux OS
 
    R->>OS: sleep(0.033) 호출
    OS->>CPU_R: CPU 반납, 다른 콜백 처리
    Note over CPU_R: joint_states 콜백 등 실행
    OS-->>R: 33ms 후 깨움

// Arduino: delay() 사용 — CPU가 완전히 멈춤
void loop() {
    digitalWrite(LED_PIN, HIGH);  // LED 켜기
    delay(1000);                  // 1초 대기 (이 동안 아무것도 못함)
    digitalWrite(LED_PIN, LOW);   // LED 끄기
    delay(1000);                  // 1초 대기
    // delay 중에는 센서 읽기, 통신 등 모두 불가!
}

import rclpy
from rclpy.node import Node
import time
 
class ControlNode(Node):
    def __init__(self):
        super().__init__('control_node')
        # 30Hz 제어 루프용 타이머
        self.timer = self.create_timer(1/30, self.control_loop)
        # joint_states 구독 — sleep 중에도 콜백 동작
        self.sub = self.create_subscription(
            JointState, '/joint_states', self.joint_callback, 10
        )
 
    def control_loop(self):
        # 제어 명령 전송
        self.publish_command()
        # sleep 중에도 joint_callback은 계속 호출됨
 
    def joint_callback(self, msg):
        # sleep과 무관하게 새 데이터가 들어올 때마다 실행
        self.current_joints = msg.position

실전 팁: ROS2에서 루프를 만들 때 while + sleep 패턴보다 create_timer()를 사용하는 것이 더 안전합니다. 타이머는 executor가 관리하므로 콜백 충돌 위험이 줄어듭니다.


Hz와 ms — 주파수와 주기의 관계

주기(ms)=1000주파수(Hz)\text{주기(ms)} = \frac{1000}{\text{주파수(Hz)}}

주파수(Hz)=1000주기(ms)\text{주파수(Hz)} = \frac{1000}{\text{주기(ms)}}

  • Hz (헤르츠): 1초에 몇 번 실행하는가 → 주파수
  • ms (밀리초): 한 번 실행하는 데 몇 밀리초가 걸리는가 → 주기

Hz와 ms는 같은 것을 반대 방향에서 표현한 것입니다. 역수 관계!

Hzms의미사용 예시
1 Hz1000 ms1초에 1번상태 모니터링
10 Hz100 ms1초에 10번센서 폴링
30 Hz33.3 ms1초에 30번로봇 제어 루프
50 Hz20 ms1초에 50번서보 모터 PWM
100 Hz10 ms1초에 100번IMU 데이터 수집
1000 Hz1 ms1초에 1000번고속 제어 시스템

# Hz → 초 변환하여 sleep에 사용
rate_hz = 30
sleep_sec = 1 / rate_hz        # 0.0333초
sleep_ms = 1000 / rate_hz      # 33.3ms
 
time.sleep(1/30)  # = sleep(0.0333초) = sleep(33.3ms) = 30Hz 루프
# ROS2에서 Rate 객체를 사용하는 방법
import rclpy
 
rclpy.init()
node = rclpy.create_node('example')
rate = node.create_rate(30)  # 30Hz Rate 생성
 
while rclpy.ok():
    # 제어 로직 실행
    do_something()
    rate.sleep()  # 남은 시간만큼만 대기하여 정확한 30Hz 유지

주의: time.sleep(1/30)은 실행 시간을 고려하지 않기 때문에 실제로는 30Hz보다 느려집니다. ROS2의 Rate.sleep()은 실행 시간을 빼고 남은 시간만 대기하므로 더 정확합니다.

flowchart LR
    Hz["주파수 (Hz)<br/>1초에 몇 번?"]
    ms["주기 (ms)<br/>한 번에 몇 ms?"]
 
    Hz -- "ms = 1000 / Hz" --> ms
    ms -- "Hz = 1000 / ms" --> Hz
 
    subgraph 예시: 30Hz 제어 루프
        H30["30 Hz"] --- M33["33.3 ms"]
    end

Arduino vs ROS2 — 왜 다를 수밖에 없는가

두 환경의 차이는 운영체제의 유무에서 비롯됩니다.

flowchart TB
    subgraph Arduino ["Arduino (베어메탈)"]
        direction TB
        A1[사용자 코드] --> A2[하드웨어 직접 제어]
        A2 --> A3["delay() = CPU 정지"]
        style A3 fill:#f96,stroke:#333
    end
 
    subgraph ROS2 ["ROS2 (Linux 위)"]
        direction TB
        R1[ROS2 노드] --> R2[Linux 커널]
        R2 --> R3[스케줄러]
        R3 --> R4["sleep() = 다른 프로세스 실행"]
        R3 --> R5[콜백 처리]
        R3 --> R6[토픽 수신]
        style R4 fill:#6c6,stroke:#333
    end
항목ArduinoROS2 (Linux)
OS없음Linux
스케줄러없음 (단일 루프)있음 (선점형 멀티태스킹)
대기 방식바쁜 대기 (busy wait)커널 타이머
적합한 작업단순 센서/액추에이터복잡한 로봇 시스템

정리

개념핵심
delay()CPU 독점, 모든 것 멈춤 (Arduino)
sleep()CPU 양보, 다른 작업 계속 가능 (ROS2)
Hz1초당 실행 횟수 (주파수)
ms1회 실행 소요 시간 (주기)
변환 공식ms=1000/Hz\text{ms} = 1000 / \text{Hz}
  • Arduino의 delay()와 ROS2의 sleep()"대기"라는 목적은 같지만, 블로킹 여부가 완전히 다릅니다
  • 제어 루프 설계 시 목표 Hz를 먼저 정하고, 이를 ms로 변환하여 타이머/sleep에 적용하세요
  • ROS2에서는 time.sleep() 대신 **create_timer() 또는 Rate.sleep()**을 사용하는 것이 권장됩니다