[아두이노] NeoPixel 제어

IOT/아두이노|2019. 4. 1. 09:00

[아두이노] NeoPixel 제어 


오늘의 주제는 NeoPixel로 재밌는 LED 제어에 대해서 살펴보도록 하겠습니다. NeoPoxel를 제어하기 위해서 Adafruit_NeoPixel 라이브러리를 이용하는데 별로 어렵지 않게 LED 색을 만들어 내기 때문에 재미 있는 시간이 될거라 생각됩니다. 사용되는 함수는 몇개 안되지만 그래도 상상력을 얼마나 발휘하느냐에 따라서 멋진 작품들을 만들 수 있는 부품임으로 관심을 많이 가져주세요.

1. NeoPixel


NeoPixel은 여러 종류가 있습니다. 긴 띠, 한개짜리, 링형 막대형 등 다양한 부품들이 있습니다. 가상시뮬레이터에서는 아래 그림처럼 한개짜리와 원형과 여러종류의 링을 제공하고 있네요. 다루는 것은 다 동일하니깐 간단히 다뤄보도록 하겠습니다.


NeoPixel의 핀 구조는 Vcc, Gnd 핀이 각각 두개씩 있으며 Vin과 Vout 핀으로 나눠져 있습니다. Vcc, Gnd은 NeoPixel의 전원을 담당하겠죠. Vin은 해당 NeoPixel의 들어오는 입력값이고 Vout은 해당 NeoPixel에 들어왔던 입력값 중 일부를 다음 NeoPixel로 보내는 출력핀이 됩니다. 아래 그림의 Orange 선을 보시면 됩니다. Orange 선이 신호값이 들어오고 다음 NeoPixel로 보내는 흐름선으로 생각하시면 됩니다.


예를들어, 순차적으로 1초 단위로 1,2,3 NeoPixel이 켜진다고 가정해 봅시다. 신호 값은 1,2,3번에 불이 켜지라고 명령을 내리게 됩니다. 이때 데이터값이 Orange선으로 따라서 해당 위치에 불이 들어오게 합니다. 고로, Orange선은 명령값을 전달하는 통로로 생각하시면 됩니다.


다시 종합하면, 3번까지 NeoPixe에 불이 들어오라고 명령을 내리면 그 신호 값이 1번에 불이 들어오고 2번의 Neopixel에 신호를 보내고 2번에 불이들어오고 다시 Orange선을 통해서 3번 NeoPixel에 신호값이 전달되어 3번에 불이 들어오게 됩니다. NeoPixel간의 연결은 Vout과 Vin을 연결한 통로로 신호가 전달되니깐 이 점만 잘 기억해 주세요.

2. 회로도 구성


  • 준비물 : NeoPixel Ring 12 1개, NeoPixel 6개, 아두이노우노
  • 내용 : 사용 할 NeoPixel를 간단히 연결해 보자.

대충 두종류의 NeoPixel를 Vcc, Gnd 명칭을 잘 보고 연결하시면 됩니다. 그리고 Vin, Vout은 3번핀에 연결했네요. 제 블로그에 올렸던 회로도에서 약간만 수정했네요.


주의할것은 1개짜리 NeoPixel을 연결할때 두번째 라인은 회전시켜서 배치한 거라 Vout, Vin을 잘 체크하시고 연결하셔야 합니다. 그냥 드래그해서 배치한 형태에서 두번째 라인을 Vout에서 Vout로 연결하는 실수를 하시면 안됩니다.

3. 라이브러리 추가


참조 : [아두이노] LCD16x2 I2C(LiquidCrystal_I2C) 제어
라이브러리 출처 : https://github.com/adafruit/Adafruit_NeoPixel


NeoPixel 라이브러리는 여러 종류가 있습니다. 실제로 실험하실 분은 위 링크된 참조 LCD16x2 I2C 포스트 내용중에 라이브러리 추가하는 방법이 잘 나와 있으니깐 보시고 라이브러리를 추가하시면 됩니다.

그런데 가상시뮬레이터로 하시는 분들은

#include <Adafruit_NeoPixel.h>

이 문구만 있으면 됩니다.

라이브러리 올려주신 분의 github 주소입니다. 거기 가셔서 Adafruit_NeoPixel.h, Adafruit_NeoPixel.h.cpp 파일을 꼭 보시기 바랍니다.

거기 보시면 아래와 같은 코딩들이 있습니다. 타입 설정하는데 어느정도 보셔야 합니다. 실제로 구현 하실때 왜 불이 안들어오지 의도치 않게 결과가 나오는 이유가 타입 설정 때문에 그렇습니다. 하나로 고정되어 있는게 아니라 부품에 따라서 그 타입에 맞게 설정해야지 정상적으로 동작합니다.

 Constructor: number of LEDs, pin number, LED type
 
 LED type :
 NEO_KHZ800  800 KHz datastream
 EO_KHZ400  400 KHz datastream
 
 NEO_RGB  ((0 << 6) | (0 << 4) | (1 << 2) | (2))
 NEO_RBG  ((0 << 6) | (0 << 4) | (2 << 2) | (1))
 NEO_GRB  ((1 << 6) | (1 << 4) | (0 << 2) | (2))
 NEO_GBR  ((2 << 6) | (2 << 4) | (0 << 2) | (1))
 NEO_BRG  ((1 << 6) | (1 << 4) | (2 << 2) | (0))
 NEO_BGR  ((2 << 6) | (2 << 4) | (1 << 2) | (0))
 NEO_RGBW
 ...
 NEO_WRGB 
 ...
 

다른 함수들도 꼭 보시고 대충 어떤 느낌으로 흘러가는지만 살펴보세요. 나중에 색을 세팅하는 함수에서 위에 나와있는 RGB라는 글자들이 보이시죠. 위치를 나타냅니다. 색을 세팅하는 함수는 같지만 해당 위치의 인자값은 달라지게 됩니다. RGB 인자를 순서대로 넣었는데 처음 세팅을 BGR로 해버렸다면 코딩하는 사람은 RGB라고 생각하고 색 값을 넣었지만 색 세팅함수에서는 BGR로 인식해버릴 수 있으니깐 잘 살펴보셔야 해요. 그리고 함수명들만 헤더 파일에 어떤게 있는지만 한번 살펴봐 주세요.

4. 코딩


  • 사용함수 : Adafruit_NeoPixel(), neopixel.begin(), neopixel.setPixelColor(), neopixel.show(), neopixel.clear()
  • 내용 : 간단히 순차적으로 NeoPixel에 불이 들어오게 하자.
  • 참고소스 : [아두이노] 3x3x3 CUBE LED 제어 III

함수

  • Adafruit_NeoPixel(NeoPixel수, NeoPixelPin, NEO_GRB + NEO_KHZ800) : 생성자함수로 NeoPixel 클래스 객체를 만들때 사용합니다. 몇개의 NeoPixel를 사용하고 어느 핀을 사용할지와 Neopixel 타입을 설정을 하는 함수입니다. 우선 가상시뮬에이터는 이 값을 기본으로 사용하세요.
  • neopixel.begin() : 라이브러릴 사용하면 꼭 객체 시작을 알리는 함수가 있죠. 이것도 마찬가지입니다. 사용하겠다고 선언.
  • neopixel.setPixelColor(위치, R, G, B) : 색을 나타냅니다. 위와 같은 Neopixel 타입을 설정했을때 RGB의 위치는 이와 같이 결정됩니다. 참고로 타입이 바뀌면 색상 위치도 바뀌니깐 햇갈리지 마세요. 쉽게말해서, 색을 세팅하는 함수입니다.
  • neopixel.show() : 색이 세팅한 값을 실제로 보여주라는 함수입니다. Orange선을 통해서 해당 위치에 색이 켜지겠지요.
  • neopixel.clear() : 지금까지 켜진 NeoPixel를 초기화하는 함수입니다. 켜진 NeoPixel를 다 꺼지겠지요.
    참고로 clear()함수로 명령을 내렷다고 해서 NeoPixel에 들어온 불이 꺼지지는 않습니다. 내부적으로 세팅한 setPixelColor()함수의 값만 초기화 될 뿐이죠. 내부적으로 초기화 된 값에서 show()함수를 실행 시켜야 초기화된 상태로 NeoPixel이 꺼지게 됩니다. ( "왜! 안꺼져! 이런 실수를 안하셨으면 해요!")

설계

NeoPixel 라이브러리를 사용하니깐 사용할 객체를 선언해야 합니다. 한개짜리 6개와 12개짜리 링 한개로 총 18개의 NeoPixel를 제어하니깐 아래와 같이 선언하시면 되겠습니다.

const byte neopixelPin = 3;
Adafruit_NeoPixel neopixel = Adafruit_NeoPixel(18, neopixelPin, NEO_GRB + NEO_KHZ800);

순차적으로 NeoPixel로 불이 들어와야 하니깐 18개NeoPixel 기준으로 for문 한개를 사용하여 18번 반복하면 되겠죠.

for(int i=0;i<18;i++){
   NeoPixel i에 불이 들어오게하라;
}

이 로직으로 대충 돌아가야 겠죠. NeoPixel 함수는 불이 들어올려면 색을 세팅하고 그 세팅값을 NeoPixel로 보내야 합니다.

setPixelColor()함수로 색을 정하고, show()함수로 출력하면 됩니다.

  for(int i=0;i<24;i++){
    neopixel.setPixelColor(i, 255, 0, 0);    
    neopixel.show();
    delay(100);    
  } 

대충 이렇게 코딩 됩니다. i번째 위치 NeoPixel에 RGB에서 R(255)로 Red색으로 세팅하고 show()함수로 해당 위치에 NeoPixel에 불이 들어오게 됩니다. 그렇게 어렵지 않죠. NeoPixel 라이브러리로 간단히 해당 위치에 원하는 색으로 불이 들어오게 하는 함수가 이 두 함수입니다. 어떻게 로직을 짜냐에 따라서 재밌는 패턴이 만들어 지고 화려하게 불이 들어오게 할 수 도도 있게 됩니다.

딜레이는 빠르게 보기 위해서 0.1초로 했네요. 가상시뮬레이터에는 지연렉이 좀 있기 때문에 좀 빠르게 보기 위한 값이니깐 실험할 때는 원하는 시간 간격으로 돌려보세요.

그리고, 시작은 begin()함수를 사용하고 초기화는 clear()함수를 사용합니다. 꺼먹지 마세요. 그리고 clear()함수 명령을 내렸는데 왜 NeoPixel이 안꺼지지 하고 혼동하시는 분이 있는데요. 무조건 Neopixel에 명령을 보내는 것은 show()함수 입니다. 내부적으로 clear()함수로 초기화 되었어도 출력된 NeoPixel은 초기화 되지 않습니다. show()함수로 같이 쓰시면 외부의 NeoPixel까지 초기화 된다는 점을 꼭 기억해주세요.

전체적으로 코딩을 하면,

#include <Adafruit_NeoPixel.h>

const byte neopixelPin = 3; 
Adafruit_NeoPixel neopixel = Adafruit_NeoPixel(18, neopixelPin, NEO_GRB + NEO_KHZ800);

void setup() {
  neopixel.begin();  
}

void loop() {  
  for(int i=0;i<18;i++){
    neopixel.setPixelColor(i, 255, 0, 0);    
    neopixel.show();
    delay(100);    
  } 
  neopixel.clear(); 
  neopixel.show();
  delay(100);    
}

5. 결과




오늘은 회로도 만드는 것이 간단하니깐 전과정을 기록해 봤네요. 그리고 결과는 Red 색 하나만 순차적으로 불이 들어오게 하면 좀 그러니깐 R->G->B순으로 불이 들어오게 가상시뮬레이터에서 돌려봤네요. 어떤 결과가 나오는지 동영상을 꼭 확인 해보세요. 아니면 공개 회로도에 링크된 곳에 가셔서 가상시뮬레이터를 직접 돌려보세요.


마무리


진짜 오늘 배운 부품 소재는 재맸는 소재입니다. 아마도 한번은 보셨을지 모르겠네요. 클럽에 네온싸인 같은 것이나 할로윈 축제때 어떤 사람을 보면 몸에서 야광 LED가 깜박이는 것들을 보셨을꺼에요. 유튜브를 검색을 하시면 NeoPixel로 모니터를 만들어서 동영상을 감상하시는 분도 있고 연주가들은 악기에 붙여서 휘황찬란하게 표현하는 분도 있고 찾아보시면 스케일들이 남다른 분들의 영상물들을 많이 보실 수 있을 꺼에요. 아마도 실제로 구매해서 만들고 싶은 충동이 느껴지실 거라고 생각됩니다. 그런데 개당 가격이 좀 비싼편이라서 약간 구매해서 실험하기는 애매한 점도 있습니다. 우리에게는 가싱시뮬레이터가 있고 그걸 통해서 대리 만족을 느끼면 됩니다.

오늘은 간단한 동작으로만 채웠습니다. 그 이유는 여러분들의 상상력을 발휘할 시간을 주기 위해서 입니다. 다양한 표현을 시도해 보세요. 여러분들은 NeoPixel로 난 무엇을 만들어 볼까? 무엇을 만들 수 있을까? 계속 자신에게 질문을 던져주세요. 그리고 순차적으로 출력되는 저 for문을 가지고도 다양한 표현을 할 수 있습니다. setPixelColor()함수와 show()함수의 의미와 동작원리를 곰곰히 잘 생각해보시면 for문 한개로도 꽤 다양한 패턴을 만드실 수 있으니깐 꼭 동작 원리를 이해해 주세요.


댓글()

[아두이노] 74HC595+8x8 도트매트릭스 제어

IOT/아두이노|2019. 3. 31. 09:00

[아두이노] 74HC595+8x8 도트매트릭스 제어



이전 시간에는 8x8 LED 핀을 일일히 아두이노에 연결을 하다 보니깐 16개의 핀이 필요했습니다. 너무 많은 핀을 사용하다 보니깐 줄여보고 싶은 마음이 생기지 않나요. 우리가 7 Segment LED를 제어할 때 사용한 부품이 떠오르는 분이 있을 지 모르겠군요. 그 부품은 74HC595칩 핀으로 쉬프트레지스트를 이용하면 16개 핀에서 3개의 핀으로 줄어들게 됩니다. 74HC595 칩은 8개의 출력 핀을 제공하는데 이 칩을 직렬로 연결하면 무제한 핀을 사용할 수 있습니다. 물론 Max7219칩 하나로 16개핀을 한번에 제어가 가능합니다. 하지만 가상시뮬레이터에서는 해당 칩이 없고 74HC595 칩뿐이 없으니 그걸로 실험을 할 수 밖에 없겠죠.
그러면 74HC595 칩 2개를 이용해서 8x8 LED를 제어해보록 하겠습니다.

1. 74HC595 직렬 연결



복습의 시간으로 latchPin(12), clockPin(11), dataPin(9)을 통해서 제어합니다. 9번, 11번, 12번 핀을 연결한 선 색을 잘 보시기 바랍니다. 11, 12번핀은 서로 연결되어 있지만 9번 데이터 입력되는 핀은 Orange 선이 수평선으로 이여진게 직렬 데이터가 전송되는 연결인데 오른쪽 74HC595핀의 Q7s핀에서 왼쪽 74HC595의 dataPin에 입력으로 이여집니다.

예) 아래와 같은 입력이 이뤄질 경우74HC595 직렬 연결시 데이터는 어떻게 배치되는지 살펴 봅시다.

0B11111110 00000001


어떤 느낌으로 들어가시는지 아시겠지요.

실제로 코딩할대는 byte 배열 변수를 층과 호실로 나눠서 2개를 만들었는데요. 가능하다면 int형으로 하나로 16bit로 표현하셔도 됩니다. 이해를 돕기위해서 실험은 2개의 byte(8bit)로 실험 했습니다.

2. 회로도 구성


  • 준비물 : Yellow LED 64개, 저항 220옴 8개, 74HC595 칩 2개, 아두이노우노, 뻥판
  • 내용 : 8x8 도트매트릭스 형태를 표현하고 74HC595 칩 두개로 출력하니깐 거기에 맞게 배치해보자.


3. 코딩



복습

  • 74HC595칩 제어 :
    - latchPin, clockPin, dataPin을 pinMode(출력핀, 모드)로 선언
    - digitalWrite(결쇠핀, 열림/잠금)으로 74HC595 칩에 입력할때 열림/잠금을 제어
    - shiftOut(데이터입력핀, 클럭핀, MSBFIRST, 전송값)으로 이걸 통해서 역순으로 데이터가 배치

설계

1층만 개방은 1층에 Gnd 상태이고 나머지 층은 HIGH상태여야 하잖아요.

OB11111110

이렇게 됩니다. 오른쪽 끝이 1층이고 역순으로 순서대로 '87654321'층으로 위치가 결정됩니다.

그럼 1층1호실에 불이 들어올려면은 어떻게 해야 할까요. '0B11111110' 로 1층만 개방해야 겠죠.
1~8호실중에 1호실만 불이 들어올려면 1호실만 HIGH 상태이고 나머지는 Gnd(LOW)상태여야 겠죠.

0B00000001

1층(0B11111110)과 1호실(0B00000001)로 합쳐져서 1호실만 불이 들어오게 됩니다.
층별 개방할려면,

0B11111110,  
0B11111101,
0B11111011,
0B11110111,
0B11101111,
0B11011111,
0B10111111,  
0B01111111,

이렇게 '0'인 위치만 개방 해야겠죠

호실별 개방할려면,

0B00000001,  
0B00000010,  
0B00000100,  
0B00001000,  
0B00010000,  
0B00100000,  
0B01000000,  
0B10000000

이렇게 '1'인 위치로 호실만 전류를 보내겠다는 표현이겠죠. 각층의 8개의 호실에 불이 순서대로 깜박일려면 2중 for문을 사용해서 표현하면 됩니다.

for(int i=0;i<8;i++){
 for(int j=0;j<8;j++){
     층[i] 패턴에 호실[j=0~7]까지 순서대로 호실패턴이 대입을 수행;
 }
}

즉, 층[0] 패턴일때 호실[0]~[7]패턴이 대입된다면
'0B11111110 ' + '0B00000001 = 1층1호실 불이 들어옴
'0B11111110 ' + '0B00000010 = 1층2호실 불이 들어옴
'0B11111110 ' + '0B00000100 = 1층3호실 불이 들어옴
.......

대충 이런식으로 층[0]~[7]까지의 패턴이 있는데 각 층에 대한 호실[0]~[7]까지의 패턴이 순차적으로 대입되는 로직을 머리속에서 그리셔야 합니다. 어떤식인지 이해하셨는지 모르겠네요.

전체적으로 코딩을 하면,

byte layer[]={ //층
0B11111110,  
0B11111101,
0B11111011,
0B11110111,
0B11101111,
0B11011111,
0B10111111,  
0B01111111,
};
byte room[]={ //호실
0B00000001,  
0B00000010,  
0B00000100,  
0B00001000,  
0B00010000,  
0B00100000,  
0B01000000,  
0B10000000
};
int latchPin = 11; //ST_CP Pin
int clockPin = 12; //SH_CP Pin
int dataPin = 9; //DS Pin

void setup(){
pinMode(latchPin, OUTPUT);
pinMode(clockPin, OUTPUT);
pinMode(dataPin, OUTPUT);
}
void loop(){
  for(int i=0;i<8;i++){  //순차적으로 릴레이 깜박이기    
    for(int j=0;j<8;j++){
      digitalWrite(latchPin, LOW);
      shiftOut(dataPin, clockPin, MSBFIRST, layer[i]);
      shiftOut(dataPin, clockPin, MSBFIRST, room[j]);
      digitalWrite(latchPin, HIGH);       
      delay(100);
    }
  }  
}

4. 결과


간단히 실험 결과 영상만 올렸습니다. 회로도 제작 과정은 생략합니다. 이전 74HC595 칩을 제어할때 다뤘던 내용이니 결과로 어떻게 출력되니지만 확인하시고 회로도 만드는 과정은 74HC595편을 참고하세요


마무리


오늘은 어제 실험한 내용에 오래전에 배운 74HC595칩을 결합을 해보았습니다.

내일은 LED의 꽃인 네오픽셀을 다루는 것을 해보겠습니다. LED에 관해서는 얼추 기초적인 부분은 다 끝난 것 같네요. 나머지 부분은 대부분 여러가지 부품과 결합해서 표현하는 응용편이니깐요. 암튼 네오픽셀이 가장 화려하고 재미 있을 듯 싶네요.


댓글()

[아두이노] 8x8 도트매트릭스 제어

IOT/아두이노|2019. 3. 30. 09:00

[아두이노] 8x8 도트매트릭스 제어 



이건 조이스틱 제어 한뒤에 다룰려고 했던 주제인데 3x3x3 CUBE LED랑 같기 때문에 이번에 같이 소개하는 게 좋을 것 같아서 바로 도트매트릭스에 대해서 살펴보도록 하겠습니다.

1. 8x8 도트매트릭스


제가 가지고 있는 모델입니다. 뒷면에 핀이 8x8핀으로 구성되어 있는데 그냥 이상태에서 실험하시면 딱 이 부품만 사용하면 아두이노에서 사용할 수 있는 핀은 몇개 안남습니다. 여기에 따로 모듈을 붙여서 사용핀을 줄여서 실험하셔야 다른 부품들과 같이 다양한 실험을 할 수 있습니다. 모듈화 된 제품을 구하시는게 좋고요. Max7219 칩인가 그걸로 도트매트릭스 핀을 다 제어가 가능합니다. 아니면 74HC595를 2개 사용하여 8핀씩 제어하는 방법도 있고요. 그냥 모듈화 된 것을 구매하셔서 라이브러리로 쉽게 제어하는게 편하겠지요.


오늘 다루는 내용은 사실 실제부품과는 차이가 있고 원리를 설명하며 가상시뮬레이터에서 실험을 통해거 그 결과를 보기 위한 내용으로 꾸며질 꺼에요. 그 이유는 도트매트릭스 부품이 가상시뮬레이터에서는 없기 때문이고 해당 8x8 도트매트릭스 부품은 핀 번호가 좀 복잡합니다. 단순하에 8x8핀이 있다고 해서 대충 연결해서는 원하는 결과를 얻을 수 없습니다.

실제로 실험하실려면은 아래 아두이노 튜토리얼를 참고 하시기 바랍니다.

이것만 보시면 안되고 앞쪽과 뒷쪽을 구분해야 합니다. 앞쪽에 볼록 약간 뛰어 나온 모양이 있는데 그 부분이 앞쬭인데 부품이름이 적혀있는 면일 꺼에요. 가장 중요한 핀번호를 알아야 합니다.

여기 블로그 게시물을 쓴분의 8x8 도트매트릭스 뒷면에 핀번호 사진이 있습니다. 그걸 보시고 핀번호를 확인하시면 됩니다. 그러면 아두이노 공식 홈페이지에 나온 튜토리얼 예제를 보시고 핀 연결한 뒤에 실제로 돌려보시면 아마 될꺼에요.

이걸 다 다룰려면 포스트가 또 길어지고 실제 아두이노를 다루는 것에 초점을 둔게 아니라 가상시뮬레이터 실험을 통해서 아두이노를 이해하는게 목적임으로 간단히 8x8 도트매트릭스 원리를 이해하는 내용으로 전개하겠습니다.

그러면 직접 LED로 제작해야죠. 도트매트릭스 회로도를 만들어 봅시다. 다시 노가다의 길이 시작 되었네요.

2. 8x8 도트매트릭스 회로도 구성


  • 준비물 : Yellow LED 64개, 저항 220옴 8개, 아두이노우노
  • 내용 : 8x8 도트매트릭스 형태를 표현하자.


아마도 지난시간에 3x3x3 CUBE LED를 같다고 생각하시는 분들이 있을거에요. 맞습니다. 도트매트릭스가 이런 구조로 된 부품입니다. 이 회로도를 만드는 중에 난 지금 뭐하고 있는거지 하는 생각이 들정도로 엄청 노가다 작업을 했네요. 동작 원리는 동일합니다. 층과 호실의 개념으로 접근하시면 됩니다. 이건 지난시간에 많이 설명을 해서 더이상 반복하지 않게습니다.

스피드하게 다음 코딩작업으로 갈까요. 참고로 A0~A4핀은 아날로그 입력핀인데 pinMode()를 선언하면 디지털핀 용으로 사용이 가능합니다. 그래서, 16개 핀이 필요한데 겨우 배치했네요. 참고로 0,1핀은 통신핀으로 일부러 사용하지 않았습니다. 사용할 수 있지만 나중에 시리얼통신을 할 때 적용해야 하기 때문에 습관을 0,1핀을 될 수 있으면 사용안하시는게 좋습니다.

3. 코딩



복습

  • pinMode(사용핀, 사용모드) : 사용핀을 INPUT/OUTPUT/INPUT_PULLUP 모드중 뭘로 사용할지를 정함.
  • digitalWrite(사용핀, 출력형태) : 사용핀을 HIGH(5V) or LOW(0V) 중 출력 형태를 정함.
  • delay(1000) : 1초(1000)를 대기한다.

int layer[8] = {10,11,12,13,A0,A1,A2,A3};
int room[8] = {2,3,4,5,6,7,8,9};
int m_layer = 8;  // 층 수
int m_room = 8; // 호실 수

void setup()
{  
  for(int i=0;i<m_layer;i++){
    pinMode(layer[i], OUTPUT); // 층 선언
    pinMode(room[i], OUTPUT); //호실 선언
    digitalWrite(layer[i], HIGH);  // 각층 닫기
    digitalWrite(room[i], LOW);  // 각호실 닫기   
  }   
}
void loop()
{
  for(int i=0;i<m_layer;i++){
    digitalWrite(layer[i], LOW);
    for(int j=0;j<m_room;j++){      
      digitalWrite(room[j], HIGH);
      delay(100);
      digitalWrite(room[j], LOW);
    }
    digitalWrite(layer[i], HIGH);
  }
}  

4. 패턴글자 출력 코딩


참고소스 : [아두이노] 3x3x3 CUBE LED 패턴 코딩 완성본


