Печать

Методическая разработка для кружка робототехники, электроники, урока технологии. Собираем схему и пишем программу для увлекательной электронной логической игры-головоломки “Flip-Flop”. Игра собрана на Arduino. Идею игры предложил автор многих логических игр Сергей Полозков.

В электронной игре “Flip-Flop” игровое поле состоит из 8-ми светодиодов расположенных в 2 ряда. Светодиоды могут быть только в 2-х состояниях, включено или выключено. В начале игры некоторые светодиоды включены в случайном порядке. Цель игры включить все светодиоды.

Для управления игрой имеется 3 кнопки, 2 кнопки - выбор светодиода (+/-) и кнопка "play". Кнопками +/- игрок выбирает один из светодиодов, выбранный светодиод подмигивает. Нажатие на кнопку "play" инвертирует выбранный светодиод и весь противоположный выбранному светодиоду ряд. То есть, нажатие на кнопку "play" инвертирует состояние сразу 5-ти светодиодов. Инвертирует, значит меняет состояние на противоположное.

Принципиальная электрическая схема

Для сборки электронной схемы игры “Flip-Flop” понадобится:

1. Плата Arduino NANO           1 шт.
2. Светодиоды                  8 шт.
3. Резисторы 220 Ом             8 шт.
4. Кнопки тактовые              3 шт.
5. Макетная плата              1 шт.
6. Блок питания 4,5 В           1 шт.

Рис. 1. Принципиальная электрическая схема игры-головоломки “Flip-Flop”.

На схеме, рис. 1 микроконтроллер ATMega 328 установленный на плате Arduino NANO опрашивает состояние кнопок Btn[0], Btn[1] и Btn[2]. Так же, этот микроконтроллер, следуя логике программы, записанной в его flash память, управляет подачей напряжения на светодиоды Led[0]...Led[7].

Кнопки управления Btn[0], Btn[1], Btn[2] одним своим выводом подключены к микроконтроллеру через порты D2, D3, D4 на плате Arduino. Второй вывод каждой кнопки подключён к общему проводу (GND) на плате Arduino.

Когда игрок нажимает кнопку, соответствующий порт микроконтроллера подключается к GND и на этом порту, в программе, можно обнаружить логический 0.

Когда кнопка отжата, порт этой кнопки ни к чему не подключён. Если в программе, в это время считать состояние порта, на нем может быть обнаружена логическая 1 или 0. В таких случаях говорят что порт находится в состоянии неопределённости.

В микроконтроллерах AVR, в том числе и в микроконтроллере ATMega 328 имеются внутренние резисторы сопротивлением 10 кОм, подтягивающие цифровые порты на + источника питания. По умолчанию, эти подтягивающие резисторы отключены от портов, но их можно программно включить с помощью функции

pinMode(Btn[i], INPUT_PULLUP);

в программе на C++ в Arduino IDE (интегрированная среда разработки). Здесь i — номер кнопки в программе. 

Когда кнопка не нажата и на её порт подключён подтягивающий резистор, в программе, на этом порту можно считать логическую 1, а когда игрок нажмёт кнопку, будет считываться логический 0.

const byte Btn[] = {2, 3, 4};
const byte Led[] = {5, 6, 7, 8, 9, 10, 11, 12};

void setup() {
}

void loop() {
}

Скетч 1. Программа  для игры-головоломки “Flip-Flop”.

В программе скетч 1, в первых двух строках мы создаём два числовых массива в которых, фактически, описаны подключения кнопок и светодиодов к плате Arduino. В массиве Btn перечислены порты Arduino, к которым подключены кнопки. В массиве Led перечислены порты к которым подключены светодиоды. Значениями этих массивов являются числовые однобайтовые константы. 

const byte Btn[] = {2, 3, 4};
const byte Led[] = {5, 6, 7, 8, 9, 10, 11, 12};

