[아두이노] NeoPixel로 글자패턴 만들기

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

[아두이노] NeoPixel로 글자패턴 만들기 


오늘의 주제는 NeoPixel를 이용해서 지난 시간에 배운 3x3x3 CUBE LE와 8x8 Dot Matrix에서 사용한 코딩기법을 복습하는 시간을 가져보도록 하겠습니다. 기본 출력 로직을 회로도에 맞춰서 만들어 놓으면 나머지 패턴 글자만 만드는 작업만 좀 고생 되겠지만 이 후 코딩은 글자 패턴를 만드는 작업뿐이 없으니깐 아마 편하게 코딩 할 수 있을꺼에요. 암튼 좀 어려울 수 있는 내용이지만 코딩을 처음 어떻게 시작해서 어떻게 진화한 코딩으로 바뀌는지 느끼는 시간이 되었으면 합니다. 코딩은 처음부터 정해진 코딩으로 뚝닥 완성되는게 아니라 출발은 아래 글처럼 하나의 글자를 증흥적으로 코딩해보고 다른 방식들을 찾아보면서 최종적으로 자신이 원하는 형태의 로직 형태를 찾아서 그 결과 로직을 토대로 다양한 글자들을 만들어 아래와 같은 결과물을 얻게되는데 이 과정을 잘 살펴 보셨으면 합니다. 완성된 코딩은 그렇게 까지 의미는 없습니다. 자신이 출발은 어디서 부터이고 그 출발에서 어떻게 변화 해가는지가 중요합니다. 아래의 코딩과정을 길게 나열한 것은 그 의미를 전달하기 위해서 입니다. 이 글을 쓰는 저자가 완성된 로직을 만들기 위해서 처음에 여기서부터 출발했고 어떤 과정을 거쳐서 여기까지 왔구나 정도만 이해하시면 됩니다.

코딩한 결과가 중요한 것이 아니라 코딩하는 과정이 무척 중요합니다.

1. NeoPixel 회로도 배치 고민


처음에는 위와 같은 순서대로 회로도를 연결했습니다. 문제는 위치에 대한 공간인지가 좀 불편하실 수 있으며 나중에 2차배열로 위치값을 정해야 하는데 순번이 햇갈릴 수 있기 때문에 이경우는 초보분들에게 혼동을 야기 할 것 같아서 포기했습니다.


그래서 옆으로 눞여 봤는데 홀수 행은 배열 형태를 잘 나타나는데 짝수행은 배열의 역순 위치 값으로 표기 되기 때문에 이 또한 혼동을 야기 할 것 같아서 포기했습니다.

최종적으로 선 연결을 복잡하게 한 대신에 배열 형태를 맞춰서 디자인 했습니다. 그러면 어떻게 했는지 회로도 구성에서 살펴보도록 하겠습니다.

2. 회로도 구성


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


회로도의 경우는 자신의 원하는 형태로 연결하시면 됩니다. 단, 연결하실 때는 어떤식으로 코딩할지에 대해서 고려하시고 연결하셔야 해요. 연결하고 나서 그 연결된 형태를 고려해서 코딩을 해도 되지만 반대로 코딩 할 로직을 어느정도 틀이 잡혀 있으면 그 틀에 맞게 회로도를 배치하는 것도 좋습니다. 제가 코딩을 소개하는 방향에서는 2차배열을 사용하기 때문에 최대한 위치에 대한 순서를 맞춰서 회로도의 선을 연결했네요.

3. 코딩 과정


  • 사용함수 : Adafruit_NeoPixel(), neopixel.begin(), neopixel.setPixelColor(), neopixel.show(), neopixel.clear()
  • 내용 : "STEEM CODINGMAN" 글자를 만들어 출력하자.
  • 참고소스 :

함수

  • 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를 다 꺼지겠지요.

[기본 소스]

#include <Adafruit_NeoPixel.h>

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

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

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

순서대로 NeoPixel에 불이 들어오는지 테스트를 하였습니다.