참고소스는 각 층별로 패턴을 저장했다면 이번 경우는 패턴을 기준으로 각층별의 패턴을 묶어서 저장했네요. 약간의 차이가 있습니다.
'STEEM'이란 글자를 만들기 위해서 8x8에서 'S'를 하나의 패턴으로 층별로 값을 묶었습니다. 지난시간에는 1층이면 1층만의 패턴을 하나의 배열변수에 저장했다면 이번에는 'S'자를 만드는 1~8층의 정보를 하나의 배열변수에 저장했다고 생각하시면 됩니다. 약간은 차이가 있고 이런식으로도 패턴을 저장하는구나를 보여주기 위해서 입니다. 사실, 이방식으로 패턴을 저장하는게 더 옳은 방법입니다. 지난 포스팅에 패턴은 층별 개별적인 패턴을 만들기 때문에 층별 배열변수로 저장이 이뤄졌고요. 이번에는 전체 층을 합산한 패턴을 만들기 때문에 패턴에 대한 전체층의 값을 묶어서 저장해서 좋더 효율으로 패턴을 만들고 출력할 수 있다는 점을 생각하시고 코드를 보시기 바랍니다.

int layer[8] = {10,11,12,13,A0,A1,A2,A3};
int room[8] = {2,3,4,5,6,7,8,9};
int m_layer = 8;  // 층 수
int m_room = 8; // 호실 수

unsigned int layerPattern[5][8] ={ //패턴
  //S :   
  {0x3C,0x66,0x06,0x1C,0x38,0x60,0x66,0x3C},
  //T : 
  {0x18,0x18,0x18,0x18,0x18,0x18,0xFF,0xFF},  
  //E : 
  {0x7E,0x7E,0x60,0x7E,0x7E,0x60,0x7E,0x7E},
  //E : 
  {0x7E,0x7E,0x60,0x7E,0x7E,0x60,0x7E,0x7E},
  //M : 
  {0xC3,0xC3,0xC3,0xC3,0xDB,0xFF,0xE7,0xC3}  
};

void setup()
{  
  for(int i=0;i<m_layer;i++){
    pinMode(layer[i], OUTPUT); // 층 선언
    pinMode(room[i], OUTPUT); //호실 선언
    digitalWrite(layer[i], HIGH);  // 각층 닫기
    digitalWrite(room[i], LOW);  // 각호실 닫기
   
  }  
 
}
void loop()
{
  for(int i=0;i<5;i++){  //순차적으로 패턴수만큼 동작
    for(int j=0;j<30;j++){  //m_delay 시간만큼 해당 패턴을 유지한다.
      for(int p=0;p<8;p++){ //층 별 패턴
        LEDSetting(layer[p], layerPattern[i][p]);  //1층 i번째 패턴        
      }
    }
    delay(500);
  }  
}  
void LEDSetting(int layer, unsigned int state){
  digitalWrite(layer, LOW); //층 개방
  for(int i=0;i<8;i++){    
    digitalWrite(room[i],bitRead(state, i)); //호실 개방     
  }
  delay(5);
  for(int i=0;i<8;i++){     
    digitalWrite(room[i], LOW);   //호실 닫힘   
  }
  digitalWrite(layer, HIGH); //층 닫힘    
}

5. 결과


가상시뮬레이터 결과는 순차적으로 릴레이 출력과 글자 패턴을 출력하는 영상입니다. 따로 만드는 과정은 생략했고요. 그냥 어떻게 동작하는지만 살펴보세요. 그리고 글자 패턴은 'STEEM' 글자가 출력되게 했네요. 가상시물레이터가 뚜렷하게 출력되지는 않네요.

마무리


코딩에 대한 설명은 생략했습니다. 지난시간에 순차적으로 릴레이하는 코딩을 그대로 인용하였고, 패턴 만들기로 2편의 포스팅으로 대충 감을 잡으셨을꺼에요. 따로, 8x8 도트 매트릭스를 설명 드릴 것은 없네요. 3x3x3 CUBE LED나 8x8 도트매트릭스는 다 동일한 원리니깐요. 오늘은 복습 차원으로 생각하시면 되겠습니다.

사실 이 포스트는 지난 3x3x3 CUBE LED할 때 완성했는데 따로 Steem API가 재미 있어서 그쪽으로 맛보기로 포스팅하다 보니깐 이제서야 올리게 되었네요. 1일 1포스트를 하다 보니깐 뭔가 빨리 올리고 싶어서 밀리게 되네요.

아! Steem API 몇개 함수를 이용해서 또 재밌는 걸 만들려고 구상중이네요. 그건 포스팅을 안하겠지만요. 아두이노하다거 포스트 정체성이 상실하니깐 아두이노를 당분간 계속 올려야겠네요. 혹시 재밌는 표현이 완성된 것 있으면 주말에나 한번씩 포스팅을 해봐야 겠네요.


댓글()

[아두이노] 아두이노 변수란

IOT/아두이노|2019. 3. 29. 09:00

[아두이노] 아두이노 변수란 



오늘은 아두이노의 변수에 대해서 살펴보도록 하겠습니다. 사실 변수에 대한 개념 정의를 아두이노 초기에 소개했었야 할 부분입니다. 개념적 설명 부분이 전부라 우선은 가상시뮬레이터로 회로도를 만들고 결과를 출력시키는 부분에 초점을 맞춰 흥미를 전달하고자 하는데 초점을 맞췄기 때문에 지금까지 다루지 않았네요. 무척 중요한 주제인데 이제서야 설명이 들어가네요. 왜! 중요하냐면요 ATmega328 아두이노의 경우 프로그램 메모리가 32KB, 데이터메모리 2KB 밖에 사용할 수 없습니다. 데이터메모리에는 2KB(2x1024byte)는 2048byte 뿐이 기록할 수 없습니다. 그래서 변수를 함부로 막 사용해서는 안되고 효율적인 변수사용이 필요합니다. 경우에 따라서는 구지 변수로 선언하지 말고 상수로 표현해서 사용공간을 확보해야 합니다.

그래서 변수와 상수에 대해서 알아야하는 큰 이유가 됩니다. 이제 변수와 상수에 대해서 살펴보도록 하겠습니다.

1. 변수와 상수


수학시간에 많이 들었던 단어일꺼에요. 변수란 변하는 수이고 상수란 변하지 않는 수라고 기본적으로 알고 있을 겁니다. 그럼 프로그램에서 변수와 상수를 살펴보도록 하죠.

예) int i =10;



i는 변수이고 10은 상수입니다. i 공간에는 10이 저장될 수 있고 11이 저장될 수도 있기 때문에 변수라고 부릅니다. 하지만 10은 10이란 수로 고정되어 10을 11로 바꿀 수는 없기 때문에 상수라고 부릅니다. 여기서 변수란 프로그램에서는 저장공간이라는 개념을 추가해야 합니다.

프로그램에서는 어떤 값이 저장될때 메모리주소지에 저장됩니다. 그 주소지는 숫자형태로 되어 있어서 우리고 직접 주소지로 접근하기는 어렵습니다. '0x7fffad92dd80'주소지가 있다면 이 주소지공간에 10이 저장되었다고 생각해보세요.
'0x7fffad92dd80=10' 실제로 들어가겠죠. 하지만 코딩을 하는 인간 입장에서는 주소지로 해서 접근하는 건 힘이 듭니다. 그래서 변수라는 이름을 사용하여 그 주소지를 대신 접근할 수 있게 합니다.

i의 주소지가 '0x7fffad92dd80'이라 했을 때 i=10; 하면 우리가 해독할때는 "i변수공간에 10이 저장되는 구나" 라고 생각하지만 컴퓨터는 '399942732'주소지에 10이 기록된다라고 생각하시면 됩니다.

위 설명은 다 필요 없고 그냥 프로그램에서는 변수란 저장공간이라는 개념만 탑재하시면 됩니다.

예) int A[3] = {10, 20, 30};


즉, A[0]이란 변수가 주소지 '0x7fffad92dd80'을 가리키고, 이 주소지에 '10'이란 값이 저장됩니다.

2. 프로그램언어에서 상수 표현


총 3가지로 위 그림에서 보는 것 처럼 사용할 수 있습니다. 10은 상수라고 쉽게 아는데 나머지 두개는 왜 상수인지 잘 이해가 안되는 분들도 있을 겁니다.

첫번째, '#define'으로 선언된 경우의 A는 10을 가리킨다고 생각하시면 됩니다. A라는 단어 자체가 10이라고 고정시킨 이름이라고 생각하시면 돼요. A는 무조건 10이고 11이나 9가 절대 될 수 없는 상수라고 생각하세요.

두번째, 'const' 단어가 붙으면 상수변수라고 생각하시면 됩니다. 즉, B라는 상수변수가 10을 지칭한다고 생각하시면 됩니다. 'const' 붙은 상수변수 B는 무조건 10이고 B에는 11이나 9가 절대 될 수 없는 상수라고 생각하세요.

그러면 이런걸 왜 선언하고 사용하느냐고 생각하실꺼에요. 상수 사용은 프로그램의 가독성을 높이기 위해서 입니다. 즉, 상수를 그 자체로 쓰게 되면은 사실 이 숫자가 가리키는 의미 쉽게 알 수 없습니다. 상수로 사용을 하되 그 의미를 전달하고자 할때 상수변수라는 걸 사용하게 됩니다.

그리고 '#define'과 'const'의 구별하는 법은 사실을 딱히 없습니다. 암묵적으로 대문자 표현의 경우는 '#define'으로 선언하고 'const'변수는 일반적으로 변수 표현으로 선언해서 사용하는 경우가 많습니다. 대문자형 변수명이면 '#define'으로 선언된 상수변수구나 정도로 생각할 수 있습니다. 물론 'const' 변수에서도 대문자 선언을 하는 경우도 있습니다. 그리고 일반변수와 상수변수를 차이를 두기위해서 대문자변수명과 소문자변수명으로 구별해서 쓰시면 구별하는게 아주 좋습니다.

그리고, 아두이노에서 상수변수를 많이 보셨을거에요. HIGH, LOW, OUTPUT, INPUT 등등요. HIGH은 '1'이란 값을 가리킵니다. LOW은 '0'이란 값을 가리킵니다. 여기서 아두이노는 출력표현을 할때 '1' or '0'으로 해도 출력되는 이유가 상수변수가 저장된 원래 값이 1과 0이기 때문입니다. '1' or '0'으로 표현하면 의미를 전달하기가 어렵습니다. 하지만 HIGH or LOW로 표현하면 출력시 전류를 보내는 구나 아니면 전류를 보내지 않는 구나 하고 쉽게 의미를 이해 할 수 있게 됩니다. 상수변수에 대해서 이제 어느정도 이해를 하셨을 거라 생각됩니다.

마지막으로 상수변수를 사용하면 좋은점은 '#define' 이나 'const'으로 선언된 상수변수는 데이터메모리 주소지를 사용하지 않습니다. 프로그램메모리에 기록됩니다. 그 이유는 컴파일시에 프로그램에 이식되기 때문이지요. 표현이 적절할 지 모르겠지만 프로그램 언어로 코딩된 명령문들은 그대로 아두이노가 해독하지 못합니다. 기계가 이해할 수 있는 언어로 번역해야 합니다. 그 과정이 컴파일 과정이고요. 인간의 언어에서 기계 언어로 바뀌는 과정이라고 생각하시면 됩니다. 이때 상수변수로 선언된 것들은 상수변수가 가리키는 숫자값을 그대로 상수변수가 사용된 위치에 숫자로 대입되어 컴파일을 거치게 됩니다. 그래서 데이터메모리 주소지를 사용하지 않게 됩니다. 그런 이유로 상수변수를 사용하는 이유가 되겠습니다.

즉,

int A = 10;

int형만큼의 자료공간을 데이터메모리를 차지합니다.

const int B =10;

컴파일때 B라는 위치에 10이 대입되어 컴파일 되기 때문에 데이터메모리를 차지않지 않습니다.

그럼 '#define'과 'const' 차이가 궁금하시는 분들이 있을거에요. 이 경우는 컴파일 때 차이가 납니다. '#define'은 경우는 그냥 사용된 상수의 크기 판별안하고 대입되어 컴파일 되지만 'const'은 선언된 변수의 크기에 체크하고 컴파일이 진행됩니다. 추천은 'const'를 사용하는게 좋다고 합니다.

아두이노 예제 소스들을 보면 오래된 자료들은 '#define'으로 핀 번호를 지정하는 경우가 많으나 최근 자료들은 'const' 예제들이 주로 사용됩니다.

3. 아두이노에서 변수 선언하는 이유


void setup() {
pinMode(13, OUTPUT);
}

void loop() {
digitalWrite(13, HIGH);
delay(1000);
digitalWrite(13, LOW);
delay(1000); 
}

이전 포스팅들을 보면 위와 같은 코딩을 주로 했습니다. 그 이유는 우선 해당 함수가 몇번 핀과 연관이 되었는지 이해를 시키기 위해서였죠. 하지만 정석으로 코딩을 하면 다음과 같습니다.

const byte LedPin = 13;

void setup() {
pinMode(LedPin, OUTPUT);
}

void loop() {
digitalWrite(LedPin, HIGH);
delay(1000);
digitalWrite(LedPin, LOW);
delay(1000); 
}

대충 이런식으로 하면 되겠죠. 이렇게 코딩하면 장점이 뭘까요. 비로 setup(), loop()함수를 건들 필요가 없어집니다. 가령 13번핀에 LED 출력을 담당했는데 다른 부품을 같이 사용하면서 13번 자리를 다른 부품에게 양보하고 LED를 2번 핀으로 옮겼다고 생각해 보세요. 그럼 setup()와 loop()함수에 표현된 13이란 숫자를 전부 찾아가서 2이란 숫자로 바꿔야 겠죠. 이렇게 핀번호를 변수로 선언하면 변수가 선언된 곳에서 13을 2로 변경만 하면 자동으로 setup(), loop()함수가 2번핀으로 변경됩니다.
단적으로 보여준 예이지만 이런식으로 핀 번호를 변수로 바꾸면 핀위치가 바뀌더라고 수정이 쉽게 되는 장점을 가지고 있습니다.

또 하나는 변수는 선언된 위치에 따라 그 영역이 달라진다는 점을 주의해서 사용해야 합니다. 함수 밖에 선언되면 모든 함수에 영향권에 두게 되면서 setup()와 loop()함수에서 그 변수를 사용 가능해집니다. 하지만 변수가 setup()함수 내에나 loop()함수 내에서 선언하게 되면 그 함수내에서만 사용할 수 있고 함수가 끝나면 소멸하게 된다는 점을 꼭 기억해 주세요.

[ 변수의 영역 ]


쉽게 설명하면은, 위 그림에서 보는 것처럼 a가 선언된 변수 위치는 setup(), loop(), test()함수내에서도 사용이 가능합니다. 하지만 b변수는 setup()함수에서만 사용이 가능하고 setup()함수가 끝나면 소멸합니다. c변수는 loop()함수안에서만 사용이 가능하고 loop()함수가 끝나면 소멸합니다. 하지만 다른점은 c변수는 test(c) 명령으로 c값을 test()함수의 k변수로 넘겨주게 됩니다. 결론은 c의 값이 test()함수에서도 계속 사용이 가능하게 됩니다. 그리고 d변수는 test()함수가 끝나면 소멸합니다. 대충 변수의 영역에 대해서 살펴보았습니다. 변수를 어디에 선언을 해야할지 감이 잡히셨는지 모르겠네요.

4. 자료형


자료형이란 변수를 저장하는 공간의 크기입니다. 아래 표는 가상시뮬레이터에서 측정한 저장공간의 크기입니다. 운영체제에 따라서 컴파일러에 따라서 그 값은 달라질 수 있다는 점을 감안하시고 보세요. 쉽게 말해서 자료형은 뭘 담을 수 있는 그릇으로 생각하시면 됩니다. 예를들어, 1리터를 담을 수 있는 A 그릇과 50리터를 담을 수 있는 B그릇과 100리터를 담을 수 있는 C 그릇이 있습니다. 조건으로 20리터의 물을 담을려고 한다면 어느 그릇에 담아야 할까요? A 그릇은 1리터 밖에 담지 못하고 그릇 밖으로 쏟아지겠죠. B그릇은 20리터를 담고도 30리터를 더 담을 수 있어서 충분하겠죠. C그릇은 20리터를 담고도 80리터를 더 담을 수 있겠죠. 결론은 B, C그릇에 담으면 해결됩니다. 그런데 어느 그릇이 더 효율적인가요 정답은 B그릇이겠죠. C그릇은 20리터를 담을 수 있지만 80리터를 담을 수 있는 공간이 낭비하게 됩니다. 크기만 크고 자리는 다 차지하고 B 그릇에 비해 더 비효율적이겠죠. 프로그램언어에서도 마찬가지 입니다. 변수도 필요한 공간만 있으면 되지 구지 큰 공간이 필요 없습니다. 자원 낭비일 뿐이죠. 프로그램을 코딩할때도 보다 효율적으로 자원을 관리하는게 무척 중요합니다.

그래서 아래의 char, int, long, float, double, boolean 등 의 사용되는 용도에 따라서 그 그릇의 크기를 지정해주게 자원을 보다 효율적으로 사용이 가능해지겠죠.


제 블로그에 있던 소스인데 그대로 가져왔네요.

void setup() {
  Serial.begin(9600); 
  
  Serial.print("boolean : ");  
  Serial.println(sizeof(boolean));
  Serial.print("byte : ");  
  Serial.println(sizeof(byte));
  Serial.print("char : ");
  Serial.println(sizeof(char));
  Serial.print("short : ");
  Serial.println(sizeof(short));  
  Serial.print("int : ");
  Serial.println(sizeof(int));
  Serial.print("long : ");
  Serial.println(sizeof(long)); 
  Serial.print("float : ");
  Serial.println(sizeof(float));
  Serial.print("double : ");
  Serial.println(sizeof(double));
  Serial.print("uint8_t : ");
  Serial.println(sizeof(uint8_t));    
  Serial.print("uint16_t : ");
  Serial.println(sizeof(uint16_t));
  Serial.print("uint32_t : ");
  Serial.println(sizeof(uint32_t));  
}
void loop() {
}

가상시뮬레이터에서 체크값이네요.

보시면 생소한 uint8_t, uint16_t, unint32_t라는 자료형이 있는데 직접적으로 정의해놓은 변수인데 8bit, 16bit, 32bit 크기의 자료형이라고 생각하시면 됩니다. 아두이노 라이브러리 소스를 분석해보시면 이런 표현의 변수를 많이 볼 수 있을거에요.

5. static와 extern 변수


static 선언변수는 한번 선언되면 프로그램이 종료될때까지는 소멸하지 않는 변수입니다.


위 그림에서 보는것 처럼 'static'으로 선언된 A 값은 처음에 '10'이 출력되고 다음 loop()함수때 '20'이 출력되고 이런식으로 '10'씩 증가하고 B는 처음에 '10이 출력되고 다음 loop()함수때도 '10을 출력되고 이런식으로 계속 '10'만 출력합니다. 그 말은 B변수는 loop()함수가 끝나면 B가 B+10한 결과값 20은 자동 소멸해버린다는 의미 입니다. 하지만 'static'으로 선언된 A변수는 A+10을 해서 20이라는 값이 저장되고 loop()함수가 끝나도 소멸되지 않고 다음 loop()함수때 20값을 유지할 수 있는 변수라는 것을 의미합니다. 'static' 선언변수를 아시겠지요.

그럼 다음으로 'extern'으로 선언된 변수가 또 있습니다. 이 경우 현재 파일뿐 아니라 다른 파일에서도 접근이 가능한 변수입니다.

아두이노에서는 시간과 관련해서 이 변수선언하여 컨트롤 하는 경우가 종종 있습니다.

extern unsigned long timer0_millis;

이렇게 선언되었을 경우에 코딩 로직이 다음과 같다고 생각하시면,

val=millis();
if(val>10000) timer0_millis=0;

millis()함수는 현재 시간값을 가져옵니다. 타이머가 돌기 시작하면 계속 시간값을 커지겠죠. 그런데 10초가 지난 후 어떻게 될까요. 'extern'로 선언된 timer0_millis변수값을 '0'으로 초기화 하면 millis()의 현재시간은 '0'이 됩니다. 다시 초기화 된 상태에서 시작됩니다. 용어적으로 설명할려고 하니깐 좀 애매하고 해서 예제를 하나 들어서 설명을 했네요. 'extern'은 시간관련한 아두이노의 라이브러리파일 어딘가에 사용되는 변수인데 그 변수는 이런식으로 전역변수로 선언하게 되면 그 값을 개발자의 의도대로 변경이 가능해지게 됩니다. 우리가 건들수 없는 변수를 직접 접근할 수 있게 되는 것이죠. 'extern' 선언이 없으면 아무리 timer0_millisd를 0으로 초기화 하더라도 millis()함수안에 시간값은 초기화 되지 않습니다. timer0_millis은 현재 개발자가 코딩한 영역에서만 그 영향권에 있기 때문입니다. 하지만 'extern'로 선언하게 되면 라이브러리 maillis()안에 있는 timer0_millis보다 우선권을 갖게됩니다. 설명이 참 애매하게 됐는데요. 쉽게말해서, 라이브러리에 표현된 변수가 있고 그 변수를 개발자가 'extern'으로 선언하면 개발자가 코딩한 영역에서 해당 변수값을 변경할 수 있다고 생각하시면 됩니다.

그러면 언제 이것을 사용하느냐고 물으신다면 다음과 같은 경우에 사용합니다. 아두이노는 전원이 공급되면 타이머가 돌아가기 시작하는데 그 타이머는 unsigned loog형의 숫자만큼 증가했다가 나중에 초기화 됩니다. 시간으로 따지면 엄청나게 긴 시간인데 정확히 날짜는 기억 안나네요. 만약에 아두이노에서 시간과 관련한 정확한 컨트롤이 필요할대는 이 부분을 건들어서 제어해야겠죠. 왜냐면 아두이노를 무한으로 켜놓을 경우 일정 시기가 되면 시간이 리셋되니깐요.

마무리


오늘은 변수와 상수에 대해서 살펴보았네요. 설명이 좀 길었지만 알아두시면 아두이노를 다룰 때 유용합니다. 그리고 이해가 안되면 변수란 변하는 수이고 상수란 변하지 않는 수이다. 그리고 변수는 저장공간이다 이정도만 이해하시면 됩니다. 더이상 깊게 아실 필요는 없고요. 추가로 변수를 쓰면 좋은점 선언된 변수의 영역정도만 이해해 주세요. 'static'이나 'extern'은 잘 안쓰는 표현이기 때문에 이런게 있구나 정도로 넘어가시면 됩니다. 물론 'static'가 비슷한 효과로 아예 setup()함수와 loop()함수 밖에다 선언하면 되니깐 이 부분이 어려운 분들은 잠시 이해하는 부분을 미루셔도 됩니다.

그냥 변수와 상수라는 개념만 탑재하시면 됩니다.

댓글()

[아두이노] 릴레이 모듈+온도센서+DC모터 응용

IOT/아두이노|2019. 3. 28. 09:00

[아두이노] 릴레이 모듈+온도센서+DC모터 응용 



다른 주제로 포스팅을 끝냈는데 @ellenalee 님이 릴레이 모듈로 선풍기를 제어 해보셨다고 하셔서 이걸 한번 비슷하게 구현해보면 릴레이 모듈의 응용으로 괜찮을 것 같아서 가상시뮬레이터에서 느낌만 비슷하게 한번 구현해 보도록 하겠습니다.

1. 회로도 구성


  • 준비물 : relay DPDT 1개, DC Motor 1개, Temperature Sensor[TmP36] 1개, Power Supply, 아두이노우노
  • 내용 : 온도센서의 값에 의해서 모터가 회전할 수 있게 회로도를 구성해 보자.


대충 DC Motor가 선풍기라고 생각해 봅시다. 느낌은 그런데로 비슷해가 표현 했네요.

3. 코딩


  • 사용함수 : analogRead(), map()
  • 내용 : LED 깜박이는 소스를 이용해서 그 값을 제어값으로 해서 Light bulb 깜박이게 해보자.
  • 참고소스 : [아두이노] 온도센서(TMP36) 제어

복습

  • analogRead(아날로그핀) : 아날로그신호값을 읽음
  • map(입력값,입력최소값,입력최대값,출력최소값,출력최대값) : 입력값이 입력범위에 기준에 맞게 출력범위의 해당값을 출력.

변형함수(만든함수)

  • fmap(입력값,입력최소값,입력최대값,출력최소값,출력최대값) : map()함수와 동일하지만 자료형 타입을 float형으로만 변경했습니다. 계산값이 실수형 값이라서 map()함수를 사용하기가 애매해서 인자들을 전부 float형으로 변경했네요.

[ 온도센서 측정 소스 ]

[아두이노] 릴레이 모듈+온도센서+DC모터 응용
void loop(){
  float V =fmap(analogRead(A0),0,1023,0,5); //map함수 원리를 이용한 다이렉트 Voltage계산
   //공식
  //float V = analogRead(A0)*5.0/1023;
 
  float C = (V-0.5)*100;  //섭씨 C = (F-32)*1.8;
  float F = C*9.0/5.0+32; //화씨 F = C*1.8+32;
 
}

float fmap(long x, long in_min, long in_max, float out_min, float out_max)
{
  return (x - in_min) * (out_max - out_min) /(float) (in_max - in_min) + out_min;
}

여기서 대충 온도 C변수 값에 따라서 릴레이모듈를 제어하면 되겠죠

[아두이노] 릴레이 모듈+온도센서+DC모터 응용
   if(C>35){    //35도 이상이면 참
     digitalWrite(2, HIGH); //스위치 켜라
  }
  else{
     digitalWrite(2, LOW);  //스위치 꺼라
  }  

끝! 간단하게 느낌만 살렸습니다.

전체적인 코드를 살펴보면,

[아두이노] 릴레이 모듈+온도센서+DC모터 응용
void setup()
{
  Serial.begin(9600);
  pinMode(7, INPUT_PULLUP);
  pinMode(2, OUTPUT);
}
void loop()
{
  
  float V =fmap(analogRead(A0),0,1023,0,5); //map함수 원리를 이용한 다이렉트 Voltage계산
  float C = (V-0.5)*100;  //섭씨 C = (F-32)*1.8;
  
  if(C>35){    
     digitalWrite(2, HIGH); 
  }
  else{
     digitalWrite(2, LOW);   
  }   
}

