본문 바로가기

개발자/Arduino

아두이노에서 멀티태스킹 구현하기 1 - Multi-tasking the arduino : Blink without delay

 

 

아두이노에서 멀티태스킹 구현하기 1 - Multi-tasking the arduino : Blink without delay

 

아두이노 는 단순하고 파워풀한 프로세서이다. 운영체제가 없고 한번에 하나의 일만 처리한다. 보통 delay() 함수를 사용해 프로그램을 잠시 기다리는 방법을 사용한다. 그러나 이것은 세상을 멈추는 일이다. delay()를 사용하는 것은 프로세서를 단일한 방향으로만 동작하게 하고, 바쁘니까 기다리라고 하는 것이다. delay 가 수행되는 동안에는 어떤 입력도 받을 수 없고, 출력도 할 수 없다. 100% 프로세서에 물려있고, delay를 사용하는 동안은 물속에서 죽어 있는 것과 다름없다. (표현 좋네. ^^)

 

본 자료의 이미지와 코드 출처는 https://learn.adafruit.com/multi-tasking-the-arduino-part-1/ 임을 밝혀둔다.

 

우리는 첫번째로 delay() 를 사용하지 않는 프로그램을 만들어 보는 것으로 시작한다.

 

아래는 가장 먼저 실습하게 되는 Blink 프로그램이다.

/*
  Blink
  Turns on an LED on for one second, then off for one second, repeatedly.
 
  This example code is in the public domain.
*/
 
// Pin 13 has an LED connected on most Arduino boards.
// give it a name:
int led = 13;
 
// the setup routine runs once when you press reset:
void setup() 
{                
    // initialize the digital pin as an output.
      pinMode(led, OUTPUT);     
}
 
// the loop routine runs over and over again forever:
void loop() 
{
    digitalWrite(led, HIGH);   // turn the LED on (HIGH is the voltage level)
      delay(1000);               // wait for a second
      digitalWrite(led, LOW);    // turn the LED off by making the voltage LOW
      delay(1000);               // wait for a second
}
    
 

이코드에서 처리 시간의 대부분을 delay 에서 기다린다. 아두이노 프로세서는 Blink 하는 동안 아무일도 할 수 없다. 

 

여기에 서보모터를 동작하도록 코드를 추가해도 기다리는 것은 마찬가지고, 동시에 Blink 동작과 서보모터를 제어하는 일을 수행할 수는 없다. 

#include <Servo.h> 
 
// Pin 13 has an LED connected on most Arduino boards.
// give it a name:
int led = 13;
 
Servo myservo;  // create servo object to control a servo 
                // twelve servo objects can be created on most boards
 
int pos = 0;    // variable to store the servo position 
 
void setup() 
{ 
      // initialize the digital pin as an output.
      pinMode(led, OUTPUT);     
      myservo.attach(9);  // attaches the servo on pin 9 to the servo object 
} 
 
void loop() 
{ 
      digitalWrite(led, HIGH);   // turn the LED on (HIGH is the voltage level)
      delay(1000);               // wait for a second
      digitalWrite(led, LOW);    // turn the LED off by making the voltage LOW
      delay(1000);               // wait for a second
  
      for(pos = 0; pos <= 180; pos += 1) // goes from 0 degrees to 180 degrees 
      {                                  // in steps of 1 degree 
        myservo.write(pos);              // tell servo to go to position in variable 'pos' 
        delay(15);                       // waits 15ms for the servo to reach the position 
      } 
      for(pos = 180; pos>=0; pos-=1)     // goes from 180 degrees to 0 degrees 
      {                                
        myservo.write(pos);              // tell servo to go to position in variable 'pos' 
        delay(15);                       // waits 15ms for the servo to reach the position 
      } 
} 

 

시간을 맞추기 위해 millis() 함수를 사용하고, 시간 감시자가 되어보자. 아래 프로그램이 delay() 를 없앤 소스코드이다. 이름하여 BlinkWithoutDelay 라고 하자. 프로그램은 언듯 보기에 더 복잡하게 구현한듯 보이지만 여기서 중요한 개념인 State Machine 의 모습을 볼 수 있다.

 

 

 

