2024.03.26(am): 반응속도 게임

이 자료는 로봇만들기 재능봉사단 활동에 사용될 자료입니다.

키트 구매 링크

오늘은 반응속도 게임기를 만들어보려고 하는데요,
게임 방식은 유저 두명이 동시에 버튼을 누르면 도트 매트릭스에 X 표시에서 O표시로 바뀌게 되고 먼저 누르는 사람쪽으로 화살표 방향이 보여지게 되는 게임입니다.

오늘 사용할 부품은 아두이노 나노, 도트매트릭스, 푸쉬버튼 스위치입니다.

아두이노 나노는 아두이노의 작은 형태 중 하나로, 아두이노 UNO의 기능을 더 작고 경제적인 크기로 축소한 것입니다. 이는 여전히 ATmega328P 마이크로컨트롤러를 기반으로 하며, 디지털 및 아날로그 핀을 포함한 다양한 기능을 제공합니다.

아두이노 나노와 8×8 도트 매트릭스를 함께 사용할 때, 주로 도트 매트릭스를 제어하기 위해 디지털 핀과 SPI 통신을 사용합니다. 이를 위해 아두이노의 디지털 핀 및 SPI 핀에 맞게 연결하고, 코드를 작성하여 LED를 제어합니다.

따라서 아두이노 나노와 8×8 도트 매트릭스를 함께 사용할 때에도 기본적인 방법은 아두이노 UNO와 유사합니다. 단지 핀 매핑이나 특정한 라이브러리 사용 시에는 호환성을 고려하여 코드를 작성해야 합니다.

SPI 통신이란 ?

SPI(Serial Peripheral Interface)는 마이크로컨트롤러와 주변 장치 간에 통신을 위한 동기식 직렬 통신 프로토콜입니다. 이는 주로 디지털 통신을 필요로 하는 장치들 간에 데이터를 주고받을 때 사용됩니다. 주로 직렬 데이터 통신이 필요한 디바이스들 간에 통신할 때 사용되며, 대표적으로 센서, 디스플레이, 메모리 등이 해당됩니다.

SPI는 보통 마스터-슬레이브 구조로 동작합니다. 마스터는 통신을 제어하고, 슬레이브는 마스터의 요청에 응답합니다. SPI는 다음과 같은 주요 특징을 가지고 있습니다:

  1. 동기식 통신: 마스터가 클럭을 생성하고, 이를 기반으로 데이터를 송수신합니다. 이는 모든 디바이스들이 동일한 클럭을 사용하여 동시에 데이터를 주고받을 수 있도록 해줍니다.
  2. 전이중 통신: 마스터와 슬레이브 간에 동시에 데이터를 주고받을 수 있습니다.
  3. 풀-디플렉스 통신: 데이터를 동시에 송수신할 수 있어서 송신과 수신이 동시에 일어날 수 있습니다.
  4. 단일 마스터, 다중 슬레이브 구조: 한 개의 마스터가 여러 개의 슬레이브를 제어할 수 있습니다.

SPI는 보통 4개의 핀으로 구성되어 있습니다:

  • SCK (Serial Clock): 클럭 신호를 전송하는 핀으로, 마스터가 생성하고 슬레이브들이 이 클럭을 사용하여 데이터를 송수신합니다.
  • MOSI (Master Out Slave In): 마스터에서 슬레이브로 데이터를 전송하는 핀입니다.
  • MISO (Master In Slave Out): 슬레이브에서 마스터로 데이터를 전송하는 핀입니다.
  • SS (Slave Select): 특정 슬레이브를 선택하기 위한 핀으로, 마스터가 특정 슬레이브와 통신할 때만 활성화됩니다.

아두이노에서 SPI 통신은 SPI 라이브러리를 통해 구현할 수 있으며, 이를 사용하여 다양한 주변 장치들과 통신할 수 있습니다.

아두이노 - 8X8 도트 매트릭스 모듈 사용 : 네이버 블로그

