DC모터는 영구자석의 자기장과 그 자기장 속에 놓여 있는 도체에 흐르는 전류에 의해 발생한 전자력 간의 반발력으로 회전하는 구동기이며 DC전원을 사용한다. 기동 토크가 크며 인가되는 전압에 대해 회전 특성이 선형적으로 비례하며 가격이 저렴한 장점이 있는 반면에 구조상 브러시(brush)와 정류자(commutator)에 의한 기계적 점점이 있다는 단점이 있다. 이 접점으로 인해 회전 시 전기적인 불꽃이나 잡음이 발생하며 이는 uC로 제어할 때는 고려하여 제거하여야 한다. DC모터는 인가 전압의 극성을 바꾸어 인가하면 방향을 바꾸어 회전한다.


[그림 1] 다양한 DC모터들


 만약 아두이노의 전원이 PC의 USB에서만 공급된다면 DC모터를 직접 구동하는 것은 무리가 있다. DC모터는 보통 큰 전류가 필요하기 때문에 최대 공급 전류가 최대 500mA 정도인(노트북인 경우 공급 전류량은 더 작아진다.) USB로 직접 구동할 수는 없다. 그러나 구동 전류가 작은 초소형 모터의 경우 USB전원으로도 충분해 제어할 수 있으며 이 경우 높은 회전 속도나 큰 토크(회전력)을 기대할 수는 없다. 중형 DC모터를 아두이노로 제어하려면 전용 모터 제어 쉴드를 사용하는 것이 유리하다.


소형 DC모터의 제어

 실험을 위해서 [그림 2]와 같은 3V로 구동되는 소형 DC모터를 선택하였으며 드라이버 IC로는 소형 DC모터의 구동에 많이 쓰이는 BA6208을 사용하였다. 소전류로도 구동이 가능한 초소형 모터이므로 USB전원만으로도 제어하는데 충분하다.

[그림 2] 실험에 사용된 초소형 DC모터

BA6208은 소형 DC모터 구동 IC이다. SIP패키지의 BA6208 외형은 [그림 3]과 같다. 그림에서 보면 홈이 파인 쪽이 1번 핀이다. 논리부와 출력부로 구성되는데 논리부는 모터의 회전 방향을 제어하며, 출력부는 논리 제어에 따라 100mA까지 출력 전류를 공급할 수 있다. 공급 전압의 최대 값은 18V이다


[그림 3] BA6208의 외형과 핀 기능


다음 표는 BA6208의 논리 값에 따른 동작을 정리한 것이다.

[표 1] BA6208의 입력 신호에 따른 모터 동작 표


아두이노 우노의 5번과 6번 핀을 BA6208로 연결하여 모터를 제어하도록 하겠다. 결선도는 다음 그림과 같다. VCC는 아두이노 우노의 3.3V핀과 연결한다.

[그림 4] 아두이노와 BA6208 그리고 모터의 결선도

 한 가지 유의할 것은 DC모터가 회전하면서 발생하는 전기적인 잡음 때문에 전원이 불안정해질 가능성이 있으며 이것은 전체 시스템이 불안정하게 만드는 요인이 되기도 한다. 이를 억제하기 위해서 BA6208단의 VCC와 GND 사이에 커패시터(10uF)을 연결하여 전원을 안정시키는 것이 좋으나 아두이노 우노 보드를 사용하는 경우 전원을 안정화 시키는 회로가 포함되어 있으므로 별도로 커패시터를 달아 줄 필요는 없다.

 다음 예는 1초 간격으로 모터의 회전 속도를 바꾸는 예제이다.


#define A_IN 5
#define B_IN 6
void setup() {
   pinMode(A_IN, OUTPUT);
   pinMode(B_IN, OUTPUT);
}
void loop() {
   digitalWrite(A_IN, HIGH); // 정회전
   digitalWrite(B_IN, LOW);
   delay(1000);
   digitalWrite(A_IN, LOW); // 역회전
   digitalWrite(B_IN, HIGH);
   delay(1000);
}


PWM을 이용한 속도제어

 이제 analogWrite() 함수를 이용하여 속도를 제어해보도록 하겠다. 다음 예제는 시리얼 통신으로 PC에서 -255~255 사이의 정수를 입력하면 그 값을 analogWrite()함수로 내보내어 모터의 속도를 조절하는 예제이다.


