====== 아두이노 그대로 따라하기 - 2.LED 켜고 끄기 ====== 가장 기본이라 할 수 있는 특정 핀의 시그널을 컨트롤하는 방법에 대해 기술한다. 흔히 GPIO 라 불리는 인터페이스는 하드웨어 통신의 가장 기본적인 방법이다. 이를 응용한 것이 I2C 나 UART 등이다. ====== 무작정 따라하기 ====== 핀에 흘리는 전류를 High(1) 이나 Low(0) 로 조절하여 이를 LED 로 확인해보자. 먼저아래의 코드를 작성하여 컴파일하여 아두이노에 올려보자. const int LED = 13; ---- 1 void setup() ---- 2 { pinMode(LED, OUTPUT); ---- 3 } void loop() ---- 4 { digitalWrite(LED, HIGH); ---- 5 delay(1000); ---- 6 digitalWrite(LED, LOW); ---- 7 delay(1000); } 특정 LED 하나가 깜박이는 걸 볼 수 있다. {{ :computer:embedded:led.jpg |}} 이제 코드를 보자. 아두이노는 H/W 를 접해보지 않은 사람도 알 수 있도록 아주 직관적이다. - LED 라는 상수 변수가 미리 정의되어 있다. 이것은 GPIO 핀을 가리키며, 13 을 입력한 것은 13번째 GPIO 핀을 지정했다. 위치는 아두이노 PCB 기판에 나와 있기 때문에 이해가 쉽다(회로도 참고할 것) - setup 함수는 아두이노 프로그래밍 코드라면 반드시 선언되어야 한다. 이는 말그대로 초기화 함수 인데, loop 함수가 실행되기 전에 호출된다. - GPIO 핀의 성격을 정의한다. 앞서 지정한 13번 핀을 출력 핀으로 지정한다. - C 프로그램의 main() 함수 역할로서, 반드시 지정되어야 한다. 무한 루프로 실행된다. - 앞서 지정한 핀의 출력을 High(1) 로 내보낸다. - 1000 ms(= 1초) 지연시킨다. - 핀의 출력을 Low(0) 로 내보낸다. ====== 스위치를 누르고 있는 동안 LED 켜기 ====== 아래 그림처럼 회로를 꾸민다. 저항은 10k 옴을 사용한다. {{ :computer:embedded:switch.jpg |}} 그리고 코드를 작성한다. const int LED = 13; const int BUTTON = 7; int val = 0; void setup() { pinMode(LED, OUTPUT); pinMode(BUTTON, INPUT); ---- 1 } void loop() { val = digitalRead(BUTTON); ---- 2 if(val == HIGH) { digitalWrite(LED, HIGH); } else { digitalWrite(LED, LOW); } } 코드를 보자. - 7번 핀을 입력 받도록 설정했다. - 7번 핀으로 입력값을 읽어들인다. High 라면 1, Low 라면 0 이 반환된다. ====== 스위치를 누를 때마다 LED 켜고 끄고 하기 ====== 한번 누르면 켜지고, 다시 한번 누르면 꺼지도록 코드를 수정해보자. const int LED = 13; const int BUTTON = 7; int val = 0; int old_val = 0; ---- 1 int state = 0; ---- 2 void setup() { pinMode(LED, OUTPUT); pinMode(BUTTON, INPUT); } void loop() { val = digitalRead(BUTTON); if((val == HIGH) && (old_val == LOW)) ---- 3 { state = 1 - state; } old_val = val; ---- 4 if(state == HIGH) { digitalWrite(LED, HIGH); } else { digitalWrite(LED, LOW); } } 전보다 상태값을 저장하는 변수와 루틴이 추가되었다. 스위치를 누를 때(입력 신호가 LOW -> HIGH 로 바뀌는)를 인식하기 위해서이다. - 현재 상태보다 이전 상태을 저장하는 변수 선언 - 현재 LED 의 켜고꺼짐 상태를 저장하는 변수 선언, - 스위치를 눌렀을 때의 상태를 인식하기 위한 조건문 - 현재 상태값을 예전 상태값에 저장한다 ===== 바운싱 제거하기 ===== 위의 코드가 완벽하지 않을 수 있는데, 스위치 기계 구조에서 오는 문제점 때문이다. 푸시버튼은 아주 간단한 장치로 두 금속조각이 스프링에 의해 떨어진 상태다. 버튼을 누를 때, 두 조각이 서로 연결되어 전기가 흐를 수 있다. 버튼을 완전히 누르지 않았을 때는 바운싱 이라는 가짜신호가 나타난다. 푸시버튼이 바운싱할 때 아두이노는 켜지고 꺼지는 신호가 아주 빠르게 바뀌는 것처럼 인식한다. 바운싱을 제거하는 기술적인 방법이 많이 있지만, 코드에서는 대개 변경을 감지한 후에 10~50 밀리초 정도 대기하는 명령을 추가하는 정도면 충분하다. ... if((val == HIGH) && (old_val == LOW)) { state = 1 - state; delay(10); ---- 1 } ... 위와 같이 지연하는 코드를 추가했다. ====== LED 밝기 조절하기 ====== 아래와 같이 회로를 꾸며보자. 저항은 270 옴을 사용한다. {{ :computer:embedded:led1.jpg |}} 먼저 밝기의 의미에 대해서 알아보자. 밝기는 H/W 적으로 어떻게 조절할까? 전기 신호는 High 와 Low 뿐인데, 이걸 가지고 어떻게? 단순하게 High 와 Low 의 중간 레벨의 신호를 흘려보내면 밝기가 어중간하지 않을까? 결론부터 말하면, 틀렸다. 모든 전자회로는 High 와 Low 에서만 정확하게 동작한다. 정말 심플하지 않은가? 밝기조절은 LED 를 껐다가 켜는 것으로 가능하다. 시간당 빠르게 또는 느리게 이를 반복하는 것으로 우리의 눈은 마치 밝기가 조절되는 것으로 보이기 때문이다. 디지털 신호와 달리 아날로그 신호는 High -> Low -> High 를 반복하는 파형을 그린다. 바로 아날로그 출력을 사용해서 밝기 조절을 할 수 있다. 이제 코드를 보자. const int LED = 9; ---- 1 int i = 0; void setup() { pinMode(LED, OUTPUT); } void loop() { for(i=0; i<255; i++) { analogWrite(LED, i); ---- 2 delay(10); } for(i=255; i>0; i--) { analogWrite(LED, i); delay(10); } } 새로운 함수가 나왔다. 앞서 나온 digitalWrite 함수와 짝을 이루는 아날로그 신호를 출력하는 함수다. - 아날로그 신호를 출력할 수 있는 PWM(펄스 폭 변조) 기능을 사용할 수 있도록 설계된 핀이 9,10,11 번이다. - 두번째 인자의 값은 0 ~ 255 숫자를 사용할 수 있다. 높을 수록 많은 변조 가해 출력한다. 0은 완전히 꺼진 상태이고, 255는 완전히 켜진 상태다. ====== 스위치를 누를 때, 밝기 조절하기 ====== 앞서 만들었던 두개의 회로를 모두 연결해보자. const int LED = 9; const int BUTTON = 7; int val = 0; int old_val = 0; int state = 0; int brightness = 128; unsigned long starttime = 0; ---- 1 void setup() { pinMode(LED, OUTPUT); pinMode(BUTTON, INPUT); } void loop() { val = digitalRead(BUTTON); if((val == HIGH) && (old_val == LOW)) { state = 1 - state; starttime = millis(); ---- 2 delay(10); } if((val == HIGH) && (old_val == HIGH)) { if(state == 1 && (millis() - starttime) > 500) ---- 3 { brightness++; delay(10); if(brightness > 255) ---- 4 { brightness = 0; } } } old_val = val; if(state == 1) { analogWrite(LED, brightness); }else { analogWrite(LED, 0); } } 스위치를 누르고 있는 동안, 밝기가 변해야 하기 때문에 시간을 재는 함수가 추가되었다. - 스위치를 누른 시간을 저장하는 변수 선언 - 현재 시간 반환하는 함수(millis) 를 호출하여 저장한다. - 500 ms 이상 스위치를 눌렀는지 조건을 확인한다. - 오랫동안 눌렀을 경우, 다시 0으로 초기화 한다. ====== 가변저항을 사용하여 밝기 조절하기 ====== 말그대로 저항값이 바뀌는 저항이다. 마치 라디오 볼륨처럼 다이얼을 돌리면 저항값이 바뀐다. 이번에는 다이얼을 돌리는 것에 따라 LED 밝기를 조절해보겠다. ===== 회로도 ===== {{ :computer:embedded:regi.jpg |}} 회로를 만들 때, 가변 저항은 아래 그림처럼 브레드보드에 꼽는다. {{ :computer:embedded:regi2.jpg |}} ===== 소스코드 ===== const int se = 0; const int led = 9; void setup() { Serial.begin(9600); pinMode(led, OUTPUT); } void loop() { int val = analogRead(se); Serial.println(val); int inten = map(val, 0, 1023, 0, 255); // 가변 저항기의 값을 기초로 LED 의 밝기를 구한다. 0~1023 의 입력을 0~255 범위로 스케일링 analogWrite(led, inten); delay(100); } 아두이노 보드는 아날로그 입력을 0 에서 1023 까지 1024 단계로 표현한다. 이에 비해 analogWrite 는 0 에서 255 까지 256 단계가 있기 때문에 map 이라는 메서드를 사용해 범위를 맞추고 있다. ====== 7 세그먼트 사용하기 ====== 0 부터 9 까지의 숫자를 나타낼 수 있는 7 Segment 를 제어해보자. 세븐 세그먼트는 크게 2가지로 나뉜다. Anode(에노드) / Cathode(캐노드) 형에 따라 회로가 바뀐다. 3번과 8번 핀을 각각 에노드는 전원(Vcc)에 연결하고 캐노드는 그라운드(Gnd)에 연결한다. 내가 가진 것은 캐노드 형으로 그라운드에 연결했다. {{ :computer:embedded:7seg.gif |}} {{ :computer:embedded:seven_segment_led_pins.jpg |}} {{ :computer:embedded:led-diagram.jpg |}} | Arduino Pin | 7 Segment Pin Connection | | 2 | 7 (A) | | 3 | 6 (B) | | 4 | 4 (C) | | 5 | 2 (D) | | 6 | 1 (E) | | 7 | 9 (F) | | 8 | 10 (G) | | 9 | 5 (DP) | ===== 회로도 ===== {{ :computer:embedded:7seg.fzz |}} ===== 소스코드 ===== byte seven_seg_digits[10][7] = { { 1,1,1,1,1,1,0 }, // = 0 { 0,1,1,0,0,0,0 }, // = 1 { 1,1,0,1,1,0,1 }, // = 2 { 1,1,1,1,0,0,1 }, // = 3 { 0,1,1,0,0,1,1 }, // = 4 { 1,0,1,1,0,1,1 }, // = 5 { 1,0,1,1,1,1,1 }, // = 6 { 1,1,1,0,0,0,0 }, // = 7 { 1,1,1,1,1,1,1 }, // = 8 { 1,1,1,0,0,1,1 } // = 9 }; void setup() { pinMode(2, OUTPUT); pinMode(3, OUTPUT); pinMode(4, OUTPUT); pinMode(5, OUTPUT); pinMode(6, OUTPUT); pinMode(7, OUTPUT); pinMode(8, OUTPUT); pinMode(9, OUTPUT); writeDot(0); // start with the "dot" off .(점) 을 끈다 } void writeDot(byte dot) { digitalWrite(9, dot); } void sevenSegWrite(byte digit) { byte pin = 2; for (byte segCount = 0; segCount < 7; ++segCount) { digitalWrite(pin, seven_seg_digits[digit][segCount]); ++pin; } } void loop() { for (byte count = 10; count > 0; --count) { delay(1000); sevenSegWrite(count - 1); } delay(4000); } 숫자가 작아지면서 9 ~ 0 순으로 출력된다. ====== 도트 매트릭스 사용하기 ====== 도트 매트릭스 역시 여러가지 종류가 있다. 여기서는 5(가로) * 7(세로) 크기의 LED 를 사용한다. 아래 데이터시트를 참고한다. {{ :computer:embedded:matrix.jpg |}} 아두이노와 연결하기 위해서는 총 12개의 핀이 필요한데, D2 ~ D13 을 사용할 것이다. 핀 매핑은 다음과 같다. | Arduino Pin | Dot Matrix Pin | Col/Row | | D2 | 12 | Row 1 | | D3 | 1 | Col 1 | | D4 | 11 | Row 2 | | D5 | 3 | Col 2 | | D6 | 2 | Row 3 | | D7 | 10 | Col 3 | | D8 | 9 | Row 4 | | D9 | 7 | Col 4 | | D10 | 4 | Row 5 | | D11 | 8 | Col 5 | | D12 | 5 | Row 6 | | D13 | 6 | Row 7 | ===== 회로도 ===== {{ :computer:embedded:matrix.fzz |}} ===== 소스코드 ===== 도트 매스릭스 제어를 위해서는 영화처럼 1초당 30회 이상 화면을 출력하여야 한다. 이런 작업을 위해서 타이머를 이용해야 하는데, MsTimer2 라이브러리가 필요하다. #include // 타이머2 를 이용한 잔상효과 전광판을 만들기 위해 타이머2 라이브러리를 추가한다 // 도트 메트릭스에 출력할 폰트를 설계한다. 1 은 LED ON, 0 은 LED OFF 로 표시 #define SPACE { \ {0,0,0,0,0,0,0}, \ {0,0,0,0,0,0,0}, \ {0,0,0,0,0,0,0}, \ {0,0,0,0,0,0,0}, \ {0,0,0,0,0,0,0} \ } #define I { \ {0,0,1,1,1,0,0}, \ {0,0,0,1,0,0,0}, \ {0,0,0,1,0,0,0}, \ {0,0,0,1,0,0,0}, \ {0,0,1,1,1,0,0} \ } #define Heart { \ {0,0,1,0,1,0,0}, \ {0,1,1,1,1,1,0}, \ {0,0,1,1,1,0,0}, \ {0,0,1,1,1,0,0}, \ {0,0,0,1,0,0,0} \ } #define Y { \ {0,0,1,0,1,0,0}, \ {0,0,1,0,1,0,0}, \ {0,0,0,1,0,0,0}, \ {0,0,0,1,0,0,0}, \ {0,0,0,1,0,0,0} \ } #define O { \ {0,0,0,1,0,0,0}, \ {0,0,1,0,1,0,0}, \ {0,0,1,0,1,0,0}, \ {0,0,1,0,1,0,0}, \ {0,0,0,1,0,0,0} \ } #define U { \ {0,1,0,0,0,1,0}, \ {0,1,0,0,0,1,0}, \ {0,1,0,0,0,1,0}, \ {0,0,1,0,1,0,0}, \ {0,0,0,1,0,0,0} \ } //폰트를 출력할 변수 선언 byte col = 0; int row = 0; byte leds[5][7]; // 5 * 7 LED, 5 * 7 폰트 데이터를 저장할 2차 배열 변수를 선언 int font = 0; int i,j,k; int pins[13] = {-1,2,3,4,5,6,7,8,9,10,11,12,13}; // 메트릭스에 연결될 아두이노의 핀을 어레이 변수에 저장, -1 은 dummy 데이터 pin[0] 은 -1 이며, pin[1] 은 2 이다 int cols[5] = {pins[2], pins[4], pins[6], pins[8], pins[10]}; // 매트릭스 세로 줄의 핀번호를 정의, int rows[7] = {pins[1], pins[3], pins[5], pins[7], pins[9], pins[11], pins[13]}; // 매트릭스 가로 줄의 핀번호를 정의 //출력 폰트 수 const int numFonts = 6; // 출력할 폰트의 수만큼 3차원 배열 폰트를 정의, 출력할 문자는 'I 하트 YOU' 와 빈칸을 포함해서 6 문자이다 byte fonts[numFonts][5][7] = { I, Heart, Y, O, U, SPACE }; //폰트 준비 사용자 함수 void setFont(int font) // 출력할 폰트를 준비하는 사용자 함수, leds[i][j] = fonts[font][i][j]; 에서 7 개의 폰트 중에서 출력할 폰트를 led[i][j] 배열 변수에 저장한다. 이 변수는 dotDisplay() 사용자 함수에서 도트 매트릭스 LED 에 출력된다 { for(int i = 0; i < 5; i++) { for(int j = 0; j < 7; j++) { leds[i][j] = fonts[font][i][j]; } } } //흐르는 폰트를 만드는 사용자 함수 void slideFont(int font, int del) // 현재의 1에서 6까지 Row 데이터를 leds[j][i] = leds[j][i+1]; 하여 1칸씩 앞으로 당기고, 7번째 Row 데이터는 leds[j][6] = fonts[font][j][0 + k]; 와 같이 지정한 폰트의 데이터를 leds 변수에 저장하여 { // 지연시간 delay(del) 시간 동안 매트릭스의 LED 에 출력시킨다. 7개의 Row 데이터를 차례대로 앞으로 흐르도록 한다. for(k = 0; k < 7; k++) { for(i = 0; i < 6; i++) { for(j = 0; j < 5; j++) { leds[j][i] = leds[j][i+1]; } } for(j = 0; j < 5; j++) { leds[j][6] = fonts[font][j][0 + k]; } delay(del); } } //인터럽트 서비스 루틴 void dotDisplay() // leds[][] 2차원 어레이 변수에 저장되어 있는 폰트를 도트매트릭스 LED 로 출력하는 사용자 함수이다. 먼저 7개의 Row 데이터를 출력하고 한 column 을 표시한다. { // 이와 같은 동작을 5회하여 한 화면을 출력한다. 이 사용자 함수는 MsTimer2::set(2, dotDisplay); 에 의해 2ms 마다 호출되어 초당 30 프레임 이상의 화면이 출력된다. // 1줄(column) 데이터 출력을 위한 준비 digitalWrite(cols[col], LOW); // 이전 col OFF col++; if(col == 5) { col = 0; } // 7개의 row 데이터를 준비시킴 for(row = 0; row < 7; row++) { if(leds[col][6-row] == 1) { digitalWrite(rows[row], LOW); } else { digitalWrite(rows[row], HIGH); } } // 매트릭스 1줄(column) 출력 digitalWrite(cols[col], HIGH); } void setup() { // 사용하는 모든 핀을 출력으로 설정 for(int i = 1; i <= 12; i++) { pinMode(pins[i], OUTPUT); } MsTimer2::set(2, dotDisplay); // 타이머 인터럽트 핸들러 등록, 인터럽트가 2ms 마다 발생하면 영화와 같은 잔상효과를 연출할 수 있다. 그리고 타이머2를 작동(시작)시킨다 MsTimer2::start(); // 화면이 껌뻑이면 2ms 보다 작은 값으로 변경하여 사용한다 setFont(font); } void loop() { // 출력할 폰트 계산 font = ++font % numFonts; // font++ 한 값을 numFonts 로 나눈 나머지를 font 값으로 계산한다. 값은 0~6 이 된다 // 다음 폰트를 준비 시킨다. 60ms 마다 slideFont(font, 60); // 흘러가는 폰트 만드는 함수를 호출한다. 출력할 폰트와 흐르는 시간 간격(ms)을 전달인수로 사용한다. 60보다 큰 수를 사용하면 문자의 흐르는 속도가 느려진다. } ---- {{indexmenu>:#1|skipns=/^(wiki|etc|diary|playground)$/ skipfile=/^(todays|about|guestbook)$/ nsort rsort}} ----