아두이노로 LED matrix 제어하기 #11 : 슬라이드 효과 만들기

2018. 3. 7. 15:10

Arduino/Display

Arduino LedControl Library : 슬라이드 효과 내기

이전 글에서 비트 연산자를 통하여 개별 LED 단위로 제어하는 방법을 살펴 보았습니다.

이번 글에서는, 이 비트 연산자를 이용하여 문자 슬라이드(시프트) 효과를 구현하겠습니다. 이전에 모듈 단위로 시프트했던 방법보다 더욱 부드러운 움직임을 만들 수 있습니다.

모듈내에서 시프트 하기

우선, 하나의 모듈 내에서 시프트 효과를 처리해 보겠습니다.

#include "LedControl.h"
LedControl lc = LedControl(12,11,10,4);
//
void setup() {
  lc.shutdown(0, false);
  lc.shutdown(1, false);
  lc.shutdown(2, false);
  lc.shutdown(3, false);
  //
  lc.setIntensity(0,8);
  lc.setIntensity(1,8);
  lc.setIntensity(2,8);
  lc.setIntensity(3,8);
  //
  lc.clearDisplay(0);
  lc.clearDisplay(1);
  lc.clearDisplay(2);
  lc.clearDisplay(3);
}
//
void loop() {
//
}

예제 기본 틀입니다.

먼저, LedControl 라이브러리를 사용하기 위해 해당 헤더 파일을 포함시켰고, LedControl 개체(lc)를 하나 생성하였습니다.

setup() 함수 내에서 필요한 초기화 작업을 수행하는데, 모듈의 개수가 총 4개입니다. 이전 글보다 하나 더 늘렸습니다. 이 기본 틀에 필요한 코드를 추가해 나가겠습니다.

void loop() {
  //
  byte data = B00000001;
  //
  lc.setRow(0, 0, data);
  lc.setRow(1, 0, data);
  lc.setRow(2, 0, data);
  lc.setRow(3, 0, data);
  //
}

data 변수의 값을 이용해 각 모듈에 동일한 모양을 출력합니다. byte type의 변수 data는 마지막 비트만 "1"이므로, 같은 위치의 LED만 on되고, 나머지는 off될 것입니다. setRow() 함수를 이용해서 각 모듈의 첫 번째 라인, 오른쪽 마지막 LED만 켜진 상태가 됩니다.

void loop() {
  //
  byte data = B00000001;
  lc.setRow(0, 0, data);
  lc.setRow(1, 0, data);
  lc.setRow(2, 0, data);
  lc.setRow(3, 0, data);
  //
  delay(1000);
  lc.setRow(0, 0, data << 1);
  lc.setRow(1, 0, data << 1);
  lc.setRow(2, 0, data << 1);
  lc.setRow(3, 0, data << 1);
  //
  delay(1000);
}

위 코드는 비트 시프트 연산을 한 번 수행하여 왼 쪽으로 하나 이동하여 LED를 켜는 것입니다. 이런 식으로 몇 번 더 수행하면 오른쪽 끝에서 왼쪽 끝으로 슬라이드하는 듯 효과를 줄 수 있습니다.

void loop() {
  //
  byte data = B00000001;
  for (int i = 0; i < 8; i++) {
    lc.setRow(0, 0, data << i);
    lc.setRow(1, 0, data << i);
    lc.setRow(2, 0, data << i);
    lc.setRow(3, 0, data << i);
    delay(1000);
  }
}

for문을 이용하여 위에서 말한 내용을 표현한 코드입니다. 모듈내에서 비트 시프트 연산자를 이용하여 위와 같이 슬라이드 효과를 낼 수 있습니다.

모듈과 모듈 사이의 시프트 효과 내기

이제 오른쪽 모듈에서 왼쪽 모듈로 데이터를 넘겨 주어 모듈간의 시프트 효과를 표현해 보겠습니다.

