Принцип работы 2-осевого джойстика и подключение к Arduino + Processing
Когда вы слышите термин «аналоговый джойстик», первое, что приходит на ум, — это игровой контроллер. Хотя джойстики определённо распространены в играх, в мире DIY-электроники они открывают целую вселенную захватывающих возможностей.
Вы можете использовать один для управления движениями робота или ровера, позволяя ему двигаться вперёд, назад или поворачивать интуитивными движениями большого пальца. Или, возможно, вы хотите создать систему плавного управления камерой, где лёгкие нажатия на джойстик приводят к медленным панорамированиям или наклонам.
Давайте разберёмся, как эти модули джойстиков работают и как можно подключить их к Arduino для ваших проектов.
Обзор оборудования
Если вы когда-либо играли на контроллере PlayStation, вы сразу узнаете этот аналоговый джойстик. Он выглядит и ощущается очень похоже на те, что Sony использует в своих контроллерах. Особенность этого джойстика в том, что он «самоцентрирующийся» и «подпружиненный». Это означает, что когда вы перестаёте нажимать на него и отпускаете, он автоматически возвращается в центральное положение. Джойстик также имеет удобную чашеобразную ручку, идеально подходящую для большого пальца, как стики на игровых контроллерах.
Потенциометры
В основе работы этого джойстика лежат два потенциометра по 5 кОм. Если внимательно посмотреть на модуль джойстика, вы заметите два серых прямоугольных компонента по бокам — это потенциометры. При перемещении джойстика вы заметите, что каждый из них реагирует только на движение в одном направлении. Потенциометр с одной стороны определяет только движение влево или вправо (ось X), а другой — только движение вверх или вниз (ось Y).
Эти потенциометры подключены к джойстику через механическую систему, называемую «карданный механизм», которую мы обсудим чуть позже. Это умное расположение гарантирует, что при диагональном движении джойстика оно правильно разделяется на горизонтальную и вертикальную составляющие.
Кнопка мгновенного действия
Ещё одна интересная особенность джойстика — встроенная кнопка мгновенного действия — это маленькая чёрная коробочка, которую можно увидеть сбоку модуля. Эта кнопка активируется при нажатии непосредственно на ручку джойстика.
Если присмотреться, вы заметите, что нажатие на ручку вызывает нажатие маленького рычага на кнопку. Эта продуманная конструкция гарантирует, что рычаг может воздействовать на кнопку независимо от положения джойстика. Это означает, что вы можете толкать джойстик в любом направлении и при этом нажимать на него.
Теперь давайте глубже изучим модуль джойстика и поймём, как он работает.
Как работает модуль аналогового джойстика?
Поистине удивительно, как джойстик может точно преобразовывать даже мельчайшие движения ваших пальцев в электрические сигналы, которые компьютер или микроконтроллер может понять. Эта точность обеспечивается продуманной конструкцией джойстика, использующей два потенциометра и карданный механизм.
Карданный механизм
Карданный механизм — это то, что позволяет джойстику плавно двигаться в нескольких направлениях. При движении джойстика ручка перемещает тонкий стержень, расположенный между двумя вращающимися осями с прорезями. Эти оси образуют карданную систему. Одна ось управляет движением по оси X (влево и вправо), а другая — по оси Y (вверх и вниз).
Когда вы наклоняете джойстик вперёд или назад, ось Y вращается. Когда перемещаете влево или вправо — вращается ось X. И вот что особенно интересно — когда вы толкаете джойстик по диагонали, обе оси вращаются одновременно.
Теперь, как джойстик преобразует эти физические движения в электрические сигналы, понятные Arduino? Ответ кроется в двух специальных потенциометрах.
Каждая из вращающихся осей джойстика подключена к своему потенциометру. При движении джойстика оси вращаются, что вызывает вращение потенциометров. Внутри каждого потенциометра находится контактная дорожка (движок), скользящая по резистивной дорожке. Когда джойстик перемещается до упора в одном направлении, движок перемещается к одному концу дорожки. При перемещении в противоположном направлении — к другому концу.
Это изменение положения создаёт изменение сопротивления. Измеряя значения потенциометров, можно определить точное положение джойстика.
Чтение аналоговых значений с джойстика
Джойстик выдаёт аналоговый сигнал с напряжением от 0 до VCC, представляющий его положение. При перемещении джойстика по оси X от одного крайнего положения к другому, выходное напряжение изменяется от 0V до VCC. То же самое происходит для оси Y. Когда джойстик находится в центральном (или покоящемся) положении, выходное напряжение составляет примерно половину напряжения питания.
Это изменяющееся напряжение может быть считано аналого-цифровым преобразователем (АЦП) микроконтроллера для определения точного положения джойстика.
При использовании Arduino, который имеет 10-битное разрешение АЦП, аналоговые показания для каждой оси могут варьироваться от 0 до 1023. При перемещении джойстика по оси X показания изменяются предсказуемым образом. При нажатии до упора влево ось X показывает значение, близкое к 0. Когда джойстик в центральном или нейтральном положении — приблизительно 512. При нажатии до упора вправо — близкое к 1023.
Ось Y работает точно так же. При нажатии до упора вверх ось Y показывает значение, близкое к 0. В центральном положении — около 512. При нажатии до упора вниз — близкое к 1023.
При диагональном перемещении джойстика оба значения X и Y изменяются одновременно. Считывая оба значения вместе, программа может определить точное положение джойстика в любой момент. Например, если X = 512 и Y = 1023, вы знаете, что джойстик нажат прямо вниз. Если X = 0 и Y = 0 — он нажат в верхний левый угол.
На рисунке ниже показаны различные значения, которые можно ожидать при перемещении джойстика в различных направлениях.
Распиновка модуля джойстика
Давайте рассмотрим назначение каждого вывода модуля джойстика:
GND — это вывод заземления.
VCC питает модуль джойстика. Подключите к положительному источнику питания — обычно 5V или 3.3V, в зависимости от потребностей проекта.
VRx — вывод аналогового выхода, соответствующий горизонтальному (лево-право) перемещению джойстика. При перемещении до упора влево на этот вывод подаётся напряжение, близкое к 0 вольт. При перемещении до упора вправо — напряжение, близкое к VCC (5V или 3.3V). Когда джойстик в центре без воздействия, вывод выдаёт примерно половину VCC (около 2.5V при питании 5V).
VRy работает так же, как VRx, но для вертикального (вверх-вниз) перемещения. Перемещение до упора вверх даёт напряжение, близкое к 0 вольт, до упора вниз — максимальное напряжение (VCC). В центральном положении вывод выдаёт примерно половину VCC.
SW — вывод встроенной кнопки внутри джойстика. Кнопка активируется при нажатии на ручку. Важно понимать, что по умолчанию этот вывод находится в «плавающем» состоянии, то есть не даёт чётких сигналов HIGH или LOW сам по себе. Для правильной работы кнопки нужен подтягивающий резистор — это может быть внутренний подтягивающий резистор Arduino (активируемый в коде) или внешний резистор. С подтяжкой вывод нормально читается как HIGH, а при нажатии на джойстик изменяется на LOW.
Схема подключения модуля джойстика к Arduino
Давайте подключим модуль джойстика к Arduino. Это довольно просто, когда вы знаете, какие провода куда идут.
Сначала подключим сигналы от джойстика к Arduino. Подключите вывод VRx (ось X) к аналоговому выводу A0 Arduino, а вывод VRy (ось Y) — к аналоговому выводу A1. Для определения нажатия кнопки джойстика подключите вывод SW к цифровому выводу D8 Arduino.
Затем подключим питание. Подключите VCC к выходу 5V Arduino, а GND — к земле Arduino.
Вот таблица подключений для справки:
| Joystick Module | Arduino | |
| GND | GND | |
| VCC | 5V | |
| VRx | A0 | |
| VRy | A1 | |
| SW | 8 |
На рисунке ниже показано подключение модуля джойстика к Arduino.
После правильного подключения можно приступать к написанию кода для чтения положения джойстика и определения нажатия кнопки.
Пример Arduino 1 — Чтение показаний джойстика
Вот простой скетч Arduino, который считывает все входные данные с джойстика и отображает их на экране компьютера через монитор последовательного порта.
// Arduino pin numbers
const int SW_pin = 8; // digital pin connected to switch output
const int X_pin = 0; // analog pin connected to X output
const int Y_pin = 1; // analog pin connected to Y output
void setup() {
pinMode(SW_pin, INPUT);
digitalWrite(SW_pin, HIGH);
Serial.begin(9600);
}
void loop() {
Serial.print("Switch: ");
Serial.print(digitalRead(SW_pin));
Serial.print(" | ");
Serial.print("X-axis: ");
Serial.print(analogRead(X_pin));
Serial.print(" | ");
Serial.print("Y-axis: ");
Serial.print(analogRead(Y_pin));
Serial.println(" | ");
delay(200);
}
После загрузки скетча вы должны увидеть следующий вывод в мониторе последовательного порта.
Объяснение кода:
В начале мы указываем Arduino, какие выводы подключены к джойстику. Мы используем цифровой вывод 8 для встроенной кнопки. Для отслеживания движения джойстика используем аналоговые выводы A0 и A1. Вывод A0 измеряет горизонтальное движение (ось X), а вывод A1 — вертикальное (ось Y).
// Arduino pin numbers
const int SW_pin = 8; // digital pin connected to switch output
const int X_pin = 0; // analog pin connected to X output
const int Y_pin = 1; // analog pin connected to Y output
В функции setup() мы подготавливаем кнопку к правильному чтению. Сначала конфигурируем вывод кнопки как вход. Также активируем внутренний подтягивающий резистор Arduino с помощью функции digitalWrite(pin, value). Это гарантирует, что когда кнопка не нажата, Arduino читает значение 1 (HIGH), а при нажатии — 0 (LOW). Наконец, мы начинаем связь между Arduino и компьютером, чтобы видеть показания джойстика в мониторе последовательного порта.
pinMode(SW_pin, INPUT);
digitalWrite(SW_pin, HIGH);
Serial.begin(9600);
В функции loop() мы непрерывно считываем состояние кнопки и положение джойстика, а затем отображаем значения в мониторе последовательного порта. Сначала проверяем, нажата ли кнопка, с помощью digitalRead(SW_pin). Если функция возвращает 1 — кнопка не нажата, если 0 — кнопка нажата.
Затем считываем положение джойстика по осям X и Y с помощью analogRead(X_pin) и analogRead(Y_pin). Эти функции дают значения от 0 до 1023 в зависимости от перемещения джойстика. Чем ближе значение к 0, тем сильнее джойстик нажат в одном направлении, а значение, близкое к 1023, означает нажатие в противоположном направлении. Эти показания выводятся в монитор последовательного порта в организованном виде, чтобы легко наблюдать изменение значений при перемещении джойстика.
В конце добавляем небольшую задержку в 200 миллисекунд перед следующим набором показаний, чтобы экран не обновлялся слишком быстро, что облегчает наблюдение за поведением джойстика.
Serial.print("Switch: ");
Serial.print(digitalRead(SW_pin));
Serial.print(" | ");
Serial.print("X-axis: ");
Serial.print(analogRead(X_pin));
Serial.print(" | ");
Serial.print("Y-axis: ");
Serial.print(analogRead(Y_pin));
Serial.println(" | ");
delay(200);
Пример Arduino 2 — Анимация движений джойстика в Processing IDE
Давайте создадим интересный проект Arduino, демонстрирующий, как модуль джойстика может управлять анимацией в Processing IDE.
По завершении вы сможете видеть виртуальное представление положения джойстика на экране компьютера. При перемещении физического джойстика круг на экране будет двигаться соответственно!
Это только начало — когда вы поймёте принцип работы, можно расширить проект для анимации игровых персонажей, управления движением камер видеонаблюдения или даже разработки управления для дистанционных транспортных средств.
Код Arduino
Для этого проекта Arduino должен отправлять три данных на компьютер: положение по оси X, положение по оси Y и состояние кнопки (нажата или не нажата).
Код Arduino аналогичен предыдущему примеру, но с одним важным отличием: значения отправляются через запятые. Эти запятые действуют как разделители, позволяя программе Processing легко определить, где заканчивается одно значение и начинается следующее.
Когда вы загрузите этот код в Arduino, он будет непрерывно отправлять данные вида: «512,498,1» (представляющие положение по X, положение по Y, состояние кнопки).
Загрузите этот скетч в Arduino:
int xValue = 0; // read value of the X axis
int yValue = 0; // read value of the Y axis
int bValue = 0; // value of the button reading
void setup() {
Serial.begin(9600); // Open the serial port
pinMode(8, INPUT); // Configure Pin 2 as input
digitalWrite(8, HIGH);
}
void loop() {
// Read analog port values A0 and A1
xValue = analogRead(A0);
yValue = analogRead(A1);
// Read the logic value on pin 2
bValue = digitalRead(8);
// We display our data separated by a comma
Serial.print(xValue, DEC);
Serial.print(",");
Serial.print(yValue, DEC);
Serial.print(",");
Serial.print(!bValue);
// We end with a newline character to facilitate subsequent analysis
Serial.print("\n");
// Small delay before the next measurement
delay(10);
}
Код Processing
После загрузки кода Arduino оставьте Arduino подключённым к компьютеру. Теперь нужно запустить код Processing, который будет считывать значения, разделённые запятыми, и создавать визуальное отображение.
Код Processing будет:
Устанавливать соединение с Arduino
Считывать входящие данные (состояние кнопки, положение по X, положение по Y)
Использовать эти данные для рисования круга на экране, движущегося вместе с джойстиком
Изменять размер круга при нажатии кнопки джойстика
import processing.serial.*; //import the Serial library
Serial myPort;
int x; // variable holding the value from A0
int y; // variable holding the value from A1
int b; // variable holding the value from digital pin 2
PFont f; // define the font variable
String portName;
String val;
void setup() {
size(512, 512); // window size
// we are opening the port
myPort = new Serial(this, Serial.list()[0], 9600);
myPort.bufferUntil('\n');
// choose the font and size
f = createFont("Arial", 16, true); // Arial, 16px, anti-aliasing
textFont(f, 16); // size 16px
}
// drawing loop
void draw() {
fill(0); // set the fill color to black
clear(); // clean the screen
fill(255); // set the fill color to white
if (b == 1) // check if the button is pressed
{
// draw a larger circle with specified coordinates
ellipse(x / 2, y / 2, 50, 50);
} else {
// we draw a circle with a certain coordinates
ellipse(x / 2, y / 2, 25, 25);
}
// we display data
text("AnalogX=" + (1023 - x) + " AnalogY=" + (1023 - y), 10, 20);
}
// data support from the serial port
void serialEvent(Serial myPort) {
// read the data until the newline n appears
val = myPort.readStringUntil('\n');
if (val != null) {
val = trim(val);
// break up the decimal and new line reading
int[] vals = int(splitTokens(val, ","));
// we assign to variables
x = vals[0];
y = vals[1];
b = vals[2];
}
}
Когда вы запустите этот код в Processing IDE, появится окно. Внутри него белый круг будет двигаться в соответствии с положением джойстика. Если вы толкнёте джойстик вверх, вниз, влево или вправо — круг последует за этими направлениями. Нажатие кнопки джойстика увеличит круг, визуально показывая, что кнопка активна. В верхнем левом углу окна также будут отображаться значения осей X и Y джойстика, обновляющиеся в реальном времени.
Объяснение кода:
Разберём код пошагово.
В начале программы мы импортируем библиотеку Serial, которая позволяет Processing обмениваться данными с Arduino.
import processing.serial.*; //import the Serial library
Serial myPort;
Затем объявляем переменные для хранения положений X и Y джойстика, состояния кнопки и шрифта для отображения текста.
int x; // variable holding the value from A0
int y; // variable holding the value from A1
int b; // variable holding the value from digital pin 2
PFont f; // define the font variable
String portName;
String val;
В функции setup() мы создаём окно 512 × 512 для отображения движения джойстика. Программа устанавливает соединение с Arduino, открывая первый доступный последовательный порт. Если это не сработает, может потребоваться вручную выбрать правильный порт, к которому подключён Arduino. После этого выбирается шрифт для отображения данных джойстика на экране.
void setup() {
size(512, 512); // window size
// we are opening the port
myPort = new Serial(this, Serial.list()[0], 9600);
myPort.bufferUntil('\n');
// choose the font and size
f = createFont("Arial", 16, true); // Arial, 16px, anti-aliasing
textFont(f, 16); // size 16px
}
Функция draw() отвечает за непрерывное обновление дисплея. Сначала экран очищается для удаления предыдущего кадра. Затем рисуется белый круг с координатами, определяемыми положением джойстика. Если кнопка джойстика нажата — круг рисуется больше. Если не нажата — остаётся меньше. Значения X и Y джойстика также отображаются текстом в верхнем левом углу, чтобы видеть изменение показаний в реальном времени.
void draw() {
fill(0); // set the fill color to black
clear(); // clean the screen
fill(255); // set the fill color to white
if (b == 1) // check if the button is pressed
{
// draw a larger circle with specified coordinates
ellipse(x / 2, y / 2, 50, 50);
} else {
// we draw a circle with a certain coordinates
ellipse(x / 2, y / 2, 25, 25);
}
// we display data
text("AnalogX=" + (1023 - x) + " AnalogY=" + (1023 - y), 10, 20);
}
Функция serialEvent() обрабатывает входящие данные от Arduino. Она ожидает полную строку данных, затем считывает и обрабатывает её. Полученные значения разделены запятыми, поэтому мы разбиваем их на три части: значение оси X, значение оси Y и состояние кнопки. Эти значения затем присваиваются соответствующим переменным для обновления дисплея.
void serialEvent(Serial myPort) {
// read the data until the newline n appears
val = myPort.readStringUntil('\n');
if (val != null) {
val = trim(val);
// break up the decimal and new line reading
int[] vals = int(splitTokens(val, ","));
// we assign to variables
x = vals[0];
y = vals[1];
b = vals[2];
}
}