8×8 도트 매트릭스는 아두이노와 같은 마이크로컨트롤러를 이용하여 다양한 시각적인 출력을 만들기 위해 사용되는 디지털 디스플레이입니다. 이러한 도트 매트릭스는 8개의 열과 8개의 행으로 구성되어 있으며, 각 행과 열의 교차점에는 LED가 있어서 총 64개의 LED로 구성됩니다.

8×8 도트 매트릭스는 다양한 프로젝트에 활용될 수 있으며, 주로 아래와 같은 목적으로 사용됩니다:

  1. 텍스트 표시: 간단한 메시지나 숫자를 표시할 수 있습니다. 이를 통해 시간, 온도, 습도 등의 정보를 표시할 수 있습니다.
  2. 그래픽 표시: 간단한 그래픽 패턴이나 아이콘을 표시할 수 있습니다. 이를 통해 게임, 시각적 효과 등을 구현할 수 있습니다.
  3. 애니메이션 표시: 여러 개의 이미지를 연속적으로 표시하여 애니메이션을 만들어낼 수 있습니다.
  4. 시각적 응용 프로그램: 빛 감지, 움직임 감지, 음향 등과 연계하여 다양한 시각적 응용 프로그램을 구현할 수 있습니다.

아두이노와 8×8 도트 매트릭스를 함께 사용할 때는 주로 LED를 켜고 끄는 방법을 제어하기 위해 특정 라이브러리를 사용합니다. 대표적으로는 Adafruit의 MAX7219 라이브러리나 LedControl 라이브러리 등이 있습니다. 이러한 라이브러리를 사용하면 간단한 코드로도 도트 매트릭스를 제어할 수 있습니다.

예를 들어, 아래는 8×8 도트 매트릭스에 “HELLO”라는 텍스트를 스크롤링하여 표시하는 아두이노 코드의 간단한 예시입니다:

#include <LedControl.h>

// 도트 매트릭스를 연결한 핀 번호 설정
const int dataInPin = 12;
const int clkPin = 11;
const int loadPin = 10;
const int numDevices = 1;

// LedControl 객체 생성
LedControl lc = LedControl(dataInPin, clkPin, loadPin, numDevices);

void setup() {
  // 도트 매트릭스 초기화
  lc.shutdown(0, false);
  // 최대 밝기 설정
  lc.setIntensity(0, 8);
  // 화면 클리어
  lc.clearDisplay(0);
}

void loop() {
  // "HELLO" 문자열 스크롤링
  scrollText("HELLO");
}

// 텍스트 스크롤링 함수
void scrollText(String text) {
  for (int i = 0; i < text.length() * 8; i++) {
    // 한 글자씩 이동하면서 표시
    for (int j = 0; j < 8; j++) {
      int column = (i + j) % (text.length() * 8);
      int charIndex = column / 8;
      int offset = column % 8;
      byte pattern = getCharPattern(text[charIndex]);
      lc.setRow(0, j, pattern << offset);
    }
    delay(100);
  }
}

// 각 문자에 대한 패턴을 반환하는 함수
byte getCharPattern(char c) {
  switch (c) {
    case 'H':
      return B11111111;
    case 'E':
      return B10010010;
    case 'L':
      return B10000000;
    case 'O':
      return B11000011;
    default:
      return B00000000;
  }
}

이 코드는 LedControl 라이브러리를 사용하여 “HELLO”라는 텍스트를 스크롤링하여 8×8 도트 매트릭스에 표시합니다. 도트 매트릭스에 문자를 표시하기 위해 각 문자에 대한 패턴을 정의하고, 이를 순서대로 이동시켜서 화면에 표시하는 방식으로 동작합니다.

MAX7219 또는 MAX7221과 같은 LED 드라이버 칩을 사용하는 경우 도트 매트릭스 모듈이 5개의 핀을 가지게 됩니다. 시리얼 인터페이스를 통해 아두이노 또는 다른 마이크로컨트롤러와 통신하여 여러 개의 LED를 제어할 수 있는 기능을 제공합니다.