void setup() {
  randomSeed(analogRead(0));
  for (int i = 0; i < 8; i++) {
    pinMode(Led[i], OUTPUT);
    digitalWrite(Led[i], random(2));
  }
  for (int i = 0; i < 3; i++) {
    pinMode(Btn[i], INPUT_PULLUP);
  }
}

void loop() {

}

Скетч 2. Программа  для игры-головоломки “Flip-Flop”.

В программе скетч 2 мы наполнили функцию setup() содержанием.

В нашей программе мы планируем, для случайного включения светодиодов в начале игры, использовать функцию random(). Функция random() — генератор последовательности псевдо-случайных целых чисел в заданном параметрами диапазоне. В математической функции — генераторе псевдо-случайной последовательности имеются начальные параметры, которые влияют на порядок элементов в псевдо-случайной последовательности. Не значительное изменение одного из этих параметров приводит к тому, что функция random() начинает генерировать совершенно другую псевдо-случайную последовательность. 

Функция randomSeed() позволяет установить начальные параметры генератора псевдо случайной последовательности для функции random(). Параметром функции randomSeed() должно быть случайное число. Микроконтроллер ATMega 328, установленный на плате Arduino может сгенерировать случайное число в диапазоне от 0 до 1024 с помощью АЦП, измеряя уровень напряжения на не подключённом ни к чему аналоговом входе. Что мы и делаем в программе скетч 2 с помощью функции analogRead(). 

Далее, в программе скетч 2, в цикле for из 8 повторений, с помощью функции pinMode() мы настраиваем порты, к которым подключены светодиоды, как выходы. В функции pinMode() первый параметр — это номер настраиваемого порта, второй параметр может принимать значения констант OUTPUT, INPUT и INPUT_PULLUP. Если мы передаём в функцию pinMode() параметр OUTPUT, порт настраивается как выход. 

В этом же цикле, с помощью функции digitalWrite() на 8-ми портах, к которым подключены светодиоды, устанавливаются логические 0 и 1. Выбор 0 или 1 происходит случайным образом с помощью функции random(). Параметры функции random() определяют диапазон числовой последовательности из которой производится случайная выборка. В нашей программе, параметром функции random() является число 2. Таким образом, функция random() генерирует случайное число из числовой последовательности 0, 1. 0 — левая граница числовой последовательности (используется по умолчанию, если мы не указываем первый параметр в функции random), 2 — правая граница до которой строится последовательность. Правая граница не входит в числовую последовательность. 

Вернёмся к функции digitalWrite(). Первым параметром этой функции является номер порта на плате Arduino, а второй параметр должен принимать значение 0 или 1. Если мы, с помощью функции digitslWrite() записываем в порт 0, на соответствующем выводе платы Arduino устанавливается напряжение близкое к 0 Вольт. Все напряжения измеряются относительно общей шины (GND). Если же мы записываем в порт 1, на соответствующем выводе платы Arduino устанавливается напряжение близкое к напряжению питания микроконтроллера (5 Вольт) и загорается подключённый к этому выводу светодиод см. рис. 1.

Там же  в функции setup() имеется ещё один цикл for из трёх повторений. В этом цикле, с помощью функции pinMode(), мы настраиваем порты, к которым подключены кнопки, как входы. Здесь мы передаём функции pinMode параметр INPUT, что настраивает порт как вход. 

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

Цикл меняет линейный (построчный) порядок выполнения программы, для части кода, на циклический. В результате организации цикла, часть кода (тело цикла) выполняется несколько раз. 

Цикл for — называют параметрическим циклом (цикл с изменяющимся параметром и, чаще всего, с фиксированным числом повторений).

Для организации цикла for необходимо осуществить три не обязательные операции:

  1. Инициализацию параметра с присваиванием ему начального значения;
  2. создание логического выражения (условия повторения цикла);
  3. создание второго выражения модификатора, возможно, изменяющего параметр цикла.

