[아두이노] 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를 마무리하도록 하겠습니다.


댓글()