Warning: Cannot modify header information - headers already sent by (output started at /var/www/html/ognigiorno.com/wp-content/plugins/math-comment-spam-protection/math-comment-spam-protection.php:1) in /var/www/html/ognigiorno.com/wp-includes/feed-rss2.php on line 8
I servomotori (o servo) sono dei dispositivi molto utili per chi si diletta nella costruzione di robot e più in generale per tutte le realizzazioni che uniscono meccanica ed elettronica (spesso chiamata meccatronica o cibernetica). Si tratta in effetti di motori di precisione controllabili elettronicamente. Vediamo in questo articolo come funzionano e come fare per controllarli tramite microcontrollori.
Cosa sono i servomotori
In un piccolissimo contenitore racchiudono un motore, un riduttore ed il circuito che lo controlla. Il riduttore serve per aumentare la potenza disponibile. Il circuito permette di controllare la posizione del servo tramite impulsi inviati su un singolo filo.
I servo hanno infatti tre soli fili di collegamento. Si tratta di positivo e negativo di alimentazione e del filo di controllo (spesso erroneamente indicato come PWM).
Il rotore che esce dal corpo del servo (che può essere di plastica o metallico) ha collegata una croce che permette di utilizzarlo nelle costruzioni. Hanno solitamente liberta di movimento limitata a 180 gradi (mezzo giro). Smontandoli è possibile modificarli per eliminare questa limitazione e renderlo libero di girare 360 gradi come un motore normale.
Come si controllano
L’impulso di controllo serve per controllare la posizione, cioè per far muovere il rotore fino a che non raggiunge l’angolazione desiderata.
Il controllo è un segnale alto che si ripete a distanza di 20 ms, seguito dal livello basso. In base alla durata dell’impulso 1, il servo si posiziona tra 0 e 180 gradi.
Nel disegno è illustrato il meccanismo e la tabella di seguito riporta alcuni esempi (ogni costruttore porta delle leggere differenze)
Impulso di controllo | Cavi di collegamento | ||||||
Costruttore | min. | centro. | max | Hz | + batt | -batt | impulso |
Futaba | 0.9 | 1.5 | 2.1 | 50 | rosso | nero | bianco |
Hitech | 0.9 | 1.5 | 2.1 | 50 | rosso | nero | giallo |
Graupner/Jr | 0.8 | 1.5 | 2.2 | 50 | rosso | marrone | arancio |
Multiplex | 1.05 | 1.6 | 2.15 | 40 | rosso | nero | giallo |
Robbe | 0.65 | 1.3 | 1.95 | 50 | rosso | nero | bianco |
Simprop | 1.2 | 1.7 | 2.2 | 50 | rosso | azzurro | nero |
Le caratteristiche
Coppia: la forza che è in grado di esercitare. È espressa in kg/cm e indica quanti chili il servo è in grado di muovere per ogni cm di lunghezza del braccio (a partire dal perno di rotazione)
Velocità di rotazione. Espressa solitamente in gradi (o secondi) al secondo
Angolo di rotazione. Più diffusi servo che ruotano 180, ma ce ne sono che limitano a 90 o estendono a 270 il loro movimento
Tipo di ingranaggi: plastica o metallo. È indice della robustezza
]]>
I componenti utilizzati sono infatti 5 condensatori ed un connettore DB9 per utilizzare la seriale del computer. Nessuna logica, nessun processore, né firmware. Lo scopo è quella di collegare la al computer a scopo di debug o durante la fase di sviluppo o test del firmware, oppure di realizzare un dispositivo di interfacciamento per le realizzazioni che prevedono un funzionamento autonomo e un trasferimento dati o configurazione di parametri.
In questo modo il circuito può lavorare con i livelli TTL, comunicando con altri controllorie può essere costruito senza il MAX232, riducendo quindi di molto l’ingombro (soprattutto per via dei condensatori).
Questa scheda è poi utile come modulo di una demoboard, per cui, una volta realizzata, abbiamo a disposizione un “blocco” del progetto.
Come si può vedere dalla foto, proprio per l’uso non pensato per il dispositivo finale, utilizza l’integrato in versione DIL montato su zoccolo e non la versione SMD.
I problemi da affrontare
La scelta non è dettata esclusivamente dal linguaggio in termini di formalismo e sintattico (altrimenti immagino che tutti andrebbero sul BASIC senza troppi dubbi). Le problematiche da affrontare riguardano
– l’ottimizzazione del codice scritto in termini di risorse impiegate (memoria RAM, Program memory, tempi di esecuzione)
– il controllo diretto delle componenti a basso livello (registri, funzionalità speciali,…)
– il controllo diretto sui tempi di esecuzione necessario quando si lavora in real time
– la gestione della memoria
– la riusabilità del codice scritto e la sua portabilità su controllori differenti
– l’ergonomia nella scrittura del codice
Cosa fa un compilatore
“Il compilatore si occupa di tradurre in codice binario (o codice macchina) il programma scritto nel linguaggio che esso interpreta”. Questo è quello che macroscopicamente si associa al concetto di compilatore. In realtà un buon compilatore fa molto di più, soprattutto in relazione a quanto il linguaggio è “distante” dalla macchina. Si parla di generazione del linguaggio, pensando anche alla reale evoluzione storica che hanno avuto i linguaggi di programmazione
Spesso si può quindi parlare di assembler e codice macchina come se fossero sinonimi.
Si capisce che un linguaggio 3GL porta delle caratteristiche che complicano la traduzione in linguaggio macchina, in particolare, le strutture tipiche di un linguaggio di programmazione:
Siccome questi linguaggi hanno l’obiettivo principale di mascherare al programmatore la gestione delle risorse a basso livello (gestione RAM e program memory), è il compilatore che si occupa di automatizzare la traduzione.
Ragioniamo per esempio alle variabili. Quando scriviamo un programma in assembler utilizziamo genericamente indirizzi di memoria per apoggiare dei valori. Se non creiamo intrecci, possiamo utilizzare la stessa locazione per scopi differenti in rami diversi del programma. Per comodità ci costruiamo una “mappa” per cui ci è chiaro l’utilizzo di ogni porzione di memoria.
In C ci limitiamo a dichiarare una variabile sapendo quale è il suo scope (la sua visibilità). La gestione della memoria (scegliere quale locazione, decidere con chi può essere condivisa, da quale ramo di codice può essere scritta, ….) è tutte ad opera del compilatore.
Ogni compilatore implementa un suo preciso meccanismo per effettuare tutte queste operazioni. È proprio qui la differenza dei compilatori.
Proviamo a semplificare il lavoro di un compilatore:
Per tutte queste gestioni il codice macchina prodotto da un compilatore contiene molte più istruzioni di quelle strettamente necessarie per eseguire le operazioni a basso livello.
Questo è il motivo per cui genericamente si dice “l’assembler è più performante del C”. Ci si riferisce allo strato di funzionalità base che il compilatore introduce, l’overhead prodotto dal compilatore.
Questo è necessario perché un linguaggio 3GL si propone come linguaggio indipendente dalla macchina fisica su cui viene eseguito. Questa “indipendenza” si paga.
Dopo questa semplificata descrizione, riassumo alcune caratteristiche dei due linguaggi:
Linguaggio C
Assembler
Indipendentemente dal linguaggio, scrivendo firmware sui controllori è necessario avere la conoscenza del processore con cui stiamo lavorando. Per applicazioni più semplici e avendo a disposizione una buona libreria, con il C possiamo facilmente cavarcela anche senza conoscere i dettagli dell’hardware
La mia scelta
Innanzi tutto va tenuto conto che da hobbista utilizzo strumenti non professionali. Non ho intenzione di spendere migliaia di euro quando con un po’ di lavoro in più (si tratta sempre di passione) posso ottenere ottimi risultati gratis.
I miei strumenti sono MPLAB ed il compilatore Picc di HI-TECH versione freeware.
A meno di intervenire pesantemente su parametri di configurazione del compilatore, avere a disposizione compilatori con buona ottimizzazione e avere una conoscenza del compilatore più dispendiosa della conoscenza del processore, ci sono molte operazioni che non sono di fatto possibili in C.
Cito un esempio: le routine di trasmissione seriale. Scritte in C e compilate per i controllori PIC baseline funzionano senza problemi. Compilando lo stesso codice su controllori di fascia diversa, il compilatore cambia i criteri di ottimizzazione (che sono poi legati a effettive differenze hardware) e smettono di funzionare. C’è una spiegazione. Nella comunicazione seriale (bit banging) è richiesta una precisa gestione del tempo. L’overhead introdotto dal compilatore nel secondo caso è tale per cui sarebbe richiesta una ricalibrazione dei ritardi introdotti nel codice.
Abbiamo un’alternativa, la stessa scelta che è stata fatta nel mondo dei computer. Ormai a costi bassi possiamo trovare controllori sempre più potenti, che offrono come funzionalità base quasi tutto quello che serve, con memoria pressoché illimitata. Quindi per far lampeggiare un LED scriviamo un semplice programma in C e lo facciamo girare su un controllore 1 32bit dotato di porta USB, Ethernet, con 32k di program memory, 256k memoria RAM, gestione nativa del BUS I2C e SPI, 32 convertitori A/D, …
Forse è un po’ eccessivo? È comunque una possibilità. Con i computer hanno fatto così!
Io preferisco identificare il processore più adatto allo scopo e dimensionarlo correttamente, disegnare il circuito e dimensionare il tutto in base alle effettive esigenze.
Come linguaggio utilizzo insieme il C e l’assembler: ho costruito una buona libreria di funzioni base in assembler che utilizzo chiamandole da funzioni C. Solitamente il main lo scrivo in C, scrivo in C funzioni che non richiedono particolare ottimizzazione, uso le funzioni assembler e sto sempre attento alla percentuale di risorse utilizzate. Quando sforo, mi tocca sempre convertire in assembler alcune delle funzioni C.
Come si può vedere, ho utilizzato il PIC 16F84 come controllore del dispositivo. Questo controlla tramite BUS I2C le tre periferiche attive:
Al controllore è inoltre collegato il MAX232 che si occupa di adattare i livelli per la seriale del computer (converte da TTL del PIC a +/-12v utilizzati dal PC)
Firmware
Il codice che allego è scritto in jal, un linguaggio ormai non più mantenuto. È infatti mia intenzione riscrivere in C (utilizzo il compilatore picc di HI-TECH) tutto il codice.
Ritengo questo progetto interessante anche per la completezza delle periferiche: modificando il programma è possibile utilizzare i dispositivi per qualsiasi altro scopo per cui siano necessari un RTC, un rilevatore di temperatura, un display, la connessione seriale e la EEPROM.
]]>Come si può immaginare questo è il primo metodo: se non abbiamo il trasmettitore da clonare, tramite questo circuito che pilota un trasmettitore radio (Io uso i moduli Quasar) tramite un PIC (il 16F506 nel mio caso). Il PIC genera tutti i codici a 12bit e in meno di 15 minuti li invia tutti (vanno inviati per 3 volte consecutive per la decodifica).
Se il nostro scopo è costruire un radiocomando duplicato del primo, probabilmente è più efficiente interfacciare il circuito ad un computer (per esempio via seriale tramite RS232) per avere indicazione sul codice che viene effettivamente trasmesso.
Se accendo il circuito per strada, l’effetto (in realtà molto divertente) è quello di vedere tutti i cancelli aprirsi, fino a quando non si apre anche il mio.
Il secondo metodo consiste nel realizzare un decodificatore in grado di ricevere il codice inviato dal telecomando e visualizzarlo (possiamo usare un display o la solita seriale). Utilizzando un modulo ricevitore radio collegato al controllore si riesce ad ottenere il valore che deve essere ritrasmesso.
Se il trasmettitore è di quelli con i dip switch si può leggere il codice dalla posizione degli interruttori.
Lo schema pratico ed il codice sono allegati in questo post.
Tutto questo vale solo se lavoriamo con un radiocomando a codifica fissa. I rolling code funzionano in modo diverso, inviando un codice differente per ogni trasmissione.
Il costo di tutto questo è davvero irrisorio: i controllori costano meno di un euro (comprati dal sito Microchip) e i moduli Quasar costano circa 5 euro l’uno.
]]>
I collegamenti del display
(Fare comunque riferimento alla documentazione tecnica del produttore. Nel mio caso si tratta di Hitachi HD44780)
Pin Nome
1 Vss
2 Vdd
3 Contrasto (collegare a massa)
4 RS
5 RW (di solito a massa)
6 E
7 D0 (n.c. per interfacciamento a 4 bit)
8 D1 (n.c. per interfacciamento a 4 bit)
9 D2 (n.c. per interfacciamento a 4 bit)
10 D3 (n.c. per interfacciamento a 4 bit)
11 D4
12 D5
13 D6
14 D7
Dalla tabella vediamo che per utilizzare un collegamento a 4 bit dobbiamo utilizzare i 4 più significativi del display, il register Select (RS) e il pin Enable (E)
Protocollo di comunicazione
In caso di interfacciamento a 8 bit, i dati vanno inviati tutti in una volta
Inserire un ritardo di almeno 2ms tra ogni operazione
Inizializzazione del display
Appena acceso, il display, va inizializzato, inviando i comandi base. Primo fra tutti la selezione del modo di comunicazione a 4 o 8 bit.
La sequenza esatta è visibile dal codice sorgente scaricabile qui
Inviare caratteri al display
Dopo l’inizializzazione è possibile inviare i caratteri trasmettendo il loro valore ASCII.
Il display incrementa automaticamente la posizione, quindi non è necessario fare nulla tra un carattere ed il successivo
Il codice allegato
Il codice che allego l’ho testato con controllori PIC (!6f506, 16F84, 16F689 e 16F887), ma è utilizzabile con tutti i controllori che hanno almeno 6 piedini disponibili.
Purtroppo mi sono trovato in questa necessità e le routine fornite di esempio con il compilatore PICC di Hitech (il compilatore che utilizzo di solito) non sono riuscito a farle funzionare. Non so se perché errate, o se per colpa dell’ottimizzazione che nella versione freeware non è garantita.
Lavorando a basso livello è infatti avere la massima ottimizzazione.
Pazienza: dovendo lavorare con il 16F506 per il mio progetto, le ho dovute riscrivere. Il file i2c.h è scaricabile qui e contiene il codice che implementa le funzioni di base in linguaggio C.
Va notato l’utilizzo del registro TRIS. Essendo un registro in sola scrittura nel 16F506, per valorizzarlo ho utilizzato una variabile di appoggio in cui memorizzo il valore (m_TRIS). Con processori differenti è necessario modificare la gestione del registro. Per questo motivo nel file si trova nella parte iniziale e modificabile agendo sulle definizioni di SCL_IN, SCL_OUT, SDA_IN e SDA_OUT.
Ecco un esempio di come utilizzare la libreria:
i2c_init();
i2c_start();
i2c_tx(0xA2);
i2c_tx(0x02);
i2c_tx(0x00);
i2c_stop();
L’esempio riportato mostra come scrivere un valore nella EEPROM. La lettura avviene in modo analogo:
i2c_start();
i2c_tx(0xA2);
i2c_tx(0x02);
i2c_start();
i2c_tx(0xA3);
Putch (i2c_rx(1));
Putch (i2c_rx(1));
Putch (i2c_rx(0));
i2c_stop();
Questo codice invece legge dalla memoria e trasmette i valori letti sulla seriale utilizzando le librerie RS232 qui descritte
Con questa libreria è possibile quindi controllare tutti i dispositivi I2C (RTC, memorie EEPROM, …)
]]>
Il modulatore
Per la comunicazione viene utilizzato un LED emettitore di infrarossi, un led che emette luce ad una frequenza non visibile. Questa luce viene modulata in modo da trasmettere in sequenza (trasmissione seriale) le informazioni (bit).
Nell’ambiente esistono moltissime fonti di infrarossi (la luce del sole, le lampadine, …) per questo motivo le informazioni vengono solitamente modulate su una portante di qualche decina di kHz (i valori utilizzati vanno da 30 a 60).
Vediamo cosa significa:
La trasmissione viene miscelata alla frequenza della portante. Il risultato è visibile in figura. In questo modo il ricevitore è in grado di ricostruire il segnale originario tramite un filtro passa-banda centrato sulla frequenza della portante. Si eliminano in questo modo disturbi ed interferenze.
A semplificazione del circuito di decodifica sono disponibili in commercio componenti che racchiudono ricevitore e filtro (TSOP12xx, dove xx indica la frequenza della portante. TSOP1236 riceve a 36kHz, TSOP1248 a 48kHz, e così via). Questi ricevitori hanno 3 piedini: +5v, GND e signal out. Quest’ultimo si può collegare direttamente al piedino di ingresso del nostro controllore.
La trasmissione
A questo punto possiamo concentrarci esclusivamente sui dati che vengono trasmessi, pensando al protocollo di comunicazione e a come vengono codificati i simboli (1 e 0). Purtroppo non esiste uno standard universale, ma ogni produttore ha sviluppato il suo.
Mi voglio concentrare in questo articolo sul protocollo adottato da Philips (il mio telecomando), tratterò a parte una raccolta di protocolli differenti. Il protocollo qui descritto è uno dei diversi implementati da Philips. Il nome corretto è RC-5.
Codifica
Il primo elemento da analizzare è come vengono rappresentati i due simboli 1 e 0. è in base a questo che va scritto l’algoritmo per il PIC (assembler o C).
La portante utilizzata è 36kHz
Modulazione:
Protocollo
3 bit di start – 5 bit indirizzamento – 7 bit comando
I primi 3 bit vengono utilizzati per segnalare l’inizio trasmissione: i primi due hanno valore fisso a 1, il terzo cambia ogni volta che viene lasciato e ripremuto un pulsante. Serve per distinguere tra una pressione prolungata dello stesso tasto e due pressioni successive. Questo bit viene chiamato Toggle (T)
L’indirizzamento è utilizzato per identificare il dispositivo da controllare (televisore, videoregistratore, …)
Il comando identifica il tasto premuto.
In figura vediamo come è composta la stringa che vene trasmessa alla pressione di ogni tasto. I valori dell’esempio sono: Indirizzo = 2, comando = 13
Passiamo alla pratica.
Manca solo il codice. L’esempio qui allegato decodifica il telecomando e trasmette sulla seriale (RS232) i due valori di comando e device. Serve come esempio, per comprendere il funzionamento della routine di decodifica. Questa può essere infatti utilizzata per qualsiasi impiego. Il progetto allegato è scritto e compilato per il 12F519, ma il codice è utilizzabile per tutti i PIC baseline. Con la versione free del compilatore picc di HI-TECH (quello che utilizzo io) vanno modificati i tempi per controllori di fascia più alta (l’ottimizzazione non è il massimo). In alternativa va riscritto il codice in assmbler. Per ora l’ho utilizzato solo su questo controllore, ma appena scriverò l’assembler, lo posterò.
Le caratteristiche principali:
L’interfaccia seriale comunica utilizzando il protocollo descritto qui ed implementa i seguenti comandi:
L’utilizzo tipico avviene tramite tastiera, per cui ad ogni tasto premuto viene visualizzata la relativa cifra, partendo da destra con shift verso sinistra. Il comando di lettura del valore visualizzato permette la completa asincronicità tra display e utilizzatore: quando serve viene letto il valore.
Gli ultimi due comandi servono per sfruttare la memoria EEPROM disponibile sul controllore, che per la gestione dei disply non vengono utilizzate. Siccome c’è sempre carenza di memoria, perché non sfruttarla?
Schema elettrico
R1-R4: 1k ohm
R5-R12: 330 ohm
T1-T4: BC517
DIS1-DIS4 Display anodo comune
Il controllore visualizza i valori delle 4 cifre, multiplexando i 4 display, alimentati tramite 4 darlington. L’interfaccia seriale lavora a livelli TTL e sfrutta gli stessi pin utilizzati per la programmazione (ICSP).
Allego il progetto completo MPLAB. Il compilatore utilizzato è il PICC di Hitech
Per implementare altri comandi di deve modificare la funzione
unsigned char interrupt ricezione(void), in particolare intervenendo nel blocco switch, inserendo altri case, riferiti ai comandi da implementare:
...
c = GetCommand();
if (!c) {
switch (read_Buff(2)) {
case 5:
...
ecco ora una parte del codice di un progetto che utilizza il modulo:
c = ReadKey();
switch (c) {
case 0x0a:
sendCommand (0x35, 6, 0);
break;
case 0x1a:
sendCommand (0x35, 7, 0);
break;
case 0x0b:
DelayMs (200);
sendCommand (0x35, 8, 0);
if (getCommand()==0)
sendSlave();
break;
default:
DelayMs (200);
c += 0x30;
sendCommand (0x35, 5, c);
}
La codifica/decodifica viene quasi sempre realizzata utilizzando una coppia di circuiti integrati (HT12E e HT12D o equivalenti), dedicati allo scopo, abbinati ad un trasmettitore e un ricevitore radio.
Il trasmettitore invia un codice composto da 12 bit. I radiocomandi hanno solitamente un dip switch che permette di selezionare il codice. Il codice identico deve essere impostato sul ricevitore, per permettere il riconoscimento.
Radiocomandi a più canali utilizzano una parte dei 12 bit per identificare il canale: per N canali si ha un indirizzo a 12-N bit.
Di seguito lo schema a blocchi tipico del radiocomando:
La velocità di trasmissione solitamente utilizzata è di 1000 baud, la codifica è la seguente:
Avendo queste informazioni siamo quindi in grado di realizzare un radiocomando con codifica e decodifica tramite PIC.
Per la parte radio utilizziamo i moduli trasmettitore e ricevitore della Quasar che permettono con pochi euro di ottenere ottimi risultati. Questi moduli possono essere acquistati presso qualsiasi distributore di componenti elettronici, anche se io preferisco risparmiare ed acquistare direttamente sul sito del produttore.
L’utilizzo dei moduli richiede veramente poco. Entrambi ricevitore e trasmettitore hanno 4 connessioni: positivo e negativo di alimentazione, antenna e dati.
Ecco la routine del trasmettitore (la frequenza di oscillazione da me utilizzata è di 8Mhz).
void TxChar(unsigned char c) {
unsigned char bitno;
INIT_PORT;
bitno = 4;
do {
TxData = 0;
delay_2Us (165);
if(c & 1)
TxData = 0;
else
TxData = 1;
c = (c >> 1) | 0x80;
delay_2Us (164);
TxData = 1;
delay_2Us (165);
} while(--bitno);
}
Come si vede dal codice, la routine effettua la trasmissione dei soli 4 bit meno significativi del parametro in ingresso. Questo per maggiore comodità, essendo a 12 bit il codice che deve essere trasmesso.
Il ricevitore è un po’ più complesso, a causa dei disturbi tipici del canale radio. Ho quindi implementato una sorta di macchina a stati finiti con due stati: in attesa e in ricezione:
int RxRadio(void) {
unsigned char bitno, tmp, width, w0;
state0:
width = 0;
d1 = 0;
d2 = 0;
bitno = 0;
while(!RxDt)
continue; /* wait for start bit */
while (RxDt) {
delay_2Us(4);
width++;
if (width > 45)
goto state0;
}
if (width < 30)
goto state0;
state1:
width = 0;
w0 = 0;
tmp = 1;
while (!RxDt){
delay_2Us(4);
w0++;
if (w0 > 90)
goto state0;
}
while (RxDt) {
delay_2Us(4);
width++;
if (width > 90)
goto state0;
}
if (width < 20)
goto state0;
if (width > w0)
tmp = 0;
if (bitno > 7)
d1 = (d1 >> 1) | (tmp << 7);
else
d2 = (d2 >> 1) | (tmp << 7);
bitno ++;
if (bitno == 12)
return width;
goto state1;
}
Per ridurre problemi derivanti da disturbi o errori di ricezione, il decodificatore si aspetta di ricevere gli stessi dati per tre volte consecutive. In caso contrario non effettua la decodifica. La trasmissione avviene quindi correttamente se chiamiamo 3 volte con gli stessi valori la TxChar.
Di seguito un esempio che illustra come poter effettuare la trasmissione di tutte le combinazioni possibili in sequenza, utile se abbiamo perso il telecomando e vogliamo individuare il codice per duplicarlo.
void InitTx(void) {
TxData = 1;
delay_2Us (165);
TxData = 0;
}
void EndTx (void) {
TxData = 0;
DelayMs(10);
}
...
for (j=0; j<16; j++)
for (i=0; i<256; i++)
for (t=0; t<4; t++) {
InitTx();
TxChar (i);
TxChar (i>>4);
TxChar (j);
EndTx();
}
for (;;);
va considerato che la trasmissione completa dura:
(1000 baud x 12 bit + 10 ms + 330 us) x 3 volte = 67 ms per ogni codice trasmesso
2^12 combinazioni x 36 ms = 4096 x 67 ms = 274,432 s = 4,57 minuti
]]>