[아두이노] [강좌] 27. 시간 관련 함수 (2) - BlinkWithoutDelay 예제

아두이노와 관련된 질문들을 보다 보면, delay() 함수 때문에 고민을 하는 초보자들을 많이 볼 수 있다. 고민을 하는 초보자들 중에는 동작이 되지 않는 이유가 delay() 함수 때문이라는 것조차 모르는 경우도 정말, 많다.


대표적인 질문이 바로 이런 질문이다. "시리얼 통신이 안돼요~ㅜㅜ" 또는 "센서 값을 못 읽어오는 것 같아요, 왜죠?" 등등.


이런 질문을 받으면, 나는 제일 먼저 소스에 delay() 함수가 있는지를 확인한다. 




자, 다음과 같은 기능을 하는 소스를 생각해보자. 습도 센서와 조도 센서, LED, 부저가 연결되어 있고, 1초 간격으로 습도 센서의 값을 시리얼 통신으로 전송하고 싶다. 그리고 조도 센서의 값이 500 이하가 되면 LED를 1초 간격으로 3번 깜빡인다. 또, 시리얼 통신으로 'A'라는 값이 수신되면 부저를 한 번 울린다.


참고 용 소스이므로 습도 센서 변환식은 제외하고 그냥 아날로그 값을 그대로 전송해도 좋다. 습도 센서와 조도 센서에 대한 자세한 내용은 이전 강좌를 참조


만들어 보자. 우선 습도 센서와 조도 센서의 값을 읽어와야 하겠고.

 int lightValue = analogRead(A0);

 int humValue = analogRead(A1);

 


1초 간격으로 시리얼 데이터를 전송하려면,


 Serial.println(humValue);

 delay(1000);


정도면 되겠네.


그리고 조도 센서를 체크해서 500 이하면 3번 LED를 깜빡여야 하니까, 조건문이랑 반복문을 쓰면 되겠넹~


 if(lightValue < 500) {

   for(int i=0; i<3; i++) {

     digitalWrite(13, HIGH);

     delay(1000);

     digitalWrite(13, LOW);

     delay(1000);

   }

 }

 


마지막으로, 시리얼 통신을 통해 'A' 값을 받으면 부저를 울리는 소스는 시리얼 이벤트 함수로 적용해보자. 배운 건 써먹어야지. 한 번만 울리면 되니까, 1000Hz 주파수로 500ms만 울릴래.


 void serialEvent() {

   char c = Serial.read();

   if(c == N'A') {

     tone(8, 1000, 500);

   }

 }



완벽한 것 같다. 소스를 합쳐볼까.


 NoDelayTest.ino

 

 int lightPin = A0;

 int humPin = A1;

 int ledPin = 13;

 int buzzerPin = 8;

 

 void setup() {

   pinMode(ledPin, OUTPUT);

   Serial.begin(9600);

 }


 void loop() {

   int lightValue = analogRead(lightPin);

   int humValue = analogRead(humPin);


   Serial.println(humValue);

   delay(1000);


   if(lightValue < 500) {

     for(int i=0; i<3; i++) {

       digitalWrite(ledPin, HIGH);

       delay(1000);

       digitalWrite(ledPin, LOW);

       delay(1000);

     }

   }

 }


 void serialEvent() {

   char c = Serial.read();

   if(c == N'A') {

     tone(buzzerPin, 1000, 500);

   } 

 }


 



자, 위 함수는 의도대로 동작하는 소스일까?




이 소스가 바로 "시리얼 통신이 안돼요~"와 "센서값을 못 읽어오는 것 같아요"의 질문을 하게 만드는 대표적인 잘못된 소스 코드이다. 



왜??


하나하나 뜯어보면 잘못된 곳이 없다. 컴파일 에러도 안난다. 처음엔 습도 센서의 값도 잘 출력되고, 'A' 문자를 보내면 부저도 잘 울린다. 약간 반응이 느린 감이 없잖아 있지만 뭐 그 정도는 봐줄만 하다. 