float fmap(long x, long in_min, long in_max, float out_min, float out_max)
{
  return (x - in_min) * (out_max - out_min) /(float) (in_max - in_min) + out_min;
}

4. 결과


[ 35도 이하일때 ] : 그림에서는 25도 위치



[ 35도 이상일때 ] : 그림에서는 57도 위치


동영상을 찍어야 했지만 그냥 이미지로 간단히 차이점을 화살표로 표시 해 놨습니다. 25도일때는 0.00A로 전류가 흐르지 않는 상태이고 57도 위치에서는 79.9mA로 전류가 흐르는 상태입니다. 그 차이점으로 확인해주시기 바랍니다.

코딩에서는 35도 이상이면 모터가 회전되게 되어 있습니다. 실제 시뮬레이터로 돌아가는 장면을 보고 싶으면 공개되 회로도에서 바로 시뮬레이터를 실행 시켜서 그 결과를 확인 할 수 있습니다.


링크된 곳으로 가면 위 그림과 같은 창이 뜹니다. 거기서 1번 눌러주셔서 2번의 시뮬레이터 실행버턴의 창이 뜹니다. 그리고 2번을 누르시면 실행을 시킬 수 있습니다. 한번 온도조절을 해보시고 결과를 확인해 보세요.

마무리


상상이 어렵지만 표현은 그렇게 어렵지 않습니다. 매번 상상의 나래를 펼치라는 멘트를 날리는 이유가 바로 그 이유입니다. 온도에 따라서 선풍기를 자동을 켜볼까라는 상상력이 뭔가를 만들어 냅니다.

여러분들도 어떤 원리를 배우게 되면 그 원리에서만 멈추지 말고 상상력을 더해서 새로운 뭔가를 창조할 수 있는 능력을 키우시면 엄청난 결과를 얻을 수 있을 거에요.

언제나 그럴듯이 상상의 나래를 펼쳐 보세요.

댓글()

[아두이노] 릴레이 모듈 제어

IOT/아두이노|2019. 3. 27. 09:00

[아두이노] 릴레이 모듈 제어 



오늘은 릴레이 모듈을 제어하는 방법을 살펴보도록 하겠습니다. 아두이노는 기본적으로 5V 전류에 동작하는 싱글보드입니다. 그래서 5V이상의 전류를 다룰 수가 기본적으로 없습니다. 그걸 대신해주는 역할이 릴레이 모듈입니다. 높은 전압에 동작하는 전자기기를 아두이노가 릴레이 모듈을 사용하여 쉽게 제어가 가능합니다. 예로 선풍기, 에어컨, 남방기기, 현광등 외 높은 전압을 사용하는 전자기기를 제어할 수 있습니다. 릴레이 모듈은 동작은 기본적으로 스위치 역활을 연상하시면 됩니다. 이제 릴레이 모듈에 대해서 살펴보도록 하죠.

1. 릴레이 모듈


제가 가지고 있는 모델입니다. 아래 그림에서 주의할 점은 부품마다 아두이노 연결하는 핀 위치가 다릅니다. 이 부품은 가운데가 Vcc(5V) 이지만 어떤 부품은 가운데가 제어선인 경우도 있고요. 어떤 부품은 가운데가 Gnd핀인 경우도 있습니다. 실전에서는 사용하는 모듈 부품 정보를 토대로 주의해서 선을 연결하시면 됩니다. 그리고 제어 가능한 전류 표시는 위면에 잘 나와 있으니깐 본인이 제어하고자 하는 전류의 크기를 잘 체크하시고 사용하시면 되겠습니다.


이건 제가 사용하는 부품의 기준으로 설명을 드리겠습니다. 이부분은 다른 부품일때 위와 같이 연결하시면 작동하지 않습니다. 그냥 원리를 이해하는 수준으로 읽어주시기 바랍니다. 왼쪽은 실제 높은 전압을 사용하는 전류를 가운데 공용선에 연결해서 그 전류가 Output1, Output2로 흘러 갈지를 릴레이 모듈에서 제어를 하게 됩니다. 오른쪽은 아두이노와 연결하는데 Vcc(5V), Gnd 핀은 그대로 연결하시면 되고요. 제어선은 아두이노의 디지털핀(출력값)을 통해 릴레이 모듈을 제어하는 스위치 연활을 하게 됩니다. HIGH(5V) or LOW(0V) 값에 따라서 Output1과 Output2로 왔다 갔다 연결이 됩니다. 가령, Output1에 연결되어 전류가 공급되고 있다면 제어선값이 반대값이 입력되면 Output1의 연결이 끊어지고 Output2에 연결됩니다.


그림을 못 그리지만 대충 그림판에서 느낌만 그려봤네요. 가령 초기상태로 0V일 때 왼쪽 그림의 모습이라고 했을때 5V가 제어선으로 입력되면 선이 위쪽으로 붙게 됩니다. 즉, 0V일 때 아래선에 붙게 되고 5V일때 윗 선에 붙는다고 생각하시면 됩니다.

대충 어떤식으로 모듈이 움직이는지 아시겠지요.

[ 가상시뮬레이터에서 사용할 Relay DPDT 모듈 ]


처음 이것을 실험할 때 가장 애 먹은 주제입니다. 2쌍으로 위쪽과 아래쪽으로 선 연결이 나뉘는데요. 선 연결은 윗면의 그림 모양으로 되어 있는 데로 선을 연결하시면 됩니다.


대충 A,B가 한쌍이고, C,D가 한쌍입니다. 표기가 안된 쪽은 아두이노에 연결하시면 됩니다. 하나는 제어선 하나는 Gnd선입니다. 회로도 구성을 보시면 아마 쉽게 이해가 가실꺼에요.

2. 회로도 구성


  • 준비물 : relay DPDT 1개, Light bulb 2개, Power Supply, 아두이노우노
  • 내용 : Light bulb를 교대로 깜박이게 할 수 있도록 회로도를 구성해보자.


참고로 위면은 보시고 선은 연결하세요. 첨에 반대로 연결했다가 삽질을 했네요. 글자가 뒤집어지게 회전 시키고 선을 연결하세요. 정면은 오른쪽이 아두이노우노랑 연결해야 하거든요. Relay DPDT 모듈에 선 핀을 어떻게 연결해야할지 아시겠지요. 어느선이 전원선이고 어느선이 출력선인지요. 가상시뮬레이터에서 딱히 연결할 부품이 없어서 백열등 같은 모양의 전등을 연결해봤네요. Power Supply에서 10V의 전류로 불이 들어온다고 가정한 회로도 입니다.

3. 코딩


  • 사용함수 : pinMode(), digitalWrite(), delay()
  • 내용 : LED 깜박이는 소스를 이용해서 그 값을 제어값으로 해서 Light bulb 깜박이게 해보자.
  • 참고소스 : [아두이노] LED 제어

복습

  • pinMode(사용핀, 사용모드) : 사용핀을 INPUT/OUTPUT/INPUT_PULLUP 모드중 뭘로 사용할지를 정함.
  • digitalWrite(사용핀, 출력형태) : 사용핀을 HIGH(5V) or LOW(0V) 중 출력 형태를 정함.
  • delay(1000) : 1초(1000)를 대기한다.

따로 수정할 부분은 없습니다. 그냥 기본적인 LED 깜박이기 소스를 그대로 적용할 예정입니다.

[LED 깜박이기 기본 소스]

void setup() {
pinMode(13, OUTPUT);
}

void loop() {
digitalWrite(13, HIGH);
delay(1000);
digitalWrite(13, LOW);
delay(1000); 
}

13번 핀값이 HIGH와 LOW를 1초 단위로 출력되잖아요. HIGH or LOW일 때 어떤 결과가 나오는지 보도록 하죠.

4. 결과


[ digitalWrite(13, HIGH); ]



[ digitalWrite(13, LOW); ]


[실험 촬영 영상]
위에 소개한 릴레이모듈에서 초기값(0V)이 Output2로 연결되었네요. 제어선(13번) 핀에서 HIGH(5V) 상태가 되면 Output1으로 선이 연결됩니다. 제어선(5번) 핀에 HIGH or LOW의 상태에 따라서 Output1과 Output2로 교차해서 연결되어 두개의 LED가 깜박이게 되는 효과를 보여주네요.


마무리


릴레이 모듈이 일상생활에서 꽤 유용한 부품으로 활용 분야가 넓습니다. 예를 들면은 온도 센서가 있습니다. 온도에 따라 낭방기기를 On/Off 시킬 수 있겠죠. 또는 wifi 모듈과 연동한다면 집 밖에서도 인터넷을 통해서 집안 전자기기들을 작동시킬 수도 있겠죠. 전류를 컨트롤 할 수 있다는 것은 엄청난 일을 할 수 있습니다. 단순히 On/Off라고만 생각하시면 안되고 그냥 리모콘 정도의 사고에 머물면 안되고 다른 부품들과 연동했을때 원격으로 제어 하거나 자동 제어를 할 수 있는 시스템을 구축할 수 있기 때문에 그 활용도는 무궁무진 합니다.

한번 상상의 나래를 펼쳐 보세요.


댓글()

[아두이노] 2진수 계산법 암기

IOT/아두이노|2019. 3. 26. 09:00

[아두이노] 2진수 계산법 암기 



오늘은 간단히 진법 변환에 대해 살펴보도록 하겠습니다.

1. 10진수


123(10진수) => 우리가 사용하는 정수입니다.
123의 10진수 :


2. 2진수 변환


123을 2진수로 변환 : 123을 2로 나눈 나머지를 나열하는 숫자인데 화살표 방향으로 순서대로 나열하시면 됩니다. 이 과정은 2보다 작은 숫자가 나올때까지 나누게 됩니다.


123(10진수)가 1111011(2진수)로 표현됩니다. 마지막 2보다 작은 숫자가 나올때 그 몫도 포함해서 역순으로 순차적으로 나열하시면 됩니다. 처음 나눈 나머지는 오른쪽 첫번째 입니다.

1111011(2) 값을 다시 10진수 변환


1111011(2)를 10진수로 변환을 통해 2진수 값이 정확히 표현 되었는지 확인 과정이 필요하겠죠.


2진수 숫자를 다시 10진수화를 통해 정상적으로 계산된 것을 확인 하셨죠. 여기서 숫자가 규칙적으로 증가하는 것을 느끼셨을 거에요.


2의 몇승으로 이렇게 순차적으로 증가합니다. 그래서 2진수 계산 할 때 해당 위치에 값이 1이면 2의 자릿수승값으로 계산하시면 쉽게 암산이 가능합니다.

가령, 1101(2) 수가 있으면 오른쪽 자리부터 순서대로 해당 위치값이 1인 자리에 2의 자릿수 승값을 다 더해주면 됩니다. 이렇게 '1+4+8=13' 표현 하시면 됩니다.

10진수와 2진수 변환을 이제 쉽게 할 수 있겠죠.

2진수 소수점 변환


예) 0.125(10) 10진수가 있을때 2진수로 변환은 계속 곱하기 2을 해주면 됩니다. 그리고 소수점 이상 반올림 한 숫자는 버리기고 다시 2를 곱해줍니다. '1.0'이 될때까지 계속 소숫점 수에다 2를 곱해줍니다. 그리고 반올림한 숫자가 '0' or '1'이 나오는데 순서대로 나열하시면 됩니다.


위 그림처럼 0.25에서 '0'을 0.5에서 '0'을 1.0에서 '1'을 취합니다. 정수일때는 역순으로 나열하지만 소수점일때는 정순으로 나열합니다. 그래서 '001' 이 나오게 됩니다.

0.001(2) 값을 다시 10진수로 변환



위 표처럼 계산이 이루어지고 원래의 십진수 '0.125' 가 나오는 것을 확인이 되시죠. 여기서도 숫자의 규칙이 있습니다. 아래의 표처럼 반대로 2의 -몇승으로 증가합니다.


숫자가 2의 –4승까지는 잘 외워지는데 그 담부터 햇갈리실꺼에요. 0.5에서 –1승씩 증가할 때마다 0.5를 곱해준다고 생각하시면 됩니다.

종합)

123.125라는 10진수가 있다면 이 수를 2진수로 변환 하시오?

어떻게 할까요. 소수점을 기준으로 둘을 나눠서 계산하시면 됩니다.


3. 왜! 2진수를 배워야 하느냐!


아두이노를 다루다 보면 아누이노우노에 저장할 수 있는 공간이 그리 많지 않습니다. 단순한 표현은 그냥 해도 되지만 좀 복잡한 로직을 설계하다보면 자원공간이 부족합니다. 그렇게 때문에 최대한 효율적으로 코딩이 필요할 때 사용하기 아주 좋습니다.

예를 들어, LED 8개를 제어 한다고 생각해보죠. '1'은 HIGH, '0'은 LOW라고 할때

byte LED[8] = {1,1,0,0,1,0,0,1};

이렇게 표현하면 1byte 저장공간이 총 8byte의 공간이 필요합니다. 하지만 이걸 2진수로 이용한다면 어떻게 될까요.

byte LED = 0B11001001;

이렇게 1byte로 표현이 가능해집니다. 왜! 2진수를 배워야 하는지 이해를 하셨죠.

그리고, 패턴을 길게 만들거나 코딩이 표현하기 복잡할 때 2진수로 오히려 편하게 표현이 되는 경우가 있습니다. 그럴때 활용하기 위해서 2진수를 배워두셔야 합니다. 그리고 2진수로 코딩하다보면 좀 타이핑 글자수가 늘어날 수 있는데 그게 불편할 때는 10진수나 16진수로 변환해서 표현해도 됩니다. 다 같은 값을 가리키고 저장하기 때문입니다. 아두이노가 알아서 2진수값으로 해독하니깐요. 우리가 시각적인 눈으로 10진수 형태로 보는 것일 뿐 아두이노는 2진수 형태로 전류의 on/off로 해독하기 때문에 신경 쓸 필요가 없습니다.

4. 8진수와 16진수


계산법은 동일합니다. 8의 몇승이든 16의 몇승이든 자릿수에 맞춰서 계산하면 됩니다. 여기서 16진수만 알아 두시면 좋습니다.

우선, 알아두어야 할 것은 BCD(8421) 코드표입니다. 숙지하셔야 합니다.


BCD 8421 코드표는 "0001 + 0001 = 0010" 이렇게 숫자가 덧셈으로 반올림 된다고 생각하시면 돼요.

그리고 이것을 어떻게 다 외우냐고 하실 수 있습니다. 컴퓨터관련 학과에서 처음 공부할때 암기하는 표입니다. 그냥 외우시면 안외워집니다. 간단히 자릿수 2진수 "0001, 0010, 0100, 1000" 이렇게 해당된 자리만 암기하시면 됩니다. {1, 2, 4, 8} 어디서 많이 본 숫자의 규칙이죠. 2진수의 2의 승의 차릿수입니다. 가령 8은 '1000' 앞 뒤자리는 0111과 1001이겠죠 7과 9가 됩니다. 이런식으로 4개의 2진수 규칙을 숙지하고 전후자리의 값으로 더하고 빼주면 쉽게 숫자로 표현이 가능합니다.
그리고, '1010' 부터는 'A' 알파벳 시작이라고 생각하고 몇자리 안되니깐 더해주시면 됩니다.

8진수, 16진수 변환법


00110001(2) 이진수가 있습니다. 8진수, 16진수로 변환할려면 아래 표처럼 묶어서 계산하시면 아주 쉽게 변환할 수 있습니다. 2진수로 표현하면 어느 진수로든 쉽게 변환이 가능합니다.


여기서 참고하실것은,


이렇게 진수값 앞에 기호를 표기해야지 컴퓨터가 2, 8, 16진수구나 하고 해독합니다.

결과


'0B00110001(2)', '061(8)', '0x31(16)'로 표현이 결과가 49라는 10진수 값이 출력되는 것을아래 그림에서 컴파일 된 그림으로 확인하시거나 ideone 사이트 가셔서 직접 그 결과가 나오는지 코딩해 보세요. 다른 값들로 표현해보시는 것도 좋겠죠.


마무리


오늘 포스팅은 수학시간이 된 것 같네요. 컴퓨터 전공하면 젤 먼저 배우게 되는 기초 계산법입니다. 그리고, 이 기초 계산법은 가장 많이 사용하는 계산법이기도 하고요.

아두이노에서 10진수를 2진수로 표현하면 bit로 쪼개 계산에 활용하기 좋습니다. bitRead(x,n)함수로 원하는 위치의 비트 추출을 쉽게 할 수 있기 때문이지요. 그래서 활용 범위는 무척 넓습니다. 이번에 진수 계산법을 제대로 배워 두시면 아두이노에서 꽤 유용하게 써먹을 수 있을거에요.

지난시간에 배운 3x3x3 CUBE LED에서 편하게 패턴을 만들게 활용을 했죠. 7 Segment LED에서도 잘 써먹는 방식이고요. 다른곳에서도 비트값으로 제어하는 부품에서도 잘 사용하니깐 잘 알아두시기 바랍니다.

댓글()

[아두이노] 3x3x3 CUBE LED 패턴 놀이

IOT/아두이노|2019. 3. 25. 09:00

[아두이노] 3x3x3 CUBE LED 패턴 놀이 



3x3x3 CUBE LED로 한주를 그냥 보내 버렸네요. LED 제어에 가장 재밌는 부분이라서 설명해야 할 것이 산더미 인데 더이상 했다가는 너무 한 주제로 무리한 것 같아서 다음에 비슷한 주제 나오면 또 신나게 달려볼까 합니다. 주말이라서 오늘은 편하게 휴식하는 맘으로 지난시간에 완성한 함수를 이용해서 패턴만 만들어서 재밌게 표현하고 놀았습니다. 위에 링크된 공개된 회로도를 여러분들의 계정에 복사하셔서 패턴만 워드나 기타 문서 편집기에서 표로 만들어서 표현하고 싶은 패턴을 색칠하시고 패턴을 완성하시면 그걸 시뮬레이터로 테스트 해보세요. 그냥 오늘은 표로 책실하는 놀이나 하고 그 결과를 시뮬레이터로만 돌려 보면 휴식을 취합니다.

1. 3x3x3 CUBE LED 회로도 구성


  • 준비물 : Red, Blue, Green LED 각각 9개, 저항 220옴 3개, 아두이노우노
  • 내용 : 3x3x3 CUBE LED 형태를 표현하자.


완성 좀 더 그럴싸 하게 표현하려고 좀 노력했네요. 일자모양의 LED를 제어하니깐 느낌이 안 살고해서 큐브 모양을 한번 제대로 보여드리기 위해서 최대한 큐브모양에 가깝게 표현 했네요.

2. 패턴 만들기





위 표를 책칠한 위치의 자리를 잘 생각하셔서 2진수로 표현하고 그 것을 여기다 에 올리면 너무 길어질 것 같아서 10진수로 다시 표현했네요.

unsigned int layerPattern1[6] ={325,170,16,341,186,511}; //1층
unsigned int layerPattern2[6] ={7,56,448,73,146,292}; //2층
unsigned int layerPattern3[6] ={273,146,84,56,63,511}; //3층

세줄로 위 표를 색칠한 패턴을 완성했습니다.


3. 코드 수정


  • layerPattern1[],layerPattern2[],layerPattern2[] 이부분은 패턴이 저장된 배열변수로 베이스 소스에서 이부분만 수정

  • int m_pattern = 6; : 패턴수가 6개라서 6으로 수정

  • int m_delay[9] ={30,30,30,30,30,30}; //유지시간은 패턴 6개이고 30이라는 시간만큼 유지

끝! 지난시간의 소스에서 이부분만 수정하면 나머지 그대로 사용하시면 됩니다. 즉, 3x3x3 CUBE LED은 위 세개만 변경하시면 원하는 패턴을 다 만들 수 있습니다.


[완성 소스]

int layer[3] = {A0,A1,A2};
int room[9] = {2,3,4,5,6,7,8,9,10};
int m_layer = 3;  // 층 수
int m_room = 9; // 호실 수


//패턴 정수로 표현
unsigned int layerPattern1[6] ={325,170,16,341,186,511}; //1층
unsigned int layerPattern2[6] ={7,56,448,73,146,292}; //2층
unsigned int layerPattern3[6] ={273,146,84,56,63,511}; //3층

int m_pattern = 6; //패턴수
int m_delay[9] ={30,30,30,30,30,30}; //패턴유지시간

void setup()
{  
  for(int i=0;i<m_layer;i++){
    pinMode(layer[i], OUTPUT); // 층 선언
    digitalWrite(layer[i], HIGH);  // 각층 닫기
  }
  
  for(int i=0;i<m_room;i++){
    pinMode(room[i], OUTPUT); //호실 선언
  }
}
void loop()
{
  for(int i=0;i<m_pattern;i++){  //순차적으로 패턴수만큼 동작
    for(int j=0;j<m_delay[i];j++){  //m_delay 시간만큼 해당 패턴을 유지한다.
      LEDSetting(layer[0], layerPattern1[i]);  //1층 i번째 패턴        
      LEDSetting(layer[1], layerPattern2[i]);  //2층 i번째 패턴        
      LEDSetting(layer[2], layerPattern3[i]);  //3층 i번째 패턴        
    }  
  }   
}    
void LEDSetting(int layer, unsigned int state){
  digitalWrite(layer, LOW); //층 개방
  for(int i=0;i<9;i++){    
    digitalWrite(room[i],bitRead(state, i)); //호실 개방     
  }
  delay(5);
  for(int i=0;i<9;i++){     
    digitalWrite(room[i], LOW);   //호실 닫힘   
  }
  digitalWrite(layer, HIGH); //층 닫힘    
}

4. 결과


[ 가상시뮬레이터 결과 ]
가상시뮬레이터로 돌리니깐 느낌이 아직도 잘 안사네요. 실제 제작한분 있으면 거기에 이 소스를 대입하면 좀 화려할 듯 싶네요.



마무리


주말이라서 새로운 주제로 나가기 좀 그래서 휴식하는 맘으로 지난시간에 완성한 소스에서 재밌는 패턴을 만들어서 시뮬레이션하는걸로 끝냈습니다.

사실 주말에 Steem API가 땡겨서 맨땅에 해당 하면서 javascript로 데이터를 불러오는 것 까지는 성공했는데 함수들을 정확히 다 이해를 못해서 구글 검색을 통해 예제만 신나게 찾고 있네요. 함수 래퍼런스가 너무 부실해서 C언어 지식으로 대충 때려 맞추고 있네요. 라즈베리파이에 파이썬 3.5가 깔려서 Steem 라이브러리를 설치하고 정보 불러오는 것 까지도 성공했는데 파이썬으로 접근하는 함수나 명령문들이 아직은 자료가 부족해서 기본정보만 접근하는 것만 성공하고 때려치우고 javascript로 공부하느라 아두이노를 잠시 주말은 외도하고 간단히 패턴 놀이로 포스팅 합니다.


댓글()

[아두이노] 3x3x3 CUBE LED 패턴 코딩 완성본

IOT/아두이노|2019. 3. 24. 09:00

[아두이노] 3x3x3 CUBE LED 패턴 코딩 완성본



지난 시간의 포스팅으로 끝낼려고 했는데 왠지 이렇게 마무리 하면 좀 찜찜한 기분이 들어서 좀 더 완벽한 코딩을 만들어 놓고 마무리 짓는게 더 나을 것 같아서 추가로 포스팅을 합니다. 3x3x3 CUBE LED를 마무리 하려다가 문득 코딩이 순간 머리속에서 스쳐 지나가서 간단하게 완성본을 만들어 봤네요.


1. 3x3x3 CUBE LED 회로도 구성


  • 준비물 : Red, Blue, Green LED 각각 9개, 저항 220옴 3개, 아두이노우노, 뻥판
  • 내용 : 3x3x3 CUBE LED 형태를 표현하자.


2. 패턴 코딩 시행 착오


만든 함수

  • pattern(사용층1, 사용호수1, 유지시간)
  • pattern(사용층1, 사용층2, 사용호수1, 사용호수2, 유지시간)
  • pattern(사용층1, 사용층2, 사용층3, 사용호수1, 사용호수2, 사용호수3, 유지시간)
    이렇게 표현해서 원하는 사용층만 사용하도록 바꿨습니다.
void pattern(int layer1,int room1,int m_delay){
  for(int i=0;i<m_delay;i++){ //for문이 delay()함수 효과    
    LEDSetting(layer1,room1);   
  }  
}

void pattern(int layer1,int layer2,int room1, int room2, int m_delay){
  for(int i=0;i<m_delay;i++){ //for문이 delay()함수 효과    
    LEDSetting(layer1,room1);
    LEDSetting(layer2,room2); 
  }  
}

void pattern(int layer1,int layer2,int layer3,int room1,int room2, int room3,int m_delay){
  for(int i=0;i<m_delay;i++){ //for문이 delay()함수 효과    
    LEDSetting(layer1,room1);
    LEDSetting(layer2,room2);    
    LEDSetting(layer3,room3);    
  }  
}
  • LEDSetting(사용층, 사용호수) : 기본적으로 LED에 전원을 공급해서 전류가 0.01초 만큼 공급되도록 세팅했습니다.
void LEDSetting(int layer, int room){
  digitalWrite(layer, LOW); //층 개방
  digitalWrite(room, HIGH); //호실 개방 
  delay(10);
  digitalWrite(room, LOW);   //호실 닫힘
  digitalWrite(layer, HIGH); //층 닫힘  
}

위와 같은 함수를 만들었네요. 사용층 수에 따라서 인수가 달라질 것을 감안하여 오버로드 함수로 표현을 이용했습니다.

로직을 짜면,

void loop()
{
  //Red LED만 순차적 릴레이
  for(int i=0;i<9;i++){ 
    pattern(layer[0],room[i],10);    
  }     
  
  //RED, BLUE만 동시 서로 역순 순차적 릴레이
  for(int i=0;i<9;i++){ 
    pattern(layer[0],layer[1],room[i],room[8-i],20);    
  }  
  
  //RED, BLUE, GREEN LED 모두 순차적 릴레이 
  for(int i=0;i<9;i++){ 
    pattern(layer[0],layer[1],layer[2],room[8-i],room[i],room[i],5);    
  }  
}

[ 결과 ]



결과는 짧은 동영상에서 보는 것과 같이 만족스럽게 다양한 패턴으로 동작의 결과를 얻었습니다.

