PIT 2012: Workshop@UniNA Arduino: Open Hardware - a cura di Luciano Esposito - con il patrocinio del Preside della Facoltà di Ingegneria dell'università degli Studi di Napoli Federico II: Prof. Piero Salatino e con il sostengo del Prof. Antonio Pescapè Comics Unina Research Group
Indice Argomenti trattati Cos'è Arduino e com'è fatto IDE e processo di sviluppo Programmazione su Arduino Il protocollo I2C Controllo di dispositivi esterni Propedeuticità Buona conoscenza del C/C++ Rudimenti di architettura dei calcolatori Un po' di elettronica di base
Cos'è Arduino? È una piattaforma di sviluppo software e fornisce un supporto per la prototipazione rapida di hardware È compatibile con tutti i sistemi operativi Lo sviluppo software si basa su un semplice ed intuitivo IDE (Integrated Develpment Environment) È controllabile tramite USB Gli schemi elettrici ed il codice sorgente degli applicativi a supporto (IDE, compilatore, programmatore, ecc.) sono disponibili in rete: open hardware e open software Progetto COMPLETAMENTE italiano
Com'è fatto? Il cuore principale di Arduino è il chip Atmega della Atmel Possiede numerosi slot di I/O sia analogici sia digitali Dispone di due slot da 3,3V e 5V per alimentare circuiti esterni Utilizza un connettore USB per alimentazione e/o controllo, ed un connettore standard per l'alimentazione
Il microcontrollore Atmega 328P All'interno del chip Atmega non è presente solo un processore ma un intero sistema di elaborazione che più tecnicamente è detto SoC (system-on-a-chip). Troveremo infatti oltre alla CPU anche: EEPROM (Electrically Erasable Programmable Read-Only Memory) memoria non volatile Memoria RAM Registri e dispositivi I/O Dispositivo per la comunicazione seriale (USART) Oscillatore Convertitori A/D e D/A (SAR e DAC ) Ecc...
La CPU AVR È di tipo RISC (Reduced Instruction Set Computer). Istruzioni a lunghezza variabile con PC (Program Counter) a 14 bit Utilizza un'architettura che separa la memoria istruzioni dalla memoria dei dati (tipo Harvard) Possiede una pipeline a singolo stadio (solo prefetch) capace di eseguire una singola istruzione per periodo di clock Gestisce le interruzioni Possiede 32 registri ad 8 bit general purpose Possiede lo stack register ed un set di istruzioni che ne permettono la gestione Cinque modi di indirizzamento (Diretto, Indiretto, Indiretto con offset, Indiretto con pre e post incremento)
Le memorie AVR (1/2) Il microcontrollore ATMega 328P possiede due spazi di memoria fondamentali: la memoria istruzioni (Memory Flash) e la memoria dati (SDRAM) Memoria Istruzioni è organizzata in due spazi: Boot Flash Section (solo read) e Application Flash Section (capacità di 32 Kbyte ma con un parallelismo di 16 bit) Memoria Dati è organizzata secondo l'indirizzo di memoria: Indirizzi da 0x0000 a 0x001F accedono ai 32 registri general purpose Indirizzi da 0x0020 a 0x005F accedono a 64 registri I/O Indirizzi da 0x0060 a 0x00FF accedono 160 Extended register I/O Indirizzi da 0x0100 a 0x08FF accedono alla SRAM interna (capacità 2Kbyte)
Le memorie AVR (2/2) È presente all'interno del chip anche una memoria EEPROM da 1KByte accessibile mediante 3 registri utilizzando particolari istruzioni Registro EEAR (EEPROM Address Register) a 16 bit Registro EEDR (EEPROM Data Register) a 8 bit Registro EECR (EEPROM Control Register) a 8 bit
I dispositivi I/O (1/2) Sono presenti all'interno del chip diversi dispositivi per gestire i vari I/O: 8-bit e 16-bit Timer/Counter con PWM Si tratta di dispositivi in grado di generare un segnale utilizzando la tecnica PWM (Pulse-Width Modulation) che consente di ottenere una tensione media variabile che dipende dalla durata e dall'ampiezza di ogni singolo impulso. Tale dispositivo utilizza un contatore a 8 o 16 bit per ottenere tale tensione. USART (Universal Synchronous and Asynchronous serial Receiver and Transmitter) Tale dispositivo converte flussi di bit di dati da un formato parallelo a un formato seriale asincrono (o sincrono) o viceversa. Può operare sia in modalità slave sia in modalità master
I dispositivi I/O (2/2) Convertitore A/D a 10 bit Tale dispositivo permette di trasformare un segnale analogico in un segnale numerico. Il microcontrollore usa un convertitore a successive approssimazioni (SAR) con un circuito del tipo Sample and Hold per mantenere costante il livello di tensione acquisito. La tensione di riferimento interna è di 1,1V altrimenti può essere impostata o dall'esterno mediante il pin AREF (Analog REFerence) o utilizzando la tensione che Arduino può erogare a circuiti esterni (5V o 3,3V) 2-wire Serial Interface Si tratta di un dispositivo capace di connettere al microcontrollore ulteriori dispositivi, fino ad un massimo di 128, utilizzando solo due fili uno per i dati (SDA) ed uno per il clock (SCL)
Ulteriori dispositivi Dispositivi per la generazione e gestione di più clock Gestione dell'alimentazione e risparmio energetico (sleep mode, idle mode, power-save mode, ecc...) Timer watchdog Dispositivi per il controllo delle cause di reset, registri per il controllo dei timer watchdog, ecc... Vettore per il controllo delle interruzioni interne ed esterne debugwire on-chip sistema che utilizza un'interfaccia bidirezionale per controllare il flusso del programma e per scrivere all'interno delle memorie non volatili (EEPROM e FLASH)
Il ciclo di sviluppo SW (1/3) Il nostro programma (o sketch) deve passare attraverso tre fasi prima di poter essere eseguito su Arduino. Similmente al processo di sviluppo di un programma in C/C++ le fasi sono: Pre-proccessing Compilazione e collegamento Caricamento sulla board
Il ciclo di sviluppo SW (2/3) L'ambiente di sviluppo di Arduino compie alcune trasformazioni allo sketch prima di compilarlo. Fase 1: Pre-proccessing 1 - Aggiunge allo sketch la libreria WProgram.h che contiene tutte le definizioni delle funzioni necessarie alla propria board Arduino per poter funzionare 2 - L'ambiente di sviluppo cerca le definizioni delle funzioni scritte dall'utente e ne crea i prototipi, cioè le dichiarazioni 3 - Il preprocessore inserisce tutti i prototipi di funzione ed i vari statements #include prima del blocco di definizione delle variabili o dei tipi strutturati.
Il ciclo di sviluppo SW (3/3) Fase 2: Compilazione e collegamento Lo sketch così completo è compilato tramite i compilatori avr-gcc e avr-g++ secondo l'architettura e le esigenze della propria board Arduino. Si generano così file con estensione.o (file oggetto) che vengono successivamente collegati dal linker, insieme alle librerie, arrivando infine ad un unico file con estensione.hex Fase 3: Caricamento sulla board Il file.hex, risultato finale della compilazione, viene caricato dal programma avrdude sulla board. Il processo di caricamento viene eseguito controllando alcune variabili di configurazione tipiche della board sulla quale si dovrà caricare lo sketch (nome della board, tipo di microcontrollore, tipo di bootloader, ecc..)
L'IDE di sviluppo Tutte le fasi viste in precedenza sono trasparenti all'utente e vengono effettuate dall'ide (Integrated Develpment Environment) di Arduino Possiede un'interfaccia semplice ed intuitiva ed è disponibile per tutti i sistemi operativi. Applicazione open source
Programmare su Arduino Analizziamo un semplice esempio: La procedura setup() è utile per permettere l'inizializzazione di alcune funzioni tipiche di Arduino La procedura loop() gestisce il ciclo principale del programma const unsigned int LED_PIN = 13; const unsigned int PAUSE = 500; void setup() { pinmode(led_pin,output); } void loop(){ } digitalwrite(led_pin, HIGH); delay(pause); digitalwrite(led_pin, LOW); delay(pause); Questa funzione imposta il pin 13 sulla scheda Arduino come output elettronico Queste funzioni permettono di impostare il valore alto o basso (5V) sul pin 13 mediante il tag HIGH e LOW Questa funzione imposta l'intervallo di tempo da attendere
Il protocollo I2C (1/2) Protocollo di comunicazione seriale di tipo master/slave con bus sincrono Sono presenti due linee di comunicazione dati: SDA (Serial DAta line) per i dati SCL (Serial Clock Line) per il clock Vi è inoltre la presenza di due linee di servizio: GND o massa Vdd alimentazione L'indirizzamento dei dispositivi avviene mediante indirizzi a 7 bit per un totale di 128 dispositivi di cui, però, 16 riservati scendendo ad un massimo di 112 effettivamente controllabili
Il protocollo I2C (2/2) Un bus I2C per poter operare ha bisogno di due nodi: Nodo master: dispositivo che emette il segnale di clock Nodo slave: nodo che si sincronizza sul segnale di clock senza poterlo controllare In generale ci sono 2 modi distinti di operare sia per il master che per lo slave (non mutuamente esclusivi durante una sessione di comunicazione): Un master trasmette, controlla il clock e invia dati agli slave Un master riceve, controlla il clock ma riceve dati dallo slave Lo slave trasmette, il dispositivo non controlla il clock ma invia dati al master Lo slave riceve, il dispositivo non controlla il clock e riceve dati dal master
Il nunchuk Wii (1/3) Il nunchuk wii dispone di: Un accelerometro su 3 assi (X, Y, Z) Una levetta capace di fornire la propria posizione rispetto agli assi X ed Y Due pulsanti Z e C Utilizza il protocollo I2C per comunicare con la Wii lo stato dei pulsanti, la posizione della levetta e i valori dell'accelerometro
Il nunchuk Wii (2/3) L'unica operazione che un dispositivo master può effettuare con il nunchuk wii è la lettura dei valori dei vari sensori e dei dispositivi presenti nel controller La lettura avviene quando il master richiede tale operazione, leggendo un pacchetto dati di 6 byte proveniente dal controller
Il nunchuk Wii (3/3) Il byte 0 contiene il valore dell'asse X della levetta mentre il byte 1 contiene il valore dell'asse Y. Variano tra 29 e 228 (circa) e sono valori interi senza segno ad 8 bit I valori di accelerazione lungo gli assi X,Y e Z sono numeri a 10 bit. I byte 2, 3 e 4 contengono gli 8 bit più significativi, mentre i due bit meno significativi sono contenuti all'interno del byte 5 Il byte 5 è suddiviso in gruppi di uno o più bit. Il bit 0, meno significativo, contiene lo stato del pulsante Z (0 se è premuto altrimenti vale 1). Il bit 1 contiene invece lo stato del pulsante C con medesima semantica del bit precedente. I restanti bit, come già detto, contengono i bit meno significativi dei valori di accelerazione
Collegamento con Arduino Collegamento fisico: Il plug del nunchuk possiede 6 connettori ma solo 4 di questi sono utilizzati. Il connettore Data andrà collegato al pin analogico 4 (A4) Il connettore Clock andrà collegato al pin analogico 5 (A5) Il connettore 3,3V al rispettivo pin su Arduino Il connettore GND al pin di massa su Arduino Il collegamento software verrà effettuato mediante l'interfaccia Wire disponibile con l'ide di Arduino
L'interfaccia Wire È una classe che consente la comunicazione mediante il protocollo I2C con il dispositivo 2-wire Serial Interface I principali metodi sono: begin() o begin(address) Inizializza la classe Wire ed il bus I2C begintrasmission(address) Comincia una comunicazione con un dispositivo slave avente l'indirizzo address endtrasmission() - Termina una comunicazione con un dispositivo slave send(byte) Invia un byte al dispositivo slave [vedi anche write()] requestfrom(address, count) Usato dal dispositivo master per richiedere count byte al dispositivo slave receive() - legge i byte ricevuti dal dispositivo slave [vedi anche read()]
Leggere dal Nunchuk (1/5) #include <Wire.h> unsigned char outbuf[6]; unsigned char joy_x_axis; unsigned char joy_y_axis; int cnt = 0; int z_button = 0; int c_button = 0; int dtime = 1000; Includo la libreria Wire.h Buffer dove verrà contenuto il pacchetto proveniente dal nunchuk Oggetto istanza della classe TwoWire definita all'interno della libreria Wire.h TwoWire link; void setup(){ Serial.begin (9600); link.begin (); nunchuck_init (); Serial.print ("Finished setup\n"); } Inizializzazione programma Metodo che inizializza la comunicazione tra master e slave
Leggere dal Nunchuk (2/5) void nunchuck_init(){ link.begintransmission (0x52); link.send (0x40); link.send (0x00); link.endtransmission (); } void send_zero(){ link.begintransmission (0x52); link.send (0x00); link.endtransmission (); } void loop(){ link.requestfrom (0x52, 6); while (link.available ()) { outbuf[cnt] = decode(link.receive()); cnt++; } cnt = 0; send_zero(); printnunchuckdata(); delay(dtime); } Procedura che avvia e gestisce l'handshake tra arduino ed il nunchuk Procedura che richiede al nunchuk di trasmettere un altro pacchetto dati Loop principale del programma che si occupa di decodificare il pacchetto proveniente dal nunchuk
Leggere dal Nunchuk (3/5) unsigned char decode ( unsigned char b ){ return ( b ^ 0x17 ) + 0x17; } void printnunchuckdata(){ joy_x_axis = outbuf[0]; joy_y_axis = outbuf[1]; unsigned int accel_x_axis = ( (unsigned int) (outbuf[2] << 2) ( outbuf[5] >> 2 & 0x03) ); unsigned int accel_y_axis = ( (unsigned int) (outbuf[3] << 2) ( outbuf[5] >> 4 & 0x03) ); unsigned int accel_z_axis = ( (unsigned int) (outbuf[4] << 2) ( outbuf[5] >> 6 & 0x03) ); unsigned char z_button = 0; unsigned char c_button = 0; Procedura di stampa dei valori letti dal nunchuk Funzione di decodifica di ogni byte proveniente dal nunchuk
Leggere dal Nunchuk (4/5) switch ( outbuf[5] & 0x03 ) { case 0: c_button = 0; z_button = 0; break; case 1: c_button = 0; z_button = 1; break; Lettura dei bit rappresentativi dello stato dei pulsanti presenti sul nunchuk case 2: c_button = 1; z_button = 0; break; } case 3: c_button = 1; z_button = 1; break;
Leggere dal Nunchuk (5/5) } Serial.print("Asse X: "); Serial.print(outbuf[0],DEC); Serial.print("\t"); Serial.print("Asse Y: "); Serial.print(outbuf[1],DEC); Serial.print("\t"); Serial.print("Acc X: "); Serial.print(outbuf[2],DEC); Serial.print("\t"); Serial.print("Acc Y: "); Serial.print(outbuf[3],DEC); Serial.print("\t"); Serial.print("Acc Z: "); Serial.print(outbuf[4],DEC); Serial.print("\t"); Serial.print("Button Z: "); Serial.print(z_button,DEC); Serial.print("\t"); Serial.print("Button C: "); Serial.print(c_button,DEC); Serial.print("\t"); Serial.print("\n"); Stampa sul monitor di tutti i valori (come interi positivi in base dieci) provenienti dal nunchuk mediante la classe Serial
Servomotori Particolare tipo di motore generalmente di piccola potenza, le cui condizioni operative, a differenza dei motori tradizionali, sono soggette ad ampie e spesso repentine variazioni sia nel campo della velocità che della coppia motrice, alle quali si deve adattare con la massima rapidità e precisione. Da un servomotore elettrico è generalmente richiesta bassa inerzia, elevata linearità tensione/velocità e corrente/coppia, rotazione (o traslazione) uniforme con bassa oscillazione di coppia angolare (o forza) senza posizioni preferenziali e capacità di sopportare picchi impulsivi di potenza. Possiede già all'interno tutta l'elettronica e la meccanica necessaria alla movimentazione e al controllo.
Collegamento e controllo Collegare un servomotore all'arduino è molto semplice. Di norma tali dispositivi prevedono tre cavi. Due di questi sono di alimentazione e vanno quindi collegati ai rispettivi slot della board (5V e GND). Il terzo cavo è solitamente usato per il controllo. Il controllo di un servomotore può essere effettuato collegando il cavo di controllo all'uscita PWM dell'arduino gestito dalla libreria, fornita dall'ide, Servo.h. La movimentazione sarà gestita mediante un apposito metodo che posizionerà il servomotore ad una specifica angolazione definita via software dal programmatore.
Controllare il servomotore (1/3) #include <Wire.h> #include <Servo.h>... TwoWire link; Servo Motor1; void setup(){ Istanza classi TwoWire e Servo Inclusione delle librerie Servo.h e Wire.h per il controllo rispettivamente del servomotore e nunchuk } link.begin (); nunchuck_init (); Motor1.attach(9); Inizializzazione del nunchuk e collegamento software del servomotore al pin 9 dell'arduino void nunchuck_init(){... } void send_zero(){... }
Controllare il servomotore (2/3) void loop(){ link.requestfrom (0x52, 6); while (link.available ()) { outbuf[cnt] = decode(link.receive()); cnt++; } Ricezione e decodifica di ogni byte ricevuto dal nunchuk cnt = 0; send_zero(); control(); delay(dtime); } unsigned char decode ( unsigned char b ){... } Avvio della procedura per il controllo del servomotore Funzione di decodifica
Controllare il servomotore (3/3) void control() { } unsigned char pos; pos = outbuf[0]; Motor1.write(pos); Lettura della posizione della levetta del nunchuk e posizionamento del servomotore NB. Questo piccolo esempio permette la semplice movimentazione del servomotore, ma non tiene conto di vari effetti di disturbo che potrebbero verificarsi muovendo la levetta del nunchuk. Ci sarebbe bisogno di una funzione che eliminasse i vari disturbi... lascio a voi il completamento del programma ^_^
Ringraziamenti Un sentito ringraziamento a chi ha permesso lo svolgersi di tutto questo: al Preside della Facoltà di Ingegneria dell'università degli Studi di Napoli Federico II: Prof. Piero Salatino e al Prof. Antonio Pescapè, nostro eterno supporter Ai ragazzi dell'associazione NaLug Napoli GNU/Linux Users Group http://www.nalug.net info@nalug.net
Riferimenti Riferimenti: www.arduino.cc Datasheet ATMega328P http://it.wikipedia.org www.atmel.com Luciano Esposito luhck88@gmail.com