Site menu Piscando um LED no Raspberry
e-mail icon
Site menu

Piscando um LED no Raspberry

e-mail icon

2012.09.29

Este artigo expressa a opinião do autor na época da sua redação. Não há qualquer garantia de exatidão, ineditismo ou atualidade nos conteúdos. É proibida a cópia na íntegra. A citação de trechos é permitida mediante referência ao autor e este sítio de origem.

Figura 1: Raspberry com LED ligado em porta GPIO

Eletrônica é um hobby que eu lamento muito não praticar. Acho interessantíssimo, mas já tenho hobbies demais, além do trabalho e da família, todos disputando por recursos e principalmente por tempo e espaço físico.

Assim, eu evito até pensar em eletrônica, para não cair em tentação, mas com uma inveja amuada dos colegas que mexem com isso.

Bem, aí chegou meu Raspberry Pi, comprado para que fosse um servidor de rede... ele vem com aqueles pinos GPIO na placa... a tentação ficou muito forte. Mas ainda resisti por alguns dias. Não adianta fazer um LED piscar sem propósito, não ganho nada com isso.

Mas eis que surgiu um problema: o Raspberry trabalha "sem console" aqui. Para dar shutdown limpo, só entrando via SSH. Ou simplesmente puxar da tomada, arriscando bagunçar o sistema de arquivos. Mexer no sistema para ele funcionar "read only", muuuito chato.

Dentro deste problema, a oportunidade (ou o pretexto?) para fazer algo útil com os pinos GPIO :) Uma chave de desligamento!

Usei os circuitos descritos aqui e aqui. Um descritivo geral dos pinos GPIO pode ser encontrado aqui. Os circuitos são bem básicos, mas é sempre agradável poder "colar" de algum lugar para evitar algum erro crasso (que poderia custar um Raspberry frito).

Os pinos GPIO do Raspberry vêm direto do processador, sem qualquer proteção. É como ligar coisas na porta paralela do PC, porém ainda mais perigoso, porque a tensão é 3.3V (se ligar em 5V, queima) e a função de cada pino é programável por software. Um circuito ruim pode funcionar agora, e queimar a placa no próximo reboot.

Há placas de desenvolvimento, como a GertBoard, mais apropriadas para quem quer mexer com eletrônica a sério. Mas, para piscar um LED e adicionar uma chave, achei que valia o risco tentar "a seco".

O esquema

A ideia é desligar o computador quando uma chave é ligada. Um programinha em Python monitora o pino GPIO conectado à chave, e executa o shutdown quando a chave vai para ON.

O LED pisca em ritmos diferentes conforme o estado do programa monitor: devagar no estado normal, bem rápido quando o shutdown é invocado.

Se a chave já estava em ON quando o programa inicia, obviamente ele não desliga o computador, mas o LED pisca um pouco mais rápido para sinalizar esta condição.

Meu circuito ficou um espaguete de fios, mas comprei alguns exemplares de uma plaquinha que encaixa em cima do Raspberry, para refazer tudo de forma mais limpa.

O programa pode ser obtido aqui. Segue uma discussão de cada parte do mesmo.

#!/usr/bin/env python

import time
import RPi.GPIO as gpio
import os

LED_PIN = 15
SWITCH_PIN = 16

gpio.setwarnings(False)
gpio.setmode(gpio.BOARD)
gpio.setup(LED_PIN, gpio.OUT)
gpio.setup(SWITCH_PIN, gpio.IN)

O uso de cada pino GPIO, inclusive a "direção" (leitura ou escrita) é programável por software, conforme o script faz acima. Mesmo um circuito que vá usar um pino como OUT, tem de prever a situação do pino estar IN por erro de software, e não deixar entrar muita corrente.

O Python-RPI usa a numeração dos pinos na própria placa, seguindo a convenção do conector. Há outras duas convenções: do esquema do Raspberry, e a do processador Broadcom. Assim, o pino 15 pode também ser chamado de GPIO 22 (Broadcom) ou GPIO 3 (projeto da placa). A convenção do Python é a mais burra, porém acaba sendo a mais sábia, porque basta pegar a placa e contar.

class TimeoutManager(object):
	def __init__(self):
		self.map = {}

	def process_timeouts(self):
		for k, v in self.map.items():
			if v["when"] < time.time():
				self.remove(k)
				v["cb"]()

		to = time.time() + 60
		for k, v in self.map.items():
			if v["when"] < to:
				to = v["when"]

		dt = to - time.time()
		if dt >= 0:
			time.sleep(dt)

	def add(self, name, to, cb):
		self.map[name] = {"when": time.time() + to, "cb": cb}

	def remove(self, name):
		try:
			del self.map[name]
		except KeyError:
			pass