완성되었다 싶었는데 문제점이 발견 되더군요. 이 방식을 사용하면 동시에 제어를 하더라도 각층의 LED은 1개만 컨트롤 할 수 밖에 없다는게 문제점이네요. 여기서, 딜레이 시간을 짧게해서 연속으로 다른 호실에 불이 들어오게 하면 되는데 이렇게 되면 또, LEDSetting()함수안에 delay(10)이 같은 층이여도 반복되니깐 그 시간이 누적되면 동시에 불이 들어오는 착시현상이 사라지게 되는 문제에 봉착 됐네요. 그래서 다시 원점에서 코딩을 시작하게 되었네요.

2. 완성된 패턴 코딩 설명


배열변수에 패턴 저장

각층의 패턴을 아예 16bit로 만들었네요. '0B' 표시는 2진수라는 표현입니다. 시각적으로 불이 들어오는 위치를 '1'이고 불이 안들어오는 위치를 '0'으로 나눴네요. bit로 쪼개면 변수 크기을 최소화 하고 효율적인 코딩을 할 수 있어서 사용 하였습니다. 가령, '0'과 '1'을 개별적으로 저장 변수를 만들경우 패턴이 늘어날수록 변수저장공간이 부족한 아두이노에게는 좀 문제가 있겠죠. 그래서 bit로 쪼갠 정보를 많이 이용합니다. LED의 상태를 '0'과 '1'로만 표현이 가능하니 가장 이상적인 표현이라고 할 수 있습니다.

아래 그림만 보면 '1'이 나타내는 곳에 LED가 불이 들어오니깐요. 대충 어떤식으로 LED가 켜지는지 쉽게 알 수 가 있겠죠.

unsigned int layerPattern1[9] ={ //1층 패턴
  0B0000000000000001, 
  0B0000000000000010,
  0B0000000000000100,
  0B0000000000001000,
  0B0000000000010000,
  0B0000000000100000,
  0B0000000001000000,
  0B0000000010000000,
  0B0000000100000000
};
unsigned int layerPattern2[9] ={ //2층 패턴
  0B0000000100000000, 
  0B0000000010000000,
  0B0000000001000000,
  0B0000000000100000,
  0B0000000000010000,
  0B0000000000001000,
  0B0000000000000100,
  0B0000000000000010,
  0B0000000000000001
};
unsigned int layerPattern3[9] ={ //3층 패턴
  0B0000000100000001, 
  0B0000000010000010,
  0B0000000001000100,
  0B0000000000101000,
  0B0000000000010000,
  0B0000000000101000,
  0B0000000001000100,
  0B0000000010000010,
  0B0000000100000001
};

또는, 2진수 계산법을 아신다면 표현은 인간이 표현하기 쉬운 숫자로 표기가 가능합니다.

//1층 패턴
unsigned int layerPattern1[9] ={1,2,4,8,16,32,64,128,256};

//2층 패턴
unsigned int layerPattern2[9] ={256,128,64,32,16,8,4,2,1};

//3층 패턴
unsigned int layerPattern3[9] ={258,130,68,40,16,40,58,130,258};

2진수로 표현된 것을 10진수로 변환하면 우리가 사용하는 일반 숫자로 나타낼 수 있어요. 3x3x3 CUBE LED은 한층의 9개의 LED를 사용하기 때문에 비트 한자리씩 총 9개의 비트자리만 사용하면 됩니다. 그래서 표현 숫자는 0~511 숫자로 9개의 LED 패턴을 만들어 낼 수 있습니다. 중요한것은 어느 숫자가 어느 LED인지는 쉽게 알 수 없습니다. 10진수 정수를 2진수로 변환해야지 그 위치에 LED에 불이 들어오는 구나 생각되는 것이죠. 10진수 형태로 나타낸 패턴은 포털 검색에서 2진수 변환이라는 키워드를 치셔서 왜 저렇게 숫자로 나타내는지 찾아보세요. 진수변환이 주 목적이 아니니깐 위에 2진수 표현한 배열변수를 그대로 사용하겠습니다.

잘 이해가 안가는 분들을 위해서 아래 그림을 참고하시면 됩니다.


그림에서 아래 1부터 9까지의 숫자는 비트 자릿수 입니다. 그냥 1호실부터 9호실로 생각하시면 돼요. 참고로 역순으로 표현한 이유는 나중에 bitRead()함수를 이용해서 1비트씩 읽는데 bitRead(숫자,읽을위치)로 하는데 읽을 위치가 '0'이면 숫자의 첫번째자리의 비트를 읽어오기 때문입니다. "나는 정순으로 배치하고 '8'번째 위치부터 역순으로 읽으면 되잖아!" 하시는 분들은 그렇게 하셔도 됩니다. 이 표현은 제가 코딩하는 스타일의 편의 상 이렇게 한것뿐인점 참고해 주세요.

그런데 앞에 '0'의 갯수가 더 많은 이유가 뭐지하고 생각하시는 분들도 있을꺼에요. 1byte는 8bit로 구성되어 있고 2byte은 16bit가 됩니다. unsigned int 자료형을 2byte의 자료 공간을 갖기 때문에 표기는 전체 16bit를 다 표현해야겠죠. 그래서 9bit의 값만 사용하지만 앞에 사용하지않는 비트는 전부 '0'으로 채우게 됩니다. 쉽게 생각하시면 오른족 9bit만 3x3x3 CUBE LED에 이용된다고 생각하시면 됩니다.

그리고 패턴을 만들때 워드창에다가 표를 만들어서 위 그림처럼 색칠하기를 해보세요. 그리고 색이 칠해진 자리는 '1'로 표기하고 색이 안칠해진 자리는 '0'으로 표기하여 16bit를 표현하시면 됩니다. 어렵지 않겠죠.

1(10진수) => 0B0000000000000001(2진수)와 같습니다. 둘 다 똑같은 값인점 참고해주세요. 10진수는 인간이 이해하기 쉬운 숫자라면 2진수는 컴퓨터가 이해하기 쉬운 숫자라고 생각하시면 됩니다.

예) 표에서 녹색 1패턴은 어떻게 표현 할까요.

0B0000000100000001

이렇게 해서 패턴을 만드는 것은 어렵지 않겠죠


만든 함수

  • LEDSetting(사용층핀, 사용층패턴) : 사용층의 모든 호실의 패턴를 인수로 넘겨줘서 출력한다.

여기서 층 개방/닫힘은 위아래로 묶어준뒤에 그안에 for문으로 9개 LED의 상태를 개방하고 0.01초 동안 불이 들어오게 한뒤에 다시 for문을 통해 9개 LED를 닫습니다. LED에 불이 들어올때 착시효과 만들기 위한 LED 불이 최소한 켜져있도록 만든 기준 함수입니다. 쉽게 말해서 특정 위치에 LED가 최소한으로 전류가 공급될 시간을 주기 위한 함수라고 생각하시면 됩니다.

void LEDSetting(int layer, unsigned int state){
  digitalWrite(layer, LOW); //층 개방
  for(int i=0;i<9;i++){    
    digitalWrite(room[i],bitRead(state, i)); //패턴 호실 개방     
  }
  delay(10);
  for(int i=0;i<9;i++){     
    digitalWrite(room[i], LOW);   //모든 호실 닫힘    
  }
  digitalWrite(layer, HIGH); //층 닫힘  
  
}

아두이노 IDE에서 코딩할 때 기본적으로 라이브러리에서 제공되는 bitRead()함수를 1bit씩 추출을 쉽게 할 수 있어서 편하게 표현을 할 수 있었네요.

x은 읽을값이고 n은 읽을 위치입니다. 참고로 읽는 순서는 오른쪽에서 부터입니다. 그래서 1호실 개방하는데 '0B0000000000000001'로 표현했다면 bitRead(0B0000000000000001,0) 함수로 0번째(첫번째) 위치 1을 추출하게 됩니다.


로직을 수행하면,

int m_pattern = 9; //패턴수
int m_delay = 20; //유지시간

void loop()
{

  for(int i=0;i<m_pattern;i++){     
    for(int j=0;j<m_delay;j++){
      LEDSetting(layer[0], layerPattern1[i]);          
      LEDSetting(layer[1], layerPattern2[i]);          
      LEDSetting(layer[2], layerPattern3[i]);          
    }
  
  }
}

1층(layerPattern1[9]), 2층(layerPattern2[9]), 3층(layerPattern3[9])에 기록된 9개 패턴들을 간단히 출력하는 로직입니다.

3. 패턴 코딩 완성본


int layer[3] = {A0,A1,A2};
int room[9] = {10,9,8,7,6,5,4,3,2};
int m_layer = 3;  // 층 수
int m_room = 9; // 호실 수

unsigned int layerPattern1[9] ={ //1층 패턴
  0B0000000000000001, 
  0B0000000000000010,
  0B0000000000000100,
  0B0000000000001000,
  0B0000000000010000,
  0B0000000000100000,
  0B0000000001000000,
  0B0000000010000000,
  0B0000000100000000
};
unsigned int layerPattern2[9] ={ //2층 패턴
  0B0000000100000000, 
  0B0000000010000000,
  0B0000000001000000,
  0B0000000000100000,
  0B0000000000010000,
  0B0000000000001000,
  0B0000000000000100,
  0B0000000000000010,
  0B0000000000000001
};
unsigned int layerPattern3[9] ={ //3층 패턴
  0B0000000100000001, 
  0B0000000010000010,
  0B0000000001000100,
  0B0000000000101000,
  0B0000000000010000,
  0B0000000000101000,
  0B0000000001000100,
  0B0000000010000010,
  0B0000000100000001
};
int m_pattern = 9; //패턴수
int m_delay[9] ={20,20,20,20,20,20,20,20,20}; //패턴유지시간

void setup()
{  
  for(int i=0;i<m_layer;i++){
    pinMode(layer[i], OUTPUT); // 층 선언
    digitalWrite(layer[i], HIGH);  // 각층 닫기
  }
  
  for(int i=0;i<m_room;i++){
    pinMode(room[i], OUTPUT); //호실 선언
  }
}

void loop()
{

  for(int i=0;i<m_pattern;i++){  //순차적으로 패턴수만큼 동작
    for(int j=0;j<m_delay[i];j++){  //m_delay 시간만큼 해당 패턴을 유지한다.
      LEDSetting(layer[0], layerPattern1[i]);  //1층 i번째 패턴        
      LEDSetting(layer[1], layerPattern2[i]);  //2층 i번째 패턴        
      LEDSetting(layer[2], layerPattern3[i]);  //3층 i번째 패턴        
    }  
  }
}
    
void LEDSetting(int layer, unsigned int state){
  digitalWrite(layer, LOW); //층 개방
  for(int i=0;i<9;i++){    
    digitalWrite(room[i],bitRead(state, i)); //호실 개방     
  }
  delay(10);
  for(int i=0;i<9;i++){     
    digitalWrite(room[i], LOW);   //호실 닫힘   
  }
  digitalWrite(layer, HIGH); //층 닫힘    
}

4. 결과


[ 가상시뮬레이터 결과 ]



[실제 아두이노 결과 ] 3층은 선이 부족해서 3층 패턴이 맘에 들어서 2층으로 옮겨왔고 1층 패턴은 그대로 뒀네요.


실제 결과는 LEDSetting()함수내 최소한 불이 켜져있을 시간을 delay(10)으로 했는데 3층의 딜레이 시간이 총 0.03초 정도가 되더군요. 그외 명령문들 수행결과까지 하면 딜레이가 좀 길어저 깜박거림이 좀 심해져 눈의 피로도를 유발하더군요. 어쩔 수 없이 delay(10)을 delay(5~7)사이로 돌렸네요. 그렇게 되면 패턴이 돌아가는 시간이 빨라져서 유지시간 m_delay[9] 배열변수의 값들을 약간씩 늘여서 깜박이는 속도를 다시 보정 하였습니다.

마무리


어제 포스팅을 올리고 나서 뭔가가 너무 아쉬워서 다른 각도로 접근하여 완성본을 만들어 봤네요.
상단에 layerPattern1[], layerPattern2[], layerPattern3[] 배열변수만 나중에 원하는 패턴을 '0'과 '1'로만 만들어 내고 패턴수와 그 패턴을 유지하는 시간만 만들어 내면 나머지는 그대로 사용하시면 됩니다. 완성본이라고 하지만 아직은 즉흥적으로 코딩한 거라 좀 아쉬운 점이 많습니다.

좀 더 코딩부분을 다뤄서 패턴 클래스를 만들고 하면 체계적으로 제어가 되지 않을까 생각이 들었는데 더이상 가는건 아닌것 같아서 너무 깊게 들어가는 것보다 다른 주제로 넘어가는게 좋을 것 같아서 여기서 멈추겠습니다.

이제는 장시간에 걸쳐서 다룬 3x3x3 CUBE LED를 마무리하도록 하겠습니다.


댓글()

[아두이노] 3x3x3 CUBE LED 패턴 코딩 II

IOT/아두이노|2019. 3. 23. 09:00

[아두이노] 3x3x3 CUBE LED 패턴 코딩 II



지난 시간에 3x3x3 CUBE LED 패턴 포스팅에서 패턴을 만드는 것과 그걸 일정시간 유지하는 방법을 배웠습니다. 그리고 2가지 패턴을 교차로 LED를 깜박이게 하는 것까지 했었죠. 이제는 패턴을 좀 더 길게 만들고 중복되는 코딩 부분을 제거하여 코딩을 최소화하는 과정을 배워 보겠습니다.


1. 3x3x3 CUBE LED 회로도 구성


  • 준비물 : Red, Blue, Green LED 각각 9개, 저항 220옴 3개, 아두이노우노, 뻥판
  • 내용 : 3x3x3 CUBE LED 형태를 표현하자.


2. 패턴 코딩 과정


패턴 코딩 과정의 두번째 시간입니다. 지난시간의 코드에서 연장선상에서 이여가겠습니다.

[ 1층1호실2층2호실과 1층2호실2층1호실 교차 깜박이기]

int layer[3] = {A0,A1,A2};
int room[9] = {10,9,8,7,6,5,4,3,2};
int m_layer = 3;  // 층 수
int m_room = 9; // 호실 수

void setup()
{
  for(int i=0;i<m_layer;i++){
    pinMode(layer[i], OUTPUT);
    digitalWrite(layer[i], HIGH);    
  }
  
  for(int i=0;i<m_room;i++){
    pinMode(room[i], OUTPUT);
  }
}

void loop()
{
    //1층1호실과 2층2호실
     for(int i=0;i<30;i++){ //for문이 delay()함수 효과
         digitalWrite(layer[0], LOW);  // 1층 개방
         digitalWrite(room[0], HIGH);  // 1호실 개방
         delay(10); //0.01초 동안 불 켜기
         digitalWrite(room[0], LOW);  // 1호실 닫힘
         digitalWrite(layer[0], HIGH);  // 1층 닫힘
  
         digitalWrite(layer[1], LOW);  // 2층 개방
         digitalWrite(room[1], HIGH);  // 2호실 개방
         delay(10); //0.01초 동안 불 켜기
         digitalWrite(room[1], LOW);  // 2호실 닫힘
         digitalWrite(layer[1], HIGH);  // 2층 닫힘
    }
    
    //1층2호실과 2층1호실
    for(int i=0;i<30;i++){ //for문이 delay()함수 효과
        digitalWrite(layer[0], LOW);  // 1층 개방
        digitalWrite(room[1], HIGH);  // 2호실 개방
        delay(10); //0.01초 동안 불 켜기
        digitalWrite(room[1], LOW);  // 2호실 닫힘
        digitalWrite(layer[0], HIGH);  // 1층 닫힘
  
        digitalWrite(layer[1], LOW);  // 2층 개방
        digitalWrite(room[0], HIGH);  // 1호실 개방
        delay(10); //0.01초 동안 불 켜기
        digitalWrite(room[0], LOW);  // 1호실 닫힘
        digitalWrite(layer[1], HIGH);  // 2층 닫힘
    }
}

여기서, 어느게 중복되고 있나요. 바로 for문 이하의 안에 명령문들이 두번 중복되는 것을 보실거에요. 2가지여서 2번 중복되었는데 10가지라면 10번 for문을 코딩해야한다면 정말 노가다 코딩이 되겠죠.

[ 중복코드를 외부함수로 ]

이제는 그 중복되는 부분을 차라리 외부로 빼서 호출하여 명령을 수행하도록 하여 코딩하는 양을 줄이도록 하겠습니다.

void pattern(int layer1, int layer2, int room1, int room2){
  for(int i=0;i<30;i++){ //for문이 delay()함수 효과
    digitalWrite(layer1, LOW); 
    digitalWrite(room1, HIGH);  
    delay(10);
    digitalWrite(room1, LOW);  
    digitalWrite(layer1, HIGH); 
  
    digitalWrite(layer2, LOW); 
    digitalWrite(room2, HIGH);  
    delay(10);
    digitalWrite(room2, LOW);  
    digitalWrite(layer2, HIGH); 
  }  
}

새롭게 함수를 만들었습니다. 그렇게 이 로직은 사실 기존의 코딩한 로직의 연장 선상으로 표현한 것입니다. 사실은 이렇게 하면 안되고 내부 로직도 변경하고 명령문도 새롭게 만들어야 합니다. 혼동을 최소화하기 위해서 이전 코드를 그대로 인용하여 표현했다는 점을 감안하시고 보셨으면 해요.

그러면, 어떻게 loop()함수에서 코딩이 될까요.

void loop(){
    pattern(layer[0],layer[1],room[0],room[1]);  //1층1호실, 2층2호실
    pattern(layer[0],layer[1],room[1],room[0]);  //1층2호실, 2층1호실
}

  • pattern(1층,2층,1층호실,2층호실) : 2층과 2호실에 제어.

코딩이 딱 두줄로 바뀌었지요. 그리고 loop()함수만 봐도 대충 의미를 이해하실꺼에요.

좀 길게 스토리를 만들어 볼까요. 가령 1층은 1호실부터 9호실까지 순차적으로 깜박이고 2층은 9호실부터 1호실까지 순차적으로 깜박이게 명령을 내려 볼까요.

void loop(){
    pattern(layer[0],layer[1],room[0],room[8]);  
    pattern(layer[0],layer[1],room[1],room[7]);  
    pattern(layer[0],layer[1],room[2],room[6]);  
    pattern(layer[0],layer[1],room[3],room[5]);  
    pattern(layer[0],layer[1],room[4],room[4]);  
    pattern(layer[0],layer[1],room[5],room[3]);  
    pattern(layer[0],layer[1],room[6],room[2]);  
    pattern(layer[0],layer[1],room[7],room[1]);  
    pattern(layer[0],layer[1],room[8],room[0]);     
}

그런데, 또 문제가 생겼네요. 중복 제거 코딩을 했는데 또 중복이 발생했네요. 불필요한 코딩이 늘어났는데 다시 해결해 볼까요. 이번에도 순차적이니깐 for문으로 처리해 버리죠.

void loop(){
  for(int i=0;i<9;i++){
    pattern(layer[0],layer[1],room[i],room[8-i]);    
  }
}

간단히 loop()함수 내 3출 코딩으로 줄어 들었네요. 이제 더 나아가 반대로 역순으로 깜박이게 하고 싶은 충동이 안느껴지나요. 위 for문에서 room[i],room[8-i]만 서로 바꿔주면 됩니다. 그래서 아래와 같은 완성된 코드만 만들어집니다.

void loop(){
 
  //for문으로 한줄 명령으로 릴레이  
  for(int i=0;i<9;i++){
    pattern(layer[0],layer[1],room[i],room[8-i]);    
  }  
    
  //역순  
  for(int i=0;i<9;i++){
    pattern(layer[0],layer[1],room[8-i],room[i]);    
  }
}

만약에 이 명령문들을 일일히 수작업으로 명령을 다 코딩했다고 생각해보세요. pattern()함수를 릴레이 패턴 for문 당 9번 총 pattern()함수 내부의 코드를 18번 일일히 수정하면서 코딩해야 합니다. pattern()함수 내부 명령문들도 많은데 그 많은 명령문을 18번이나 중복해서 코딩해야 한다고 상상해보세요. 진정한 노가맨이 되겠죠.

프로그램 문법을 좀 배우면 표현력의 한계는 없고 남들보다 더 쉬운 코딩을 할 수 있습니다.

loop()함수안의 코드는 의미전달과 최소화만 해야합니다. 뭘 하는지만 전달해주고 세부적인 명령문들은 외부함수로 빼서 제어해야합니다. 그래야 나중에 코드를 수정하거나 다른사람들이 보기에도 편해집니다. 수작업으로 모든 명령문들을 일일히 loop() 함수에 다 집어 넣게 되면은 도대체 loop()함수 내에서 무슨 동작하는지 머리만 복잡해 집니다.

위 loop()함수 내 코드만 보더라도 pattern()함수를 for문으로 해서 순차적으로 뭘 표현했구나하고 쉽게 이해할 수 있습니다. 정확히 어떤 동작인지는 모르지만 pattern()함수라는 뭔지는 모르지만 9번 순차적으로 반복이 이루어졌고 그걸 또 역순으로 9번 순차적으로 진행했구나 정도는 쉽게 loop() 함수만 보면 알 수 있습니다. loop()함수의 동작은 pattern()함수는 아직은 뭔지 모르더라도 그걸 순차적으로 반복하고 또 역으로 반복했구나 쉽게 loop()함수의 동작을 이해 할 수 있습니다.

3. 소스


int layer[3] = {A0,A1,A2};
int room[9] = {10,9,8,7,6,5,4,3,2};
int m_layer = 3;  // 층 수
int m_room = 9; // 호실 수

void setup()
{
  for(int i=0;i<m_layer;i++){
    pinMode(layer[i], OUTPUT);
    digitalWrite(layer[i], HIGH);    
  }

  for(int i=0;i<m_room;i++){
    pinMode(room[i], OUTPUT);
  }
}

void loop(){

  //for문으로 한줄 명령으로 릴레이  
  for(int i=0;i<9;i++){
    pattern(layer[0],layer[1],room[i],room[8-i]);    
  }  

  //역순  
  for(int i=0;i<9;i++){
    pattern(layer[0],layer[1],room[8-i],room[i]);    
  }
}

void pattern(int layer1, int layer2, int room1, int room2){
  for(int i=0;i<30;i++){ //for문이 delay()함수 효과
    digitalWrite(layer1, LOW); 
    digitalWrite(room1, HIGH);  
    delay(10);
    digitalWrite(room1, LOW);  
    digitalWrite(layer1, HIGH); 
  
    digitalWrite(layer2, LOW); 
    digitalWrite(room2, HIGH);  
    delay(10);
    digitalWrite(room2, LOW);  
    digitalWrite(layer2, HIGH); 
  }  
}

4. 결과


어떻게 패턴 코딩을 진행했는지 녹화를 해 놓았으니깐 살펴보시고 마지막에 시뮬레이션 결과를 확인해 보세요. 실제 아두이노에서도 실행한 장면도 들어 있으니깐 보시고 결과물이 어떻게 나왔는지 확인해 주세요.

가상시뮬레이터에서 실험한 기록 일지

실제아두이노에서 실험한 영상

마무리


지난시간의 내용을 기반으로 오늘은 연장선상에서 이여갔습니다. 사실, 오늘 코딩한 것에도 문제점이 많습니다. 그것은 강제적으로 1층과 2층에 꼭 불이 하나는 들어와야 한다는 전재 조건이 있습니다. 그 부분을 수정하자면 pattern()함수 내부를 완전 수정해야하기 때문에 그냥 수정 안하고 그 문제점을 가지고 오늘 포스팅을 했네요. 오늘은 전달하고자 하는 바는 중복코드를 제거하는 방법입니다. 그리고 loop()함수의 명령문들은 뺄 수 있으면 외부 함수로 빼내고 하나의 묶음으로 묶어서 loop()내에서는 의미 전달코딩을 하면 보다 효율적인 코딩을 할 수 있다는 것을 보여 드리는게 목적입니다.

결론, loop()함수는 최소 동작의 의미만 표현하며 중복되는 부분은 외부함수로 빼서 표현의 최소화 입니다. 지난시간에는 서로다른 층의 다른 호실의 LED에 불이 들어오게 하는 방법과 패턴 LED가 일정시간 유지될 수 있도록 하는 방법을 배웠습니다. 이 두가지를 꼭 기억하셔서 다른 곳에서도 활용해 보세요.

마지막으로 상단에 가상 시뮬레이터에서 실험한 회로도가 공개 시켰네요. 혹시 회로도 만들기 귀찮은 분들을 위해서 패턴만 만들어보시라고 공개 했네요. 가셔서 계정이 있으면 복사하시면 본인 계정으로 회로도가 복사 됩니다. 거기서 편하게 편집하시면 됩니다.


댓글()

[아두이노] 3x3x3 CUBE LED 패턴 코딩 I

IOT/아두이노|2019. 3. 22. 09:00

[아두이노] 3x3x3 CUBE LED 패턴 코딩 I



지난 시간에 3x3x3 CUBE LED 코딩을 간소화 하는 방법을 살펴보았습니다. 오늘은 좀 더 깊게 들어가서 LED를 깜박이는 패턴을 만들어보는 시간을 갖도록 하겠습니다. 그냥 순차적으로 깜박이는 것은 for문 하나면 해결되지만 서로 다른 층과 서로 다른 호실에 동시에 불이 들어오게 하는 방법은 좀 다른 관점의 코딩이기 때문입니다. 그 이유는 동시에 서로 다른 층의 다른 호실에 불이 들어오게 할 수 없습니다. 왜냐면 각 호실은 같은 층의 연결되어 있고 각층은 동일 호실과 선이 연결되어 있기 때문입니다. 만약, 1층 1호실과 2층 2호실에 불이 들어오게 하려고 1층과 2층을 동시에 개방하면 1층 1,2호실과 2층 1,2호실에 LED가 켜지게 됩니다. 이 부분에 대해서 간단히 살펴보고 그걸 해결하기 위한 방법을 설명 드리겠습니다. 사실 힘들게 뻥판 노가다로 전선을 연결했는데 다시 원위치 시키기 아쉬워서 패턴이라는 내용으로 포스팅을 한편 더 작성하네요.


1층과 2층의 LED만 사용합니다. 전선부족으로 지난시간에 2,3층 3개의 LED에 선을 연결못했는데 3층선 일부를 2층선으로 옮겨와서 1,2층 선을 모두 연결한 상태에서 실험했네요.