#define A_IN 5
#define B_IN 6
void setup() {
   Serial.begin(9600);
   Serial.setTimeout(100);
}
void loop() {
   if (Serial.available()) { // 만약 시리얼 버퍼에 데이터가 있다면
   // 정수로 해석하여 읽어들인다.
       short motorSpeed = Serial.parseInt();
       if (motorSpeed >= 0){
           analogWrite(A_IN, motorSpeed);
           analogWrite(B_IN, 0);
       } else {
           analogWrite(A_IN, 0);
           analogWrite(B_IN, -motorSpeed);
       }
   }
}


이전에 언급한 바와 같이 아두이노의 PWM 주파수는 490Hz, 980Hz인데 소형 모터를 제어하기에는 무리가 없다.



Posted by 살레시오
,

 고휘도 LED는 이전 실험에서 사용된 일반 LED보다 밝기가 대폭 개선된 LED이다.


[그림 1] 고휘도 LED

일반적인 LED와 외형은 큰 차이가 없지만 밝기가 획기적으로 개선된 것이고 전력 효율이 높아서 가정용 조명이나 신호등, 차량의 전조등 등으로 널리 사용된다.


 구동 회로는 일반 LED와 다르지 않게 저항을 직결하여 전원이 연결하면 되는데 문제는 저항값으로 어떤 값을 사용하는가이다. 데이터쉬트를 살펴보면 최대 허용 전류는 20mA 이고 (전류가 클수록 더 밝다.) 이 전류가 흐를 때 다이오드 양단의 전압 강하는 3.0~3.6V 이다. 따라서 만약 5V 전원을 사용하고 최대 전류를 흘릴 때 전압강하가 3.0V라고 가정하면 저항값은 오옴의 법칙에 의해서 다음과 같이 간단히 계산할 수 있다.



이 저항값이 허용되는 가장 최소 저항이므로 이것보다 큰 용량의 저항을 선택하면 구동하는데 무리가 없을 것이다. 실습에는 100 Ω의 저항을 선택했다.


첫 번째 실험

 첫 번째 예제로 서서히 밝아졌다가 다시 서서히 어두워지는 동작을 하는 프로그램을 작성해 보자.


#define HLED 5
void setup() {
   pinMode(HLED,OUTPUT);
}
void loop() {
   for (int k=0; k<256; k++) {
       analogWrite(HLED,k);
       delay(15);
   }
   for (int k=255; k>=0; k--) {
       analogWrite(HLED, k);
       delay(15);
   }
}


위 프로그램에서 delay(15) 함수를 이용하여 서서히 밝아지거나 서서히 어두워지는 효과를 내었다.


두 번째 실험

 두 번째로 스위치를 누르면 고휘도 LED가 서서히 켜지는 프로그램을 작성해 보자. 완전히 꺼진 상태에서 최고 밝기로 켜지는 시간은 4초로 설정한다. 버튼을 떼면 그 즉시로 고휘도 LED가 꺼져야 한다.

#define HLED 6
#define SW 3
void setup() {
   pinMode(HLED,OUTPUT);
   pinMode(SW,INPUT_PULLUP);
}
int iL=0;
void loop() {
   if (digitalRead(SW)==LOW) {
       analogWrite(HLED, iL++);
       if (iL>255) iL = 255;//❶
       delay(15);
   } else {
       digitalWrite(HLED,LOW);
       iL = 0;
   }
}

전술한 바와 같이 analogWrite()함수의 두 번째 인수의 범위는 0~255이다. 따라서 ❶에서 이 범위를 넘으면 (즉 256이 되면) 255값을 계속 가지도록 if 문으로 처리했음을 유의해서 보자.


세 번째 실험

 이전 실험에서는 스위치를 떼면 그 즉시 꺼졌지만 이번 예제에서는 서서히 꺼지는 부분을 추가해 보자. 꺼지는 속도는 완전히 켜졌을 때에서 완전히 꺼질 때까지의 시간이 4초 정도 되게 설정한다.


#define HLED 6
#define SW 3
void setup() {
   pinMode(HLED,OUTPUT);
   pinMode(SW,INPUT_PULLUP);
}
int iL=0;
void loop() {
   if (digitalRead(SW)==LOW) {
       analogWrite(HLED, iL++);
       if (iL>255) iL = 255; //❶
       delay(15);
   } else {
       analogWrite(HLED, iL--); //❷
       if (iL<0) iL = 0;
       delay(15);
   }
}