Эти три операции записываются в круглых скобках и разделяются точкой с запятой ( ; ). Тело цикла (блок операторов) заключают в фигурные скобки.

Когда в программе встречается цикл:

  • Происходит инициализация параметра цикла — только один раз, когда цикл for начинает выполняться.
  • Проверка условия повторения цикла осуществляется перед каждым выполнением тела цикла. Пока условие истинно, цикл выполняется.
  • Выражение, названное нами модификатором, выполняется после каждого выполнения тела цикла.

В нашей программе, скетч 2, в первом цикле for объявлен целочисленный параметр цикла — переменная i с начальным значением равным 0. Цикл выполняется пока i<8. После выполнения двух функций входящих в тело цикла, выполняется операция i++. Переменная i увеличивается на 1.

const byte Btn[] = {2, 3, 4};
const byte Led[] = {5, 6, 7, 8, 9, 10, 11, 12};
bool btnState[] = {1, 1, 1};

void setup() {
  randomSeed(analogRead(0));
  for (int i = 0; i < 8; i++) {
    pinMode(Led[i], OUTPUT);
    digitalWrite(Led[i], random(2));
  }
  for (int i = 0; i < 3; i++) {
    pinMode(Btn[i], INPUT_PULLUP);
  }
}

void loop() {
  if (!digitalRead(Btn[0]) and btnState[0]) {
    for (int j = 0; j < 4; j++) {
      digitalWrite(Led[0], !digitalRead(Led[0]));
      delay(50);
    }
  }
  btnState[0] = digitalRead(Btn[0]);
  delay(20);
}

Скетч 3. Программа  для игры-головоломки “Flip-Flop”.

В программе скетч 3, в цикле loop() мы программируем функцию кнопки Btn[0]. Эта кнопка, при нажатии, должна выбрать соседний слева, с ранее выбранным, светодиод. Номер выбранного светодиода сохраняется в переменной n. Выбранный светодиод подмигивает 2 раза. 

const byte Btn[] = {2, 3, 4};
const byte Led[] = {5, 6, 7, 8, 9, 10, 11, 12};
bool btnState[] = {1, 1, 1};
byte n = 0;

void setup() {
  randomSeed(analogRead(0));
  for (int i = 0; i < 8; i++) {
    pinMode(Led[i], OUTPUT);
    digitalWrite(Led[i], random(2));
  }
  for (int i = 0; i < 3; i++) {
    pinMode(Btn[i], INPUT_PULLUP);
  }
}

void loop() {
  if (!digitalRead(Btn[0]) and btnState[0]) {
    n = ((n - 1) & 0b00000111);
    for (int j = 0; j < 4; j++) {
      digitalWrite(Led[n], !digitalRead(Led[n]));
      delay(50);
    }
  }
  btnState[0] = digitalRead(Btn[0]);
  delay(20);
}

Скетч . Программа для игры-головоломки “Flip-Flop”.

const byte Btn[] = {2, 3, 4};
const byte Led[] = {5, 6, 7, 8, 9, 10, 11, 12};
bool btnState[] = {1, 1, 1};
byte n = 0;

void setup() {
  randomSeed(analogRead(0));
  for (int i = 0; i < 8; i++) {
    pinMode(Led[i], OUTPUT);
    digitalWrite(Led[i], random(2));
  }
  for (int i = 0; i < 3; i++) {
    pinMode(Btn[i], INPUT_PULLUP);
  }
}

void loop() {
  if (!digitalRead(Btn[0]) and btnState[0]) {
    n = ((n - 1) & 0b00000111);
    for (int j = 0; j < 4; j++) {
      digitalWrite(Led[n], !digitalRead(Led[n]));
      delay(50);
    }
  }
  if (!digitalRead(Btn[1]) and btnState[1]) {
    n = ((n + 1) & 0b00000111);
    for (int j = 0; j < 4; j++) {
      digitalWrite(Led[n], !digitalRead(Led[n]));
      delay(50);
    }
  }
  btnState[0] = digitalRead(Btn[0]);
  btnState[1] = digitalRead(Btn[1]);
  delay(20);
}

