Tecnologia vestible

Per començar Elements d'entrada Programació CircuitPython   Recursos CITCEA
Elements no electrònics Elements de sortida Programació Arduino    
Projectes Elements de control Dades pràctiques   Inici

Comunicació mitjançant raigs infraroigs

La placa Circuit Playground Express incorpora un emissor i un receptor d'infraroigs que ens permeten comunicar dues plaques entre elles o un comandament a distància i una placa. La biblioteca adafruit_irremote incorpora la possibilitat de codificar i descodificar en el protocol NEC que és força emprat ja que aquest fabricant japonès venia els circuits integrats a altres fabricants.

Cada protocol té les seves característiques. Hi sol haver un inici de transmissió, una adreça (que identifica el fabricant) i unes dades (la comanda que s'envia). En aquesta pàgina de San Bergmans hi podeu trobar una descripció de la codificació NEC. En aquesta codificació s'envia una adreça de dos bytes i unes dades de dos bytes. Els dos bytes de dades són complementaris (la seva suma és 255). En la codificació original els dos bytes d'adreça també eren complementaris però en la codificació extesa ja no ho són (per donar espai a més fabricants).

Tractament de missatges en brut

Per entendre una mica com funciona tot plegat, anem a fer uns programes que tractin els missatges en brut, és a dir agafant directament la llargada de cada un dels semipolsos del senyal infraroig. Per tant, el que farem serà mesurar quan dura cada activació i cada desactivació del LED. Aquest mètode és poc eficient perquè emmagatzema moltíssima informació per a cada comanda però, en canvi, és pràcticament universal i ens serveix per a tots els comandaments.

El següent programa espera rebre un senyal IR i quan l'ha rebut ens mostra el nombre de semipolsos que s'han rebut i la durada (en microsegons) de cada un.

# Programa BR1
import board
import time
import pulseio
import adafruit_irremote
polsos = pulseio.PulseIn(board.IR_RX, maxlen=200, idle_state=True)
descodificador = adafruit_irremote.GenericDecode()
print("Prem un boto del comandament")
while True:
    pulse = descodificador.read_pulses(polsos)
    print("S'han rebut ", len(pulse), " polsos")
    print("Polsos rebuts:\n", pulse)
    time.sleep(0.2)

Per provar aquest programa podeu agafar el comandament de qualsevol aparell i prémer un dels botons encarant el comandament a la placa Circuit Playground Express. Jo ho he provat prement el botó d'engegar del comandament d'un televisor i he obtingut els següents valors:

S'han rebut  67  polsos
Polsos rebuts:
 [4533, 4449, 619, 1613, 615, 1618, 620, 1612, 616, 516, 618, 509,
 615, 514, 621, 508, 618, 512, 620, 1612, 646, 1587, 621, 1611,
 617, 512, 622, 508, 616, 513, 621, 508, 616, 514, 620, 509,
 615, 1617, 621, 508, 616, 514, 620, 509, 615, 514, 620, 509,
 615, 517, 617, 1613, 615, 514, 620, 1613, 615, 1617, 621, 1611,
 617, 1616, 622, 1610, 618, 1614, 644]

Tingueu present que molts comandaments si premeu el botó de manera seguida obtindreu resultats inesperats. Per exemple si premem el botó de baixar el volum de manera continuada és perquè volem que el volum baixi diverses posicions de manera força ràpida. Si cada cop el comandament enviés l'ordre sencera el procés seria lent i poc eficient. Per això és habitual que hi hagi un codi especial (normalment molt curt) que correspon a la comanda repetir, és a dir que es repeteixi la comanda anterior. Aquest codi és el que s'envia quan es prem la tecla diverses vegades molt seguides o quan es manté premuda durant un cert temps.

Si premem un altre cop el mateix botó hauríem d'obtenir el mateix resultat però podem observar que no és així:

S'han rebut  67  polsos
Polsos rebuts:
 [4503, 4480, 589, 1644, 584, 1651, 586, 1644, 584, 545, 589, 540,
 584, 546, 588, 541, 593, 536, 588, 1645, 593, 1639, 589, 1643,
 585, 545, 588, 543, 591, 536, 588, 541, 593, 536, 588, 542,
 592, 1640, 588, 542, 592, 537, 587, 542, 592, 538, 586, 543,
 591, 539, 585, 1647, 591, 538, 586, 1646, 592, 1641, 586, 1646,
 592, 1641, 587, 1645, 593, 1639, 589]

Això és degut a que els polsos no són exactament de la mateixa mida. És un fet normal i els receptors ja s'encarreguen de donar un marge de tolerància.

Si ens mirem els valors rebuts, veurem que (amb un cert marge) es poden classificar en tres possibles casos: 4500, 1700 i 550. Si ens mirem la pàgina de San Bergmans podem observar que hi surten, teòricament, cinc valors que corresponen al codi normalitzat NEC. Aquests serien els següents (en microsegons):

	Primer pols trama:		             9000
	Segon pols trama:		             4500
	Primer tram valor:		              560
	Segon tram valor 1:		2250 - 560 = 1690
	Segon tram valor 0:		1120 - 560 =  560

I observem que dos dels nombres són iguals, per tant en queden quatre. Veiem que els valors que nosaltres hem obtingut es corresponen amb tres d'aquests quatre nombres. Podem fer, doncs, una funció que busqui aquests quatre valors (amb un marge de, per exemple, el 20 % i els substitueixi pel valor de referència. El següent programa és una modificació de l'anterior en la que si els valors rebuts es corresponen amb els de referència son substituïts per aquests i en cas contrari es queden tal com s'han rebut.

# Programa BR2
import board
import time
import pulseio
import adafruit_irremote
polsos = pulseio.PulseIn(board.IR_RX, maxlen=200, idle_state=True)
descodificador = adafruit_irremote.GenericDecode()
def normal(pls):
    nou_pls = pls
    for i in range(len(pls)):
        pol = pls[i]
        if (pol > 9000 * 0.8) and (pol < 9000 * 1.2):
            pol = 9000
        elif (pol > 4500 * 0.8) and (pol < 4500 * 1.2):
            pol = 4500
        elif (pol > 1690 * 0.8) and (pol < 1690 * 1.2):
            pol = 1690
        elif (pol > 560 * 0.8) and (pol < 560 * 1.2):
            pol = 560
        nou_pls[i] = pol
    return nou_pls
print("Prem un boto del comandament")
while True:
    pulse = descodificador.read_pulses(polsos)
    print("S'han rebut ", len(pulse), " polsos")
    print("Polsos rebuts:\n", pulse)
    print("Polsos normalitzats:\n", normal(pulse))
    time.sleep(0.2)

Prement el mateix botó, la resposta del programa seria:

S'han rebut  67  polsos
Polsos rebuts:
 [4498, 4484, 593, 1639, 589, 1644, 594, 1638, 589, 540, 594, 535,
 588, 542, 592, 537, 587, 542, 592, 1640, 588, 1645, 592, 1640,
 588, 541, 593, 536, 587, 543, 591, 538, 589, 540, 591, 538,
 586, 1647, 591, 538, 586, 543, 590, 540, 584, 545, 589, 540,
 594, 536, 588, 1645, 593, 536, 588, 1644, 594, 1639, 588, 1644,
 584, 1648, 590, 1643, 585, 1647, 590]
Polsos normalitzats:
 [4500, 4500, 560, 1690, 560, 1690, 560, 1690, 560, 560, 560, 560,
 560, 560, 560, 560, 560, 560, 560, 1690, 560, 1690, 560, 1690,
 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560,
 560, 1690, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560,
 560, 560, 560, 1690, 560, 560, 560, 1690, 560, 1690, 560, 1690,
 560, 1690, 560, 1690, 560, 1690, 560]
 

on observem que tots els valors han estat normalitzats. Si algun dels valors rebuts no es correspongués amb un dels quatre es quedaria sense modificar.

Podem fer que el nostre comandament ens serveixi per a activar alguna funció de la nostra placa Circuit Playground Express. Per exemple, en el programa següent anem a fer que la tecla d'incrementar el canal ens encenqui els NeoPixels de color verd i la de decrementar els encengui de color vermell. Els LED s'apagaran passat mig segon.

# Programa BR3
import board
import neopixel
import time
import pulseio
import adafruit_irremote
polsos = pulseio.PulseIn(board.IR_RX, maxlen=200, idle_state=True)
descodificador = adafruit_irremote.GenericDecode()
cadena = neopixel.NeoPixel(board.NEOPIXEL, 10, brightness=0.3)
ch_up = [4500, 4500, 560, 1690, 560, 1690, 560, 1690, 560, 560, 560, 560,
    560, 560, 560, 560, 560, 560, 560, 1690, 560, 1690, 560, 1690,
    560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560,
    560, 1690, 560, 560, 560, 560, 560, 1690, 560, 560, 560, 560,
    560, 560, 560, 1690, 560, 560, 560, 1690, 560, 1690, 560, 560,
    560, 1690, 560, 1690, 560, 1690, 560]
ch_dwn = [4500, 4500, 560, 1690, 560, 1690, 560, 1690, 560, 560, 560, 560,
    560, 560, 560, 560, 560, 560, 560, 1690, 560, 1690, 560, 1690,
    560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560,
    560, 560, 560, 560, 560, 560, 560, 1690, 560, 560, 560, 560,
    560, 560, 560, 1690, 560, 1690, 560, 1690, 560, 1690, 560, 560,
    560, 1690, 560, 1690, 560, 1690, 560]
def normal(pls):
    nou_pls = pls
    for i in range(len(pls)):
        pol = pls[i]
        if (pol > 9000 * 0.8) and (pol < 9000 * 1.2):
            pol = 9000
        elif (pol > 4500 * 0.8) and (pol < 4500 * 1.2):
            pol = 4500
        elif (pol > 1690 * 0.8) and (pol < 1690 * 1.2):
            pol = 1690
        elif (pol > 560 * 0.8) and (pol < 560 * 1.2):
            pol = 560
        nou_pls[i] = pol
    return nou_pls
def compara(pls1, pls2):
    if len(pls1) != len(pls2):
        return False
    for i in range(len(pls1)):
        if pls1[i] != pls2[i]:
            return False
    return True
print("Prem un boto del comandament")
while True:
    pulse = normal(descodificador.read_pulses(polsos))
    print("S'han rebut ", len(pulse), " polsos")
    print("Polsos normalitzats:\n", pulse)
    if compara(pulse, ch_up):
        cadena.fill((0, 255, 0)) 
    if compara(pulse, ch_dwn):
        cadena.fill((255, 0, 0)) 
    time.sleep(0.5)
    cadena.fill((0, 0, 0)) 

Podem fer servir també la nostra placa Circuit Playground Express per enviar els senyals d'infraroig a un altre dispositiu. El programa següent envia els mateixos senyals de canvi de canal que rebia el programa anterior. Si tenim dues plaques podem enviar-los des d'una (en la que hi posarem el programa següent, BE1) i rebre'ls a l'altra (en la que hi haurà el programa anterior, BR3).

També podem fer servir la placa per actuar sobre l'aparell real. En el meu cas, he pogut canviar el canal del televisor fent servir aquest programa.

# Programa BE1
import board
import array
import time
import pulseio
import digitalio
pwm = pulseio.PWMOut(board.IR_TX, frequency=38000, duty_cycle=32768)
polsos = pulseio.PulseOut(pwm)
botoA = digitalio.DigitalInOut(board.D4)
botoA.direction = digitalio.Direction.INPUT
botoA.pull = digitalio.Pull.DOWN
botoB = digitalio.DigitalInOut(board.D5)
botoB.direction = digitalio.Direction.INPUT
botoB.pull = digitalio.Pull.DOWN
ch_up = array.array('H', [4500, 4500, 560, 1690, 560, 1690, 560, 1690, 560, 560, 560, 560,
    560, 560, 560, 560, 560, 560, 560, 1690, 560, 1690, 560, 1690,
    560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560,
    560, 1690, 560, 560, 560, 560, 560, 1690, 560, 560, 560, 560,
    560, 560, 560, 1690, 560, 560, 560, 1690, 560, 1690, 560, 560,
    560, 1690, 560, 1690, 560, 1690, 560])
ch_dwn = array.array('H', [4500, 4500, 560, 1690, 560, 1690, 560, 1690, 560, 560, 560, 560,
    560, 560, 560, 560, 560, 560, 560, 1690, 560, 1690, 560, 1690,
    560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560,
    560, 560, 560, 560, 560, 560, 560, 1690, 560, 560, 560, 560,
    560, 560, 560, 1690, 560, 1690, 560, 1690, 560, 1690, 560, 560,
    560, 1690, 560, 1690, 560, 1690, 560])
print("Prem un boto de la placa")
while True:
    if botoA.value:
        print("Polsador A premut")
        polsos.send(ch_up)
    if botoB.value:
        print("Polsador B premut")
        polsos.send(ch_dwn)
    time.sleep(0.2)

Cal tenir en compte que la majoria de comandaments envien la comanda que correspon al botó que s'ha premut en cada moment però això no sempre és així. En molts aparells d'aire condicionat, per dir un cas habitual, nosaltres tenim la temperatura indicada a la pantalla i el botó d'augment de temperatura canvia aquest valor i envia l'ordre. Imaginem, per exemple, que tenim 22 °C a la pantalla del comandament i piquem dues vegades el botó d'incrementar però sense apuntar cap a l'aparell. Ara el comandament indicarà 24 °C però l'aparell seguirà amb la consigna de 22 °C. Si ara premem el botó d'augmentar la potència del ventilador apuntant a l'aparell el comandament enviarà la nova potència però també la consigna de 24 °C. Això fa que les ordres d'aquest tipus d'aparells siguin trames molt llargues en les que no és fàcil identificar el significat dels diferents valors, és fàcil copiar una trama però no ho és generar-ne una que s'adapti a les nostres necessitats.

Exemples amb els codis NEC

La biblioteca adafruit_irremote incorpora la possibilitat de codificar i descodificar en el protocol NEC. La podem fer servir per rebre senyals de comandaments amb aquest protocol, com aquest que és força econòmic.

Comandament  [AF]

En el següent programa la tecla d'augmentar el volum ens encendrà els NeoPixels de color verd i la de disminuir-lo els encendrà de color vermell. Els LED s'apagaran passat mig segon.

# Programa NR1
import board
import neopixel
import time
import pulseio
import adafruit_irremote
polsos = pulseio.PulseIn(board.IR_RX, maxlen=200, idle_state=True)
descodificador = adafruit_irremote.GenericDecode()
cadena = neopixel.NeoPixel(board.NEOPIXEL, 10, brightness=0.3)
vol_up = [255, 2, 191, 64]
vol_dwn = [255, 2, 255, 0]
print("Prem un boto del comandament")
while True:
    pulses = descodificador.read_pulses(polsos)
    try:
        rebut = descodificador.decode_bits(pulses, debug=False)
    except adafruit_irremote.IRNECRepeatException:
        continue    # Codi massa curt, potser codi de repeticio
    except adafruit_irremote.IRDecodeException as e:
        continue    # Codi no reconegut
    print("Valors rebuts: ", rebut)
    if rebut == vol_up:
        cadena.fill((0, 255, 0)) 
    if rebut == vol_dwn:
        cadena.fill((255, 0, 0)) 
    time.sleep(0.5)
    cadena.fill((0, 0, 0)) 

La part de la recepció dels codis (línies amb text de color groc) és una mica més complicada perquè preveu la possibilitat que hi hagi errors que, si no es tracten, farien aturar el programa.

Si el nostre comandament no envia codis NEC ens podem trobar que funcioni o que no. En molts casos, però, aquest programa ens donarà uns valors concrets per a cada tecla. Si aquests valors són sempre els mateixos per a una tecla concreta podem fer servir aquest programa malgrat el comandament no sigui de tipus NEC. En el cas del comandament del televisor que hem comentat a l'apartat anterior (programa BR3) he obtingut els següents valors per a la tecla d'incrementar el canal:

 ch_up = [31, 31, 183, 72]

i aquests altres per a la de decrementar-lo:

 ch_dwn = [31, 31, 247, 8]

i substituïnt aquests valors en les dues variables del programa anterior (NR1) ha funcionat perfectament. Els quatre valors obtinguts tenen relació amb les durades dels semipolsos que hem vist a l'apartat anterior, en el proper apartat ho explicarem.

Ara anem a fer un programa que ens generi aquests mateixos codis per poder tenir una placa Circuit Playground Express que ens faci de comandament i així enviar codis d'una placa a l'altra.

# Programa NE1
import time
import adafruit_irremote
import pulseio
import digitalio
import board
pwm = pulseio.PWMOut(board.IR_TX, frequency=38000, duty_cycle=2 ** 15)
polsos = pulseio.PulseOut(pwm)
codificador = adafruit_irremote.GenericTransmit(header=[9500, 4500],
    one=[550, 550], zero=[550, 1700], trail=0)
botoA = digitalio.DigitalInOut(board.BUTTON_A)
botoA.direction = digitalio.Direction.INPUT
botoA.pull = digitalio.Pull.DOWN
botoB = digitalio.DigitalInOut(board.BUTTON_B)
botoB.direction = digitalio.Direction.INPUT
botoB.pull = digitalio.Pull.DOWN
led = digitalio.DigitalInOut(board.D13)
led.direction = digitalio.Direction.OUTPUT
vol_up = [255, 2, 191, 64]
vol_dwn = [255, 2, 255, 0]
while True:
    if botoA.value:
        print("Boto A  -->  Vol +")
        led.value = True
        codificador.transmit(polsos, vol_up)
        led.value = False
        time.sleep(0.2)
    if botoB.value:
        print("Boto B  -->  Vol -")
        led.value = True
        codificador.transmit(polsos, vol_dwn)
        led.value = False
        time.sleep(0.2)

Podem provar de fer servir aquest programa (amb els codis corresponents) per comandar el nostre dispositiu però si la codificació del dispositiu no és NEC no ens funcionarà. Per exemple, posant-hi els codis corresponents al comandament del televisor la placa que duu el programa NR1 els interpreta correctament però, en canvi, el televisor no els entén. En el proper apartat intentarem resoldre aquest problema.

Adaptant els codis als nostres dispositius

Treballar amb la llargada dels semipolsos ja hem vist que és poc eficient. Anem a fer el mateix que hem fet amb els codis NEC però de manera que ens serveixi per als nostres aparells i comandaments.

Aturem-nos un moment a mirar la línia del pwm del programa anterior (NE1).

pwm = pulseio.PWMOut(board.IR_TX, frequency=38000, duty_cycle=2 ** 15)

observem que aquí la freqüència indicada és de 38 kHz que és la més habitual. En cas que el dispositiu que ens interessi controlar no faci servir aquesta freqüència és probable que el nostre programa no funcioni. Per saber la freqüència cal disposar d'un receptor (fotodíode o fototransistor) i un oscil·loscopi.

Ara mirem la línia del codificador.

codificador = adafruit_irremote.GenericTransmit(header=[9500, 4500],
    one=[550, 550], zero=[550, 1700], trail=0)

i veurem que hi ha quatre valors o parelles de valors que es poden configurar. Les dades per ajustar aquests paràmetres les obtindrem de les durades dels semipolsos que haurem obtingut amb el programa BR1.

El que comentarem a continuació a mi m'ha funcionat en totes les proves que he fet però si en algun cas no us funciona sempre queda el recurs de fer servir la llargada dels semipolsos.

Atès que farem servir els mateixos programes (canviant els valors) que per als codis NEC ens hem d'adaptar al que s'ha establert a la biblioteca adafruit_irremote que no coincideix del tot amb el que s'explica a la pàgina de San Bergmans.

Agafem, per exemple, la llista de valors corresponents a la tecla de disminuir canal que hem fet servir en programes anteriors. Els valors eren:

 [4500, 4500, 560, 1690, 560, 1690, 560, 1690, 560, 560, 560, 560,
 560, 560, 560, 560, 560, 560, 560, 1690, 560, 1690, 560, 1690,
 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560,
 560, 560, 560, 560, 560, 560, 560, 1690, 560, 560, 560, 560,
 560, 560, 560, 1690, 560, 1690, 560, 1690, 560, 1690, 560, 560,
 560, 1690, 560, 1690, 560, 1690, 560]

Els valors marcats en verd corresponen a la capçalera ([4500, 4500]) i el final (560), els marcats en groc corresponen a zeros ([560, 1690]) i els que estan en blanc seran els uns ([560, 560]). A continuació hem escrit els bits que corresponen, filera per filera, a cada parella de valors.

 H 0 0 0 1 1
 1 1 1 0 0 0
 1 1 1 1 1 1
 1 1 1 0 1 1
 1 0 0 0 0 1
 0 0 0 T

Ara hem marcat en groc i en blanc, alternadament, els grups de vuit bits. Cada un d'aquests grups serà un byte. Aquests quatre bytes escrits en decimal quedaran així:

 H   31   31   247   8   T

On observem que els dos bytes de dades són complementaris.

A partir del que acabem de fer, podem configurar tots els paràmetres del codificador. Per al cas del comandament que estem comentant els valors serien:

 header = [4500, 4500]
 one = [560, 560]
 zero = [560, 1690]
 trail=560
 ch_dwn = [31, 31, 247, 8]

Podem veure que els valors obtinguts per a la polsació de la tecla són els mateixos que hem trobat amb el programa NR1. El programa, doncs, els entenia perfectament però el televisor no els entenia perquè la capçalera i el final no es corresponen amb els del programa NE1. Si ens ho mirem en més detall veurem que els valors corresponents als uns i als zeros no són exactament els mateixos (550 contra 560 i 1700 contra 1690) però en aquest cas les diferències entren dins dels marges d'error admesos.

Ara anem a fer el mateix amb la comanda d'augmentar canal.

 [4500, 4500, 560, 1690, 560, 1690, 560, 1690, 560, 560, 560, 560,
 560, 560, 560, 560, 560, 560, 560, 1690, 560, 1690, 560, 1690,
 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560, 560,
 560, 1690, 560, 560, 560, 560, 560, 1690, 560, 560, 560, 560,
 560, 560, 560, 1690, 560, 560, 560, 1690, 560, 1690, 560, 560,
 560, 1690, 560, 1690, 560, 1690, 560]
 H 0 0 0 1 1
 1 1 1 0 0 0
 1 1 1 1 1 1
 0 1 1 0 1 1
 1 0 1 0 0 1
 0 0 0 T
 H   31   31   183   72   T

Com era d'esperar, els quatre paràmetres són els mateixos i el codi corresponent a la tecla coincideix amb el que havíem obtingut amb el programa NR1.

Ara substituirem aquests paràmetres i els codis corresponents en el programa NE1.

# Programa GE1
import time
import adafruit_irremote
import pulseio
import digitalio
import board
pwm = pulseio.PWMOut(board.IR_TX, frequency=38000, duty_cycle=2 ** 15)
polsos = pulseio.PulseOut(pwm)
codificador = adafruit_irremote.GenericTransmit(header=[4500, 4500],
    one=[560, 560], zero=[560, 1690], trail=560)
botoA = digitalio.DigitalInOut(board.BUTTON_A)
botoA.direction = digitalio.Direction.INPUT
botoA.pull = digitalio.Pull.DOWN
botoB = digitalio.DigitalInOut(board.BUTTON_B)
botoB.direction = digitalio.Direction.INPUT
botoB.pull = digitalio.Pull.DOWN
led = digitalio.DigitalInOut(board.D13)
led.direction = digitalio.Direction.OUTPUT
ch_up = [31, 31, 183, 72]
ch_dwn = [31, 31, 247, 8]
while True:
    if botoA.value:
        print("Boto A  -->  CH +")
        led.value = True
        codificador.transmit(polsos, ch_up)
        led.value = False
        time.sleep(0.2)
    if botoB.value:
        print("Boto B  -->  CH -")
        led.value = True
        codificador.transmit(polsos, ch_dwn)
        led.value = False
        time.sleep(0.2)

En el cas del televisor, aquest programa ha funcionat correctament.

 

 

 

En aquest web, les fotografies marcades amb [AF] són del web d'Adafruit, les marcades amb [SF] del web d'Sparkfun i les marcades amb [AU] del web d'Arduino.

 

 

 

 

 

 

 

 

 

 

Llicència de Creative Commons
Aquesta obra d'Oriol Boix està llicenciada sota una llicència no importada Reconeixement-NoComercial-SenseObraDerivada 3.0.