Самодельная танцевальная платформа для игры Dance Dance Revolution

в 12:16, , рубрики: Dance Dance Revolution, DanceDanceRevolution, DancePad, DDR, In the Groove, ITG, OutFox, Project OutFox, rythmgame, Stepmania

В этой статье я хочу рассказать, как сделать танцевальную платформу для игры Dance Dance Revolution, аналогичную платформам аркадных автоматов, чтобы можно было играть дома.
Вот что должно получиться в итоге:

Собранная платформа

Собранная платформа

Видео с демонстрацией работы платформы:

Запасная ссылка на VK Video 

Содержание статьи:

Вступление

Dance Dance Revolution (DDR) это танцевальная ритм-игра, в которой нужно в такт музыке нажимать кнопки, переступая ногами на платформе. Изначально эти игры появились в конце 90-х в виде аркадных автоматов.

Чтобы играть в Dance Dance Revolution дома нужна танцевальная платформа с кнопками. Она должна быть аналогична платформе аркадного автомата, а электронику автомата заменит ПК с установленной ритм-игрой. На маркетплейсах продаются мягкие коврики, но они не слишком удобные и не позволяют играть также быстро, как на аркадном автомате. 

Так выглядит аркадный автомат Dance Dance Revolution:

Аркадный автомат Dance Dance Revolution

Аркадный автомат Dance Dance Revolution

И процесс игры на таком аркадном автомате:

Танцевальный коврик

Чтобы попробовать играть дома сначала я купил мягкий танцевальный коврик на AliExpress. Цена такого коврика сейчас (январь 2025 года) около 1500 рублей. Тут девять больших кнопок, в том числе четыре кнопки со стрелками и две маленькие кнопки Select и Start вверху. 

Четырех стрелок и кнопок Select и Start хватает, чтобы перемещаться по меню игры и выбирать песни вообще не трогая клавиатуру, что очень удобно.

Мягкий танцевальный коврик

Мягкий танцевальный коврик

Видео с демонстрацией работы коврика:

Запасная ссылка на VK Video 

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

Мягкий коврик:

  • Минусы

    • Не чувствуются нажатия

    • Не чувствуется положение ног относительно кнопок. Из-за этого можно легко начать смещаться в сторону в процессе игры и в какой-то момент перестать попадать по кнопкам

    • Скользит по полу, может деформироваться в процессе игры

  • Плюсы:

    • Дешевый

    • Легкий 

    • Компактный, можно свернуть или согнуть для хранения

Жесткая платформа:

  • Минусы:

    • Дорогая, если покупать готовую, а не делать самому

    • Тяжелая

    • Занимает много места

  • Плюсы:

    • Чувствуются нажатия на кнопки

    • Чувствуется положение ног на платформе

    • Не скользит по полу и не смещается, даже если активно двигаться и прыгать на ней

Общие моменты по сборке

Требования к платформе:

  • Простота изготовления, доработки, ремонта, а также доступность материалов и инструментов

  • Надежность. Следует из простоты: чем проще, тем надежнее

  • Достаточное количество кнопок, чтобы перемещаться в меню игры, используя только кнопки на платформе

За основу статью пользователя @labyrinth: https://habr.com/ru/articles/235323/ 
По сути повторил всё, что там есть, упростив некоторые моменты. Конструкцию датчиков нажатия скопировал почти полностью. Также использовал идеи из других инструкций по изготовлению танцевальных платформ, список источников приведу в конце статьи.

Компоновка платформы

Компоновка платформы будет такая: четыре квадратные кнопки со стрелками и узкие кнопки Start/Select в верхней части платформы для навигации по меню. Такая же компоновка у мягких танцевальных ковриков. Такого количества кнопок достаточно, чтобы полностью управлять играми Stepmania или Project OutFox, не трогая мышку и клавиатуру. Размеры кнопки на игровом автомате Dance Dance Revolution составляют 10.79 дюйма (24.7 см). Это можно посмотреть, например, на этом сайте:

Сайт с указанием размеров кнопок

Сайт с указанием размеров кнопок

