GPIO

Interface GPIO

As placas Raspberry Pi possuem uma interface GPIO (General Purpose Input/Output) para ligação a componentes eletrónicos externos.

Esta interface é composta por 40 pinos tal como a seguinte imagem mostra:

RPi-GPIO

Entre estes pinos é possível encontrar:

  • pinos de alimentação: GND, 5V e 3.3V
  • pinos de canais de comunicação: UART, I2C e SPI
  • pinos genéricos de entrada/saída

Nesta página iremos abordar precisamente o uso destes últimos.

Cuidados elétricos a ter

Os pinos definidos como saídas apresentam tensões de cerca de 0V no caso do nível lógico 0, e cerca de 3,3V para o nível lógico 1. Este valor de tensão, mesmo quando ligado a um dispositivo alimentado com 5V, continua em geral a ser visto como um valor lógico 1.

Por seu lado a corrente em cada pino não deverá superar os 16mA (50mA no cômputo geral de todos os pinos). Por exemplo, ao ligarmos um LED (cuja tensão de condução é de cerca de 2V) com uma resistência de 1kΩ, a corrente envolvida seria de 1,3mA que estando bem abaixo do limite citado continua a acender suficientemente o LED.

Caso se pretendam correntes maiores (por exemplo para controlar um relé) teremos que recorrer a circuito baseado num transístor.

No caso das entradas é preciso ter o cuidado de não se ultrapassar a tensão de 3,3V. Caso o sinal a ligar a uma entrada está na gama 0..5V, será necessário usar um adaptador de níveis ou um simples divisor de tensão (entre uma resistência de 1,8kΩ e outra de 3,3kΩ).

Biblioteca pigpio

Para se usar os pinos presentes na interface GPIO das placas Raspberry Pi iremos usar a biblioteca pigpio. Essa bilbioteca possui um módulo Python que solicita todas as funcionalidades de controlo desse interface, a um programa (pigpiod) que corre em background na placa. Os recursos requeridos por esse programa (daemon) podem ser visto através do comando:
top

Existem soluções alternativas como por exemplo o package RPi.GPIO. Contudo a biblioteca pigpio possui características interessantes, nomeadamente a possibilidade de poder ser usada de forma remota. Ou seja, é possível controlar o interface da placa mesmo estando o programa Python a correr num computador remoto, bastando para tal que se estabeleça uma ligação de rede à placa. No nosso caso, tal como foi explicado aqui, a ligação faz-se pelo IP 10.0.0.1XX.

Para isso será necessário instalar a biblioteca pigpio também no computador. No caso do Windows isso faz-se abrindo uma janela PowerShell e executando o comando:
pip install http://abyz.co.uk/rpi/pigpio/pigpio.zip

Isto vai adicionar a biblioteca na pasta C:\Python27\Lib\site-packages.

Projeto GPIO

Vamos agora criar um projeto Python no Eclipse designado GPIO e que servirá para demonstrar as diversas funcionalidades associadas aos pinos de entrada/saída presentes na interface GPIO.

Com a perspetiva PyDev ativa, fazer:

  • File > New > PyDev Project
  • Project name: GPIO
  • Finish
  • File > New > PyDev Module
  • Name: GPIO
  • Finish
  • Template: <Empty>
  • OK

Editar o ficheiro GPIO.py com o seguinte código, não esquecendo de editar o IP para o da placa de cada grupo:

import platform
import pigpio

if platform.uname()[4][0:3] == "arm":
    pi = pigpio.pi()
else:
    pi = pigpio.pi("10.0.0.1XX") # altere o IP para o da sua placa

print("pigpio v{} @ {}".format(pi.get_pigpio_version(), platform.uname()[0]))

pi.stop()

Para executar o programa, fazer:

  • Run > Run > Python Run

A resposta comprova que o programa correu no PC Windows:
pigpio v61 @ Windows

De seguida faça copy/paste do ficheiro GPIO.py para a pasta edm da placa Raspberry Pi (usando a perspetiva Remote System Explorer).

Estabeleça uma ligação SSH com a placa e execute o comando:
python ~/edm/GPIO.py

A resposta mostra que desta vez o programa correu no sistema operativo Linux da placa:
pigpio v61 @ Linux

Entradas/saídas

As várias funcionalidades associadas ao uso das portas de entrada/saída serão seguidamente demonstradas através do seguinte circuito, composto por dois botões (Bp e Bi) e três LEDs (Lr: vermelho, Ly: amarelo, e Lg: verde):

RPi-Semaforo

Como se pode confirmar os pinos GPIO associados aos LEDs e botões são então os seguintes:

