RS's Travel & Electronic

u8glib : 문자를 로그라이크 게임 처럼 움직이기 본문

시퀀스와 아두이노

u8glib : 문자를 로그라이크 게임 처럼 움직이기

RS 2021. 7. 13. 18:37
SMALL

지난번 버튼 함수 포스팅의 다섯 번째 함수와 U8 GLIB라는 라이브러리를 사용해서 

128x64 OLED디스플레이에 띄운 문자를 마치 로그라이크 게임 플레이하듯이 움직여봅니다.

 

아래는 준비물입니다.

 

 

128x64 OLED I2C 디스플레이

https://mechasolution.com/shop/goods/goods_search.php?searched=Y&log=1&skey=all&hid_pr_text=&hid_link_url=&edit=&sword=OLED&x=0&y=0&_idn= 

한국에서 바로 살 수 있는곳 중에서는 메카 설루션이 좀 싼 거 같습니다.

개중에는 디스플레이 윗줄은 노란색, 아랫줄은 흰색 등 투칼라로 만들어진 디스플레이가 있습니다. 색 변경은 불가능한 모델이니까 미리 확인하고 사세요. 저는 I2C만 사용하지만 SPI를 선택하고 싶다면 그것도 상관없습니다.

 

택트 버튼 6개

https://www.devicemart.co.kr/goods/view?no=36627 

어디에서나 파는 물건이고 개당 몇십 원 밖에 안 하니 아무데서나 사도 좋습니다만, 4핀 달린 버튼은 쳐다보지도 마세요. 실험하기 불편합니다. 브레드보드에 잘 안 끼워져요. 2핀 달린 놈이 최곱니다.

 

아두이노 mini or uno or MEGA or ETC

아두이노 종류로 쓸 수 있는거면 뭐든 좋습니다.

메카 설루션, 디바이스 마트, 아이씨 뱅큐 등 어디서든 팔고 있으니 비교해서 제일 싸게 살 수 있는 곳에서 사세요.

 

그리고, U8glib 라이브러리.

U8glib.zip
1.02MB

압축 풀어서 아두이노 설치 폴더/libraries에 넣어 줍니다.

 

 

저는 미리 만들어놓은 실험용 보드들을 사용하겠습니다.

별다른 건 아니고, 뭔가 실험해 보고 싶은 내용이 생겼을 때 즉각 해볼 수 있도록 핀을 확장하거나 통합하거나 해둔 겁니다. 이렇게 하면 그때그때마다 시간을 절약할 수 있죠. 기성품 중에 이렇게 만들어놓은 보드가 있기도 하지만 제가 직접 커스텀을 해야 저한테 맞는 파츠를 쓸 수 있습니다.

 

 

 

연결입니다.

 

문자를 움직이거나 변화를 줄 여섯개의 버튼은 각각 아두이노의

 

UP 2
RIGHT 3
LEFT 4
DOWN 5
아스키값 증가 6
아스키값 감소 7

 

번에 연결해 주세요. 

 

 

OLED와 아두이노를 연결하는건 I2C냐 SPI냐, 그리고 아두이노가 무슨 종류냐에 따라 다르겠죠.

I2C 일 경우 다음과 같습니다.

 

보드 SDA SCL
Uno, Ethernet A4 A5
Mega2560 20 21
Leonardo 2 3
Due 20 21

 

 

SPI일 경우는 이렇게 하시고요.

 

보드 MOSI MISO SCK SS - slave SS - master
Uno 11 또는 ICSP-4 12 또는 ICSP-1 13 또는 ICSP-3 10 -
MEGA 51 또는 ICSP-4 50 또는 ICSP-1 52 또는 ICSP-3 53 -
Leonardo ICSP-4 ICSP-1 ICSP-3 - -
Due ICSP-4 ICSP-1 ICSP-3 - 4, 10, 52

 

근데, 사실 전 SPI 통신을 사용하는 모듈을 사용해 본 적이 없습니다.

핀을 4개나 연결해야 된다는 게 너무 불편해서요.

SPI가 통신속도가 빠르다고는 하지만 솔직히 잘 모르겠고요.

위 연결법은 여러 고수들의 블로그에도 똑같이 나와있는 거니까 저대로 연결하면 잘 될 겁니다. 

 

 

코드입니다.

 

#include "U8glib.h"

U8GLIB_SSD1306_128X64 u8g(U8G_I2C_OPT_NONE);  // I2C / TWI 

#define     CHATTERINGTIME 5
#define     CONTINUESEC    500
#define     CONTINUECYCLE  200 
#define     BUTTON         2

#define BT_U 2
#define BT_R 3
#define BT_L 4
#define BT_D 5
#define BT_1 6
#define BT_2 7


int x_ray = 10;
int y_ray = 10;

char mob[2] = "A";


