Saltar al contenido principal

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

Captura del módulo GPIO de JARU para configurar pines digitales, entradas analógicas, PWM e interrupciones

Plataformas​

WindowsESP32 / ESP32-S3ESP32-P4ESP32-S31
Los pinesLos del simulador GPIO, del 0 al 48Los del chipLos del chipLos del chip
GPIO.read de un pin en GPIO.OUTPUTLo último que se ha escritoLo último que se ha escrito00
PWM de serie5000 Hz y 8 bits (de 0 a 255)2000 Hz y 10 bits (de 0 a 1023)IgualIgual
Canales PWM1616 en el ESP32 y 8 en el ESP32-S388
Resolución máxima del PWM16 bits20 bits en el ESP32 y 14 en el ESP32-S320 bits20 bits
GPIO.areadDe 0 a 4095: lo que marque la tarjeta del simuladorDe 0 a 4095De 0 a 4095 en los pines del ADC1, y 0 en los demásEl valor en bruto de su ADC, de 17 bits y sin calibrar
resolution(bits) y frequency(hz) sin pinDan errorCambian todos los canalesIgualIgual
GPIO.ONLOW y GPIO.ONHIGHSaltan una vez al llegar al nivelSaltan una y otra vez mientras el pin sigue en ese nivelIgualIgual
Argumentos que no valenExcepción (salvo pinmode, write y read, que no los miran)Un valor de error y, casi siempre, un mensaje en el monitor serieIgualIgual

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 el ESP32-P4 y el ESP32-S31, un pin de salida no se puede leer

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ónQué haceDevuelve
GPIO.pinmode(pin, modo)Dice si un pin es de entrada o de salida, y con qué resistenciatrue
GPIO.write(pin, valor)Pone un pin de salida a nivel alto o bajotrue
GPIO.read(pin)Lee el nivel de un pin0 o 1
GPIO.aread(pin)Lee el valor analógico de un pinUn entero
GPIO.awrite(pin, valor)Saca una señal PWM por un pintrue
GPIO.resolution(bits) / GPIO.resolution(pin, bits)Cambia la resolución del PWMtrue
GPIO.frequency(hz) / GPIO.frequency(pin, hz)Cambia la frecuencia del PWMtrue
GPIO.getduty(pin)Lee el valor del PWM de un pinUn entero
GPIO.detach(pin)Suelta el PWM de un pintrue
GPIO.onInterrupt(pin, modo, funcion)Llama a una función cuando cambia un pintrue
GPIO.offInterrupt(pin)Deja de vigilar un pintrue

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:

ModoPara qué
GPIO.INPUTEntrada, sin resistencia.
GPIO.OUTPUTSalida. En ESP32 y ESP32-S3, el pin también se puede leer; en el P4 y el S31, no (ver arriba).
GPIO.PULLUPEntrada 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.PULLDOWNEntrada con la resistencia interna a nivel bajo: sin nada conectado lee 0.
GPIO.OPEN_DRAINSalida 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:

ModoSalta cuando…
GPIO.RISINGEl pin pasa de bajo a alto.
GPIO.FALLINGEl pin pasa de alto a bajo. Es el de un botón con GPIO.PULLUP.
GPIO.CHANGEEl pin cambia, en cualquier sentido.
GPIO.ONLOWEl pin está a nivel bajo. En la placa salta una y otra vez mientras siga así; en Windows, una vez al llegar.
GPIO.ONHIGHEl 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.

  • onInterrupt no prepara el pin: llama antes a pinmode.
  • Puede haber 16 pines con interrupción a la vez. Otro onInterrupt en un pin que ya la tiene cambia su función y su modo.
  • Para un botón, mejor FALLING o RISING que ONLOW u ONHIGH: 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)
Cuándo corre la función

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:

ConstanteValorModo
GPIO.INPUT0x01Entrada
GPIO.OUTPUT0x03Salida
GPIO.PULLUP0x05Entrada con pull-up
GPIO.PULLDOWN0x09Entrada con pull-down
GPIO.OPEN_DRAIN0x13Salida de drenador abierto

Los modos de onInterrupt:

ConstanteValorSalta cuando…
GPIO.RISING1Sube
GPIO.FALLING2Baja
GPIO.CHANGE3Cambia
GPIO.ONLOW4Está a nivel bajo
GPIO.ONHIGH5Está a nivel alto

Y los dos niveles, que son del lenguaje y se escriben sin GPIO.:

ConstanteValor
HIGH1
LOW0

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​

  • resolution y frequency sin pin, en Windows. Dan ERR_INVALID_ARG_NUMBER: pasa siempre el pin.
  • PWM de serie distinto en Windows. En la placa, awrite va de 0 a 1023 de serie; en Windows, de 0 a 255. Pon GPIO.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.