Elemento Variável GPIO
1º Botão Bp 23
2º Botão Bi 24
LED vermelho Lr 16
LED amarelo Ly 20
LED verde Lg 21

Saídas

Vamos começar por ligar o LED Lr durante 3 segundo:

import platform
import pigpio
import time

if platform.uname()[4][0:3] == "arm":
    pi = pigpio.pi()
else:
    pi = pigpio.pi("10.0.0.1XX") # altere o IP para o da sua placa

Lr = 16
pi.set_mode(Lr, pigpio.OUTPUT)
pi.write(Lr, 1)
time.sleep(3)
pi.write(Lr, 0)

pi.stop()

O impulso assim gerado, quando de duração até 100µs, pode ser obtido de forma mais simples e precisa recorrendo à função gpio_trigger(). No exemplo seguinte será igualmente gerado um impulso de 50µs no pino ligado ao LED Lr:

import platform
import pigpio
import time

if platform.uname()[4][0:3] == "arm":
    pi = pigpio.pi()
else:
    pi = pigpio.pi("10.0.0.1XX") # altere o IP para o da sua placa

Lr = 16
pi.set_mode(Lr, pigpio.OUTPUT)
pi.gpio_trigger(Lr, 50, 1)
pi.write(Lr, 1)
time.sleep(3)
pi.write(Lr, 0)

pi.stop()

Desta forma evita-se a necessidade de implementar a sequência ligar/delay/desligar (ou vice-versa). Observe no osciloscópio o sinal assim produzido para o LED Lr.

Screen Capture

Entradas

Vamos agora só ligar o LED Lr após se premir o botão Bp (ativo ao nível baixo):

import platform
import pigpio
import time

if platform.uname()[4][0:3] == "arm":
    pi = pigpio.pi()
else:
    pi = pigpio.pi("10.0.0.1XX") # altere o IP para o da sua placa

Bp = 23
pi.set_mode(Bp, pigpio.INPUT)
while pi.read(Bp):
    time.sleep(.05)

Lr = 16
pi.set_mode(Lr, pigpio.OUTPUT)
pi.write(Lr, 1)
time.sleep(3)
pi.write(Lr, 0)

pi.stop()

De notar a importância de dentro do ciclo while colocar um sleep (em lugar dum simples pass) para que assim o microprocessador não fique demasiado sobrecarregado. Comprove esta situação recorrendo ao comando top depois de executar o programa na placa.

Uma forma alternativa de aguardar pela ocorrência duma transição é recorrer à função wait_for_edge():

import platform
import pigpio
import time

if platform.uname()[4][0:3] == "arm":
    pi = pigpio.pi()
else:
    pi = pigpio.pi("10.0.0.1XX") # altere o IP para o da sua placa

Bp = 23
pi.set_mode(Bp, pigpio.INPUT)
while not pi.wait_for_edge(Bp, pigpio.FALLING_EDGE, 30):
    pass

Lr = 16
pi.set_mode(Lr, pigpio.OUTPUT)
pi.write(Lr, 1)
time.sleep(3)
pi.write(Lr, 0)

pi.stop()

De notar a existência dum timeout (30 segundos neste caso) ao fim do qual, caso não ocorra a transição pretendida, a função retorna Falso.

Pull-Ups/Downs

No circuito apresentado só a presença da resistência de pull-up no botão Bp permite obter um nível lógico definido quando ele não está premido. É contudo possível dispensar essa resistência caso se indique ao microprocessador para a colocar internamente no pino correspondente:

import platform
import pigpio
import time

if platform.uname()[4][0:3] == "arm":
    pi = pigpio.pi()
else:
    pi = pigpio.pi("10.0.0.1XX") # altere o IP para o da sua placa

Bp = 23
pi.set_mode(Bp, pigpio.INPUT)
pi.set_pull_up_down(Bp, pigpio.PUD_UP)
while not pi.wait_for_edge(Bp, pigpio.FALLING_EDGE, 30):
    pass

Lr = 16
pi.set_mode(Lr, pigpio.OUTPUT)
pi.write(Lr, 1)
time.sleep(3)
pi.write(Lr, 0)

pi.stop()

Dependendo da forma como o botão está ligado, em vez duma resistência de pull-up (PUD_UP), poderá ser necessário colocar uma de pull-down (PUD_DOWN), ou não colocar nenhuma (PUD_OFF que é a situação que ocorre à partida).

Callbacks

Às vezes pode ser contudo preferível não bloquear a execução dum programa pela espera da alteração do estado duma entrada, mas sim definir uma função de callback, que será chamada sempre que essa entrada apresentar uma determinada transição:

  • ascendente (RISING_EDGE)
  • descendente (FALLING_EDGE)
  • ambas (EITHER_EDGE)