이 프로그램의 동작은 스위치를 누르고 있으면 서서히 켜지고 떼면 다시 서서히 꺼지게 된다. analogWrite()함수의 두 번째 인수의 범위는 0~255이다. 따라서 ❶과 ❷에서 이 범위를 넘으면 그 한계값을 계속 가지도록 if 문으로 처리했음을 유의해서 보자.



Posted by 살레시오
,

 아두이노의 디지털핀은 오직 HIGH(5V) 아니면 LOW(0V) 두 가지 신호 외에는 출력할 수 없으며 전압의 관점에서 보면 5V와 0V만 가질 수 있다. 하지만 PWM (pulse width modulation, 펄스 폭 변조) 기능을 이용하면 마치 아날로그 전압처럼 0V와 5V 사이의 전압으로 (예를 들면 2V, 3.5V 등) 출력을 낼 수 있다. 따라서 LED의 밝기를 제어한다든가 모터의 회전 속도를 제어하는데 사용할 수 있다.

 PWM은 진정한 의미의 아날로그 출력은 아니고 흉내를 내는 것인데 그 원리는 다음 그림과 같다. (출처 : arduino.cc)


[그림 1] PWM의 원리


이 그림에서 보면 주기적인 구형파를 발생하고 이 구형파의 폭을 조절하여 그 듀티비 (HIGH 구간 대비 LOW 구간의 비율)로 아날로그 전압값을 가지도록 한다. 이 구형파의 주기를 매우 빠르게 (아두이노 우노의 경우 490Hz 혹은 980Hz이다.) 하면 상대적으로 반응 속도가 느린 기계 장치(모터 등)는 이것을 아날로그 전압으로 인식하게 된다.


 예를 들어서 그림 1(b)의 경우 주기의 1/4동안만 on 이 되므로 평균 출력 전압도 5V * ¼ 인 1.25V 정도가 된다. 만약 LED가 연결되었다면 LED는 정확하게 on구간에서만 켜지고 off구간에서는 꺼진다. 하지만 눈으로 보기에는 이것이 인식하지 못 할만큼 고속으로 동작하므로 밝기가 다르게 보이는 것이다.


 아두이노의 모든 핀이 PWM 출력을 낼 수 있는 것은 아니고 아두이노 우노의 경우 3, 5, 6, 9, 10, 11번 핀이 PWM출력을 낼 수 있으며 이것은 정품 보드의 경우 다음 그림과 같이 ~로 표시되어 있다.


[그림 2] 아두이노 우노의 PWM 핀들

PWM의 동작 주파수는 다음과 같다.

  • 3, 9, 10, 11번 핀 - 490Hz

  • 5, 6번 핀 – 980Hz (5,6번 핀이 좀 더 고속으로 동작)


PWM기능을 사용하기 위해서는 다음과 같은 analogWrite()함수를 이용한다.


analogWrite(pin, value)

  • pin : 3, 5, 6, 9, 10, 11 중 하나 (아두이노 우노의 경우)

  • value : 0에서 255 사이의 정수.


그리고 PWM 기능을 사용하기 위해서는 해당 핀을 출력으로 설정하여야 한다.



Posted by 살레시오
,

 CdS 광전도 셀(CdS photoconductive cell) 또는 CdS셀은 황화카드뮴을 주성분으로 하는 광전도 소자로, 빛의 양에 따라 저항값이 변하는 일종의 가변 저항으로 생각할 수 있다. 빛의 양에 따라 내부 저항값이 변하는 특성이 있으므로 광 가변 저항기라고도 불리는데, 조사되는 빛의 양이 클수록 저항값이 낮아지지만 입사광이 거의 없으면 거의 절연체에 가까워질 정도로 저항값이 커지게 된다. 따라서 광량에 의해서 개폐되는 전기적인 스위치로도 생각할 수 있으며 회로도 스위치의 그것과 동일하게 제작되었다.


