Руководство по 8x8 точечной матрице MAX7219 с Arduino + игра Pong

Точечная матрица, которую мы будем использовать в этом руководстве, представляет собой матрицу 8x8, что означает, что она имеет 8 столбцов и 8 строк, то есть содержит в общей сложности 64 светодиода.

8x8 точечная матрица MAX7219 с Arduino

Микросхема MAX7219 упрощает управление точечной матрицей, используя всего 3 цифровых вывода платы Arduino.

Лучший вариант — купить точечную матрицу с микросхемой MAX7219 в виде готового модуля, это упростит подключение. Вы можете посмотреть точечную матрицу на Maker Advisor и найти лучшую цену.

Модуль точечной матрицы MAX7219

Вы можете управлять несколькими матрицами одновременно. Для этого нужно просто соединить их друг с другом, так как они имеют выводы с обеих сторон для расширения точечной матрицы.

Необходимые компоненты

Для этого руководства вам понадобятся:

Вы можете использовать приведенные выше ссылки или перейти непосредственно на MakerAdvisor.com/tools, чтобы найти все компоненты для ваших проектов по лучшей цене!

Подключение выводов

Вам нужно подключить всего 5 выводов от точечной матрицы к плате Arduino. Подключение довольно простое:

Вывод точечной матрицы

Подключение к Arduino Uno

GND

GND

VCC

5V

DIN

Цифровой вывод

CS

Цифровой вывод

CLK

Цифровой вывод

Как управлять точечной матрицей с помощью Arduino

Для упрощения управления точечной матрицей вам нужно скачать и установить в Arduino IDE библиотеку LedControl. Чтобы установить библиотеку, выполните следующие шаги:

  1. Нажмите здесь, чтобы скачать библиотеку LedControl. У вас должен появиться .zip-файл в папке загрузок

  2. Распакуйте .zip-файл, и вы получите папку LedControl-master

  3. Переименуйте папку из LedControl-master в LedControl

  4. Переместите папку LedControl в папку libraries вашей установки Arduino IDE

  5. Наконец, перезапустите Arduino IDE

Использование функций библиотеки LedControl

Самый простой способ отобразить что-либо на точечной матрице — использовать функции setLed(), setRow() или setColumn(). Эти функции позволяют управлять одним отдельным светодиодом, одной строкой или одним столбцом за раз.

Вот параметры для каждой функции:

*setLed(addr, row, col, state)*

  • addr — это адрес вашей матрицы, например, если у вас только 1 матрица, значение int addr будет равно нулю.

  • row — это строка, в которой расположен светодиод

  • col — это столбец, в которой расположен светодиод

  • state

    • Значение true или 1, если вы хотите включить светодиод

    • Значение false или 0, если вы хотите его выключить

*setRow(addr, row, value)*

*setCol(addr, column, value)*

Индексация

Как было сказано ранее, эта матрица имеет 8 столбцов и 8 строк. Каждый из них индексируется от 0 до 7. Вот рисунок для лучшего понимания:

Индексация строк и столбцов матрицы 8x8

Если вы хотите отобразить что-либо на матрице, вам просто нужно знать, в определенной строке или столбце, какие светодиоды включены или выключены.

Например, если вы хотите отобразить счастливое лицо, вот что вам нужно сделать:

Пример отображения лиц на матрице 8x8

Код

Вот простой скетч, который отображает три типа лиц: грустное лицо, нейтральное лицо и счастливое лицо. Загрузите следующий код на вашу плату:

/*
 Created by Rui Santos

 All the resources for this project:
 https://randomnerdtutorials.com/
*/

#include "LedControl.h"
#include "binary.h"

/*
 DIN connects to pin 12
 CLK connects to pin 11
 CS connects to pin 10
*/
LedControl lc=LedControl(12,11,10,1);

// delay time between faces
unsigned long delaytime=1000;

// happy face
byte hf[8]= {B00111100,B01000010,B10100101,B10000001,B10100101,B10011001,B01000010,B00111100};
// neutral face
byte nf[8]={B00111100, B01000010,B10100101,B10000001,B10111101,B10000001,B01000010,B00111100};
// sad face
byte sf[8]= {B00111100,B01000010,B10100101,B10000001,B10011001,B10100101,B01000010,B00111100};

void setup() {
  lc.shutdown(0,false);
  // Set brightness to a medium value
  lc.setIntensity(0,8);
  // Clear the display
  lc.clearDisplay(0);
}