A versão seguinte do nosso programa de demonstração irá incrementar um contador sempre que se carregar no botão Bi:

import platform
import pigpio
import time

if platform.uname()[4][0:3] == "arm":
    pi = pigpio.pi()
else:
    pi = pigpio.pi("10.0.0.1XX") # altere o IP para o da sua placa

counter = 1

def onBi(gpio, level, tick):
    global counter

    print(gpio, level, tick)
    print(counter)
    counter = counter + 1

Bi = 24
pi.set_mode(Bi, pigpio.INPUT) 
pi.callback(Bi, pigpio.FALLING_EDGE, onBi)

Bp = 23
pi.set_mode(Bp, pigpio.INPUT)
while pi.read(Bp):
    time.sleep(.05)

Lr = 16
pi.set_mode(Lr, pigpio.OUTPUT)
pi.write(Lr, 1)
time.sleep(3)
pi.write(Lr, 0)

pi.stop()

Debouncing

Este programa padece contudo dum problema associado ao fenómeno de bouncing do botão. Com efeito ao carregar/largar o botão podem ocorrer diversas transições como a seguinte imagem ilustra:

debounce_bouncing

Uma forma de resolver este problema passa por só dar conta das transições que ocorram pelo menos 50ms após a anterior:

import platform
import pigpio
import time

if platform.uname()[4][0:3] == "arm":
    pi = pigpio.pi()
else:
    pi = pigpio.pi("10.0.0.1XX") # altere o IP para o da sua placa

counter = 1

def onBi(gpio, level, tick):
    global counter

    print(gpio, level, tick)     
    print(counter)
    counter = counter + 1

Bi = 24
pi.set_mode(Bi, pigpio.INPUT) 
pi.set_glitch_filter(Bi, 50000)
pi.callback(Bi, pigpio.FALLING_EDGE, onBi)

Bp = 23
pi.set_mode(Bp, pigpio.INPUT)
while pi.read(Bp):
    time.sleep(.05)

Lr = 16
pi.set_mode(Lr, pigpio.OUTPUT)
pi.write(Lr, 1)
time.sleep(3)
pi.write(Lr, 0)

pi.stop()

Uma forma alternativa para fazer o debouncing será colocar um pequeno condensador (100nF) aos terminais do botão.

PWM

Vamos agora supor que se pretende gerar para o LED Lr um sinal com uma frequência de 1000Hz, que a cada segundo vai alterando o seu duty-cycle* para: 0%, 10%, 20%, etc. até 100%.
* Percentagem do período em que o sinal vale 1.

Embora se pudesse pensar que tal poderia ser feito com o ligar e desligar do LED em instantes temporais controlados pela função sleep(), tal não seria viável dadas as temporizações demasiado apertadas que a frequência em causa implica. Em alternativa iremos sim usar a funcionalidade de geração dum sinal PWM:

import platform
import pigpio
import time

if platform.uname()[4][0:3] == "arm":
    pi = pigpio.pi()
else:
    pi = pigpio.pi("10.0.0.1XX") # altere o IP para o da sua placa

Bp = 23
pi.set_mode(Bp, pigpio.INPUT)
while pi.read(Bp):
    time.sleep(.05)

Lr = 16
pi.set_mode(Lr, pigpio.OUTPUT)
pi.set_PWM_frequency(Lr, 1000)
for duty in range(0, 101, 10):
    pi.set_PWM_dutycycle(Lr, int(duty*255/100))
    print("duty-cycle = {:3d}%".format(duty))
    time.sleep(1)
pi.write(Lr, 0)

pi.stop()

Sugere-se a visualização no osciloscópio do sinal gerado. Como se pode constatar, apesar dele continuar a ser um sinal digital, do ponto de vista de perceção da intensidade do LED pelo olho humano, dá a sensação de que se trata dum sinal analógico de amplitude igual ao valor médio do sinal.

A geração de sinais periódicos com uma determinada frequência pode ser também usada para alimentar um altifalante produzindo-se assim a nota musical correspondente.

Waveforms

Caso o sinal a gerar tenha características menos regulares, será necessário recorrer a uma outra funcionalidade fornecida pela biblioteca pigpio: a geração de waveforms. Para tal serão definidos os diferentes impulsos que o compõem indicando os instantes em que ocorrem.

A seguinte versão do programa gera durante 5 segundos um sinal para o LED Lr que simula um “bater de coração”.

import platform
import pigpio
import time

if platform.uname()[4][0:3] == "arm":
    pi = pigpio.pi()
else:
    pi = pigpio.pi("10.0.0.1XX") # altere o IP para o da sua placa

Bp = 23
pi.set_mode(Bp, pigpio.INPUT)
while pi.read(Bp):
    time.sleep(.05)