2) 하나의 'S' 글자패턴 만들기


위 표에서 보는 것 처럼 2차 배열이 있으면 'S'라는 글자를 만들면 해당 위치에 가운데 표에서 보는 것처럼 색이 칠해지겠죠. 그 위치값을 가지고 글자패턴을 만든다면 아래와 같이 표현이 됩니다.

const byte p[7][5]= {
        {0,1,2,3,0},
        {5,0,0,0,9},
        {10,0,0,0,0},
        {0,16,17,18,0},
        {0,0,0,0,24},
        {25,0,0,0,29},
        {0,31,32,33,0}};

즉, 배열에다가 위치값을 아예 저장하는 방식입니다. 출력을 해볼까요.

   for(int i=0;i<7;i++){
      for(int j=0;j<5;j++){
          if(p[i][j]!=0)neopixel.setPixelColor(p[i][j], 255, 0, 0);    
      }     
    }
    neopixel.show();
    delay(1000);

위 코딩으로 2차원 배열로 행7과 열 5로 순차적으로 0이 아닌 위치에 neopixel.setPixelColor()함수로 색을 세팅합니다. 2차 for문이 다 돌고나면 'S'라는 글자의 위치에는 색이 다 세팅되어 있겠죠. 그리고 나서 neopixel.show()함수로 실제 NeoPixel로 출력하게 됩니다. 그러면 'S'라는 글자를 볼 수 있습니다.

#include <Adafruit_NeoPixel.h>

const byte neopixelPin = 3; 
Adafruit_NeoPixel neopixel = Adafruit_NeoPixel(35, neopixelPin, NEO_GRB + NEO_KHZ800);
const byte p[7][5]= {
        {0,1,2,3,0},
        {5,0,0,0,9},
        {10,0,0,0,0},
        {0,16,17,18,0},
        {0,0,0,0,24},
        {25,0,0,0,29},
        {0,31,32,33,0}};
void setup() {  
   neopixel.begin();   
}       
void loop(){        
    for(int i=0;i<7;i++){
      for(int j=0;j<5;j++){
          if(p[i][j]!=0)neopixel.setPixelColor(p[i][j], 255, 0, 0);    
      }     
    }
    neopixel.show();
    delay(1000);
    neopixel.clear();
    neopixel.show();
    delay(100);  
}       

2) 글자패턴 다른식으로 접근

사실 위 코딩으로 's' 글자를 만들때 위치값을 해도 되지만 '0'과 '1'로 표현을 해보도록 하겠습니다. 글자가 들어가는 위치를 '1'로 그렇지 않는 곳은 '0'으로 표현 하겠습니다.

const byte p[7][5]= 
        {{0,1,1,1,0},
        {1,0,0,0,1},
        {1,0,0,0,0},
        {0,1,1,1,0},
        {0,0,0,0,1},
        {1,0,0,0,1},
        {0,1,1,1,0}};

그러면 처음 코딩한 곳에서 어디를 수정해야 할까요. 바로 for문안에 문장이겠죠.

   byte y=0;
   for(int i=0;i<7;i++){
      for(int j=0;j<5;j++){
          if(p[i][j]!=0)neopixel.setPixelColor(y+j, 255, 0, 0);   
      }
            y+=5; // 행위치값을 5씩 증가,
    }

그냥 i+y하면 안됩니다. 그 이유는 i가 0일때는 j에 더하면 '0,1,2,3,4'가 나오지만 i가 1일때는 '1,2,3,4,5'가 나오게 됩니다. 원래는 '5,6,7,8,9'이렇게 나와야 하는데 말이죠. 이렇게 나오게 하기 위해서 y라는 변수를 행 위치변수로 하나 만들어서 행의 위치를 나타내는 변수가 필요합니다. 행인 i가의 for문이 증가할때마다 행위치변수인 y은 5씩 증가하게 표현함으로 2차 배열의 위치를 정확히 지정할 수 있게 됩니다.

