아두이노는 물질 세계와 연결된 센서와 입출력 장치를 다루는 일을 합니다.다양한 상황에 적각적으로 대응할 수 있도록 프로그램 논리를 구성해야 합니다.
아두이노 13번핀에 led가 달려있고,Serial을 통해서 led가 깜빡거려야할 시간을 입력 받는 스케치입니다.
예를 들어 시리얼 모니터에 “s 10″을 입력하면 10초 동안 깜빡거리게 됩니다.
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 |
const int ledPin = 13; char c; int sec = 0; bool ledOn = false; void setup() { // put your setup code here, to run once: Serial.begin(9600); pinMode(ledPin,OUTPUT); } void loop() { // put your main code here, to run repeatedly: if(Serial.available()) { c = Serial.read(); if (c=='s') { sec = Serial.parseInt(); Serial.print("s="); Serial.println(sec); } } if (sec > 0 ) { for(int i = 0; i<sec;i++) { if (ledOn) digitalWrite(ledPin,LOW); else digitalWrite(ledPin,HIGH); delay(1000); ledOn = !ledOn; } sec = 0; Serial.println("blink end"); } } |
위 스케치의 문제는 무엇일까요?
무엇보다도 led가 깜빡거리는 동안에는 Serial을 통한 입력이 먹통이 된다는 사실입니다.
제일 문제가 되는 문장은 “delay(1000)”입니다.아무 일도 안하고 1초동안 그냥 대기하는 거죠.하나의 led를 다룰때는 문제가 되지 않지만 다른 일을 동시에 해야할 때는 치명적인 결함이 됩니다.
아무런 비판없이 “delay()”를 사용하지만,본격적으로 스케치를 작성할 때는 가능한 사용하지 말아야 합니다.피할 수 없다면 꼭 필요할 때 아주 짧은 시간 동안만 사용해야 합니다.
복잡한 스케치에는 여러 개의 플래그가 사용됩니다.플래그에 좋은 이름을 붙이면 스케치의 가독성이 높아집니다.
이전과 똑 같이 아두이노 13번핀에 led가 달려있고,Serial을 통해서 led가 깜빡거려야할 시간을 입력 받는 스케치입니다.
예를 들어 시리얼 모니터에 “s 10″을 입력하면 10초 동안 깜빡거리게 됩니다.
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 |
const int ledPin = 13; char c; int sec = 0; bool ledOn = false; unsigned long thisMil,nextMil; void setup() { // put your setup code here, to run once: Serial.begin(9600); pinMode(ledPin,OUTPUT); nextMil = millis() + 1000; // 1초뒤 } void loop() { // put your main code here, to run repeatedly: thisMil = millis(); if(Serial.available()) { c = Serial.read(); if (c=='s') { sec = Serial.parseInt(); Serial.print("s="); Serial.println(sec); } } if (thisMil < nextMil) return; else nextMil = thisMil + 1000; if (sec > 0 ) { if (ledOn) digitalWrite(ledPin,LOW); else digitalWrite(ledPin,HIGH); ledOn = !ledOn; sec--; if (sec == 0) Serial.println("blink end"); } } |
millis()는 대단히 많이 사용되는 함수입니다.1초에 1000만큼 증가하므로 만약 1초 후에 어떤 일을 하고 싶으면 다음 시간은 “millis()+1000″이 됩니다.
1 2 3 4 5 6 7 8 |
//실시간으로 처리할 //문장을 두면 됩니다. if (thisMil < nextMil) return; else nextMil = thisMil + 1000; //주기적으로 처리할 //문장을 두면 됩니다. |
이 문장 형식은 주기적으로 수행할 문장의 입구에 배치하면 됩니다.이후의 문장은 1초에 한번씩만 수행하게 됩니다.
1초에 한번씩 led를 깜빡거리게 될뿐만 아니라 Serial을 통합 입력도 대기없이 가능합니다.
그러나 이와 달리 led제어가 좀 더 복잡해지거나 다른 센서까지 더해지면 1초 단위의 방식도 문제가 될 수 있습니다.
예를 들어 시리얼 모니터에 “s 10″을 입력하면 10초 동안 깜빡거리게 됩니다.
이 스케치의 특징은 제어를 시작하는 함수와 중간에 처리하는 함수를 각각 사용한다는 점입니다.
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 |
const int ledPin = 13; char c; int sec = 0; bool ledOn = false; unsigned long thisMil,nextMil; void setup() { // put your setup code here, to run once: Serial.begin(9600); pinMode(ledPin,OUTPUT); nextMil = millis() + 1000; // 1초뒤 } void loop() { // put your main code here, to run repeatedly: thisMil = millis(); ledUpdate(); if(Serial.available()) { c = Serial.read(); if (c=='s') { sec = Serial.parseInt(); Serial.print("s="); Serial.println(sec); ledStart(sec,800,200); } } if (thisMil < nextMil) return; else nextMil = thisMil + 1000; } //이하: ledStart와 ledUpdte를 위한 영역임 bool ledActive =false; int ledOnMil; int ledOffMil; unsigned long ledLastMil; void ledStart(int durationSec,int onMil,int offMil) { ledActive = true; ledLastMil = thisMil + durationSec * 1000; ledOnMil = onMil; ledOffMil = offMil; } void ledUpdate() { static bool ledFirst = true; static bool ledOn; static unsigned long ledNextMil; if (ledActive == false) return; if (thisMil > ledLastMil){ digitalWrite(ledPin,LOW); ledActive = false; return; } if (ledFirst == true) { ledOn = true; digitalWrite(ledPin,HIGH); ledNextMil = thisMil + ledOnMil; ledFirst = false; } else if (thisMil > ledNextMil && ledOn == true){ ledOn = false; digitalWrite(ledPin,LOW); ledNextMil = thisMil + ledOffMil; } else if (thisMil > ledNextMil && ledOn == false){ ledOn = true; digitalWrite(ledPin,HIGH); ledNextMil = thisMil + ledOnMil; } } //이상: ledStart와 ledUpdte를 위한 영역임 |
이 스케치는 백그라운드 방식으로 코딩하는 전형적인 예입니다.메인 로직과 상관없이 시간 지체없이 처리되어야 하는 일에 적합합니다.7 세그먼트 표시,led 제어,부저 제어와 같은 곳에 유용하게 사용할 수 있습니다.
1 2 3 4 |
bool ledActive =false; int ledOnMil; int ledOffMil; unsigned long ledLastMil; |
위 선언문은 ledStart()와 ledUpdate()에 공용으로 사용되는 변수 이므로 전역 변수로 선언했습니다.만약 led수가 여러 개이면 스케치를 어떻게 바꾸어야할까요?상당히 복잡한 문제입니다.그래서 오브젝트 방식의 프로그래밍이 필요합니다.
예를 들어 시리얼 모니터에 “s 10″을 입력하면 10초 동안 깜빡거리게 됩니다.
이 스케치의 특징은 오브젝트를 사용한다는 점입니다.
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 |
// 이하: class LedControl에 속함 class LedControl { public: LedControl(int pin); // constructor void ledStart(int durationSec,int onMil,int offMil); void ledUpdate(); private: int ledPin; int ledOnMil; int ledOffMil; unsigned long ledLastMil; volatile bool ledActive =false; volatile bool ledFirst; volatile bool ledOn; volatile unsigned long ledNextMil; volatile unsigned long thisMil; }; // ↑위 ;에 유의 // 아래 void 없음에 유의,constructor LedControl::LedControl(int pin) { ledPin = pin; pinMode(ledPin,OUTPUT); } void LedControl::ledStart(int durationSec,int onMil,int offMil) { ledFirst = true; ledActive = true; ledLastMil = millis() + durationSec * 1000; ledOnMil = onMil; ledOffMil = offMil; } void LedControl::ledUpdate() { thisMil = millis(); if (ledActive == false) return; if (thisMil > ledLastMil){ digitalWrite(ledPin,LOW); ledActive = false; return; } if (ledFirst == true) { ledOn = true; digitalWrite(ledPin,HIGH); ledNextMil = thisMil + ledOnMil; ledFirst = false; } else if (thisMil > ledNextMil && ledOn == true){ ledOn = false; digitalWrite(ledPin,LOW); ledNextMil = thisMil + ledOffMil; } else if (thisMil > ledNextMil && ledOn == false){ ledOn = true; digitalWrite(ledPin,HIGH); ledNextMil = thisMil + ledOnMil; } } // 이상: class LedControl에 속함 const int led1Pin = 13; const int led2Pin = 12; LedControl led1(led1Pin); LedControl led2(led2Pin); char c; int sec = 0; unsigned long thisMil,nextMil; void setup() { // put your setup code here, to run once: Serial.begin(9600); //pinMode(ledPin,OUTPUT); nextMil = millis() + 1000; // 1초뒤 } void loop() { // put your main code here, to run repeatedly: thisMil = millis(); led1.ledUpdate(); led2.ledUpdate(); if(Serial.available()) { c = Serial.read(); if (c=='s') { sec = Serial.parseInt(); Serial.print("s="); Serial.println(sec); led1.ledStart(sec,800,200); led2.ledStart(sec,300,300); } } if (thisMil < nextMil) return; else nextMil = thisMil + 1000; } |
이 스케치에는 LedControl이라는 class(클래스)를 이용해서 led1와 led2라는 object(객체)를 만들어서 사용하고 있습니다.오브젝트를 이용하면 스케치를 재사용하기가 쉬워집니다.
1 2 3 4 5 6 7 8 9 10 11 12 |
class LedControl { public: // 오브젝트 바깥에 알려지는 것 선언 LedControl(int pin); // constructor에는 void가 없다. void ledStart(int durationSec,int onMil,int offMil); void ledUpdate(); private: // 내부적으로 사용할 것 선언 int ledPin; bool ledActive =false; .... .... }; // class 뒤에는 ;가 있다. |
class에는 이름과 사용될 함수,변수를 선언합니다.public과 private의 차이에 주목합시다.마지막에 ;가 있어야 합니다.constructor는 오브젝트를 생성할 때 수행되는 함수입니다.return되는 것이 전혀 없습니다.void조차도 없음에 주의합시다.
아래는 class에 속한 함수를 나타냅니다.
1 2 3 4 5 6 7 |
void LedControl::ledStart(int durationSec,int onMil,int offMil) { ledFirst = true; ledActive = true; ledLastMil = millis() + durationSec * 1000; ledOnMil = onMil; ledOffMil = offMil; } |
일반적인 함수와 다른 점은 함수 이름 앞에 “class이름::”이 붙어 있다는 점입니다.함수는 여러 오브젝트에 공유되므로 값이 변하는 변수를 static으로 선언하면 곤란해집니다.
예를 들면 아두이노가 설치된 경로의 libraries폴더 아래에 LedControl이란 폴더를 만들고 class문 부분은 LedControl.h,관련 함수 부분은 LedControl.cpp로 만들어 보관 하면 됩니다.나중에 사용할 때는 스케치에서
1 |
#include |
를 삽입하면 됩니다.
아래는 LedControl.h의 전형적인 모양입니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
#ifndef LedControl_h_ #define LedControl_h_ class LedControl { public: LedControl(int pin); // constructor void ledStart(int durationSec,int onMil,int offMil); void ledUpdate(); private: int ledPin; int ledOnMil; int ledOffMil; unsigned long ledLastMil; volatile bool ledActive =false; volatile bool ledFirst; volatile bool ledOn; volatile unsigned long ledNextMil; volatile unsigned long thisMil; }; #endif |
다음에 유의하면 됩니다.
1 2 3 4 |
#ifndef LedControl_h_ #define LedControl_h_ // class의 내용이 여기에 옵니다 #endif |
아래는 LedControl.cpp의 전형적인 모양입니다.
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 |
#include "LedControl.h" #include LedControl::LedControl(int pin) { ledPin = pin; pinMode(ledPin,OUTPUT); } void LedControl::ledStart(int durationSec,int onMil,int offMil) { ledFirst = true; ledActive = true; ledLastMil = millis() + durationSec * 1000; ledOnMil = onMil; ledOffMil = offMil; } void LedControl::ledUpdate() { thisMil = millis(); if (ledActive == false) return; if (thisMil > ledLastMil){ digitalWrite(ledPin,LOW); ledActive = false; return; } if (ledFirst == true) { ledOn = true; digitalWrite(ledPin,HIGH); ledNextMil = thisMil + ledOnMil; ledFirst = false; } else if (thisMil > ledNextMil && ledOn == true){ ledOn = false; digitalWrite(ledPin,LOW); ledNextMil = thisMil + ledOffMil; } else if (thisMil > ledNextMil && ledOn == false){ ledOn = true; digitalWrite(ledPin,HIGH); ledNextMil = thisMil + ledOnMil; } } |
다음에 유의하면 됩니다.
1 2 3 |
#include "LedControl.h" #include // 함수들이 여기에 옵니다 |
예를 들어 시리얼 모니터에 “s 10″을 입력하면 10초 동안 깜빡거리게 됩니다.
이 스케치의 특징은 타이머 인터럽트를 사용한다는 점입니다.
아두이노에는 타이머가 세개 있습니다.타이머1과 타이머3은 16비트 타이머이고,타이머2는 8비트 타이머입니다.
아래 스케치는 타이머1 인터럽트를 이용해서 led를 백그라운드에서 처리하는 내용입니다.
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 |
const int led1Pin = 13; const int led2Pin = 12; const int led3Pin = 11; const int led4Pin = 10; #include LedControl led1(led1Pin); LedControl led2(led2Pin); LedControl led3(led3Pin); LedControl led4(led4Pin); #include char c; int sec = 0; unsigned long thisMil,nextMil; void setup() { // put your setup code here, to run once: Serial.begin(9600); Timer1.initialize(1000); Timer1.attachInterrupt(led_update); nextMil = millis() + 1000; // 1초뒤 } void loop() { // put your main code here, to run repeatedly: thisMil = millis(); //led1.ledUpdate(); //led2.ledUpdate(); //led3.ledUpdate(); //led4.ledUpdate(); if(Serial.available()) { c = Serial.read(); if (c=='s') { sec = Serial.parseInt(); Serial.print("s="); Serial.println(sec); led1.ledStart(sec,800,200); led2.ledStart(sec,500,500); led3.ledStart(sec,300,300); led4.ledStart(sec,100,100); } } if (thisMil < nextMil) return; else nextMil = thisMil + 1000; } void led_update(){ led1.ledUpdate(); led2.ledUpdate(); led3.ledUpdate(); led4.ledUpdate(); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
// 라이브러리를 삽입 #include void setup() { // 초기화: 이 경우는 1000마이크로초 (1/1000초) 마다 타이머 작동 Timer1.initialize(1000); // 타이머 작동할 때 사용할 함수(ISR,Interrupt Service Routine)지정 Timer1.attachInterrupt(led_update); } void loop() { // put your main code here, to run repeatedly: } void led_update(){ led1.ledUpdate(); led2.ledUpdate(); led3.ledUpdate(); led4.ledUpdate(); } |
댓글 남기기