Arduino:Примеры/Гайд по использованию светодиодной матрицы MAX7219 с Arduino (плюс игра Pong)

Материал из Онлайн справочника
Перейти к навигацииПерейти к поиску

Перевод: Максим Кузьмин
Проверка/Оформление/Редактирование: Мякишев Е.А.



Гайд по использованию светодиодной матрицы MAX7219 с Arduino (плюс игра Pong)[1]

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

Управление светодиодной матрицей можно упростить при помощи чипа MAX7219 – благодаря ему вам понадобится всего 3 цифровых контакта платы Arduino. Думаю, будет лучше купить модуль, в который уже встроены и светодиодная матрица, и чип MAX7219. Это упростит возню с проводами. Такой модуль можно купить на eBay менее чем за 2 доллара.

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

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

  • Одна светодиодная матрица (8 на 8) с чипом MAX7219 (см. на eBay)
  • Одна плата Arduino (см. на eBay)
  • Один потенциометр на 1 кОм
  • Провода-перемычки

Подключение контактов

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

  • Контакт GND на матрице – к контакту GND на Arduino
  • Контакт VCC – к контакту 5V
  • Контакт DIN – к цифровому контакту
  • Контакт CS – к цифровому контакту
  • Контакт CLK – к цифровому контакту

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

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

  • Кликните здесь, чтобы скачать ZIP-архив с библиотекой.
  • Распакуйте скачанный архив. В итоге у вас должна получиться папка под названием «LedControl-master».
  • Переименуйте ее на «LedControl».
  • Переместите папку «LedControl» в папку «libraries» IDE Arduino.
  • Перезапустите IDE Arduino.

Функции библиотеки LedControl

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

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

  • setled(addr, row, col, state)
    • addr – адрес матрицы; к примеру, если вы используете всего одну матрицу, то в этом параметре должен стоять «0»
    • row – ряд, в котором находится светодиод
    • col – столбец, в котором находится светодиод
    • state – состояние светодиода; true или 1 – чтобы включить светодиод, false или 0 – чтобы выключить
  • setRow(addr, row, value)
  • setCol(addr, row, value)
    • value – значение, которое обозначает, какие светодиоды надо включить

Индекс

Итак, матрица состоит из 8 столбцов и 8 рядов. Каждый из них имеет индекс от «0» до «7». Ниже – рисунок для наглядности:

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

К примеру, если я хочу показать смайлик, то мне нужно сделать следующее:

Код

Ниже – простой скетч, который показывает на матрице три разные рожицы: веселую, «покерфейс» и грустную. Загрузите его на свою Arduino:

/*
 Автор – Руи Сантос (Rui Santos)
 Более подробно о проекте на: http://randomnerdtutorials.com/
*/

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

/*
 Контакт DIN на матрице – к цифровому контакту 12 на Arduino
 Контакт CLK – к цифровому контакту 11
 Контакт CS – к цифровому контакту 10
*/
LedControl lc=LedControl(12,11,10,1);

// задержка между разными рожицами:
unsigned long delaytime=1000;

// веселая рожица:
byte hf[8]= {B00111100,B01000010,B10100101,B10000001,B10100101,B10011001,B01000010,B00111100};
// «покерфейс»:
byte nf[8]={B00111100, B01000010,B10100101,B10000001,B10111101,B10000001,B01000010,B00111100};
// грустная рожица:
byte sf[8]= {B00111100,B01000010,B10100101,B10000001,B10011001,B10100101,B01000010,B00111100};

void setup() {
  lc.shutdown(0,false);
  // выставляем яркость на среднее значение:
  lc.setIntensity(0,8);
  // очищаем дисплей:
  lc.clearDisplay(0);  
}

void drawFaces(){
  // показываем грустную рожицу:
  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);
  
  // показываем «покерфейс»:
  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);
  
  // показываем веселую рожицу:
  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();
}

В результате у вас должно получится примерно следующее:

Игра «Pong»

Игра «Pong» для этого примера была написана Алессандро Пасотти (Alessandro Pasotti). Кроме того, для этого проекта вам нужно будет добавить к схеме выше потенциометр на 1 кОм. Подключите друг к другу компоненты как показано на рисунке ниже:

Код

Теперь загрузите на Arduino вот этот код:

/*  
 *   Игра «Pong» на светодиодной матрице 8x8 (проект с itopen.it)
*/
 
#include "LedControl.h"
#include "Timer.h"
 
#define POTPIN A5 // потенциометр
#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; // роза ветров, «0» - это север
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);
    // начальная позиция:
    xball = random(1, 7);
    yball = 1;
    direction = random(3, 6); // идем на юг
    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: ");  //  "Координаты X и Y: " 
    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("->");
    }
 
    // проверяем удар: модифицируем направление (влево или вправо):
    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;
    }
 
    // проверяем ортогональные направления и границы:
    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;
    }
    
    // при попадании в угол:
    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() {
  // в начале MAX72xx находится в режиме сбережения энергии;
  // нужно его «разбудить»: 
  pinMode(POTPIN, INPUT);
 
  lc.shutdown(0,false);
  // выставляем яркость на среднюю величину:
  lc.setIntensity(0, 8);
  // очищаем дисплей:
  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();
    // двигаем платформу:
    setPad();
#ifdef DEBUG
    Serial.println(xpad);
#endif
    // обновляем данные на экране:
    drawGame();
    if(checkLoose()) {
        debug("LOOSE");
        gameOver();
        newGame();
    }
    delay(GAME_DELAY);
}

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

Вот так игра в «Pong» выглядит вживую:

См.также

Внешние ссылки