Questo articolo è propedeutico per ogni applicazione che richieda interazione con un telecomando, oppure con un apparecchio normalmente comandato da telecomando.
Nota: non si applica ad apri-cancello o qualunque altro telecomando non a infrarossi.
Sono abbastanza vecchio da ricordare quando in famiglia avevamo un televisore che non solo era senza telecomando, era pure in bianco e nero.
Come si cambiava canale o si operava sul volume? Semplicemente alzando i glutei dal divano (e andando a premere qualcosa sull’apparecchio).
I telecomandi hanno cambiato tutto questo: ci permettono di fare tutta una serie di operazioni su TV/stereo/altro senza dover fisicamente interagire con l’apparecchio. Ma come funzionano?
Infrarossi! Lo sanno tutti quelli che hanno provato a cambiare canale mettendo una mano davanti al telecomando, puntandolo verso il cuscino, o stando in corridoio, fuori dalla “linea di tiro”.
Ma esattamente, come funziona? Perché ogni tasto fa una cosa diversa senza mai sbagliarsi? Posso usarlo per fare altro?
Siamo qui per scoprirlo!
Nota, qualora non fosse chiaro: un estintore normalmente non si può azionare con telecomando
Questo articolo può essere utile in due casi:
- abbiamo un telecomando che ci balla tra le mani e vogliamo utilizzarlo per comandare un nostro progetto
- abbiamo un apparecchio normalmente comandato da telecomando, ma vorremmo automatizzarne il comportamento senza essere lì fisicamente a schiacciare tasti (e senza bisogno di aprirlo per fare cose più invasive)
In entrambi i casi abbiamo bisogno di capire qual è il traffico di dati generato dal telecomando per ogni tasto, per poterlo leggere nel primo caso, oppure riprodurre nel secondo.
Questo tutorial ci permetterà di ottenere il “fingerprint” associato al tasto o ai tasti che ci interessa interpretare, o simulare.
Il “fingerprint” è sostanzialmente un “burst” (o raffica) di impulsi che per ogni tasto ha (circa) la stessa durata complessiva, ma che differisce come combinazione nella durata di ciascun impulso e della pausa che lo segue per fare in modo che il ricevitore interpreti inequivocabilmente quale tasto è stato premuto.
Inoltre, per la maggior parte dei tasti è prevista una “coda” che dice al ricevitore che il tasto non è stato rilasciato (si pensi ai tasti volume + e – nel telecomando di un televisore, e all’effetto che ha tenerli premuti).
Il dettaglio
Il LED a infrarossi sul telecomando genera una serie di impulsi di durata diversa, ed utilizzando varie combinazioni ottiene l’effetto di diversificare la funzione dei tasti.
Ma se fosse così semplice sarebbe soggetto a disturbi, per cui gli impulsi acceso/spento non sono impulsi continui ma viaggiano su una portante, come si può vedere qui sotto:
o meglio, il vero segnale:
quando l’impulso è “OFF” il canale è vuoto, quando è “ON”, indipendentemente dalla lunghezza dell’impulso, il LED del telecomando non è acceso staticamente ma “vibra”.
Questo permette di fare in modo che segnali spurii non distraggano il ricevitore, che è focalizzato solo su quella frequenza: come quando ascoltiamo una stazione radio e le altre non ci disturbano (eccetto Radio Maria in galleria). Il principio è esattamente lo stesso.
Siccome in poco tempo e con un mezzo relativamente lento dobbiamo esprimere tante informazioni, non è il caso di buttare via niente: infatti la durata degli impulsi e quella delle pause concorrono entrambe a formare un segnale unico; dosandoli saggiamente si riescono ad ottenere combinazioni inequivocabili, che quindi il ricevitore interpreterà correttamente.
Nota: La frequenza più largamente utilizzata per la portante è 38KHz. Ci sono marche dove i progettisti vogliono fare i fenomeni e usano altre portanti, ma 38KHz è quella che va per la maggiore.
Nel caso in cui il telecomando lavori con una portante diversa esistono sensori appropriati, ma il principio è esattamente lo stesso.
Lista della spesa
Cosa mi serve avere/sapere?
- Competenze:
- programmare un Arduino
- Attrezzatura:
- nessuna
- Materiali Elettromeccanica:
- nulla
- Materiali Elettronica:
- Una scheda di sviluppo ESP32, o in alternativa Arduino Uno.
- Sensore IR TSOP38238 (o altro modello a seconda della frequenza del telecomando)
- il telecomando da esaminare
- Connettori:
- 3 cavetti Dupont femmina/femmina
- Software:
- Arduino GUI
- Arduino GUI
Componenti
MCU
Per quello che riguarda la scheda di sviluppo ESP32, qualunque tipo è buono, purché abbia esposto almeno 1 pin di input; il sensore lavora indifferentemente a 5V e 3.3V, ma attenzione a non alimentarlo a 5V se la tensione di lavoro dei piedini è 3.3V.
Veniamo alle differenze tra ESP32 e Arduino Uno, in modo da poter scegliere accuratamente.
Per quello che riguarda il costo e per la velocità di elaborazione l’ESP fa 720K cicli puliti al secondo mentre Arduino ne fa meno di un decimo e, anche se ragioniamo a interrupt, visto che si parla di microsecondi normalmente ci si sente più tranquilli con l’ESP. Comunque tutto si può realizzare anche con Arduino Uno, dato che il software è compatibile e non ci sono librerie.
Nota: per l’utilizzo con Arduino Uno vanno adattate un paio di cose, specialmente il piedino da utilizzare per l’input.
Nota: non conosco da vicino altri Arduino con caratteristiche simili all’ESP. Personalmente, visto che si tratta di scelte strategiche che coinvolgono anche progetti industriali, periodicamente scelgo una piattaforma e consolido su quella, senza perdermi su mille dispositivi diversi; all’epoca in cui ho scelto di adottare ESP32 ho valutato costo, velocità e supporti di comunicazione; in ogni caso uso ancora Arduino (o anche ATMega nudo) quando non ho bisogno di particolare velocità o di collegarmi wireless.
Sensore IR
Il sensore TSOP38238 (l’ultimo 38 sta per la frequenza della portante) è un gioiellino, in quanto risparmia al progettista lo strazio di dover controllare la frequenza della portante, filtrare disturbi logici (impulsi spurii) o fisici (raggi infrarossi di diverse lunghezze d’onda). Lui riceve, e se riconosce la portante butta fuori un segnale pulito, ovvero gli impulsi già raffinati.
In sostanza vede questo:
e ci restituisce questo:
Il componente:
Il datasheet si trova ovunque (ad esempio qui: http://www.farnell.com/datasheets/2049183.pdf) ma l’unica informazione che ci serve veramente è questa:
Circuiteria
La circuiteria è estremamente semplice:
- 3V3 al pin 3 del TSOP
- GND al pin 2
- D13 al pin 1
Nota: diversamente da Arduino e cloni, ESP32 è prodotto in diversi formati, a seconda dei quali espone piedini diversi in quantità e posizione; nel mio caso ho scelto D13 poiché era vicino a Vin e GND, ma in altri formati potrebbe essere altrove; nel caso sia diverso, scegliere un piedino digitale in grado di essere configurato in INPUT, e modificare il codice adeguando il numero.
Nel caso si tratti di un Arduino Uno, è necessario utilizzare un piedino abilitato alla lettura degi interrupt esterni, ovvero il piedino 2 (interrupt 0) o il piedino 3 (interrupt 1).
Vin dell’ESP si chiama “Vin” e non “Voutput-di-potenza” per una ragione; si può usare per avere la tensione di riferimento, oppure per prelevare piccolissime correnti, ma non per carichi sostanziali. Il consumo del TSOP è molto basso e possiamo permetterci di alimentarlo da Vin, ma non appena saliamo di mA è necessario fare il contrario, ovvero: fornire i 5V esternamente, alimentare il carico, ed alimentare l’ESP tramite Vin.
Codice
Il codice prevede che ad ogni pressione di un tasto venga prodotta (sul serial monitor) la lista di impulsi che lo compongono, elencata in microsecondi; la lista è già nel formato giusto per essere incollata e copiata nel codice dell’applicazione definitiva sotto forma di array.
Ogni tasto viene battezzato con un nome nel formato button_xx dove “xx” è un progressivo da 01 a 99, in modo da poter elencare tutti i tasti di un telecomando senza dover fare copia e incolla ogni volta, ..a patto che ci si ricordi la sequenza nella quale i tasti sono stati premuti.
Esempio:button_01button_02button_03
e così via. Arrivati a 99 il codice torna automaticamente al valore 01.
Ogni telecomando prevede un “repeat“, quando un tasto viene tenuto premuto; il codice è in grado di selezionare il vero fingerprint del tasto e separare la parte di ripetizione; cerca anche di dare un senso alla struttura del repeat (che cambia per ogni marca) per facilitare il compito di interpretarlo qualora nell’applicazione finale serva usarlo.
Se i pulsanti vengono premuti più a lungo del necessario ed entrano in modalità “repeat”, viene elencata la serie di codici inviati per ogni ripetizione; solitamente la prima è diversa dalle altre, mentre dalla seconda in poi si inizia a riconoscere un “pattern”, o uno schema ricorrente.
Nota: Fino a quando non viene intercettata una pausa di 200ms (segno che il pulsante è stato rilasciato) non viene prodotto nessun output. Se è necessario per un determinato tasto ottenere sia il fingerprint che il codice di ripetizione, consiglio di premere il tasto per almeno 500ms, ma quanto detto sopra vale: finché non viene rilasciato il tasto, nulla apparirà a video.
Inoltre, si noterà che mentre il fingerprint è diverso per ogni tasto, la ripetizione tende ad essere uguale per tutti; il che ha molto senso, visto che sappiamo qual è il tasto, non è necessario specificare una ripetizione diversa per ognuno.
Il codice prevede un massimo di 200 tempi tra di impulsi e pause, il che è abbondantemente sufficiente per raccogliere il fingerprint e anche diversi cicli di ripetizione; in ogni caso è possibile aumentare questo valore modificando un punto nel codice (variabile maxentries). Tutti gli impulsi in eccesso vengono scartati. Occhio: aumentare maxentries con parsimonia, specialmente con Arduino Uno, per evitare di saturare la RAM.
Nota: statisticamente il fingerprint si colloca in una zona tra 64 e 76 “tempi raccolti” il che si traduce in 32-38 impulsi con la relativa pausa. Ho comunque preferito prevedere la cifra tonda di 200 (100 impulsi più altrettante pause) per essere sicuro di non perdermi niente in caso di telecomandi un po’ freak.
Per facilitare la raccolta dei dati emessi sull’interfaccia seriale, le righe contenenti l’output effettivo (quello pronto per essere copiaincollato) sono precedute da un punto e virgola (;); questo permette di copiare, incollare temporaneamente in un foglio di calcolo (come Libreoffice Calc o Microsoft Excel) ed usando come separatore il “;” tutto quello che interessa si troverà nella colonna B dove può essere ordinato per compattare e quindi finalmente copiato nel codice finale.
Infine, volendo resettare il contatore dei pulsanti a 1, è sufficiente attendere 20 secondi senza premere tasti; può essere utile quando si desidera catalogare i fingerprint di più telecomandi tutti in una sessione e fare un copiaincollone unico alla fine, senza dover resettare l’ESP.
Se si desidera modificare il tempo di intervallo per il reset del contatore (countdown), ricercare la riga:
const unsigned int cdown_intv = 20;
il countdown prevede anche due avvertimenti, che appaiono a seriale per segnalare lo stato del conto alla rovescia, attualmente impostati al 50% e 25% del tempo rimanente, ma che si possono cambiare modificando le variabili:
const byte cdown_w1at = 50;
const byte cdown_w2at = 25;
Parecchie costanti per modificare il comportamento, come queste, sono inserite nel codice, ma sono dichiarate in testa e facilmente modificabili per adattare l’applicazione a possibili casi molto diversi, che non ho avuto l’opportunità di collaudare.
La velocità della seriale è impostata su 115200bps; se nell’interfaccia seriale si vedono solo strani simboli, o anche nulla, il problema è quello. Basta reimpostare la velocità della seriale dalla interfaccia dell’IDE di Arduino al valore di 115200bps.
Nota: il software è commentato in inglese e fatto per aver senso in lingua; ho smesso di scrivere software e commenti in italiano nell’84, per non dover tradurre dopo (quando decido di condividere in caso di codice mio) o per non mettere in difficoltà i colleghi di nazionalità estera.
Nota: l’indentazione è rigorosamente allman.
Verticalmente prende più spazio, ma quando devi rileggere (magari anni dopo) ventimila righe di codice, risolvi problemi in tempo zero mentre i fighetti che hanno fatto la scelta di altri stili “perché allman è demodé” ne avranno anche solo diciottomila, ma stanno battendo la testa contro al muro.
Consiglio gratis da parte di uno che ha scritto milioni di righe, che ad oggi ancora funzionano (e generano fatturato).
Un paio di punti/spunti interessanti nel codice
Gli impulsi hanno durate molto brevi, in alcuni casi 300 microsecondi, ed è quindi impensabile intercettarli usando i cicli normali; serve un interrupt.
Nota: un interrupt è un impulso fisico (cambio di stato su un piedino) che arriva dall’esterno del programma e può essere usato per costringere il processore ad eseguire una parte di codice definita dal programmatore. Molto utile quando non si sa in anticipo quando potrebbe arrivare un dato (comunicazione asincrona).
Quando si verifica la condizione prevista, il processore sospende l’esecuzione del codice “regolare” ed esegue la parte di codice dedicata all’evento; al termine, riprende da dove aveva sospeso.
È necessario memorizzare ogni cambio di stato sul piedino poiché, come si diceva, sia la durata degli impulsi che quella delle pause ha un significato; tuttavia non ci preoccupiamo di memorizzare “come” cambia: infatti l’output (piedino 1 del TSOP) si comporta come un open collector con resistore di pull-up, quindi è invertente (per via del resistore di pull-up, in stato di riposo il piedino sarà sempre al valore alto, HIGH, 5V o 3.3V); il primo cambio sarà sempre da HIGH a LOW, come allo spegnersi dell’ultimo impulso sarà da LOW a HIGH per tornare a riposo. L’informazione sulla direzione del cambio diventa ridondante e questo semplifica di parecchio il progetto.
L’interrupt, nel nostro caso, è triggerato da un cambio di stato, e siccome gli interrupt devono essere il più possibile agili, all’interno viene eseguito il minimo indispensabile; sostanzialmente memorizzare quando è successo (in valore assoluto di micros()), aggiornare il puntatore, e poco altro. La prima lettura è speciale in quanto memorizza quando è avvenuto il “first hit“, per poter successivamente passare dai valori assoluti a quelli relativi (il tempo passato dall’impulso precedente) che rappresentano la durata effettiva di ogni impulso e di ogni pausa in mezzo.
Attivazione dell’interrupt
attachInterrupt(pin_IR, record_value, CHANGE);
pin_IR è il numero del piedino (13 in questo caso)
record_value è il nome dell’ISR (la routine) da chiamare quando si verifica l’evento
CHANGE è il tipo di evento: cambio di stato, in un senso o nell’altro; a seconda dell’applicazione, è possibile intercettare anche solamente RISING (da LOW a HIGH) o FALLING (da HIGH a LOW)
ISR (Interrupt Service Routine)
// ISR for recording IR pin changes
//
void record_value()
{
// unconditionally store the last hit (ms!)
//
last_hit = millis();
// if we have exceeded the max number of samples,
// there's no need to collect other crap
//
if(entryno >= maxentries)
return;
// the array is not complete as of yet:
// fill it up
//
// store zee time
//
chng_time[entryno] = micros();
// get the next entry prepped
//
entryno++;
}
Tutto tace finché almeno un impulso soddisfa la condizione dell’interrupt, attiva la ISR e viene registrato; in presenza di almeno un impulso, il ciclo normale del software va in allerta e controlla che last_hit (che viene sempre aggiornato quando si verifica un cambio di stato sul piedino) non diventi più vecchio di almeno 200 ms, il che è considerato un tempo sufficiente per arguire che il tasto è stato rilasciato. A quel punto, i valori contenuti nell’array dei tempi devono essere convertiti da assoluti (il microsecondo in cui è stato registrato il cambio) a relativi (il tempo dall’ultimo cambio).
Succede più o meno così:
Si imposta il puntatore ind a 1first_bogus, che potremmo tradurre come “primo elemento dal valore di dubbia validità” registrerà la posizione del primo impulso lungo o del primo elemento a zero, entrambi segni che il fingerprint è terminato
ind = 1; first_bogus = 0;
a questo punto memorizziamo il microsecondo in cui è iniziato tutto, quello registrato nel primo cambio di stato; l’elemento zero rimarrà sempre a zero; il ciclo parte quindi da 1 e finisce all’ultimo elemento presente nell’array
prev_val = chng_time[0];
chng_time[0] = 0;
while(ind < entryno)
{
// might be valid, might be empty, who knows
// if valid, check for a long pulse
//
if(chng_time[ind] > 0)
{
qui sottraiamo il tempo dell’evento precedente dal microsecondo registrato, ed otteniamo la differenza di tempo netta; quindi spostiamo il paletto in avanti per la durata netta dell’impulso appena scoperta, affinché al prossimo giro il punto di partenza sia aggiornato
// change the value
// scoot ahead the starting post
//
chng_time[ind] -= prev_val;
prev_val += chng_time[ind];
il valore nell’array è ormai relativo: controlliamo se è un impulso lungo (parametro, costante, long_pulse_min)
se first_bogus non è ancora stato usato, memorizziamo il puntatore; in questo modo ricorderemo solo il primo impulso lungo
// check the value and store just the first
//
if(chng_time[ind] > long_pulse_min)
if(first_bogus == 0)
first_bogus = ind;
}
else
se l’elemento è zero, e se first_bogus non è ancora stato usato, memorizziamo il primo elemento a zero; teoricamente qui non ci si passa mai, ma con l’hardware che comanda, i salvagente non sono mai troppi
nota: l’elemento [0] è zero, ma noi siamo partiti dall’elemento [1]
// if it's zero it's the first empty entry
// remember just the one
//
if(first_bogus == 0)
first_bogus = ind;
avanti il prossimo
// move on
//
ind++;
}
se la sequenza registrata non aveva code di ripetizione, first_bogus va assegnato comunque; altrimenti considera che si tratti di ripetizione a partire dall’inizio
// we might not have a first bogus yet, this happens when a button press
// has been completely clean; fix it using the pointer entryno
//
if(first_bogus == 0)
first_bogus = entryno;
il resto è piuttosto elementare; qui c’è l’intero codice che può essere caricato ed utilizzato direttamente (salvo eventuale piedino di segnale differente).
Nota: Questa è la versione base del software; ne esiste una più sofisticata (e decisamente più complicata come codice) che chiede 5 pressioni per ogni tasto ed emette la media calcolata per ogni impulso; utile per telecomandi con elettronica traballante. Ma ho visto che i fingerprint hanno differenze di conformazione talmente marcate che -prevedendo una certa tolleranza- non è mai un problema distinguere i tasti. Con una tolleranza di +/- 50 microsecondi si fa sempre centro, ma si è ancora ben lontani dall’equivocare un tasto per un altro.
Questo è tutto, spero che il progetto possa essere utile; personalmente ho un ESP dedicato a questa cosa e lo uso con frequenza.
Qualunque commento e miglioria è ben accetto!







