Как стать автором
Обновить

Ping-pong на Arduino для начинающих

Оглавление


  1. Вместо введения
  2. Постановка задачи
  3. Что для этого нужно?
  4. По чуть-чуть про каждый элемент
  5. Сборка
  6. Кодирование
  7. Заключение

Вместо введения


Весна 2016о года, подходит к концу восьмой семестр обучения на кафедре ЭВМ в БГУИР. В качестве курсового по СиФО ЭВМ (Структурная и функциональная организация ЭВМ) нам предложили два варианта исполнения: на бумаге либо на железе. Мне показалось, что «на железе» будет интереснее, тк в универе всё на бумаге, а тут что-то новенькое. В голову пришла идея сделать игру. Сразу вспомнился тетрис и игра Ping-Pong.

Постановка задачи


Сделать игру Ping-Pong. Обязательные критерии:

  • для двух игроков
  • беспроводное управление
  • усложнение игры с каждым новым уровнем

Что для этого нужно?


Наш Ping-Pong будет состоять из следующих элементов:

  • Arduino UNO R3 (х2)
  • Bluetooth Shield HC-05 (х2)
  • Модуль светодиодной матрицы MAX7219 (х2)


По чуть-чуть про каждый элемент


Arduino UNO R3
Arduino UNO R3

Arduino UNO R3 – микроконтроллер ATmega328 фирмы Atmel. Модуль Arduino UNO R3 наиболее популярен среди главных модулей набора, имеющих интерфейс USB. Устройство является аппаратным средством, упрощающим разработку и макетирование схем с использованием МК Atmel. Микроконтроллер установлен в панель. Это позволяет провести проверку работы проекта на МК различных модификаций, заменить МК при выходе из строя. Программа написанная Вами для управления системой из компонентов Arduino записывается в МК входящий в модуль Arduino UNO R3. К модулю подключаются различные периферийные устройства: управляющая электроника электродвигателей, клавиатуры и индикаторы, клеммные разветвители для датчиков, модули связи по радио, самодельные модули. Модуль содержит простые средства индикации и кнопка сброса.

Параметры Arduino Uno R3:

— Тактовая частота: 16 МГц
— Размеры: 68,6 Х 53,4 мм
— Вес: 25 г

Память микроконтроллера ATmega328:

— Оперативная память, тип: SRAM 2 кБ
— Память программ, флер: 32 кБ из которых 512 байт заняты загрузчиком.
— Долговременная память данных, тип: EEPROM 1 кБ.

Интерфейсы Arduino Uno R3:

На контакты соединителей, размещенных по периметру платы, выведены сигналы интерфейсов. Приводим расположение соединителей интерфейсов по краям платы в порядке по движению часовой стрелки, начиная от разъема USB. В группе из шести штырей подписанных “AREF” находятся интерфейсы:

— контакты интерфейса ICSP для записи программы в контроллер USB–USART,
— два контакта интерфейса I2C: SDA и SCL.

Контакты USART соединены с соответствующими выводами микросхемы последовательной шины преобразователя USB–USART.
Еще одна группа из шести контактов – интерфейс ICSP для записи программы в главный контроллер. Однорядная шестиконтактная розетка содержит второй интерфейс I2C.

Bluetooth Shield HC-05
Bluetooth Shield HC-05
Bluetooth Shield HC-05 — Удобный модуль связи Bluetooth V2.0 + EDR устанавливается поверх контроллера Arduino. Модуль позволяет организовать двунаправленную радиосвязь по протоколу Bluetooth при управлении различными объектами и получении данных. Применение модуля оснащает Arduino беспроводным интерфейсом и позволяет связать мостом UART–Bluetooth–UART систему, изготовленную из компонентов Arduino и второй прибор. Может работать в режимах мaster, slave или в режиме петли для проверки и отладки программ. Организация связи чаще всего происходит по двум схемам. Контроллер-Arduino – для связи Arduino и прибора на основе микроконтроллера, оснащенного интерфейсом UART. Компьютер-Arduino – используя специально написанную программу и размещенную на андроиде, ноутбуке или ПК. Модуль способен автоматически подключаться к связи с устройством мaster.

Через Bluetooth принимается информация и передается на контакты модуля D0 и D1, подключенные к интерфейсу UART основной платы Arduino. На плате модуля смонтирована небольшая плата популярного модуля HC-05 отвечающего за работу Bluetooth и несущего всю программную и информационную нагрузку устройства. HC-05 поддерживает связь с устройствами Bluetooth по стандарту SPP. На плате HC-05 расположена миниатюрная антенна, которой служит дорожка в виде змейки на верхнем слое платы.