/* 방향 위 */
void btPush_U()
{
  static boolean btUStat = false;
  static boolean labtUStat = true;
  static unsigned long labtUTime = 0;

  static boolean nowPushed = false;
  static unsigned long pushStartTime = 0;
  static unsigned long continueStartTime = 0;
  
  boolean readingStat = digitalRead(BT_U);
  
  if (nowPushed) {
    if (readingStat) nowPushed = false;
  
    if ( (nowPushed) && ( (millis() - pushStartTime) > CONTINUESEC ) ) {
      if ( (millis() - continueStartTime) > CONTINUECYCLE ){
        if (0 < (y_ray-5)) y_ray -= 5;
        draw();
        continueStartTime = millis();
      }
    }
  }
  
  if (readingStat != labtUStat) labtUTime = millis();
  if ( (millis() - labtUTime) > CHATTERINGTIME ) {
    if (readingStat != btUStat) {
      btUStat = readingStat;
      if (btUStat == false) {
        nowPushed = true;
        pushStartTime = millis();
        if (0 < (y_ray-5)) y_ray -= 5;
        draw();
      }
    }
  }
  labtUStat = readingStat;
}


/* 방향 우 */
void btPush_R()
{
  static boolean btRStat = false;
  static boolean labtRStat = true;
  static unsigned long labtRTime = 0;

  static boolean nowPushed = false;
  static unsigned long pushStartTime = 0;
  static unsigned long continueStartTime = 0;
  
  boolean readingStat = digitalRead(BT_R);
  
  if (nowPushed) {
    if (readingStat) nowPushed = false;
  
    if ( (nowPushed) && ( (millis() - pushStartTime) > CONTINUESEC ) ) {
      if ( (millis() - continueStartTime) > CONTINUECYCLE ){
        if (127 > (x_ray+5)) x_ray += 5;
        draw();
        continueStartTime = millis();
      }
    }
  }
  
  if (readingStat != labtRStat) labtRTime = millis();
  if ( (millis() - labtRTime) > CHATTERINGTIME ) {
    if (readingStat != btRStat) {
      btRStat = readingStat;
      if (btRStat == false) {
        nowPushed = true;
        pushStartTime = millis();
        if (127 > (x_ray+5)) x_ray += 5;
        draw();
      }
    }
  }
  labtRStat = readingStat;
}


/* 방향 좌 */
void btPush_L()
{
  static boolean btLStat = false;
  static boolean labtLStat = true;
  static unsigned long labtLTime = 0;

  static boolean nowPushed = false;
  static unsigned long pushStartTime = 0;
  static unsigned long continueStartTime = 0;
  
  boolean readingStat = digitalRead(BT_L);
  
  if (nowPushed) {
    if (readingStat) nowPushed = false;
  
    if ( (nowPushed) && ( (millis() - pushStartTime) > CONTINUESEC ) ) {
      if ( (millis() - continueStartTime) > CONTINUECYCLE ){
        if (0 < (x_ray-5)) x_ray -= 5;
        draw();
        continueStartTime = millis();
      }
    }
  }
  
  if (readingStat != labtLStat) labtLTime = millis();
  if ( (millis() - labtLTime) > CHATTERINGTIME ) {
    if (readingStat != btLStat) {
      btLStat = readingStat;
      if (btLStat == false) {
        nowPushed = true;
        pushStartTime = millis();
        if (0 < (x_ray-5)) x_ray -= 5;
        draw();
      }
    }
  }
  labtLStat = readingStat;
}


/* 방향 아래 */
void btPush_D()
{
  static boolean btDStat = false;
  static boolean labtDStat = true;
  static unsigned long labtDTime = 0;

  static boolean nowPushed = false;
  static unsigned long pushStartTime = 0;
  static unsigned long continueStartTime = 0;
  
  boolean readingStat = digitalRead(BT_D);
  
  if (nowPushed) {
    if (readingStat) nowPushed = false;
  
    if ( (nowPushed) && ( (millis() - pushStartTime) > CONTINUESEC ) ) {
      if ( (millis() - continueStartTime) > CONTINUECYCLE ){
        if (63 > (y_ray+5)) y_ray += 5;
        draw();
        continueStartTime = millis();
      }
    }
  }
  
  if (readingStat != labtDStat) labtDTime = millis();
  if ( (millis() - labtDTime) > CHATTERINGTIME ) {
    if (readingStat != btDStat) {
      btDStat = readingStat;
      if (btDStat == false) {
        nowPushed = true;
        pushStartTime = millis();
        if (63 > (y_ray+5)) y_ray += 5;
        draw();
      }
    }
  }
  labtDStat = readingStat;
}


