Подключение ПК мыши к Денди. Насколько это удобно?

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!

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

В качестве функционального клея для стыковки двух железяк выбрал Arduino Nano просто потому, что была под рукой. Плюс пятивольтовая логика всех компонентов намекала на простое подключение одного к другому безо всяких согласователей уровней и прочего обвеса. Мышь - самая дешевая с PS/2 интерфейсом, свежекупленная, специально для этого проекта. Собственно, и все. Несмотря на широкую распространенность PS/2 аксессуаров, найти рабочую библиотеку оказалось не простой задачей. Одни библиотеки были достаточно стары и не собирались под свежей ARDUINO SDK, другие собирались, но работали криво, либо не работали вообще. В конце концов, наиболее подходящая библиотека, все же, была найдена и вроде даже работала. Сознаюсь, смалодушничал. Можно было уделить чуть больше времени для изучения протокола мыши, но для одноразовой задачи делать этого совсем не хотелось. Далее осталась часть эмуляции нажатий геймпада приставки. Вот тут действительно, проще всего написать самому, тем более что ничего особенного там нет. Внутри джойстика Денди контроллера NES стоит микросхема сдвигового регистра.

Схема джойстика обыкновенного (данные на Q7 задом наперед, очепятка)
Схема джойстика обыкновенного (данные на Q7 задом наперед, очепятка)

Каждый бит входа привязан к кнопке контроллера. Проще говоря, она преобразовывает комбинацию одновременно нажатых кнопок в последовательность, которую уже читает консоль и реагирует на нее действием на экране. По сигналу LATCH (читай Reset), микросхема сбрасывает свой счетчик и ждет тактового сигнала CLOCK для опроса каждого бита входа. При каждом такте отдается состояние каждого следующего бита микросхемы, т.е. для опроса всех 8 кнопок контроллера требуется 8 тактов. А далее по кругу - снова сброс, регистрация текущих нажатий кнопок и 8 тактов опроса состояний. Но тут вот какой нюанс. CLOCK и состояния кнопок можно назвать инверсными сигналами. То есть, активный уровень такта — это когда он равен нулю, равно как и нажатие кнопки будет с уровнем 0.

Более детальный механизм опроса
Более детальный механизм опроса

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

 waitLatch(); 
  for (int i = 0; i < 8; i++) {
    if (dataPad & (1 << i))
      writeLo();
    else
      writeHi();
    waitClock(HIGH);
  }
  writeHi();

"Вот и все", наивно подумал я. Опрашиваем мышь, интерпретируем ее кнопки/направление перемещения в команды контроллера и отправляем. Проблемы начались при первых же тестах. Контроллер явно эмулировался неправильно. Были спонтанные "нажатия" тех кнопок, которые я не нажимал, пропуски команд и прочая чертовщина. Начав разбираться в чем же дело, к своему удивлению, выяснил, что ардуинка недостаточно расторопна - в момент опроса мыши она (естественно) забивает на опрос консоли. А когда не забивает - пропускает какое-то количество сигналов LATCH, но если даже и не пропускает, то спотыкается на тактовых сигналах CLOCK. С тактовыми сигналами ей приходилось сложнее всего. Импульсы (тактами их назвать сложно) настолько коротки, что

digitalWrite(HIGH);
digitalWrite(LOW);

выполняются гораздо дольше самого импульса и 5-7 биты либо не отправляются совсем, либо отправляются с запозданием, когда уже совсем не надо. Признаться, первый раз с таким столкнулся, раньше времени реакции штатных функций всегда хватало для всего. Но это не страшно, так как вместо digitalWrite() можно напрямую оперировать состояниями портов, что в десятки (!!!) раз быстрее. digitalRead() отправляется туда же, так как она тоже не отличается быстродействием.

void Gamepad::waitClock(int state) {
  if (state) {
    while (PIND & (1 << PIND2)) {};
  } else {
    while (!(PIND & (1 << PIND2))) {};
  }
}

void Gamepad::waitLatch() {
  while (!(PIND & (1 << PIND3))) {};
}

void Gamepad::writeLo() {
  PORTD &= ~(1 << 4);
}

void Gamepad::writeHi() {
  PORTD |= (1 << 4);
}
Тестирование, страдание и детский восторг
Тестирование, страдание и детский восторг

Запускаю проект… уже гораздо лучше. Лучше, но все равно плохо. Ложные нажатия почти ушли, а вот пропуски остались. Из самого очевидного, попробовал запихать повесить LATCH на внешнее прерывание и из него отправлять буфер нажатых кнопок. Не получилось. Консоль опрашивает контроллер с частотой 50 или 60Гц (в зависимости от региона консоли), но некоторые игры могут опрашивать сразу 2 раза, с чем прерывания уже не справляются. Через некоторое время я все же победил проблему и заставил ардуину работать как надо. Ну... почти как надо. Для поставленной задачи вполне достаточно. Пришлось на время отправки посылки полностью отключать прерывания, иначе ардруина может не вовремя задуматься и испортить посылку. Уверен, что есть более элегантное решение, но уже хотелось быстрее запустить.

waitLatch(); // Ждем сигнал сброса от NES
  cli();       // Отключаем прерывания, чтобы ничего не мешало правильному формированию пакета

  // Передаем каждый бит при изменении сигнала Clock
  for (int i = 0; i < 8; i++) {
    if (dataPad & (1 << i))
      writeLo();
    else
      writeHi();

    waitClock(HIGH);
  }
  writeHi();

  sei(); // Включаем прерывания для опроса мыши

Управление мышью было назначено так: перемещение мыши интерпретируются как нажатие кнопок крестовины направлений. Левая и правая кнопки - кнопки В и А контроллера, а Start и Select повесил на прокрутку колеса.

Исходники проекта тут https://github.com/HotPixelChannel/Mouse-To-NES

А как все это работает на деле - велкам на ютуб https://youtu.be/-WQ3YLDiz-E

Источник: https://habr.com/ru/post/691218/


Интересные статьи

Интересные статьи

Почему накопители SSD ускоряются после очистки и насколько важен размер кэша — бенчмарки популярных моделей PCIe 4.0 В прошлом году SSD впервые в истории обогнали HDD по объёму про...
Всем доброго времени суток. В этой статье мы разберём подключение TFT дисплея ER-TFT101-1 (10 дюймов, RA8876 драйвер) к плате STM32F429L Discovery по 16-битному параллельному интерфейсу 8080 испо...
Устраивать конкурсы в инстаграме сейчас модно. И удобно. Инстаграм предоставляет достаточно обширный API, который позволяет делать практически всё, что может сделать обычный пользователь ручками.
Если честно, к Д7 у меня несколько неоднозначное отношение. В некоторых местах я попискиваю от восторга, а в некоторых хочется топать ногами и ругаться неприличными словами.
С версии 12.0 в Bitrix Framework доступно создание резервных копий в автоматическом режиме. Задание параметров автоматического резервного копирования производится в Административной части на странице ...