Скетч . Программа для игры-головоломки “Flip-Flop”.

const byte Btn[] = {2, 3, 4};
const byte Led[] = {5, 6, 7, 8, 9, 10, 11, 12};
bool btnState[] = {1, 1, 1};
byte n = 0;

void setup() {
  randomSeed(analogRead(0));
  for (int i = 0; i < 8; i++) {
    pinMode(Led[i], OUTPUT);
    digitalWrite(Led[i], random(2));
  }
  for (int i = 0; i < 3; i++) {
    pinMode(Btn[i], INPUT_PULLUP);
  }
}

void loop() {
  for (int i = 0; i < 2; i++) {
    if (!digitalRead(Btn[i]) and btnState[i]) {
      n = ((n + 1 * i - 1 * not(i)) & 0b00000111);
      for (int j = 0; j < 4; j++) {
        digitalWrite(Led[n], !digitalRead(Led[n]));
        delay(50);
      }
    }
    btnState[i] = digitalRead(Btn[i]);
  }
  delay(20);
}

Скетч . Программа для игры-головоломки “Flip-Flop”.

const byte Btn[] = {2, 3, 4};
const byte Led[] = {5, 6, 7, 8, 9, 10, 11, 12};
bool btnState[] = {1, 1, 1};
byte n = 0;

void setup() {
  randomSeed(analogRead(0));
  for (int i = 0; i < 8; i++) {
    pinMode(Led[i], OUTPUT);
    digitalWrite(Led[i], random(2));
  }
  for (int i = 0; i < 3; i++) {
    pinMode(Btn[i], INPUT_PULLUP);
  }
}

void loop() {
  for (int i = 0; i < 2; i++) {
    if (!digitalRead(Btn[i]) and btnState[i]) {
      n = ((n + 1 * i - 1 * not(i)) & 0b00000111);
      for (int j = 0; j < 4; j++) {
        digitalWrite(Led[n], !digitalRead(Led[n]));
        delay(50);
      }
    }
    btnState[i] = digitalRead(Btn[i]);
  }
   if (!digitalRead(Btn[2]) and btnState[2]) {
    digitalWrite(Led[n], !digitalRead(Led[n]));
  }
  btnState[2] = digitalRead(Btn[2]);
  delay(20);
}

Скетч . Программа для игры-головоломки “Flip-Flop”.

const byte Btn[] = {2, 3, 4};
const byte Led[] = {5, 6, 7, 8, 9, 10, 11, 12};
bool btnState[] = {1, 1, 1};
byte n = 0;

void setup() {
  randomSeed(analogRead(0));
  for (int i = 0; i < 8; i++) {
    pinMode(Led[i], OUTPUT);
    digitalWrite(Led[i], random(2));
  }
  for (int i = 0; i < 3; i++) {
    pinMode(Btn[i], INPUT_PULLUP);
  }
}

void loop() {
  for (int i = 0; i < 2; i++) {
    if (!digitalRead(Btn[i]) and btnState[i]) {
      n = ((n + 1 * i - 1 * not(i)) & 0b00000111);
      for (int j = 0; j < 4; j++) {
        digitalWrite(Led[n], !digitalRead(Led[n]));
        delay(50);
      }
    }
    btnState[i] = digitalRead(Btn[i]);
  }
  if (!digitalRead(Btn[2]) and btnState[2]) {
    for (int i = not(n % 2); i < 8; i = i + 2) {
      digitalWrite(Led[i], !digitalRead(Led[i]));
    }
    digitalWrite(Led[n], !digitalRead(Led[n]));
  }
  btnState[2] = digitalRead(Btn[2]);
  delay(10);
}

Скетч . Программа  для игры-головоломки “Flip-Flop”.

 

Рис. 2. Игра-головоломка “Flip-Flop” собранная на макетной плате.