본문 바로가기

개발자/Arduino

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




다음 연재글 참고


아두이노에서 멀티태스킹 구현하기 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



이전에 하던 작업을 깨끗하게 마무리 해보자. 일부 서보모터 제어코드에 동일한 원칙을 적용하고 몇 가지 추가적인 작업을 수행해 보자. 아래 그림과 같이 브레드 보드에 두 개의 서보모터를 연결하고, 가지고 있다면 세 번째 LED도 연결하자.


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




아래 코드는 표준 서보모터를 제어하는 코드다. 여기서는 두려움에 떠는 delay() 함수를 호출한다는 것에 주의하자. 깨끗한 상태 머신을 만들기 위해 필요한 부분을 가져갈 것이다. 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// Sweep
// by BARRAGAN <http://barraganstudio.com> 
// This example code is in the public domain.
 
 
#include <Servo.h> 
 
Servo myservo;  // create servo object to control a servo 
                // a maximum of eight servo objects can be created 
 
int pos = 0;    // variable to store the servo position 
 
void setup() 
  myservo.attach(9);  // attaches the servo on pin 9 to the servo object 
 
 
void loop() 
  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>=1; 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 
  } 
cs



아래의 Sweeper 클래스는 스윕 액션을 캡슐화하지만 타이밍에 millis( ) 함수를 사용한다. 이것은 Flasher 클래스가 LED에 대해 수행하는 것과 매우 비슷하다. 또한 서보를 특정 핀과 연결하기 위해 Attach( ) 및 Detach( ) 함수를 추가해야 한다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class Sweeper
{
  Servo servo;              // the servo
  int pos;              // current servo position 
  int increment;        // increment to move for each interval
  int  updateInterval;      // interval between updates
  unsigned long lastUpdate; // last update of position
 
public
  Sweeper(int interval)
  {
    updateInterval = interval;
    increment = 1;
  }
  
  void Attach(int pin)
  {
    servo.attach(pin);
  }
  
  void Detach()
  {
    servo.detach();
  }
  
  void Update()
  {
    if((millis() - lastUpdate) > updateInterval)  // time to update
    {
      lastUpdate = millis();
      pos += increment;
      servo.write(pos);
      Serial.println(pos);
      if ((pos >= 180|| (pos <= 0)) // end of sweep
      {
        // reverse direction
        increment = -increment;
      }
    }
  }
};
cs


얼마나 작업을 해야 할까 ?


아래의 코드를 보면 

이제 우리는 필요한 만큼의 Flashers와 Sweepers를 인스턴스화 할 수 있다. Flashers 각 인스턴스에는 2 줄의 코드가 필요하다.


- 인스턴스를 선언하는 한 줄 : Flasher led1(11, 123, 400);

- 루프에서 업데이트를 호출하는 한 줄 : led1.Update();


스위퍼의 각 인스턴스에는 다음과 같이 3 줄의 코드 만 있으면 된다.


- 인스턴스를 선언하는 하나 : Sweeper sweeper1(15);

- 하나를 설정에서 핀에 연결 : sweeper1.Attach(9);

- 그리고 루프에서 업데이트 할 하나의 호출 : sweeper1.Update();


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
#include <Servo.h> 
 
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
    }
  }
};
 
class Sweeper
{
  Servo servo;              // the servo
  int pos;              // current servo position 
  int increment;        // increment to move for each interval
  int  updateInterval;      // interval between updates
  unsigned long lastUpdate; // last update of position
 
public
  Sweeper(int interval)
  {
    updateInterval = interval;
    increment = 1;
  }
  
  void Attach(int pin)
  {
    servo.attach(pin);
  }
  
  void Detach()
  {
    servo.detach();
  }
  
