Skip to content

Commit

Permalink
joystick module
Browse files Browse the repository at this point in the history
  • Loading branch information
ImplFerris committed Jan 21, 2025
1 parent f4b242a commit cfaa17a
Show file tree
Hide file tree
Showing 12 changed files with 350 additions and 0 deletions.
5 changes: 5 additions & 0 deletions src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,4 +97,9 @@
- [Write Data](./rfid/write-data.md)
- [Change Auth Key](./rfid/change-auth-key.md)
- [Project Ideas](./rfid/project-ideas.md)
- [Joystick](./joystick/index.md)
- [Movement and ADC](./joystick/movement-and-12-bit-adc-value.md)
- [Pin layout](./joystick/pin-layout.md)
- [Circuit](./joystick/circuit.md)
- [Print ADC Values](./joystick/print-adc-values.md)
- [Projects](./projects.md)
68 changes: 68 additions & 0 deletions src/joystick/circuit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Connecting the Joystick to the ESP32

Let's connect the joystick to the ESP32. We need to connect the VRX and VRY pins to the ADC pins of the ESP32. The joystick will be powered with 3.3V instead of 5V because the ESP32's GPIO pins are only 3.3V tolerant. Connecting it to 5V could damage the ESP32's pins. Thankfully, the joystick can operate at 3.3V as well.


<table>
<thead>
<tr>
<th>ESP32 Pin</th>
<th style="width: 250px; margin: 0 auto;">Wire</th>
<th>Joystick Pin</th>
</tr>
</thead>
<tbody>
<tr>
<td>GND</td>
<td style="text-align: center; vertical-align: middle; padding: 0;">
<div class="wire black" style="width: 200px; margin: 0 auto;">
<div class="male-left"></div>
<div class="male-right"></div>
</div>
</td>
<td>GND</td>
</tr>
<tr>
<td>3.3V</td>
<td style="text-align: center; vertical-align: middle; padding: 0;">
<div class="wire red" style="width: 200px; margin: 0 auto;">
<div class="male-left"></div>
<div class="male-right"></div>
</div>
</td>
<td>VCC</td>
</tr>
<tr>
<td>GPIO 13</td>
<td style="text-align: center; vertical-align: middle; padding: 0;">
<div class="wire green" style="width: 200px; margin: 0 auto;">
<div class="male-left"></div>
<div class="male-right"></div>
</div>
</td>
<td>VRX</td>
</tr>
<tr>
<td>GPIO 14</td>
<td style="text-align: center; vertical-align: middle; padding: 0;">
<div class="wire blue" style="width: 200px; margin: 0 auto;">
<div class="male-left"></div>
<div class="male-right"></div>
</div>
</td>
<td>VRY</td>
</tr>
<tr>
<td>GPIO 32</td>
<td style="text-align: center; vertical-align: middle; padding: 0;">
<div class="wire yellow" style="width: 200px; margin: 0 auto;">
<div class="male-left"></div>
<div class="male-right"></div>
</div>
</td>
<td>SW</td>
</tr>
</tbody>
</table>

<img style="display: block; margin: auto;margin-top:30px;" alt="connecting joystick with esp32" src="./images/connecting joystick with esp32.png"/>
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/joystick/images/joystick-pin-layout.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/joystick/images/joystick-usb-output.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/joystick/images/joystick.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 19 additions & 0 deletions src/joystick/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Joystick

In this section, we'll explore how to use the Joystick Module. It is similar to the joysticks found on PS2 (PlayStation 2) controllers. They are commonly used in gaming, as well as for controlling drones, remote-controlled cars, robots, and other devices to adjust position or direction.

## Meet the hardware - Joystick module

<img style="display: block; margin: auto;width:250px;" alt="joystick" src="./images/joystick.jpg"/>

You can move the joystick knob vertically and horizontally, it will send its position (X and Y axes) to the MCU (e.g., ESP32). Additionally, the knob can be pressed down like a button. The joystick typically operates at 5V, but it can also be connected to 3.3V.

## How it works?
The joystick module has two 10K potentiometers: one for the X-axis and another for the Y-axis. It also includes a push button, which is visible.

When you move the joystick from right to left or left to right(X axis), you can observe one of the potentiometers moving accordingly. Similarly, when you move it up and down(Y-axis), you can observe the other potentiometer moving along.

<img style="display: block; margin: auto;width:550px;" alt="joystick" src="./images/joystick-potentiometers-push-button.jpg"/>

You can also observe the push-button being pressed when you press down on the knob.

21 changes: 21 additions & 0 deletions src/joystick/movement-and-12-bit-adc-value.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
## Joystick Movement and Corresponding ADC Values
When you move the joystick along the X or Y axis, it produces an analog signal with a voltage that varies between 0 and 3.3V(or 5V if we connect it to 5V supply). When the joystick is in its center (rest) position, the output voltage is approximately 1.65V, which is half of the VCC(VCC is 3.3V in our case).