1. 서로 다른 층과 호실에 동시에 불을 켤 수 없는가?


1층 1호실과 2층 2호실에 동시에 불이 들어오게 한다고 가정해 봅시다. 그러면 1층과 2층을 Gnd(-)로 개방해야하잖아요. 그런데 1층(Gnd)와 1호실(Vcc)로 해서 1층 1호실에 불이 들어옵니다. 하지만 2층 2호실의 경우도 동시에 2층(Gnd)와 2호실(Vcc)을 하게 된다면 아래와 같은 현상이 발생합니다.


1층 1,2호실과 2층 1,2호실에 불이 전부 들어오게 됩니다. 선이 층을 기준으로 모든 호실이 하나로 선(Gnd)이 연결되어 있고 호실을 기준으로 모든 층이 선(Vcc)로 연결되어 있기 때문입니다. 그래서 동시에 불이 들어오게는 불가능 합니다. 그럼 해결책이 없느냐고 물으신다면 바로 딜레이 시간을 줘서 1층과 2층을 짧은 시간에 개방하고 닫고 해서 1층1호실에 불이 들어오게 한뒤에 2층 2호실에 불이 들어오게 함으로써 착시현상이 일어나게 처리하면 해결 할 수 있습니다.


위 그림에서 A동작을 수행시 불이 충분히 켜질 시간(delay) 주고 난 뒤에 바로 B동작을 수행합니다. 참고로 A동작과 B동작의 딜레이 시간은 아주 짧아야 합니다. 착시현상으로 동시에 불이 들어오는 것처럼 느끼게 됩니다.


대충 위 그림처럼 착시로 동시에 불이 들어오게 보여야 합니다. 이제 어떻게 하는지 알아보도록 하겠습니다.

2. 3x3x3 CUBE LED 회로도 구성


  • 준비물 : Red, Blue, Green LED 각각 9개, 저항 220옴 3개, 아두이노우노, 뻥판
  • 내용 : 3x3x3 CUBE LED 형태를 표현하자.


힘들게 디자인 했으니 그대로 활용하겠습니다.

3. 패턴 코딩 과정


  • 사용함수 : pinMode(), digitalWrite(), delay()
  • 내용 : 간단히 2x2x2 CUBE LED를 순차적으로 깜박이기
  • 참고 : [아두이노] LED 제어

복습

  • pinMode(사용핀, 사용모드) : 사용핀을 INPUT/OUTPUT/INPUT_PULLUP 모드중 뭘로 사용할지를 정함.
  • digitalWrite(사용핀, 출력형태) : 사용핀을 HIGH(5V) or LOW(0V) 중 출력 형태를 정함.
  • delay(1000) : 1초(1000)를 대기한다.

3x3x3 CUBE LED 순차적 깜박이는 코드

int layer[3] = {A0,A1,A2};
int room[9] = {10,9,8,7,6,5,4,3,2};
int m_layer = 3;  // 층 수
int m_room = 9; // 호실 수

void setup()
{
  for(int i=0;i<m_layer;i++){
    pinMode(layer[i], OUTPUT);
    digitalWrite(layer[i], HIGH);    
  }
  
  for(int i=0;i<m_room;i++){
    pinMode(room[i], OUTPUT);
  }
}

void loop()
{
  
  for(int i=0;i<m_layer;i++){
    digitalWrite(layer[i], LOW);  
    for(int j=0;j<m_room;j++){
      digitalWrite(room[j], HIGH);  
      delay(100);
      digitalWrite(room[j], LOW);  
    }
    digitalWrite(layer[i], HIGH);  
  }
}

여기서, 다른 부분은 그냥두고 이부분만 수정하시면 됩니다.

  for(int i=0;i<m_layer;i++){
    digitalWrite(layer[i], LOW);  
    for(int j=0;j<m_room;j++){
      digitalWrite(room[j], HIGH);  
      delay(100);
      digitalWrite(room[j], LOW);  
    }
    digitalWrite(layer[i], HIGH);  
  }

이 명령은 순차적으로 깜박이는 로직입니다. 여기서 1층 1호실과 2층 2호실에 불이 동시에 들어오게 하려면 어떻게 해야 할까요. 1,2층 개방/닫힘 명령과 1,2호실 개방/닫힘 명령이 필요합니다.

  • 층 개방 : digitalWrite(해당층핀, LOW);
  • 층 닫힘 : digitalWrite(해당층핀, HIGH);
  • 호실 개방 : digitalWrite(호실핀, HIGH);
  • 호실 닫힘 : digitalWrite(호실핀, LOW);

[ 1층1호실과 2층2호실 불 들어오게 하기 ]

위 코드에서 layer[0]은 1층, layer[1]은 2층, layer[2]은 3층이고 room[0]은 1호실, room[1]은 2호실, room[2]은 3호실, room[3]은 4호실입니다. 배열변수로 그렇게 선언했기 때문에 이점을 생각하고 코딩을 해보도록 할까요.

먼저, 1층 1호실을 불이 들어오게 명령을 내려 볼까요.

digitalWrite(layer[0], LOW);  // 1층 개방
digitalWrite(room[0], HIGH);  // 1호실 개방
delay(10); //0.01초 동안 불 켜기
digitalWrite(room[0], LOW);  // 1호실 닫힘
digitalWrite(layer[0], HIGH);  // 1층 닫힘

다음 2층 2호실에 불이 들어오게 명령을 내려 볼까요.

digitalWrite(layer[1], LOW);  // 2층 개방
digitalWrite(room[1], HIGH);  // 2호실 개방
delay(10); //0.01초 동안 불 켜기
digitalWrite(room[1], LOW);  // 2호실 닫힘
digitalWrite(layer[1], HIGH);  // 2층 닫힘

결과는 다음과 같습니다.


[ 소스 ]

int layer[3] = {A0,A1,A2};
int room[9] = {10,9,8,7,6,5,4,3,2};
int m_layer = 3;  // 층 수
int m_room = 9; // 호실 수

void setup()
{
  for(int i=0;i<m_layer;i++){
    pinMode(layer[i], OUTPUT);
    digitalWrite(layer[i], HIGH);    
  }
  
  for(int i=0;i<m_room;i++){
    pinMode(room[i], OUTPUT);
  }
}

void loop()
{
  digitalWrite(layer[0], LOW);  // 1층 개방
  digitalWrite(room[1], HIGH);  // 1호실 개방
  delay(10); //0.01초 동안 불 켜기
  digitalWrite(room[1], LOW);  // 1호실 닫힘
  digitalWrite(layer[0], HIGH);  // 1층 닫힘
  
  digitalWrite(layer[1], LOW);  // 2층 개방
  digitalWrite(room[0], HIGH);  // 2호실 개방
  delay(10); //0.01초 동안 불 켜기
  digitalWrite(room[0], LOW);  // 2호실 닫힘
  digitalWrite(layer[1], HIGH);  // 2층 닫힘
}

[ 결과 ]



[ 1층2호실과 2층1호실 불 들어오게 하기 ]

위 코드를 반대로 표현하면 됩니다.

digitalWrite(layer[0], LOW);  // 1층 개방
digitalWrite(room[1], HIGH);  // 2호실 개방
delay(10); //0.01초 동안 불 켜기
digitalWrite(room[1], LOW);  // 2호실 닫힘
digitalWrite(layer[0], HIGH);  // 1층 닫힘

다음 2층 2호실에 불이 들어오게 명령을 내려 볼까요.

digitalWrite(layer[1], LOW);  // 2층 개방
digitalWrite(room[0], HIGH);  // 1호실 개방
delay(10); //0.01초 동안 불 켜기
digitalWrite(room[0], LOW);  // 1호실 닫힘
digitalWrite(layer[1], HIGH);  // 2층 닫힘

결과는 다음과 같습니다.


[ 소스 ]

... 생략

void loop()
{
  digitalWrite(layer[0], LOW);  // 1층 개방
  digitalWrite(room[1], HIGH);  // 2호실 개방
  delay(10); //0.01초 동안 불 켜기
  digitalWrite(room[1], LOW);  // 2호실 닫힘
  digitalWrite(layer[0], HIGH);  // 1층 닫힘
  
  digitalWrite(layer[1], LOW);  // 2층 개방
  digitalWrite(room[0], HIGH);  // 1호실 개방
  delay(10); //0.01초 동안 불 켜기
  digitalWrite(room[0], LOW);  // 1호실 닫힘
  digitalWrite(layer[1], HIGH);  // 2층 닫힘
}

[ 결과 ]



[ 1층1호실과 2층2호실의 불을 일정시간 단위로 깜박이기 ]

하나의 패턴은 그냥 loop()함수로 돌리면 그 패턴의 LED만 불이 들어옵니다. 둘 이상의 경우는 해당 패턴의 모양이 일정시간 유지해야 합니다. 하지만 연속해서 표현하면 아주 짧게 딜레이를 줬기 때문에 해당 패턴모양이 원하는 시간동안 유지하지 못하는 현상이 발생합니다. 또는 두가지 패턴이 하나의 패턴으로 겹치는 현상도 발생합니다. 그래서 하나의 패턴을 일정시간 유지시키기 위해서는 딜레이 함수와 같은 효과를 코딩으로 표현해야 합니다.

딜레이를 아주 짧게 줬기 때문에 짧게 패턴을 일정횟수만큼 반복하면 반복된 시간만큼이 해당 패턴의 모양을 유지하는 시간으로 만들어 낼 수 있습니다.

가령,

 for(int i=0;i<30;i++){ //for문이 delay()함수 효과
   digitalWrite(layer[0], LOW);  // 1층 개방
   digitalWrite(room[0], HIGH);  // 1호실 개방
   delay(10); //0.01초 동안 불 켜기
   digitalWrite(room[0], LOW);  // 1호실 닫힘
   digitalWrite(layer[0], HIGH);  // 1층 닫힘
  
   digitalWrite(layer[1], LOW);  // 2층 개방
   digitalWrite(room[1], HIGH);  // 2호실 개방
   delay(10); //0.01초 동안 불 켜기
   digitalWrite(room[1], LOW);  // 2호실 닫힘
   digitalWrite(layer[1], HIGH);  // 2층 닫힘
}

표현을 하면 1층1호실은 0.01초만큼 불이들어온 후에 2층2호실은 0.01초만큼 불이 들어오게 됩니다. 총 합산하면 0.02초만큼 1층1호실과 2층2호실의 시간이 소요됩니다. 그걸 30회 반복한다고 했죠. 보는 수치상으로 계산하면 0.6초여야 동안 같은 패턴을 유지하게 된다고 생각하시면 됩니다. 그런데 실제로 돌리시면 0.06초동안 유지되는게 아니라 명령어 수행시간도 합산하면 미세하지만 좀 늘어나겠죠. 신경 쓸 필요는 없는 부분이고요. 30번 반복이란 예를 든 것일뿐 다른 값으로 어느정도 시간이 유지되는지를 테스트 해보고 원하는 시간으로 보정을 해보세요. for문 안에 delay(10)도 그냥 제 아두이노로 실험했을때 대충 정한 값이라 이 값이 크면 착시효과가 낮으니깐 어느정도 다른 값들을 대입해보면서 원하는 딜레이시간으로 보정해 해보세요.

[ 1층1호실과 2층2호실 패턴과 1층2호실과 2층1호실 패턴을 교차 ]

위 for문에 반대패턴을 코딩한걸 삽입하면 되겠죠.

[ 소스 ]

... 생략

void loop()
{
    //1층1호실과 2층2호실
     for(int i=0;i<30;i++){ //for문이 delay()함수 효과
         digitalWrite(layer[0], LOW);  // 1층 개방
         digitalWrite(room[0], HIGH);  // 1호실 개방
         delay(10); //0.01초 동안 불 켜기
         digitalWrite(room[0], LOW);  // 1호실 닫힘
         digitalWrite(layer[0], HIGH);  // 1층 닫힘
  
         digitalWrite(layer[1], LOW);  // 2층 개방
         digitalWrite(room[1], HIGH);  // 2호실 개방
         delay(10); //0.01초 동안 불 켜기
         digitalWrite(room[1], LOW);  // 2호실 닫힘
         digitalWrite(layer[1], HIGH);  // 2층 닫힘
    }
    
    //1층2호실과 2층1호실
    for(int i=0;i<3[](http://)0;i++){ //for문이 delay()함수 효과
        digitalWrite(layer[0], LOW);  // 1층 개방
        digitalWrite(room[1], HIGH);  // 2호실 개방
        delay(10); //0.01초 동안 불 켜기
        digitalWrite(room[1], LOW);  // 2호실 닫힘
        digitalWrite(layer[0], HIGH);  // 1층 닫힘
  
        digitalWrite(layer[1], LOW);  // 2층 개방
        digitalWrite(room[0], HIGH);  // 1호실 개방
        delay(10); //0.01초 동안 불 켜기
        digitalWrite(room[0], LOW);  // 1호실 닫힘
        digitalWrite(layer[1], HIGH);  // 2층 닫힘
    }
}

[ 결과 ]

위 그림에서 보는것처럼 교차로 깜박이게 됩니다. 이런식으로 해서 패턴을 만들고 그 패턴을 일정시간동안 유지시킬 수 있게 되면은 다양한 패턴을 만들어 낼 수 있겠죠.

마무리


이번 포스팅은 원래 패턴 코딩을 한편으로 제작할려고 했던 내용입니다. 그런데 포스팅의 글을 쓰다가 보니 아직 제대로 코딩하는 법으로 들어가지 못했는데 분량이 장난 아니게 늘어났네요. 어쩔 수 없이 다음편으로 연장해야 할 듯 싶네요. 너무 길게 쓰면은 피로감이 밀려올 수 있으니 이번 포스팅은 패턴을 만들고 그 패턴을 일정시간 유지하는 것에서 마무리 하도록 하겠습니다.

다음편에서는 이렇게 패턴을 만들고 for문을 통해서 일정시간을 유지시키는 방법으로 매 패턴들을 표현한다면 코딩량이 엄청나겠죠. 단 두가지 패턴을 코딩했는데도 이정도인데 10개이상 되는 패턴을 만든다면 노가다 코딩이 되겠죠.

그래서 중복되는 부분을 제거하는 코딩을 다음 포스팅에 연재 하도록 하겠습니다. 오늘은 이렇게 패턴을 만드는 것과 일정시간 유지하는 걸 배웠으니 어떤 패턴을 만들지 머리속에서 구상해보세요. 사실 전체과정을 동영상으로 기록하고 실제 아두이노에서도 결과까지 다 준비 됐는데 그건 다음에 올리도록 하겠습니다.

댓글()

[아두이노] 3x3x3 CUBE LED 제어 III

IOT/아두이노|2019. 3. 21. 09:00

[아두이노] 3x3x3 CUBE LED 제어 III



오늘은 좀 더 프로그램 코딩에 관해서 이야기 하도록 하겠습니다. 그리고 제목을 3x3x3 CUBE LED를 다룬다고 해놓고 지난시간에 2x2x2 CUBE LED를 실험해서 좀 찜찜한 기분이 들어서 노가다를 감수하고 코딩이 간소화 되니깐 3x3x3 CUBE LED를 해도 되겠다 싶어서 회로도 디자인을 했네요. 우선 배치도를 구상하고 최대한 보기 편하게 만들려고 노력했네요. 전개는 우선 지난시간에 코딩한 2x2x2 CUBE LED 코딩을 기반으로 프로그래머 스타일의 문법을 가미하면서 간소화 시키는 것을 설명드리고 그다음 간소화한 코딩을 3x3x3 CUBE LED 회로도에 맞게 변경하여 실험하는 결과를 보여드리도록 하겠습니다.

1. 2x2x2 CUBE LED 코드 간소화


아래 코드만 봐도 머리가 아프죠. 이걸 프로그램 문법을 이용해서 간소화 하도록 하겠습니다.

int layer1 = 11;
int layer2 = 12;

int room1 = 2;
int room2 = 3;
int room3 = 4;
int room4 = 5;

void setup()
{
  pinMode(layer1, OUTPUT);
  pinMode(layer2, OUTPUT);

  pinMode(room1, OUTPUT);
  pinMode(room2, OUTPUT);
  pinMode(room3, OUTPUT);
  pinMode(room4, OUTPUT);

  //초기화 각 층을 막아놓습니다.
  digitalWrite(layer1, HIGH);
  digitalWrite(layer2, HIGH);
}

void loop()
{
  //1층 점등
  digitalWrite(layer1, LOW);  
  digitalWrite(room1, HIGH);  
  delay(1000);
  
  digitalWrite(room1, LOW);  
  digitalWrite(room2, HIGH);  
  delay(1000);
  
  digitalWrite(room1, LOW);  
  digitalWrite(room2, HIGH);  
  delay(1000);
  
  digitalWrite(room2, LOW);  
  digitalWrite(room3, HIGH);  
  delay(1000);

  digitalWrite(room3, LOW);  
  digitalWrite(room4, HIGH);  
  delay(1000);
  
  digitalWrite(room4, LOW);  
  digitalWrite(layer1, HIGH);  
  delay(1000);
  
  //2층 점등
  
  digitalWrite(layer2, LOW);  
  digitalWrite(room1, HIGH);  
  delay(1000);
  
  digitalWrite(room1, LOW);  
  digitalWrite(room2, HIGH);  
  delay(1000);
  
  digitalWrite(room1, LOW);  
  digitalWrite(room2, HIGH);  
  delay(1000);
  
  digitalWrite(room2, LOW);  
  digitalWrite(room3, HIGH);  
  delay(1000);

  digitalWrite(room3, LOW);  
  digitalWrite(room4, HIGH);  
  delay(1000);
  
  digitalWrite(room4, LOW);  
  digitalWrite(layer2, HIGH);  
  delay(1000);

}

1) 변수를 배열변수로 바꾼다.

기존수정
int layer1 = 11; int layer2 = 12;int layer[2] = {11, 12};
int room1 = 2; int room2 = 3; int room3 = 4; int room4 = 5;int room[4] = { 2, 3, 4, 5}




2) for문을 사용하여 중복 코드를 제거한다.

기존 코드

  pinMode(layer1, OUTPUT);
  pinMode(layer2, OUTPUT);
  //초기화 각 층을 막아놓습니다.
  digitalWrite(layer1, HIGH);
  digitalWrite(layer2, HIGH);

수정 코드

    for(int i=0;i<2;i++){
        pinMode(layer[i], OUTPUT);
        digitalWrite(layer[i], HIGH);
  }

기존 코드

  pinMode(room1, OUTPUT);
  pinMode(room2, OUTPUT);
  pinMode(room3, OUTPUT);
  pinMode(room4, OUTPUT);

수정 코드

    for(int i=0;i<4;i++){
        pinMode(room[i], OUTPUT);
    }

3) void loop() 안의 코드를 간소화

수정 코드

  for(int i=0;i<2;i++){
    digitalWrite(layer[i], LOW);              
    for(int j=0;j<4;j++){
           digitalWrite(room[j], HIGH);  
           delay(1000);
           digitalWrite(room[j], LOW);  
    }    
    digitalWrite(layer[i], HIGH);      
  }

1,2번의 수정은 코드만 보면 대충 뭘 줄였는지 알 수 있지만 3번 코드 간소화 경우는 로직이라서 간단히 설명을 드리겠습니다. for문은 순차적으로 반복하는 문법입니다.

예)

for(int i=0;i<3;i++) { 
    Serial.println("Hellow" );
}

for문은 i가 0시작해서 i<3보다 작을때까지 반복합니다. 뒤에 i++은 증감연산자로 'i=i+1'로 생각하시면 됩니다. for문이 한번씩 돌때마다 i++로 i가 1씩 증가하는데 그렇게 되면 결과는 다음과 같습니다.

결과 :

Hellow
Hellow
Hellow

이렇게 i가 0부터 0,1,2까지 참임으로 for문 안의 명령어 Serial.println()함수를 수행하지만 i가 3이되면 거짓으로 for문을 빠져 나옵니다.

이제 for문을 이해했으면 왜 수정코드를 이렇게 코딩했을까요.

2x2x2 cube led은 1층 4개, 2층 4개의 LED로 구성되어 있잖아요. 여기서 1층부터 순차적으로 1,2,3,4호실에 불이 들어와야 하니깐 4번을 순차적으로 같은 동작을 수행해야 합니다. 그래서 for문으로 4번을 순차적으로 반복할려면 아래와 같은 구조가 됩니다.

for(j=0;j<4;j++){
  명령문;
}

1호실 부터 1초 단위로 불이 들어올려면 1호실만 HIGH 상태가 1초동안 대기 되었다가 다시 LOW로 바뀌고 2호실, 3호실, 4호실 순으로 반복되어야 겠죠.

digitalWrite(layer[i], LOW);  // 해당 i층 Gnd 상태 (해당층 개방)
for(int j=0;j<4;j++){
  digitalWrite(room[j], HIGH);  //해당 j호실 전원 공급
  delay(1000);
  digitalWrite(room[j], LOW);  //해당 j호실 전원 차단  
}
digitalWrite(layer[i], HIGH);  //해당 i층 HIGH 상태 (해당층 닫힘)

이렇게 되는 것이죠. 그리고 Vcc와 Gnd은 한쌍이라고 했죠. 1층을 Gnd(-)로 하려면 1층을 LOW상태로 만들어 합니다. 그래서 for문 밖에다가 1층을 LOW상태로 만들었네요. 왜 안에다가 안넣고 밖에다가 표현했을까요. 그 이유는 아두이노가 1층을 Gnd 상태로 만들었는데 또다시 Gnd 상태 만들어라고 반복 명령을 내리면 비효율적인 코딩이 되잖아요. 구지 반복 명령을 할 필요없이 한번만 해도 되는 문장은 for문에 넣을 필요는 없습니다.

여기서 for문이 2중 for문으로 구성되었는데 그 이유는 1층 1,2,3,4호실을 깜박였으면 2층 1,2,3,4호실 깜박여야 하잖아요. 각 호실을 깜박이는 동작을 for문으로 만들었는데 그 for문을 2층이니깐 2번 반복해야 하니깐 2중 for문을 만들어야 합니다.

for(int i=0;i<2;i++){ // i=0,1 일때까지만 2번 반복 (층 반복)
 해당층 개방;
 for(int j=0;j<4;j++){ // j=0,1,2,3 일때까지만 4번 반복 (호실 반복)
    명령문;
 }
 해당층 닫힘;
}

그래서 아래와 같은 로직이 나오게 된 것이죠.

    for(int i=0;i<2;i++){
        digitalWrite(layer[i], LOW);  // 해당 i층 Gnd 상태 (해당층 개방)
        for(int j=0;j<4;j++){
            digitalWrite(room[j], HIGH);  //해당 j호실 전원 공급
            delay(1000);
            digitalWrite(room[j], LOW);  //해당 j호실 전원 차단  
        }
     digitalWrite(layer[i], HIGH);  //해당 i층 HIGH 상태 (해당층 닫힘)
    }

전체적으로 종합해보면

int layer[2] ={11,12};
int room[4] = {2,3,4,5};


void setup()
{
  for(int i=0;i<2;i++){
    pinMode(layer[i], OUTPUT);
    digitalWrite(layer[i], HIGH);
  }
  
  for(int i=0;i<4;i++){
    pinMode(room[i], OUTPUT);
  }
}

void loop()
{
    for(int i=0;i<2;i++){
        digitalWrite(layer[i], LOW);  // 해당 i층 Gnd 상태 (해당층 개방)
        for(int j=0;j<4;j++){
            digitalWrite(room[j], HIGH);  //해당 j호실 전원 공급
            delay(1000);
            digitalWrite(room[j], LOW);  //해당 j호실 전원 차단  
        }
     digitalWrite(layer[i], HIGH);  //해당 i층 HIGH 상태 (해당층 닫힘)
    }
}

2. 3x3x3 CUBE LED 회로도 구성


  • 준비물 : Red, Blue, Green LED 각각 9개, 저항 220옴 3개, 아두이노우노, 뻥판
  • 내용 : 3x3x3 CUBE LED 형태를 표현하자.


입체적 이미지를 평면적으로 뻥판에 나타내면 아래와 같은 회로도가 그려집니다.


실제로 제작을 한다면


위 그림처럼 철사를 사각형으로 만들고 빨간 꼭지점에 LED의 '-' 쪽을 각 꼭지점에 납땜하시면 됩니다. 그러면 총 9개가 하나의 극으로 연결이 되는데 이걸 1층이라고 생각하시면 됩니다. 이걸 3개를 만들어서 LED의 '+'쪽을 세로로 연결하시면 3층이 완성되어 전체 이미지는 3x3x3 CUBE LED가 됩니다. 실제로 만들면 무지 간단한데 이걸 가상시뮬레이터에서 표현하고 뻥판에 배치하니깐 무지 복잡해 보이네요.

최대한 보기 쉽게 표현 한건데 이것 역시 완성하고 나니깐 지난시간의 회로도보다 더 복잡하네요.

3. 코딩


  • 사용함수 : pinMode(), digitalWrite(), delay()
  • 내용 : 간단히 2x2x2 CUBE LED를 순차적으로 깜박이기
  • 참고 : [아두이노] LED 제어

복습

  • pinMode(사용핀, 사용모드) : 사용핀을 INPUT/OUTPUT/INPUT_PULLUP 모드중 뭘로 사용할지를 정함.
  • digitalWrite(사용핀, 출력형태) : 사용핀을 HIGH(5V) or LOW(0V) 중 출력 형태를 정함.
  • delay(1000) : 1초(1000)를 대기한다.

2x2x2 CUBE LED 간소화 코딩을 3x3x3 CUBE LED 형태로 수정

int layer[3] = {A0,A1,A2};
int room[9] = {10,9,8,7,6,5,4,3,2};
int m_layer = 3;  // 층 수
int m_room = 9; // 호실 수

void setup()
{
  for(int i=0;i<m_layer;i++){
    pinMode(layer[i], OUTPUT);
    digitalWrite(layer[i], HIGH);    
  }
  
  for(int i=0;i<m_room;i++){
    pinMode(room[i], OUTPUT);
  }
}

void loop()
{
  
  for(int i=0;i<m_layer;i++){
    digitalWrite(layer[i], LOW);  
    for(int j=0;j<m_room;j++){
      digitalWrite(room[j], HIGH);  
      delay(100);
      digitalWrite(room[j], LOW);  
    }
    digitalWrite(layer[i], HIGH);  
  }
}

