The first read
Six wires, no library, and a sketch that prints the stick, the click and the button five times a second. X and Y come out as raw counts so you can see which way each one rises; BTNS comes out in millivolts and as a button's name, from the six lines in the last chapter.
Six wires
GND to GND, and 3V3 to 3V3 on every board, the Uno included. X, Y and BTNS to three analog pins and KEY to a digital one:
| Board | X | Y | BTNS | KEY |
|---|---|---|---|---|
| Arduino Uno | A0 | A1 | A2 | D2 |
| ESP32 | GPIO 34 | GPIO 35 | GPIO 32 | GPIO 25 |
| ESP32-S3 | GPIO 4 | GPIO 5 | GPIO 6 | GPIO 7 |
| Raspberry Pi Pico | GP26 | GP27 | GP28 | GP15 |
The pins are the same in every sketch in this book. On the ESP32s, X, Y and BTNS are all on ADC1, the converter that keeps working with Wi-Fi on, and none of the four is a pin the chip reads at boot. On a Pico they are its three analog inputs, all of them.
The sketch
readMv() reads millivolts: analogReadMilliVolts on an ESP32 or
ESP32-S3, and the count scaled by the ADC's full scale on an Uno or a Pico.
whichButton() walks the six lines from telling them
apart, highest first,
and returns the first button whose line the reading is above.
X and Y print as plain counts, on purpose. The next article turns them into something tidier; this one is for finding out what your stick does.
What you should see
The serial monitor at 115200 prints five lines a second. On an Uno, with the stick let go and U5 pressed, something like:
X 331 Y 344 KEY 0 BTNS 0 mV none
X 330 Y 344 KEY 0 BTNS 1100 mV U5
X 612 Y 343 KEY 0 BTNS 0 mV none
X 331 Y 344 KEY 1 BTNS 0 mV noneLet go, X and Y sit near the middle of what this board can reach: near 338 on an Uno, 512 on a Pico, 2048 on an ESP32, and never exactly. Now push the stick towards the header, then away, then left and right, and write down which number moves and which way. That is your stick's orientation, and the steering sketch has a switch for each axis in case it is not the way you want.
KEY prints 1 only while the stick is pushed straight down. The buttons print their names one at a time; press two and only one appears, which is one button at a time.
The code
No library. X and Y print as analogRead counts, KEY as 0 or 1, and BTNS in millivolts with the button it names. Change the four _PIN numbers to the pins you wired; the comment above them lists all four boards.
/*
Slim Joystick - first read TK21 / /p/tk21
Wiring. Parts up, header along the top. Count from the square pad,
which is GND at the right-hand end, leftwards:
GND -> GND
3V3 -> 3V3 on every board, the Uno included (it is printed 3V3)
BTNS -> A2 on an Uno, GPIO 32 on an ESP32, GPIO 6 on an
ESP32-S3, GP28 on a Raspberry Pi Pico (analog)
X -> A0, GPIO 34, GPIO 4, GP26 (same order, analog)
Y -> A1, GPIO 35, GPIO 5, GP27 (analog)
KEY -> D2, GPIO 25, GPIO 7, GP15 (digital)
Arduino IDE
Tools > Board your board, e.g. ESP32S3 Dev Module
Tools > Port the one that appears when you plug in
Tools > USB CDC On Boot Enabled (ESP32-S3 only)
No library needed.
*/
// The pins X, Y, BTNS and KEY are wired to.
// Uno: A0 A1 A2 2. ESP32: 34 35 32 25. ESP32-S3: 4 5 6 7. Pico: 26 27 28 15.
const int X_PIN = 4;
const int Y_PIN = 5;
const int BTNS_PIN = 6;
const int KEY_PIN = 7;
// Uno and Pico only: the ADC's full scale, in mV.
// Uno: 5000. Pico: 3300.
const float FULL_SCALE_MV = 5000.0;
// The buttons in ladder order, and the line under each one's level,
// in mV with 3V3 on the header: halfway to the next level down.
const char* BUTTON_NAME[] = {"U11", "U10", "U5", "U7", "U8", "U6"};
const int LIMIT_MV[] = {2475, 1375, 963, 743, 605, 275};
// Millivolts on an analog pin, on any of the four boards.
float readMv(int pin) {
#if defined(ARDUINO_ARCH_ESP32)
return analogReadMilliVolts(pin); // calibrated in the chip
#else
return analogRead(pin) * FULL_SCALE_MV / 1023.0;
#endif
}
// Which button BTNS shows: 0 to 5 in ladder order, or -1 for none.
int whichButton(float mv) {
for (int i = 0; i < 6; i++) {
if (mv > LIMIT_MV[i]) return i;
}
return -1;
}
void setup() {
Serial.begin(115200);
pinMode(KEY_PIN, INPUT); // R1 on the board pulls it down
}
void loop() {
int x = analogRead(X_PIN);
int y = analogRead(Y_PIN);
int key = digitalRead(KEY_PIN); // HIGH while the stick is pushed down
float btns = readMv(BTNS_PIN);
int b = whichButton(btns);
Serial.print("X ");
Serial.print(x);
Serial.print(" Y ");
Serial.print(y);
Serial.print(" KEY ");
Serial.print(key);
Serial.print(" BTNS ");
Serial.print(btns, 0);
Serial.print(" mV ");
Serial.println(b < 0 ? "none" : BUTTON_NAME[b]);
delay(200); // five lines a second
}readMv() uses analogReadMilliVolts on an ESP32 or ESP32-S3 and scales the count everywhere else, so the one list of lines works on every board. On an Uno set FULL_SCALE_MV to 5000; on a Pico, 3300.
The same read in MicroPython, for an ESP32, an ESP32-S3 or a Pico. X and Y print as 16-bit counts, 0 to 65535 on every board, and BTNS in millivolts with the button it names.
"""
Slim Joystick - first read, MicroPython TK21 / /p/tk21
Wiring. Parts up, header along the top. Count from the square pad,
which is GND at the right-hand end, leftwards:
GND -> GND
3V3 -> 3V3 (the header is printed 3V3; never 5V)
BTNS -> GPIO 32 on an ESP32, GPIO 6 on an ESP32-S3,
GP28 on a Raspberry Pi Pico (analog)
X -> GPIO 34, GPIO 4, GP26 (same order, analog)
Y -> GPIO 35, GPIO 5, GP27 (analog)
KEY -> GPIO 25, GPIO 7, GP15 (digital)
Thonny
Run > Configure interpreter MicroPython (ESP32) or
MicroPython (Raspberry Pi Pico)
Save it to the board as main.py to run it on every power-up.
Nothing to install: machine, sys and time are built in.
"""
import sys
import time
from machine import ADC, Pin
# The GPIO numbers X, Y, BTNS and KEY are wired to.
# ESP32: 34 35 32 25. ESP32-S3: 4 5 6 7. Pico: 26 27 28 15.
X_PIN, Y_PIN, BTNS_PIN, KEY_PIN = 4, 5, 6, 7
# The buttons in ladder order, and the line under each one's level,
# in mV with 3V3 on the header.
BUTTON_NAME = ("U11", "U10", "U5", "U7", "U8", "U6")
LIMIT_MV = (2475, 1375, 963, 743, 605, 275)
ESP = sys.platform == "esp32" # ESP32 and ESP32-S3
def analog(pin):
adc = ADC(Pin(pin))
if ESP:
adc.atten(ADC.ATTN_11DB) # the full range, to about 3.1 V
return adc
x_adc = analog(X_PIN)
y_adc = analog(Y_PIN)
btns = analog(BTNS_PIN)
key = Pin(KEY_PIN, Pin.IN) # R1 on the board pulls it down
def millivolts(adc):
if ESP:
return adc.read_uv() / 1000 # calibrated in the chip
return adc.read_u16() * 3300 / 65535
def which_button(mv):
for i, limit in enumerate(LIMIT_MV):
if mv > limit:
return i
return -1
while True:
mv = millivolts(btns)
b = which_button(mv)
print("X %5d Y %5d KEY %d BTNS %4.0f mV %s" % (
x_adc.read_u16(), y_adc.read_u16(), key.value(), mv,
"none" if b < 0 else BUTTON_NAME[b]))
time.sleep_ms(200) # five lines a secondThere is no Uno here: an Uno cannot run MicroPython. The atten line matters on an ESP32: without it the ADC stops near 1 V and U11 and U10 read the same. KEY is Pin.IN with no pull, because R1 on the board already pulls it down. Stop it with Ctrl-C.
When it does not work
The tracks have no supply or no ground. Check the 3V3 and GND wires first: count from the square pad, which is GND at the right-hand end, parts up. Then check that X and Y are on analog pins and that the sketch's pin numbers match them.
Press U11, the left red button. If it still says none, BTNS is not on the pin the sketch reads, or 3V3 is missing. If U11 works and the others do not, BTNS is on a digital-only pin or the lines in the sketch have been changed.
The sketch sets KEY to INPUT because R1 on the board pulls it down. If you changed it to INPUT_PULLUP, change it back. If KEY is not wired, the pin floats and prints anything.
Set Tools > USB CDC On Boot to Enabled and upload again. Without it the S3's USB port does not bring up a serial port at boot, so the sketch runs with nowhere to print.
Turning the stick's raw numbers into −100 to 100, with 0 when it is let go.
A centre and a dead zone →Edit this page — content/books/slim-joystick/the-first-read.mdx
Questions about this product
See what other owners have asked, and read their solutions.
Slim Joystick
Loading discussions…
Discuss this article
Ask about this page. The answer stays here, on the page it belongs to, for whoever hits the same wall next.