<div class="alert-box alert-box-info">
<span class="icon"><i class="fa fa-info"></i></span>
<div class="alert-content">
<b class="alert-title">Voltage Divider</b>
<p>The reason it is 1.65V in the center position is that the potentiometer acts as a voltage divider. When the potentiometer is moved, its resistance changes, causing the voltage divider to output a different voltage accordingly. Refer the <a href="/core-concepts/voltage-divider.html">voltate divider section</a>.</p>
</div>
</div>

The joystick has a total of 5 pins, and we will shortly discuss what each of them represents. Out of these, two pins are dedicated to sending the X and Y axis positions, which should be connected to the ADC pins of the microcontroller.

As you may already know, the ESP32 has a 12-bit ADC, which converts analog signals (voltage differences) into digital values. Since it is a 12-bit ADC, the analog values will be represented as digital values ranging from 0 to 4095. If you're not familiar with ADC, refer to the [ADC section](../core-concepts/adc.md) that we covered earlier.

<img style="display: block; margin: auto;width:580px;" alt="joystick-movement" src="./images/joystick-movement-and-corresponding-esp32-adc-values.jpg"/>

**Note:**

The ADC values in the image are just approximations to give you an idea and won't be exact. For example, I got around 1850 for X and Y at the center position. When I moved the knob toward the pinout side, X went to 0, and when I moved it to the opposite side, it went to 4095. The same applies to the Y axis.So, You might need to calibrate your joystick.

37 changes: 37 additions & 0 deletions src/joystick/pin-layout.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Pin layout

The joystick has a total of 5 pins: power supply, ground, X-axis output, Y-axis output, and switch output pin.

<img style="display: block; margin: auto;width:400px;margin-bottom: 10px;" alt="joystick" src="./images/joystick-pin-layout.jpg"/>

<table border="1" style="border-collapse: collapse; width: 100%;">
<thead>
<tr>
<th style="width:14%">Joystick Pin</th>
<th>Details</th>
</tr>
</thead>
<tbody>
<tr>
<td><span class="slanted-text black">GND</span></td>
<td>Ground pin. Should be connected to the Ground of the circuit.</td>
</tr>
<tr>
<td><span class="slanted-text red">VCC</span></td>
<td>Power supply pin (typically 5V or 3.3V ).</td>
</tr>
<tr>
<td><span class="slanted-text green">VRX</span></td>
<td>The X-axis analog output pin varies its voltage based on the joystick's horizontal position, ranging from 0V to VCC as the joystick is moved left and right.</td>
</tr>
<tr>
<td><span class="slanted-text blue">VRY</span></td>
<td>The Y-axis analog output pin varies its voltage based on the joystick's vertical position, ranging from 0V to VCC as the joystick is moved up and down.</td>
</tr>
<tr>
<td><span class="slanted-text purple">SW</span></td>
<td>Switch pin. When the joystick knob is pressed, this pin is typically pulled LOW (to GND).</td>
</tr>
</tbody>
</table>

200 changes: 200 additions & 0 deletions src/joystick/print-adc-values.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
## Print Joystick Movement ADC Values

In this program, we'll observe how joystick movement affects ADC values in real time. We will connect the ESP32 with the joystick.

As you move the joystick, the corresponding ADC values will be printed in the system console. You can compare these values with the [previous Movement and ADC Diagram](./movement-and-12-bit-adc-value.md);they should approximately match the values shown. Pressing the joystick knob will print **"Button Pressed"** along with the current coordinates.


## Generate project using esp-generate
We will enable async (Embassy) support for this project. To create the project, use the `esp-generate` command. Run the following:

```sh
esp-generate --chip esp32 joystick-movement
```

This will open a screen asking you to select options.

- Select the option "Adds embassy framework support".

Just save it by pressing "s" in the keyboard.


### Configure ADC
Let's set up the ADC and configure GPIO 13 and GPIO 14, which are mapped to the VRX and VRY pins of the joystick:

```rust
let mut adc2_config = AdcConfig::new();
let mut vrx_pin = adc2_config.enable_pin(peripherals.GPIO13, Attenuation::Attenuation11dB);
let mut vry_pin = adc2_config.enable_pin(peripherals.GPIO14, Attenuation::Attenuation11dB);

let mut adc2 = Adc::new(peripherals.ADC2, adc2_config);

```

We also configure GPIO15 as a pull-up input for the button:

```rust
let btn = Input::new(peripherals.GPIO32, Pull::Up);
```

### Printing Co-ordinates