void drawFaces(){
  // Display sad face
  lc.setRow(0,0,sf[0]);
  lc.setRow(0,1,sf[1]);
  lc.setRow(0,2,sf[2]);
  lc.setRow(0,3,sf[3]);
  lc.setRow(0,4,sf[4]);
  lc.setRow(0,5,sf[5]);
  lc.setRow(0,6,sf[6]);
  lc.setRow(0,7,sf[7]);
  delay(delaytime);

  // Display neutral face
  lc.setRow(0,0,nf[0]);
  lc.setRow(0,1,nf[1]);
  lc.setRow(0,2,nf[2]);
  lc.setRow(0,3,nf[3]);
  lc.setRow(0,4,nf[4]);
  lc.setRow(0,5,nf[5]);
  lc.setRow(0,6,nf[6]);
  lc.setRow(0,7,nf[7]);
  delay(delaytime);

  // Display happy face
  lc.setRow(0,0,hf[0]);
  lc.setRow(0,1,hf[1]);
  lc.setRow(0,2,hf[2]);
  lc.setRow(0,3,hf[3]);
  lc.setRow(0,4,hf[4]);
  lc.setRow(0,5,hf[5]);
  lc.setRow(0,6,hf[6]);
  lc.setRow(0,7,hf[7]);
  delay(delaytime);
}

void loop(){
  drawFaces();
}

Просмотреть исходный код

В итоге у вас получится что-то вроде этого:

Анимация отображения лиц на матрице MAX7219

Игра Pong

Игра Pong, которую вы сейчас попробуете, была создана Alessandro Pasotti.

Для игры Pong вам нужно просто добавить потенциометр 1 кОм к предыдущей схеме. Соберите новую схему, как показано ниже:

Схема подключения для игры Pong с матрицей MAX7219

Код игры Pong

Затем загрузите следующий код на вашу плату Arduino:

/*
 *   Play pong on an 8x8 matrix - project from itopen.it
 */

#include "LedControl.h"
#include "Timer.h"

#define POTPIN A5 // Potentiometer
#define PADSIZE 3
#define BALL_DELAY 200
#define GAME_DELAY 10
#define BOUNCE_VERTICAL 1
#define BOUNCE_HORIZONTAL -1
#define NEW_GAME_ANIMATION_SPEED 50
#define HIT_NONE 0
#define HIT_CENTER 1
#define HIT_LEFT 2
#define HIT_RIGHT 3

//#define DEBUG 1

byte sad[] = {
B00000000,
B01000100,
B00010000,
B00010000,
B00000000,
B00111000,
B01000100,
B00000000
};

byte smile[] = {
B00000000,
B01000100,
B00010000,
B00010000,
B00010000,
B01000100,
B00111000,
B00000000
};

Timer timer;

LedControl lc = LedControl(12,11,10,1);

byte direction; // Wind rose, 0 is north
int xball;
int yball;
int yball_prev;
byte xpad;
int ball_timer;

void setSprite(byte *sprite){
    for(int r = 0; r < 8; r++){
        lc.setRow(0, r, sprite[r]);
    }
}

void newGame() {
    lc.clearDisplay(0);
    // initial position
    xball = random(1, 7);
    yball = 1;
    direction = random(3, 6); // Go south
    for(int r = 0; r < 8; r++){
        for(int c = 0; c < 8; c++){
            lc.setLed(0, r, c, HIGH);
            delay(NEW_GAME_ANIMATION_SPEED);
        }
    }
    setSprite(smile);
    delay(1500);
    lc.clearDisplay(0);
}

void setPad() {
    xpad = map(analogRead(POTPIN), 0, 1020, 8 - PADSIZE, 0);
}

void debug(const char* desc){
#ifdef DEBUG
    Serial.print(desc);
    Serial.print(" XY: ");
    Serial.print(xball);
    Serial.print(", ");
    Serial.print(yball);
    Serial.print(" XPAD: ");
    Serial.print(xpad);
    Serial.print(" DIR: ");
    Serial.println(direction);
#endif
}

int checkBounce() {
    if(!xball || !yball || xball == 7 || yball == 6){
        int bounce = (yball == 0 || yball == 6) ? BOUNCE_HORIZONTAL : BOUNCE_VERTICAL;
#ifdef DEBUG
        debug(bounce == BOUNCE_HORIZONTAL ? "HORIZONTAL" : "VERTICAL");
#endif
        return bounce;
    }
    return 0;
}