[그림 1] Cds 관전도 셀의 외형

 회로 구성은 택스위치와 동일하게 CdS셀의 한 핀에 아날로그 핀을 연결하고 다른 핀은 GND와 직결하면 된다. 단, 이 경우 아날로그 핀의 내부 풀업저항을 이용해야 하므로 다음과 같이 설정해야 한다. (만약 A0에 센서가 연결되어 있다면)

pinMode(A0, INPUT_PULLUP);

센서 주변에 조사광이 많다면 (주위가 밝다면) CdS셀의 저항이 0에 가까워지므로 아날로그 핀은 GND에 연결이 되므로 0에 가까운 값이 읽혀질 것이다. 반대로 센서 주위가 어둡다면 저항이 커지므로 내부 풀업 저항에 의해서 1023에 가까운 값이 읽혀질 것이다. 즉, 밝을수록 더 작은 값이 읽혀지게 된다.

첫 번째 예제

 첫 번째 예제로 Cds센서의 한 쪽 다리를 A0에 연결하고 다른 핀은 GND에 연결한 후 내부 풀업 저항을 연결한다. 그런 후 아날로그 값을 읽어서 시리얼 터미널에 그 값을 출력하는 간단한 프로그램을 작성해보도록 하겠다.

void setup() {
   pinMode(A0, INPUT_PULLUP);
   Serial.begin(115200);
}
void loop() {
   Serial.println(analogRead(A0));
   delay(100);//1초에 약 10번 정도 출력한다.
}


두 번째 예제

 이번에는 주변이 밝으면 내장 LED가 꺼지고 어느 정도 어두워지면 LED가 켜지는 프로그램을 작성해 보자.

#define TH 100

void setup() {
   pinMode(A0, INPUT_PULLUP);
   pinMode(13, OUTPUT);
}
void loop() {
   if ( analogRead(A0) > TH ) {
       digitalWrite(13, HIGH);
   } else {
       digitalWrite(13, LOW);
   }
}


여기서 상수 TH는 경계값으로서 이 값 이상이면 충분히 어둡다고 판단하여 LED를 켜게 된다. 이 값 이하이면 LED는 꺼진다.




Posted by 살레시오
,

 여기에서는 온도 센서 LM35DZ를 이용하여 아날로그 입력 실험을 수행하도록 하겠다. 이 소자의 외형과 핀의 기능은 다음 그림과 같다. 세 개의 다리가 있는데 각각 5V, 출력, GND 이고 가운데 출력핀을 아두이노의 아날로그 핀에 연결해야 한다.


[그림 1] 온도센서 LM35-DZ


데이터시트에 따르면 이 소자는 섭씨 +2도~+150도 의 범위를 측정할 수 있으며 섭씨 1도는 약 10mV 의 전위차를 갖는다. 예를 들어서 센서핀의 전압이 0.21V라면 210mV 이므로 온도는 21도씨라는 것이다. 따라서 아날로그 값을 읽어들여서 약간의 계산(전압값에 100을 곱한다)을 거쳐서 섭씨 온도가 얻어진다.


 만약 A0 핀에 센서가 연결되어 있다고 하면 이를 수행하는 코드는 다음과 같다.


short sVal = analogRead(A0); // 아날로그값: 0~1023의 값을 갖는다.
float fVoltage = sVal*5.0/1024; // 실제 전압값을 변환 (5.0을 곱함에 유의한다.)
float fTemp = fVoltage * 100; // 전압값을 섭씨 온도로 변환한다.


이제 이것을 함수로 구현하여 1초마다 한 번씩 온도를 감지하여 PC로 시리얼 통신을 통해서 표시해주는 예제는 다음과 같다.


void setup() {
   Serial.begin(9600);
}

void loop() {
   Serial.println(getTemp());
   delay(1000);
}

float getTemp() {
   short sVal = analogRead(A0);
   float voltage = sVal*5.0/1024; // 실제 전압값을 구한다.
   return voltage*100;
}

[그림 5.2.2] 1초마다 온도를 감지하여 표시하는 결과 화면


 만약 기준 전압으로 내부 전압인 1.1V를 사용하면 분해능이 약 5배 정도 높아지므로 OP-AMP와 같은 외부 회로 없이 간접적으로 5배 증폭의 효과를 볼 수 있다. 1.1V라도 섭씨 110도에 해당되므로 실온을 측정하는 경우라면 크게 문제가 없을 것이다. 다음과 같이 앞의 예제를 조금만 수정하면 된다.