Я немного уменьшил размер кнопок в соответствии с размерами досок которые у меня были. Примерные размеры и конфигурация платформы, которые у меня получились, показаны на рисунке ниже.

Размеры и конфигурация платформы

Размеры и конфигурация платформы

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

Материалы и инструменты

Платформа будет из дерева. Я использовал доски от старой мебели, можно использовать фанеру, ДСП, ДВП и т. д. 

Материалы для основы платформы:

  • Толстые доски или фанера или ДСП, толщиной 1-2 см. Как я уже говорил, использовал доски от старой мебели (ДСП). Из этих досок будут сделаны квадратные кнопки размером 24х24 см и прямоугольное основание размером 83х74 см, а также кнопки Start/Select в виде узких полос длиной 24 см и шириной 8 см

  • Тонкая фанера или ДВП, толщиной 1-2 мм. Тут тоже использовал ДВП “картонки” от задних стенок старого шкафа. Нужен большой лист 83х74 см для основания, маленькие квадратные листы размером 24х24 см для подкладки под кнопки и полосы шириной 2-3 см и длиной 24 см или чуть меньше для подкладки под кнопки и датчики

  • Самоклеящаяся “виниловая” пленка. Я использовал прозрачную и непрозрачную пленку. Пленки должно быть достаточно, чтобы обклеить квадратные кнопки размером 24х24 см, толщиной 2-3 см с лицевой стороны и краев

Прозрачная пленка

Прозрачная пленка
Более плотная непрозрачная пленка

Более плотная непрозрачная пленка

Крепеж:

  • Мебельные гайки (я использовал гайки с диаметром резьбы 4 мм, в магазине они называются “мебельная врезная гайка M4”). Понадобится 16 штук (по 2 на каждую кнопку и 4 на центральную часть)

  • Винты с потайной головкой разной длины (диаметр резьбы такой же как у гаек, у меня 4 мм). Длина зависит от толщины досок, я купил сразу много винтов разной длины, чтобы подобрать нужные уже в процессе сборки. Понадобится также 16 штук

  • Шурупы, тоже купил сразу много разных, подбирал подходящую длину в процессе. Понадобится 12 - 16 штук (по 3 - 4 на каждый боковой неподвижный сегмент)

  • Кровельные шайбы с резиновой подкладкой. Их удобно использовать как подкладки под кнопки, чтобы приподнять их, вместо них можно подложить кусочки ДСП. Понадобится 12 штук (по 2 на каждую кнопку) 

Мебельные гайки, винты, кровельные шайбы

Мебельные гайки, винты, кровельные шайбы

Материалы для датчиков нажатия:

  • Металлические линейки длиной 15 см. На один датчик нужно 2 линейки, получается нужно 12 линеек на 6 кнопок.

  • Деревянные линейки, из них удобно делать подкладки для датчиков, вместо них можно подложить кусочки ДСП 

  • Провода 

  • Термоусадочная трубка 

  • Контактные разъемы для проводов, чтобы можно было отсоединять датчики. Например, штыревые разъемы с шагом 2.54 мм (“гребенка”), как на макетной плате, или можно разрезать макетные провода

Металлические линейки

Металлические линейки
Разъемы и макетные провода

Разъемы и макетные провода

Электроника:

  • Микроконтроллер. Понадобится 6 цифровых портов ввода для кнопок, 1 цифровой порт ввода для переключателя режимов (необязательно), 1 цифровой порт вывода для светодиодного индикатора (необязательно). Контроллер должен иметь возможность подключения к ПК и эмуляции устройства ввода (клавиатура или геймпад). Я использовал Arduino Pro Micro (маленький аналог Arduino Leonardo, работает на чипе ATMEGA32U4) и сделал оба варианта ввода на ПК: клавиатура и геймпад, с переключателем режима ввода. В статье https://habr.com/ru/articles/235323/ в качестве электронной части используется готовый геймпад, при этом датчики кнопок танцевальной платформы припаиваются к кнопкам геймпада на печатной плате. Тоже рабочий вариант, чтобы не заморачиваться с программированием

Arduino Pro Micro

Arduino Pro Micro