첫번째 코딩한 것에서 위 표현을 변경할 부분에 삽입하셔서 바꾸시면 됩니다. 변수선언과 for문안에 로직과 y변수 선언하시면 됩니다.

3) '0'과 '1'의 표현이면 '0B00000000'로 표현

'0'과 '1'이 나왔으면 어떤 표현이 떠오르지 않나요. 바로 byte형으로 하나의 숫자 값으로 '0B00000000'로 표현이 떠오르셨다면 지난 내용을 제대로 공부하셨던 분일꺼에요, 이 표현을 통해 열을 표현한 5개의 배열공간을 하나의 숫자로 표현이 가능해 집니다.

01110 + 000 => 0B01110000

뒤에다 '000' 붙였는지만 앞에다 붙이셔도 됩니다. 실험은 뒤에다 붙여서 실험했는데 이 표현은 제 마음이니깐요 이게 꼭 해야하는 건 아니니깐 앞이든 뒤든 상관이 없고 나중에 for문을 표현할 때 앞이냐 뒤냐에 따라서 표현이 좀 달라지기는 하지만 그렇게 큰 의미는 없는 표현입니다.

const byte p[7]= {
0B01110000,
0B10001000,
0B10000000,
0B01110000,
0B00001000,
0B10001000,
0B01110000 }

이렇게 표현되는데 아두이노라이브러리에 bit를 읽는 함수를 기억하실지 모르겠네요. bitRead(x,n)함수가 있습니다. X라는 숫자에서 n번째 위치의 bit값을 가져오는 함수입니다.

위의 패턴배열변수에서

0B01110000

이값을 가져올려면

const byte p=0B01110000;
for(int j=7;j>2;j--){
     bitRead(p, j);
}

이렇게 표현해야합니다. 읽는 위치는 역순이니깐요. 오른쪽끝이 0번째가 됩니다. 그런데 왼쪽부터 순서대로 하나식 가져올려면 7번 위치의 자리에 값부터 순서대로 읽어와야겠죠. 총 5개니깐 j는 2보다 커야하고요. 순서대로 가져오니깐 'j--'로 감소연산자로 1씩 감소시켜야 겠죠. 이렇게 표현해야 왼쪽부터 순서대로 1bit씩 가져오게 됩니다.

로직은 이러헥 되겠죠.

    byte x=0; //열위치
   byte y=0;//행위치
   for(int i=0;i<7;i++){
      for(int j=7;j>2;j--){
          if(bitRead(p[i,j)]!=0)neopixel.setPixelColor(y+j, 255, 0, 0);   
                x+=1;
      }
            x=0;
            y+=5; // 행위치값을 5씩 증가,
    }

여기서 열의 위치값을 j로 할 수 없습니다. j가 7부터 시작하기 때문이지요. 그렇기 때문에 따로 x변수로 열도 위치변수를 만들어 줘야 합니다. j가 반복수행 할 때 x를 1씩 증가하게 하고 j가 5번 반복이 끝나면 x를 초기화 해야합니다. 두번째 행의 위치를 처음부터 다시 찾아야 하니깐요.

변수 선언과 이 로직을 처음 코딩한 소스에서 삽입하고 변경하시면 됩니다. 따로 전체소스는 안올리겠습니다.

4) 'S' 글자 16진수화

0B01110000 = 0x70

const byte p[7] ={0x70,0x88,0x80,0x70,0x08,0x88,0x70};

이렇게 한줄로 대폭 줄일 수 있습니다. 단, 힘든 16진수 계산이 필요하지만요. 따로 16진수로 출력되는 프로그램을 만드셔도 됩니다. 해당 2진수 값이 저장된 배열을 for문을 이용해서 출력을 16진수로 바꾸시면 됩니다. 참고로 10진수로 변경하셔도 됩니다.

5) 'STEEM CODING MAN' 글자패턴을 만들기