문제는 조도 센서의 값이 500 이하가 되면서부터이다. 갑자기 'A'를 보내도 부저가 울리지 않다가 한참 뒤에야 울리곤 한다. 습도 센서 값도 시리얼 모니터 창에 출력이 됐다, 안됐다 한다. 주위가 분명히 밝은데도 LED가 계속 깜빡이기도 한다. 




왜????


모든 것은 delay() 함수 때문이다.


delay() 함수는 사용자가 지정한 시간동안 아두이노 보드가 아무런 동작도 하지 않게 잠시 멈추는 함수이다. 말 그대로 멈추기 때문에 delay() 함수 이 외의 모든 소스 코드(인터럽트 처리 함수 제외)가 동작하지 않는다. 물론 시리얼 통신도. analogRead()도.



loop() 함수에 처음 들어온 후 습도 센서와 조도 센서의 값을 읽고 습도 센서의 값을 시리얼 통신으로 전송한다. 그리고 1초 멈춤. 그리고 조도 센서의 값을 500과 비교한다. 500보다 작은가? 작지 않다면 loop() 함수가 끝난다. 다시 loop() 함수에 들어와서 습도 센서와 조도 센서 값을 읽는다. 


이번엔 조도 센서의 값이 500보다 작다. if() 함수의 구문을 실행한다. for() 반복문으로 LED를 켜고 1초 멈춤, LED를 끄고 1초 멈춘다. 이 것을 3번 반복한다. 총 6초가 소요된다. 이 시간동안 아두이노 보드는 센서 값을 읽을 수도, 시리얼 통신으로 전송할 수도, 수신된 값을 확인할 수도 없다. 



게다가 아날로그 입력 값이라는 것은 연속된 값의 신호라서 디지털 신호처럼 꾸준히 HIGH가 나오는 것이 아니라 적당한 값이 연속적으로 변화하게 되는데, 7초(6초+습도 값 전송 후 1초) 간격으로 아날로그 값을 확인할 경우 실제 입력 값이 바로 적용되지 않아 밝아도 LED가 깜빡이는 현상이 나타나는 것이다. 




moon_and_james-33




아무튼, 2개 이상의 센서를 조합해서 사용하는 경우, 특히 다른 모듈과의 통신 기능이 추가되는 경우 위와 같은 오류가 많이 발생하게 되는데, 이를 해결하는 방법은 delay() 함수를 빼는 방법 밖에는 없다.



뭐... 그럼 어떻게 1초 간격을 만들어..? brown_and_cony-17 




다음은 Blink 예제를 delay() 함수 없이 구현한 "BlinkWithoutDelay" 예제이다. '파일→예제→02.Digital→BlinkWithoutDelay" 선택.




 BlinkWithoutDelay.ino


 const int ledPin =  13;  

 int ledState = LOW;     


 unsigned long previousMillis = 0;     

 const long interval = 1000;          


 void setup() {

   pinMode(ledPin, OUTPUT);

 }


 void loop()

 {

   unsigned long currentMillis = millis();

 

   if(currentMillis - previousMillis >= interval) {

     previousMillis = currentMillis;   


     if (ledState == LOW)

       ledState = HIGH;

     else

       ledState = LOW;


     digitalWrite(ledPin, ledState);

   }

 }





주석을 제외하고, 중요한 부분은 빨간 색으로 표시해뒀다. 


살펴보자. unsigned long 타입의 변수 previousMillis와 long 타입의 interval 변수가 선언되어 있다. interval 변수는 1000이라는 값이 저장되었고, 앞에 'const'가 붙어 변하지 않는 상수 값임을 알린다. (const가 붙은 변수의 값은 프로그램 도중에 바꿀 수 없다.)



그리고 loop() 함수를 보자. 역시 unsigned long 타입의 변수 currentMillis를 선언하고, millis() 함수로 읽어 온 시간 값을 저장한다. millis() 함수는 프로그램이 시작된 이후의 시간을 밀리초로 반환하는 함수. currentMillis 값과 previousMillis 값과의 차이를 interval 값과 비교한다. previousMillis 변수에는 초기 값으로 0이 저장되어 있으므로, currentMillis의 값이 1000이 될 때까지, 즉 프로그램이 시작된 후 1초가 지날 때까지 아무 동작도 하지 않는다. 