Инструменты:

  • Электролобзик

  • Стамеска

  • Киянка

  • Напильник

  • Отвертка

  • Паяльник

  • Нож, линейка, рулетка, карандаш

Расходные материалы:

  • Изолента (куда же без неё)

  • Клей для дерева

  • Акриловый двусторонний скотч на пенной основе 

  • Припой, паяльная кислота (для того, чтобы припаять провода к стальным линейкам. Я использовал ортофосфорную кислоту)

Двусторонний скотч

Двусторонний скотч

Сборка датчика нажатий

Начнем с датчика. Датчик представляет собой две упругие металлические пластины, разделенные деревянными вставками по краям. При нажатии в центр датчика пластины соприкасаются и замыкают контакт. Думаю принцип работы датчика понятен из видео ниже.

Собранный датчик

Собранный датчик
Видео работы датчика

Видео работы датчика

Для одного датчика понадобятся две металлические линейки. Можно взять любые другие металлические пластины, но линейки подходят идеально. 

Зачищаем наждачной бумагой центральные части линеек с одной стороны, которой они будут соприкасаться при нажатии на датчик. Наждачка счищает всю краску, поверхности должны получится чистыми и гладкими.

Зачищенные линейки

Зачищенные линейки

Далее зачищаем концы линеек и припаиваем провода. Я использовал ортофосфорную кислоту, обычный припой ПОС-60 и толстые многожильные провода. Остатки кислоты протер ватой с водой. Держится крепко - не оторвёшь. Провода сделал короткие, со штырями на концах, чтобы можно было втыкать в макетную плату для отладки. На соединительных проводах в платформе потом сделал разъемы, в которые вставляются провода датчиков. Таким образом датчики можно будет легко отсоединить от платформы, чтобы почистить или заменить. Штыри и разъемы использовал стандартные, с шагом 2.51 мм, которые подходят к макетной плате и макетным проводам.

Всё готово для пайки

Всё готово для пайки
В процессе пайки

В процессе пайки
Припаянные провода

Припаянные провода

Далее нужно сделать деревянные вставки, которые будут размещаться по краям датчиков. Можно использовать деревянные линейки или обрезки фанеры. И собираем датчик с помощью изоленты.

Деревянные вставки

Деревянные вставки
Датчик готов к сборке

Датчик готов к сборке
Склеено изолентой

Склеено изолентой

Всего нужно собрать четыре датчика для кнопок со стрелками и два более коротких датчика из половин линеек для кнопок Start/Select.

Четыре датчика для кнопок со стрелками

Четыре датчика для кнопок со стрелками
Два коротких датчика для кнопок Start/Select

Два коротких датчика для кнопок Start/Select

Меняя толщину и расположение деревянных вставок меняем амплитуду и силу нажатия, необходимую для срабатывания датчика. Это можно подобрать опытным путем на собранной платформе. Я начал с толстых вставок по краям, чтобы получить максимальный ход кнопок и минимальное усилие нажатия. Через некоторое время использования платформы, захотел сделать наоборот минимальный ход кнопок, чтобы они срабатывали от малейшего нажатия. Для этого уменьшил толщину вставок и сместил их ближе к центру.

На середину датчика наклеиваем несколько слоев двустороннего скотча или вспененного полиэтилена, чтобы получилась мягкая площадка, на которую сверху будет нажимать кнопка. 

Площадка сверху датчика

Площадка сверху датчика

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

Датчик, обернутый в полиэтилен

Датчик, обернутый в полиэтилен

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

Эта проблема полностью ушла, когда я нанёс немного “медной пасты” на контактные поверхности датчиков. Такой же эффект получался и от графитовой смазки. 

Смазка для контактов датчиков

Смазка для контактов датчиков

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

Устройство кнопки

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

На фото и видео ниже показана отдельная кнопка, которую я собрал для тестирования. С помощью неё уже можно играть в какие-нибудь однокнопочные игры, типа раннеров.

Тестовая кнопка

Тестовая кнопка
Работа датчика при нажатии на кнопку

Работа датчика при нажатии на кнопку