글자 한개를 만드는 것도 힘들데 총 14자의 글자를 만든다고 생각하니깐 앞이 깜깜하시죠. 이제 글자를 쉽게 만드는 꼼수를 찾아냈는데 알려드리도록 하겠습니다.



위 블로그에 올리신 분께서 젤 하단에 이런 프로그램을 링크 걸어 놓았네요. 글자 패턴을 만들기 힘드신분들을 위한 배려인 듯 합니다. 저도 이런거 프로그램을 만든분 없는지 구글 검색을 통해서 찾아보다가 우연히 발견했네요. 그리고, 쉽게 글자를 만들었네요.


7행의 패턴 글자이기 때문에 마지막 8행의 16진수 값은 필요 없으니깐 지우시면 됩니다. 이렇게 글자를 만들면 다음과 같은 패턴 배열변수가 완성되었네요.

이런식으로 글자들을 14자를 완성하면 이렇게 간단히 표현됩니다.

const byte p[14][7]={ //글자 패턴이 14개임
  {0x70,0x88,0x80,0x70,0x08,0x88,0x70}, //S
    {0xF8,0x20,0x20,0x20,0x20,0x20,0x20}, //T
    {0xF8,0x80,0x80,0xF0,0x80,0x80,0xF8}, //E
    {0xF8,0x80,0x80,0xF0,0x80,0x80,0xF8}, //E
    {0x88,0xD8,0xA8,0x88,0x88,0x88,0x88}, //M
    {0x70,0x88,0x80,0x80,0x80,0x88,0x70}, //C
    {0x70,0x88,0x88,0x88,0x88,0x88,0x70}, //0
    {0xF0,0x88,0x88,0x88,0x88,0x88,0xF0}, //D
    {0x70,0x20,0x20,0x20,0x20,0x20,0x70}, //I
    {0x88,0x88,0xC8,0xA8,0x98,0x88,0x88}, //N
    {0x70,0x88,0x80,0xB8,0x88,0x88,0x70}, //G
    {0x88,0xD8,0xA8,0x88,0x88,0x88,0x88}, //M
    {0x70,0x88,0x88,0xF8,0x88,0x88,0x88}, //A
    {0x88,0xD8,0xA8,0x88,0x88,0x88,0x88} //M
    };

이 글자가 14개의 패턴 글자가 나오니깐 기존 소스에서 14번 for문을 한번 더 돌려야 겠죠.

for(int k=0;k<14;k++){

        기존 2차for문 로직;
        y=0; // 하나의 패턴 글자가 끝나니깐 y행위치 변수값을 초기화 해야겠죠.
}

최종적으로 코딩을 하면

#include <Adafruit_NeoPixel.h>

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


const byte p[14][7]={ //글자 패턴이 14개임
    {0x70,0x88,0x80,0x70,0x08,0x88,0x70}, //S
    {0xF8,0x20,0x20,0x20,0x20,0x20,0x20}, //T
    {0xF8,0x80,0x80,0xF0,0x80,0x80,0xF8}, //E
    {0xF8,0x80,0x80,0xF0,0x80,0x80,0xF8}, //E
    {0x88,0xD8,0xA8,0x88,0x88,0x88,0x88}, //M
    {0x70,0x88,0x80,0x80,0x80,0x88,0x70}, //C
    {0x70,0x88,0x88,0x88,0x88,0x88,0x70}, //0
    {0xF0,0x88,0x88,0x88,0x88,0x88,0xF0}, //D
    {0x70,0x20,0x20,0x20,0x20,0x20,0x70}, //I
    {0x88,0x88,0xC8,0xA8,0x98,0x88,0x88}, //N
    {0x70,0x88,0x80,0xB8,0x88,0x88,0x70}, //G
    {0x88,0xD8,0xA8,0x88,0x88,0x88,0x88}, //M
    {0x70,0x88,0x88,0xF8,0x88,0x88,0x88}, //A
    {0x88,0xD8,0xA8,0x88,0x88,0x88,0x88} //M
    };

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

