«Easy things should be easy and hard things should be possible» «Простые вещи должны оставаться простыми, а сложные — стать выполнимыми»
Ларри Уолл, разработчик языка Perl.
[Вместо предисловия]
Хотелось начать повествование банально, но на самом деле я не страдал от скуки, не ошалел от внезапно навалившегося свободного времени, и у меня не завалялась случайно в кладовке пара бесхозных контроллеров. Идея проекта возникла совершенно инстинктивно на уровне подсознания, ностальгирующего по всяким несбывшимся мальчишеским мечтам. Очень вдруг захотелось собрать своими руками несложный игровой программно-аппаратный комплекс, с графикой низкого разрешения, и наполнить ее кодом почти забытых, но таких милых сердцу, старых компьютерных игр. А кто мало-мальски умеющий программировать и рожденный в СССР не мечтал сделать свой Тетрис? Еще очень хотелось в этом проекте от души поэкспериментировать с железом и протоколами обмена данными. По специальности я врач, а электроника и программирование лишь мое хобби, поэтому не судите строго все, что будет представлено ниже. Сам же я буду нещадно критиковать некоторые свои решения и реализации, так как они в итоге оказались или слабыми, или избыточными, а порой и вовсе ошибочными.
[Подготовка]
Для начала я решил изучить идею создания своей игрушки (назвал её Tetris-Box) и поискать прототипы. Поиск в интернете оказался весьма результативен. Я обнаружил десятки первопроходцев, которые опубликовали свои замечательные проекты. Основная масса их иностранцы, но приятно, что несколько авторов оказались соотечественниками. Интерес мой был предельно удовлетворен, и сразу захотелось сделать лучше, больше и мощнее... Сделать что-то такое на пределе возможностей Arduino и своих возможностей.
Комплектующие в основной массе заказал на Ebay. Стоит отметить, что этот способ покупки меня ни разу не разочаровал. Во-первых, можно найти практически все, и во-вторых, все было точно, как в аптеке, все работало и получилось совсем не дорого. Единственная проблема – долго ждать, но я никуда не торопился. По мере поступления деталей начал собирать фрагменты схемы и тестировать отдельные модули на пригодность для использования. В это же время собирал по кусочкам код. На подготовку ушло почти 2 месяца.
[Аппаратная часть]
Схема игрушки состоит из следующих блоков:
• Центральный процессор (Arduino MEGA2560), он выполняет все основные функции Tetris-box (см. описание ниже). • Дисплей светодиодный RGB 16х24 (Funduino + 8x8 RGB LED Matrix 6 шт.). • Аналоговая клавиатура (игровая раздельно 3+2 кнопки, управление плеером MP3: 5 кнопок). • 3х-осный акселерометр (MAA7455). • Вспомогательный контроллер (ATmega328), он управляет MP3 проигрывателем, вибродвигателем и цифровым дисплеем (см. описание ниже). • 7-полосный эквалайзер (MSGEQ7, 2 шт.). • MP3 проигрыватель (Grove Serial_MP3_Player). • Звуковая система (2 платы УНЧ MP410 2х2.2 Вт на основе микросхем TPA2012D + 2 динамика по 2 Вт). • Вибродвигатель (Grove Vibration Motor). • Цифровой дисплей на 10 знаков (MAX7219 2 шт., A-5161BS 10 шт.). • Вентиляторы охлаждения (кулер 4х4 см. – 5V, 2 шт.).
[Центральный процессор (ЦП)]
Сердцем игрушки стал Arduino Mega 2560 [1]. Это устройство на основе микроконтроллера ATmega2560, в котором есть все необходимое для удобной работы: 54 цифровых входа/выхода, 16 аналоговых входов, 4 последовательных порта UART, FLASH-память для программ 256 килобайт и очень большое ОЗУ - 8 килобайт. На самом деле мне не удалось задействовать даже половины всех возможностей ATmega2560, но вот оперативной памяти временами катастрофически не хватало. Так что выбор был достаточным и необходимым.
Платформа программируется посредством среды разработки Arduino. Микроконтроллер ATmega2560 поставляется с записанным загрузчиком, облегчающим запись новых программ без использования внешних программаторов прямо через USB.
На ЦП выполняется основная игровая программа, обрабатываются нажатия кнопок клавиатуры и значений акселерометра, рендерится графика и происходит обмен данными со вспомогательным контроллером.
[Светодиодный дисплей RGB 16х24]
Я решил сделать дисплей на светодиодных матрицах RGB 8х8. Размер экрана сразу ограничил до 16х24 пикселей, что в конечном итоге и определило габариты всего устройства. Меньше мне показалось не круто, а больше в руках держать было бы не удобно.
В качестве контроллера RGB LED матрицы я выбрал плату Funduino, клон Colorduino [2]. Этот контроллер специально разработан для управления трехцветными светодиодными матрицами, и достаточно хорошо документирован. В его основе использованы микроконтроллер ATMega328P, 8 канальный усилитель тока M54564 и 24 - канальный драйвер управления светодиодами DM163. Чип DM163 позволяет задавать 16384 полутонов по каждому цвету. Использование контроллеров RGB LED матриц позволило освободить центральный процессор, управляющий всей системой, от дополнительных вычислений.
Физические матричные индикаторы бывают двух модификаций: с круглым и квадратным пикселем. В экспериментах с ними мне показалось, что квадратный пиксель для моих задач выглядит более эффектно, поэтому я остановил свой выбор на них [3].
Плата контроллера чуть меньше матрицы, и спроектирована так, что их можно соединять друг с другом, получая дисплеи большего размера. Аппаратные интерфейсы контроллера работают по протоколам I2C и UART, и позволяют подключать его к устройствам линейки Arduino.
В блок дисплея состоит из следующих основных компонентов:
• Funduino V2 (Funduino) - 6 шт. • 8x8 матричный RGB дисплей с общим анодом – 6 шт.
Размер общего полученного дисплея - 16х24 пикселя, 12х18 см.
Свой дисплей я собрал из трех пар контроллеров, соединенных друг с другом. Каждая пара подключалась к центральному процессору независимо от других пар по протоколу UART. Данные передаются только в одну сторону – от ЦП в блоки дисплея, без обратной связи.
Почему так? В сети я нашел достаточное количество примеров подключения Funduino к Arduino по протоколу I2C. ATmega2560 и ATMega328P имеют аппаратную реализацию протокола I2C (TWI). Все примеры работали замечательно, пока соединение ограничивалось одним или двумя контроллерами. При большем количестве соединений картинка обновлялась слишком медленно. Штатная скорость передачи данных по I2C – 100 кбит/сек, но на практике же она не превышала 15-20 кбит/сек. Ситуация не изменилась и при настройке протокола на скорость 400 кбит/сек. Вероятно, все дело в том, принимающий данные контроллер в прерывании таймера занят отображением данных на светодиодной матрице, что заметно влияет на скорость приема и обработки данных в основном цикле программы. При обновлении кадра каждый из 6 контроллеров должен получить 192 байта информации, соответственно для всех - 1152 байта. В моем эксперименте обновление кадра происходило более чем за 500 мс, что было совершенно неприемлемо. Допускаю, что я что то делал не так, но улучшить эту ситуацию мне не удалось.
Потом начались эксперименты с UART. Здесь все было совсем не просто, но результаты получились намного лучше. UART в контроллерах ATMEL так же реализован аппаратно, и он очень быстрый (до 2 мбит/сек). Получение данных на стороне приемника можно организовать по прерыванию. Но в UART не предусмотрена адресация приемников, поэтому передать данные можно только сразу все и одновременно всем ведомым устройствам. Каждый Funduino был запрограммирован индивидуально, имел личный номер и «знал» свою позицию на экране. Так, получив весь массив данных, он брал из них свой кусочек и отображал на дисплее. В принципе и с одним UART этот принцип почти заработал, но 2 КБ SRAM ATmega328 в Funduino были задействованы полностью, что выражалось в неустойчивой работе. В качестве центрального процессора в проекте я использовал Arduino Mega 2560. Он имеет на борту 4 UART и это решило все проблемы. Соединив Funduino по парам и повесив каждую на свой UART, я получил и более, чем достаточную скорость обновления картинки, и рационально использовал память, и упростил код. Теперь каждая пара Funduino получала 384 байта данных и каждый из них забирал свою половину.
Идея работала, раздражало только не сильное и не регулярное мерцание дисплея. Теперь это было связано с тем, что код отображения данных на светодиодной матрице выполнялся в главном цикле и конечно прерывался во время приема данных. В эксперименте удалось найти компромиссную скорость передачи, когда мерцание стало незаметным.
Все Funduino были смонтированы вместе на монтажной PCB подходящего размера при помощи пластикового крепежа (см. фотогалерею ниже во врезке).
[3х-осный акселерометр]
Предполагалось, что игрушка будет управляться не только кнопками, но и движением (наклонами) корпуса. Для этого необходимо знать положение корпуса относительно Земли. В этом могут помочь датчики, определяющие ускорение. Одним из таких, является акселерометр компании NXP Semiconductors MMA7455L [4]. Этот крошечный чип способен замерять вектор кажущегося ускорения по трем осям, оцифровывать результат и отправлять полученные данные по интерфейсам SPI или I2C(TWI). Точность замера ускорения — 10 бит. Чувствительность акселерометра может выбрать пользователь (±2g, ±4g и ±8g).
Для связи акселерометра и Arduino Mega 2560 по умолчанию используется интерфейс I2C, поэтому вход CS подключен к питанию. Чипу на шине I2C присвоен адрес 1Dh (см. [4]).
Для меня до сих пор загадка, что там внутри этой крошечной микросхемки, но то, что благодаря ей, можно определять ориентацию игрушки в трёхмерном пространстве, меня приятно взволновало. Знакомство с датчиком такого уровня сложности для меня вообще было впервые, и я провел ни один битый час над изучением его повадок: калибровка, погрешность, шумы, фильтр Калмана, занимательная тригонометрия, скорость движения. Когда эта штука начала меня слушаться, мне показалось, что я постиг тайны мироздания. На самом же деле все было очень скромно, я просто научился различать направления движения, углы наклона корпуса и вычислять скорость этих наклонов, чего для моих задач оказалось более, чем достаточно.
[Вспомогательный контроллер (ВК)]
Помните 486DX? Мне когда-то очень нравилась сама идея сопроцессора, когда в системе есть специализированный процессор, который функционирует параллельно, расширяет функционал центрального процессора и избавляет его от лишней вычислительной нагрузки. Я решил эту идею реализовать и добавил в систему контроллер ATmega328 [5], на который повесил всю вспомогательную периферию. ВК отвечает за вывод значений на цифровой дисплей, а также управление MP3 проигрывателем, спикером, вибродвигателем и 7 полосным анализатором спектра. Возможно, что с этими задачами вполне мог бы справится сам Arduino Mega 2560 и все это просто мои фантазии, но какое поле для экспериментов. И ничто не могло меня остановить =).
Связь ЦП и ВК осуществлена через интерфейс I2C. Микроконтроллеры Atmega имеют аппаратную поддержку интерфейса I2C (TWI). Линии интерфейса SDA и SCL у микроконтроллера ATmega328 сидят на ножках c номерами 27 (PC4) и 28 (PC5), соответственно, на плате Arduino Mega, SDA — это цифровой порт 20, а SCL — цифровой порт 21. Была разработана несложная система команд, благодаря которым ЦП управлял ВК и получал необходимые данные.
[MP3 проигрыватель и усилитель]
Конечно же моя игрушка сразу замышлялась со звуком. Меню программы, видеоэффекты, игры и даже отдельные их эпизоды могут иметь свое фоновое звуковое сопровождение. В этом мне помог Grove - Serial MP3 Player [6], который выполнен на базе чипа WT5001 [7] и обладает очень большим потенциалом. Проигрыватель имеет несколько периферийных портов: стандартный интерфейс UART, стандартный разъем для стереонаушников, линейный выход, а также интерфейс карты памяти MicroSD.
Цифрами на рисунке помечены:
(1). Левый канал линейного выхода. (2). Разъем 3.5 мм для стереонаушников. (3). Правый канал линейного выхода. (4). Чип WT5001. (5). Светодиодный индикатор: светится, когда идет воспроизведение. (6). Последовательный интерфейс UART (RX, TX, VCC, GND). (7). MicroSD карта до 2 GB.
Простой на первый взгляд модуль оказался вполне функциональным и очень послушным. Посылая команды к модулю через последовательный порт, можно контролировать состояние воспроизведения MP3, выбирать композицию по номеру, изменять громкость и режим воспроизведения. Проигрыватель подключен к управляющему контроллеру ATmega328 посредством программного последовательного интерфейса, реализованного в известной библиотеке SoftwareSerial [8] . В игрушке предусмотрено и аппаратное управление воспроизведением и громкостью при помощи кнопок. Аналоговая 5-кнопочная клавиатура вынесена на правую панель корпуса.
Все музыкальные композиции в определённом порядке записаны на MicroSD. Это важно, так как воспроизведение возможно только по порядковому номеру композиции, а не по имени файла.
Выходная мощность MP3 проигрывателя для работы на динамики оказалась недостаточной, поэтому я добавил в проект компактный стереофонический усилитель «D»-класса 2 х 2.2 Вт [9].
Звук игрушки воспроизводится на 2-ваттные динамики (каждый сопротивлением 4 ом), которые, которые установлены в боковые панели корпуса. Можно подключить наушники, тогда динамики автоматически отключаются. Я конечно еще тот меломан, но чистота и громкость звука меня вполне удовлетворили. При монтаже MP3 проигрывателя допустил досадную конструктивную ошибку. Не предусмотрел возможность извлечения карты MicroSD без разборки корпуса. Думал не понадобится.
[Резистивная (аналоговая) кнопочная клавиатура]
Идея заключена в том, что несколько резистивных делителей напряжения с разными коэффициентами деления с помощью кнопок подключаются к аналоговому выходу контроллера. Нажатие каждой кнопки задает определённый уровень напряжения, которое можно измерить в программе и определить, какая именно из кнопок была нажата. Ценность такой клавиатуры в том, что на один аналоговый выход можно повесить несколько кнопок. Однако есть и серьезные недостатки. Во-первых, это время обработки нажатия. В устройствах, требующих высокой скорости реакции на нажатие, этот вариант я бы не рекомендовал. Во-вторых, это сложность обработки комбинированных нажатий кнопок. А в целом получилась забавная штука для программирования. В игрушке задействовано 3 клавиатуры. Одна для управления MP3 проигрывателем (кнопки "ПУСК-ПАУЗА", "ВПЕРЕД", "НАЗАД", "ГРОМКОСТЬ +", "ГРОМКОСТЬ -") и две (2 кнопки слева "ВЛЕВО", "ВНИЗ" и 3 кнопки справа "ВЫБОР", "ВПРАВО", "ВВЕРХ") для управления игровым процессом. Сначала я проектировал одну общую клавиатуру для управления игрой, но в эксперименте обнаружил невозможность обработки сочетанных нажатий. Позже я нашел и другие схемы подключения, где комбинированные нажатия кнопок предусмотрены сразу. Переделывать все полностью я поленился, поэтому после небольших доработок просто разделил кнопки на 2 группы и повесил правую и левую клавиатуры на отдельные аналоговые входы, что дало возможность комбинировать нажатия некоторых кнопок. Мне этого было вполне достаточно.
Выходное напряжение делителя легко рассчитать по известной формуле или измерить вольтметром, а можно считать правильные значения непосредственно контроллером. Я поступил именно так. Написал тестовую программу, которая делала 50 выборок напряжения при нажатии на кнопку и вычисляла среднее. Пожалуй, это самый правильный вариант, так как это было хорошей разминкой в написании кода, да и номиналы резисторов могут отличаться от тех, которые на схеме (схема ниже отражает только общий принцип работы аналоговой клавиатуры).
[Цифровой дисплей]
Матричный дисплей с разрешением 16х24 имеет недостаточные возможности для визуализации цифровой информации во время игры. 10-значный цифровой светодиодный дисплей специально включен в проект для отображения счета, уровня, времени и т.п., без чего невозможно обойтись в любой электронной игрушке.
Драйвер MAX7219 [10] позволяет управлять семисегментными индикаторами с общим катодом. Максимум к нему можно подключить 8 индикаторов. В игрушке таких индикаторов 10, поэтому понадобилось 2 микросхемы MAX7219, включенные каскадом (DOUT подключен к DIN).
Драйвера управляются по интерфейсу SPI (в режиме 0) с частотой до 10 МГц. Нужно отметить, что драйвер MAX7219 не полностью соответствует стандартам SPI, но тем не менее его можно программировать по SPI без особых ограничений.
Индикатор собран на PCB подходящего размера, все соединения выполнены монтажным проводом, заработал сразу и заняло это все не более 2 часов. Когда засветились циферки, не без гордости вдруг вспомнил, как в 1984 году (9 класс) паял частотомер на серии К155 с 5-ю индикаторами. Как изменился с того времени мир...
[Спикер и вибродвигатель]
Спикер и вибродвигатель добавлены в игрушке для создания звуковых спецэффектов и эффекта вибрации корпуса. Звуки спецэффектов генерируются при помощи процедуры Arduino Tone() и выводятся не спикер. Но то ли подумал я недостаточно, то ли делал что-то криво, то ли спикер [11] купил неудачный, но такая реализация меня не устроила: звук был отвратительный, громкость практически не регулировалась. Позднее я отказался от спикера и стал подавать сигнал прямо с контакта ATmega328 через резистор на вход правого канала усилителя.
Вибродвигатель [12] прекрасно вписался в проект и позволил прикоснуться к технологии тактильной отдачи, когда, к примеру, не только видишь и слышишь касание мячика ракетки, но чувствуешь это руками. Управление двигателем очень простое: подали питание- вибрирует, отключили - отдыхает.
[7-полосный анализатор спектра на основе микросхемы MSGEQ7]
Благодаря этому устройству, игрушку можно использовать в режиме цветомузыки или как дисплей эквалайзера. Эту забавную микросхему выпускает компания Mixed Signal Integration [13]. Она представляет собой 7-полосный фильтр для графического эквалайзера. Чип способен из входного аудиосигнала выделить частотные полосы 63 Гц, 160 Гц, 400 Гц, 1 кГц, 2.5 кГц, 6.25 кГц и 16 кГц. В игрушке я использовал по одной микросхеме на правый и левый канал.
Работать с MSGEQ7 просто, она управляется по двум цифровым входам RESET и STROBE. После подачи стартового импульса RESET и небольшой задержки, достаточно подать семь импульсов на линию STROBE, и после каждого стробирующего импульса, на выходе ANALOG OUT будет появляться напряжение, пропорциональное содержанию одной из семи частотных полос в аудиосигнале. С двумя микросхемами я скорее всего погорячился. Можно было вполне обойтись и одной, подключив к ней оба канала. Но проверить это я не успел, так как до реализации цветомузыки в игрушке руки пока не дошли.
[Корпус и финальная сборка игрушки]
Корпус для игрушки подходящего размера 225х165х65 мм я нашел в магазине Чип и Дип [14]. Он выполнен из светло-серого пластика, который легко обрабатывается. Все технологические отверстия и окна были сделаны без особых сложностей при помощи свёрл, лобзика и напильников. Механическая прочность верхней крышка после выпиливания окон для графического и цифрового дисплеев понизилась, поэтому ее пришлось усилить накладками изнутри. Штатные черные боковые панели корпуса мне не понравились, и я заменил на светлые стеклотекстолитовые.
Сборка игрушки была хаотичной, но энергичной и смелой. Места в корпусе было достаточно, поэтому без особых переделок и дублей все модули заняли свои почетные места. Немного пришлось повозиться с подгонкой дисплея, что бы он стоял вровень с верхней крышкой. Внутренняя компоновка получилось достаточно удобный и функциональным, так как потом не раз приходилось полностью все разбирать и собирать по новой, но большого труда это не составляло. Практически для всех модулей (MP3 проигрыватель, усилитель, вибродвигатель, акселерометр и т.п.) я предусмотрел свои посадочные места в специальные разъемы и все соединения между модулями выполнены так же кабелями с разъемами (см. фотогалерею во врезке).
На заднюю панель я вывел USB разъем и разъем питания Arduino Mega 2560, включатель вентиляторов охлаждения и еще один дублирующий разъем питания. На передней панели оказался вход для наушников. На боковых панелях размещены динамики, вентиляторы охлаждения, клавиатура управления MP3 проигрывателя, а также кнопка Reset.
С вентиляторами охлаждения все вообще получилось забавно. Когда-то мне пришлось заглянуть внутрь геймпада, где я с удивлением обнаружил кулер, при полном отсутствии электронных компонентов с повышенной теплоотдачей. О его истинном назначение я узнал позже. Оказалось, что он там для охлаждения рук игрока. В игрушке я планировал охлаждать электронные компоненты в замкнутом корпусе, но оказалось, что никакого серьезного нагрева не происходит. Поэтому кулеры только для охлаждения рук, а если честно, то это просто лишние детали.
С современной художественной и эстетической точки зрения такой корпус просто ужасен, но это образец «шедевральной самоделки» из моих 80-х.
Первые эксперименты с выводом на матричный индикатор:
Идет процесс отладки:
Принцип крепления модулей графических индикаторов Funduino:
Все модули графического индикатора закреплены:
Начальные стадии сборки конструкции Tetris-Box:
Соединение модулей друг с другом:
Вид на выключатель питания и коннекторы:
Вид на боковую панель управления и динамики:
[Программирование]
Если внимательно посмотреть на аппаратную часть игрушки, то нетрудно убедиться, что в ней задействовано целых 8 микроконтроллеров: 6 ATMega328P в Funduino, 1 вспомогательный контроллер ATMega328P и центральный процессор ATMega 2560. Все программы для контроллеров написаны на языке С++ в среде Arduino. Среда Arduino, мне показалась не слишком развитой и удобной для комфортного программирования, но вполне себе такая достаточная для проектов такого уровня. Сразу сообщу, что не я ставил перед собой задачи написать всё и «с нуля», да и вряд ли бы я осилил такое непростое программирование со своими любительскими навыками. Так и получилось, что-то написал я сам, что-то нашел в сети и адаптировал к своим задачам. Очень большой удачей было обнаружить достаточно свежий проект (2016 год, чуть-чуть меня опередил) от пользователя Dragon-dreamer [15]. О, это код очень высокого полета! С доработками под аппаратную часть игрушки он лег в основу моего проекта. Программы игрушки я опишу схематично и просто, акцентируя только на важных для понимания общих принципов местах. Если кто-то из любопытства все же захочет посмотреть исходники, то предупреждаю, что они в рабочем беспорядке, так как все еще в процессе и с комментариями на English, так как среда Arduino пару раз сделала строки на кириллице не читаемыми.
В игрушке сотрудничают три отдельные программы: основная игровая программа на ATMega 2560, Firmware на всех Funduino и Firmware вспомогательного контроллера.
Funduino. Напомню, что Funduino собраны парами, и каждая из трех пар получает данные от центрального процессора по одному из трех USART. Каждая Funduino знает свой порядковый номер в паре 0 или 1. Это важно, так как по этому признаку микроконтроллер Funduino берет из массива полученных данных свою часть. Полученные данные это ничто иное как величины яркости соответствующих точек матрицы, которые микроконтроллер в цикле построчно передает в светодиодный драйвер DM163 [16], создавая устойчивое изображение. В оригинальных примерах прошивки Funduino [17], найденных в сети, получение данных происходит в главном цикле программы, процедура управления DM163 работает по прерыванию и обновление полного кадра происходит с частотой 100-120 Гц. В своих экспериментах при таком алгоритме мне не удалось добиться устойчивого получения данных на скорости превышающей 38400 бит/сек. При большей скорости данные терялись. Мне показалось логичным попробовать и прием данных и управление драйвером обрабатывать в прерывании. Ожидалось, что эти прерывания как-то сами договорятся друг с другом и подружатся. На деле же прерывание по таймеру откровенно мешало прерыванию по приему байта по USART. Идея работала, но передача была возможна только на малых скоростях. Тогда я вынес процедуру управления DM163 в основной цикл, и произошло чудо. Не без глюков, конечно, но мне удалось обеспечить передачу данных на скоростях до 1 мбит/сек. Побочным эффектом было нерегулярное мерцание некоторых линий матрицы. На меньших скоростях от мерцания удалось избавиться. В проекте я остановился на скорости передачи 250000 бит/сек, что позволило обновлять кадр дисплея за 12-15 мс.
Заострю внимание на трех фрагментах кода:
1. Инициализация USART.
// Установка скорости передачи 250000 бит/сек:
UBRR0 =3;
// Разрешить приемник, передатчик и прерывание по завершению приема байта:
UCSR0B = (1<< TXEN0)|(1<< RXEN0)|(1<< RXCIE0);
if (data_ready)
{
// Получены все данные.
cli(); // запретить прерывания
data_ready =false; // сбросить флаг готовности данныхif (LCDM_A%2==0)
{
// Матрица первая в паре, взять первые 192 байта:
memcpy(Colorduino.curDrawFrame, LCDM_buf, 192);
}
else
{
// Матрица вторая в паре, взять следующие 192 байта:
memcpy(Colorduino.curDrawFrame, LCDM_buf +192, 192);
sei(); // разрешить прерывания
}
// Выводим построчно данные матрицы в DM163:
Colorduino.refresh();
// Задержка 800 мкс:
Colorduino.delay_us(822);
}
...
3. Прерывание по завершению приема байта.
ISR(USART_RX_vect)
{
switch(rx_MODE)
{
case RX_WAIT_SYNC:
// Режим ожидания синхронизации.if (data_count >=8)
data_count =0;
// Берем байт из регистра приемника USART и кладем в буфер команды:
data_in[data_count] = UDR0;
if ((data_in[data_count++] == CHAR_NEWLINE))
{
// Получен End of line!if ((data_in[0]=='S')
& (data_in[1]=='Y')
& (data_in[2]=='N')
& (data_in[3]=='C'))
{
// Принята команда синхронизации (SYNC).// Очистка буфера команды:
memset(data_in, 0, sizeof(data_in));
// Инициализируем переменные:
data_ready =false;
data_count =0;
// Переход в режим приема реальных данных:
rx_MODE = RX_RECEIVE_DATA;
}
data_count =0;
}
break;
case RX_RECEIVE_DATA:
// Режим приема данных.// Берем байт из регистра приемника USART и кладем в буфер данных:
LCDM_buf[data_count++] = UDR0;
if (data_count == LCDM_BUF_SIZE)
{
// Данные готовы.// Устанавливаем флаг, который проверяется в главном цикле:
data_ready =true;
// Переходим в режим ожидания синхронизации:
rx_MODE = RX_WAIT_SYNC;
}
break;
}
}
Список модулей программы Funduino:
Название модуля
Назначение
MX_CDUINO.ino
Инициализация. Основной цикл и обработчик прерывания по приему байта по USART
Colorduino.h, Colorduino.cpp
Код управления DM163
Вспомогательный контроллер (ВК). ВК управляет периферийными устройствами, получая команды от ЦП. Обмен данными с ЦП идет по протоколу I2C. Для работы с протоколом I2C у Arduino есть штатная библиотека Wire, которая упрощает взаимодействие с I2C/TWI-устройствами, как в режиме master (главное устройство на шине), так и в режиме slave (подчиненное устройство). ВК, который работает в режиме slave, при инициализации библиотеки присвоен адрес h08. Для управления была разработана система команд. Каждая команда начинается и заканчивается байтом h7F. Второй байт указывает длину команды без учета стартового и стопового байта. Третий байт код команды. Остальные байты до стопового – аргументы и параметры.
Список основных команд ВК:
Имя команды
Код
Назначение
O_NONE
7F 02 00 7F
Пустая команда
O_PLAY_SD
7F 04 A0 00 01 7F
Команда «Воспроизведение» для MP3 проигрывателя с номером композиции
O_PAUSE
7F 02 A3 7F
Команда «Пауза» для MP3 проигрывателя
O_STOP
7F 02 A4 7F
Команда «Стоп» для MP3 проигрывателя
O_NEXT
7F 02 A5 7F
Команда «Следующая» для MP3 проигрывателя
O_PREVIOUS
7F 02 A6 7F
Команда «Предыдущая» для MP3 проигрывателя
O_SET_VOLUME
7F 03 A7 1F 7F
Установка громкости MP3 проигрывателя
O_READ_VOLUME
7F 02 C1 7F
Получить значения громкости
O_PLAY_MODE
7F 03 A9 02 7F
Установка вида воспроизведения MP3 проигрывателя
O_READ_PLAY_STATE
7F 02 C2 7F
Получить режим воспроизведения
O_7SEG_SET_NUMBER
7F 08 E0 01 02 00 00 00 00 7F
Вывести число на цифровой дисплей
O_READ_MSGEQ7
7F 02 E1 7F
Получить данные эквалайзера
O_7SEG_SET_INTENSITY
7F 03 E2 09 7F
Установить яркость свечения цифрового дисплея
O_VIBRO
7F 04 E3 00 00 7F
Включить вибродвигатель на указанный интервал времени
O_TONE
7F 07 E5 00 00 00 00 00 7F
Генерировать звук с указанной частотой и длительностью. Используется функция Arduino Tone()
O_7SEG_CLEAR
7F 02 E6 7F
Очистить цифровой дисплей
O_7SEG_SET_CHAR
7F 04 E7 01 48 7F
Вывести символ на цифровой дисплей в указанной позиции
O_7SEG_SET_DIGIT
7F 04 E8 01 05 7F
Вывести цифру на цифровой дисплей в указанной позиции
O_KEY_CTRL
7F 03 B0 01 7F
Запретить или разрешить ручное управление с клавиатуры MP3 проигрывателем
При получении правильной команды ВК распознает, выполняет ее и возвращает код подтверждения центральному процессору. До сих пор удивляюсь, как это все вместе работает.
Список модулей программы ВК:
Имя файла модуля
Назначение
MX_BOX.ino
Инициализация. В основном цикле программы выполняется прием команд от ЦП, их распознавание и выполнение. Обработчик приема и передачи данных по протоколу I2C. Обработчик нажатий кнопок.
mx_Keyboard.h, mx_Keyboard.cpp
Класс занимается обработкой нажатий кнопок аналоговой клавиатуры: защита от дребезга, распознание короткого, длинного нажатия и автоповтор.
wt5001_mp3.h, wt5001_mp3.cpp
Класс для работы с MP3 проигрывателем: управление режимом воспроизведения, выбор композиции, регулировка громкости, ПУСК-СТОП-ПАУЗА и т.п. Используется библиотека Arduino SoftwareSerial
mx_MSGEQ7.h, mx_MSGEQ7.cpp
Класс обеспечивает получение данных 7 полосного эквалайзера. Функция фильтрации шумов.
mx_base_class.h
Описание базового класса для всех модулей
mx_7Seg.h, mx_7Seg.cpp
Класс для работы 10-значным 7-сегментным индикатором. Работа с MAX7219 организована по протоколу SPI.
mx_Samples.h
Пример мелодии.
Как работает центральный процессор. После инициализации, в главном цикле запускается основное меню программы, где можно выбрать игру или выполнить настройки.
Для каждой игры кроме ручного режима можно выбрать режим AI, когда будет самостоятельно играть бот.
ЦП обеспечивает основной игровой процесс. Управление игрой возможно, как кнопками, так и движением (наклонами) корпуса игрушки. Во время ожидания выбора меню и игр в фоновом режиме выполняются музыкальные композиции. Во время игры возможны звуковые и вибрационные эффекты. Игру можно прервать и перевести в режим паузы, когда на экране выполняется специальная заставка. Из этого режима можно вернуться в игру или завершить ее. Лучшие результаты игр записываются в энергонезависимую память микроконтроллера. Графика «рисуется» в буфер, который при каждом изменении, передается по UART на Funduino и немедленно отображается на дисплее. ЦП управляет всеми периферическими устройствами, отдавая команды ВК.
Графическая библиотека для различных дисплеев с шикарным функционалом: набор примитивов (точка, линия, квадрат, круг, вывод растровой картинки и т. п.), работа со шрифтами и т. д. Copyright (c) 2013 Adafruit Industries. All rights reserved.
gamma.h
Таблица гамма коррекции цвета для цветовой модели HSV.
mx_accelerometer.h, mx_accelerometer.cpp
Класс - «обертка» для низкоуровневой библиотеки mx_MMA_7455. Опрашивает акселерометр по таймеру с частотой 61 Гц. Ее функционал позволяет определять направление и скорость движения корпуса игрушки.
mx_AI.h, mx_AI.cpp
Для каждой игры, кроме ручного режима предусмотрен режим AI (искусственный интеллект). В этом модуле и лежат все алгоритмы для ботов.
mx_animation.h, mx_animation.cpp
Функция для создания динамического эффекта прокрутки (скроллинга) текста в меню и других местах.
mx_asteroids.h, mx_asteroids.cpp
Игра «Астероиды».
mx_base_class.h
Описание базового класса для всех модулей.
mx_bitmap.h, mx_bitmap.cpp
Вывод на экран растровой картинки. Дублирует функционал Adafruit_GFX. Планирую избавиться.
mx_colors.h, mx_colors.cpp
Библиотека для работы с цветом: константы, конвертация разных моделей цветов, преобразование яркости, цветовой круг, градиенты и т. п.
mx_Config.h
Некоторые настройки.
mx_debugger.h, mx_debugger.cpp
Модуль отладки. Был удобен для тестирования. В принципе больше не нужен.
mx_display.h, mx_display.cpp
Класс для работы с дисплеем. Экземпляр класса Adafruit_GFX, предоставляя весь функционал этой библиотеки. Переопределяется только одна низкоуровневая процедура drawPixel.
mx_display_helper.h, mx_display_helper.cpp
Класс-«обертка» для низкоуровневой библиотеки mx_display. Добавлены процедуры сдвига экрана.
mx_font.h, mx_font.cpp
Библиотека для работы со шрифтом. Дублирует функционал Adafruit_GFX. Планирую избавиться.
mx_game_helper.h, mx_game_helper.cpp
Класс с набором стандартных игровых анимационных примитивов: Вступление, пауза, завершение игры и т. п.
mx_keyboard.h, mx_keyboard.cpp
Класс занимается обработкой нажатий кнопок аналоговой клавиатуры: защита от дребезга, распознание короткого, длинного нажатия и автоповтор.
mx_labirint.h, mx_labirint.cpp
Игра «Лабиринт».
mx_memory_free.h, mx_memory_free.cpp
Определение свободной памяти. Помогает при отладке. В принципе больше не нужен.
mx_menu.h, mx_menu.cpp
Класс организует большое и единственное меню.
mx_MMA_7455.h, mx_MMA_7455.cpp
Класс для работы с датчиком акселерометра. Использует протокол I2C
mx_move_helper.h, mx_move_helper.cpp
Класс предназначен для универсальной обработки движений от кнопок и акселерометра.
mx_muse.h, mx_muse.cpp
Класс для работы с MP3 проигрывателем: управление режимом воспроизведения, выбор композиции, регулировка громкости, ПУСК-СТОП-ПАУЗА и т. п. Кроме этого может получать данные от эквалайзера, управлять спикером и вибродвигателем. Работает опосредовано через функционал класса mx_slave_box
mx_num_display.h, mx_num_display.cpp
Класс для работы с 10-значным цифровым дисплеем. Работает опосредовано через функционал класса mx_slave_box
mx_queue.h, mx_stack.h
Классы для обеспечения работы со структурами данных типа очередь и стек. Используется в некоторых играх.
mx_slave_box.h, mx_slave_box.cpp
Класс, обеспечивающий взаимодействие ЦП и ВК, которые связаны по протоколу I2C.
Набор классов для работы с энергонезависимой памятью EEPROM. В которой хранятся настройки и лучшие результаты игр.
mx_tennis.h, mx_tennis.cpp
Игра «Теннис».
mx_tetris.h, mx_tetris.cpp
Игра «Тетрис».
mx_timer.h, mx_timer.cpp
Класс Таймер задает ритм игровому процессу. Игровой такт составляет 7 мс.
MX_TRIX.ino
Инициализация. В основном цикле программы обрабатываются вызовы меню.
mx_uart.h, mx_uart.cpp
Класс для работы с UART
mx_util.h, mx_util.cpp
Разные вспомогательные процедуры и функции.
[Заключение]
Создание игрушки было непростым, но очень увлекательным делом. По сути получилась экстремальная лабораторная работа по всестороннему изучению микроконтроллеров в боевых условиях. Тут тебе и ввод-вывод, и таймеры и UART и I2C... Удалось связать между собой и заставить служить общей задаче множество электронных устройств. Достаточно масштабным и не скучным было и программирование всего этого железа. Огромное удовольствие доставило написание программ так называемых «ботов» с зачатками «Искусственного Интеллекта». Когда-то давно была мечта написать змейку, которая доползает до «конца» или игрока в тетрис, который никогда не проигрывает. Ну, вот, тот самый случай. Несмотря на сложности, все цели были достигнуты и все мечты сбылись. И уж совсем будет счастье через край, если эта статья вдохновит кого-нибудь на подобные творческие подвиги.
Короткий демонстрационный видеоролик [18]:
Полный исходный код можно скачать по ссылке [19].
[Ссылки]
Примечание: ссылки, где встречается site:имядомена это на самом деле поисковая строка Google.
1. Arduino MEGA 2560. 2. Colorduino site:beta-estore.com. 3. 60mm Square 8*8 LED Matrix - RGB (Square-Dot) site:itead.cc. 4. AN3468 MMA745xL Digital Accelerometer site:nxp.com. 5. ATmega328 DATASHEET COMPLETE site:atmel.com. 6. Grove - Serial MP3 Player site:wiki.seeed.cc. 7. WT5001 site:microelectronicos.com. 8. Библиотека SoftwareSerial site:arduino.ua. 9. MP410 Сверхэкономичный стереофонический цифровой усилитель «D»-класса 2 х 2.2 Вт (TPA2012D) site:masterkit.ru. 10. MAX7219/MAX7221 site:sparkfun.com. 11. Grove-Speaker site:wiki.seeed.cc. 12. Grove - Vibration Motor site:wiki.seeed.cc. 13. MSGEQ7 Seven Band Graphic Equalizer site:sparkfun.com. 14. G748, Корпус для РЭА 225х165х65мм, пластик site:chipdip.ru. 15. GitHub - dragon-dreamer/RgbTetris site:github.com. 16. DM163 8x3-channel constant current LED driver site:siti.com.tw. 17. Colorduino Schematic and Demo Code site:itead.cc. 18. MY MEGA TETRIS BOX site:youtube.com. 19. 170425Tetris-Box-public.zip - исходный код, фото, документация.