/* Blink without Delay
 
 Turns on and off a light emitting diode(LED) connected to a digital  
 pin, without using the delay() function.  This means that other code
 can run at the same time without being interrupted by the LED code.
 
 The circuit:
 * LED attached from pin 13 to ground.
 * Note: on most Arduinos, there is already an LED on the board
 that's attached to pin 13, so no hardware is needed for this example.
 
 
 created 2005
 by David A. Mellis
 modified 8 Feb 2010
 by Paul Stoffregen
 
 This example code is in the public domain.
 
 
 http://www.arduino.cc/en/Tutorial/BlinkWithoutDelay
 */
 
// constants won't change. Used here to 
// set pin numbers:
const int ledPin =  13;      // the number of the LED pin
 
// Variables will change:
int ledState = LOW;             // ledState used to set the LED
long previousMillis = 0;        // will store last time LED was updated
 
// the follow variables is a long because the time, measured in miliseconds,
// will quickly become a bigger number than can be stored in an int.
long interval = 1000;           // interval at which to blink (milliseconds)
 
void setup() 
{
      // set the digital pin as output:
      pinMode(ledPin, OUTPUT);      
}
 
void loop()
{
      // here is where you'd put code that needs to be running all the time.
 
      // check to see if it's time to blink the LED; that is, if the 
      // difference between the current time and last time you blinked 
      // the LED is bigger than the interval at which you want to 
      // blink the LED.
      unsigned long currentMillis = millis();
 
     if(currentMillis - previousMillis > interval) 
    {
        // save the last time you blinked the LED 
        previousMillis = currentMillis;   
 
        // if the LED is off turn it on and vice-versa:
        if (ledState == LOW)
              ledState = HIGH;
        else
              ledState = LOW;
 
        // set the LED with the ledState of the variable:
        digitalWrite(ledPin, ledState);
      }
}
 

 

위 코드는 어떻게 동작하는가 ? 시간이 1초가 지날 때마다 millis 함수를 이용해서 시간을 얻어와 1초가 지나는 것을 감지하면 LED 의 이전 상태를 반대로 바꾸어주는 코드이다.

자 이제 앞서 살펴본 상태 기계(State Machine)를 살펴보자. 그리고 LED 가 켜진 시간과 꺼진 시간을 다르게 하는것을 프로그램해보자. 아래 소스코드를 살펴보면 On time 과 Off time 이 다르게 성정되어 동작하는 것을 볼 수있다. 이것을 우리는 FlashWithoutDelay 라고 부르자.

// These variables store the flash pattern
// and the current state of the LED
 
int ledPin =  13;      // the number of the LED pin
int ledState = LOW;             // ledState used to set the LED
unsigned long previousMillis = 0;        // will store last time LED was updated
long OnTime = 250;           // milliseconds of on-time
long OffTime = 750;          // milliseconds of off-time
 
void setup() 
{
       // set the digital pin as output:
    pinMode(ledPin, OUTPUT);      
}
 
void loop()
{
       // check to see if it's time to change the state of the LED
       unsigned long currentMillis = millis();
 
       if((ledState == HIGH) && (currentMillis - previousMillis >= OnTime))
       {
        ledState = LOW;  // Turn it off
        previousMillis = currentMillis;  // Remember the time
        digitalWrite(ledPin, ledState);  // Update the actual LED
      }
      else if ((ledState == LOW) && (currentMillis - previousMillis >= OffTime))
      {
        ledState = HIGH;  // turn it on
        previousMillis = currentMillis;   // Remember the time
        digitalWrite(ledPin, ledState);      // Update the actual LED
      }
}

 

State + Machine = State Machine

 

LED가 켜져 있는지 또는 꺼져 있는지를 추적하는 변수(ledstate)가 있. 그리고 마지막 변경이 언제 일어 났는지를 추적하는 변수(previousMillis) 이 있다. 이 두가지 변수가 State Machine 의 주요 부분이다. 

그리고 아래에 현재 LED의 상태를 살펴보고 언제 변경해야 하는지, 어떻 상태로 변경을 해야 하는지를 결정하는 코드가 있다. 그 부분이 기계 부분이다. 루프를 통과 할 때마다 우리는 머신을 실행하고 머신은 상태를 업데이트한다. 

 

아래에서 우리는 다음으로 여러 상태 머신을 결합하여 동시에 실행할 수있는 방법을 살펴 보자.

 