void loop() {
  byte x=0;
  byte y=0;
  for(int k=0;k<14;k++){ //글자 패턴의 숫자만큼 반복
    for(int i=0;i<7;i++){
        for(int j=7;j>2;j--){
            if(bitRead(p[k][i],j)!=0) neopixel.setPixelColor(y+x, 0, 255, 0);    
            x+=1;
        }
        x=0;//열 초기화
        y+=5;//열이 5칸이니 행이 증가할때마다 5가 더해져야함
    }
    y=0; //행 초기화
    neopixel.show();
    delay(100);   
    neopixel.clear(); 
    neopixel.show();
    delay(100);
  }
}

이렇게 완성이 되었습니다. 코드를 이해 못하셔도 좋습니다. 제가 처음에 어떻게 시작을 했고 어떤 과정을 통해서 여기까지 왔는지의 과정만 이해하시면 됩니다. 어떤 코딩을 할 때 처음부터 완벽한 코딩은 없습니다. 하나씩 차근 차근 원리를 코딩하고 그것을 끊임없이 다른 방법은 없는지 찾으면서 코딩을 하는게 중요합니다.

그 의미를 잘 이해해 주셨으면 합니다.

4. 결과


원래는 'STEEM CODINGMAN'이라는 14자를 출력해야 하는데 동영상 녹화를 논스톱으로 하다보니깐 'O'자를 실수로 빼먹었네요. 13자만 출력되었네요. 어쩐지 글자가 다 출력되고 다시 loop()함수가가 시작하기 전에 딜레이 시간이 생겨서 가상시뮬레이터 상에서 발생하는 렉인 줄 알았는데 13자 출력하고 반복 패턴글자 1글자 분량의 동작을 더 수행하다 보니 결과는 없고 1글자 분량의 동작은 수행만큼의 딜레이 현상처럼 보였네요. 수정할려다가 그러면 영상을 편집해야 하기 때문에 그냥 패턴글자를 'STEEM CDINGMAN'로 출력하는걸로 끝내겠습니다.

참고로, 공개된 회로도 링크 주소로 가셔서 결과를 실제 돌려보시면 정상적으로 'STEEM CODINGMAN'이란 글자가 출력되도록 수정했으니깐 참고하세요.


만드는 과정이 좀 길었네요. 다 자르고 뒤에 결과만 편집할려다가 그냥 올렸습니다. 젤 마지막의 글자 패턴 결과만 보시거나 아니면 실제로 공개된 회로도 링크 주소로 가셔서 직접 돌려보셔도 됩니다.

마무리


NeoPixel이 재밌다고 했는데 갑자기 코딩수업이 되어서 어렵게 느껴졌을 듯 싶네요. 간단한 단순 패턴을 만들어서 화려하게 꾸미고 싶었는데 글자가 너무 땡겨서 글자 출력으로 하고 말았네요. 사실 특정한 문자로 출력하는 것은 좀 코딩이 복잡하고 불편합니다. 단순하게 홀짝 출력이나 릴레이 색상출력이나 큰틀에서의 한두가지 패턴을 색상과 덧붙여서 출력하는게 더 재미 있고 화려한데 말이죠.
이부분은 이 포스팅을 읽으시는 분들의 몫으로 남겨 두겠습니다. 혹시 땡겨서 밤에 만들어 내일 올릴 수 도 있겠지만 오늘밤 땡기냐 안땡기냐로 기분에 맡겨야 겠습니다.
갑자기 코딩이 좀 어렵게 됐는데 이것 말고 간단히 색상 별 한두가지 패턴으로 지그재그로 꾸며 보세요. 그리고 위치를 달리해서 어떤 독특한 모양을 만들어 보세요.
아니면 이 출력하는 원리를 잘 생각해보셔서 어느 부품하고 연결하면 좋을지 상상의 나래를 한번 펼쳐보세요.


댓글()