그리고는 1초가 지나 millis() 함수가 1000을 반환하게 되면 "if(currentMillis - previousMillis >= interval)"의 조건을 만족하게 되어 조건문의 내용을 실행하게 된다. 조건문의 내용은 ledState의 값을 확인하여 LOW면 HIGH로, HIGH면 LOW로 바꿔주는, LED를 켜고 끄는 내용이고. 유의해야 할 점은 조건문을 만족했을 때 previousMillis의 값을 currentMillis 값으로 업데이트 해준다는 것. 그래야 다시 두 값의 차이가 1초가 될 때까지 조건문을 실행하지 않을 테니까. 


설명이 조금 어려울 수 있지만, 사실 어려운 내용은 아니다. millis() 함수를 이용하여 얼마만큼의 시간이 지났는가를 체크하는 것일 뿐. 



이 소스에서는 if() 조건문의 조건이 만족할 경우나 만족하지 않을 경우 모두 delay() 함수가 사용되지 않기 때문에 여기에 시리얼 통신이나 센서 값을 체크하는 소스가 들어가도 멈추는 경우 없이 동작할 수 있다. 와우!!


brown_and_cony-8




간단하게 LED가 깜빡이는 예제이지만, 이 원리를 이용하면 위에서 예로 들었던 예제 역시 멈추거나 늦는 현상 없이 동작하도록 수정할 수 있다. 수정하는 것은.. 여러분의 몫으로 남겨두겠어요. ㅋㅋㅋㅋㅋ




안녕!

0
0
이 글을 페이스북으로 퍼가기 이 글을 트위터로 퍼가기 이 글을 카카오스토리로 퍼가기 이 글을 밴드로 퍼가기

임베디드 보드

번호 제목 글쓴이 날짜 조회수
118 아두이노 ESP32 Analog Inputs (ADC) +4 icon 양재동메이커 02-12 16,149
117 아두이노 TIP : Serial의 Port가 Open 시점 확인 icon 양재동메이커 01-21 13,007
116 아두이노 ESP32 Boot Mode icon 양재동메이커 12-28 12,847
115 아두이노 아두이노 에러 리스트(Arduino Error list) icon 양재동메이커 11-24 17,834
114 아두이노 ESP32 main.cpp +1 icon 양재동메이커 11-19 13,198
113 아두이노 ESP32 EEPROM 와 IR Remote icon 양재동메이커 08-06 13,097
112 아두이노 Learn ESP32 icon 양재동메이커 06-25 12,671
111 라즈베리 파이 라즈베리 파이 (Raspberry Pi) 기초 icon 양재동메이커 06-19 14,170
110 라즈베리 파이 (동영상 강의) 라즈베리 파이 강좌 Link icon 양재동메이커 06-17 13,272
109 STM32 / MBED [MED] Switch debouncing icon HellMaker 12-30 15,251
108 기타 [타이젠] 아두이노의 16x2 LCD Display라이브러리 LiquidCrystal_I2C의 타이젠 포팅 icon 양재동메이커 09-15 14,819
107 기타 [타이젠] GPIO의 디지탈 출력과 입력 인터럽트의 C++ Class 제작 icon 양재동메이커 09-12 14,301
106 마이크로비트 서보 모터 icon HellMaker 09-03 14,117
105 마이크로비트 아날로그 온도센서 (마이크로 비트 센서 활용) icon HellMaker 09-01 14,631
104 마이크로비트 터치센서 (마이크로 비트 센서 활용) icon HellMaker 09-01 13,738
103 마이크로비트 디지털 홀 센서 (마이크로 비트 센서 활용) icon HellMaker 08-29 12,922
102 마이크로비트 리니어 홀 센서 (마이크로 비트 센서 활용) icon HellMaker 08-29 12,471
101 마이크로비트 불꽃 감지 센서 (마이크로 비트 센서 활용) icon HellMaker 08-26 12,811
100 마이크로비트 로터리 엔코더 (마이크로 비트 센서 활용) icon HellMaker 08-25 12,920
99 마이크로비트 2컬러 LED(3mm) (센서 활용) icon HellMaker 08-22 12,917