위에서 우리는 LED 가 ON 상태인지 OFF 상태인지를 추적하는 변수를 가졌다. 그리고 LED의 변화가 언제 발생했는지를 추적하는 변수도 가졌다. 이것이 State Machine 의 상태(State) 부분이다.

 

상태와 변화될 때를 결정하는 코드를 가지고 있는데 이게 머신 부분이다. 아래에서 우리는 여러개의 상태 머신과 동시에 작동시키는 방법을 알아본다.

 

LED두 개를 연결하여 멀티 태스킹을 시험하기로 하자. 두번째의 LED 를 연결하여 첫 번째 LED 와 완전히 다른 속도로 동작하는 두번째 State Machine 을 만들어 본다.

 

 

  

// These variables store the flash pattern
// and the current state of the LED
 
int ledPin1 =  12;      // the number of the LED pin
int ledState1 = LOW;             // ledState used to set the LED
unsigned long previousMillis1 = 0;        // will store last time LED was updated
long OnTime1 = 250;           // milliseconds of on-time
long OffTime1 = 750;          // milliseconds of off-time
 
int ledPin2 =  13;      // the number of the LED pin
int ledState2 = LOW;             // ledState used to set the LED
unsigned long previousMillis2 = 0;        // will store last time LED was updated
long OnTime2 = 330;           // milliseconds of on-time
long OffTime2 = 400;          // milliseconds of off-time
 
void setup() 
{
  // set the digital pin as output:
  pinMode(ledPin1, OUTPUT);      
  pinMode(ledPin2, OUTPUT);      
}
 
void loop()
{
  // check to see if it's time to change the state of the LED
  unsigned long currentMillis = millis();
 
  if((ledState1 == HIGH) && (currentMillis - previousMillis1 >= OnTime1))
  {
    ledState1 = LOW;  // Turn it off
    previousMillis1 = currentMillis;  // Remember the time
    digitalWrite(ledPin1, ledState1);  // Update the actual LED
  }
  else if ((ledState1 == LOW) && (currentMillis - previousMillis1 >= OffTime1))
  {
    ledState1 = HIGH;  // turn it on
    previousMillis1 = currentMillis;   // Remember the time
    digitalWrite(ledPin1, ledState1);      // Update the actual LED
  }
  
  if((ledState2 == HIGH) && (currentMillis - previousMillis2 >= OnTime2))
  {
    ledState2 = LOW;  // Turn it off
    previousMillis2 = currentMillis;  // Remember the time
    digitalWrite(ledPin2, ledState2);  // Update the actual LED
  }
  else if ((ledState2 == LOW) && (currentMillis - previousMillis2 >= OffTime2))
  {
    ledState2 = HIGH;  // turn it on
    previousMillis2 = currentMillis;   // Remember the time
    digitalWrite(ledPin2, ledState2);      // Update the actual LED
  }
}

 

이러한 방법으로 메모리 또는 GPIO 핀이 부족할 때까지 더 많은 상태 시스템을 추가 할 수 있다. 각 상태 머신은 자체 플래시 on off 속도를 가질 수 있다. 연습으로 위의 코드를 편집하여 세 번째 상태 시스템을 추가할 수 도 있다. 아래만 잘 지켜서 코딩하면 말이다.

 

- 먼저 모든 상태 변수와 코드를 하나의 상태 시스템에서 복제하십시오.

- 그런 다음 모든 변수의 이름을 다시 지정하여 첫 번째 시스템과의 충돌을 피하십시오.

 

위와 같은 방법이 어렵지는 않지만 효율적으로 보이진 않는다. 같은 코드를 반복해서 쓰는 것이 다소 낭비되는 것처럼 보인다. 그럼 도 효율적인 방법을 알아보자. 

 

이러한 복잡성을 관리하는 더 좋은 방법, 더 간단하고 효율적인 프로그래밍 기법을 알아보자.

 

마지막 스케치를 다시 한 번 살펴 보면 코드들이 매우 반복적이다. 같은 코드는 각 깜박이는 LED 거의 그대로 복제돈다. 유일한 변화는 varable 이름이다. 우리가 만들 코드는 약간의 객체 지향 프로그래밍 (OOP Objective Oriented Programming)에 대한 중요 개념이다.

 

