GPIO module
The GPIO module drives the board's input and output pins: it reads and writes digital levels, reads analog values with the converter (ADC), outputs PWM signals and calls a function when a pin changes (interrupts). On Windows, the pins are those of the IDE's GPIO simulator.
use GPIO

Platforms
| Windows | ESP32 / ESP32-S3 | ESP32-P4 | ESP32-S31 | |
|---|---|---|---|---|
| The pins | Those of the GPIO simulator, from 0 to 48 | The chip's | The chip's | The chip's |
GPIO.read of a pin in GPIO.OUTPUT | The last thing written | The last thing written | 0 | 0 |
| Default PWM | 5000 Hz and 8 bits (from 0 to 255) | 2000 Hz and 10 bits (from 0 to 1023) | Same | Same |
| PWM channels | 16 | 16 on the ESP32 and 8 on the ESP32-S3 | 8 | 8 |
| Maximum PWM resolution | 16 bits | 20 bits on the ESP32 and 14 on the ESP32-S3 | 20 bits | 20 bits |
GPIO.aread | From 0 to 4095: whatever the simulator card says | From 0 to 4095 | From 0 to 4095 on the ADC1 pins, and 0 on the rest | The raw value of its ADC, 17-bit and uncalibrated |
resolution(bits) and frequency(hz) without a pin | Give an error | Change every channel | Same | Same |
GPIO.ONLOW and GPIO.ONHIGH | Fire once on reaching the level | Fire again and again while the pin stays at that level | Same | Same |
| Arguments that aren't valid | Exception (except pinmode, write and read, which don't check them) | An error value and, nearly always, a message on the serial monitor | Same | Same |
On Windows, the module touches no hardware: it reads and writes in a memory it shares with the simulator, whether it's open or not. Without the simulator, nobody moves the inputs: an input pin reads 0 (1 with GPIO.PULLUP), and GPIO.aread, 0. Interrupts are simulated: the VM looks at the pins every few milliseconds.
On those boards, GPIO.pinmode(pin, GPIO.OUTPUT) leaves the pin output-only: GPIO.read() of that pin always returns 0, and GPIO.onInterrupt() doesn't fire with what the program writes. On the ESP32 and ESP32-S3 it does. If you need to know how you left an output pin, keep it in a variable.
Functions
| Function | What it does | Returns |
|---|---|---|
GPIO.pinmode(pin, mode) | Says whether a pin is an input or an output, and with which resistor | true |
GPIO.write(pin, value) | Sets an output pin high or low | true |
GPIO.read(pin) | Reads the level of a pin | 0 or 1 |
GPIO.aread(pin) | Reads the analog value of a pin | An integer |
GPIO.awrite(pin, value) | Outputs a PWM signal on a pin | true |
GPIO.resolution(bits) / GPIO.resolution(pin, bits) | Changes the PWM resolution | true |
GPIO.frequency(hz) / GPIO.frequency(pin, hz) | Changes the PWM frequency | true |
GPIO.getduty(pin) | Reads the PWM value of a pin | An integer |
GPIO.detach(pin) | Releases the PWM of a pin | true |
GPIO.onInterrupt(pin, mode, function) | Calls a function when a pin changes | true |
GPIO.offInterrupt(pin) | Stops watching a pin | true |
pin is the GPIO number, as on the chip: GPIO.read(15) reads GPIO 15. The VM doesn't check that the pin exists on your board.
pinmode(pin, mode)
Gets pin pin ready for the use you're going to give it. mode is one of the constants:
| Mode | What for |
|---|---|
GPIO.INPUT | Input, with no resistor. |
GPIO.OUTPUT | Output. On the ESP32 and ESP32-S3, the pin can also be read; on the P4 and the S31, it can't (see above). |
GPIO.PULLUP | Input with the internal resistor to the high level: with nothing connected it reads 1. It's the usual one for a button that connects the pin to GND. |
GPIO.PULLDOWN | Input with the internal resistor to the low level: with nothing connected it reads 0. |
GPIO.OPEN_DRAIN | Open-drain output: the pin pulls down to GND or is left floating. |
On a board, a mode that isn't one of these gives an error value and writes Invalid pin mode in 'gpio.pinmode()'. on the serial monitor. On Windows, any mode is accepted.
On the ESP32, GPIOs 34 to 39 are input-only and have no internal resistors: there, GPIO.PULLUP and GPIO.PULLDOWN do nothing. It's covered in Buttons, pins and gamepad.
use GPIO
GPIO.pinmode(2, GPIO.OUTPUT) // GPIO 2, as an output
GPIO.pinmode(15, GPIO.PULLUP) // GPIO 15, as an input with pull-up
write(pin, value)
Sets the output pin pin high with value equal to 1, true or HIGH, and low with 0, false or LOW. Any other number that isn't 0 counts as high.
use GPIO
GPIO.pinmode(2, GPIO.OUTPUT)
// An LED that blinks 5 times
for (var i = 0; i < 5; i++)
GPIO.write(2, HIGH)
pause(500)
GPIO.write(2, LOW)
pause(500)
end
read(pin)
Reads the level of pin pin: 1 (high) or 0 (low).
use GPIO
GPIO.pinmode(15, GPIO.PULLUP)
if (GPIO.read(15) == 0) then
println("Button pressed") // with PULLUP, pressed is 0
else
println("Button released")
end
aread(pin)
Reads the value of the analog converter (ADC) on pin pin: the higher the voltage, the higher the number. On the ESP32, ESP32-S3 and P4 it goes from 0 to 4095 (12 bits).
- Only some pins have an ADC: check your board's pinout. On the P4, the ones that aren't on ADC1 always read
0. - On the ESP32-S31, the value is the raw one from its ADC, 17-bit and uncalibrated: it doesn't go from 0 to 4095. Measure which values your circuit gives you.
use GPIO
var value = GPIO.aread(34)
println("ADC: ", value, " Volts: ", value * 3.3 / 4095)
The volts calculation assumes 4095 is 3.3 V: take it as an approximation.
awrite(pin, value)
Outputs a PWM signal on pin pin: a wave that switches very fast between high and low, and that on an LED looks like an in-between brightness. value is the time spent high (the duty), from 0 to the maximum of the resolution: by default, from 0 to 1023 on a board and from 0 to 255 on Windows.
The first time you use a pin with PWM, the VM assigns it one of its PWM channels. You don't need pinmode.
On a board, a value above the maximum is clipped to the maximum, and a negative one counts as 0; on Windows they're clipped between 0 and 65535. If there are no free channels left (how many there are, in Platforms), it throws ERR_OUT_OF_MEMORY (No free LEDC channels.); detach frees a pin's one.
use GPIO
GPIO.resolution(2, 8) // 8 bits on GPIO 2: from 0 to 255, as on Windows
GPIO.frequency(2, 5000)
// An LED that breathes 3 times
for (var n = 0; n < 3; n++)
for (var i = 0; i < 256; i = i + 5)
GPIO.awrite(2, i)
pause(20)
end
for (var i = 255; i >= 0; i = i - 5)
GPIO.awrite(2, i)
pause(20)
end
end
resolution(pin, bits)
Changes the PWM resolution of pin pin to bits bits: with 8, awrite goes from 0 to 255; with 10, from 0 to 1023; with 12, from 0 to 4095. Without pin, resolution(bits) changes every channel; on Windows that form gives ERR_INVALID_ARG_NUMBER, so always pass the pin.
On Windows, bits is clipped between 1 and 16. On a board it isn't checked: beyond the chip's maximum (see Platforms), the channel doesn't change and there's no warning.
use GPIO
GPIO.resolution(2, 12) // GPIO 2, from 0 to 4095
frequency(pin, hz)
Changes the PWM frequency of pin pin to hz hertz. Without pin, frequency(hz) changes every channel; like resolution, that form gives an error on Windows.
On a board, PWM channels go in pairs: the first and the second pin you use with PWM share frequency and resolution, and so do the third and the fourth, and so on. Changing one's changes the other's. If you need two different frequencies (a servo at 50 Hz and an LED at 5000 Hz), the two pins have to fall in different pairs: for instance, the first and the third to use PWM.
use GPIO
GPIO.frequency(2, 1000) // 1 kHz on GPIO 2
getduty(pin)
Returns the value the PWM of pin pin has: the last one you gave it with awrite.
A pin without PWM returns 0.
use GPIO
GPIO.awrite(2, 512)
println(GPIO.getduty(2)) // 512
detach(pin)
Disconnects pin pin from its PWM channel: the pin stops outputting the signal.
The channel becomes free for another pin, and a later awrite on the same pin gives it one again.
use GPIO
GPIO.awrite(2, 512)
// … use the PWM …
GPIO.detach(2)
onInterrupt(pin, mode, function)
Calls function each time pin pin does what mode says:
| Mode | It fires when… |
|---|---|
GPIO.RISING | The pin goes from low to high. |
GPIO.FALLING | The pin goes from high to low. It's the one for a button with GPIO.PULLUP. |
GPIO.CHANGE | The pin changes, either way. |
GPIO.ONLOW | The pin is low. On a board it fires again and again while it stays that way; on Windows, once when it gets there. |
GPIO.ONHIGH | The pin is high, like ONLOW. |
function can be a function (onPress), a method of an object (button.onPress) or the name of a global function in a string ("onPress"). It takes two parameters: the pin number and its level (0 or 1) at the moment of the change. If it has another number of parameters, the events are dropped without a word.
onInterruptdoesn't set the pin up: callpinmodefirst.- There can be 16 pins with interrupts at once. Another
onInterrupton a pin that already has one changes its function and its mode. - For a button,
FALLINGorRISINGare better thanONLOWorONHIGH: a pin that stays at one level would fill the VM's event queue.
If something isn't valid, on Windows it throws an exception: ERR_TYPE_MISMATCH if an argument isn't of the right type, ERR_INVALID_VALUE if the mode isn't in the table, and ERR_OUT_OF_MEMORY if there are already 16. On a board it returns an error value: ERR_TYPE_MISMATCH, ERR_OUT_OF_MEMORY, or ERR_INVALID_OPERATION if the pin doesn't accept interrupts; the mode isn't checked.
use GPIO
var presses = 0
func onPress(pin, level)
presses = presses + 1
println("Button on pin ", pin, ": ", presses)
end
GPIO.pinmode(15, GPIO.PULLUP)
GPIO.onInterrupt(15, GPIO.FALLING, onPress)
// The program stays alive to receive the interrupts
while (true)
pause(100)
end
With a method, the function runs with its this, and the interrupt keeps the instance alive while it's registered:
use GPIO
class Button
def init(pin)
this.pin = pin
this.presses = 0
GPIO.pinmode(pin, GPIO.PULLUP)
GPIO.onInterrupt(pin, GPIO.FALLING, this.onPress)
end
def onPress(pin, level)
this.presses = this.presses + 1
end
end
var button = Button(15)
The chip detects the change instantly, but your function is run by the VM inside the program, like the Timer callbacks: one at a time, at the safe points (when a loop goes round and when calling or returning from a function) and never inside pause(). That's why the program has to stay alive, with a loop of short pauses. When the program ends, the interrupts are removed.
offInterrupt(pin)
Stops watching pin pin and returns true, also if it had no interrupt. On a board, a pin that doesn't accept interrupts returns an error value (ERR_INVALID_OPERATION).
use GPIO
func onPress(pin, level)
println("Pressed")
end
GPIO.pinmode(15, GPIO.PULLUP)
GPIO.onInterrupt(15, GPIO.FALLING, onPress)
// … later on
GPIO.offInterrupt(15)
Constants
The pinmode modes:
| Constant | Value | Mode |
|---|---|---|
GPIO.INPUT | 0x01 | Input |
GPIO.OUTPUT | 0x03 | Output |
GPIO.PULLUP | 0x05 | Input with pull-up |
GPIO.PULLDOWN | 0x09 | Input with pull-down |
GPIO.OPEN_DRAIN | 0x13 | Open-drain output |
The onInterrupt modes:
| Constant | Value | It fires when… |
|---|---|---|
GPIO.RISING | 1 | It goes up |
GPIO.FALLING | 2 | It goes down |
GPIO.CHANGE | 3 | It changes |
GPIO.ONLOW | 4 | It's low |
GPIO.ONHIGH | 5 | It's high |
And the two levels, which belong to the language and are written without GPIO.:
| Constant | Value |
|---|---|
HIGH | 1 |
LOW | 0 |
Complete example
A button on GPIO 15 turns an LED on GPIO 2 on and off. The interrupt function ignores the button's bounces: a mechanical button gives several edges on each press.
use GPIO
const BUTTON_PIN = 15
const LED_PIN = 2
GPIO.pinmode(LED_PIN, GPIO.OUTPUT)
GPIO.pinmode(BUTTON_PIN, GPIO.PULLUP) // the button connects the pin to GND: pressed is 0
var lit = false
var lastPress = 0
func onPress(pin, level)
if (clock() - lastPress > 200) then // the button's bounces, out
lastPress = clock()
lit = !lit
GPIO.write(LED_PIN, lit)
end
end
GPIO.onInterrupt(BUTTON_PIN, GPIO.FALLING, onPress)
while (true)
pause(50)
end
In the Windows GPIO simulator, connect a Pushbutton to GP15. To see the LED in the simulator, read the warning in Platforms.
Pitfalls and known issues
resolutionandfrequencywithout a pin, on Windows. They giveERR_INVALID_ARG_NUMBER: always pass the pin.- Different default PWM on Windows. On a board,
awritegoes from 0 to 1023 by default; on Windows, from 0 to 255. CallGPIO.resolution(pin, bits)at the start so that it behaves the same on both. - PWM channels in pairs, on a board. Two pins with PWM in the same pair of channels share frequency and resolution.
- The board's buzzer sound also uses a PWM channel.
- Interrupts need the program alive. They don't run inside
pause(), and when the program ends they're removed. - Button bounces. Each press of a mechanical button gives several edges: discard the ones that arrive very close together, as in the example.
All the known issues of JARU, together, are in Known issues.