왼쪽으로 시프트한다면, 2번 3번 모듈의 왼 쪽에서 첫 번째 LED의 데이터가 왼 쪽 모듈의 오른쪽 마지막 LED로 가야 합니다. 이를 위해서 다음과 같이 처리합니다.

  • 오른쪽 모듈의 데이터를 오른쪽으로 7번 시프트한다.( data >> 7)
  • 왼 쪽 모듈은 왼쪽으로 1번 시프트한다.(data << 1)
  • 두 데이터를 비트 OR 연산으로 합쳐 준다.

오른쪽 모듈의 0번 값이 왼쪽 모듈의 7번 값으로 들어가야 하므로 자리를 맞추기 위해서 비트시프트 7번(data >> 7)을 수행하고, 이를 왼쪽 모듈과 비트 OR 연산을 하여 합성하면 되는데, 이 때, 앞쪽 모듈의 데이터는 먼저 시프트가 되어 있어야 합니다.

void loop() {
  //
  byte data0 = B00000001;
  byte data1 = B10000001;
  byte data2 = B10000001;
  byte data3 = B10000001;
  byte temp;
  //
  lc.setRow(0, 0, data0);
  lc.setRow(1, 0, data1);
  lc.setRow(2, 0, data2);
  lc.setRow(3, 0, data3);
  delay(1000);
  //
  data0 = data0 << 1; // 첫 번째 모듈은 왼쪽으로 1칸 이동
  //
  temp = data1 >> 7;
  data0 = data0 | temp;
  data1 = data1 << 1;
  //
  temp = data2 >> 7;
  data1 = data1 | temp;
  data2 = data2 << 1;
  //
  temp = data3 >> 7;
  data2 = data2 | temp;
  data3 = data3 << 1;
  //
  lc.setRow(0, 0, data0);
  lc.setRow(1, 0, data1);
  lc.setRow(2, 0, data2);
  lc.setRow(3, 0, data3);
  //
  delay(1000);
}

간단히 테스트 하기 위해서 데이터를 4개 준비하고, 오른쪽 모듈의 첫 번째 LED값을 전달하기 위한 임시 변수(temp) 하나를 선언하였습니다. 비트 연산자들을 이용하여 원하는 대로 슬라이드 효과를 내는 것을 결과로 확인하실 수 있습니다.

#include "LedControl.h"
LedControl lc = LedControl(12,11,10,4);
//
void setup() {
  lc.shutdown(0, false);
  lc.shutdown(1, false);
  lc.shutdown(2, false);
  lc.shutdown(3, false);
  //
  lc.setIntensity(0,8);
  lc.setIntensity(1,8);
  lc.setIntensity(2,8);
  lc.setIntensity(3,8);
  //
  lc.clearDisplay(0);
  lc.clearDisplay(1);
  lc.clearDisplay(2);
  lc.clearDisplay(3);
}
//
void loop() {
  //
  byte data[4] = {B00000001, B10000001, B10000001, B10000001};
  //
  int i, j;
  for (i = 0; i < 4; i++) {
    lc.setRow(i, 0, data[i]);
  }
  delay(1000);
  //
  for (i = 0; i < 24; i++) {
    data[0] = (data[0] << 1) | (data[1] >> 7);
    data[1] = (data[1] << 1) | (data[2] >> 7);
    data[2] = (data[2] << 1) | (data[3] >> 7);
    data[3] = (data[3] << 1);
    //
    for (j = 0; j < 4; j++) {
      lc.setRow(j, 0, data[j]);
    }
    delay(1000);
  }
}

바로 위 코드를 배열과 for문을 이용해서 다시 구성한 소스입니다. 또, 임시 변수를 사용하지 않는 코드입니다.

문자 슬라이드 효과 만들기

여기 까지의 코드는 출력할 데이터를 직접 시프트 처리하여 출력하였습니다. 하지만 출력 데이터가 길어지면, 시프트 연산을 전체 데이터에 수행하는 건 너무 비효율적이고, 또 원래의 데이터가 변경되지 않길 바랄 수 도 있습니다. 이를 해결하기 위해서, 이전 글에서 사용했던 버퍼 변수를 만들어서 처리하겠습니다.