A classe acima é "boilerplate"; faz papel de um event loop simplificado. Uma aplicação mais séria poderia usar um event loop de verdade como GLib.

class LED(object):
	def __init__(self, tom):
		self.tom = tom
		self.io(True)

	def blink(self, hz, duty):
		self.hz = hz
		self.duty = duty
		self.tom.remove("led")
		if self.hz < 0.000001 or self.duty > 0.9999 \
				or self.duty < 0.0001:
			self.io(self.duty > 0.5)
			return
		self.cycle = 1.0 / self.hz
		self.duty_on = self.cycle * self.duty
		self.duty_off = self.cycle - self.duty_on
		self.toggle()

	def toggle(self):
		self.tom.add("led", self.lit and self.duty_off \
				or self.duty_on, self.toggle)
		self.io(not self.lit)

	def io(self, lit):
		self.lit = lit
		gpio.output(LED_PIN, lit)

A classe acima faz o LED piscar segundo um ritmo e "ciclo ativo" (% de tempo que o LED fica ligado) escolhidos.

Note que previ a situação do ritmo ser 0Hz ou o "ciclo ativo" ser 0% ou 100%, casos em que o LED fica continuamente apagado ou aceso, sem piscar.

O circuito do LED é simplesmente um LED vermelho e um resistor limitador, entre o pino GPIO e a terra. Sem chance de dar galho se o pino estiver IN. A corrente é limitada a 5mA (o GPIO não pode dar mais que isso), mas o LED brilha o suficiente com até menos que isso, ao menos para nossos fins.

class StateMachine(object):
	def __init__(self, tom, led):
		self.tom = tom
		self.led = led
		self.INITIAL = 0
		self.REARM = 1
		self.IDLE = 2
		self.SHUTDOWN = 3
		self.map = { \
		    self.INITIAL:  
		      {Switch.OFF: [self.IDLE,     self.go_idle],
		       Switch.ON:  [self.REARM,    self.go_rearm]},
		    self.REARM:
		      {Switch.OFF: [self.IDLE,     self.go_idle]},
		    self.IDLE:
		      {Switch.ON: [self.SHUTDOWN, self.go_shutdown]},
		    self.SHUTDOWN:
		      {},
		}
		self.current = self.INITIAL

	def switched(self, new_state):
		try:
			new_state = self.map[self.current][new_state]
		except KeyError:
			# no change in state
			return

		self.current = new_state[0]
		new_state[1]()

	def go_idle(self):
		self.led.blink(0.33, 0.025)

	def go_rearm(self):
		self.led.blink(2, 0.05)

	def go_shutdown(self):
		self.led.blink(10, 0.05)
		os.system("/sbin/shutdown -h now")

A classe acima é uma máquina de estado que toma a decisão de desligar o computador (chave ON, estando em OFF antes), e também controla o ritmo do LED conforme o estado, para dar um feedback.

Assim o usuário (eu) sabe que o script está rodando e que a chave está funcionando.

class Switch(object):
	ON = 1
	OFF = 0
	UNDEFINED = 2

	def __init__(self, tom, sm):
		self.current_state = Switch.UNDEFINED
		self.last_state = self.current_state
		self.tom = tom
		self.sm = sm
		self.read()
		
	def read(self):
		self.tom.add("switch", 0.25, self.read)
		state = gpio.input(SWITCH_PIN) \
			and Switch.ON or Switch.OFF

		if state != self.current_state and \
		   state == self.last_state:
			# confirmed (debounced) change
			self.current_state = state
			self.sm.switched(state)

		self.last_state = state

Controle da chave com "debouncing". Toda chave dá algum mau contato quando é comutada. Isto pode ser resolvido em hardware, com um capacitor; ou em software, como fiz acima.

O circuito da chave possui dois resistores, de modo que o pino nunca é ligado diretamente a +3.3V nem à terra, evitando problemas se o pino ficar em modo OUT por erro de software.

def heartbeat():
	print "."

to = TimeoutManager()
led = LED(to)
sm = StateMachine(to, led)
switch = Switch(to, sm)

led.blink(0, 1)
to.add("heartbeat", 60, heartbeat)

while True:
	to.process_timeouts()

O código acima é simplesmente a "amarração" das classes. A chave atua sobre a máquina de estado, que por sua vez controla o LED, e todo mundo usa o gerenciador de timeouts.

Para mexer com eletrônica a sério, provavelmente o Arduino é uma escolha melhor. Mas caso seu projeto envolvendo hardware também precise de um computador de uso geral, o Raspberry pode ser interessante. Aliás, a placa GertBoard tem um Arduino como processador auxiliar, proporcionando o melhor de dois mundos.

e-mail icon