- Подписка на печатную версию:
- Подписка на электронную версию:
- Подшивки старых номеров журнала (печатные версии)
LXF105:Arduino
Материал из Linuxformat.
- Arduino Аппаратный хакинг для энтузиастов гаражной электроники
Содержание |
Arduino: Плюс счетчик очков
- ЧАСТЬ 3: Прежде чем перейти к новому эксперименту, закончим проект. Грэм Моррисон завершит игру «Саймон сказал», добавив индикатор очков.
Это третий, заключительный урок нашей серии «Создание игры “Саймон сказал” с помощью Arduino». Но не беспокойтесь, это не конец учебников по Arduino. Через месяц мы займемся совершенно другим. Игра у нас сейчас в отличной форме, с созвездием мигающих огоньков и шумным звуком. Но ваши успехи пока что можно оценить, только загибая пальцы при каждой верно угаданной мелодии. Для решения этой проблемы мы добавим счетчик очков, использовав новый компонент: классический семисегментный индикатор. Эти недорогие, технически несложные устройства ассоциируются с дешевой электроникой и применяются везде, от цифровых часов до микроволновых печей. Кроме того, они использовались во многих электронных играх из 70-х и 80-х, что добавляет ощущения подлинности нашему проекту.
Семисегментный индикатор
Окончательную принципиальную схему игры см. на http://www.linuxformat.co.uk/mag/arduino.
Существует несколько разновидностей семисегментного индикатора. Для нашего проекта мы использовали самые легкодоступные, известные под именем «с общим катодом». Другие виды могут не заработать (они способны менять полярность контактов), так что попытайтесь при возможности достать именно такой. Семисегментный индикатор состоит из, сами понимаете, семи отдельных элементов – черточек, из кото- рых образуется цифра, и к каждому имеется контакт на задней стороне компонента. Таким образом, индикатор работает в точности как сборка из семи светодиодов. В зависимости от модели, вам также могут потребоваться семь резисторов для каждого соединения. К вящей путанице, многие дисплеи включают еще и восьмой элемент, который можно употребить как запятую, но мы его проигнорируем. Чтобы управлять семью элементами, для их включения и выключения нам понадобятся семь отдельных цифровых выходов на Arduino. К счастью, сосчитав использованные контакты, вы должны обнаружить, что у нас осталось ровно семь свободных. Прежде всего сдвинем соединения, чтобы их расположение стало чуть более логичным. Вот как мы сделаем:
- Семь элементов 0, 1, 2, 3, 4, 5, 6
- Кнопки 7, 8, 9
- Светодиоды 10, 11, 12
- Динамик 13
Освободив место для индикатора на цифровом разъеме Arduino, следующим шагом воткните элемент на свободный участок макетной платы; удобно будет поместить его над центральной канавкой. Индикатор должен находиться над канавкой, а контакты окажутся по обе стороны от нее. Убедитесь, что вы видите, c какой дорожкой соединен каждый контакт, так как нам сейчас нужно подключить каждый контакт через дорожки к соответствующим выходам на Arduino. Семисегментные индикаторы имеют стандартную маркировку, где каждый элемент обозначен буквами А-G, двигаясь по часовой стрелке сверху. Сверьтесь с нашей диаграммой, как они помечены. Теперь подключайте каждый элемент, начиная с А, к соответствующему контакту на Arduino, начиная с 0 (нуля), так, чтобы задействовать все элементы. Кроме того, необходимо подключить GND (землю) макетной платы к GND на индикаторе. Вот и все нужные нам соединения. Теперь добавим программную логику для управления.
Рулим индикатором
Откройте исходный код нашего проекта и убедитесь, что переменные ledPin, inPin и speakerOut, присутствующие в верхней части файла, отражают новый порядок соединений Arduino. Теперь добавим переменные для индикатора, но сделаем это немного иначе, чтобы сэкономить место в журнале. Вместо семи различных чисел и семи присваиваний, используем массив, содержащий номер контакта для каждого сегмента. Преимущество такого подхода, помимо экономии чернил, заключается в том, что можно изменять порядок подсоединения контактов, просто корректируя значения в массиве.
/* DISPLAY ORDER: a,b,c,d,e,f,g */ int segPin[7]={0,1,2,3,4,5,6};
Кто не сталкивался с массивами ранее, представьте его себе как строку таблицы, причем размер массива определяется длиной строки. Сейчас мы собираемся создать еще один, только на этот раз он будет двумерным – похожим на электронную таблицу со строками и столбцами. Каждая строка содержит сигналы «вкл» и «выкл», чтобы зажигать необходимое количество сегментов на дисплее, и каждая колонка ссылается на определенный сегмент:
bool segNum[10][7]={ {1,1,1,1,1,1,0}, {0,1,1,0,0,0,0}, {1,1,0,1,1,0,1}, {1,1,1,1,0,0,1}, {0,1,1,0,0,1,1}, {1,0,1,1,0,1,1}, {1,0,1,1,1,1,1}, {1,1,1,0,0,0,0}, {1,1,1,1,1,1,1}, {1,1,1,1,0,1,1}, };
Нам надо хранить значения только «вкл» и «выкл», поэтому для двумерного массива выбран тип «bool». Это сокращение от Boolean [логический], то есть его значение может быть либо «истиной», либо «ложью» (1 или 0). Так мы сэкономим память, что особенно важно при работе с устройствами вроде Arduino, где ее всего ничего [еще большей экономии памяти можно добиться, закодировав каждый элемент массива sigNum в виде двоичного числа, например, sigNum[0]=1111110b=126, т.е. символ нуля – прим.ред.]. Теперь инициализируем контакты, которые мы используем для индикатора – это можно сделать, добавив следующие строки в функцию setup():
Для увеличения уровня очков до 16 можно использовать шестнадцатеричный дисплей. Увеличьте размер segNum до [16] [7], замените проверку на ошибки в displayNum на number<=15 и добавьте следующие строки к элементам массива:
{1,1,1,0,1,1,1}, {0,0,1,1,1,1,1}, {1,0,0,1,1,1,0}, {0,1,1,1,1,0,1}, {1,0,0,1,1,1,1}, {1,0,0,0,1,1,1}
for (int i=0; i<7; i++){ pinMode(segPin[i], OUTPUT); }
Цикл for перебирает массив segPin и инициализирует контакты, оответствующие текущему элементу массива. Итак, мы все настроили; осталось только написать функцию вывода нужных нам чисел. Назовем ее displayNum:
void displayNum (int number) { if (number <= 9){ for (int i=0; i<7; i++){ if (segNum[number][i]){ digitalWrite(segPin[i], HIGH); } else { digitalWrite(segPin[i], LOW); } } } }
Она проще, чем кажется. Первое условие if – это просто проверка на ошибку, чтобы убедиться, что мы не пытаемся вывести номера больше, чем 9 (индикатор не способен показывать такие числа). После этого мы перескакиваем в цикл for, который зажигает или гасит каждый элемент дисплея в соответствии со значениями, хранящимися в строке, определенной переменной number – ее значение мы передаем в функции. Все, что нам сейчас нужно сделать, это вызвать функцию с нужным числом очков.
Так как цифр можно изобразить всего 10, показывать число шагов на индикаторе нелепо: даже самый посредственный игрок сумеет заработать больше девяти очков. Вместо этого будем показывать нечто вроде уровней, деля число очков на три и выводя уровень на индикатор. В результате через каждые три успешные шага счет на дисплее будет увеличиваться на единицу – итого 27 шагов, чтобы добраться до девятого уровня; задача достаточно сложная. Для вывода уровня просто добавьте displayNum(score/3) в цикл if(winning) в конце нашей программы. Цикл станет таким:
if (winning) { displayNum(score/3); score++; }
Вот и все, что нужно сделать – ну, почти все: не решен лишь один вопрос. Если вы подключили выход динамика к контакту 13, как говорилось в начале данного урока, вы уже могли заметить проблему: уровень звука на входе 13 еле слышим. А все потому, что контакт 13 на самом деле предназначен для тестирования: он уже содержит резистор, снижающий и ток, и громкость динамика. К счастью, в Arduino есть недокументированная функция, которая нам поможет. Контакт 3 разъема ICSP на плате Arduino (следующий после переключателя reset) – это еще один контакт 13, но без резистора; можете подключить динамик к нему и вновь обрести звук. Но будьте осторожны: если динамик по-прежнему подключен к 9-вольтовому источнику, короткое замыкание с одним из других контактов на разъеме ICSP может повредить вашу плату Arduino. Убедитесь, что питание отключено и ваш динамик не присоединен к другим контактам.
Наша простая игра «Саймон сказал» теперь полностью функциональна. Конечно, как и все великие сериалы, она еще только началась. Наш код нуждается в хорошей проверке на ошибки, и неплохо бы добавить еще одну кнопку для перезапуска текущей игры, так как нажатие reset на плате отнимает слишком много времени. А можете придумать, как перенести схему с макетной платы и установить ее в подходящий корпус, в стиле семейных развлечений 1970-х. Удастся – пришлите нам фотографию. LXF
Декодируем индикатор
Иногда бывает трудно определить, какой элемент на семисегментном индикаторе к какому контакту подключен. Если у вас нет описания, то проще всего выяснить это методом проб и ошибок. Подключите индикатор к Arduino и напишите простую программу для запуска элементов в определенном порядке. Сделав задержку достаточно долгой, вы легко разглядите, какие именно элементы в настоящее время подключены к определенным контактам. Кстати, мы именно это и делаем в нашей программе – просто измените значения элементов массива на следующее (вместо символов, которые там были заданы):
bool segNum[10][7]={ {1,0,0,0,0,0,0}, {0,1,0,0,0,0,0}, {0,0,1,0,0,0,0}, {0,0,0,1,0,0,0}, {0,0,0,0,1,0,0}, {0,0,0,0,0,1,0}, {0,0,0,0,0,0,1}, {0,0,0,0,0,0,0}, {0,0,0,0,0,0,0}, {0,0,0,0,0,0,0}, };
Теперь замените основной цикл следующим:
for (int i=0; i<9; i++){ displayNum(i); delay (300); }
Тут перебираются все элементы индикатора, через длинные паузы. Без информации, куда подцеплен каждый элемент, последовательность на индикаторе покажется случайной, но если записать порядок, в котором зажигались элементы, вы сможете определить, какой контакт чем управлял, и либо перецепить провода, либо изменить порядок контактов в массиве segPin.