이번 예제는 알파벳 26글자를 오른쪽에서 왼쪽으로 슬라이드 하는 소스입니다. 처음 화면은 비어 있고, 첫 번째 글자부터 한 열씩 모듈 가장 오른쪽에 나타나도록 처리합니다.

#include "LedControl.h"
LedControl lc = LedControl(12,11,10,4);
//
byte data[26][8] = {
  {
  B00000000,
  B00111100,
  B01100110,
  B01100110,
  B01111110,
  B01100110,
  B01100110,
  B01100110
},{
  B00000000,
  B01111100,
  B01100110,
  B01100110,
  B01111100,
  B01100110,
  B01100110,
  B01111100
},{
  B00000000,
  B00111100,
  B01100110,
  B01100000,
  B01100000,
  B01100000,
  B01100110,
  B00111100
},{
  B00000000,
  B01111100,
  B01100110,
  B01100110,
  B01100110,
  B01100110,
  B01100110,
  B01111100
},{
  B00000000,
  B01111110,
  B01100000,
  B01100000,
  B01111100,
  B01100000,
  B01100000,
  B01111110
},{
  B00000000,
  B01111110,
  B01100000,
  B01100000,
  B01111100,
  B01100000,
  B01100000,
  B01100000
},{
  B00000000,
  B00111100,
  B01100110,
  B01100000,
  B01100000,
  B01101110,
  B01100110,
  B00111100
},{
  B00000000,
  B01100110,
  B01100110,
  B01100110,
  B01111110,
  B01100110,
  B01100110,
  B01100110
},{
  B00000000,
  B00111100,
  B00011000,
  B00011000,
  B00011000,
  B00011000,
  B00011000,
  B00111100
},{
  B00000000,
  B00011110,
  B00001100,
  B00001100,
  B00001100,
  B01101100,
  B01101100,
  B00111000
},{
  B00000000,
  B01100110,
  B01101100,
  B01111000,
  B01110000,
  B01111000,
  B01101100,
  B01100110
},{
  B00000000,
  B01100000,
  B01100000,
  B01100000,
  B01100000,
  B01100000,
  B01100000,
  B01111110
},{
  B00000000,
  B01100011,
  B01110111,
  B01111111,
  B01101011,
  B01100011,
  B01100011,
  B01100011
},{
  B00000000,
  B01100011,
  B01110011,
  B01111011,
  B01101111,
  B01100111,
  B01100011,
  B01100011
},{
  B00000000,
  B00111100,
  B01100110,
  B01100110,
  B01100110,
  B01100110,
  B01100110,
  B00111100
},{
  B00000000,
  B01111100,
  B01100110,
  B01100110,
  B01100110,
  B01111100,
  B01100000,
  B01100000
},{
  B00000000,
  B00111100,
  B01100110,
  B01100110,
  B01100110,
  B01101110,
  B00111100,
  B00000110
},{
  B00000000,
  B01111100,
  B01100110,
  B01100110,
  B01111100,
  B01111000,
  B01101100,
  B01100110
},{
  B00000000,
  B00111100,
  B01100110,
  B01100000,
  B00111100,
  B00000110,
  B01100110,
  B00111100
},{
  B00000000,
  B01111110,
  B01011010,
  B00011000,
  B00011000,
  B00011000,
  B00011000,
  B00011000
},{
  B00000000,
  B01100110,
  B01100110,
  B01100110,
  B01100110,
  B01100110,
  B01100110,
  B00111110
},{
  B00000000,
  B01100110,
  B01100110,
  B01100110,
  B01100110,
  B01100110,
  B00111100,
  B00011000
},{
  B00000000,
  B01100011,
  B01100011,
  B01100011,
  B01101011,
  B01111111,
  B01110111,
  B01100011
},{
  B00000000,
  B01100011,
  B01100011,
  B00110110,
  B00011100,
  B00110110,
  B01100011,
  B01100011
},{
  B00000000,
  B01100110,
  B01100110,
  B01100110,
  B00111100,
  B00011000,
  B00011000,
  B00011000
},{
  B00000000,
  B01111110,
  B00000110,
  B00001100,
  B00011000,
  B00110000,
  B01100000,
  B01111110
}};
//
void setup() {
  lc.shutdown(0, false);
  lc.shutdown(1, false);
  lc.shutdown(2, false);
  lc.shutdown(3, false);
  //
  lc.setIntensity(0,8);
  lc.setIntensity(1,8);
  lc.setIntensity(2,8);
  lc.setIntensity(3,8);
  //
  lc.clearDisplay(0);
  lc.clearDisplay(1);
  lc.clearDisplay(2);
  lc.clearDisplay(3);
}
//

