6. 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 as correntes não deverão superar os 16mA por pino (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 > File
  • File name: GPIO.py
  • Finish

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 v31 @ 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 v31 @ 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

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 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()

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. Um aparente bug na biblioteca pigpio parece também impedir a deteção do primeiro toque no botão.

Uma solução que contorna estes problemas seria a seguinte:

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
lastTick = 0

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

    if tick - lastTick > 500000:
        if level == 0 or lastTick == 0:
            lastTick = tick
            print(counter)
            counter = counter + 1

Bi = 24
pi.set_mode(Bi, pigpio.INPUT) 
pi.callback(Bi, pigpio.EITHER_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()

PWM

Vamos agora supor que no final do programa se pretende gerar para o LED Lg 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

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

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)

Lg = 21
pi.set_mode(Lg, pigpio.OUTPUT)
pi.set_PWM_frequency(Lg, 1000)
for duty in range(0, 101, 10):
    pi.set_PWM_dutycycle(Lg, int(duty*255/100))
    print("duty-cycle = {:3d}%".format(duty))
    time.sleep(1)
pi.write(Lg, 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 precessã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 em paralelo com o processo PWM já descrito, 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

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

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)

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)    

Lg = 21
pi.set_mode(Lg, pigpio.OUTPUT)
pi.set_PWM_frequency(Lg, 1000)
for duty in range(0, 101, 10):
    pi.set_PWM_dutycycle(Lg, duty)
    print("duty-cycle = {:3d}%".format(duty))
    time.sleep(1)
pi.write(Lg, 0)

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

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

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)
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

sample

Deixe um comentário

O seu endereço de email não será publicado. Campos obrigatórios marcados com *