#define SUPPLY_VOLTAGE 1.1

void setup() {
   Serial.begin(9600);
   analogReference(INTERNAL);
}

void loop() {
   Serial.println(getTemp());
   delay(1000);
}

float getTemp() {
   short sVal = analogRead(A0);

   // 1.1을 곱한 뒤 1024로 나눈다.

   float voltage = sVal*SUPPLY_VOLTAGE/1024;
   return voltage*100;
}


 한 가지 더 부연할 것은 실제 온도와 센싱된 온도와는 아무래도 차이가 나게 마련이다. 따라서 정밀하게 온도를 잴 수 있는 온도계 값과 센싱된 값을 비교하여서 그 차이만큼 보정을 해주면 좀 더 정확하게 온도를 잴 수 있을 것이다.




Posted by 살레시오
,

 대부분의 아두이노에서 사용되는 ATmega 계열의 마이크로콘트롤러는 아날로그 입력을 받을 수 있는 A/D변환 핀이 있는데 A0, A1, A2, .. 와 같이 이름이 붙어 있다.  이 핀들은 일반적인 디지털 핀으로도 사용할 수 있다. 따라서 아두이노에서 사용자가 디지털 핀의 수가 부족해서 더 필요하다면 아날로그 핀을 디지털 핀으로 사용할 수 있다, 아래 그림에서 보듯이 아두이노 우노의 경우 A0 부터 A5 까지 6개의 아날로그핀이 있다



[그림 1] 아두이노의 아날로그 핀들


만약 A0핀을 디지털 출력핀으로 사용하고 싶다면 다음과 같이 A0 상수를 이용하여 pinMode()함수를 호출한다.


pinMode(A0, OUTPUT);
digitalWrite(A0, HIGH); 혹은  digitalWrite(A0, LOW);


다른 예로 A1핀을 디지털 입력핀으로 사용하고 싶다면 A1상수를 이용하면 된다. 입력으로 설정할 때 내부 풀업 저항도 연결할 수 있다.


pinMode(A1, INPUT);
pinMode(A1, INPUT_PULLUP);


그리고 digitalRead() 함수를 똑같이 사용할 수 있다.


int ia = digitalRead(A1);


이전 포스트에서 설명한 것 처럼 A0핀은 디지털 14번 핀으로 사용되는 것이다, A1은 15번 핀이고 A5는 19번 핀이다.


 만약 디지털 핀이 부족하다면 이와 같은 방법으로 아날로그 핀도 디지털 핀과 똑같이 사용할 수 있다.


아두이노 강좌 전체 목록 (TOP) >>>

C++ 언어 전체 강좌 목록 >>>

c{ard},n{ad021}

Posted by 살레시오
,

 아날로그핀에 연결된 ADC의 기준 전압을 바꿀 수 있는데  analogReference() 함수를 이용하면 된다.


ananlogReference(type)

 

ADC 기준 전압은 아날로그 입력값이 1023로 읽히는 최대 전압 값을 의미하며 이 전압값을 설정해 주는 함수이다. 우노의 경우 따로 설정해 주지 않으면 5V이지만 이 함수를 이용하여 다른 값을 설정해 줄 수 있다. type의 종류는 다음과 같다.


  • DEFAULT : 아두이노의 동작 전압(우노는 5V 이고 보드에 따라서 3.3V일 수 있다.)

  • INTERNAL : 내장 전압 (우노는 1.1V)

  • EXTERNAL : AREF핀에 인가된 전압 (0~ 5V 사이어야 됨)

  • INTERNAL1V1 : 내장된 1.1V (Arduino Mega에서만 사용된다.)

  • INTERNAL2V56 : 내장 2.56V (Arduino Mega 에서만 사용됨)


아두이노 우노와 관련된 옵션은 DAFAULT, INTERNAL, EXTERNAL 세 개 이며 별도로 설정하지 않는다면 기본적으로 DEFAULT가 사용된다.


 만약 INTERNAL로 설정하면 기준 전압이 1.1V이므로 기본 모드보다 더 높은 분해능(0.0011V)을 얻을 수 있으며 약 4.5배(=5/1.1)의 증폭효과가 있다.


 만약 3.3V를 기준 전압으로 사용하고 싶다면 우노 보드상의 3.3V핀과 AREF 핀을 결선한 후 EXTERNAL 옵션을 설정하면 된다. (다음 그림 참조) 이 경우 분해능은 0.0032V 이고 5V를 기준 전압으로 사용하는 경우 대비 약 1.5배의 증폭 효과가 있다. 아두이노 우노의 3.3V 핀은 외부 전원(7V~12V)을 연결한 경우뿐만 아니라 USB만 연결한 경우에도 정확히 3.3V 전압을 출력하므로 편리하게 사용할 수 있다.