우선, 위와 같이 출력할 데이터를 추가하였습니다. 영문 대문자 26글자입니다. 나머지는 동일합니다.

//
void loop() {
  //
  int i, j, k;
  byte buffers[4+1][8];
  //
  for (i = 0; i < 4; i++) {
    for (j = 0; j < 8; j++) {
      buffers[i][j] = B00000000;
    }
  }

5번 행을 보면, buffers 라는 바이트 타입 배열 변수를 선언하였습니다. 첫 번째 첨자 값이 "4+1"인 이유는, 모듈 4개와 데이터 처리를 위한 임시 변수 하나를 추가로 더 선언하기 위함입니다. buffers 변수 4개는 각각 모듈 4개에 출력할 데이터를 갖고 있고, 나머지 하나는 다음 글자를 가져와서 한 열씩 시프트해주는 역할을 합니다.

변수 선언 후에, "0"값으로 모두 초기화해주었습니다.

// 문자 슬라이드 처리
  for (i = 0; i < 26; i++) { // 전체 글자수 만큼 반복
    memcpy(&buffers[4], &data[i], sizeof(data[0]));
    //
  }

문자 슬라이드를 처리하는 가장 바깥쪽 for문입니다. 글자수 만큼 26번 반복합니다.

for문 안쪽의 memcpy() 함수에 의해 매번 한 글자씩 buffers 마지막 변수에 저장합니다. 그리고, 이어질 코드에서 한 열씩 시프트해서 처리하게 됩니다.

// 문자 슬라이드 처리
  for (i = 0; i < 26; i++) { // 전체 글자수 만큼 반복
    memcpy(&buffers[4], &data[i], sizeof(data[0]));
    //
    for (j = 0; j < 8; j++) { // 한 글자는 8열이므로, 글자당 8번 수행
      for (k = 0; k < 8; k++) { // 한 글자는 8행
        // 슬라이드 처리
      }
      for (k = 0; k < 8; k++) { // 슬라이드 처리후 출력
        // 출력 코드
      }
    }
  }

슬라이드 처리 관련 전체 구조입니다. 글자수 만큼 26번, 글자당 8번 시프트 처리해야 하고, 한 글자를 출력하기 위해선 8번 행을 출력해야 합니다.

#include "LedControl.h"
LedControl lc = LedControl(12,11,10,4);
//
byte data[26][8] = {
  {
  B00000000,
  B00111100,
  B01100110,
  B01100110,
  B01111110,
  B01100110,
  B01100110,
  B01100110
},{
  B00000000,
  B01111100,
  B01100110,
  B01100110,
  B01111100,
  B01100110,
  B01100110,
  B01111100
},{
  B00000000,
  B00111100,
  B01100110,
  B01100000,
  B01100000,
  B01100000,
  B01100110,
  B00111100
},{
  B00000000,
  B01111100,
  B01100110,
  B01100110,
  B01100110,
  B01100110,
  B01100110,
  B01111100
},{
  B00000000,
  B01111110,
  B01100000,
  B01100000,
  B01111100,
  B01100000,
  B01100000,
  B01111110
},{
  B00000000,
  B01111110,
  B01100000,
  B01100000,
  B01111100,
  B01100000,
  B01100000,
  B01100000
},{
  B00000000,
  B00111100,
  B01100110,
  B01100000,
  B01100000,
  B01101110,
  B01100110,
  B00111100
},{
  B00000000,
  B01100110,
  B01100110,
  B01100110,
  B01111110,
  B01100110,
  B01100110,
  B01100110
},{
  B00000000,
  B00111100,
  B00011000,
  B00011000,
  B00011000,
  B00011000,
  B00011000,
  B00111100
},{
  B00000000,
  B00011110,
  B00001100,
  B00001100,
  B00001100,
  B01101100,
  B01101100,
  B00111000
},{
  B00000000,
  B01100110,
  B01101100,
  B01111000,
  B01110000,
  B01111000,
  B01101100,
  B01100110
},{
  B00000000,
  B01100000,
  B01100000,
  B01100000,
  B01100000,
  B01100000,
  B01100000,
  B01111110
},{
  B00000000,
  B01100011,
  B01110111,
  B01111111,
  B01101011,
  B01100011,
  B01100011,
  B01100011
},{
  B00000000,
  B01100011,
  B01110011,
  B01111011,
  B01101111,
  B01100111,
  B01100011,
  B01100011
},{
  B00000000,
  B00111100,
  B01100110,
  B01100110,
  B01100110,
  B01100110,
  B01100110,
  B00111100
},{
  B00000000,
  B01111100,
  B01100110,
  B01100110,
  B01100110,
  B01111100,
  B01100000,
  B01100000
},{
  B00000000,
  B00111100,
  B01100110,
  B01100110,
  B01100110,
  B01101110,
  B00111100,
  B00000110
},{
  B00000000,
  B01111100,
  B01100110,
  B01100110,
  B01111100,
  B01111000,
  B01101100,
  B01100110
},{
  B00000000,
  B00111100,
  B01100110,
  B01100000,
  B00111100,
  B00000110,
  B01100110,
  B00111100
},{
  B00000000,
  B01111110,
  B01011010,
  B00011000,
  B00011000,
  B00011000,
  B00011000,
  B00011000
},{
  B00000000,
  B01100110,
  B01100110,
  B01100110,
  B01100110,
  B01100110,
  B01100110,
  B00111110
},{
  B00000000,
  B01100110,
  B01100110,
  B01100110,
  B01100110,
  B01100110,
  B00111100,
  B00011000
},{
  B00000000,
  B01100011,
  B01100011,
  B01100011,
  B01101011,
  B01111111,
  B01110111,
  B01100011
},{
  B00000000,
  B01100011,
  B01100011,
  B00110110,
  B00011100,
  B00110110,
  B01100011,
  B01100011
},{
  B00000000,
  B01100110,
  B01100110,
  B01100110,
  B00111100,
  B00011000,
  B00011000,
  B00011000
},{
  B00000000,
  B01111110,
  B00000110,
  B00001100,
  B00011000,
  B00110000,
  B01100000,
  B01111110
}};
//
void setup() {
  lc.shutdown(0, false);
  lc.shutdown(1, false);
  lc.shutdown(2, false);
  lc.shutdown(3, false);
  //
  lc.setIntensity(0,8);
  lc.setIntensity(1,8);
  lc.setIntensity(2,8);
  lc.setIntensity(3,8);
  //
  lc.clearDisplay(0);
  lc.clearDisplay(1);
  lc.clearDisplay(2);
  lc.clearDisplay(3);
}
//
void loop() {
  //
  int i, j, k;
  byte buffers[4+1][8];
  //
  for (i = 0; i < 4; i++) {
    for (j = 0; j < 8; j++) {
      buffers[i][j] = B00000000;
    }
  }
  // 문자 슬라이드 처리
  for (i = 0; i < 26; i++) { // 전체 글자수 만큼 반복
    memcpy(&buffers[4], &data[i], sizeof(data[0]));
    //
    for (j = 0; j < 8; j++) { // 한 글자는 8열이므로, 글자당 8번 수행
      for (k = 0; k < 8; k++) { // 한 글자는 8행
        // 슬라이드 처리
        buffers[0][k] = (buffers[0][k] << 1) | (buffers[1][k] >> 7);
        buffers[1][k] = (buffers[1][k] << 1) | (buffers[2][k] >> 7);
        buffers[2][k] = (buffers[2][k] << 1) | (buffers[3][k] >> 7);
        buffers[3][k] = (buffers[3][k] << 1) | (buffers[4][k] >> 7);
        buffers[4][k] = (buffers[4][k] << 1);
      }
      for (k = 0; k < 8; k++) { // 슬라이드 처리후 출력
        // 출력 코드
        lc.setRow(0, k, buffers[0][k]);
        lc.setRow(1, k, buffers[1][k]);
        lc.setRow(2, k, buffers[2][k]);
        lc.setRow(3, k, buffers[3][k]);
      }
      delay(10);
    }
  }
  delay(1000);
}

이 예제의 완성된 전체 소스입니다. 실행 모습은 아래 동영상을 참고하세요!

슬롯 머신 효과

이번에는 가로 방향이 아닌 세로 방향으로 슬라이드 효과를 주어, 슬롯 머신을 간단히 표현해 보겠습니다.

long randomNumber;
  randomSeed(analogRead(0));
  for (i = 0; i < 4; i++) {
    randomNumber = random(26);
    memcpy(&buffers[i], &data[randomNumber], sizeof(data[0]));
  }

슬롯 머신처럼, 출력할 문자를 랜덤으로 정하기 위해서 위와 같이 코드를 추가하였습니다. random() 함수는 인수로 주어진 숫자의 범위에서 난수(랜덤 넘버)를 구해주는데, 4번 행처럼 26이 인수라면 0부터 25까지의 범위에서 숫자가 선택됩니다.

그리고, 2번 행은 random() 함수의 초기값을 지정하는 randomSeed() 함수입니다. 이 씨드 값이 같거나 생략되면 결과는 항상 동일합니다. 예를 들어, 1, 2, 3, 4가 선택됐다면, 다음 번에도 역시 1, 2, 3, 4가 선택됩니다. 이 문제를 해결하려면 randomSeed() 함수에 항상 다른 숫자를 입력해 주어야 합니다. 현재, 아두이노의 아날로그 0번 핀은 아무 것도 연결되어 있지 않은 상태(floating)인데, 이 핀의 값을 읽으면 완벽하게 "0"값이 아닌 쓰레기값이 읽혀 집니다. 이 쓰레기 값이 늘 변하기 때문에 randomSeed() 의 인수로 사용하였습니다.

//
  bool rolling[4] = {true, true, true, true};
  int count[4] = {0, -5, -10, -15};
  //

위 코드는 멈추는 시점을 잡기 위해서 사용한 코드입니다.

for (j = 0; j < 4; j++) {
      count[j]++;
      if (count[j] > 10) rolling[j] = false;
    }

문자 슬라이드 처리 후에 위와 같은 코드를 삽입하였습니다. count[] 배열에 미리 저장된 수치가 되면 슬라이드를 멈추게 하는데, 각 모듈마다 멈추는 시간을 달리 하기 위해 넣은 코드입니다.

while(true) {
    for (i = 0; i < 8; i++) {
      for (j = 0; j < 4; j++) {
        if (rolling[j]) {
          byte change = buffers[j][7];
          //
          for (k = 6; k >= 0; k--) {
            buffers[j][k+1] = buffers[j][k];
          }
          buffers[j][0] = change;
        }
      }
      for (j = 0; j < 8; j++) {
        lc.setRow(0, j, buffers[0][j]);
        lc.setRow(1, j, buffers[1][j]);
        lc.setRow(2, j, buffers[2][j]);
        lc.setRow(3, j, buffers[3][j]);
      }
      delay(5);
    }
    for (j = 0; j < 4; j++) {
      count[j]++;
      if (count[j] > 10) rolling[j] = false;
    }
  }

세로 방향 슬라이드는 가로 방향보단 훨씬 간단합니다. 바이트 내에서 비트 연산을 할 필요가 없고, 출력 데이터(1 byte)를 그냥 다음 행에 출력하면 되기 때문입니다.

문자가 슬라이딩 도중 멈추지 않도록 하는 것과 첫 번째 모듈부터 차례로 멈추도록 하는 부분만 신경쓰면 되었습니다. 아래 동영상으로 결과를 확인하세요!

여기 까지, 가로 및 세로 방향으로 문자를 슬라이드 하는 방법을 알아 보았습니다. 이상입니다.

Comments