Доска кнопки должна быть достаточно жесткая, чтобы она не прогибалась при нажатии на неё, а именно наклонялась. При этом датчик будет срабатывать даже если нажать на край кнопки. Поэтому для кнопки достаточно одного датчика, кнопка будет всё равно срабатывать при нажатии почти в любой точке. Датчики на платформе будут располагаться с внутренних краев кнопок, т. к. в этом месте кнопки чаще всего нажимаются, особенно в быстрых треках, где нет времени перемещать ноги далеко от внутренних краев кнопок. 

Датчик срабатывает даже при нажатии на край кнопки

Датчик срабатывает даже при нажатии на край кнопки

Сборка основания платформы

Внешний вид и примерные размеры платформы приведены в предыдущем разделе “компоновка платформы”. В качестве материалов нужны доски, ДСП или фанера 1 - 2 см толщиной и более тонкая фанера или ДВП толщиной 1 - 2 мм. Я использовал доски от старой мебели. 

Платформа будет состоять из четырех слоёв. Далее на рисунке перечислены слои в направлении снизу вверх.

Слои платформы

Слои платформы

Слой 1:
Нижний слой, фанерное основание из цельного листа фанеры.

Слой 2: 
Центральная часть из цельной доски толщиной 1 - 1.5 см. В центре будет вырезана квадратная рамка под датчики. После вырезания останется центральный квадрат, который тоже пригодится при сборке.

Слой 3:
Тонкие фанерные вставки для регулировки высоты сегментов верхней части платформы и кнопок. 

Слой 4:
Верхняя часть платформы: кнопки и жестко закрепленные сегменты по краям и в середине, из той же доски, что и центральная часть (толщина 1 - 2 см, должны выдерживать вес человека, если на них прыгать).

Вот список деталей из досок и фанеры, которые понадобятся для сборки: 

Список деталей

Список деталей

Размеры деталей удобно разметить на миллиметровой бумаге в натуральную величину и затем переносить на заготовки.

Разметка на миллиметорвке

Разметка на миллиметорвке

Вырезаем все детали. Доски я пилил электролобзиком, фанеру также электролобзиком или можно резать её ножом.

Детали вырезаны

Детали вырезаны

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

Мебельные гайки в основании платформы, вид с обратной стороны

Мебельные гайки в основании платформы, вид с обратной стороны

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

Расположение датчика (выделен зеленым) и вставок между основанием и кнопкой (выделены красным)

Расположение датчика (выделен зеленым) и вставок между основанием и кнопкой (выделены красным)

И наконец прикручиваем кнопку винтами к мебельным гайкам в основании платформы. Край кнопки будет выступать за пределы окна в центре основания платформы, чтобы этим краем нажимать датчик. 

Установленная кнопка. Здесь также уже установлены угловые неподвижные сегменты и центральная часть платформы

Установленная кнопка. Здесь также уже установлены угловые неподвижные сегменты и центральная часть платформы

Кнопка получается разборной, пластину кнопки в любой момент можно открутить и прикрутить обратно. Далее собираем остальные три кнопки таким же образом.  Для кнопок Start/Select понадобятся более узкие пластины, собираются они аналогично кнопкам со стрелками.

Ниже на фото показана платформа на более позднем этапе сборки. Красным выделено расположение вставок между основанием и кнопками, зеленым выделены места расположения датчиков. На этом фото уже установлены угловые неподвижные сегменты и снизу платформы приклеен лист фанеры.

Расположение датчиков (выделено зеленым) и вставок между основанием и кнопками (выделены красным)

Расположение датчиков (выделено зеленым) и вставок между основанием и кнопками (выделены красным)
Установленные датчики, под датчики положено несколько полосок фанеры, чтобы приподнять их

Установленные датчики, под датчики положено несколько полосок фанеры, чтобы приподнять их

Вот ещё фото платформы с прикрученными кнопками и фанерным основанием, и фото, где видно расположение датчиков под кнопками.

Прикрученные кнопки

Прикрученные кнопки
Расположение датчиков под кнопками

Расположение датчиков под кнопками