층수와 호실수를 변수로 따로 빼서 setup(), loop() 함수 로직은 이제 수정하지 않는 방향으로 변경했네요. 이렇게 하면 위에 변수 선언부분만 수벙하시면 2x2x2 CUBE LED로 바꿀 수 있고 3x3x3 CUBE LED로도 쉽게 변경이 가능해집니다.

4. 결과


지난 시간에 가상시뮬레이터 결과가 지연 렉 때문에 정상적으로 결과가 안나와서 실제 아두이노로 표현했는데 브라우저 문제였네요. 오늘 크롬에서 가상시뮬레이터를 돌리니깐 어제 1초단위로 깜박이는 동작이 깔끔하게 보여줬네요. 오늘도 가상시뮬레이터로 돌린 결과와 현실 아두이노에서 돌린 결과를 실제 촬영해서 기록했네요.

그런데 실제 아두이노에서는 전선이 부족하여 정상적으로 동작은 하지만 일부 LED은 연결할 수 없어서 일부 LED에 불이 안들어온 점은 감안하고 보세요. 최대한 선들을 구햇지만 선 부족으로 1층은 9개 연결을 다 했지만 2,3층은 6개까지 연결하고 나니깐 더이상 선이 없어서 2,3층 3개의 LED은 돌릴 수 없었네요. 그래도 전달하고자하는 실험 내용의 결과는 정상적으로 얻었으며 안켜진 LED은 오류가 아니니깐 감안하시고 보세요.

추가 내용

마지막 동영상 장면에서 delay()시간차 연결을 몇초 담았는데요. 혹시 궁금하실분이 잇을 것같아서 만약 1층1호실 2층 2호실을 같은 시간대 불이 들어오게 할려면 어떻게 해야할지 의문을 품는 분들이 아마 있을꺼에요. 큐브 자체가 회로도를 저리 구성하기 때문에 물리적으로는 사실 어렵습니다. 하지만 인간의 시각을 이용한 방법으로 착시 효과로 표현은 가능합니다. 이 말은 인간의 눈은 두 LED가 있으면 delay()시간을 아주 짧게 주면 동시에 불이 들어오는 것처럼 착시 현상이 발생합니다. 아두이노에서는 그 착시효과를 이용해서 delay()시간으로 각 층에 LED에 불을 넣으면 됩니다.

  digitalWrite(layer[0], LOW);  
  digitalWrite(room[0], HIGH);   
  delay(20);
  digitalWrite(room[0], LOW);
  digitalWrite(layer[0], HIGH);

  digitalWrite(layer[1], LOW);  
  digitalWrite(room[2], HIGH);   
  delay(20);
  digitalWrite(room[2], LOW);   
  digitalWrite(layer[1], HIGH); 

즉, 1층 1호실에 불이 0.02(20)초 동안 전원 공급했다가 차단하고 바로 2층 2호실에 0.02(20)초 동안 정원 공급하면 어떻게 될까요. 분명 코딩상으로 차이가 나지만 시각적으로 보면 delay(20)초 간격으로 1층 1호실과 2층 2호실이 동시에 불이 들어오는 것처럼 착시효과가 발생합니다. 이러한 방법으로 서로 다른층의 LED를 동시에 불을 들어오게 합니다.

위 그림을 보듯이 짧게 딜레이를 줘서 각 층의 원하는 위치에 거의 동시에 불이들어오게 착시효과를 나타내고 잇네요.

마무리


원래는 한개의 포스팅으로 3편의 내용을 함축하고 싶었는데 그냥 회로도 그리고 코딩하고 결과만 딸랑 보여주면 별로 의미가 없을 것 같아서 최대한 많은 것을 설명할려고 노력했는데 빠진 부분이 아직도 너무 많네요. RGB LED를 이용한 CUBE LED도 표현하면 좋은데 실제 보유한 RGB LED가 한개 뿐이고 가상시뮬레이터로 표현하자니 2핀짜리 LED 선연결도 이리 복잡한데 4핀짜리 RGB LED를 큐브 모양으로 만들 엄두가 안나네요. 원리를 이번 포스팅에 설명한 원리랑 같기 때문에 도전하고 싶은 분들은 도전해보세요. 지금 표현한 것에 3배 노가다를 하시면 충분히 표현이 가능하실꺼에요.
전 도저히 못하겠네요. 도전하실분들은 RGB LED를 큐브로 만들어 보세요.


댓글()

[아두이노]3x3x3 CUBE LED 제어 II

IOT/아두이노|2019. 3. 20. 09:00

[아두이노]3x3x3 CUBE LED 제어 II



지난 시간에 간단히 3x3x1 LED를 제어 해봄으로써 3x3x3 CUBE LED의 원리를 이해 했습니다. 오늘 실험에서는 3x3x3 CUBE LED를 실험할 예정이였지만 실제 구현까지 할려고 하니 총 27개 LED를 다뤄야 하고 뻥판에 배치하자니 지져분해지고 오히려 의미 전달이 잘 안될 것 같아요. 2x2x2 CUBE LED 간단히 소개할까 합니다.

1. 3x3x3 CUBE LED


모습은 아래 그림처럼 구성되어 있습니다. 가로로 보시면 Gnd(-) 부위에 녹색선으로 사각형 모양을 만들어 하나로 선을 만듭니다. 그게 세로로 보는 것 처럼 3층으로 이루어진 것을 3x3x3 CUB LED라고 부릅니다.

[ 세로 ][ 가로 ]


여기서, 무조건 Gnd(-)로 9개 LED를 하나로 묶어을 필요는 없습고 반대로 Vcc(+)로 9개 LED를 하나로 묶을 수 있습니다. 그냥 선택사항일 뿐이죠. 저는 9개 LED를 Gnd로 묶어서 하나의 선으로 만든 것일 뿐이죠.

이걸 실험할려면 총 27개의 LED를 제어를 해야하고 실제로 회로도를 만들면 좀 복잡해 질 수 있어서 의미 전달보다 시각적으로 복잡하게 느낄 수 있어서 차라리 2x2x2 CUBE LED를 실험하는게 의미 전달하는데 더 나을 것 같아서 수정하였습니다.

2. 2x2x2 CUBE LED


2x2x2 CUBE LED 모습인데 가상시뮬레이터에서 그릴려고 하니깐 모양이 참 애매하네요. 대충 이런식으로 배치한다고 생각하시면 됩니다.


실제로 CUBE 모양을 만들려면 납땡을 하고 철사가 필요한데 납땜 도구를 찾아서 제작을 할까 고민을 하다가 CUBE를 만드는데 LED를 다쓰면 나중에 다른 실험을 할때 LED가 부족할 것 같았서 뻥판에 배치하여 그 의미만 전달하는 걸로 표현했습니다.


[ 실행 결과 ]


녹색은 1층을 나타내고 노랑은 2층으로 생각하시면 됩니다. 위 그림에서 각 호실은 오렌지 1호실, 블루 2호실, 엘로우 3호실, 화이트 4호실를 가리킵니다. 여기서, 오렌지 1호실은 2층 1호실과 연결되고요. 블루, 엘로우, 화이트 들은 각층에 해당 호실과 선이 연결됩니다. 각층 Gnd(-)은 아두이노의 Gnd(-)에 연결 되었고, 각 오렌지, 블루, 엘로우, 화이트 선은 아두이노의 Vcc(5V)에 연결되었습니다. 결론은 각 층은 Gnd(-) 연결하고 각호실에 Vcc(5V)에 모두 연결되었기 때문에 전부 불이 들오게 됩니다.

만약, 오렌지 1호실만 Vcc(5V)에 연결하고 각층 Gnd(-)은 전부 연결했다며 아래 그림처럼 1층 1호실, 2층 1호실에 불이 들어 오겠죠. 다시 정리하자면, Vcc의 전류가 엘로우 1호선만 공급되는데 1층, 2층 중 Gnd(-)로 모두 연결되었기 때문에 둘다 전류가 흘러가기 때문에 각 층 1호실에 불이 들어오게 됩니다. 만약 1층이 Gnd(-)와 연결이 끊어졌고 2층만 Gnd(-)에 연결되었다면 2층 1호실만 불이 들어 오겠죠.


이렇게 Vcc(5V)가 연결된 선을 따라서 최종 목적인 Gnd(-)로 연결된 곳만 전류가 정상적으로 흘러가게 됩니다. 이것만 이해하시면 됩니다.


[ 참고 ] 전류의 흐름

[예로 1층 1호실에 불이 들어왔다면]


전류의 흐름을 빨간선으로 표시한 부분이 정상적으로 연결되어 전류가 흘러가는 표시 이고요. 위 그림처럼 화살표 방향으로 흘러간다는 것을 참고해 주세요. 대충 어떤 느낌이신지 아시겠지요.


아두이노에서는 디지털 핀에 연결하여 제어를 하게 되는데 그 과정을 배워보도록 하겠습니다.

3. 회로도 구성


  • 준비물 : Green LED 4개, Yellow LED 4개, 저항 220옴 2개, 아두이노우노, 뻥판
  • 내용 : 2x2x2 CUBE LED 형태를 표현하자.


총 6개의 핀을 사용하게 됩니다. 2,3,4,5번 핀은 각 호실을 나타내는 핀이고 11,12번 핀은 층을 나타내는 핀입니다. 지금까지 LED 제어를 할때 Vcc(+) 방향에 저항을 붙여왔습니다. 하지만 Gnd(-)방향에 저항을 붙여 놓아도 상관 없습니다. 저항으로 흐르는 전류가 제어가 되기 때문에 어느 방향으로 저항을 붙이든 상관이 없다는 점을 참고해 주세요.

4. 코딩


  • 사용함수 : pinMode(), digitalWrite(), delay()
  • 내용 : 간단히 2x2x2 CUBE LED를 순차적으로 깜박이기
  • 참고 : [아두이노] LED 제어

복습

  • pinMode(사용핀, 사용모드) : 사용핀을 INPUT/OUTPUT/INPUT_PULLUP 모드중 뭘로 사용할지를 정함.
  • digitalWrite(사용핀, 출력형태) : 사용핀을 HIGH(5V) or LOW(0V) 중 출력 형태를 정함.
  • delay(1000) : 1초(1000)를 대기한다.

설계

이전 시간에 직접 전류를 선으로 Vcc(+), Gnd(-)를 연결해서 해당 위치의 불이 들어오게 만들었습니다. 그렇다면 아두이노에서는 어떻게 명령을 내려야 할까요.

1층 1호실에 불이 들어올려면 오레지(2)핀에 Vcc(5V)가 공급되고 1층(11)핀이 Gnd(-)가 되면 되겠죠. 직접 선을 연결한다는 생각을 하시면 됩니다.

그걸 코딩화 하면은 digitalWrite()함수를 사용해야 합니다.

  • 오렌지(2)핀이 Vcc(5V)가 공급 => digitalWrite(2, HIGH);
  • 1층(11)핀이 Gnd(0V)가 되어야 하니깐 => digitalWrite(11, LOW);

이렇게 표현하시면 됩니다.

1층을 순차적으로 깜박이게 하려면 어떻게 해야 할까요.

층별로는 1층(11)핀이 Gnd 상태이고 2층은 Gnd 상태가 아니면 되죠.

  • digitalWrite(11, LOW);
  • digitalWrite(12, HIGH);

이렇게 표현합니다. 왜 2층에 HIGH를 공급했을까요. 디지털 출력은 HIGH or LOW 두가지 상태만 존재합니다. 그리고 LED가 불이 들어올려면 Vcc와 Gnd가 연결해야 전류가 흘러서 LED에 불이 들어온다고 했죠. 가령 해당 LED가 Vcc쪽으로 5V를 공급하고 Gnd쪽으로 5V 상태면 서로 충돌하게 되잖아요. 그러면 전류가 흐를 수 없게 됩니다. 같은 극이 되면은 전류를 흐르지 않습니다. 이런 원리를 이용해서 LED를 제어하게 됩니다.

1층만 순차적으로 LED가 1,2,3,4 호실에 불이 들어올려면

digitalWrite(12, HIGH);

2층이 HIGH 상태에서 1층1호실을 불이 들어올려면

digitalWrite(11, LOW); 
digitalWrite(2, HIGH);
delay(1000);

2번 Vcc, 11번 Gnd로 1층 1호실에 불이 들어오고 1초간 대기한다는 의미입니다.

그러면 1층 2호실이면 같은 문장으로.

// 2층 잠금
digitalWrite(12, HIGH);

// 1층 개방
digitalWrite(11, LOW); 

// 1층 1호실 켜기
digitalWrite(2, HIGH);
delay(1000);

// 1층 1호실 끄고 2호실 켜기
digitalWrite(2, LOW);
digitalWrite(3, HIGH);
delay(1000);

.....

주석에서 설명하는 것처럼 켠 LED은 끄고 다음 LED은 켜고 이 문장을 반복해서 표현하면 됩니다.

2층으로 넘어갈때는

// 1층 잠금
digitalWrite(11, HIGH); 

// 2층 개방
digitalWrite(12, LOW); 

// 2층 1호실 켜기
digitalWrite(2, HIGH);
delay(1000);

// 2층 1호실 끄고 2층 2호실 켜기
digitalWrite(2, LOW);
digitalWrite(3, HIGH);
delay(1000);

.....

이렇게 반대로 생각하시면 됩니다.

전체적으로 코딩을 하면

int layer1 = 11;
int layer2 = 12;

int room1 = 2;
int room2 = 3;
int room3 = 4;
int room4 = 5;


void setup()
{
  pinMode(layer1, OUTPUT);
  pinMode(layer2, OUTPUT);

  pinMode(room1, OUTPUT);
  pinMode(room2, OUTPUT);
  pinMode(room3, OUTPUT);
  pinMode(room4, OUTPUT);

  //초기화 각 층을 막아놓습니다.
  digitalWrite(layer1, HIGH);
  digitalWrite(layer2, HIGH);
}

void loop()
{
  //1층 점등
  digitalWrite(layer1, LOW);  
  digitalWrite(room1, HIGH);  
  delay(1000);
  
  digitalWrite(room1, LOW);  
  digitalWrite(room2, HIGH);  
  delay(1000);
  
  digitalWrite(room1, LOW);  
  digitalWrite(room2, HIGH);  
  delay(1000);
  
  digitalWrite(room2, LOW);  
  digitalWrite(room3, HIGH);  
  delay(1000);

  digitalWrite(room3, LOW);  
  digitalWrite(room4, HIGH);  
  delay(1000);
  
  digitalWrite(room4, LOW);  
  digitalWrite(layer1, HIGH);  
  delay(1000);
  
  //2층 점등
  
  digitalWrite(layer2, LOW);  
  digitalWrite(room1, HIGH);  
  delay(1000);
  
  digitalWrite(room1, LOW);  
  digitalWrite(room2, HIGH);  
  delay(1000);
  
  digitalWrite(room1, LOW);  
  digitalWrite(room2, HIGH);  
  delay(1000);
  
  digitalWrite(room2, LOW);  
  digitalWrite(room3, HIGH);  
  delay(1000);

  digitalWrite(room3, LOW);  
  digitalWrite(room4, HIGH);  
  delay(1000);
  
  digitalWrite(room4, LOW);  
  digitalWrite(layer2, HIGH);  
  delay(1000);

}

코딩만 길게 표현이 된 것일뿐 LED을 켜고 끄고 이렇게 순차적으로 반복되는 문장일뿐 길다고 복잡하게 생각하실 필요는 없어요.

아래 두 표현으로만 코딩한 내용을 순차적으로 깜박이는 패턴을 만들려고 하니깐 전체적 코딩에서 길어지게 보였을분 아래 두 표현만 이해하시면 됩니다. 순차적으로 깜박이는게 중요한게 아니라 어떻게 전류를 흐르게 하고 차단을 하는지를 이해하시면 됩니다.

  digitalWrite(layer1, HIGH); //1층 잠금   
  digitalWrite(layer1, LOW); //1층 열림
  

  digitalWrite(room1, HIGH); //켜기 
  digitalWrite(room1, LOW);  //끄기

오늘 코딩한 표현은 흐름의 동작을 직설적으로 다 일일히 코딩으로 보여준거고요. 프로그램 문법을 사용하면 간단히 줄일 수 있습니다. 프로그램 문법보다는 일일히 동작하는지를 살펴보는게 주 목적입니다. for문을 사용하면 아주 간단하게 축소 시킬 수 있지만 오늘은 그 부분을 다루지 않을께요.

5. 결과


가상시뮬레이터에 부품이 많이 배치되면은 온라인상에서 실험하는거랑 렉이 좀 발생하네요. 실험 소스대로 하면 컴사양과 인터넷 사양에 따라 달라지겠지만 제 컴에서는 1초가 꽤 길게 느껴지더군요. 아주 짧은 시간으로 돌렸는데 첫음에는 빠르게 동작하다가 나중에는 거의 1초 단위로 순차적으로 깜작이더군요. 마지막에 실제 아두이노로 실험한 결과를 올려놨으니깐 젤 마지막 부분인 14분 20초부터 영상을 보시고 동작을 살펴보세요.

마무리


원래는 3x3x3 CUBE LED를 할려고 계획했는데 실제로 뻥판에 구현하는 장면까지 넣을려고 하니깐 뻥판이 너무 지져분해지고 의미 전달이 안될 것 같아서 2x2x2 CUBE LED로 변경했네요. 이 경우는 총 8개 LED만 제어하면 되기 때문에 코딩량도 적고 회로도 배치하는 것도 복잡하지 않고 실제 제작에서도 간단하게 실험할 수 있어서 좋은 것 같더군요.
개인적으로 3x3x3 CUBE LED를 실제로 납땜하면서 구현해보는게 가장 이상적이라고 생각되네요. 문제는 딱 제가 보유한 LED 개수가 딱 색상별로 10개씩 있어서 만약에 3x3x3 CUBE LED를 실제로 만들면 색상별로 1개씩 밖에 남지않아서 다음 실험에 LED를 활용할 수 없어서 실제로 제작한 모습은 보여드릴 수 없어서 아쉽네요. 이런건 실제로 만들어지는 걸 봐야 시각적 효과가 큰데 말이죠.

참고로 RGB LED로 구현도 가능합니다. 이경우는 총 4핀이여서 제작 과정이 좀 복잡하지만 원리는 동일합니다. 한번 연구해 보세요.


댓글()

[아두이노] 3x3x3 CUBE LED 제어 I

IOT/아두이노|2019. 3. 19. 09:00

[아두이노] 3x3x3 CUBE LED 제어 I



오늘의 주제는 흥미를 유발할 수 있는 3x3x3 CUBE LED 원리를 배우도록 하겠습니다. 지난 [아두이노] 스위치버턴을 이용한 Keypad 제어에서 스위치 버턴을 건물의 층과 호실로 나눠서 살펴봤는데 스위치 버턴은 아두이노로 입력을 받는 방식이라면 역으로 3x3x3 CUBE LED은 아두이노에서 LED로 출력하는 방식이라고 생각 하시면 됩니다. 스위치 버턴은 시각적으로 이해하기는 어렵지만 이번 포스팅 내용은 최대한 시각적인 표현을 통해 LED 제어하는 원리를 쉽게 이해할 수 있도록 표현해 보겠습니다.

1. 3x3x3 CUBE LED 원리


무료이미지 없고 실제로 제작한 것이 없어서 남의 이미지를 가져오는게 좀 문제가 있어서 가상시뮬레이터 최대한 표현은 해 봤네요. 검색어로 3x3x3 CUBE LED를 치시면 사진으로 화려한 이미지들을 볼 수 있을꺼에요. 이런걸 만드는 거구나 하고 우선 구경만 해보세요.


3x3x3 CUBE LED은 x,y 축으로 3x3 LED가 있고 z축으로 3x3 LED가 3개가 있는 것을 3x3x3 CUBE LED라고 부릅니다. 평면이 아닌 3차원 LED라고 생각하면 편하실꺼에요.

아래 그림은 x,y축으로 1개의 LED 단면을 나타낸 실험 그림입니다. 그림을 보면 5V 파워서플라이를 이용해서 전류를 보낼때 회로도 처럼 +, - 를 보내면 아래 한줄이 전부 불이 들어오는 결과를 보실 수 있을거에요. 왜 이렇게 불이 들어왔는지 그 원리에 대해서 공부해보도록 하겠습니다.

회로도결과

2. LED 원리



LED은 다리가 긴쪽이 '+' 이고 다리가 짧은쪽이 '-' 입니다. 가상시뮬레이터에서 사용하는 LED은 다리가 긴쪽이 꺽여있는 걸 보실꺼에요. 다리가 길다는 것을 표현한 이미지 입니다.


위 그림처럼 LED은 5V의 전류가 공급되면 바로 터져버립니다. 그래서 저항을 붙여줘서 전류의 양을 필요한 만큼만 공급되게 해야 아래와 같이 정상적으로 켜집니다. 220옴을 사용했네요. [아두이노] LED 제어 편에서 LED 색상별 필요한 전류가 표로 나와 있으니깐 참조하세요.



3. LED 동작



전류의 공급이 이루어진 LED만 불이 들어오는 것을 보실 수 있죠. 해당 LED에 Vcc(+) 전류가 공급되면 Gnd(-)핀과 쌍으로 연결된 LED에만 정상적으로 전류가 흐르니깐 LED에 불이 들어오게 됩니다. 이 동작 원리를 잘 이해해 주세요. 3x3x3 CUBE LED의 기본 동작 원리이기 때문입니다.


4. 3x3x3 CUBE LED 동작



위 LED 동작에서 보신 3개의 LED를 하나의 층으로 생각하고 총 3층으로 구성되었다고 생각하세요. 3개의 LED가 3개씩 총 9개가 배치된 그림입니다.

전선의 색을 잘 살펴봐주세요. 세로로 같은색을 이루고 있죠. 그 색은 서로 연결되어 있는거라고 생각해 주세요. 오렌지, 블루, 핑크색 선들이 세로로 연결되어 있으니깐 혼동하지 마세요

이제 3층 3호실로 이루어진 건물이 있다고 생각하세요. 각층의 1호실은 핑크선, 각층의 2호실은 블루선, 각층의 3호실은 오렌지선으로 연결되어 있다고 생각하세요.

1층 1호실방에 불이 들어올려면 LED 동작에서 이해 했다면 Vcc(+)와 Gnd(-)를 연결을 어떻게 해야 할까요. 핑크선에 Vcc(+)을 연결하고 1층의 녹색선에 Gnd(-)를 연결하면 됩니다.


여기서 핑크선에 Vcc(+)를 연결하면 1층1호실, 2층1호실, 3층1호실에 Vcc(+) 전류가 공급됩니다. 여기서 1층 녹색선만 Gnd(-)선이 연결되어 있다면 1층1호실만 불이 들어옵니다. 하지만 2,3층 녹색선도 Gnd(-)에 연결되어 있다면 어떻게 될까요.



위 그림처럼 각층에 1호실에 Vcc(+)가 공급되니깐 각층마다 Gnd(-)가 연결된다면 각층 1호실 전부다 불이 들어오게 되겠죠. 이 의미를 잘 이해해 주세요.

그러면 다시 2층 2호실에 불이 들어올려면 어떻게 해야 할까요. 블루선에 Vcc(+)가 연결되고 2층 녹색선만 Gnd(-)선이 연결된다면 아래와 같이 불이 들어 오겠죠.


어렵지 않죠. 여러분들도 원하는 위치에 불이 들어오게 한다면 Vcc(+)와 Gnd(-)를 어떻게 연결할지 그려보시거나 가상시뮬레이터에서 한번 실험 해보세요.

이게 3x3x3 CUBE LED의 원리입니다. 오늘 실험은 3x3x1 CUBE LED 라고 생각하시면 되겠죠. 3x3x3 CUBE LED라면 9 호실로 이루어진 3층 건물로 생각하시면 됩니다. 3호실이 아니라 9호실로 늘어났다고 생각하시면 됩니다. 단지 LED를 큐브 모양으로 배치했을뿐 위에서 다룬 LED 동작을 3층 9호실로 호실만 늘어난 구조라고 생각하시면 오늘 내용은 이게 전부입니다.

Vcc(+)와 Gnd(-)을 어떻게 연결하느냐에 따라 LED에 불이 들어오는지만 이해하시면 됩니다. 핑크, 블루, 오렌지 선에 공급되는 Vcc(+)와 각층에 연결된 Gnd(-)선이 서로 연결되었을때 원하는 위치에 LED에 불이 들오는 것만 이해하시면 됩니다.

설명보다는 실제로 가상시뮬레이터에서 선을 연결해보세요. 그래야 이해가 되실 듯 싶네요

마무리


위에서 Vcc(+), Gnd(-) 선을 연결하여 해당 위치의 LED에 불이 들어오게 하는 방법을 배웠습니다. 하지만 중요한것은 아두이노에서는 위에서 실험한것처럼 그때 그때마다 선을 바꿀 수 없잖아요. 그걸 아두이노가 디지털핀으로 Vcc(+0)와 Gnd(-)을 출력함으로써 그 역활을 대신합니다.


파워서플라이에서 선이 전부다 Vcc(+), Gnd(-)가 연결되어 있죠 1층 1,2,3호실에 불이 들어오고 나머지는 불이 안들어오게 하기 위해서 표현한 예인데 아두이노였다면 디지털핀 6개를 Vcc(+)와 Gnd(-)를 출력으로 보내면 보시는 것처럼 LED불이 들어오게 되겠죠.

그리고 선을 보시면 녹색선에 Gnd(-) 대신에 Vcc(+)를 연결하신 걸 보실꺼에요. 양극이 같은 극이 되면은 전류가 흐를 않는 원리를 이용한 것입니다. 왜 이렇게 구지 연결했냐면 다음편에 소개할 아두이노에서는 해당핀을 Vcc(+) or Gnd(-) 에서 핀은 둘중 하나의 상태를 유지하기 때문에 시각적으로 보여주기 위해서 파워서플라이 연결을 이런식으로 표현 한 것이죠.

이부분은 다음편에서 자세히 이야기 하도록 하겠습니다. 오늘은 LED 선을 어떻게 연결하면 어느 위치에 LED가 불이 들어오는지 전류의 흐름을 잘 관찰하시고 그 원리를 이해만 해주시면 어렵지 않을 것에요. 그리고 이 부분이 핵심이니깐 전류의 흐름의 느낌을 잘 이해해 주세요.