Обеспечена индикация, показывающая режим работы. Соединение по Bluetooth автоматически восстанавливается в течении 30 минут при нарушении связи в результате превышения предельного расстояния между связанными приборами. Использование устройства не требует от инженера знаний особенностей стандарта Bluetooth.

Параметры:

— Частота приема и передачи: 2,4…2,48 Мгц;
— Есть функция адаптивного переключения частоты;
— Чувствительность: 80 dBм;
— Мощность передачи, мВт: максимальная 2,5; минимальная 0,25
— Дальность связи: 10 м;
— Скорость обмена данными: максимальная 115200 бод;
— Возможно переключение в режим низкого энергопотребления;
— UART интерфейс с программируемой скоростью передачи.

Связь может быть установлена только между устройствами, роли которых в диалоге обмена информацией мaster и slave.

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

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

Ток потребления. MAX7219 поддерживает два режима работы.

Первый простой, при котором каждый бит байта данных отвечает за состояние одного элемента индикатора. Этот режим можно использовать для отображения нестандартных символов или при работе со светодиодными матрицами. Для задания свечения того или иного элемента семисегментного индикатора, можно использовать следующую последовательность битов: «PABCDEFG», где P – десятичная точка.

Второй режим – декодирование из двоично-десятичного кода. В этом режиме, в качестве данных используется двоичное представление десятичной цифры, что позволяет избежать преобразования данных. В драйвер записывается непосредственно десятичная цифра, которую необходимо отобразить. Микросхема MAX7219 в этом режиме способна индицировать цифры от 0 до 9, символ минуса, буквы E,H,L,P и пустой индикатор. После подачи питания, по умолчанию, установлен простой режим. Изменить режим каждого из 8 индикаторов можно путем установки соответствующего бита в регистре 09h.

Сборка


Каскадное подключение светодиодных матриц MAX7219


Для начала, я решил подключить светодиодные матрицы. Подключал по схеме, которая находится ниже.

image

Подключение Bluetooth Shield HC-05


Подключить Bluetooth оказалось самым простым. Просто берем Shield и надеваем на Arduino. Bluetooth подключен.

Результат приведен ниже.



Master-slave подключение двух Arduino


Как видно из рисунка ниже, подключение происходит через аналоговые PIN 5 и PIN 4 на master-Arduino и на slave-Arduino. Также и там, и там подключаем GND. Убедитесь, чтобы обе Arduino были запитаны.

image

image

В результате получилось так:



Кодирование


И так, мы уже собрали полностью схему. Осталось всё закодировать. У нас одна Arduino будет типа «Master», которая будет управлять игрой, счетом и отслеживать двух игроков, которые будут подключены по Bluetooth. Действия первого игрока буду контролировать по Bluetooth этой же Arduino, а действия второго игрока Slave-Arduino будет присылать.

Код первой Arduino


#include "Wire.h"                          // библиотека для "общения" двух Arduino
#include "LiquidCrystal_I2C.h"     
#include "LedControl.h"               // библиотека для работы с LED-матрицами
#include "SoftwareSerial.h"         // библиотека для работы с Bluetooth

#define N 16                                // длина поля
#define M 8                                 // ширина поля

#define rxPin 2 
#define txPin 3

#define LedCount 2                    // количество LED-матриц

byte fullRows[8] = {B11111111, B11111111, B11111111, B11111111, B11111111, B11111111, B11111111, B11111111};
// комбинации для вывода на экраны LED-матриц грустных/веселых смайликов
byte endGameHappy2Rows[8] = {B00000000, B00100100, B00000000, B00000000, B10000001, B01000010, B00111100, B00000000};
byte endGameSad2Rows[8] = {B00000000, B00100100, B00000000, B00000000, B00111100, B01000010, B10000001, B00000000};
byte endGameHappy1Rows[8] = {B00000000, B00111100, B01000010, B10000001, B00000000, B00000000, B00100100, B00000000};
byte endGameSad1Rows[8] = {B00000000, B10000001, B01000010, B00111100, B00000000, B00000000, B00100100, B00000000};


SoftwareSerial btSerial(rxPin, txPin);              // инициализируем Bluetooth
LedControl lc = LedControl(12, 11, 10, 2);      // инициализируем LED-матрицы
LiquidCrystal_I2C lcd(0x27,16,2);
int _speed = 0;
int ourSpeed = 5;
int _angle = 0;
int _px;
int _py;
int left = 3;
int right = 3;
int leds[N][M]; // массив, в котором отображается поле