Lr = 16
pi.set_mode(Lr, pigpio.OUTPUT)
heart = []
heart.append(pigpio.pulse(1<<Lr, 0, 100000))
heart.append(pigpio.pulse(0, 1<<Lr, 100000))
heart.append(pigpio.pulse(1<<Lr, 0, 100000))
heart.append(pigpio.pulse(0, 1<<Lr, 700000))
pi.wave_clear()
pi.wave_add_generic(heart)
heartWave = pi.wave_create()
pi.wave_send_repeat(heartWave)    

time.sleep(5)

pi.wave_tx_stop()
pi.wave_clear()            
pi.write(Lr, 0)

pi.stop()

Neste exemplo a forma de onda (waveform) é gerada de forma cíclica (wave_send_repeat) mas podia ser gerada de forma pontual (wave_send_once).

Temporizações

Por fim imagine que pretende incluir a possibilidade de interromper o período de 3 segundos durante o qual o LED Lr foi ligado, caso se prima o botão Bi.

A linha de código até aqui usada time.sleep(3) impediria que durante esse período fosse lido o estado do botão pois o processo estaria “adormecido”. Uma alternativa seria a implementação dum ciclo enquanto não tivessem decorrido os 3 segundos (algo que se pode controlar pelos microsegundos retornados pela função get_current_tick). Para que o processador não fique contudo sobrecarregado com a execução contínua do while será importante a introdução dum pequeno sleep em cada ciclo. A duração deste sleep será um compromisso entre “tempo de resposta ao botão” e “sobrecarga do processador”. No seguinte exemplo o valor usado foi de 50ms:

import platform
import pigpio
import time

if platform.uname()[4][0:3] == "arm":
    pi = pigpio.pi()
else:
    pi = pigpio.pi("10.0.0.1XX") # altere o IP para o da sua placa 

Bp = 23
pi.set_mode(Bp, pigpio.INPUT)
while pi.read(Bp):
    time.sleep(.05)

Bi = 24
pi.set_mode(Bi, pigpio.INPUT)

Lr = 16
pi.set_mode(Lr, pigpio.OUTPUT)
pi.write(Lr, 1)
start = pi.get_current_tick()
while pi.get_current_tick() - start < 3000000: # 3 segundos
    if pi.read(Bi) == 0: break
    time.sleep(.05)
pi.write(Lr, 0)

pi.stop()

Projetos propostos

Seguidamente serão propostos dois projetos a desenvolver pelos estudantes com base nos conhecimentos adquiridos ao longo desta página.

Semáforo

Pretende-se desenvolver um programa que controle um semáforo para automóveis situado junto a uma passadeira para peões. Note que os tempos referidos de seguida embora irrealistas, tornam o processo de simulação mais rápido!

O ciclo normal de funcionamento será de 9 segundos em verde (LED Lg), 1 segundo em amarelo (Ly) e 5 segundos em vermelho (Lr).

Caso seja premido o botão dos peões (Bp) o tempo de verde deverá ser imediatamente terminado desde que já tenham decorrido 4 segundos. Se esse tempo ainda não decorreu a passagem para amarelo deverá ocorrer só após esses 4 segundos.

O botão Bi servirá para que na central de controlo do tráfego se inicie/termine o modo “intermitente” no semáforo. Neste modo o semáforo deverá ter o amarelo a piscar ciclicamente (1 segundo ligado, 1 segundo desligado).

Display

Neste projeto pretende-se ligar um display de 7 segmentos à placa Raspberry Pi. Embora existam pinos disponíveis para ligar individualmente os vários segmentos, neste caso pretende-se recorrer a um registo de deslocamento (74HC595) o que permite o uso de apenas 3 pinos (Dat, Clk e Set).

display_pinout

Como o seguinte circuito mostra, iremos usar apenas uma resistência limitadora de corrente no cátodo comum do display em lugar duma resistência por ánodo. Desta forma a montagem será mais simples embora se possam registar brilhos diferentes nos segmentos.

display

Desenvolva uma função Python capaz de apresentar no display o número que lhe é passado como argumento:

import platform
import pigpio
import time

if platform.uname()[4][0:3] == "arm":
    pi = pigpio.pi()
else:
    pi = pigpio.pi("10.0.0.1XX") # altere o IP para o da sua placa

Dat = 18
Clk = 14
Set = 15

def display(num):
    print(num)
    # adicione aqui o seu codigo

for n in range(0, 10):
    display(n)
    time.sleep(1)

pi.stop()

Recorra a esta função para implementar no projeto semáforo uma contagem decrescente durante o período de verde para os peões (durante os 5 segundos em que o LED Lr está ativo).

semaforo