루프에 작은 OOP를 넣자.

 

아두이노 언어는 객체 지향 프로그래밍을 지원하는 C ++의 변종이다. 언어의 OOP 기능을 사용하면 우리는 C++ 클래스로 LED 점멸 동작과 함께 상태 변수 및 기능 모두를 수집 할 수 있다. 이것은 어렵지 않다. 우리는 이미 모든 코드를 작성했으므로 클래스로 다시 패키지 하면 간단하게 할 수 있다.

클래스 정의 :

 

우리는 "깜박임" 클래스를 선언함으로써 시작한다. 그런 다음 FlashWithoutDelay의 모든 변수를 클래스에 추가한다. 이러한 변수는 클래스의 일부이기 때문에 멤버변수 라고 한다.

class Flasher
{
    // Class Member Variables
    // These are initialized at startup
    int ledPin;      // the number of the LED pin
    long OnTime;     // milliseconds of on-time
    long OffTime;    // milliseconds of off-time
 
    // These maintain the current state
    int ledState;                     // ledState used to set the LED
    unsigned long previousMillis;      // will store last time LED was updated
};

 

다음은 컨스트럭터, 즉 생성자라는 것을 추가한다. 생성자는 클랫,와 똑같은 이름이고, 하는 일은 모든 변수들을 초기화 시키는 일을 한다.

class Flasher
{
    // Class Member Variables
    // These are initialized at startup
    int ledPin;      // the number of the LED pin
    long OnTime;     // milliseconds of on-time
    long OffTime;    // milliseconds of off-time
 
    // These maintain the current state
    int ledState;                     // ledState used to set the LED
    unsigned long previousMillis;      // will store last time LED was updated
 
  // Constructor - creates a Flasher 
  // and initializes the member variables and state
      public:
      Flasher(int pin, long on, long off)
      {
        ledPin = pin;
        pinMode(ledPin, OUTPUT);     
      
        OnTime = on;
        OffTime = off;
    
        ledState = LOW; 
        previousMillis = 0;
      }
};

 

마지막으로 loop 에 들어가는 코드를 Update() 라고 부르는 멤버함수 안으로 가져온다. 이것은 초기 void loop() 내용과 동일하다. 단지 이름만 바뀌었을 뿐이다. 

class Flasher
{
    // Class Member Variables
    // These are initialized at startup
    int ledPin;      // the number of the LED pin
    long OnTime;     // milliseconds of on-time
    long OffTime;    // milliseconds of off-time
 
    // These maintain the current state
    int ledState;                     // ledState used to set the LED
    unsigned long previousMillis;      // will store last time LED was updated
 
  // Constructor - creates a Flasher 
  // and initializes the member variables and state
  public:
  Flasher(int pin, long on, long off)
  {
    ledPin = pin;
    pinMode(ledPin, OUTPUT);     
      
    OnTime = on;
    OffTime = off;
    
    ledState = LOW; 
    previousMillis = 0;
  }
 
  void Update()
  {
    // check to see if it's time to change the state of the LED
    unsigned long currentMillis = millis();
     
    if((ledState == HIGH) && (currentMillis - previousMillis >= OnTime))
    {
        ledState = LOW;  // Turn it off
      previousMillis = currentMillis;  // Remember the time
      digitalWrite(ledPin, ledState);  // Update the actual LED
    }
    else if ((ledState == LOW) && (currentMillis - previousMillis >= OffTime))
    {
      ledState = HIGH;  // turn it on
      previousMillis = currentMillis;   // Remember the time
      digitalWrite(ledPin, ledState);      // Update the actual LED
    }
  }
};

 

단순히 이미 작성한 코드들을 Flasher 클래스 안으로 재배치 한 것이다. LED 를 깜박이게 하는 상태 변수들과 함수들을 캡슐화(Encapsulation) 한 것이다.

  

자 이제 사용해 보자.

 

깜빡 거리게 하고 싶은 모든 LED에 대해 생성자(Constructor)를 호출하여 Flasher 클래스의 인스턴스를 만든다. 루프를 통과 할 때마다 Flasher의 각 인스턴스에 대해 Update ()를 호출하면 된다. 더 이상 전체 상태 코드를 복제 할 필요가 없다. 단지 Flasher 클래스의 또 다른 인스턴스를 요구하면 그때 만들어주면 된다.

 