int scoreFirstPlayer = 0;  // счет первого игрока
int scoreSecondPlayer = 0; // счет второго игрока 

char secondPlayerStay = '3';

void setup()
{
  lcd.init();
  lcd.backlight();

  pinMode(rxPin, INPUT);
  pinMode(txPin, OUTPUT);
  btSerial.begin(38400);
  Serial.begin(38400);

  Wire.begin(9); 
  Wire.onReceive(receiveEvent);

  for (int i = 0; i < LedCount; i++)
  {
    //С помощью библиотеки включаем матрицу
    lc.shutdown(i, false);
    //Выставляем яркость на средний уровень
    lc.setIntensity(i, 8);
    //Очищаем матрицу, выключаем все светодиоды
    lc.clearDisplay(i);
  }

  lcd.print("   Start game!");
  gameStart();
}

void checkCollision() {
  if (_px == N - 2)
  {
    if (_angle == 315 || _angle == 0 || _angle == 45)
    {
      if (_py == right || _py == right + 1 || _py == right - 1 || _py == right + 2)
      {
        if (_angle == 0 && _py == right)
          retorted(225);
        else if (_angle == 0 && _py == right + 1)
          retorted(135);
        else if (_angle == 45 && _py == right - 1)
          retorted(180);
        else if (_angle == 45 && _py == right)
          retorted(135);
        else if (_angle == 45 && _py == right + 1)
          retorted(135);
        else if (_angle == 315 && _py == right + 2)
          retorted(180);
        else if (_angle == 315 && _py == right + 1)
          retorted(225);
        else if (_angle == 315 && _py == right)
          retorted(225);
      }
    }
  }
  else if (_px == 1)
  {
    if (_angle == 225 || _angle == 180 || _angle == 135)
    {
      if (_py == left || _py == left + 1 || _py == left - 1 || _py == left + 2)
      {
        if (_angle == 180 && _py == left)
          retorted(315);
        else if (_angle == 180 && _py == left + 1)
          retorted(45);
        else if (_angle == 135 && _py == left - 1)
          retorted(0);
        else if (_angle == 135 && _py == left)
          retorted(45);
        else if (_angle == 135 && _py == left + 1)
          retorted(45);
        else if (_angle == 225 && _py == left + 2)
          retorted(0);
        else if (_angle == 225 && _py == left + 1)
          retorted(315);
        else if (_angle == 225 && _py == left)
          retorted(315);
      }
    }
  }
  if (_px == N - 1)
  {
    scoreFirstPlayer++;
    gameEnd(1);
  }
  else if (_px == 0)
  {
    scoreSecondPlayer++;
    gameEnd(2);
  }
  else if (_py == M - 1)
  {
    if (_angle == 45)
      _angle = 315;
    else if (_angle == 135)
      _angle = 225;
  }
  else if (_py == 0)
  {
    if (_angle == 225)
      _angle = 135;
    else if (_angle == 315)
      _angle = 45;
  }
}

// функция, читающая то, что присылает вторая Arduino первой
void receiveEvent(int bytes) {
  secondPlayerStay = (char)Wire.read();   
    Serial.println(secondPlayerStay);
}


void loop() {
  clearLeds();

  int button1 = LOW;
  int button2 = LOW;
  int button3 = LOW;
  int button4 = LOW;

  // Читаем из Bluetooth состояния первого игрока. если пришел ноль, значит влево. Если пришла единица - вправо.
  while (btSerial.available())
  {
    char c = (char)btSerial.read();
    if (c == '0')
    {
      button2 = HIGH;
    }
    if (c == '1')
    {
      button1 = HIGH;
    }
  }

  if(secondPlayerStay != '3')
  {
    if (secondPlayerStay == '0')
    {
      button4 = HIGH;
    }
    if (secondPlayerStay == '1')
    {
      button3 = HIGH;
    }
  }
  secondPlayerStay = '3';


  if (button1 == HIGH) {
    delay(10);
    if (button1 == HIGH) {
      left--;
    }
  }
  if (button2 == HIGH) {
    delay(10);
    if (button2 == HIGH) {
      left++;
    }
  }
  if (button3 == HIGH) {
    delay(10);
    if (button3 == HIGH) {
      right++;
    }
  }
  if (button4 == HIGH) {
    delay(10);
    if (button4 == HIGH) {
      right--;
    }
  }
  setWall();
  _speed++;
  if (_speed == ourSpeed) {
    _speed = 0;
    checkCollision();
    calcAngleIncrement();
  }
  leds[_px][_py] = 1;
  showLeds();
}
void setWall()
{
  left = wall(left);
  right = wall(right);
  leds[0][left] = 1;
  leds[0][left + 1] = 1;
  leds[N - 1][right] = 1;
  leds[N - 1][right + 1] = 1;
}
void clearLeds() {
  for (int i = 0; i < N; i++) {
    for (int j = 0; j < M; j++) {
      leds[i][j] = 0;
    }
  }
}
int wall(int val) {
  if (val < 0) val = 0;
  if (val > 6) val = 6;
  return val;
}
void showLeds() {
  // Clear display array
  for (int i = 0; i < 8; i++) {
    for (int j = 0; j < M; j++) {
      if (leds[i][j] == 1) {
          lc.setLed(0, i, j, true);
      } 
      else {
        lc.setLed(0, i, j, false);
      }
    }
  }
  for (int i = 8; i < N; i++) {
    for (int j = 0; j < M; j++) {
      if (leds[i][j] == 1) {
        lc.setLed(1, i - M, j, true);
      } 
      else {
        lc.setLed(1, i - M, j, false);
      }
    }
  }
}