  void Update()
  {
    if((millis() - lastUpdate) > updateInterval)  // time to update
    {
      lastUpdate = millis();
      pos += increment;
      servo.write(pos);
      Serial.println(pos);
      if ((pos >= 180|| (pos <= 0)) // end of sweep
      {
        // reverse direction
        increment = -increment;
      }
    }
  }
};
 
 
Flasher led1(11123400);
Flasher led2(12350350);
Flasher led3(13200222);
 
Sweeper sweeper1(15);
Sweeper sweeper2(25);
 
void setup() 
  Serial.begin(9600);
  sweeper1.Attach(9);
  sweeper2.Attach(10);
 
 
void loop() 
  sweeper1.Update();
  sweeper2.Update();
  
  led1.Update();
  led2.Update();
  led3.Update();
}
cs



이제 간섭이 없이 논스톱으로 실행되는 5 개의 독립적인 작업을 가지게 되었다. loop ()는 단지 5 줄의 코드다. 다음으로 이러한 작업 중 일부와 상호 작용할 수 있도록 버튼을 추가 할 것이다.


아래 그림을 구현하는 일이다.



아래 코드는 루프의 각 단계에서 단추 상태를 확인한다. 버튼을 누르면 Led1 및 sweeper2가 업데이트 되지 않는다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
#include <Servo.h> 
 
 
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
    }
  }
};
 
class Sweeper
{
  Servo servo;              // the servo
  int pos;              // current servo position 
  int increment;        // increment to move for each interval
  int  updateInterval;      // interval between updates
  unsigned long lastUpdate; // last update of position
 
public
  Sweeper(int interval)
  {
    updateInterval = interval;
    increment = 1;
  }
  
  void Attach(int pin)
  {
    servo.attach(pin);
  }
  
  void Detach()
  {
    servo.detach();
  }
  
  void Update()
  {
    if((millis() - lastUpdate) > updateInterval)  // time to update
    {
      lastUpdate = millis();
      pos += increment;
      servo.write(pos);
      Serial.println(pos);
      if ((pos >= 180|| (pos <= 0)) // end of sweep
      {
        // reverse direction
        increment = -increment;
      }
    }
  }
};
 
 
Flasher led1(11123400);
Flasher led2(12350350);
Flasher led3(13200222);
 
Sweeper sweeper1(15);
Sweeper sweeper2(25);
 
void setup() 
  Serial.begin(9600);
  sweeper1.Attach(9);
  sweeper2.Attach(10);
 
 
void loop() 
  sweeper1.Update();
  
  if(digitalRead(2== HIGH)
  {
     sweeper2.Update();
     led1.Update();
  }
  
  led2.Update();
  led3.Update();
cs



위 코드는 3 개의 LED가 자체 속도로 깜박인다. 2 명의 Sweeper 는 자신의 비율로 청소한다. 그러나 버튼을 누르면 sweeper2와 led1은 버튼을 놓을 때까지 멈추게 된다. 루프에 지연이 없으므로 버튼 입력에 거의 즉각적으로 반응하여 실행된다.


결 론 

이 가이드에서는 Arduino가 사용자 입력과 같은 외부 이벤트에 응답하면서 여러 독립적인 작업을 수행하는 것이 실제로 가능하다는 것을 보여주었다. 우리는 delay ( ) 대신 millis ( )를 사용하여 시간을 정하는 방법을 배웠고 다른 작업들을 하기 위해 프로세서를 놓아주는 방법을 알 수 있었다. 


우리는 태스크를 다른 상태 머신과 독립적으로 동시에 실행할 수있는 상태 머신으로 정의하는 방법을 배웠다. 그리고이 상태 머신을 C ++ 클래스로 캡슐화하여 코드를 간단하고 간결하게 유지하는 방법을 배웠다.

이 기술은 Arduino를 슈퍼 컴퓨터로 만들지 않지만, 작지만 놀랍도록 강력한 작은 프로세서를 최대한 활용할 수 있도록 도와준다.


이 연재의 2 부에서는 이러한 기술을 토대로 Arduino가 외부 이벤트에 반응하고 여러 작업을 관리하는 다른 방법을 알아보자.