각각의 추가 LED는 단지 두 줄의 코드만 필요하다. 이 코드는 더 짧고 읽기 쉽다. 그리고 중복된 코드가 없기 때문에 더 작게 컴파일된다. 이건 아주 중요하다.

class Flasher
{
    // Class Member Variables
    // These are initialized at startup
    int ledPin;      // the number of the LED pin
    long OnTime;     // milliseconds of on-time
    long OffTime;    // milliseconds of off-time
 
    // These maintain the current state
    int ledState;                     // ledState used to set the LED
    unsigned long previousMillis;      // will store last time LED was updated
 
  // Constructor - creates a Flasher 
  // and initializes the member variables and state
  public:
  Flasher(int pin, long on, long off)
  {
    ledPin = pin;
    pinMode(ledPin, OUTPUT);     
      
    OnTime = on;
    OffTime = off;
    
    ledState = LOW; 
    previousMillis = 0;
  }
 
  void Update()
  {
    // check to see if it's time to change the state of the LED
    unsigned long currentMillis = millis();
     
    if((ledState == HIGH) && (currentMillis - previousMillis >= OnTime))
    {
        ledState = LOW;  // Turn it off
      previousMillis = currentMillis;  // Remember the time
      digitalWrite(ledPin, ledState);  // Update the actual LED
    }
    else if ((ledState == LOW) && (currentMillis - previousMillis >= OffTime))
    {
      ledState = HIGH;  // turn it on
      previousMillis = currentMillis;   // Remember the time
      digitalWrite(ledPin, ledState);      // Update the actual LED
    }
  }
};
 
 
Flasher led1(12, 100, 400);
Flasher led2(13, 350, 350);
 
void setup()
{
}
 
void loop()
{
    led1.Update();
    led2.Update();
}

 

다음 연재글 참고

 

아두이노에서 멀티태스킹 구현하기 1 - Multi-tasking the arduino : Blink without delay  http://fishpoint.tistory.com/2095

 

아두이노에서 멀티태스킹 구현하기 2 - Multi-tasking the arduino : Blink without delay

http://fishpoint.tistory.com/2096

 

아두이노에서 멀티태스킹 구현하기 3 - Multi-tasking the arduino : Blink without delay

http://fishpoint.tistory.com/2106

 

 

 

 

 

 

더욱 좋은 정보를 제공하겠습니다.~ ^^

이 글 공유하기

facebook twitter kakaoTalk kakaostory naver band
  • 오타가 있는 것 같네요.

    delay 가 수행되는 동안에는 어쩐 압력도 받을 수 없고, 출력할 수 없다.

    압력 아니고 입력 이겠죠?

  • ㅇㅇ 2018.07.30 05:44 댓글주소 수정/삭제 댓글쓰기

    안녕하세요 질문이하나있는데요
    pwm핀을써서 led를 점점밝게 점점어둡게하는걸 2개의 led를 사용해서 각각 다른패턴으로 켜졌다 꺼지게 코딩하고싶은데 많이배우지를못해서 감이없어요

    int LED1count = 5;
    int LED2count = 10;
    int a=0;
    int i=0;

    void setup()
    {
    pinMode(10, OUTPUT); // 디지털 1번핀을 출력모드로 설정.
    pinMode(11, OUTPUT); // 디지털 2번핀을 출력모드로 설정.
    }

    void loop()
    {
    for (int a = 0; a<250; a++)
    {

    if(a + LED1count == 250 )
    {
    analogWrite(10,a - 1);
    }
    else
    {
    analogWrite(10,a + 1);
    }

    if(i + LED2count == 255 )
    {
    analogWrite(11, i - 5 );
    }
    else
    {
    analogWrite(11, i + 5 );
    }
    delay(50);

    }
    }

    이런식으로 코딩을 했는데 동작도 시원찮아서 질문드려요 ㅠㅠ
    혹시 답변같은거 dlawnsals@gmail.com으로 보내주실수 있나요?

  • 상상 2018.07.30 12:28 댓글주소 수정/삭제 댓글쓰기

    이메일 보냈습니다. 참고하세요.^^

  • 본문은 왼쪽정렬이고 코드가 가운데정렬이라...