void gameStart() {
  delay(600);
  lc.clearDisplay(1);
  clearLeds();
  _px = random(7, 10);
  _py = random(3, 5);
  _angle = random(0, 2) == 0 ? 0 : 180;
  left = 3;
  right = 3;
}


void gameEnd(int player)
{
  for(int i = 0; i < M; i++)
  {
    lc.setRow(0, i, fullRows[i]);
    lc.setRow(1, i, fullRows[i]);
  }

  lcd.setCursor(0, 0);
  if(player == 1)
  {
    lcd.print("  Player 1 win");
    for(int i = 0; i < M; i++)
    {
      lc.setRow(0, i, endGameHappy1Rows[i]);
      lc.setRow(1, i, endGameSad2Rows[i]);
    }
  }
  else
  {
    lcd.print("  Player 2 win");
    for(int i = 0; i < M; i++)
    {
      lc.setRow(1, i, endGameHappy2Rows[i]);
      lc.setRow(0, i, endGameSad1Rows[i]);
    }
  }
  lcd.setCursor(0, 1);
  lcd.print("  Score: ");
  lcd.print(scoreFirstPlayer);
  lcd.print(" : ");
  lcd.print(scoreSecondPlayer);

  
  delay(2000);
  ourSpeed--;
  if(ourSpeed == 0)
  {
    ourSpeed = 1;
  }
  gameStart();
}


void calcAngleIncrement()
{
  if (_angle == 0 || _angle == 360)
  {
    _px += 1;
  }
  else if (_angle == 45)
  {
    _px += 1;
    _py += 1;
  }
  else if (_angle == 135)
  {
    _px -= 1;
    _py += 1;
  }
  else if (_angle == 180)
  {
    _px -= 1;
  }
  else if (_angle == 225)
  {
    _px -= 1;
    _py -= 1;
  }
  else if (_angle == 315)
  {
    _px += 1;
    _py -= 1;
  }
}

void retorted(int angle)
{
  _angle = angle;
}

PingPongMaster.ino

Код второй Arduino


#include "Wire.h"                          // библиотека для "общения" двух Arduino
#include "SoftwareSerial.h"         // библиотека для работы с Bluetooth

#define rxPin 2
#define txPin 3

SoftwareSerial btSerial(rxPin, txPin); // инициализируем Bluetooth 

void setup()
{
  pinMode(rxPin, INPUT);
  pinMode(txPin, OUTPUT);
  btSerial.begin(38400);
  Serial.begin(38400);
  Wire.begin();
}

void loop() 
{
  while (btSerial.available()) // если что-то пришло по Bluetooth, мы это что-то отправляем на первую Arduino
  {
    Wire.beginTransmission(9);
    char c = (char)btSerial.read();
    Serial.println(c);
    Wire.write(c);
    Wire.endTransmission();
  }
}

PingPongSlave.ino

Заключение


Курсовой проект готов. Также я всё это запихнул в корпус, чтобы выглядело более менее по-человечески. Если можно назвать контейнер из-под еды, обклеенный фольгой, корпусом. Честно говоря, этот Ping-Pong стал самым интересным курсачем за всё время обучения. Мне больше нравятся практические задания, а не бумажки и какие-то непонятные вычисления. Ну а вам это пример работы с Arduino для начинающих.
Теги:
Хабы:
Данная статья не подлежит комментированию, поскольку её автор ещё не является полноправным участником сообщества. Вы сможете связаться с автором только после того, как он получит приглашение от кого-либо из участников сообщества. До этого момента его username будет скрыт псевдонимом.