Квадратные сегменты по углам платформы между кнопками представляют собой такие же доски, как для кнопок, с квадратными вставками из фанеры под ними, чтобы поднять сегменты на один уровень с кнопками.
Эти сегменты нужно будет прикрутить шурупами к основанию платформы. Перед этим их нужно обтянуть самоклеющийся пленкой т. к. соединение будет неразборным. 

Угловые квадратные сегменты, прикрученные шурупами

Угловые квадратные сегменты, прикрученные шурупами

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

Мебельные гайки, вбитые с обратной стороны центральной части (отмечены красным)

Мебельные гайки, вбитые с обратной стороны центральной части (отмечены красным)

Съемный центральный сегмент будет состоять из двух квадратных досок: нижняя доска это часть основной большой доски, оставшаяся после выпиливания квадратного окна для датчиков; верхняя доска аналогична доскам для кнопок. Скрепляем эти доски шурупами. Далее сверлим в получившейся конструкции отверстия для винтов. 
Таким образом собранный центральный сегмент должен получиться такой же высоты, как кнопки и боковые сегменты платформы. Центральный сегмент, также как кнопки, будет прикручиваться винтами к мебельным гайкам в нижней части платформы.

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

Центральный сегмент, собранная из двух досок, с отверстиями под винты (выделены красным)
Полностью собранная верхняя часть платформы

Полностью собранная верхняя часть платформы

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

Канавка для проводов

Канавка для проводов
Уложенные провода

Уложенные провода

Перед сборкой платформы нужно обтянуть все части верхнего слоя самоклеящейся пленкой. Я использовал непрозрачную пленку для жестко закрепленных сегментов по краям и для центральной части.
Для кнопок использовал прозрачную пленку. Под неё наклеил фигуры стрелок и обозначения для кнопок Start/Select, вырезанные из непрозрачной пленки.

Обтянутый плёнкой центральный сегмент

Обтянутый плёнкой центральный сегмент
Обтянутая плёнкой кнопка

Обтянутая плёнкой кнопка

Далее можно собирать платформу. Нижней частью будет служить лист фанеры. 
К нему приклеиваем центральную часть. Она должна быть из одной большой доски, но у меня она состоит из двух досок. 
Сегменты по краям прикручиваем шурупами к центральной части. 
Устанавливаем датчики под кнопками и прокладываем провода от датчиков к центральной верхней части платформы, где будет располагаться контроллер. Для регулировки высоты датчиков под них можно положить подкладки из полосок фанеры или деревянных линеек.
Сверху на центральные части датчиков наклеиваем полоски двустороннего скотча, чтобы центральная часть датчика находилась выше уровня центральной доски и могла быть нажата кнопкой. 
Далее прикручиваем кнопки и центральную часть винтами.

Установка контроллера

Провода от датчиков будут выведены к центральной верхней части платформы между кнопками Start/Select. Туда нужно установить контроллер и припаять провода. Далее эту часть платформы можно закрыть фанерой или куском оргстекла, чтобы защитить ещё т. к. можно случайно наступить на эту часть во время игры.

Место для контроллера в верхней части платформы (выделено красным)

Место для контроллера в верхней части платформы (выделено красным)
Собираем контроллер

Собираем контроллер
Провода припаяны

Провода припаяны
Контроллер на подложке из оргстекла готов к установке

Контроллер на подложке из оргстекла готов к установке
Контроллер установлен и подключен

Контроллер установлен и подключен
И накрыт сверху пластиной из оргстекла

И накрыт сверху пластиной из оргстекла

Программирование контроллера

В исходной статье статье в качестве электронной части используется готовый геймпад. Кнопки платформы просто припаиваются к контактам кнопок на плате геймпада.
Рассмотрим другой вариант: использование Arduino в качестве контроллера. В качестве контроллера используется Arduino Pro Micro (маленький аналог Arduino Leonardo, работает на чипе ATMEGA32U4). Кнопки подключены к цифровым входам, также к цифровому входу подключен переключатель режимов работы и светодиод для индикации нажатий. 

Схема подключения контроллера

Схема подключения контроллера