/* 아스키 증가 */
void btPush_1()
{
  static boolean bt1Stat = false;
  static boolean labt1Stat = true;
  static unsigned long labt1Time = 0;

  static boolean nowPushed = false;
  static unsigned long pushStartTime = 0;
  static unsigned long continueStartTime = 0;
  
  boolean readingStat = digitalRead(BT_1);
  
  if (nowPushed) {
    if (readingStat) nowPushed = false;
  
    if ( (nowPushed) && ( (millis() - pushStartTime) > CONTINUESEC ) ) {
      if ( (millis() - continueStartTime) > CONTINUECYCLE ){
        mob[0]++;
        draw();
        continueStartTime = millis();
      }
    }
  }
  
  if (readingStat != labt1Stat) labt1Time = millis();
  if ( (millis() - labt1Time) > CHATTERINGTIME ) {
    if (readingStat != bt1Stat) {
      bt1Stat = readingStat;
      if (bt1Stat == false) {
        nowPushed = true;
        pushStartTime = millis();
        mob[0]++;
        draw();
      }
    }
  }
  labt1Stat = readingStat;
}


/* 아스키 감소 */
void btPush_2()
{
  static boolean bt2Stat = false;
  static boolean labt2Stat = true;
  static unsigned long labt2Time = 0;

  static boolean nowPushed = false;
  static unsigned long pushStartTime = 0;
  static unsigned long continueStartTime = 0;
  
  boolean readingStat = digitalRead(BT_2);
  
  if (nowPushed) {
    if (readingStat) nowPushed = false;
  
    if ( (nowPushed) && ( (millis() - pushStartTime) > CONTINUESEC ) ) {
      if ( (millis() - continueStartTime) > CONTINUECYCLE ){
        mob[0]--;
        draw();
        continueStartTime = millis();
      }
    }
  }
  
  if (readingStat != labt2Stat) labt2Time = millis();
  if ( (millis() - labt2Time) > CHATTERINGTIME ) {
    if (readingStat != bt2Stat) {
      bt2Stat = readingStat;
      if (bt2Stat == false) {
        nowPushed = true;
        pushStartTime = millis();
        mob[0]--;
        draw();
      }
    }
  }
  labt2Stat = readingStat;
}


void draw() 
{
  u8g.firstPage();  
  do {
    u8g.setFont(u8g_font_6x10);
    u8g.drawStr( x_ray, y_ray, mob); 
  } while( u8g.nextPage() );
}

void setup() 
{
  u8g.setColorIndex(255);     // white
  pinMode (BT_L, INPUT_PULLUP);
  pinMode (BT_U, INPUT_PULLUP);
  pinMode (BT_D, INPUT_PULLUP);
  pinMode (BT_R, INPUT_PULLUP);
  pinMode (BT_1, INPUT_PULLUP);
  pinMode (BT_2, INPUT_PULLUP);

  draw();
  Serial.begin (9600);
  
}

void loop() 
{
  btPush_U();
  btPush_R();
  btPush_L();
  btPush_D();
  btPush_1();
  btPush_2();


}

지금까지 시제품을 제작할 때도, 그냥 실험을 할때도 버튼 채터링을 무시하기 위한 시간 상수의 이름을 THISTIME이라고 선언해왔습니다. 별 이유가 있는 건 아니고, 그냥 어쩌다 지은 이름을 지금까지 계속 이어왔었죠. 근데 다른 사람이 보기에 이게 무슨 상수인지 모르겠다 싶어서 이번에 이름을 다시 지었습니다. CHATTERINGTIME 으로요.

 

버튼을 누르면 해당되는 동작을 하고 버튼을 누른 채로 0.5초 이상 지속하면 0.2초 간격으로 해당 동작을 반복합니다. 이 동작 간격을 주지 않으면 너무 빨리 동작이 연속으로 일어나서 사람 눈으로 파악하기가 어려워지니까요.

0.5초는 CONTINUESEC라는 상수로, 0.2초는 CONTINUECYCLE이란 상수로 선언되어 있습니다.

 

아스키 값 변화를 이용해서 출력된 문자를 변화시킬 수 있습니다. 계속 누르다 보면 영문자를 넘어서 기호가 출력되고, 어느 정도 값을 넘어가면 아무것도 출력 안되게 됩니다. OLED에서 어떤 문자들까지 출력할 수 있는지 확인하실 수 있을 겁니다.

 

 

 

스케치는 아두이노 프로 미니 5V/16 Mhz 중국산 짭퉁에 업로드했고, 용량은 위와 같이 사용되었습니다.

원래 U8 GLIB가 용량을 좀 많이 잡아먹습니다.

버튼 누름 지속에 관련된 변수들이 좀 많아서 동적 메모리를 좀 많이 사용했네요. 누름지속에 관련된 부분을 빼고 일반 푸시버튼 함수로 구현한다면 메모리를 훨씬 아낄 수 있겠습니다.

좀 짧은 분량의 로그라이크 게임 정도는 구현될 것 같습니다.

 

 

 

동작 영상입니다.

 

이후로도 제가 U8 GLIB로 128x64 OLED 디스플레이를 사용할 때의 코드들을 몇 가지 더 올려볼까 합니다.

LIST
Comments