Arduino и джойстик
В один прекрасный день попался мне на eBay интереснейший маленький модуль джойстика, который очень похож на используемые в контроллерах для PlayStation 2. Оказалось, что модуль очень прост в использовании с Arduino и стоит буквально несколько долларов.
Компаний-производителей джойстиков для Arduino несколько, в том числе Adafruit, Sparkfun и огромное количество китайских фирм. Радует, что принцип действия у них совершенно идентичный.
Общая информация о модуле джойстика для Arduino
На модуле 5 пинов: Vcc, Ground, X, Y, Key. Обратите внимание, что обозначения на вашем модуле могут отличаться. Это зависит от производителя. Джойстик аналоговый и обеспечивает более высокую точность, чем простые 'directional' джойстики, в которых используются кнопки и механические переключатели. Кроме того, на джойстик можно нажать (на моей модели прилагать для этого приличные усилия. Возможно, он просто еще не разработан). После нажатия отработает кнопка 'press to select'.
Для считывания данных с пинов X/Y надо использовать аналоговые выходы на Arduino. Пин Key замыкается землей при нажатии. В противоположном случае ни в какой цепи он не участвует. Для стабильного считывания данных с пинов Key/Select, они должны подключаться к источнику питания (Vcc) через подтягивающий резистор. Номинала встроенных на Arduino резисторов для этого будет вполне достаточно.
Посмотрите видео-пример работы джойстика с Arduino:
Схема подключения джойстика к Arduino
Arduino - Vcc
Arduino GND - GNG
ARDUINO A0 - VER/Y
Arduino A1 - HOR/X

Базовый скетч для использования джойстика с Arduino
int xPin = A1;
int yPin = A0;
int buttonPin = 2;
int xPosition = 0;
int yPosition = 0;
int buttonState = 0;
void setup() {
// инициализация обмена данными по серийному протоколу со скоростью 9600 bps:
Serial.begin(9600);
pinMode(xPin, INPUT);
pinMode(yPin, INPUT);
// активируем подтягивающий резистор на пине кнопки
pinMode(buttonPin, INPUT_PULLUP);
// Для более ранних версий Arduino (меньше 1.0.1)
// pinMode(buttonPin, INPUT);
// digitalWrite(buttonPin, HIGH);
}
void loop() {
xPosition = analogRead(xPin);
yPosition = analogRead(yPin);
buttonState = digitalRead(buttonPin);
Serial.print("X: ");
Serial.print(xPosition);
Serial.print(" | Y: ");
Serial.print(yPosition);
Serial.print(" | Button: ");
Serial.println(buttonState);
delay(100); // добавляем задержку между считыванием данных
}
Joystick Shield
Как уже упоминалось выше, модули джойстика производят многие. Интересное решение есть у компании Sparkfun. Они выпускают шилд Joystick Shield, о котором мы дальше и поговорим. Внешний вид джойстик шилда представлен на рисунке ниже.