댓글()

[아두이노] Debounce에 대해 살펴보자

IOT/아두이노|2019. 3. 18. 09:00

[아두이노] Debounce에 대해 살펴보자



오늘은 Debounce에 대해서 이야기를 하도록 하겠습니다. 채터링 or 바운딩이라는 단어가 있는데 이 의미는 실제 스위치 버턴과 같은 인간이 터치하는 센서가 있을때 이 센서를 누르고 때는 순간에 센서의 접전 부분에서 On/Off 가 여러번 반복되는 현상을 말합니다. 스플링 있는 버턴을 누를고 때는 순간에 그 버턴이 미세하지만 흔들리는 모습을 보시거나 손끝에서 느끼신적이 있을거에요. 키보드 자판을 한번 눌렀다 때 보세요. 느끼실지 모르겠지만 손끝에서 스플링이 누를때랑 땔려는 순간 약하게 미세하게 흔들림을 느끼실꺼에요. 그때가 아주 짧은 시간이지만 스위치 버턴 접전 부위에서 On/Off 가 여러번 반복되는 상황이라고 생각하시면 됩니다. 이런걸 하드웨어인 방법과 소프트웨어적인 방법으로 해결하는데 하드웨어적인 경우는 회로도에서 스위치 버턴이 흐르는 전류를 안정적 제어하기 위한 부품을 달아야 하는데 저항과 캐피시터인가 달아서 해결한다고 하는데 저도 정확히는 하드웨어적인 제어는 해보지 못해서 설명드리기 애매하네요.

아두이노 공식홈페이지에서 소개된 소프트웨어적인 방법을 소개할 하도록 하겠습니다.

1. 회로도 구성


  • 준비물 : 스위치 버턴 1개, Red LED 1개, 저항 220옴 1개, 아두이노우노
  • 내용 : 스위치 버턴을 눌렸을때 Red LED가 켜지게 한다.


3. 코딩



[ 스위치 버턴 기본 소스 ]

void setup()
{
  pinMode(13, OUTPUT);
  pinMode(7, INPUT_PULLUP);
}

void loop()
{
  digitalWrite(13, digitalRead(7));  //버턴을 누르면 불이 꺼지고 안누르면 불이 들어온다.
}

내부풀업모드를 사용할 시에 해당 핀에 초기 상태는 1이고 버턴을 누르면 0이 된다. 그래서 스위치 버턴 기본소스에서는 위 회로도에서는 누르면 불이 들어오고 때면 불이 꺼진다.



[ Dedboune 기본 소스 ] 출처는 아두이노공식홈페지의 튜토리얼 소스 내용으로 링크 걸어 놨네요. 가셔서 한번 읽어보세요.

int debounceDelay = 50; //바운드에 대한 기준 시간값
unsigned long lastDebounceTime = 0; // 현재시간값을 가져와서 저장할 변수 자료형은 같은형으로 맞춤
int buttonstate=1; //내부풀업모드 버턴이 초기값이 1이여서 초기값을 1로 잡음.
int lastButtonState=1; //마지막 버턴상태를 기록변수

void loop(){

    int reading = digitalRead(BUTTONPIN); //스위치 버턴상태값을 읽는다.

    if (reading != lastButtonState) { //현재 읽은 버턴의 상태값과 이전상태가 다른지 체크
        lastDebounceTime = millis(); // 다르면 변화(On/Off)가 일어난거니 그 시간을 변수에 저장시킨다.
    }

    if ((millis() - lastDebounceTime) > debounceDelay) {  //버턴변화가 50시간값 안에 발생했으면 바운스(채터링)으로 무시
                
                if(reading!=buttonState){ // 버턴이 On/Off 됐는지 확인(버턴의 변화가 일어났는가)       
                    buttonSate=reading; //버턴의 변화가 일어났으면 그 변화를 기록했다가 다음 버턴 변화의 비교대상이 됨
                    
                    스위치 On/Off로 인정한 동작 수행;                  
                }
        }
    
    lastButtonState = reading; //현재 읽은 버턴의 상태를 다음 버턴의 상태와 비교하기 위해 변수에 저장한다.
}

쉽게 말해서, 현재의 버턴이 누른다면 이전상태변수(lastButtonState)은 누르지 않는 상태이겠죠. 누르니깐 현재상태변수(reading)와는 다른 상태 변화가 일어나니깐 그 변화가 일어난 시간을 기록해서 그 변화가 일어나는 간격이 50이라는 시간값과 비교해서 50이하의 시간에 바운스(On/Off) 상태가 일어나면 그냥 무시한다는 의미이지요.

번역하자면

  1. 버턴의 상태값 읽기
  2. 버턴의 상태가 이전버턴의 상태와 다른가 체크하여 변화가 일어나면 그 시간을 저장한다.
  3. 현재의 시간과 이전시간과 차이가 50이하면 바운스(채터링)으로 간주하고 50이상일때는 버턴이 눌러진걸로 확정한다.

결론은 50이라는 시간값 안에 일어난 변화는 바운스(채터링)이다. 이런뜻이죠.

두 코딩을 합치면

int debounceDelay = 50; //바운드에 대한 기준 시간값
unsigned long lastDebounceTime = 0; // 현재시간값을 가져올꺼니간 자료형은 같은형으로 맞춘다.
int buttonstate=1; //왜! 1이냐면 내부풀업모드 버턴이 초기값이 1이여서 초기값을 1로 잡음.
int lastButtonState=1; //마지막 버턴상태를 기록한다. 왜냐면 다음 버턴상태와 비교하기 위해서이다.
boolean ledState = false; // LED 반전 상태값을 만들려고 변수를 선언했군요.


void setup()
{
  pinMode(13, OUTPUT); //13(RedLED)핀을 출력모드로 쓸꺼야!
  pinMode(7, INPUT_PULLUP); //스위치버턴을 내부풀업모드로 쓸꺼야.
}

void loop()
{
    int reading = digitalRead(7); //버턴값 읽는다.
    
    if (reading != lastButtonState) { //버턴의 변화가 일어났니!
        lastDebounceTime = millis(); // 그러면 현재 시간을 저장한다.
    }
    if ((millis() - lastDebounceTime) > debounceDelay) {  //버턴변화가 50시간값 안에 발생했으면 바운스(채터링)으로 무시
                                
        //진입! 성공! 50시간값이상 벌어졌으니 버턴으로 눌러진 걸로 인정 받는 거야!
            if(reading!=buttonState){ // 버턴이 On/Off 됐는지 확인(버턴의 변화가 일어났는가)       
                buttonSate=reading; //버턴의 변화가 일어났으면 그 변화를 기록했다가 다음 버턴 변화의 비교대상이 됨
                ledState=!ledState; 
        }

    }
    digitalWrite(13, ledState);  //버턴을 On/Off를 인정받은 ledState값으로 Red LED을 깜박인다.
  lastButtonState = reading; //현재 읽은 버턴의 상태를 다음 버턴의 상태와 비교하기 위해 변수에 저장한다.
}

주석 처리한 내용만 읽어보시면 대충 이해가 되실꺼에요.

이해가 안된다면 다시 한글로 로직을 설명


