Módulo GPIO
El módulo GPIO maneja los pines de entrada y salida de la placa: lee y escribe niveles digitales, lee valores analógicos con el conversor (ADC), saca señales PWM y llama a una función cuando un pin cambia (interrupciones). En Windows, los pines son los del simulador GPIO del IDE.
use GPIO

Plataformas
| Windows | ESP32 / ESP32-S3 | ESP32-P4 | ESP32-S31 | |
|---|---|---|---|---|
| Los pines | Los del simulador GPIO, del 0 al 48 | Los del chip | Los del chip | Los del chip |
GPIO.read de un pin en GPIO.OUTPUT | Lo último que se ha escrito | Lo último que se ha escrito | 0 | 0 |
| PWM de serie | 5000 Hz y 8 bits (de 0 a 255) | 2000 Hz y 10 bits (de 0 a 1023) | Igual | Igual |
| Canales PWM | 16 | 16 en el ESP32 y 8 en el ESP32-S3 | 8 | 8 |
| Resolución máxima del PWM | 16 bits | 20 bits en el ESP32 y 14 en el ESP32-S3 | 20 bits | 20 bits |
GPIO.aread | De 0 a 4095: lo que marque la tarjeta del simulador | De 0 a 4095 | De 0 a 4095 en los pines del ADC1, y 0 en los demás | El valor en bruto de su ADC, de 17 bits y sin calibrar |
resolution(bits) y frequency(hz) sin pin | Dan error | Cambian todos los canales | Igual | Igual |
GPIO.ONLOW y GPIO.ONHIGH | Saltan una vez al llegar al nivel | Saltan una y otra vez mientras el pin sigue en ese nivel | Igual | Igual |
| Argumentos que no valen | Excepción (salvo pinmode, write y read, que no los miran) | Un valor de error y, casi siempre, un mensaje en el monitor serie | Igual | Igual |
En Windows, el módulo no toca ningún hardware: lee y escribe en una memoria que comparte con el simulador, esté abierto o no. Sin el simulador, nadie mueve las entradas: un pin de entrada lee 0 (1 con GPIO.PULLUP), y GPIO.aread, 0. Las interrupciones se simulan: la VM mira los pines cada pocos milisegundos.
En esas placas, GPIO.pinmode(pin, GPIO.OUTPUT) deja el pin solo de salida: GPIO.read() de ese pin devuelve siempre 0, y GPIO.onInterrupt() no salta con lo que escribe el programa. En ESP32 y ESP32-S3 sí. Si necesitas saber cómo dejaste un pin de salida, guárdalo en una variable.
Funciones
| Función | Qué hace | Devuelve |
|---|---|---|
GPIO.pinmode(pin, modo) | Dice si un pin es de entrada o de salida, y con qué resistencia | true |
GPIO.write(pin, valor) | Pone un pin de salida a nivel alto o bajo | true |
GPIO.read(pin) | Lee el nivel de un pin | 0 o 1 |
GPIO.aread(pin) | Lee el valor analógico de un pin | Un entero |
GPIO.awrite(pin, valor) | Saca una señal PWM por un pin | true |
GPIO.resolution(bits) / GPIO.resolution(pin, bits) | Cambia la resolución del PWM | true |
GPIO.frequency(hz) / GPIO.frequency(pin, hz) | Cambia la frecuencia del PWM | true |
GPIO.getduty(pin) | Lee el valor del PWM de un pin | Un entero |
GPIO.detach(pin) | Suelta el PWM de un pin | true |
GPIO.onInterrupt(pin, modo, funcion) | Llama a una función cuando cambia un pin | true |
GPIO.offInterrupt(pin) | Deja de vigilar un pin | true |
pin es el número del GPIO, como en el chip: GPIO.read(15) lee el GPIO 15. La VM no comprueba que el pin exista en tu placa.
pinmode(pin, modo)
Prepara el pin pin para el uso que le vas a dar. modo es una de las constantes:
| Modo | Para qué |
|---|---|
GPIO.INPUT | Entrada, sin resistencia. |
GPIO.OUTPUT | Salida. En ESP32 y ESP32-S3, el pin también se puede leer; en el P4 y el S31, no (ver arriba). |
GPIO.PULLUP | Entrada con la resistencia interna a nivel alto: sin nada conectado lee 1. Es lo habitual para un botón que une el pin con GND. |
GPIO.PULLDOWN | Entrada con la resistencia interna a nivel bajo: sin nada conectado lee 0. |
GPIO.OPEN_DRAIN | Salida de drenador abierto: el pin baja a GND o se queda suelto. |
En la placa, un modo que no es ninguno de estos da un valor de error y escribe Invalid pin mode in 'gpio.pinmode()'. en el monitor serie. En Windows se acepta cualquiera.
En el ESP32, los GPIO del 34 al 39 son solo de entrada y no tienen resistencias internas: ahí GPIO.PULLUP y GPIO.PULLDOWN no hacen nada. Lo cuenta Botones, pines y mando.
use GPIO
GPIO.pinmode(2, GPIO.OUTPUT) // el GPIO 2, de salida
GPIO.pinmode(15, GPIO.PULLUP) // el GPIO 15, de entrada con pull-up
write(pin, valor)
Pone el pin de salida pin a nivel alto con valor igual a 1, true o HIGH, y a nivel bajo con 0, false o LOW. Cualquier otro número que no sea 0 cuenta como alto.
use GPIO
GPIO.pinmode(2, GPIO.OUTPUT)
// Un LED que parpadea 5 veces
for (var i = 0; i < 5; i++)
GPIO.write(2, HIGH)
pause(500)
GPIO.write(2, LOW)
pause(500)
end
read(pin)
Lee el nivel del pin pin: 1 (alto) o 0 (bajo).
use GPIO
GPIO.pinmode(15, GPIO.PULLUP)
if (GPIO.read(15) == 0) then
println("Botón pulsado") // con PULLUP, pulsado es 0
else
println("Botón suelto")
end
aread(pin)
Lee el valor del conversor analógico (ADC) en el pin pin: cuanto más alta la tensión, más alto el número. En ESP32, ESP32-S3 y P4 va de 0 a 4095 (12 bits).
- Solo algunos pines tienen ADC: mira el patillaje de tu placa. En el P4, los que no son del ADC1 leen siempre
0. - En el ESP32-S31, el valor es el que da su ADC en bruto, de 17 bits y sin calibrar: no va de 0 a 4095. Mide qué valores te da tu circuito.
use GPIO
var valor = GPIO.aread(34)
println("ADC: ", valor, " Voltios: ", valor * 3.3 / 4095)
La cuenta de los voltios supone que 4095 son 3,3 V: tómala como una aproximación.
awrite(pin, valor)
Saca por el pin pin una señal PWM: una onda que pasa muy deprisa de alto a bajo, y que en un LED se ve como un brillo intermedio. valor es el tiempo que pasa en alto (el duty), de 0 al máximo de la resolución: de serie, de 0 a 1023 en la placa y de 0 a 255 en Windows.
La primera vez que usas un pin con PWM, la VM le asigna uno de sus canales PWM. No hace falta pinmode.
En la placa, un valor por encima del máximo se recorta al máximo, y uno negativo cuenta como 0; en Windows se recortan entre 0 y 65535. Si no quedan canales libres (cuántos hay, en Plataformas), lanza ERR_OUT_OF_MEMORY (No free LEDC channels.); detach libera el de un pin.
use GPIO
GPIO.resolution(2, 8) // 8 bits en el GPIO 2: de 0 a 255, como en Windows
GPIO.frequency(2, 5000)
// Un LED que respira 3 veces
for (var vez = 0; vez < 3; vez++)
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)
Cambia la resolución del PWM del pin pin a bits bits: con 8, awrite va de 0 a 255; con 10, de 0 a 1023; con 12, de 0 a 4095. Sin pin, resolution(bits) cambia todos los canales; en Windows esa forma da ERR_INVALID_ARG_NUMBER, así que pon siempre el pin.
En Windows, bits se recorta entre 1 y 16. En la placa no se comprueba: más allá del máximo del chip (ver Plataformas), el canal no cambia y no hay aviso.
use GPIO
GPIO.resolution(2, 12) // el GPIO 2, de 0 a 4095
frequency(pin, hz)
Cambia la frecuencia del PWM del pin pin a hz hercios. Sin pin, frequency(hz) cambia todos los canales; como resolution, esa forma da error en Windows.
En la placa, los canales PWM van de dos en dos: el primero y el segundo pin que usas con PWM comparten frecuencia y resolución, y lo mismo el tercero y el cuarto, etc. Cambiar la de uno cambia la del otro. Si necesitas dos frecuencias distintas (un servo a 50 Hz y un LED a 5000 Hz), los dos pines tienen que caer en parejas distintas: por ejemplo, el primero y el tercero que usan PWM.
use GPIO
GPIO.frequency(2, 1000) // 1 kHz en el GPIO 2
getduty(pin)
Devuelve el valor que tiene el PWM del pin pin: lo último que le diste con awrite.
Un pin sin PWM devuelve 0.
use GPIO
GPIO.awrite(2, 512)
println(GPIO.getduty(2)) // 512
detach(pin)
Desconecta el pin pin de su canal PWM: el pin deja de sacar la señal.
El canal queda libre para otro pin, y un awrite posterior en el mismo pin le vuelve a dar uno.
use GPIO
GPIO.awrite(2, 512)
// … usar el PWM …
GPIO.detach(2)
onInterrupt(pin, modo, funcion)
Llama a funcion cada vez que el pin pin hace lo que dice modo:
| Modo | Salta cuando… |
|---|---|
GPIO.RISING | El pin pasa de bajo a alto. |
GPIO.FALLING | El pin pasa de alto a bajo. Es el de un botón con GPIO.PULLUP. |
GPIO.CHANGE | El pin cambia, en cualquier sentido. |
GPIO.ONLOW | El pin está a nivel bajo. En la placa salta una y otra vez mientras siga así; en Windows, una vez al llegar. |
GPIO.ONHIGH | El pin está a nivel alto, como ONLOW. |
funcion puede ser una función (alPulsar), un método de un objeto (boton.alPulsar) o el nombre de una función global en una cadena ("alPulsar"). Recibe dos parámetros: el número del pin y su nivel (0 o 1) en el momento del cambio. Si tiene otro número de parámetros, los avisos se descartan sin decir nada.
onInterruptno prepara el pin: llama antes apinmode.- Puede haber 16 pines con interrupción a la vez. Otro
onInterrupten un pin que ya la tiene cambia su función y su modo. - Para un botón, mejor
FALLINGoRISINGqueONLOWuONHIGH: un pin que se queda en un nivel llenaría la cola de avisos de la VM.
Si algo no vale, en Windows lanza una excepción: ERR_TYPE_MISMATCH si un argumento no es del tipo que toca, ERR_INVALID_VALUE si el modo no es de la tabla, y ERR_OUT_OF_MEMORY si ya hay 16. En la placa devuelve un valor de error: ERR_TYPE_MISMATCH, ERR_OUT_OF_MEMORY, o ERR_INVALID_OPERATION si el pin no admite interrupciones; el modo no se comprueba.
use GPIO
var pulsaciones = 0
func alPulsar(pin, nivel)
pulsaciones = pulsaciones + 1
println("Botón del pin ", pin, ": ", pulsaciones)
end
GPIO.pinmode(15, GPIO.PULLUP)
GPIO.onInterrupt(15, GPIO.FALLING, alPulsar)
// El programa sigue vivo para recibir las interrupciones
while (true)
pause(100)
end
Con un método, la función corre con su this, y la interrupción mantiene viva la instancia mientras esté registrada:
use GPIO
class Boton
def init(pin)
this.pin = pin
this.pulsaciones = 0
GPIO.pinmode(pin, GPIO.PULLUP)
GPIO.onInterrupt(pin, GPIO.FALLING, this.alPulsar)
end
def alPulsar(pin, nivel)
this.pulsaciones = this.pulsaciones + 1
end
end
var boton = Boton(15)
El chip detecta el cambio al instante, pero tu función la ejecuta la VM dentro del programa, como los callbacks de Timer: de uno en uno, en los puntos seguros (al dar la vuelta un bucle y al llamar a una función o volver de ella) y nunca dentro de pause(). Por eso el programa tiene que seguir vivo, con un bucle de pausas cortas. Al terminar el programa, las interrupciones se quitan.
offInterrupt(pin)
Deja de vigilar el pin pin y devuelve true, también si no tenía interrupción. En la placa, un pin que no admite interrupciones devuelve un valor de error (ERR_INVALID_OPERATION).
use GPIO
func alPulsar(pin, nivel)
println("Pulsado")
end
GPIO.pinmode(15, GPIO.PULLUP)
GPIO.onInterrupt(15, GPIO.FALLING, alPulsar)
// … más tarde
GPIO.offInterrupt(15)
Constantes
Los modos de pinmode:
| Constante | Valor | Modo |
|---|---|---|
GPIO.INPUT | 0x01 | Entrada |
GPIO.OUTPUT | 0x03 | Salida |
GPIO.PULLUP | 0x05 | Entrada con pull-up |
GPIO.PULLDOWN | 0x09 | Entrada con pull-down |
GPIO.OPEN_DRAIN | 0x13 | Salida de drenador abierto |
Los modos de onInterrupt:
| Constante | Valor | Salta cuando… |
|---|---|---|
GPIO.RISING | 1 | Sube |
GPIO.FALLING | 2 | Baja |
GPIO.CHANGE | 3 | Cambia |
GPIO.ONLOW | 4 | Está a nivel bajo |
GPIO.ONHIGH | 5 | Está a nivel alto |
Y los dos niveles, que son del lenguaje y se escriben sin GPIO.:
| Constante | Valor |
|---|---|
HIGH | 1 |
LOW | 0 |
Ejemplo completo
Un botón en el GPIO 15 enciende y apaga un LED en el GPIO 2. La función de la interrupción no hace caso de los rebotes del botón: un botón mecánico da varios flancos en cada pulsación.
use GPIO
const PIN_BOTON = 15
const PIN_LED = 2
GPIO.pinmode(PIN_LED, GPIO.OUTPUT)
GPIO.pinmode(PIN_BOTON, GPIO.PULLUP) // el botón une el pin con GND: pulsado es 0
var encendido = false
var ultimaPulsacion = 0
func alPulsar(pin, nivel)
if (clock() - ultimaPulsacion > 200) then // los rebotes del botón, fuera
ultimaPulsacion = clock()
encendido = !encendido
GPIO.write(PIN_LED, encendido)
end
end
GPIO.onInterrupt(PIN_BOTON, GPIO.FALLING, alPulsar)
while (true)
pause(50)
end
En el simulador GPIO de Windows, conecta un Pushbutton al GP15. Para ver el LED en el simulador, mira el aviso de Plataformas.
Trampas y problemas conocidos
resolutionyfrequencysin pin, en Windows. DanERR_INVALID_ARG_NUMBER: pasa siempre el pin.- PWM de serie distinto en Windows. En la placa,
awriteva de 0 a 1023 de serie; en Windows, de 0 a 255. PonGPIO.resolution(pin, bits)al principio para que vaya igual en los dos. - Canales PWM a pares, en la placa. Dos pines con PWM del mismo par de canales comparten frecuencia y resolución.
- El sonido por zumbador de la placa usa también un canal PWM.
- Las interrupciones necesitan el programa vivo. No corren dentro de
pause(), y al acabar el programa se quitan. - Los rebotes de los botones. Cada pulsación de un botón mecánico da varios flancos: descarta los que llegan muy seguidos, como en el ejemplo.
Todos los problemas conocidos de JARU, juntos, están en Problemas conocidos.