다섯 개의 핀은 다음과 같은 역할을 합니다:

  1. VCC: 전원 공급 핀입니다. 전원을 공급하는 데 사용됩니다. 일반적으로 5V 또는 3.3V로 전원을 제공합니다.
  2. GND: 접지 핀입니다. 전원의 접지를 연결합니다.
  3. DIN (Data In): 데이터 입력 핀입니다. 이 핀을 통해 마이크로컨트롤러에서 데이터가 전송됩니다.
  4. CS (Chip Select) 또는 LOAD: 칩 선택 핀입니다. 이 핀은 칩을 활성화하고 비활성화하는 데 사용됩니다. 일반적으로 LOW로 설정되면 칩이 활성화되고, HIGH로 설정되면 비활성화됩니다.
  5. CLK (Clock): 클럭 핀입니다. 이 핀은 데이터를 시리얼로 전송하는 데 사용되는 클럭 신호를 제공합니다.

이렇게 되어 있으면 마이크로컨트롤러는 데이터를 DIN 핀을 통해 보내고, CLK 핀을 통해 클럭을 제공하여 시리얼로 데이터를 전송합니다. CS 핀은 칩을 선택하여 통신을 시작하거나 종료합니다. 이러한 방식으로, MAX7219 또는 MAX7221과 같은 드라이버 칩을 사용하여 복잡한 LED 디스플레이를 비교적 간단한 방식으로 제어할 수 있습니다.

자 이제 키트를 조립해봅시다.

아래와 같이 MDF 하드웨어 판이 있습니다.

사진처럼 조립해줍니다.

고무로 된 지지대로 고정시켜줍니다.

브레드보드와 아두이노를 아래와 같이 조립해주고, 케이블은 구멍을 통해 바깥으로 빼줍니다.

푸쉬버튼 스위치는 밑에 있는 너트를 분리하여 스위치를 위에서 아래로 넣어주고

아래에서 너트를 조여줍니다.

도트매트릭스 앞면 양면테이프를 벗겨줍니다.

구멍에 맞게 부착해준 뒤, 점퍼케이블을 연결해줍니다.

아래는 회로도입니다.

https://blog.naver.com/gongzipsa/222946922203

소스코드는 아래와 같은데 도트 매트릭스를 사용하기 위한 라이브러리를 다운로드 받아야합니다. 아두이노의 라이브러리 매니저를 이용해서 다운받도록 합시다.

라이브러리명 : LedControl by Eberhard Fahle

#include "LedControl.h"  //dot matrix를 사용하기 위한 라이브러리 호출

#define dataIn 12  //DIN 12번 핀으로 사용
#define cs 11      //CS 11번 핀으로 사용
#define clk 10     //CLK 10번 핀으로 사용
#define Player1 4  //button 핀번호
#define Player2 6  //button 핀번호

LedControl lc = LedControl(dataIn, clk, cs, 1);  //LedControl('DIN핀 번호', 'CLK핀 번호', 'CS핀 번호', 'dot matrix 갯수')

bool stateCheck = 0;
bool Matrix_off[8][8] = {
  0,
};
bool MatrixInput_1[8][8] = {
  { 1, 0, 0, 0, 0, 0, 0, 1 },
  { 0, 1, 0, 0, 0, 0, 1, 0 },
  { 0, 0, 1, 0, 0, 1, 0, 0 },
  { 0, 0, 0, 1, 1, 0, 0, 0 },
  { 0, 0, 0, 1, 1, 0, 0, 0 },
  { 0, 0, 1, 0, 0, 1, 0, 0 },
  { 0, 1, 0, 0, 0, 0, 1, 0 },
  { 1, 0, 0, 0, 0, 0, 0, 1 }
};

