====== 아두이노 그대로 따라하기 - 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}}
----