void loop(){
reading 변수에 현재 스위치버턴값을 읽는다.

만약, 현재스위치버턴(reading)의 상태가 이전스위치버턴상태1(lastButtonState) 다르면 변화시간(lastDebounceTime)을 저장 한다.

만약, 현재시간(millis())-변화시간(lastDebounceTime) 차 값이 50보다 작으면 채터링으로 무시하고 넘어가고 50보다 크면 스위치 버턴의 변화를 인정한다.

만약, 현재스위치버턴(reading)이 이전버턴상태2(buttonState)가 다르면 스위치 On/Off로 간주하고 다음 동작을 수행한다. 여기서 이전버턴상태2(buttonState)은 변화로 인정받은 스위치버턴상태값이다.

인정받았으니 현재스위치버턴(reading)값은 이전버턴상태(buttonState)에 저장한다. 다음에 비교 대상이 되기 때문이다.

마지막으로 이전스위치버턴상태1(lastButtonState)은 현재스위치버턴(reading)값을 저장한다. 왜냐면 다음에 변화가 일어나는 시간을 기록하기 위해서 다음에 비교 대상이 되기 때문이다.


대충 이렇게 글로써 표현할 수 있습니다. 여기서 왜 이전스위치상태변수를 lastButtonState, buttonState로 비슷한 변수로 둘을 사용했냐면 lastButtonState은 우선 버턴의 변화가 일어나는 시간을 찾기 위해서 reading가 비교하기 위해서 입니다. 버턴이 On/Off 인정을 받던 안받던 무조건 버턴의 변화가 일어나면 그 시간을 기록해서 그 시간값이 50 이하면 채터링으로 간주하기 위해서 체크용 변수입니다. buttonState은 실제 버턴이 On/Off 된것로 인정하고 그 변화를 상태를 비교하기 위해 체크용으로 사용하는 변수입니다. 둘 차이점을 정확히 이해하시고 보면 어렵지 않을거에요.

4. 결과


스위치를 누르니깐 정상적으로 불이 들어 왔네요.


마무리


오늘은 프로그래언어쪽으로 좀 더 깊은 내용이였네요. 어려우시다면 그냥 잊으셔도 됩니다. 원리는 정리하자면 50이라는 시간값을 주고 버턴이 On/Off가 발생하면 그 버턴의 On/Off를 무시한다는 내용입니다. 50이상일때 버턴의 On/Off를 인정받고 스위치가 눌러진 다음 동작인 Red LED에 불이 들어오게 한다는게 오늘 주제의 핵심입니다.

가상시뮬레이터에서 실험하시면 컴퓨터상이니간 바운스(채터링)은 발생하지 않습니다. 실제 스플링이 붙어 있는 버턴을 누르면 원치 않는 결과를 얻을 수 있습니다. 어디까지 버턴의 터치를 인정받아야 하는지를 개발자거 설정해야 합니다. 오늘 주제는 소프트웨어적인 방식으로 제어 했지만 이 방법이 정석이라고 할 수 없습니다. 하드웨어적으로 저항+캐피시터를 부착해서 전류를 안정적으로 제어해서 채터링이 사전에 발생하지 않게 하드웨어적으로 아예 만들어 버리면 쉽게 해결 되겠죠.

하지만 우리가 실험을 하면 하드웨어적 부분은 약할 수 밖에 없습니다. 어떤 부품을 부착해야하고 어느 위치에 그 부품을 위치해야할지 막막합니다. 최대한 기초적 지식으로 아두이노를 다루면서 나머지 부분은 소프트웨어적으로 접근해서 문제를 해결하는게 더 빠릅니다. 프로그램적으로는 구글검색만 조금만 하면 왠만한 표현이 오픈소스로 거의 대부분 제공되고 있으니깐 어렵지 않게 아두이노를 공부하실 수 있을거에요.

댓글()

[아두이노] 아두이노 끼리 통신하기

IOT/아두이노|2019. 3. 17. 09:00

[아두이노] 아두이노 끼리 통신하기



시리얼통신에서 블루투스 실험을 하면 좋은데 가상 시뮬레이터에서 블루투스를 제공하지 않아서 그 대안으로 2대의 아두이노를 연결해서 시리얼통신하는 실험을 갖도록 하겠습니다. 지난시간에 배웠던 시리얼통신을 이용하여 한쪽에서는 명령을 내리고 한쪽에서 시리얼통신으로 통해서 받은 명령을 실행하는 동작을 간단하게 어떤식으로 수행되는지 진행해 보겠습니다.

1. SoftwareSerial 라이브러리


아두이노는 내장된 0,1번 핀이 시리얼 통신핀입니다. SoftwareSerial는 소프트웨어를 사용하여 아두이노의 다른 디지털 핀에서 직렬 통신을 허용하게 하기 위해서 개발된 라이브러리 입니다.

#include <SoftwareSerial.h>

  • SoftwareSerial mySerial (rx, tx) : 소프트시리얼 객체선언(rx(수신), tx(전송))
  • mySerial.begin(2400) : 시리얼 통신 시작(예로 2400 통식속도를 사용해 봤네요.)
  • mySerial.write(값) : 데이터 전송
  • mySerial.available() : 데이터 들어왔는 확인
  • mySerial.read() : 전송된 데이터 1byte 읽기

시리얼통신 함수와 별다를 게 없죠. 단지 객체변수로 선언할대 rx, tx 핀번호를 지정해 줘야 한다는 것 외에는 다 동일합니다. 시리얼통신에서 배웠던 함수들임으로 별로 어렵지 않을꺼에요.

2. 회로도 구성


  • 준비물 : Red LED 1개, 저항 220옴 1개, 아두이노우노 2개
  • 내용 : Red LED 깜박이는 기본 예제 회로도


2대의 아두이노를 보면 10, 11번의 두개의 핀을 시리얼 통신용으로 사용 됩니다. 아두이노의 전용 시리얼통신핀은 0, 1번핀입니다. 이 핀을 사용할 수는 있지만 사용을 안하는 걸 추천 드려요. 나중에 블루투스 통신에서 0,1핀을 사용하기는 하지만 여기에서는 아두이노 IDE 시리얼모니터를 사용하기 때문에 이쪽 0,1 핀을 제외한 다른 핀을 시리얼통신으로 사용할려고 10, 11번 핀을 임의로 지정했습니다.

3. 코딩


[ 시리얼통신 기본예제 소스 ]

void setup() {
  Serial.begin(9600);    //시리얼 통신 9600 통신속도로 시작
}
void loop() {
  
  if (Serial.available() > 0) { //데이터가 수신되는지 확인
    char ch = Serial.read(); //1byte 읽음
    Serial.println(ch); //1byte 읽은거 출력
  }
}

[ LED 기본예제 소스(1초 단위로 LED 깜박이기) ]

void setup() {
    pinMode(13, OUTPUT);
}

void loop() {
    digitalWrite(13, HIGH);
    delay(1000);
    digitalWrite(13, LOW);
    delay(1000); 
}

위 기본예제를 2번 아두이노에 합쳐서 동작하게 되는데 1번 아두이노의 명령에 의해서 깜박이게 코딩하도록 하죠.

시리얼통신에 사용되는 라이브러리는 SoftwareSerial 라이브러리입니다. SoftwareSerial 클래스을 이용하여 2대의 아두이노의 통신을 하게 됩니다.

전제척으로 코딩을 하면

[ 1번 아두이노 ]

#include <SoftwareSerial.h>

SoftwareSerial mySerial(10,11); //시리얼통신 핀

void setup()
{
    mySerial.begin(2400); //기존 9600이여서 다른 통신속도를 사용해야함
}

void loop()
{
  mySerial.write('1'); // 2번 아두이노에 '1'값 전송
  delay(1000);
  
  mySerial.write('0');  // 2번 아두이노에 '1'값 전송
  delay(1000);
}

간단하죠. SoftwareSerial를 인쿠루드 시키고 SoftwareSerial 객체변수를 선언할대 아두이노에서 사용될 디지털 핀을 10, 11번으로 지정했습니다. 그리고 나서 따로 할 것은 없고. write()함수 명령어로 값을 2번 아두이노에 보내는 명령 코드가 되겠습니다.


[ 2번 아두이노 ]

#include <SoftwareSerial.h>
SoftwareSerial mySerial(10,11);

char state;
int rPin=13;

void setup()
{
  Serial.begin(9600);
  mySerial.begin(2400);
  pinMode(rPin, OUTPUT);
}

void loop()
{ 
  if(mySerial.available() > 0) { //수신되었는지 상태 확인
    state = mySerial.read(); //수신된 데이터를 1byte 읽어와서 state 변수에 저장함
    
    if(state=='1') digitalWrite(rPin, HIGH); //읽어온 값이 '1'이면 참으로 Red에 HIGH로 불이 들어옴
    else digitalWrite(rPin, LOW); //'0'이면 1이 아니기 때문에 거짓으로 Red에 LOW로 불이 꺼짐
    
    Serial.println(state);  //정상적으로 읽은 값이 들어왔는지 아두이노 IDE 시리얼모니터로 출력
  }
  
}

2번 아두이노는 좀 코딩이 긴 편입니다. 정상적으로 값을 가져왔는지 아두이노 IDE 시리얼모니터로 그 값을 찍어봐야 하기 때문에

 Serial.begin(9600);
 erial.println(state);

이 두문장으로 아두이노 IDE로 state 값을 전송하게 됩니다. 여기서 state 값은 1번 아두이노에서 보내진 데이터를 수신한 값이 되겠습니다.

1번 아두이노에서 데이터를 수신하여 state 변수에 저장되는데 그 로직은 시리얼통신에서 기본적으로 사용하는 시리얼통신으로 데이터가 들어왔을때 그 값을 읽는 로직은 아래와 같습니다. 이건 왠만하면 계속 사용하기 때문에 숙지해 주세요.

  if(mySerial.available() > 0) {
        state = mySerial.read();
    }

if 조건문으로 '1'이면 불이 들어오고 '0'이면 불이 꺼진다는 조건을 만든다.

if(state=='1') 불켜!
else 불꺼;

간단하지요.

4. 결과


오른쪽 아두이노가 SoftwareSerial 을 사용하여 왼쪽 아두이노에게 '1'과 '0'의 값을 전달하고 왼쪽 아두이노도 전송된 값을 읽어서 Red LED가 깜박이게 동작을 수행하게 하는 사진입니다.


마무리


시리얼통신과 차이가 없습니다. 단지 SoftwareSerial 라이브러리를 이용하여 소프트웨어적으로 제어 했다는 것만 다를 뿐이죠.
실험에서는 코드를 따로 어떻게 가상시뮬레이터에서 하냐고 생각 하실 수 있습니다. 그냥 코딩창을 띄워놓은 상태에서 해당 아두이노를 클릭하면 해당 아두이노의 코딩창으로 바뀌게 됩니다. 그리고 해당 아두이노 코딩에 맞게 개별적으로 시뮬레이션이 되니간 쉽게 생각하세요.


그리고 이 그림처럼 직접 마우스로 해당 아두이노를 선택하시면 됩니다. 참고로 이 소스를 기반으로 블루투스를 실제 가지고 있는 분이라서 블루투스 통신 코드로 활용하셔도 됩니다.

마지막으로, 이걸로 뭘 할 수 있을지 한번 상상의 나래를 펼쳐보세요.

댓글()

[아두이노] delay()함수 안쓰고 delay 제어하기

IOT/아두이노|2019. 3. 16. 09:00

[아두이노] delay()함수 안쓰고 delay 제어하기



지금까지 delay()함수를 통해 시간 제어를 해왔습니다. delay()함수는 아두이노를 그 시간동안 암것도 하지 못하게 강제휴식 상태가 되도록 합니다. 지금까지는 대부분 하나의 부품을 제어 할때 delay()함수를 써왔고 별 문제가 발생하지 않았습니다. 하지만 부품 둘 이상 결합해서 표현 해보셨다면 정상적으로 동작하지 않는 현상이 생겼을거라 생각됩니다. 그 이유는 2개 부품이 있는데 한개의 부품의 delay() 시간을 줄때 다른 부품도 같이 대기 상태에 빠지게 됩니다. 가령 A라는 부품을 1초 대기할때 강제적으로 B도 같이 1초 대기하기 하게 됩니다. B 부품은 뭔가 작업을 해야하는데 A 부품의 delay()함수에 의해서 강제로 대기상태에 빠지게 됨으로 제대로 동작하지 않는 현상이 발생하게 됩니다. 이런 문제를 해결하기 위해서 delay() 함수를 쓰지 않고 delay 효과를 얻는 방법을 살펴보도록 하겠습니다.

오늘은 간단한 코딩을 배우는 시간입니다.

1. delay() 함수 없이 delay 시키는 코딩


  • 사용함수 : millis()(타이머 작동하면서 시간이 생성되며 그 시간값을 추출할때 사용)
  • 내용 : 현재시간값 - 이전시간 = delay 시간인지 조건문으로 비교해서 delay 시키자.

  • 현재시간값 : 현재 시간값을 저장할 변수로 unsigned long(부호가 없는 long형 정수) 자료형으로 currentMillis 변수를 선언했습니다. 시간값을 int형으로 하기 보다는 최대한 큰 변수로 선언하는게 좋습니다. 시간에는 음수가 없기 때문에 숫자를 양수만 표기하는게 좋겠죠. 그래서 기존 long형에서 표현하는 숫자의 두배정도 양수로 표기할 수 있는 부호가 없는 long형으로 변수를 선언했습니다.
    그냥 변수에 대해 이해가 안가시면 큰수를 저장할 수 있는 자료형이구나 정도로 이해하세요. 작은 수면은 int형 큰수면 long형 이정도만 우선 머리속에 담아두시면 됩니다.
unsigned long currentMillis = millis();

현재 시간값을 가져왔으면 다한 거나 마찬가지입니다. loop() 함수가 어떻게 동작한다고 했죠. 아두이노가 처음 전원이 공급되면 setup()함수가 처음 한번 수행한 뒤에서 loop()함수가 무한 반복 수행 한다고 했죠.

만약에 시간값 구하는 위 코드가 loop() 함수안에 있다면

void loop(){
    unsigned long currentMillis = millis();
}

loop()함수가 매 반복할 때마다 현재 시간을 추출하여 currentMillis 변수에 저장하게 됩니다. 처음 loop()함수가 호출이 되면 currenMillis 변수에 현재시간이 저장된 후에 두번째 loo()함수가 호출하게 되면 두번째 현재시간이 저장되겠죠. 그러면 첫번째 호출되어 저장된 현재시간은 이전시간이 되고 두번째 호출된 시간이 현재시간이 되겠죠. 여기서 currentMillis는 두번째 현재시간을 저장하기 전에 첫번째 저장된 값을 어딘가에 옮겨서 보관해야겠죠.

previousMillis = currentMillis;

간단하죠. 변수를 하나 더 만들어서 현재변수에 저장된 값을 이전변수에 저장하면 쉽게 해결됩니다.

딜레이 시간은 어떻게 구할까요.

대기시간 == 현재시간 - 이전시간

delayTime == currentMillis - previousMillis;

참 쉽죠. 현재시간에서 이전시간을 뺀 시간이 delayTime과 같아야 겠죠.

이제는 조건문으로 1초 단위로 딜레이 시켜볼까요.

unsigned long currentMillis = millis();

if(currentMillis - previousMillis >= delayTime){
  previousMillis = currentMillis;   
}

currenMillis의 시간값이 previousMillis값을 뺀 값이 delayTime시간보다 클경우 if문은 참이 됩니다. 왜 같다가 아니라 delayTime시간 이상일 경우에 참이라고 했을까요. 아두이노는 각 코딩라인을 수행할때마다 인간이 감지할 수 없지만 그래도 시간이 소요 됩니다. 결론은 현재시간을 측정할때 정확히 1초(1000)를 일치시키기 어렵습니다. 근사 초로 비교해야 합니다. 가령 1001이 현재시간이면 이것을 1초가 아닌게 아니잖아요. 1000,1001,1002 이렇게 측정될 경우 우리는 근사초로 1초로 보정해줘야 합니다. 그래서 delayTime 초보다 클경우를 참으로 잡는 이유가 되겠습니다.

delayTime(대기시간) 만큼이 만족하면 if문 안의 문장이 수행되고 이때 delay시간만큼 이동한 현재 시간이 previousMillis(이전시간) 변수에 currenMillis(현재시간)값을 저장해야겠죠. 왜냐면 다음 시간값과 비교해야하니깐요. 이렇게 표현함으로서 delayTime(대기시간)값에 만족하는 참이 될때마다 if문 안의 문장을 수행하는 로직이 됩니다.

void loop() {
  unsigned long currentMillis = millis();
  
  if(currentMillis - previousMillis >= delayTime){
    previousMillis = currentMillis;
    원하는 동작;
  }  
}

여기서 previousMillis, delayTime 변수는 외부로 빼줘야 합니다. 그 이유는 함수내에 선언된 변수는 함수가 끝나면 소멸합니다. 그래서 안에다 변수를 일반적으로 선언하면 이전 시간값은 사라지게 됩니다. 간단히 외부에 선언하시면 되겠습니다. 이전시간변수는 현재시간과 동일하니깐 부호가 없는 long형으로 해줘야하고 대기시간은 1(1000)초만 할꺼니깐 작은수이기 때문에 int형으로 선언하시면 되겠습니다.

unsigned long previousMillis = 0; 
const long interval = 1000;

2. 회로도 구성


  • 준비물 : Red LED 1개, 저항 220옴 1개, 아두이노우노
  • 내용 : Red LED 깜박이는 기본 에제 회로로


참 쉽죠. 회로도 별거 없지요. delay 원리를 쉽게 이해하기 위해 기본 LED 제어를 활용 했네요.

3. 코딩



LED 기본예제 소스(1초 단위로 LED 깜박이기)

void setup() {
    pinMode(13, OUTPUT);
}

void loop() {
    digitalWrite(13, HIGH);
    delay(1000);
    digitalWrite(13, LOW);
    delay(1000); 
}

딜레이효과 소스

unsigned long previousMillis = 0; 
const long interval = 1000;

void loop() {
  unsigned long currentMillis = millis();
  
  if(currentMillis - previousMillis >= delayTime){
    previousMillis = currentMillis;
    원하는 동작;
  }  
}

두 코딩을 합치면

int led = 13; //Red LED 핀번호
unsigned long previousMillis = 0; //이전시간
const long delayTime = 1000; //1초(1000) 대기시간
boolean ledState = false; //LED 현재상태

void setup() {
  pinMode(led, OUTPUT); //13번 핀을 출력모드로 사용.
}

void loop() {
  unsigned long currentMillis = millis(); //현재시간값 가져옴
  
  if(currentMillis - previousMillis >= delayTime){ //1000초 시간이 흘렀는지 체크
    previousMillis = currentMillis; //1초가 지나 참임으로 1초 지난 현재시간을 이전시간에 저장
    ledState=!ledState;   //1초 if문이 참이니깐 1초 단위로 ledState 값을 반전시키면 1초 단위로 참/거짓됨.
    digitalWrite(led, ledState);   //참(5V) or 거짓(0V) 
  }  
}

코드 주석을 달아 놓았으니깐 따로 설명은 필요 없겠죠.

두개 소스를 합쳐놓은 것에서

ledState=!ledState;   
digitalWrite(led, ledState);

이 표현만 추가된거죠. 인터럽트 예제에서 활용했던 방식인데 부울변수로 현재의 상태를 반전시킬때 많이 사용합니다. 디지털 출력은 0이 아닌 모든 값은 참이되고 0은 거짓이 됩니다.
그래서 부울변수로 True(1), False(0)의 값을 이용하여 1(5V), 0(0V)의 전류상태가 되는 원리를 이용한 것이죠. 반전원리를 사용할때 가장 많이 사용되는 기법입니다. 위 두 코딩을 합치는 부분에서 이 부분만 약간 추가되었을 뿐 나머진 그대로니깐 어렵지 않을거에요.

4. 결과


delay() 함수를 안써도 1초 단위로 그림에서 보는 것처럼 정상적으로 동작하네요.


마무리


delay() 함수를 안쓰게 되면 그 시간에 다른 작업을 수행 할 수 있게 됩니다. delay()함수를 써서 아두이노가 강제로 넌 쉬어! 올 스톱이 되는일은 이제 없게 된거죠. 그러면 2개이상의 부품에서 delay()함수를 사용할 경우 대기시간 문제에서 벗어났습니다.

코딩은 어렵지 않은데 정확히 어디에 써야할지 잘 연상이 안되실 수 있지만 우선 이런식으로 delay()함수 없이 1초 단위로 RED Led를 깜박이게 할 수 있구나 정도만 이해하시면 되겠습니다.

어제 포스팅한 실험을 그제 밤에 증흥적으로 3개의 주제를 합쳐서 만들어 실험 포스팅을 해서 사전 준비 없이 좀 시간을 잡아 먹었네요 그래오 오늘 포스팅은 쉬운 주제로 휴식하는 기분으로 간단한 주제로 내용을 담았네요.

댓글()

[아두이노] 서보모터를 리모콘(IRremote)으로 제어

IOT/아두이노|2019. 3. 15. 09:00

[아두이노] 서보모터를 리모콘(IRremote)으로 제어 



이번 내용을 지금까지 복습한 내용들을 종합해서 사용해보는 시간을 갖도록 하겠습니다. 총 3개의 주제를 하나로 묶어서 회로도를 구성할 예정입니다. 다 합쳐진 모습은 좀 복잡해 보일 수 있지만 개별 포스트 내용을 참고하시면 어렵지 않을거라 생각됩니다. 전체 코딩한 내용을 보면 이런걸 어떻게 코딩을 초보분들이 할 수 있냐고 생각 될 수 있지만 따로 각 주제별로 접근하면 단순한 코딩입니다. 그걸 합쳐놓아서 복잡해 보일 뿐입니다. 위에 참고자료 3개의 포스팅한 내용을 한번 다시 읽고 이번 포스팅을 보시면 "그냥 합쳐놓은거네!" 이라고 말할 꺼에요.

1. IRremote + Servo모터 + LCD16x2 소스


IRremot 소스

#include <IRremote.h>
 
int IRpin = 11;  
IRrecv irrecv(IRpin);
decode_results results;

void setup()
{
  irrecv.enableIRIn(); // 리모콘 시작
}
void loop()
{
  if (irrecv.decode(&results)) //리모콘 누른값이 없다면 패스
    {
     
      "results.value의 키값을 사용할 예정";
            
     irrecv.resume(); // 다음값
    }
}

Servo모터 소스

#include <Servo.h> 
 
Servo servo; 
int SERVO_PIN = 10;

void setup() 
{ 
    servo.attach(SERVO_PIN); 
}

void loop() 
{
    "servo.write(180) 함수로 서모모터회전시킬 예정";
}

LCD16x2 소스

#include <LiquidCrystal.h>

//LiquidCrystal(rs, enable, d4, d5, d6, d7) 
LiquidCrystal lcd(3, 4, 9, 10, 11, 12);

void setup()
{
  lcd.begin(16,2);
  lcd.setCursor(0, 0);
  lcd.print("Servo Angle :");
  delay(1000);  
}

void loop() {
  lcd.setCursor(0, 1);
  "lcd.print("각도")함수로 각도값을 출력할 예정";
}

2. 회로도 구성


  • 준비물 : IR Sensor, 리모콘, 서보모터 1개, LCD16x2 1개, 저항 220옴 1개, 가변저항 10k옴 1개, 아두이노우노, 뻥판
  • 내용 : 리모콘을 눌러서 서보모터를 회전 시키고 LCD16x2에 회전각을 출력시켜보자.


참 쉽죠. 회로도 별거 없지요. 그러면 욕먹겠지요. 지금까지 배운 서보모터 제어, LCD16x2 제어, 리모콘 제어로 총 3편의 포스팅 내용을 토대로 간단히 회로도를 구성해 봤습니다. 개별적으로 포스팅한 내용 보시면 이 회로도 구성은 별거 없다는 것을 아마 아실꺼에요. 다 합쳐놓으니깐 좀 복잡해 보일뿐이죠. 실상은 간단합니다.

만약 LCD16x2 I2C 로 출력한다면 더 간단해 집니다. 해보고 싶은분은 LCD16x2 I2C 제어편을 보시고 실제로 실험을 도전하세요.

3. 코딩


  • 사용함수 : 리모콘 함수, 서보모터 함수, LCD16x2 함수
  • 내용 : 리모콘를 화살표키를 눌러 10도씩 회전 시키고 그 결과를 LCD 16x2로 출력해보자.

[ 리모콘 함수 ]

  • irrecv.enableIRIn() : 리모콘 시작
  • irrecv.decode(&results) : 키가 눌러진지 확인
  • irrecv.resume() : 다음값

[ 서보모터 함수 ]

  • servo.attach(사용핀) : 사용핀을 서보모터 제어로 사용
  • servo. write(각도) : 각도로 회전시킨다.

[ LCD16x2 함수 ]

  • lcd.begin(가로,세로) : 화면나누기
  • lcd.setCursor(0, 0) : LCD16x2 모니터의 커서의 위치
  • lcd.print(출력값) : LCD16x2에 값을 출력

설계

1. 리모콘의 화살표 위아래 버턴만 사용한다.
2. 리모콘의 키값을 읽는다.

if (irrecv.decode(&results)) {                     
  키가 눌러졌다면 => results.value 값에 키값이 있겠죠
}

3. 각도변수에 화살표 키값을 위아래 누를때 증가/감소 시켜서 각도값을 만들어 낸다.

    if(리모콘키값 == 증가키값){  
      if(m_Angle<180){ //0~180도로 제한으로180도 이상 증가하면 안되니깐 각도 증가는 180보다 작아야함
        m_Angle=m_Angle+10; or m_Angle+=m_Angle;  =>  같은 표현
      }          
    }
    else if(리모콘키값 == 감소키값){ //0~180도 제한으로 0도 이하로 감소하면 안되니간 각도 감소는 0보다 커야함 
      if(m_Angle>0){
        m_Angle=m_Angle-10; or m_Angle-=m_Angle; => 같은 표현
      }      
    }  
<

4. LCD16x2에 각도값을 출력한다.

lcd.setCursor(0, 1); //두번째 줄에 첫칸에 커서 위치
lcd.print("                ");  //두전째 줄에 이전 기록된 값을 지우기 공백 16칸
lcd.setCursor(0, 1); //두번째 줄에 첫칸에 커서 위치한 이유 데이터를 기록할 위치를 다시 잡아줘야함
lcd.print(m_Angle); // 두번째 첫칸에 각도값이 출력됨

5. 서보모터에 각도변수에 저장된 값을 출력(회전) 한다.

servo.write(m_Angle);

간단하지요.

전체적으로 코딩을 하면

#include <IRremote.h>
#include <LiquidCrystal.h>
#include <Servo.h> 

//1
Servo servo; 
int SERVO_PIN =10;
int m_Angle = 0;

//2
//LiquidCrystal(rs, enable, d4, d5, d6, d7) 
LiquidCrystal lcd(2, 3, 4, 5, 6, 7);

//3
int RECV_PIN = 11;
IRrecv irrecv(RECV_PIN);
decode_results results;

void setup()
{  
  servo.attach(SERVO_PIN);
  
  irrecv.enableIRIn(); 
  
  lcd.begin(16,2);
  lcd.setCursor(0, 0);
  lcd.print("Servo Angle : ");
  
  
}

void loop() {
  
  if (irrecv.decode(&results)) {                     
   
     // Serial.println(results.value);
    
    if(results.value == 0xFD50AF){  //16601263 or 0xFD50AF
      if(m_Angle<180){
        m_Angle=m_Angle+10; // m_Angle+=m_Angle;
      }          
    }
    else if(results.value == 0xFD10EF){ //16584943 or 0xFD10EF
      if(m_Angle>0){
        m_Angle=m_Angle-10; // m_Angle-=m_Angle;
      }      
    }  
   
    lcd.setCursor(0, 1);
    lcd.print("                ");   
    lcd.setCursor(0, 1);
    lcd.print(m_Angle);
  
    
    irrecv.resume(); 
  }
  servo.write(m_Angle);
  delay(100);
}

4. 결과


리모콘 화살 키를 눌렀을때 결과입니다. 각도 0도에서 각도 10도가 증가하고 LCD16x2에 각도 값이 출력되고 서보모터는 10도 회전하는 장면 입니다.


동영상으로 제가 실험한 장면을 녹화 했네요. 어떤식으로 진행 했는지 보시면 그리 어렵지 않을꺼에요. 3개의 주제를 다 합치고 또 코딩하는 부분 까지 전부 다 녹화하다보니 꽤 길어졌네요. 동영상이 너무 길기 때문에 결과 이미지만 보시고 대충 이렇게 결과가 나오는 구나 정도로만 이해하시도 됩니다.


마무리


이번 포스팅은 응용편으로 각 하나의 부품을 제어하다 보면은 너무 쉽고 간단해서 이걸로 뭘 할 수 있지 생각하는 분들이 아마 있을거에요. 각 부품 포스팅 마다 예제소스도 너무 단순하게 표현해서 뭘 만들어 볼려는 분들은 부품간 연결이 이미지화 하기가 좀 힘드실꺼에요. 그래서 이번에는 좀 고려하고 코딩해야하는 부분이 있는데 그걸 다 무시하고 읽기 편하게 코딩을 배치했네요. 원래를 코딩을 하실때 이렇게 배치하시면 안되고요. 변수와 객체를 체계적으로 선언하고 코딩도 각 경계의 구분을 명확하게 하고 너무 한곳에 집중 코딩하기 보다는 외부함수로 빼서 분산시켜서 loop()함수 내 코딩은 최소화로 간결하게 가독성을 높이는 코딩을 해야 합니다. 그걸 다 무시하고 우선 초보분들은 가독성 위주로 코딩 했네요.

오늘은 서보모터랑 LCD16x2 모니터를 사용하여 리모콘을 제어했네요. 여러분들오 한번 지금까지 포스팅한 부품 중에 맘에 드는 것을 골라서 한번 만들어 보세요. LED가 맘에 든다면 LED 부품을 사용하여 숫자키 값에 따라서 전원 스위치로 활용하여 LED를 켜고 끄기를 할 수 있습니다. 또는 RGB LED로 리모콘 키값을 이용해서 컬러값을 만들어내서 RGB LED의 색을 다양하게 표현하는 실험을 할 수 있을거에요.

그외도 많지만 한번 상상의 나래를 펼쳐 보세요.


댓글()

[아두이노] 리모콘(IRremote) 제어

IOT/아두이노|2019. 3. 14. 09:00

[아두이노] 리모콘(IRremote) 제어



오늘은 리모콘을 사용하는 법을 배워보도록 하겠습니다. 제가 참고했던 라이브러리 파일이 있는 github의 자료입니다. 링크된 곳에 들어가시면 기본 데모 예제가 있습니다. 그걸 이용해서 실험하시면 됩니다.

1. IRremote 라이브러리 설치


가상시뮬레이터에서 기본적으로 제공됩니다. 라이브러리 소스를 가져올 필요 없이 라이브러리 파일을 인크루드만 하면 됩니다.

#include <IRremote.h>

실제로 한다면 라이브럴리 파일을 추가해야겠죠.


라이브러리 매니저에서 IRremote 치시면 라이브러리가 여러게 보이는데 젤 위에거로 그냥 설치하시면 됩니다.

설치하시면 예제에 들어가시면 IRremote라는 예제가 추가되었고 거기에 IRrecvDemo 예제를 실행 시키면 됩니다.


아래 소스를 보면 대충 예제가 11번 핀을 IR Sensor핀으로 사용했네요. 그리고 두개의 IRrecv, decode_results 클래스가 있는데 소스상으로 대충 보면은 IR Sensor값을 IRrecv 클래스에서 받아서 뭔가 처리를 할꺼고 decode_results 클래스는 변수명을 보면 먼지는 모르지만 센서의 결과값이 여기에 있겠구나 하고 유추가 되실 꺼에요. println()에 results.value 값을 찍은거 봐서는 리모콘를 누른 값이겠군 하고 생각하시면 됩니다.


대충 이런느김으로 라이브러리를 살펴보실때 클래스 객체명이나 또는 함수 등을 통해서 데모 예제들을 통해 유추해보면서 한번 코딩을 해독 해보세요. 데모 예제들은 심풀하게 나와 있어서 어렵지 않게 해독이 될꺼에요.

2. 회로도 구성


  • 준비물 : IR Sensor, 리모콘, 아두이노우노
  • 내용 : 키를 누르면 그 값을 시리얼모니터로 출력


참 쉽죠. 측정하는 센서를 사용할때는 전원(Vcc,Gnd)핀과 Dout핀으로 구성된다고 했죠. 아두이노에 해당 핀에 연결만 하면 도비니다. 가상 리모콘은 따로 할 건 없고요. 아주 간단하게 구성 됩니다.

3. 코딩


  • 사용함수 : irrecv.enableIRIn(), irrecv.decode()
  • 내용 : 리모콘를 누른 값을 아두이노 IDE의 시리얼 모니터로 간단히 출력하자.
  • 참고출처 : http://playground.arduino.cc/Code/Keypad

  • irrecv.enableIRIn() : 리모콘 시작
  • irrecv.decode(results) : 키가 눌러진지 확인
  • irrecv.resume() : 다음값

데모소스를 기반으로 해서 수정해 보자. 우선 위에 링크된 라이브러리 파일이 있는 github로 가보세요. 이 라이브러리를 만드신 분의 라이브러리 IRremot.h 파일을 열어 볼 수 있습니다. 거기에 decode_results 클래스를 열어 보시면 여기에 리모콘의 정보가 어떤것을 저장하는지 잘 나와 있습니다.

보시면, decode_results 클래스에서는 decode_type, address. value, bits, *rawbuf, rawlen, overflow 변수들을 담고 있습니다. 여기서 decod_type, bits, value 값만 찍어보도록 하죠.

#include <IRremote.h>
 
int IRpin = 11;  
IRrecv irrecv(IRpin);
decode_results results;

void setup()
{
  Serial.begin(9600);
  irrecv.enableIRIn(); // 리모콘 시작
}

void loop()
{
  if (irrecv.decode(&results)) //리모콘 누른값이 없다면 패스
    {
     Serial.print("results.decode_type : "); //리모콘 타입
     Serial.println(results.decode_type);
     
     Serial.print("results.bits : "); //키값길이
     Serial.println(results.bits);
     
     Serial.print("results.value : "); //키값
     Serial.println(results.value);
     
     Serial.print("results.value, HEX : "); //키값을 16진수로변환
     Serial.println(results.value, HEX);
   
     irrecv.resume(); // 다음값
    }
}

대충 11번 핀을 IR Sensor 값을 읽는 핀입니다. IRrecv 객체변수를 선언할대 인자값으로 IR핀을 넘겨줍니다. 그리고 우리가 리모콘의 키값을 얻어야 하기때문에 리모콘 키값이 저장되어 있는 decode_results 클래스 객체 변수를 선언합니ㅏㄷ.

시리얼모니터로 출력하기 때문에 지난시간에 배웠듯이 begin(9600)으로 시리얼통신 시작을 선언하고 시리얼출력을 하기 위해서 print(), println()함수를 사용했네요.

리모콘을 사용하기 위해서 enableIRin()함수를 리모콘을 사용을 선언해야겠죠.

loop()함수에서 irrecv.decode(&reults)함수로 리모콘 키가 눌러졌는지 확인 작업을 합니다. 눌러졌다면 results 객체 변수에는 키값이 저장되어 있고 리모콘 decode_type, bits, value 정보를 print(), println()함수로 출력하는 로직입니다.

여기서 중요한 것은 value값입니다. 이 값을 토대로 리모콘을 눌렀을때 누른키값의 동작을 아두이노에서 명령을 내리면 됩니다.

핵심은results.value 이거 입니다.
다른 건 몰라도 대충

  irrecv.enableIRIn(); // 리모콘 시작
    
    if(irrecv.decode(&results)){ //리모콘 눌렀는지 확인
     if(results.value==0xFD48B7) {
       Serial.println(" key : 3"")
     }
     irrecv.resume(); //다음값
 } 

리모콘을 3번키를 누르면 3번키값이 'FD48B7' 16진수 값입니다. 3번키가 눌러졌다면 프린트문장을 수행되겠죠. 만약에 특정 동작을 프린트문장 대신에 모터를 돌린다거나 불을 끈다든가 다른 동작을 수행하도록 할 수 도 있겠죠.

다 빼고 부분만 빼내오니간 별거 없죠. 키값을 미리 알수가 없으니깐 실제로 실험하실때 리모톤의 기본 정보를 출력하시고 각 키값을 누러서 값을들 미리 적어놓고 그 값을 기준으로 제어 하시면 될꺼에요.

어려운 것은 없습니다.

4. 결과


리모콘 3번 키를 눌렀을때 결과입니다.


동영상은 실험과정을 담아 놨습니다. 보시고 따라해보세요. 소스를 복사한 곳은 위에 링크된 라이브러리 Github에 등록된 자료입니다. 위 링크된 곳을 미리 창을 열어 놓고 따라서 해보세요.

마무리


IRremote 라이브러리를 누군가 이미 만들어 놓았습니다. 우리는 단지 실험을 할때 그걸 이용할 뿐이죠. 리모콘을 제어하기 위해서 라이브러리를 개발 해야하는 소모적 시간을 줄일 수 있습니다. 아두이노를 다루는 가장 큰 장점은 이미 많은 라이브러리들이 오픈 되어 있어서 우리들은 창의적인 개발만 신경쓰면 됩니다. 뭘 만들지 그것만 여러분들이 상상의 나래만 펼치면 되고 기본 베이스 소스나 코딩은 오프라인으로 다 공개되어 있기 때문데 가져다가 잘 응용하여 표현만 하시면 됩니다.

처음부터 하나하나 프로그래머로써 코딩을 한다면 리모콘 하나를 다루기 위해서 엄청난 많은 시간을 소비해야 합니다. 전문적 지식도 필요하고요. 짧게는 몇일 길게는 한달이 걸리지 모릅니다.
하지만 IRremote 라이브러리를 이미 오프라인으로 공개되어 있어서 누구나 쉽게 이걸 활용할 수 있습니다. 물론 상업적으로는 저작권의 문제가 있겠지만 단지 실험을 목적으로 우리가 다룬다면 사실 부담없이 실험 할 수 있습니다.

이 라이브러리를 통해서 리모콘 키값을 얻을 수 있게 되었고 그 키값을 통해서 해당 동작만 설계만 우리들이 표현하면 되니깐 개발 시간을 엄청 단축시킬 수 있습니다. 참 좋은 세상에 살고 있는 거지요.
암튼 리모콘의 키값을 읽을 수 있게 되었으니깐 이 키로 뭘 제어할지 상상의 나래를 펼쳐 보세요


댓글()

[아두이노] Serial 통신 제어

IOT/아두이노|2019. 3. 13. 09:00

[아두이노] Serial 통신 제어



원래 시리얼 통신은 초반부에 자세히 소개 했어야 했는데 프로세싱+아두이노 연결을 하면서 그냥 넘어가면 안될 것 같아서 이제서야 소개하게 되었네요. 센서 부품를 다룰때 그 결과를 출력하는데 많이 사용하기 때문에 매우 중요합니다. 나중에 블루투스 통신에서도 사용하니깐 위에 참고출처를 링크한 아두이노 홈페이지의 레퍼런스를 잘 살펴보시기 바래요. 오늘은 시리얼통신 기본만 소개하겠습니다.

1. Serial 통신



그림에서 보듯이 아두이노 IDE 시리얼모니터에서 아두이노로 "123"이란 값을 보내게 됩니다. 그리고 나서 아두이노는 이 값을 읽어서 다시 아두이노 IDE로 데이터를 보내고 시리얼모니터로 그 값을 출력합니다.

[ 기본 소스 ]

void setup() {
  Serial.begin(9600);    //시리얼 통신 9600 통신속도로 시작
}
void loop() {
  
  if (Serial.available() > 0) { //데이터가 수신되는지 확인
    char ch = Serial.read(); //1byte 읽음
    Serial.println(ch); //1byte 읽은거 출력
  }
}

기본 소스의 주석을 다 달았습니다. 주석만 읽으시면 대충 어떻게 통신이 이루어지는 아시겠지요.

위 이미지에서 123을 보내면 아두이노에서 1, 2, 3 이렇게 하나씩 아두이노 IDE에 출력되는 이유가 과연 뭘까요. 이것은 123을 전송했는데 byte 단위로 읽고 char(문자형) 변수 ch에 1byte 문자가 저장되는데 "123" 값에서 순차적으로 '1','2','3'을 저장하고 순자척으로 Serial.println('1'), Serial.println('2'), Serial.println('3') 이렇게 출력하게 됩니다.
이걸 정수형으로 받으면

2. 시리얼 통신 함수


기본

  • Serial.begin(통신속도) : 시리얼통신 시작. 기본통신속도는 9600을 많이 사용합니다.
    많은 아두이노 시리얼통신에서 9600을 사용합니다.
    통신속도 : 300, 600, 1200, 2400, 4800, 9600, 14400, 19200, 28800, 38400, 57600, 115200
  • Serial.end() : 시리얼통신 중단
  • Serial.available() : 데이터 도착했는지 확인
  • Serial.println(데이터) : 데이터를 상대쪽으로 전송(출력)
  • while(!Serial) : 통신 연결 확인(연결 안되면 무한 루프를 돌면서 연결을 기다림)

위 함수중에서 abailable()함수만 좀 더 살펴보도록 하죠.

예제)


데이터가 들어왔는지 상태값을 확인하는게 available()함수라고 했죠. 그러면 정확히 어떤값이 들어있는지 시리얼 모니터에 출력을 해봤습니다. 'a'라는 문자를 보내면 아두이노는 available()함수에 수신이 되면 수신되었다고 1의 상태값을 갖게 됩니다. 그러면 if문이 참이고 첫라인에서 출력이 '1'이 나오게 됩니다. 그다음 read()함수로 데이터를 읽고 정상적으로 시리얼모니터로 'a'가 두번째라인에 출력 됩니다. 그러면 데이터를 read()함수로 읽었다면 다음 available()함수의 상태값은 데이터를 읽었기 때문에 지워지고 상태값은 '0'이 됩니다 즉, read()으로 읽고 나면은 abailable()은 초기화 상태가 되는 것이죠. 그래서 세번째 라인이 0이 출력 됩니다.
쉽게 말해서, 데이터가 전송되면 available() 함수는 '1'의 상태가 되고 데이터를 읽고나면 '0'의 상태가 됩니다. 아래 그림을 보시면 좀 더 쉽게 이해가 되시겠지요.


begin(), available(), read(), print(), println() 정도만 우선 알아두시고 아래에서 소개하는 함수들 읽기와 출력에 대해 몇가지 소개만 하고 나머지는 레퍼런스를 참고해 주세요. 나중에 센서값을 어떻게 읽고 어떻게 출력할지 그 센서에 따라서 표현이 좀 달라지니깐요 레퍼런스를 꼭 봐주셔야 해요. 가상시뮬레이터에서는 위 5가지 함수만 기본 베이스로 이해하시고 사용하시면 쉽게 실험하실 수 있을꺼에요.


읽기

  • Serial.read() : 1byte 읽음
  • Serial.readBytes(buffer, length) : buffer에 length 길이만큼 읽음
  • Serial.readBytesUntil(character, buffer, length) : buffer에 length 길이만큼 읽어오는데 character가 끝문자 표시로 이문자가 있는 곳까지 읽어오게 됨.
    예) Serial.readBytesUntil('s', buffer, 10)일때 전송데이터 "100s1000"이면 buffer "100"이 첨에 저장되고 다음에 "1000"이 저장됨.
  • Serial.parseInt() : 정수형으로 읽기
  • Serial.parseFloat() : 실수형으로 읽기

출력

  • print(값) : 데이터를 출력하고 현재 라인에 커서가 그대로 유지됨.
  • println(값) : 데이터를 출력하고 새로운 라인으로 커서가 이동함.(Enter로 생각하시면됨)

주의 :

  • print(값) : 데이터를 아스키코드값 바꿔서 전송.
  • wirte(값) : 데이터를 그대로 전송.


위 그림에서 보는 것 처럼 print()함수는 '7'이란 값을 전송할때 '55'라는 아스키코드값으로 전송하고 그 값이 출력되는 걸 보실꺼에요. 하지만 write()함수는 '7'을 그래도 전송하고 받는쪽에서는 '7'을 아스키코드값으로 여기고 출력된다는 차이점을 보입니다. 둘 차이를 잘 비교하세요.


마무리


그외도 여러 함수들이 있는데 오늘 소개한 함수들도 몇개를 제외하고는 잘 사용하지 않습니다. 가상시뮬레이터에서는 그냥 단순하게 read(), print(), println()정도로 데이터를 읽거나 쓰는 정도만 합니다.

- if(Serial.available()) {  }
- while(!Serial){  }
- Serial.read()
- Serial.print()
- Serial.println()

이정도 함수만 외워두시면 기본적인 통신을 할 수 있습니다. 나중에 센서를 이용한 특정값이나 부품을 제어하는 통신을 할 경우에는 레퍼런스를 살펴 보셔야 겠지만 우선은 시리얼통신이 어떻게 이루어지는지만 살펴보세요.

댓글()