bool MatrixInput_2[8][8] = {
  { 0, 0, 1, 1, 1, 1, 0, 0 },
  { 0, 1, 0, 0, 0, 0, 1, 0 },
  { 1, 0, 0, 0, 0, 0, 0, 1 },
  { 1, 0, 0, 0, 0, 0, 0, 1 },
  { 1, 0, 0, 0, 0, 0, 0, 1 },
  { 1, 0, 0, 0, 0, 0, 0, 1 },
  { 0, 1, 0, 0, 0, 0, 1, 0 },
  { 0, 0, 1, 1, 1, 1, 0, 0 }
};

bool MatrixInput_3[8][8] = {
  { 0, 0, 0, 1, 1, 0, 0, 0 },
  { 0, 0, 0, 1, 1, 0, 0, 0 },
  { 0, 0, 0, 1, 1, 0, 0, 0 },
  { 0, 0, 0, 1, 1, 0, 0, 0 },
  { 1, 1, 1, 1, 1, 1, 1, 1 },
  { 0, 1, 1, 1, 1, 1, 1, 0 },
  { 0, 0, 1, 1, 1, 1, 0, 0 },
  { 0, 0, 0, 1, 1, 0, 0, 0 }
};

bool MatrixInput_4[8][8] = {
  { 0, 0, 0, 1, 1, 0, 0, 0 },
  { 0, 0, 1, 1, 1, 1, 0, 0 },
  { 0, 1, 1, 1, 1, 1, 1, 0 },
  { 1, 1, 1, 1, 1, 1, 1, 1 },
  { 0, 0, 0, 1, 1, 0, 0, 0 },
  { 0, 0, 0, 1, 1, 0, 0, 0 },
  { 0, 0, 0, 1, 1, 0, 0, 0 },
  { 0, 0, 0, 1, 1, 0, 0, 0 }
};

bool btnFlg1 = 0;
bool btnFlg2 = 0;
int btn1Chk() {
  if (digitalRead(Player1) == 0) {
    btnFlg1 = 1;
    return 0;
  }
  if (btnFlg1 == 1) {
    btnFlg1 = 0;
    return 1;
  }
  return 0;
}

int btn2Chk() {
  if (digitalRead(Player2) == 0) {
    btnFlg2 = 1;
    return 0;
  }
  if (btnFlg2 == 1) {
    btnFlg2 = 0;
    return 1;
  }
  return 0;
}

void MatrixLoop(bool matinput[8][8]) {
  for (int i = 0; i < 8; i++) {
    for (int j = 0; j < 8; j++) {
      lc.setLed(0, i, j, matinput[i][j]);
    }
  }
}

void setupfunc() {
  MatrixLoop(MatrixInput_1);
  randomSeed(analogRead(0));
  int randomTime = random(3, 6);
  delay(randomTime * 1000);
  MatrixLoop(MatrixInput_2);
}

void setup() {
  pinMode(Player1, INPUT_PULLUP);
  pinMode(Player2, INPUT_PULLUP);
  lc.shutdown(0, false);  //dot matrix를 깨우는 코드. shutdown('지정할 dotmatrix의 주소', 'false : 깨우기')
  lc.setIntensity(0, 8);  //밝기 설정. setIntensity('지정할 dotmatrix의 주소','밝기값(0~15)')
  lc.clearDisplay(0);     //사용하기 전 초기화. clearDisplay('지정할 dotmatrix의 주소')
}

void loop() {
  if (stateCheck == 1) {
    if (btn1Chk() == 1) {
      btnFlg2 = 0;
      MatrixLoop(MatrixInput_3);
      delay(1500);
      MatrixLoop(Matrix_off);
      stateCheck = 0;
    } else if (btn2Chk() == 1) {
      btnFlg1 = 0;
      MatrixLoop(MatrixInput_4);
      delay(1500);
      MatrixLoop(Matrix_off);
      stateCheck = 0;
    }
  } else if (stateCheck == 0) {
    if (btn1Chk() == 1 || btn2Chk() == 1) {
      setupfunc();
      stateCheck = 1;
    }
  }
  delay(1);
}

다 완성하면 아래와 같은 모습입니다.