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