<그림 2> 3.3V 와 AREF 연결도

♦ 만약 아날로그 핀의 기준 전압을 3.3V를 사용하고 싶다면 그림과 같이 연결한 후 setup()함수 내에서

         analogReference(EXTERNAL);

로 설정한다. 5V 기준 전압에 비해서 약 1.5배의 증폭 효과가 있다.

♦ 아두이노 우노의 3.3V 핀은 외부 전원(7V~12V)을 연결한 경우뿐만 아니라 USB만 연결한 경우에도 정확히 3.3V 전압을 갖는다.



아두이노 강좌 전체 목록 (TOP) >>>

C++ 언어 전체 강좌 목록 >>>

c{ard},n{ad020}

Posted by 살레시오
,

 아두이노 우노의 A0부터 A5 까지 6개의 핀을 이용하여 아날로그 입력을 받을 수 있는데 디지털 핀이 0과 1 두 값 만을 입출력으로 가지는 것과는 달리 아날로그 핀은 그 전압을 0~1023 의 정수값으로 변환하여 읽어들인다.


[그림 1] A0~A5 핀과 AREF핀

A0~A5핀으로 아날로그 입력을 받는다.

AREF핀은 아날로그 기준 전압을 설정한다.


아날로그 핀은 0~5V 사이의 전압 값을 0~1023 사이의 정수값으로 변환시킨다. 이것을 A/D변환(analog-to-digital conversion)이라고 한다. 따라서 분해능은 0.0049V (4.9mV = 5V/1024)이며 이 때 사용되는 함수가 analogRead()함수이다.


ananlogRead(pin)


입력 인수로 0(또는 A0), 1(또는 A1), … 5(또는 A5)를 주고 리턴값은 int형으로서 앞에서 언급한 대로 0~1023 값이다. 변환 시간은 100micro-sec 으로서 이론상으로는 1초에 10000번 정도 변환이 가능하다. 아날로그 핀은 디지털 핀과 달리 기본적으로 입력으로 설정되어 있으므로 별도로 입력을 설정하는 과정 없이 바로 위의 함수를 이용할 수 있다.


 한 가지 알아두어야 할 점은 아두이노 우노의 경우 다음 세 가지 방법은 같은 동작을 수행한다.


[표 1] 아두이노 우노의 analogRead()함수 동작에 사용되는 상수

방법1

방법2

방법3

비고

analogRead(0)

analogRead(A0)

analogRead(14)

A0 == 14

analogRead(1)

analogRead(A1)

analogRead(15)

A1 == 15

analogRead(2)

analogRead(A2)

analogRead(16)

A2 == 16

analogRead(3)

analogRead(A3)

analogRead(17)

A3 == 17

analogRead(4)

analogRead(A4)

analogRead(18)

A4 == 18

analogRead(5)

analogRead(A5)

analogRead(19)

A5 == 19


즉, analogRead(0)과 analogRead(A0) 그리고 analogRead(14) 는 내부적으로 같은 동작을 수행한다. 왜냐면 상수 A0, A1, …, A5 는 내부적으로 다음과 같이 14,15,...,19로 정의되어 있기 때문이다.


              #define A0  14

              #define A1  15

              ….

              #define A5  19


(필자는 처음에 상수 A0 는 내부적으로 0으로 정의되었을 것으로 짐작을 했었는데 그게 아니었다.) 보통은 [표 1]의 <방법1> 혹은 <방법2> 를 사용하고 <방법3>은 사용하지 않지만 내부적으로 A0상수가 14값을 갖는 다는 것은 알아두는 것이 좋은데 그 이유는 아날로그 핀을 디지털 핀으로 사용할 때 이 상수가 사용되기 때문이다. 디지털 핀이 13번 까지 있으므로 그 다음 숫자부터 아날로그 핀에 할당되었다는 것을 알 수 있다.