Сборка джойстик шилда
Здесь стоит упомянуть, что шилд поставляется в разобранном виде. Так что придется поработать паяльником. Полная инструкция по сборке находится по этому адресу: Joystick Shield Assembly Guide. Материал от производителя на английском языке, но фотоматериалов вполне достаточно. Так что разобраться несложно.
Для чего можно использовать джойстик?
На джойстик шилде установлено четыре кнопки справа, одна кнопка непосредственно на джойстике ну и сам аналоговый джойстик. Шилд можно использовать для управления мелодией или пикселями на мониторе. Кнопки можно использовать для навигации и управления в играх.
Для дополнительной мотивации можете заценить видео ниже:
После того, как вы собрали ваш джойстик шилд, можете смело вносить изменения в скетчи для реализации ваших задач.
Как отследить текущее положение джойстика?
Положение джойстика рассчитывается в зависимости от значений двух потенциометров, которые в нем установлены. Джойстик перемещается в двух направлениях, которые обычно обозначают как X и Y. Для считывания данных с потенциометров используем функцию analogRead(), которая возвращает значение в диапазоне от 0 до 1023. Для этого надо в функцию передать номера пинов, к которым подключен джойстик. В данном примере мы подключаемся к аналоговому пину 0 для X и к аналоговому пину 1 для Y.
Serial.println(analogRead(0)); // отображает текущее положение X координаты
Serial.println(analogRead(1)); // отображает текущее положение Y координаты
Очень удобный подход - использование констант для значений, которые не будут меняться на протяжении работы программы. Так что в коде ниже мы объявим константы для аналоговых пинов, которые мы используем и отобразим текущее положение по Х и Y в серийном мониторе Arduino IDE:
const byte PIN_ANALOG_X = 0;
const byte PIN_ANALOG_Y = 1;
void setup() {
Serial.begin(9600);
}
void loop() {
Serial.print("x:");
Serial.print(analogRead(PIN_ANALOG_X));
Serial.print(" ");
Serial.print("y:");
Serial.print(analogRead(PIN_ANALOG_Y));
Serial.print(" ");
Serial.println();
}
Как отследить текущее направление джойстика?
Очень полезный кусок кода. На основании значений положений X и Y мы можем определить, находится ли джойстик по центу или он смещен в одном из восьми направлений (вверх, вправо-вверх, вправо, вправо-вниз, вниз, влево-вниз, влево, влево-вверх).
Так как значения в каждом из направлений будет в диапазоне от 0 до 1023, можно предположить, что центр будет находиться в диапазоне 511-512. Но это не совсем так. Настолько точно текущее значение мы не получим. И если мы определим неверное значение, можем получить информацию о движении джойстика, хотя он стоял по центру и не двигался.
Для этого мы введем диапазон значений и будем считать, что любое значение в этом диапазоне будет считаться центром:
|---|---|---|
0 505 515 1023
Этот диапазон не является «истиной последней инстанции». Вам надо его подстроить под ваш джойстик, так. Эти значения вводятся в код в виде констант:
const int X_THRESHOLD_LOW = 505;
const int X_THRESHOLD_HIGH = 515;
const int Y_THRESHOLD_LOW = 500;
const int Y_THRESHOLD_HIGH = 510;
Теперь мы преобразуем каждую координату из диапазона от 0 до 1023 в диапазон от -1 до 1. Для координаты Х – 1 значит перемещение влево, 0 означает отсутствие перемещения, а 1 – перемещение вправо. Для направления Y -1 означает перемещение вниз, 0 означает отсутствие перемещения, а 1 – перемещение вверх.
Мы начнем с установки значения в каждом направлении 0 («центр»). После этого мы используем выражения if/else для проверки, принимает ли значение положения в любом из направлений большее или меньшее значение чем наш диапазон:
x_direction = 0;
y_direction = 0;
x_position = analogRead(PIN_ANALOG_X);
y_position = analogRead(PIN_ANALOG_Y);
if (x_position > X_THRESHOLD_HIGH) {
x_direction = 1;
} else if (x_position < X_THRESHOLD_LOW) {
x_direction = -1;
}
if (y_position > Y_THRESHOLD_HIGH) {
y_direction = 1;
} else if (y_position < Y_THRESHOLD_LOW) {
y_direction = -1;
}
В Arduino IDE есть функция map(), которую, теоретически могли бы использовать вместо if/else, но в данном случае метод усложняется из-за вопросов центрирования, так что применять здесь map мы не будем.
В примере, который представлен ниже, вы увидите, что в дальнейшем if/else используются для отображения направления – вы можете спокойно изменить этот пример под ваши задачи:
const byte PIN_ANALOG_X = 0;
const byte PIN_ANALOG_Y = 1;
const int X_THRESHOLD_LOW = 505;
const int X_THRESHOLD_HIGH = 515;
const int Y_THRESHOLD_LOW = 500;
const int Y_THRESHOLD_HIGH = 510;
int x_position;
int y_position;
int x_direction;
int y_direction;
void setup() {
Serial.begin(9600);
}
void loop () {
x_direction = 0;
y_direction = 0;
x_position = analogRead(PIN_ANALOG_X);
y_position = analogRead(PIN_ANALOG_Y);
if (x_position > X_THRESHOLD_HIGH) {
x_direction = 1;
} else if (x_position < X_THRESHOLD_LOW) {
x_direction = -1;
}
if (y_position > Y_THRESHOLD_HIGH) {
y_direction = 1;
} else if (y_position < Y_THRESHOLD_LOW) {
y_direction = -1;
}
if (x_direction == -1) {
if (y_direction == -1) {
Serial.println("left-down");
} else if (y_direction == 0) {
Serial.println("left");
} else {
// y_direction == 1
Serial.println("left-up");
}
} else if (x_direction == 0) {
if (y_direction == -1) {
Serial.println("down");
} else if (y_direction == 0) {
Serial.println("centered");
} else {
// y_direction == 1
Serial.println("up");
}
} else {
// x_direction == 1
if (y_direction == -1) {
Serial.println("right-down");
} else if (y_direction == 0) {
Serial.println("right");
} else {
// y_direction == 1
Serial.println("right-up");
}
}
Как настроить Arduino для отслеживания состояния кнопки (нажата ли она)?
Перед тем как узнать, нажата ли кнопка на джойстик шилде, вам надо настроить Arduino на узнавание кнопок. Как это не удивительно, это реализуется в теле функции setup()!
Сначала мы определяем константы для пинов Arduino, которые связаны с кнопками:
// Выбор кнопки, которая срабатывает при нажатии джойстика
const byte PIN_BUTTON_SELECT = 2;
const byte PIN_BUTTON_RIGHT = 3;
const byte PIN_BUTTON_UP = 4;
const byte PIN_BUTTON_DOWN = 5;
const byte PIN_BUTTON_LEFT = 6;
Если вы до этого когда-то использовали кнопки с Arduino, вы могли заметить, что для определения напряжения при нажатой кнопке, надо использовать резистор. Для уменьшения количества деталей, джойстик шилд спроектирован таким образом, что резисторы не нужны. Вы можете спросить себя: «Если для кнопок нужны резисторы, почему шилд работает без них?». Вы просто не учли, что на Arduino есть встроенные резисторы. Можно просто их активировать и использовать с нашим шилдом!
Для задействования этих встроенных подтягивающих резисторов надо установить пин в режим INPUT, а после уже активировать с помощью следующих строк:
pinMode(PIN_BUTTON_RIGHT, INPUT);
digitalWrite(PIN_BUTTON_RIGHT, HIGH);
Если вы используете подтягивающий резистор, важно помнить, что не нажатая кнопка дает сигнал HIGH, а нажатая – LOW.
Для того, чтобы настроить каждый пин на работу в режиме input и активации подтягивающих резисторов, можно использовать следующий код:
void setup() {
pinMode(PIN_BUTTON_RIGHT, INPUT);
digitalWrite(PIN_BUTTON_RIGHT, HIGH);
pinMode(PIN_BUTTON_LEFT, INPUT);
digitalWrite(PIN_BUTTON_LEFT, HIGH);
pinMode(PIN_BUTTON_UP, INPUT);
digitalWrite(PIN_BUTTON_UP, HIGH);
pinMode(PIN_BUTTON_DOWN, INPUT);
digitalWrite(PIN_BUTTON_DOWN, HIGH);
pinMode(PIN_BUTTON_SELECT, INPUT);
digitalWrite(PIN_BUTTON_SELECT, HIGH);
}
Дальше мы научимся определять, нажата ли кнопка.
Как узнать, когда была нажата кнопка на джойстик шилде?
После одоления предыдущих пунктов, можно определять, нажата ли кнопка с помощью функции digitalRead(). Когда считываемое значение будет LOW, кнопка нажата, а когда значение равно HIGH, кнопка не нажата.
if (digitalRead(PIN_BUTTON_LEFT) == LOW) {
// Кнопка нажата
} else {
// Кнопка не нажата
}
Следующий пример отобразит состояние каждой кнопки и значения с джойстика в серийном мониторе Arduino IDE:
const byte PIN_BUTTON_SELECT = 2;
const byte PIN_BUTTON_RIGHT = 3;
const byte PIN_BUTTON_UP = 4;
const byte PIN_BUTTON_DOWN = 5;
const byte PIN_BUTTON_LEFT = 6;
const byte PIN_ANALOG_X = 0;
const byte PIN_ANALOG_Y = 1;
void setup() {
Serial.begin(9600);
pinMode(PIN_BUTTON_RIGHT, INPUT);
digitalWrite(PIN_BUTTON_RIGHT, HIGH);
pinMode(PIN_BUTTON_LEFT, INPUT);
digitalWrite(PIN_BUTTON_LEFT, HIGH);
pinMode(PIN_BUTTON_UP, INPUT);
digitalWrite(PIN_BUTTON_UP, HIGH);
pinMode(PIN_BUTTON_DOWN, INPUT);
digitalWrite(PIN_BUTTON_DOWN, HIGH);
pinMode(PIN_BUTTON_SELECT, INPUT);
digitalWrite(PIN_BUTTON_SELECT, HIGH);
}
void loop() {
Serial.print("l:");
Serial.print(digitalRead(PIN_BUTTON_LEFT));
Serial.print(" ");
Serial.print("r:");
Serial.print(digitalRead(PIN_BUTTON_RIGHT));
Serial.print(" ");
Serial.print("u:");
Serial.print(digitalRead(PIN_BUTTON_UP));
Serial.print(" ");
Serial.print("d:");
Serial.print(digitalRead(PIN_BUTTON_DOWN));
Serial.print(" ");
Serial.print("x:");
Serial.print(analogRead(PIN_ANALOG_X));
Serial.print(" ");
Serial.print("y:");
Serial.print(analogRead(PIN_ANALOG_Y));
Serial.print(" ");
Serial.print("s:");
Serial.print(digitalRead(PIN_BUTTON_SELECT));
Serial.print(" ");
Serial.println();
}
Оставляйте Ваши комментарии, вопросы и делитесь личным опытом ниже. В дискуссии часто рождаются новые идеи и проекты!