We want to print the coordinates only when the vrx or vry values change beyond a certain threshold. This avoids continuously printing unnecessary values.

To achieve this, we initialize variables to store the previous values and a flag to determine when to print:

```rust
let mut prev_vrx: u16 = 0;
let mut prev_vry: u16 = 0;
let mut prev_btn_state = false;
let mut print_vals = true;
```

**Reading ADC Values:**

First, read the ADC values for vrx and vry. If there's an error during the read operation, we ignore it and continue the loop:

```rust
let Ok(vry): Result<u16, _> = nb::block!(adc2.read_oneshot(&mut vry_pin)) else {
continue;
};
let Ok(vrx): Result<u16, _> = nb::block!(adc2.read_oneshot(&mut vrx_pin)) else {
continue;
};
```

**Checking for Threshold Changes:**

Next, we check if the absolute difference between the current and previous values of vrx or vry exceeds a threshold (e.g., 100). If so, we update the previous values and set the print_vals flag to true:

```rust
if vrx.abs_diff(prev_vrx) > 100 {
prev_vrx = vrx;
print_vals = true;
}

if vry.abs_diff(prev_vry) > 100 {
prev_vry = vry;
print_vals = true;
}
```
Using a threshold filters out small ADC fluctuations, avoids unnecessary prints, and ensures updates only for significant changes.

**Printing the Coordinates**

If print_vals is true, we reset it to false and print the X and Y coordinates via the USB serial:

```rust
if print_vals {
print_vals = false;
println!("X: {} Y: {}\r\n", vrx, vry);
}
```

### Button Press Detection with State Transition
The button is normally in a high state. When you press the knob button, it switches from high to low. However, since the program runs in a loop, simply checking if the button is low could lead to multiple detections of the press. To avoid this, we only register the press once by detecting a high-to-low transition, which indicates that the button has been pressed.

To achieve this, we track the previous state of the button and compare it with the current state before printing the "button pressed" message. If the button is currently in a low state (pressed) and the previous state was high (not pressed), we recognize it as a new press and print the message. Then, we update the previous state to the current state, ensuring the correct detection of future transitions.

```rust
let btn_state = btn.is_low();
if btn_state && !prev_btn_state {
println!("Button Pressed");
print_vals = true;
}
prev_btn_state = btn_state;
```



## Clone the existing project
You can clone (or refer) project I created and navigate to the `joystick-movement` folder.

```sh
git clone https://github.com/ImplFerris/esp32-projects
cd esp32-projects/joystick-movement/
```


### The Full code

```rust
#![no_std]
#![no_main]

use embassy_executor::Spawner;
use embassy_time::{Duration, Timer};
use esp_backtrace as _;
use esp_hal::{
analog::adc::{Adc, AdcConfig, Attenuation},
gpio::{Input, Pull},
prelude::*,
};
use esp_println::println;
use log::info;

#[main]
async fn main(_spawner: Spawner) {
let peripherals = esp_hal::init({
let mut config = esp_hal::Config::default();
config.cpu_clock = CpuClock::max();
config
});

esp_println::logger::init_logger_from_env();

let timer0 = esp_hal::timer::timg::TimerGroup::new(peripherals.TIMG1);
esp_hal_embassy::init(timer0.timer0);

info!("Embassy initialized!");

let btn = Input::new(peripherals.GPIO32, Pull::Up);

let mut adc2_config = AdcConfig::new();
let mut vrx_pin = adc2_config.enable_pin(peripherals.GPIO13, Attenuation::Attenuation11dB);
let mut vry_pin = adc2_config.enable_pin(peripherals.GPIO14, Attenuation::Attenuation11dB);

let mut adc2 = Adc::new(peripherals.ADC2, adc2_config);

// let delay = Delay::new();

let mut prev_vrx: u16 = 0;
let mut prev_vry: u16 = 0;
let mut prev_btn_state = false;
let mut print_vals = true;

loop {
let Ok(vry): Result<u16, _> = nb::block!(adc2.read_oneshot(&mut vry_pin)) else {
continue;
};
let Ok(vrx): Result<u16, _> = nb::block!(adc2.read_oneshot(&mut vrx_pin)) else {
continue;
};

if vrx.abs_diff(prev_vrx) > 100 {
prev_vrx = vrx;
print_vals = true;
}

if vry.abs_diff(prev_vry) > 100 {
prev_vry = vry;
print_vals = true;
}

let btn_state = btn.is_low();
if btn_state && !prev_btn_state {
println!("Button Pressed");
print_vals = true;
}
prev_btn_state = btn_state;

if print_vals {
print_vals = false;

println!("X: {} Y: {}\r\n", vrx, vry);
}

Timer::after(Duration::from_millis(50)).await;
}
}
```

0 comments on commit cfaa17a

Please sign in to comment.