На схеме минус светодиода (Led ground) подключен к выходу контроллера (A0), на который всегда будет подаваться 0. Это сделано исключительно для удобства подключения, чтобы два длинных провода от светодиода можно было припаять рядом друг с другом. 
За основу прошивки взял этот проект: 
https://www.instructables.com/Modifying-an-L-tek-Dance-Pad-to-Poll-at-1000hz-on-/ 
https://github.com/StarlightLumi/DanceCtl 
Здесь автор утверждает, что удалось получить частоту опроса контроллера 1000 Гц. 
Я не проверял, на сколько это соответствует действительности. В своей версии прошивки использовал наиболее распространенные библиотеки для эмуляции джойстика и клавиатуры: <Keyboard.h>, <Joystick.h>.
Код прошивки контроллера выложен на GitHub: https://github.com/IvoryRubble/dance_pad.
Для компиляции понадобится VisualStudio Code с плагином PlatformIO.

Логика работы программы довольно простая: 

Процедура setup():

  • Инициализируем все входы и выходы контроллера

  • Затем читаем данные с переключателя режимов

  • В зависимости от положения переключателя инициализируем библиотеку эмуляции геймпада или клавиатуры

  • Мигаем светодиодом (2 раза для режима геймпада, 3 раза для режима клавиатуры)

Цикл loop():

  • Считываем значения с кнопок

  • Если кнопка была нажата или отпущена, то обновляем состояние соответствующей кнопки клавиатуры или геймпада

  • Обновляем состояние светодиода (горит, если кнопки не нажаты; мигает, если нажата кнопка)

Как видно на схеме подключения и в коде, кнопки замыкают входы контроллера на землю, а при не нажатых кнопках входы контроллера подтянуты к плюсу с помощью встроенных подтягивающих резисторов. Для включения подтягивающих резисторов при инициализации входов контроллера передается параметр INPUT_PULLUP:

pinMode(buttonLeftPin, INPUT_PULLUP);

Соответственно при нажатой кнопке на входе будет значение 0, а при не нажатой - 1.

Кнопки механические, ещё и самодельные, поэтому неизбежно будет дребезг контактов. Про дребезг подробно написано, например, в Википедии.
Дребезг контактов можно отфильтровать программно. Обработка состояния кнопок с фильтрацией вынесена в отдельный класс ButtonDebounce.

butonDebounce.h
class ButtonDebounce {
  public:
    ButtonDebounce() {
    }

    bool isBtnPressed = false;
    bool isBtnReleased = false;
    bool isBtnReleasedLongPress = false;
    bool btnState = false;

    void updateState(bool btnStateInput) {
      btnStateInternal = !btnStateInput; // pull_up buttons
      unsigned long currentTime = millis();
      isBtnPressed = false;
      isBtnReleased = false;
      isBtnReleasedLongPress = false;

      if (!debounceDelayPassed && currentTime - previousStateChangeTime >= debounceDelay) {
        debounceDelayPassed = true;
      }

      if (!longPressTimeoutPassed && currentTime - previousStateChangeTime >= longPressTimeout) {
        longPressTimeoutPassed = true;
      }

      if (btnStateInternal != previousState && debounceDelayPassed) {
        btnState = btnStateInternal;
        isBtnPressed = btnStateInternal;
        isBtnReleased = !btnStateInternal;

        if (isBtnPressed) {
          longPressTimeoutPassed = false;
        }

        if (isBtnReleased && longPressTimeoutPassed) {
          longPressTimeoutPassed = false;
          isBtnReleasedLongPress = true;
        }

        debounceDelayPassed = false;
        previousStateChangeTime = currentTime;
        previousState = btnStateInternal;
      }
    }
  private:
    bool debounceDelayPassed = false;
    unsigned long debounceDelay = 100;

    bool longPressTimeoutPassed = false;
    unsigned long longPressTimeout = 1500;

    uint32_t previousStateChangeTime = 0;

    bool btnStateInternal = false;
    bool previousState = false;
};

Константа ButtonDebounce::longPressTimeout отвечает за минимальное время нажатия кнопки, которое считается долгим нажатием. В примере кода выше установлено значение 1500 мс (1.5 с).