아두이노 강좌 전체 목록 (TOP) >>>

C++ 언어 전체 강좌 목록 >>>

c{ard},n{ad019}

Posted by 살레시오
,

 여기에서는 이전 포스트에 이어서 다른 인터럽트 방식에 대한 디바운싱 방법에 대해서 알아보도록 하겠다.

rising edge 의 경우 디바운싱

 이전과 같이 INT0핀에 택스위치를 연결하고, 이번에는 라이징 에지에 인터럽트가 걸리도록 설정되었다고 하자.


attachInterrupt(INT0, toggleLed, RISING);


앞의 경우와 반대로 ISR의 함수 처음에 일정시간 지연시킨 후 스위치 신호가 LOW일 때 그냥 리턴시키는데 그 이유는 falling edge에서 발생한 것이 때문이다.


void isr() {
   _delay_ms(80);
   if (digitalRead(SW)==LOW) return;
   …
   <실제 인터럽트 처리 루틴>
}


changing edge 의 경우 디바운싱

 이번에는 changing edge, 즉 falling과 rising edge 모두에서  인터럽트가 걸리도록 설정되었다고 하자.


attachInterrupt(INT0, toggleLed, CHANGE);


이 경우는 하나의 에지에서 두 개 이상의 인터럽트가 걸리는 것을 막기 위해서 단순히  지연시키는 것으로 충분하다.


void isr() {
   _delay_ms(80);
   <실제 인터럽트 처리 루틴>
   ….
}


low 인터럽트 의 경우 디바운싱

 마지막으로  low 신호에서  인터럽트가 걸리도록 설정되었다고 하자. 이 인터럽트는 핀 신호가 low 이면 몇 번이고 반복해서 인터럽트가 걸리도록 하려는 의도로 설정되는 것이다. 즉 INT0핀이 low 상태이면 ISR을 수행한 후 그래도 low상태이면 ISR이 바로 다시 수행된다. 이런 식으로 low 상태일 경우에는 계속 ISR이 반복으로 수행된다.


attachInterrupt(INT0, toggleLed, LOW);


따라서 이 경우는 별도의 디바운싱이 필요치 않다.


아두이노 강좌 전체 목록 (TOP) >>>

C++ 언어 전체 강좌 목록 >>>

c{ard},n{ad018}

Posted by 살레시오
,

 택스위치에 대해서는 이전 포스트에서 기본적인 내용을 다루었다. 여기에서는 인터럽트를 이용해서 스위치를 누를 때마다 LED가 토글(toggle)되는 프로그램을 작성하도록 하겠다. 즉, 스위치 신호의 폴링 에지 (falling edge)에서 LED의 출력 신호가 반전되도록 하는 프로그램을 작성하도록 하겠다.


터치센서와 다르게 기계적인 접점을 갖는 스위치는 점점이 붙거나 떨어지는 순간에 바운싱(bouncing) 현상이 있다. 이것은 접점이 붙거나 떨어지는 그 짧은 순간에 접점이 고속으로 여러 번 on/off 되는 현상을 말하며 기계적인 스위치라면 반드시 발생하는 현상이다. 이것은  짧은 순간(약 100ms 이내임)에 여러 번 접점이 붙었다가 떨어지는 것을 반복하므로 의도하지 않게 인터럽트가 여러 번 발생할 수 있으므로 이러한 바운싱 현상을 제거해야 올바르게 동작하는데 이것을 디바운싱(debouncing)이라고 한다.


 다음 그림에서 하단의 그림이 바운싱을 도시한 것이고 이것을 제거한 깨끗한 신호가 상단에 도시되어 있다.


[그림 1] (a) 디바운싱 (b) 바운싱 현상


<- 디바운스된 신호




<- 기계적 접점의 바운싱 현상


 디바운싱하는데 여러 가지 방법들이 알려져 있는데 크게 두 가지로 분류할 수 있으며 하드웨어적인 방법과 소프트웨어적인 것이다. 하드웨어적인 방법은 RC 저역 필터를 슈미트트리거나 디지털 인버터에 직결한 후 이 신호를 포트 핀에 연결하는 방법인데 아래의 회로도가 대표적인 것이다.


[그림 2] 디바운싱 회로

대표적인 디바운싱 회로도


