피지컬컴퓨팅

아두이노 delay() 없이 멀티태스킹 하는 법 - millis() 완전 정복

태지쌤 2026. 9. 15. 12:12
반응형

https://link.coupang.com/a/g3qhirvtFk

 

 

NEW 아두이노 올인원 키트 V2 단계별 강의자료 소스코드 제공 - 액세서리 | 쿠팡

쿠팡에서 NEW 아두이노 올인원 키트 V2 단계별 강의자료 소스코드 제공 구매하고 더 많은 혜택을 받으세요! 지금 할인중인 다른 액세서리 제품도 바로 쿠팡에서 확인할 수 있습니다.

www.coupang.com

 

 

아두이노 delay() 없이 멀티태스킹 하는 법 - millis() 완전 정복

아두이노로 LED도 깜빡이고 버튼도 확인하고 센서도 읽고 싶은데, delay() 때문에 코드가 순서대로 하나씩만 실행돼서 답답했던 적 있으신가요? 이 글에서는 아두이노 millis() 함수를 이용한 멀티태스킹(non-blocking) 구현 방법을 예제 코드와 함께 정리해봤습니다.

1. delay()의 치명적인 문제점

아두이노를 처음 배우면 누구나 이렇게 씁니다.

void loop() {
  digitalWrite(LED_BUILTIN, HIGH);
  delay(1000);
  digitalWrite(LED_BUILTIN, LOW);
  delay(1000);
}

문제는 delay(1000)이 실행되는 1초 동안 아두이노가 완전히 멈춘다는 점입니다. 이 시간 동안에는:

  • 버튼을 눌러도 반응하지 않음
  • 센서 값을 읽지 못함
  • 다른 LED를 동시에 다른 주기로 깜빡일 수 없음

delay()를 쓰는 순간, 그 시간만큼 아두이노는 "일시정지" 상태가 됩니다. 로봇이나 여러 장치를 동시에 제어해야 하는 프로젝트에서는 이게 치명적인 병목이 됩니다.

2. millis() 함수란?

millis()아두이노가 켜진 후부터 지금까지 흐른 시간을 밀리초(ms) 단위로 반환하는 함수입니다. delay()처럼 실행을 멈추지 않고, 그냥 "지금 몇 시인지" 시계를 보듯이 값을 읽어올 뿐입니다.

unsigned long now = millis();
Serial.println(now); // 예: 15234 (15.234초 경과)

이 함수를 이용하면 "특정 시간이 지났는지"만 계속 확인하면서, 그 사이에는 다른 작업을 자유롭게 실행할 수 있습니다.

3. 기본 원리 - Blink Without Delay 패턴

아두이노 공식 예제로도 유명한 "Blink Without Delay" 패턴입니다.

const int ledPin = 13;
const long interval = 1000;   // 깜빡임 간격 (1초)

int ledState = LOW;
unsigned long previousMillis = 0;

void setup() {
  pinMode(ledPin, OUTPUT);
}

void loop() {
  unsigned long currentMillis = millis();

  if (currentMillis - previousMillis >= interval) {
    previousMillis = currentMillis;      // 마지막 실행 시각 갱신
    ledState = !ledState;                // LED 상태 반전
    digitalWrite(ledPin, ledState);
  }

  // 여기에 다른 코드(버튼 체크, 센서 읽기 등)를 자유롭게 추가 가능!
}

동작 원리 쉽게 이해하기

비유하자면 delay()는 "알람 맞추고 눈 감고 가만히 기다리는 것"이고, millis() 방식은 "손목시계를 계속 흘끗흘끗 보면서, 시간이 됐을 때만 행동하는 것"입니다. loop()가 아주 빠르게(초당 수천~수만 번) 반복 실행되면서, 매번 "1초 지났나?"만 확인하고, 다른 시간에는 그냥 통과합니다.

구분 delay() millis()
실행 중 다른 작업 불가능 (완전 정지) 가능 (동시 진행)
여러 주기 동시 제어 어려움 쉬움
코드 복잡도 낮음 약간 높음
실전 활용도 간단한 테스트용 실무/로봇 프로젝트 필수

4. 여러 작업을 동시에: LED 2개 + 버튼 예제

millis() 패턴의 진짜 힘은 서로 다른 주기를 가진 여러 작업을 동시에 처리할 수 있다는 점입니다.

unsigned long prevLed1 = 0, prevLed2 = 0;
const long interval1 = 500;   // LED1: 0.5초 주기
const long interval2 = 1200;  // LED2: 1.2초 주기
int led1State = LOW, led2State = LOW;

void loop() {
  unsigned long now = millis();

  // LED1 제어 (0.5초마다 반전)
  if (now - prevLed1 >= interval1) {
    prevLed1 = now;
    led1State = !led1State;
    digitalWrite(2, led1State);
  }

  // LED2 제어 (1.2초마다 반전) - LED1과 완전히 독립적으로 동작
  if (now - prevLed2 >= interval2) {
    prevLed2 = now;
    led2State = !led2State;
    digitalWrite(3, led2State);
  }

  // 버튼 체크도 매 루프마다 지연 없이 즉시 반응
  if (digitalRead(4) == HIGH) {
    // 버튼 눌림 처리
  }
}

이렇게 하면 두 LED가 서로 다른 주기로 동시에 깜빡이면서, 버튼도 지연 없이 즉시 반응합니다. delay()만으로는 절대 구현할 수 없는 구조입니다.

5. 실무 팁 - millis() 사용 시 주의할 점

  • 변수 타입은 반드시 unsigned long: millis() 반환값은 시간이 지나면 매우 커지므로 int로 선언하면 오버플로우가 발생합니다.
  • 오버플로우 걱정은 안 해도 됨: unsigned long은 약 49일 후 0으로 되돌아가는데, 현재시간 - 이전시간 방식은 오버플로우가 나도 계산이 자동으로 맞아떨어지도록 설계되어 있어 실무에서는 신경 쓸 필요가 거의 없습니다.
  • 모터/서보 제어와 결합: 로봇 프로젝트에서는 센서 읽기, 모터 속도 조절, LED 표시를 각각 다른 interval로 millis() 패턴을 병렬로 두면 하나의 loop() 안에서 자연스럽게 "멀티태스킹처럼" 동작합니다.

마무리

아두이노는 하드웨어적으로 멀티스레딩을 지원하지 않지만, millis()를 활용한 non-blocking 패턴만 잘 익혀도 사실상 여러 작업을 동시에 처리하는 것과 같은 효과를 낼 수 있습니다. LED 깜빡임부터 시작해서, 점차 모터 제어·센서 읽기·통신 처리까지 이 패턴으로 확장해보시면 코드 구조가 훨씬 유연해지는 걸 느끼실 수 있을 거예요.

#아두이노 #Arduino #millis #아두이노멀티태스킹 #아두이노강좌 #논블로킹코드 #아두이노delay #아두이노팁 #임베디드프로그래밍 #아두이노프로젝트

반응형