Константа ButtonDebounce::debounceDelay отвечает за окно времени при фильтрации дребезга контактов. В примере кода установлено значение 100 мс (0.1 с). Фильтрация состоит в том, что после изменения состояния кнопки (кнопка была нажата или отпущена) программа перестает обновлять состояние кнопки в течение времени debounceDelay. За то время как раз пройдет дребезг и состояние кнопки стабилизируется. Значение 100 мс в примере выбрано с запасом для отладки. В финальной версии можно поставить значение порядка 10-20 мс.

Для каждой кнопки нужно создать экземпляр класса ButtonDebounce и в цикле loop() обновлять состояние кнопки, взывая метод ButtonDebounce::updateState.

Создание экземпляров класса ButtonDebounce для всех кнопок:

ButtonDebounce buttonStartState;
ButtonDebounce buttonSelectState;
ButtonDebounce buttonRightState;
ButtonDebounce buttonUpState;
ButtonDebounce buttonDownState;
ButtonDebounce buttonLeftState;

Процедура обновления состояния кнопок:

void updateStates() {
  buttonLeftState.updateState(digitalRead(buttonLeftPin));
  buttonRightState.updateState(digitalRead(buttonRightPin));
  buttonDownState.updateState(digitalRead(buttonDownPin));
  buttonUpState.updateState(digitalRead(buttonUpPin));
  buttonStartState.updateState(digitalRead(buttonStartPin));
  buttonSelectState.updateState(digitalRead(buttonSelectPin));
}

В параметр btnStateInput метода ButtonDebounce::updateState передается новое значение, считанное со входа, к которому подсоединена кнопка. Отфильтрованное состояние кнопки можно получить из поля ButtonDebounce::btnState. Также доступны флаги, показывающие изменения состояния кнопки:

  • ButtonDebounce::isBtnPressed - кнопка была нажата

  • ButtonDebounce::isBtnReleased - кнопка была отпущена после нажатия

  • ButtonDebounce::isBtnReleasedLongPress - кнопка была отпущена после долгого нажатия

Эти флаги устанавливаются один раз при наступлении события и сбрасываются после следующего обновления состояния кнопки вызовом метода ButtonDebounce::updateState.

Ниже приведена процедура считывания нажатий кнопок и передачи нажатий на ПК. Здесь обрабатываются только события нажатия и отпускания кнопки:

void setButton(int index, ButtonDebounce state, char c) {
  if (switchState) {
    if (state.isBtnPressed || state.isBtnReleased) {
      Joystick.setButton(index, state.btnState);
    }
  } else {
    if (state.isBtnPressed) {
      Keyboard.press(c);
    } else if (state.isBtnReleased) {
      Keyboard.release(c);
    }
  }
}

Переключатель режимов работы читается при старте программы. Если переключатель (Switch на схеме) замкнут, то включается режим клавиатуры, если разомкнут - режим геймпада. Режим клавиатуры удобен для отладки т. к. можно открыть текстовый редактор и видеть, как перемещается курсор и печатаются буквы при нажатии кнопок. 

В режиме клавиатуры кнопки со стрелками отвечают за нажатия стрелок, а клавиши Start/Select за кнопки Return/Esc. Эти значения можно поменять в коде в процедуре setButtons(). Первым параметром при вызове setButton() идет номер кнопки на геймпаде:

void setButtons() {
  if (!isInputEnabled) return;
  setButton(9, buttonStartState, KEY_RETURN);
  setButton(8, buttonSelectState, KEY_ESC);
  setButton(2, buttonUpState, KEY_UP_ARROW);
  setButton(1, buttonDownState, KEY_DOWN_ARROW);
  setButton(0, buttonLeftState, KEY_LEFT_ARROW);
  setButton(3, buttonRightState, KEY_RIGHT_ARROW);
}

Для геймпада настройка кнопок идентична китайскому мягкому танцевальному коврику с AliExpress. Аналогичные коврики продаются  у большого количества продавцов на меркетплесах, расположение кнопок, скорее всего, у них одинаковое.
В итоге можно подключить танцевальную платформу вместо такого коврика, и перенастраивать управление в игре при этом не нужно.