RC 필터와 슈미트트리거가 직결되어 있다. 이 회로도를 사용하면 아두이노의 내부 풀업 저항은 사용하지 못한다.


 하지만 아두이노와 같은 프로세서가 사용된다면 부가적인 디바운싱 회로 없이 소프트웨어적으로 이것을 구현하여 불필요한 인터럽트를 억제할 수 있다. 여기에서는 그 방법에 대해서 설명하도록 하겠다.  기본적인 아이디어는 ISR 함수 안에서 바운싱이 없어질 때 까지 기다렸다가 스위치 신호를 다시 읽어서 정상 호출된 것인가 아닌가를 판별하는 것이다.

falling edge 의 경우 디바운싱

 만약 INT0핀에 택스위치를 연결하고  폴링 에지에 인터럽트가 걸리도록 설정되었다고 가정하자.


attachInterrupt(INT0, toggleLed, FALLING);


 ISR 함수 처음에 바운싱이 안정될 때 까지 일정시간(80ms 정도가 적당하다.) 지연시킨 후 다음과 같이 스위치 신호를 읽은 다음 그것으로 올바른 인터럽트 호출인가를 판단한다.


void isr() {
   _delay_ms(80);
   if (digitalRead(SW)==HIGH) return;
   <실제 인터럽트 처리 루틴>
   ,,,
}


 이 코드를 보면 80ms 지연시킨 이후에 스위치 값을 읽어 HIGH라면 잘못 호출된 것으로 판단하고 아무 일도 않고 리턴한다. 즉, 이 경우는 rising edge의 바운싱 구간에서 호출된 것으로 판단하는 것이다. 반대로 80ms 지연시킨 이후에 스위치 값이 LOW라면 falling edge에서 정상 호출된 것으로 간주하고 이후에 있는 인터럽트 처리 루틴을 실행시키는 것이다.


 이 방법은  손으로 버튼을 조작하는 경우 정밀한 제어가 필요치 않으므로 필자의 경험상 너무 빠르게 버튼을 동작시키지 않는 한 대부분 정상 작동한다.


 이 설명에서 처음 소개하는 함수가 있는데 바로 _delay_ms() 함수이다. 이름을 보면 지연시키는 함수라는 것을 알 수 있을 텐데 왜 아두이노의 표준 함수인 delay() 함수 대신 이 함수를 사용했는지 의문이 들 것이다. ISR함수 안에서 아두이노의 시간관련 함수인 다음 함수들은 정상 동작하지 않는다.(주의!)


delay(), delayMicrosecond(), millis(), micros()


이 함수들은 내부적으로 타이머 인터럽트를 사용하므로 ISR안에서는 작동할 수 없다. 이유는 ISR이 호출되는 순간 인터럽트가 자동으로 막히고 ISR의 끝나야 인터럽트가 다시 가능하게 되기 때문이다. avr에 대한 지식이 있다면 이해가 쉽게 가겠지만 일단은 위 함수들은 ISR 안에서 정상 동작하지 않는다고만 알아도 된다. 이것은 필자가 구글링을 해 보고 직접 실험으로 확인한 사실이다.


 반면에 _delay_ms()함수는 단순히 소트프웨어적으로 지연을 시켜주는 함수라서 ISR안에서도 정상 작동한다. 단, 이 함수를 사용하기 위해서는 다음과 같이 헤더 파일을 인클루드 시켜야 한다. (avr-gcc에서 사용되는 유틸리티 라이브러리이다.)


#include<util/delay.h>


전체 프로그램은 다음과 같다.


#include<util/delay.h>
#define SW 2

void setup() {
         pinMode(LED_BUILTIN, OUTPUT);
         pinMode(SW, INPUT_PULLUP);
         attachInterrupt(INT0, toggleLed, FALLING);
}

void loop() {
}

void toggleLed() {
 _delay_ms(80);
 if (digitalRead(SW)==HIGH) return;
 digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
}


 스위치의 디바운싱은 고전적이만 해결하기 꽤 까다로운 문제이다. 하지만 여기서 소개한 방법을 사용하면 대부분의 경우 잘 동작한다.


아두이노 강좌 전체 목록 (TOP) >>>

C++ 언어 전체 강좌 목록 >>>

c{ard},n{ad017}

Posted by 살레시오
,