Слово от переводчика: когда-то, когда страна и деревья были большими, а воображение просто безграничным, была у меня мечта – возможность выводить изображение с моего программируемого микрокалькулятора Электроника МК-61 (ну, там графики всякие, кривые, картинки ) на экран телевизора. Времена были дикие позднесовковые, и не то что игровая приставка и очень персональный МИКРОкомпьютер («Правец 8Д» или «Специалист» или «Сикнклер»), но и видеомагнитофоны были в диковинку. В общем, народ требовал зрелищ и те, кто помнит цикл учебно – развлекательных публикаций для программируемых калькуляторов под общим названием «Путь к Земле» (журнал «Техника – Молодежи») меня поймут.
Если кратко, то в виде научно-фантастического романа с неплохим сюжетом описывалось путешествие двух идиотов случайных знакомых – профессионального космонавта и мажора кибернетика с Луны на Землю. Отдельной остроты всему сюжету придавало то, что путешествовали они на т.н. «Лунолете», то есть малом космическом судне с химическим двигателем, предназначенным для передвижения в условиях прямой видимости над лунами и прочими небесными телами похожими на биллиардный шар с простым рельефом. В каждом выпуске цикла присутствовало упрощенное правда, но вполне обоснованное математическое описание каждого маневра как в условиях сильной (относительно) гравитации близкого небесного тела, так и при влиянии на небесный снаряд героев гравитаций Земли и Луны, а также программа для расчета очередного этапа полета. В общем, глядеть на циферки на экране калькулятора не то чтоб доставало, но хотелось красивых кривых на экран (как в ЦУПе).
С другой стороны, не будем забывать что даже примитивные микроконтроллеры семейства Arduino на порядок превосходят по производительности не только микропроцессоры тогдашних флагманов – МК-52 и МК-61, но и вычислительные возможности некоторых 8-битовых игровых приставок поздних времен (Atary 2600 и прочих Рембо так точно).
В общем, вступление вышло слегка затянутым, так что перейдем к теме сегодняшнего занятия – выводе видеоизображения с Arduino на экран телевизора.
К сожалению, конструктивные особенности Arduino позволяют выводить только монохромное (черно – белые) изображения, хотя и это может быть полезным в некоторых проектах, а ЧСВ поднимет у нубов так точно…
Шаг первый. Детали и ПО
Вам понадобятся:
Детали и агрегаты:
- Микроконтроллер Arduino
- Телевизор (без него точно никуда)
- Макетная плата или шилд для Arduino
- 2 резистора номиналом 470 Ом и 1 Ком
- 2 двухпиновых монтажных переходника папа-папа
- Экранированный телевизионный кабель с тюльпаном на конце
Программное обеспечение:
- Среда разработки/прошивки Arduino. Официальная ссылка
Шаг второй. Сборка
От имени автора прошу прощение за пахабное низкое качество изображения готового ТВ – переходника. Поясняется это тем, что при написании инструкций, сначала надо их писать, а потом уже приступать к сборке с тщательным фотофиксированием всех этапов. В нашем же случае, получилось все с точностью до наоборот, так что из мутного изображения готового переходника понять что-либо просто невозможно.
Гораздо лучше, что куда и как паять, поясняет принципиальная схема, к тому же состоящая всего из нескольких деталей.
Распиновка:
Sync — цифровой вывод 9 микроконтроллера
Video — цифровой вывод 8 микроконтроллера
GND — вывод GND микроконтроллера
Шаг третий. Программирование
Самая веселая часть – программирование.
В принципе, уже вышла новая версия ТВ – библиотеки, однако она еще более глючна нестабильна чем R5.91, которую использует автор, так что лучше качайте библиотеку по приведенной выше ссылке.
Текс программы для ленивых тех, кому лень перенабирать код с копии экрана:
#include
Предполагается, что базовые принципы работы и программирования Arduino – подобных микроконтроллеров вам известны, так что автор решил не растекаться мыслью по древу, порекомендовав ознакомится с командами библиотеки ниже:
- begin(mode) Начало вывода информации на экран. Разешение стандартное — 128х96
- begin(mode,x,y) Начало вывода информации на экран. Разешение определяется пользователем аргументами x,y
- end() Очистка видеобуфера
- force_vscale(sfactor) Force the number of times to display each line.
- force_outstart(time) Force the time to start outputting on an active line.
- force_linestart(line) Force line to start outputting on.
- set_vbi_hook(func) Set the function to be called once per vertical blanking period.
- set_hbi_hook(func) Set the function to be called once per horizontal blanking period.
- hres() Команда возвращает значение горизонтального разрешения,
- vres() Команда возвращает значение вертикального разрешения,
- char_line() Команда возвращает значение количества символов, которые поместятся в строку.
- set_pixel(x,y,color) Установка цвета пикселя по заданным координатам
- get_pixel(x,y) Установка пикселя с заданными координатами в качестве точки отсчета.
- fill(color) Заливка экрана заданным цветом.
- clear_screen() Очистка экрана.
- invert() Инвертирование изображение на экране.
- shift(distance,direction) Прокрутка экрана на заданную дистанцию в любом из 6 направлений.
- draw_line(x0,y0,x1,y1,color) Создание прямой с координат (x0,y0) до координат (x1,y1).
- draw_row(row,x0,x1,color) Заполнение строки с координатами от x0 to x1 заданным цветом.
- draw_column(column,y0,y1,color) Заполнение столбца с координатами от у0 до у1 заданным цветом.
- draw_rect(x,y,w,h,color,fillcolor) Отображение прямоугольника с началом в координатах (x,y) с размерами(h,w), и заполнение заданным цветом.
- draw_rect(x,y,w,h,color) Отображение прямоугольника с началом в координатах (x,y) с размерами(h,w).
- draw_circle(x,y,radius,color,fillcolor) Отображение окружности с центором в координатах (x,y) с радиусом (RADIUS) и его заполнение заданным цветом
- draw_circle(x,y,radius,color) Отображение окружности с центором в координатах (x,y) с радиусом (RADIUS).
- bitmap(x,y,bmp,i,width,height) Отображение заданного изображения в координатах..
- print_char(x,y,c) Печать символа в координатах (x,y).
- set_cursor(x,y) Установка позиции для вывода слеующего символа.
- select_font(font) Установка шрифт для вывода текста.
- print() Вывод текста.
- println() Вывод пстой строки.
- printPGM() Вывод строки с текстом из памяти программы.
- tone(frequency) Тональный сигнал с заданной частостой.
- tone(frequency,duration) Тональный сигнал заданной частоты и длительности.
- noTone() Прикращение вывода тонового сигнала.
Шаг четверый. Завершение
Как использовать плату Arduino для вывода какой-либо информации на ТВ? Оказывается есть такая библиотека TVOut (статья на сайте ), которая позволяет выводить информацию на ТВ по НЧ-кабелю (тюльпан). Правда изображение будет черно-белым, однако этого будет достаточно для большинства проектов.
Вот игровая консоль, где используется данная библиотека
Сайт проекта
http://nootropicdesign.com/hackvision/index.html
Цена платы $43.95.
Создадим свою ТВ-приставку, состоящую из Arduino-платы и двухкоординатного джойстика и напишем простенькую игру.
Вот вид нашей приставки
Ознакомимся с возможностями библиотеки и напишем игру.
Страница библиотеки - http://code.google.com/p/arduino-tvout
Здесь можно скачать библиотеку и посмотреть описание функций.
Библиотека использует следующие выводы Arduino
Плата SYNC VIDEO AUDIO Decimilia, Uno, Nano 9 7 11 Mega 11 A7(D29) 10
Рассмотрим основные функции библиотеки.
Функции установки режима
Функция begin() инициализирует вывод видеосигнала (разрешение экрана по умолчанию 128x96)
Синтаксис:
- TVOut.begin(mode);
- TVOut.begin(mode, x, y);
Параметры:
- mode – стандарт видеосигнала:
- _PAL – режим PAL;
- _NTSC – режим NTSC.
Возвращаемое значение:
- 0 – в случае удачного соединения, 4 – в случае неудачи (недостаточно памяти для буфера вывода).
Функции задержки
Функция delay() осуществляет задержку выведенного изображения.
Синтаксис:
- TVOut.delay(ms);
Параметры:
- ms – задержка в мс с точностью: 20 мс для PAL и 16 мс для NTSC.
Функция delay_frame() осуществляет задержку выведенного изображения.
Синтаксис:
- TVOut.delay_frame(frames);
Параметры:
- frames – количество кадров для задержки..
Функция полезна для сведения к минимуму или устранения на мерцание экрана, вызванные обновлением экрана.
Функции получения параметров
Функция hres() возвращает горизонтальное разрешение экрана.
Синтаксис:
- TVOut.hres();
Параметры:
Возвращаемое значение:
- unsigned char – горизонтальное разрешение экрана.
Функция vres() возвращает вертикальное разрешение экрана.
Синтаксис:
- TVOut.vres();
Параметры:
Возвращаемое значение:
- unsigned char – вертикальное разрешение экрана.
Функция char_line() возвращает максимально возможное количество символов в одной строке при выводе текстовой информации.
Синтаксис:
- TVOut. char_line();
Параметры:
Возвращаемое значение:
- unsigned char – количество символов.
Основные графические функции
Функция set_pixel() устанавливает цвет пикселя экрана в точке с заданными координатами.
Синтаксис:
- TVOut.set_pixel(x,y,color);
Параметры:
- x,y – координаты пикселя;
- color – цвет пикселя:
- 0 – черный;
- 1 – белый;
- 2 – инвертировать цвет.
Функция get_pixel() получает цвет пикселя экрана из точки с заданными координатами.
Синтаксис:
- TVOut.get_pixel(x,y);
Параметры:
- x,y – координаты пикселя.
Возвращаемое значение:
- color – цвет пикселя:
- 0 – черный;
- 1 – белый;
- 2 – инвертировать цвет.
Функция fill() заполняет экран заданным цветом.
Синтаксис:
- TVOut.fill(color);
Параметры:
- color – цвет заполнения:
- 0 – черный;
- 1 – белый;
- 2 – инвертировать цвет.
Функция clear_screen() очищает экран, заполняя заданным цветом.
Синтаксис:
- TVOut.clear_screen(color);
Параметры:
- color – цвет заполнения:
- 0 – черный;
- 1 – белый;
- 2 – инвертировать цвет.
Функция invert() инвертирует содержимое экрана.
Синтаксис:
- TVOut.invert();
Параметры:
Функция shift_direction() сдвигает содержимое экрана.
Синтаксис:
- TVOut.shift_direction(distance, direction);
Параметры:
- distance – расстояние для сдвига содержимого экрана.
- direction – направление сдвига:
- UP=0 – вверх;
- DOWN=1 – вниз;
- LEFT=2 – влево;
- RIGHT=3 – вправо.
Функция draw_line() соединяет на экране линией две точки.
Синтаксис:
- TVOut.draw_line(x0,y0,x1,y1,color);
Параметры:
- x0,y0 – координаты первой точки;
- x1,y1 – координаты второй точки;
- color – цвет заполнения:
- 0 – черный;
- 1 – белый;
- 2 – инвертировать цвет.
Функция draw_row() заполняет строку указанным цветом между двумя точками строки.
Синтаксис:
- TVOut.draw_row(row,x0,x1,color);
Параметры:
- row – вертикальная координата строки;
- x1,x2 – горизонтальный координаты точек строки;
- color – цвет заполнения:
- 0 – черный;
- 1 – белый;
- 2 – инвертировать цвет.
Функция draw_column() заполняет строку указанным цветом между двумя точками столбца.
Синтаксис:
- TVOut.draw_column(column,y0,y1,color);
Параметры:
- column – горизонтальная координата столбца;
- y1,y2 – вертикальные координаты точек столбца;
- color – цвет заполнения:
- 0 – черный;
- 1 – белый;
- 2 – инвертировать цвет.
Функция draw_rect() рисует на экране прямоугольник.
Синтаксис:
- TVOut.draw_rect(x,y,w,h,color);
- TVOut.draw_rect(x,y,w,h,color,fillcolor);
Параметры:
- x,y – координаты левой верхней точки;
- w,h – ширина и высота рисуемого прямоугольника;
- color – цвет границ прямоугольника:
- 0 – черный;
- 1 – белый;
- 2 – инвертировать цвет.
- fillcolor – цвет заполнения прямоугольника:
- 0 – черный;
- 1 – белый;
- 2 – инвертировать цвет.
Функция draw_circle() рисует на экране круг.
Синтаксис:
- TVOut.draw_ circle(x,y,r,color);
- TVOut.draw_ circle(x,y,r,color,fillcolor);
Параметры:
- x,y – координаты центра круга;
- r – радиус круга;
- color – цвет границ круга:
- 0 – черный;
- 1 – белый;
- 2 – инвертировать цвет.
- fillcolor – цвет заполнения круга:
- 0 – черный;
- 1 – белый;
- 2 – инвертировать цвет.
Функция bitmap() выводит на экран растровое изображение.
Синтаксис:
- TVOut.bitmap(x,y,bmp,w,h);
Параметры:
- x,y – координаты левого верхнего угла точки вывода;
- bmp – указатель на массив памяти, где хранится картинка;
- w,h – ширина, высота выводимого изображения;
Ниже рассмотрим процесс создания кода выводимых растровых изображений.
Функции вывода текстовой информации
Для применения функций вывода текстовой информации требуетя подключение файлов с включенными в библиотеку или пользовательскими шрифтами. Для подключения пользовательского набора шрифтов необходимо в скетче подключить заголовочный файл:
#include
В состав библиотеки включены следующие наборы шрифтов:
- font4x6;
- font6x8;
- font8x8;
- font8x8ext.
Функция select_font() выбирает шрифт для вывода текстовой информации.
Синтаксис:
- TVOut.select_font(font);
Параметры:
- font – шрифт, подключенный в скетче.
Функция print_char() выводит символ на экран.
Синтаксис:
- TVOut.print_char(x,y,char);
Параметры:
- x,y – позиция на экране для вывода символа;
- char – символ из текущего шрифта.
Функция set_cursor() устанавливает позицию курсора для вывода текстовой информации на экран.
Синтаксис:
- TVOut.set_cursor(x,y);
Параметры:
- x,y – координаты для курсора.
Функция print() выводит на экран строку, символ или число.
Синтаксис:
- TVOut.print(x,y,string);
- TVOut.print(x,y,char,base);
- TVOut.print(x,y,int,base).
Параметры:
- x,y – координаты курсора.
- base – формат вывода:
- BYTE = 0;
- DEC = 10 (default);
- HEX = 16.
Функция println() выводит на экран строку, символ или число и в конце символ перевода строки:
Синтаксис:
- TVOut.println(x,y,string);
- TVOut.println(x,y,char,base);
- TVOut.println(x,y,int,base).
Параметры:
- x,y – координаты курсора.
- base – формат вывода:
- BYTE = 0;
- DEC = 10 (default);
- HEX = 16.
Функции вывода аудио
Функции вывода звука позволяют отправлять на телевизор через аудиовыход сигнал определенной частоты.
Функция tone() выдает аудиосигнал определенной частоты.
Синтаксис:
- TVOut.tone(frequency,duration);
- TVOut.tone(frequency).
Параметры:
- frequency – частота аудиосигнала;
- duration – длительность сигнала.
Функция noTone() прекращает выдачу аудиосигнала.
Синтаксис:
- TVOut.noTone().
Создание собственных шрифтов
Рассмотрим процесс создания пользовательских шрифтов для библиотеки TVOut.
Библиотека позволяет создавать собственные шрифты.
Существуют два вида шрифтов – фиксированной и переменной ширины. Для шрифтов фиксированной ширины первые три байта массива содержат данные о ширине символа (4), высоте символа (6) и первый печатный символ (32) . Затем идут данные для каждого последующего символа.
#include "myfont1.h" PROGMEM const unsigned char myfont1 = { 4,6,32, // 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, // 0b01000000, 0b01000000, 0b01000000, 0b00000000, 0b01000000, 0b00000000, … … };
Для шрифтов переменной ширины в описании каждого символа первый байт определяет ширину данного символа.
#include "myfont1.h" PROGMEM const unsigned char myfont1 = { 4,6,32, // 2, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, // 3, 0b01000000, 0b01000000, 0b01000000, 0b00000000, 0b01000000, 0b00000000, … … };
Создадим пользовательский шрифт myfont1. Для пользовательского шрифта myfont1 в папке TVOutfonts создаем 2 файла: myfont1.h и myfont1.cpp.
Вот содержимое файла myfont1.h
#ifndef MYFONT1_h #define MYFONT1_h #include extern const unsigned char myfont1; #endif
Теперь для использования библиотекой TVOut нашего пользовательского шрифта myfont1 в скетче необходимо подключить файл
#include "myfont1.h"
Создание графических примитивов.
Библиотека TVOut позволяет загружать на экран растровые изображения. Рассмотрим создание кода для загрузки растрового изображения функцией bitmap() библиотеки TVOut.
Сначала необходимо создать 1 битное (двухцветное изображение), например в графическом редакторе Paint.
Затем нам понадобится программа Image2Code, которая сконвертирует из нашего изображения код. Программу можно скачать по адресу . Это версия для операционной системы Windows. Скачиваем, запускаем.
Нажимаем на кнопку Convert и получаем массив.
# include # ifndef MYBITMAP1_H # define MYBITMAP1_H extern const unsigned char MyBitmap1; #endif
Далее создаем файл MyBitmap1.cpp , в него (в поле данных массива unsigned char MyBitmap1) копируем данные конвертации, убирая символы "{" и "}", и вставляя в начале ширину и высоту изображения в пикселах.
#include " MyBitmap1.h" PROGMEM const unsigned char MyBitmap1 = { 16,16, 0x73,0x8E, 0x8C,0x71, 0x00,0x00, 0x00,0x00, 0x00,0x00, 0x97,0x63, 0x94,0x55, 0xB4,0x55, 0xD4,0x67, 0x94,0x45, 0x00,0x00, 0x00,0x00, 0x00,0x00, 0xAA,0xAA, 0x55,0x55, 0x00,0x00 };
Сохраняем файлы MyBitmap1.h и myBitmap.cpp в директории нашего скетча. Для вывода нашего изображения на экран телевизора вызываем функцию TVOut.bitmap():
#include " MyBitmap1.h" TVOut.bitmap(x,y,MyBitmap);
C библиотекой ознакомились, собрана игровая консоль - теперь можно приступать к написанию игры.
Игровая приставка на Arduino? Правда-правда. С экранчиком, кнопками и играми. И это не фантазия. Существовало (и существует) несколько проектов, которые попытались это сделать и один из них (Gamebuino) мы и попытаемся самостоятельно реализовать.
Так как проект открытый, то вся схематика доступна на ВИКИ , как и ссылки на ПО, игры и другая полезная информация.
Для создания своей игровой консоли нам понадобятся недорогие компоненты - экран Nokia 5110, шесть кнопок и пьезодинамик. Все это легко находится на Aliexpress или других сайтах китайских магазинов (в основном проблема найти экран) и даже в России. Экран можно назвать «массовым» и он хорошо описан, поэтому подключить его также не проблематично, как и достать.
Так как Gamebuino использует разводку удобную для размещения элеметов на плате, то при попытке подключить элементы через стандартные пины Arduino мы получаем вот такую картину:
На самом деле часть элементов уже была «упущена», так как в Gamebuino используются контакты микропроцессора, которые не разведены на Arduino или просто не нужны. В «утиль» пошли датчик заряда аккумулятора, кнопка C и SD-карта (они взаимосвязаны) и датчик внешнего освещения. Также решено было убрать резисторы - мы не собираемся использовать дисплей в режиме 24/7 и можно обойтись без этой защиты.
Можно было бы сделать «красиво», но тогда пришлось бы исправлять код всех библиотек и «мэпить» контакты по новой схеме. Я решил ограничиться лишь косметикой - то есть использовать «стандартные» контакты и лишь по необходимости внести небольшие правки в код.
Итак - после сборки схемы необходимо установить IDE Arduino и скачать вот этот архив . Его необходимо разархивировать в директорию Документы/Arduino и после этого запустить Arduino IDE. В этот архив уже внесены изменения в код библиотек и он будет «из коробки» работать с данной схемой.
У вас должны появиться примеры Gamebuino, которые позволят проверить дисплей, кнопки и т.п. и даже запусить Pong. Для платы «используйте» стандартную Arduino UNO или Leonardo (на других пока не тестировал).
Когда у вас все заработало, двигается, пищит и т.п. возникает закономерный вопрос - а где же игры? Gamebuino использует свой загрузчик для работы с SD-картами, поэтому загрузить HEX через их loader не получится безе перепрошивки самой Arduino. Но даже если вы это сделаете, то из-за отстуствия сигнала с аккумулятора вы ничего не сможете запустить, так как прошивка будет ругаться и выключаться. Прекомпилированные HEX с играми тоже не запустить из-за проблем с датчиком заряда аккумулятора.
Но не все потеряно - можно скачать исходные коды, открыть их в Arduino IDE и «залить» в нашу консоль.
Я проверил часть игр и они… РАБОТАЮТ! Вы можете скачать архив с исходными кодами игр отсюда , открыть их в Arduino IDE и скомпилировать и загрузить самостоятельно. Остальные игры вы можете сами опробовать, скачав исходные коды с Вики Gamebuino .
Итак - миссия выполнена! Мы получили миниатюрную игровую консоль. Теперь наша задача сделать ее мобильной:) И это попытаемся сделать в следующих статьях.
Привет, Гик Таймс!
Сегодня я поведаю вам одну не очень интересную историю о том, как создал простую игровую консоль на базе arduino и сделал несложную игру для нее в моем любимом игровом движке - Unity.
Игры
Вот уже почти четыре года я занимаюсь разработкой игр на популярном игровом движке Unity (ранее Unity3D). За это время я успел создать несколько небольших игр для мобильных устройств, а также объемный многопользовательский проект.Это область для меня очень интересна и доставляет огромное удовольствие работать в ней.
Девайсы
Но иногда появляется желание попробовать что-то новое, и в один из таких дней я решил попробовать arduino.Мне стало очень интересно, как создавать свои собственные устройства и как их программировать. Почему arduino? В интернете и даже на том-же хабре и гик таймс море постов о том, почему стоит брать arduino. Но отмечу, что для меня решающим фактором в выборе arduino является простота в использовании.
А как это объединить?
Однажды на просторах интернета я наткнулся на запись о том, как один из пользователей собрал свою простенькую игровую консоль на базе arduino, снабдив ее маленьким жк дисплеем 84x48 пикселей и написал на нее пару простых игр: понг и арканоид.Эта разработка меня очень заинтересовала, и я решил создать свою версию игровой консоли на базе микроконтроллера atmega328.
Сама консоль
Первым делом я спроектировал и сделал ЛУТ-ом печатную плату для портативной консоли. Это было ошибкой - сначала надо было протестировать программу на отладочной плате, например arduino uno, так как я не предусмотрел возможность быстро и удобно заливать программы в контроллер. А еще я ошибся в самой схеме, это можно было исправить проводами, но все же обидно.После того, как я понял, что ошибся, я подключил кнопки через плату к arduino uno, а экран я подключил напрямую.
Вот что вышло:
А теперь к играм
Для создания игр я решил использовать игровой движок Unity. Писать компилятор из Mono C# в программу для arduino я не стал, но решил написать набор скриптов с помощью которых можно легко собирать игры.Все скрипты я разделил на 3 группы - действия, условия и комбайнеры.
Я думаю что назначение действий и условий объяснять не надо, а вот для чего нужен комбайнер я объясню. Комбайнер проверяет выполняется условие, а затем выполняет действие.
Из скриптов, а точнее их наличия и комбинаций алгоритм создает программу для arduino.
А что за игра?
Начать я решил с чего нибудь простого. А какая сама простая игра которую вы знаете? Правильно - понг. Но я решил сделать не совсем понг, а понг на одного - есть одна ракетка, мяч и стена, вместо второй ракетки.Я собрал ее из написанного конструктора, скомпилировал и залил в контроллер. Работает!
А теперь приведем все в порядок
Когда игра запустилась, и я в нее наигрался, я решил, что стоит переделать плату, предать ей красивый вид, использовать smd компоненты и убрать все лишнее. Я переделал схему и сделал плату.Вот что вышло
Исходники
Исходный код - очень простой.Что делает Unity - в редакторе пользователь собирает игру из ui объектов, вешает на них скрипты действий, условий и комбайнеры.
Что делает компилятор (скрипт parser.cs) - он пробегает по всем объектам, смотрит на их скрипты и добавляет в текст файла build.ino куски кода, отвечающие за исполнение аналогов этих скриптов в микроконтроллере.
Ссылка на исходники - drive.google.com/open?id=0B5INc3_98cSJMEMxZmlWUTh1Ukk
Компиляция производится при запуске игры. и сохраняется в папку Assets/build/text/builded.ino
Этот скетч и надо заливать в контроллер.
В заключении
Хочу сказать, что это было очень интересно.Я получил возможность совместить 2 своих любых занятия - разработка игр и создание девайсов.
Надеюсь вас это тоже заинтересовало, и теперь вы тоже сделаете свой крутой девайс =)
При свете дня, а затем и во сне, возникла у меня идея создания собственной регламентированной тв-приставки. Собственно, тут-то открылся передо мной богатый и насыщенный мир радиотехники. Так как ранее я не имел дела с серьезной разработкой электроники, мой выбор пал на более простой вариант - Arduino и ее самая распространенная модель - Uno .
План работы
1. Разобраться с библиотекой
2. Спаять плату видео вывода
3. Написать код
4. Вырезать корпус
Финальная внешняя составляющая не особо важна в случае с подобными проектами.
Шаг 1. Разбираемся, что к чему
После нескольких десятков минут отчаянного гугления пришел к выводу, что создать приставку даже типа Денди у меня не получится. Ну, что тут делать, раз взялся, буду доводить дело до конца.
На сайте, посвященному проектам на Ардуино и вообще радиоэлектронике в целом (не реклама) нашел статью о подобной затее. Было решено использовать библиотеку TVout
, так как приставка тв-шная. Для ее установки и работы пришлось немного пошаманить.
Необходимые функции библиотеки
Функции установки режима
Функция begin()
инициализирует вывод видеосигнала (разрешение экрана по умолчанию 128x96).
Синтаксис:
TVOut.begin(mode);
TVOut.begin(mode, x, y);
Параметры:
mode – стандарт видеосигнала:
_PAL – режим PAL;
_NTSC – режим NTSC.
Возвращаемое значение:
0 – в случае удачного соединения, 4 – в случае неудачи (недостаточно памяти для буфера вывода).
Функции задержки
Функция delay()
осуществляет задержку выведенного изображения.
Синтаксис:
TVOut.delay(ms);
Параметры:
ms – задержка в мс с точностью: 20 мс для PAL и 16 мс для NTSC.
Функция delay_frame() осуществляет задержку выведенного изображения.
Синтаксис:
TVOut.delay_frame(frames);
Параметры:
frames – количество кадров для задержки…
Функция полезна для сведения к минимуму или устранения на мерцание экрана, вызванные обновлением экрана.
Функции получения параметров
Функция hres()
возвращает горизонтальное разрешение экрана.
Синтаксис:
TVOut.hres();
Параметры:
нет.
Возвращаемое значение:
unsigned char – горизонтальное разрешение экрана.
Функция vres() возвращает вертикальное разрешение экрана.
Синтаксис:
TVOut.vres();
Параметры:
нет.
Возвращаемое значение:
unsigned char – вертикальное разрешение экрана.
Функция char_line()
возвращает максимально возможное количество символов в одной строке при выводе текстовой информации.
Синтаксис:
TVOut. char_line();
Параметры:
нет.
Возвращаемое значение:
unsigned char – количество символов.
Основные графические функции
Функция set_pixel()
устанавливает цвет пикселя экрана в точке с заданными координатами.
Синтаксис:
TVOut.set_pixel(x,y,color);
Параметры:
x,y – координаты пикселя;
color – цвет пикселя:
0 – черный;
1 – белый;
2 – инвертировать цвет.
Функция get_pixel()
получает цвет пикселя экрана из точки с заданными координатами.
Синтаксис:
TVOut.get_pixel(x,y);
Параметры:
x,y – координаты пикселя.
Возвращаемое значение:
color – цвет пикселя:
0 – черный;
1 – белый;
2 – инвертировать цвет.
Функция fill() заполняет экран заданным цветом.
Синтаксис:
TVOut.fill(color);
Параметры:
color – цвет заполнения:
0 – черный;
1 – белый;
2 – инвертировать цвет.
Функция clear_screen()
очищает экран, заполняя заданным цветом.
Синтаксис:
TVOut.clear_screen(color);
Параметры:
color – цвет заполнения:
0 – черный;
1 – белый;
2 – инвертировать цвет.
Функция invert()
инвертирует содержимое экрана.
Синтаксис:
TVOut.invert();
Параметры:
нет.
Функция shift_direction() сдвигает содержимое экрана.
Синтаксис:
TVOut.shift_direction(distance, direction);
Параметры:
distance – расстояние для сдвига содержимого экрана.
direction – направление сдвига:
UP=0 – вверх;
DOWN=1 – вниз;
LEFT=2 – влево;
RIGHT=3 – вправо.
Функция draw_line()
соединяет на экране линией две точки.
Синтаксис:
TVOut.draw_line(x0,y0,x1,y1,color);
Параметры:
x0,y0 – координаты первой точки;
x1,y1 – координаты второй точки;
color – цвет заполнения:
0 – черный;
1 – белый;
2 – инвертировать цвет.
Функция draw_row()
заполняет строку указанным цветом между двумя точками строки.
Синтаксис:
TVOut.draw_row(row,x0,x1,color);
Параметры:
row – вертикальная координата строки;
x1,x2 – горизонтальный координаты точек строки;
color – цвет заполнения:
0 – черный;
1 – белый;
2 – инвертировать цвет.
Функция draw_column() заполняет строку указанным цветом между двумя точками столбца.
Синтаксис:
TVOut.draw_column(column,y0,y1,color);
Параметры:
column – горизонтальная координата столбца;
y1,y2 – вертикальные координаты точек столбца;
color – цвет заполнения:
0 – черный;
1 – белый;
2 – инвертировать цвет.
Функция draw_rect()
рисует на экране прямоугольник.
Синтаксис:
TVOut.draw_rect(x,y,w,h,color);
TVOut.draw_rect(x,y,w,h,color,fillcolor);
Параметры:
x,y – координаты левой верхней точки;
w,h – ширина и высота рисуемого прямоугольника;
color – цвет границ прямоугольника:
0 – черный;
1 – белый;
2 – инвертировать цвет.
fillcolor – цвет заполнения прямоугольника:
0 – черный;
1 – белый;
2 – инвертировать цвет.
Функция draw_circle()
рисует на экране круг.
Синтаксис:
TVOut.draw_ circle(x,y,r,color);
TVOut.draw_ circle(x,y,r,color,fillcolor);
Параметры:
x,y – координаты центра круга;
r – радиус круга;
color – цвет границ круга:
0 – черный;
1 – белый;
2 – инвертировать цвет.
fillcolor – цвет заполнения круга:
0 – черный;
1 – белый;
2 – инвертировать цвет.
Функция bitmap()
выводит на экран растровое изображение.
Синтаксис:
TVOut.bitmap(x,y,bmp,w,h);
Параметры:
x,y – координаты левого верхнего угла точки вывода;
bmp – указатель на массив памяти, где хранится картинка;
w,h – ширина, высота выводимого изображения;
Ниже рассмотрим процесс создания кода выводимых растровых изображений.
Функции вывода текстовой информации
Для применения функций вывода текстовой информации требуетя подключение файлов с включенными в библиотеку или пользовательскими шрифтами. Для подключения пользовательского набора шрифтов необходимо в скетче подключить заголовочный файл:
#include
В состав библиотеки включены следующие наборы шрифтов:
font4x6;
font6x8;
font8x8;
font8x8ext.
Функция select_font()
выбирает шрифт для вывода текстовой информации.
Синтаксис:
TVOut.select_font(font);
Параметры:
font – шрифт, подключенный в скетче.
Функция print_char() выводит символ на экран.
Синтаксис:
TVOut.print_char(x,y,char);
Параметры:
x,y – позиция на экране для вывода символа;
char – символ из текущего шрифта.
Функция set_cursor()
устанавливает позицию курсора для вывода текстовой информации на экран.
Синтаксис:
TVOut.set_cursor(x,y);
Параметры:
x,y – координаты для курсора.
Функция print()
выводит на экран строку, символ или число.
Синтаксис:
TVOut.print(x,y,string);
TVOut.print(x,y,char,base);
TVOut.print(x,y,int,base).
Параметры:
x,y – координаты курсора.
base – формат вывода:
BYTE = 0;
DEC = 10 (default);
HEX = 16.
Функция println()
выводит на экран строку, символ или число и в конце символ перевода строки:
Синтаксис:
TVOut.println(x,y,string);
TVOut.println(x,y,char,base);
TVOut.println(x,y,int,base).
Параметры:
x,y – координаты курсора.
base – формат вывода:
BYTE = 0;
DEC = 10 (default);
HEX = 16.
Функции вывода аудио
Функции вывода звука позволяют отправлять на телевизор через аудиовыход сигнал определенной частоты.
Функция tone()
выдает аудиосигнал определенной частоты.
Синтаксис:
TVOut.tone(frequency,duration);
TVOut.tone(frequency).
Параметры:
frequency – частота аудиосигнала;
duration – длительность сигнала.
Функция noTone()
прекращает выдачу аудиосигнала.
Синтаксис:
TVOut.noTone().
Шаг 2. Паяем видеовывод
В первую очередь нам нужно спаять некую плату для вывода видеосигнала через композитный av-выход (RCA). Паяем по следующей схеме:
Расположим два резистора номиналом 470 ом и 1к ом параллельно друг другу и припаяем к ним «плюс» от кабеля-тюльпана. Далее отведем от резистора в 470 ом провод в седьмой пин на Arduino, т.к. он отвечает за вывод видео (video ), а от резистора в 1к ом отведем провод в девятый пин, так как он отвечает за синхронизацию (sync ). А «минус» от кабеля-тюльпана в «землю» на Arduino. Подробнее (англ. )
Шаг 3. Пишем код (игру)
Я не буду объяснять, что да как подключать, ведь необходимую информацию, как всегда, можно найти в интернете. Я описываю то, что очень сложно найти или этого вообще нет.
Начинаем с экрана приветствия , куда без него. Но тут встревает важный вопрос, как назвать сие чудо? Я пораскинул мозгами и придумал - Shimo . Звучит неплохо, даже технологично, по-китайски, конечно, но это не беда.
Начинаем. Чертим линию через середину экрана с помощью TV.draw_line(60,0,60,96,1); . Появляется шарик ровно в центре экрана. Напишем функцию его движения void ballmove(int vel, int angle) . Устанавливаем с помощью TV.set_pixel(x,y,1); , переменные я так и назвал.
Далее перед манипуляциями с шариком прописываем обновление экрана, а точнее, чтобы шарик не «наследил» на экране, поэтому при переходе на следующую позицию нужно закрашивать черным предыдущую. Для этого нам нужно прописать перед всем остальным TV.set_pixel(x,y,0); . После всех изменений переменных координат нужно прописать уже установку позиции и небольшую задержку - TV.delay(50); . Примерно вот так должно получиться:
Void ballmove(int vel, int angle) { TV.set_pixel(x,y,0); //Манипуляции с координатами TV.set_pixel(x,y,1); }
Теперь о самих изменениях координат. Всего восемь направлений (1-8), переменная int angle . А там уже просто, в зависимости от поворота, отнимаем или прибавляем к переменным какую-либо часть от int velocity . Я сделал так:
If(angle == 1) { y -= vel; } if(angle == 3) { x += vel; } if(angle == 5) { y += vel; } if(angle == 7) { x -= vel; } if(angle == 2) { x += round(vel/2); y -= round(vel/2); } if(angle == 4) { x += round(vel/2); y += round(vel/2); } if(angle == 6) { x -= round(vel/2); y += round(vel/2); } if(angle == 8) { x -= round(vel/2); y -= round(vel/2); }
Теперь движения ракеток. Здесь важное уточнение - я использовал только координаты по y , так как позиции ракеток по x не изменяются. Прописываем следующую функцию void racketsmove() . Далее рисуем ракетки, переменные int yb1 , int yb2 , TV.draw_line(10, yb1+8, 10, yb1-8, 1); и TV.draw_line(110, yb2+8, 110, yb2-8, 1); . Обновление экрана, то есть «без следа», аналогично случаю с шариком.
Управление ракетками производится с кнопок. Подключаем кнопки, пины 2 и 3 - первая ракетка, 4 и 5 - вторая ракетка. Проверяем нажатие кнопок и изменяем координаты.
Вот такая функция:
Void racketsmove() { TV.draw_line(10, yb1+8, 10, yb1-8, 0); TV.draw_line(110, yb2+8, 110, yb2-8, 0); if((yb1 - 8) > 1) { if(digitalRead(2) == HIGH) { yb1 -= 2;} } if((yb1 + 8) < 95) { if(digitalRead(3) == HIGH) {yb1 += 2;} } if((yb2 - 8) > 1) { if(digitalRead(4) == HIGH) {yb2 -= 2; } } if((yb2 + 8) < 95) { if(digitalRead(5) == HIGH) {yb2 += 2;} } TV.draw_line(10, yb1+8, 10, yb1-8, 1); TV.draw_line(110, yb2+8, 110, yb2-8, 1); }
Сейчас снова вернемся к ball . Теперь пропишем его коллизию и отталкивание от стен и ракеток. Функция - void ballcol() . Для этого просто проверяем его местонахождение относительно объектов, а потом и его угол. Затем этот угол изменяем на другой. С углом легко угадать.
Угол отражения равен углу падения
Можно сделать некоторые физические исключения для определенных зон ракеток.
Void ballcol() { if(x == 1 || x == 119 || (x == 10 && y < (yb1 + 3) && y > < (yb2 + 3) && y > < (yb1 - 3) && y > (yb1 - 8)) { a = 2; } if(x == 10 && y > (yb1 + 3) && y < (yb1 + 8)) { a = 4; } if(x == 110 && y < (yb2 - 3) && y > (yb2 - 8)) { a = 8; } if(x == 110 && y > (yb2 + 3) && y < (yb2 + 8)) { a = 6; } if(y == 95 || y == 1) { if(a==1){a=5;}else if(a==2){a=4;}else if(a==3){a=7;}else if(a==4){a=2;}else if(a==5){a=1;}else if(a==6){a=8;}else if(a==7){a=3;}else if(a==8){a=6;} } }
Самое сложное позади, можете успешно вздохнуть.
На данный момент нам остается только сделать систему подсчета баллов, таймер и рестарт.
Начнем с таймера. Есть переменная секунд float ts (в ней хранится абсолютно все время), переменная int tm (количество минут, которые мы получаем из ts ). Задаем значение tm операцией tm = ts/60; . И выводим значения на экран, TV.print(81,1,tm); TV.print(97,1,"."); TV.print(100,1,int(ts-(tm*60))); .
Продолжим. Функция рестарта, называем void restart() . Здесь мы возвращаем изначальные значения переменных.
Void restart() { TV.clear_screen(); x = 60; y = 48; yb1 = 48; yb2 = 48; a = 8; ts = 900.0; c1 = 0; c2 = 0; }
Финал, система подсчета баллов, она чересчур проста. Открываем гугл и вбиваем «Правила игры в настольные теннис». Ищем, за что очки даются. Находим часть про штрафы, а дальше мы успешно находим следующее: «Очко считается выигранным, если противник не успеет отразить правильно посланный ему мяч после первого отскока». Назревает вопрос, как отсчитывать удары и прочее?.. А удары и не нужно отсчитывать, ведь наш пинг-понг с двухмерной графикой.
Мы спокойно находим выход из положения и, как всегда, просто проверяем координаты относительно боковых стенок. Если происходит столкновение, то начисляем балл игроку на противоположной стороне поля. Функция - void ballscount() . Когда выйдет таймер - мы сравниваем баллы первого игрока (переменная int c1 ) и второго игрока (переменная int c2 ), объявляем победителя, делаем задержку и вызываем рестарт.
Void ballscount() { if(x == 1) { c2++; } if(x == 119) { c1++; } if(c1 > < c2 && ts == 0) { TV.println(10, 45, "Player 2 won!"); delay(10000); restart(); } else if(c1 == c2 && ts == 0) { TV.println(10, 45, "You are equal"); delay(10000); restart(); }
Вот и все, друзья, мы полностью написали код игры. Получилось довольно забавно и можно поиграть.
Для ленивых я просто напишу весь код.
Полный скрипт
Всего 218 строк.
#include
Шаг 4. Вырезаем корпус
Решил вырезать корпус на лазерном резаке (или фрезеровщике, я точно не знаю) из фанеры в 4mm. Нарисовал в InkScape, немного пошаманил и перевел в формат фрезеровщика.
Для геймпадов вырезал маленькие дощечки и просверлил в них дырки под кнопки. Получилось неплохо, но, к сожалению, я потерял фотографию.
Вывод
В процессе работы была создана простая игровая телевизионная игровая приставка на Arduino со стандартной игрой Ping Pong, с двумя геймпадами, в которую мы можем поиграть и даже залипать.
Программы