Buttons, pins and gamepad
On Windows, the controls of your game are keys. On a board, they are buttons wired to its GPIO pins, the Adafruit I2C gamepad or, on the ESP32-S31, a USB keyboard. Your program reads them the same way in every case: it assigns each action with Input.map and asks for it with Input.pressed. The functions are in the Input module; this page covers what changes on the board.
If your game is made of scene editor rooms, you don't write Input.map: the controls, their pins and the gamepad are set on the Game tab, with Edit controls..., and the code the editor generates assigns them (see The Game controls window).
Input.map on the board
What Input.map accepts depends on where the program runs:
Input.map(action, …) | Windows | ESP32 / ESP32-S3 | ESP32-P4 | ESP32-S31 |
|---|---|---|---|---|
With a key name, such as "LEFT" | Yes | No: throws ERR_TYPE_MISMATCH | No: throws ERR_TYPE_MISMATCH | Yes: a key of the USB keyboard |
With a GPIO, such as 39 | No: throws ERR_TYPE_MISMATCH | Yes, from 0 to 63 | Yes, from 0 to 63 | Yes, from 0 to 63 |
A GPIO outside 0 to 63 throws ERR_INVALID_VALUE. They are exceptions: unless you catch them with try, they stop the program (see Errors and exceptions).
For the same program to work on Windows and on the board, choose the mapping with platform(), as the examples that come with JARU IDE do:
use Input
const MOVE_LEFT = 0
const MOVE_RIGHT = 1
const JUMP = 2
if (platform() == HW_WIN32) then
Input.map(MOVE_LEFT, "LEFT")
Input.map(MOVE_RIGHT, "RIGHT")
Input.map(JUMP, "SPACE")
elsif (platform() == HW_ESP32) then
// The three buttons of the LILYGO T4, the ones the examples use
Input.map(MOVE_LEFT, 39)
Input.map(MOVE_RIGHT, 38)
Input.map(JUMP, 37)
end
Compare with HW_ESP32, not with "any board": the pins of one board are no good on another (see Pins you cannot use for a button). The constants of each chip, such as HW_ESP32S3 or HW_ESP32P4, are those of platform(), in the standard library.
How to wire a button
Input.map sets the pin as an input, with its internal pull-up resistor, and reads it inverted: the action is pressed while the pin is at 0 V. Wire the button between the pin and GND. At rest, the resistor keeps the pin at 3.3 V and the action released.
Board profiles don't say which pin each button is on: check your board's schematic. The VM doesn't check it: it accepts any number from 0 to 63, even if your chip has no such GPIO or the board uses it for something else.
The GPIO of each chip
| Chip | GPIO it has |
|---|---|
| ESP32 | 0 to 39, except 24 and 28 to 31. 34 to 39 are input only. |
| ESP32-S3 | 0 to 48, except 22 to 25. |
| ESP32-P4 | 0 to 54. |
| ESP32-S31 | 0 to 61, except 29 and 41. |
Pins you cannot use for a button
| Chip | Pins | Why |
|---|---|---|
| All | Those of the display, the SD card, the sound and the I2C bus | They already do something else. They are in your board's profile: the Display, Storage and Audio pages of ESP32 Tools. |
| ESP32 | 1 and 3 | They are the serial console: what your program prints and the VM's messages come out through them. |
| ESP32 | 34 to 39 | They have no internal pull-up resistor. Without an external one, the pin floats, and the action may read as pressed with nobody touching it. The buttons of the LILYGO T4 have their own. |
| ESP32-S3 | 43 and 44 | The serial console. |
| ESP32-S3 | 19 and 20 | They are the chip's own USB, which some boards connect through, such as the Waveshare ESP32-S3-Touch-LCD-2.8 and the JaruCade. |
| ESP32-S3 | 33 to 37 | They belong to the octal PSRAM, the memory the S3 VM works with. With Input.map on 37, the board hangs and restarts over and over. |
| ESP32-P4 | 37 and 38 | The serial console. |
| ESP32-S31 | 58 and 59 | The serial console. |
A button that gives 3.3 V when pressed
If your button pulls the pin to 3.3 V when pressed, Input.map reads it inverted. Read it yourself with the GPIO module and pass its state to the action with Input.set, once per frame and before asking for the actions:
use GPIO
use Input
use Display
const BUTTON = 21 // the GPIO of your button
const JUMP = 2
GPIO.pinmode(BUTTON, GPIO.PULLDOWN) // released, the pin stays at 0 V
Display.open(320, 240)
Display.fps = 30
while (true)
Input.set(JUMP, GPIO.read(BUTTON) == 1) // first thing in the frame
if (Input.justPressed(JUMP)) then
println("Jump!")
end
Display.update()
end
With Input.set, the action works like the others: Input.pressed, Input.justPressed and Input.released work as usual. GPIO 34 to 39 of the ESP32 have no internal pull-down either.
To read a pin, set it as an input: GPIO.INPUT, GPIO.PULLUP or GPIO.PULLDOWN. On the ESP32-P4 and the ESP32-S31, a pin in GPIO.OUTPUT is output only: GPIO.read() always returns 0 (see Supported boards).
The I2C gamepad
The I2C gamepad is the Adafruit Mini I2C Gamepad (seesaw, product 5743): a joystick and six buttons, A, B, X, Y, Start and Select. It connects to the board's I2C bus and answers at address 0x50 (0x51 to 0x53 if you cut its A0 and A1 jumpers). It is the JaruCade's gamepad.
The bus pins are in the board's profile, on the Storage page, I2C Bus group: SDA 11 and SCL 10 on the JaruCade and the Waveshare ESP32-S3-Touch-LCD-2.8, and SDA 7 and SCL 8 on the two ESP32-P4 boards. The VM doesn't read them: the scene editor and the I2C driver manager use them to write your code.
With the scene editor
In a game made of rooms, the gamepad is turned on in the Game tab, with Edit controls...: you choose which gamepad button drives each control, and the code the editor generates starts the gamepad and reads it in every room. It's all in The I2C gamepad, and a full case, with StarterPlatformer, in Your first game with rooms.
On the ESP32-S31, with the gamepad on and no control with a GPIO, the game doesn't use the keys of the USB keyboard: if the gamepad doesn't answer, it is left with no input. If you play with the keyboard, turn the gamepad off with Gamepad on, which becomes Gamepad off.
All the known issues of JARU, together, are in Known issues.
From your code
Outside the scene editor, the gamepad is handled with the SeesawGamepad driver. Add it to the project from the I2C driver manager, in the Input category, with Inser Driver: it copies it to Code/Drivers and writes its import (see I2C drivers).
The driver has no actions of its own: you tell it which Input action each button and each joystick direction drives, and on every frame, with update(), it reads the gamepad and passes it to the actions with Input.set. Your game keeps asking with Input.pressed.
use I2C
use Input
use Display
import "Drivers/DRV_SEESAW_GAMEPAD.aru"
const MOVE_LEFT = 0
const MOVE_RIGHT = 1
const JUMP = 2
var pad = nil
if (platform() == HW_WIN32) then
Input.map(MOVE_LEFT, "LEFT")
Input.map(MOVE_RIGHT, "RIGHT")
Input.map(JUMP, "SPACE")
else
I2C.init(0, 11, 10, 400000) // the JaruCade's bus: SDA 11 and SCL 10
pad = SeesawGamepad(0)
if (pad.beginChecked()) then
pad.bind(JUMP, pad.BTN_A)
pad.bindAxes(MOVE_LEFT, MOVE_RIGHT, -1, -1, 200)
else
println("No gamepad: error ", pad.getLastError())
pad = nil
end
end
Display.open(320, 240)
Display.fps = 30
while (true)
if (pad != nil) then pad.update() end // first thing in the frame
if (Input.pressed(JUMP)) then
println("Jump")
end
Display.update()
end
| Method | What it does |
|---|---|
SeesawGamepad(port) | Creates the driver on an I2C port already opened with I2C.init. |
configureAddress(addr) | Changes the address, if you have cut the jumpers. By default, 0x50. |
beginChecked() | Looks for the gamepad, checks that it is the 5743 and sets it up. Returns false if it isn't there; getLastError() says why. |
bind(action, button) | The button drives the action: BTN_A, BTN_B, BTN_X, BTN_Y, BTN_START or BTN_SELECT. |
bindAxes(left, right, up, down, deadZone) | The four joystick directions drive those actions; -1, none. deadZone is how far it moves from the center before it counts: the joystick goes from 0 to 1023, with the center at 512. |
update() | Reads the gamepad and sets the actions. Call it once per frame, before reading the actions. Returns false if a reading failed. |
pressed(button), axisX() and axisY() | The button and the joystick as the last update() read them. |
Make your Input.map calls before creating the gamepad: Input.map clears what the gamepad has put in that action. A button and a direction can drive the same action.
The ESP32-S31 USB keyboard
The ESP32-S31 board, the Korvo-1, has a USB-A port where you plug in a keyboard. With it, Input.map accepts the same key names as on Windows (the list is in Input), and GPIO too:
use Input
const MOVE_LEFT = 0
const MOVE_RIGHT = 1
const JUMP = 2
if (platform() == HW_WIN32 or platform() == HW_ESP32S31) then
// The same keys on Windows and on the ESP32-S31 USB keyboard
Input.map(MOVE_LEFT, "LEFT")
Input.map(MOVE_RIGHT, "RIGHT")
Input.map(JUMP, "SPACE")
end
- It is turned on in ESP32 Tools, with the USB keyboard checkbox on the Hardware page, which only shows up with the ESP32-S31. The Korvo-1 profile comes with it ticked.
- The keyboard, in its simplest mode. The VM opens the keyboard in the USB boot mode (the boot protocol). A keyboard without it isn't read, and neither are mice, USB gamepads or multimedia keys.
- Two keyboards at once, at most, and up to six keys at once on each, not counting
SHIFT,CTRLandALT. With more, the keyboard doesn't say which ones, and the VM keeps the ones it had. SHIFT,CTRLandALTare the left ones, as on Windows.- You can plug and unplug it with the program running. The serial monitor tells you: "USB host ready: plug a keyboard into the USB-A port." at startup, and then "USB keyboard connected" and "USB keyboard disconnected".
- If it stops responding, the monitor says "USB keyboard transfer error: unplug it and plug it again.": unplug it and plug it in again. If the board stops seeing the keyboard altogether, turn the board's power off and on again.
The Korvo-1 buttons
The four buttons of the Korvo-1 (SET, MODE, VOL- and VOL+) don't each go to their own GPIO: they all go to GPIO 42, each with a different voltage. Input.map can't read them; read them with GPIO.aread(42). On the board the VM was tested with, GPIO.aread(42) gives 0 with nothing pressed, 315 with SET, 853 with MODE, 1369 with VOL- and 1816 with VOL+. This function puts the boundary halfway between each two readings:
use GPIO
// The four Korvo-1 buttons go to GPIO 42, each with its own voltage.
// Returns the one pressed, or "" if there is none.
func KorvoButton()
var v = GPIO.aread(42)
if (v < 157) then return "" end
if (v < 584) then return "SET" end
if (v < 1111) then return "MODE" end
if (v < 1592) then return "VOL-" end
return "VOL+"
end
Check your board's numbers first, with println(GPIO.aread(42)). With two buttons pressed at once, the reading doesn't say which ones. To use them as actions, pass them with Input.set, as in A button that gives 3.3 V when pressed.