Digital Input Reading (Push Button) Tutorial
After blinking an LED, the next natural step is reading the outside world. In this tutorial you will learn how to connect a push button, read its state with GPIO.read, and finally react to presses using interrupts — all tested first in the Windows simulator and then on your physical ESP32.
What you need
- JARU IDE installed on your PC — you can complete the entire tutorial with just this.
- (Optional) An ESP32 board, a push button, an LED, resistors, and a USB cable.
Step 1: The code — Polling
The most straightforward way to read a push button is to constantly check its state inside the main loop. Open JARU IDE, create a new file called button.aru, and type the following code:
// Import the GPIO module to control pins
use GPIO
println("Starting button reading...");
// 1. Pin configuration
// Pin 4: push button with internal pull-up resistor (reads HIGH when not pressed)
GPIO.pinmode(4, GPIO.PULLUP);
// Pin 2: built-in LED as output
GPIO.pinmode(2, GPIO.OUTPUT);
println("Hardware configured. Waiting for presses...");
// 2. Main loop — polling
while (true)
var state = GPIO.read(4);
if (state == LOW) then
// The button is pressed (connects to GND → reads LOW)
GPIO.write(2, HIGH);
println("Button pressed → LED on");
else
GPIO.write(2, LOW);
end
pause(50); // Small debounce delay
end
When you configure a pin with GPIO.PULLUP, the ESP32 enables an internal resistor that keeps the pin at HIGH when nothing is connected. When the button is pressed and connects the pin to GND, the level drops to LOW. This way you don't need an external resistor.
What each part does
GPIO.pinmode(4, GPIO.PULLUP) configures pin 4 as a digital input with an internal pull-up resistor. The pin reads HIGH at rest and LOW when the button connects to GND.
GPIO.read(4) returns the current digital state of the pin: HIGH (1) or LOW (0).
state == LOW detects that the button is pressed. With pull-up, the logic is inverted: pressed = LOW.
pause(50) introduces a 50 ms delay that acts as a basic debounce filter, preventing erratic readings caused by the mechanical bounce of the button.
Step 2: Simulation on Windows
- Open the GPIO Simulator from the IDE menu Build → GPIO Sim.
- Add pin 4 manually with the New GPIO button and attach a PushButton card.
- Add pin 2 and attach an LED card.
- Click Run to execute the program on the VM.
- You will see the startup messages in the output console.
- Press the button on the PushButton card in the simulator and watch the LED card turn on while you hold it down. When you release it, the LED turns off.

- Output console
- GPIO Simulator
Starting button reading...
Hardware configured. Waiting for presses...
Button pressed → LED on
Button pressed → LED on
Pin 4 will appear in the table with direction Input and type Digital. The PushButton card lets you simulate a press with a click. Pin 2 will show the LED card lighting up while the button is held down.
Step 3: Improvement — Using interrupts
Polling works, but it forces the processor to keep checking constantly. Interrupts are more efficient: the hardware automatically notifies when the pin changes state. Create a new file button_int.aru:
// Import the GPIO module
use GPIO
println("Starting interrupt-based reading...");
// 1. Pin configuration
GPIO.pinmode(4, GPIO.PULLUP);
GPIO.pinmode(2, GPIO.OUTPUT);
// 2. State variable
var ledOn = false;
// 3. Callback function — runs when the pin changes
func onButtonPress(pin, val)
ledOn = !ledOn; // Toggle state
if (ledOn) then
GPIO.write(2, HIGH);
println("LED on (toggle)");
else
GPIO.write(2, LOW);
println("LED off (toggle)");
end
end
// 4. Attach interrupt on falling edge (HIGH → LOW = press)
GPIO.onInterrupt(4, GPIO.FALLING, onButtonPress);
println("Interrupt registered. Press the button...");
// 5. Main loop free for other tasks
while (true)
pause(100);
end
Notice the difference: the first example turns the LED on while you hold the button. This second example toggles the state with each press — press once to turn on, press again to turn off.
What each new part does
func onButtonPress() defines the callback function that will be automatically executed every time an interrupt is detected on the pin.
GPIO.onInterrupt(4, GPIO.FALLING, onButtonPress) attaches the onButtonPress function to pin 4. The GPIO.FALLING mode triggers the interrupt on the falling edge (when the pin goes from HIGH to LOW), that is, right when you press the button.
while (true) pause(100); end keeps the program alive. The main loop stays free because all the button logic is handled by the interrupt.
Available interrupt modes
| Constant | Triggers when... |
|---|---|
GPIO.RISING | The pin goes from LOW to HIGH (rising edge) |
GPIO.FALLING | The pin goes from HIGH to LOW (falling edge) |
GPIO.CHANGE | The pin changes state in either direction |
GPIO.ONLOW | The pin stays at LOW level |
GPIO.ONHIGH | The pin stays at HIGH level |
Step 4: Simulating the interrupt version
- Repeat the simulator setup: pin 4 with PushButton and pin 2 with LED.
- Run
button_int.aruwith Run. - Click the button in the simulator. Each click will toggle the LED between on and off.

- Check the console: each press prints the new state.
- Output console
- GPIO Simulator
Starting interrupt-based reading...
Interrupt registered. Press the button...
LED on (toggle)
LED off (toggle)
LED on (toggle)
The behavior is different from the first example: now each click on the PushButton changes the LED state. You don't need to hold the button down.
Step 5: Deploying to the ESP32
Once the logic is verified in the simulator, deploying to hardware is identical to the previous tutorial. You don't need to change a single line of code.
- Connect your ESP32 to the PC's USB port.
- Wire the push button between GPIO 4 and GND. No external resistor needed thanks to
GPIO.PULLUP. - In JARU IDE, select your board's COM port from the toolbar dropdown.
- Click the Upload Program button.
- Wait for the flashing process to finish, then press the physical button. The ESP32's LED will respond exactly as it did in the simulator.
The JARU philosophy continues to work: the very same button_int.aru you tested on Windows runs unchanged on the real ESP32.
Extra challenge
Combine what you have learned in this tutorial and the previous one. Try to create a program that does the following:
- When the button is pressed, the LED starts blinking automatically.
- When pressed again, the LED stops and turns off.
Hint: use a boolean variable that toggles in the interrupt and check it inside the main loop to decide whether to run the blink sequence.
Want a bigger challenge? Add a second push button on another pin to control the blink speed: each press halves the interval (500 → 250 → 125 ms) and then wraps around.
Summary
In this tutorial you have learned to configure a pin as a digital input with GPIO.pinmode and the GPIO.PULLUP mode, read the state of a push button with GPIO.read, react to presses with GPIO.onInterrupt and the GPIO.FALLING mode, distinguish between polling and interrupt-driven reading, and test both approaches on the Windows VM before deploying to the ESP32 without modifying the code.
The next tutorial introduces analog reading and writing with PWM and sensors.