int getHit() {
    if(yball != 6 || xball < xpad || xball > xpad + PADSIZE){
        return HIT_NONE;
    }
    if(xball == xpad + PADSIZE / 2){
        return HIT_CENTER;
    }
    return xball < xpad + PADSIZE / 2 ? HIT_LEFT : HIT_RIGHT;
}

bool checkLoose() {
    return yball == 6 && getHit() == HIT_NONE;
}

void moveBall() {
    debug("MOVE");
    int bounce = checkBounce();
    if(bounce) {
        switch(direction){
            case 0:
                direction = 4;
            break;
            case 1:
                direction = (bounce == BOUNCE_VERTICAL) ? 7 : 3;
            break;
            case 2:
                direction = 6;
            break;
            case 6:
                direction = 2;
            break;
            case 7:
                direction = (bounce == BOUNCE_VERTICAL) ? 1 : 5;
            break;
            case 5:
                direction = (bounce == BOUNCE_VERTICAL) ? 3 : 7;
            break;
            case 3:
                direction = (bounce == BOUNCE_VERTICAL) ? 5 : 1;
            break;
            case 4:
                direction = 0;
            break;
        }
        debug("->");
    }

    // Check hit: modify direction is left or right
    switch(getHit()){
        case HIT_LEFT:
            if(direction == 0){
                direction =  7;
            } else if (direction == 1){
                direction = 0;
            }
        break;
        case HIT_RIGHT:
            if(direction == 0){
                direction = 1;
            } else if(direction == 7){
                direction = 0;
            }
        break;
    }

    // Check orthogonal directions and borders ...
    if((direction == 0 && xball == 0) || (direction == 4 && xball == 7)){
        direction++;
    }
    if(direction == 0 && xball == 7){
        direction = 7;
    }
    if(direction == 4 && xball == 0){
        direction = 3;
    }
    if(direction == 2 && yball == 0){
        direction = 3;
    }
    if(direction == 2 && yball == 6){
        direction = 1;
    }
    if(direction == 6 && yball == 0){
        direction = 5;
    }
    if(direction == 6 && yball == 6){
        direction = 7;
    }

    // "Corner" case
    if(xball == 0 && yball == 0){
        direction = 3;
    }
    if(xball == 0 && yball == 6){
        direction = 1;
    }
    if(xball == 7 && yball == 6){
        direction = 7;
    }
    if(xball == 7 && yball == 0){
        direction = 5;
    }

    yball_prev = yball;
    if(2 < direction && direction < 6) {
        yball++;
    } else if(direction != 6 && direction != 2) {
        yball--;
    }
    if(0 < direction && direction < 4) {
        xball++;
    } else if(direction != 0 && direction != 4) {
        xball--;
    }
    xball = max(0, min(7, xball));
    yball = max(0, min(6, yball));
    debug("AFTER MOVE");
}

void gameOver() {
    setSprite(sad);
    delay(1500);
    lc.clearDisplay(0);
}

void drawGame() {
    if(yball_prev != yball){
        lc.setRow(0, yball_prev, 0);
    }
    lc.setRow(0, yball, byte(1 << (xball)));
    byte padmap = byte(0xFF >> (8 - PADSIZE) << xpad) ;
#ifdef DEBUG
    //Serial.println(padmap, BIN);
#endif
    lc.setRow(0, 7, padmap);
}

void setup() {
  // The MAX72XX is in power-saving mode on startup,
  // we have to do a wakeup call
  pinMode(POTPIN, INPUT);

  lc.shutdown(0,false);
  // Set the brightness to a medium values
  lc.setIntensity(0, 8);
  // and clear the display
  lc.clearDisplay(0);
  randomSeed(analogRead(0));
#ifdef DEBUG
  Serial.begin(9600);
  Serial.println("Pong");
#endif
  newGame();
  ball_timer = timer.every(BALL_DELAY, moveBall);
}

void loop() {
    timer.update();
    // Move pad
    setPad();
#ifdef DEBUG
    Serial.println(xpad);
#endif
    // Update screen
    drawGame();
    if(checkLoose()) {
        debug("LOOSE");
        gameOver();
        newGame();
    }
    delay(GAME_DELAY);
}

Просмотреть исходный код

Демонстрация

Вот финальная демонстрация того, как я играю в игру Pong. Веселитесь!

Демонстрация игры Pong на матрице MAX7219

Подведение итогов

Вы когда-нибудь использовали точечную матрицу в своих проектах Arduino?

Оставьте комментарий ниже, чтобы поделиться своим опытом.