Так выглядит подключенная платформа в режиме геймпада в Windows, нажаты кнопки “Вверх” и “Start”:

Скрин диагностики геймпада в Windows

Скрин диагностики геймпада в Windows

Для индикации работы контроллера используется один светодиод. При включении он показывает режим в котором находится платформа: мигает 2 раза для режима геймпада и 3 раза для режима клавиатуры. Также светодиод начинает быстро мигать при нажатии любой кнопки. Если ни одна кнопка не нажата, то светодиод постоянно горит. Мигание светодиодом реализовано асинхронно и не задерживает цикл обработки нажатий кнопок. Управление состоянием светодиода вынесено в отдельный класс Blinker.

blinker.h
class Blinker {
  public:
    Blinker() {
    }

    int state = LOW;

    void setPeriod(unsigned long highPeriod, unsigned long lowPeriod) {
      _highPeriod = highPeriod;
      _lowPeriod = lowPeriod;
    }
    void update() {
      unsigned long currentTime = millis() % (_highPeriod + _lowPeriod);
      if (currentTime < _highPeriod) {
        state = HIGH;
      } else {
        state = LOW;
      }
    }
  private:
    unsigned long _highPeriod = 500;
    unsigned long _lowPeriod = 500;
};

Для светодиода создается экземпляр класса и в цикле loop() вызывается метод Blinker::update. После этого берется состояние светодиода из поля Blinker::state и записывается на выход, к которому подключен светодиод.
Для изменения частоты мигания светодиода вызывается метод Blinker::setPeriod. Чтобы светодиод горел постоянно, нужно передать нулевое значение параметра lowPeriod.

Процедура для установки частоты мигания и обновления состояния светодиода:

void updateBlinker() {
  if (buttonLeftState.btnState || buttonRightState.btnState ||
      buttonDownState.btnState || buttonUpState.btnState ||
      buttonStartState.btnState || buttonSelectState.btnState) {
    blinker.setPeriod(50, 50);
  } else {
    blinker.setPeriod(1000, 0);
  }
  blinker.update();
}

Вызов updateBlinker() и передача на выход состояния светодиода в цикле loop():

  updateBlinker();
  digitalWrite(LED, blinker.state);

Настройка чувствительности кнопок

На этом сборка платформы завершена, можно подключать её к ПК, настраивать и играть.
В процессе понадобится настроить чувствительность кнопок. Это делается регулировкой высоты подкладок между кнопками и платформой, подкладок под датчиками, и наклеиванием двустороннего скотча на среднюю часть датчика.
Я начал с максимального хода кнопок и чтобы нажатие срабатывало в самом конце хода, когда кнопка утапливается ниже поверхности платформы. По ощущениям получается максимальная разница между платформой танцевальным ковриком. Сразу чувствуется, что нажимаешь именно на физические кнопки. Чтобы кнопки меньше стучали, наклеил на них мягкие вставки, там где они касаются платформы при нажатии. Через какое-то время решил сделать наоборот минимальный ход кнопок и нажатие от малейшего прикосновения, это позволило нажимать кнопки быстрее. Также сделал, чтобы кнопки в не нажатом состоянии были немного выше центральной части платформы, чтобы лучше чувствовать кнопки ногами.

Финальная регулировка кнопок. Расстояние между пластинами датчика и, следовательно, ход кнопок составляют всего пару миллиметров

Финальная регулировка кнопок. Расстояние между пластинами датчика и, следовательно, ход кнопок составляют всего пару миллиметров

Заключение

Играю я в основном в ProjectOutfox.
Большинство файлов с песнями в беру отсюда и отсюда.

Настройки управления в OutFox для платформы в режиме геймпада

Настройки управления в OutFox для платформы в режиме геймпада

Напоследок ещё одно фото готовой платформы и демонстрационное видео.

Танцевальная платформа

Танцевальная платформа

Запасная ссылка на VK Video 

Источники и полезные ссылки

Автор: IvoryRubble

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js