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 pre- fetch) 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 - Aggiunge allo sketch la libreria WProgram.h che contiene tutte le definizioni delle funzioni necessarie alla propria board Arduino per poter funzionare 1 - L'ambiente di sviluppo cerca le definizioni delle funzioni scritte dall'utente e ne crea i prototipi, cioè le dichiarazioni 2 - Il preprocessore inserisce tutti i prototipi di funzione ed i vari statements #include prima del blocco di definizione delle variabili o dei tipi strutturati. 3 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: const unsigned int LED_PIN = 13; const unsigned int PAUSE = 500; La procedura setup() è utile per permettere void setup() { l'inizializzazione di pinMode(LED_PIN,OUTPUT); alcune funzioni tipiche } di Arduino void loop(){ digitalWrite(LED_PIN, HIGH); La procedura loop() delay(PAUSE); gestisce il ciclo principale digitalWrite(LED_PIN, LOW); del programma delay(PAUSE); } Questa funzione imposta l'intervallo di tempo da attendere 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 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> Includo la libreria Wire.h unsigned char outbuf[6]; Buffer dove verrà contenuto il pacchetto proveniente dal nunchuk unsigned char joy_x_axis; unsigned char joy_y_axis; int cnt = 0; int z_button = 0; int c_button = 0; int dtime = 1000; TwoWire link; void setup(){ Serial.begin (9600); link.begin (); nunchuck_init (); Serial.print ("Finished setup\n"); } Oggetto istanza della classe TwoWire definita all'interno della libreria Wire.h Inizializzazione programma Metodo che inizializza la comunicazione tra master e slave Leggere dal Nunchuk (2/5) void nunchuck_init(){ Procedura che avvia e link.beginTransmission (0x52); gestisce l'handshake tra link.send (0x40); arduino ed il nunchuk link.send (0x00); link.endTransmission (); } void send_zero(){ Procedura che richiede al nunchuk di trasmettere un link.beginTransmission (0x52); altro pacchetto dati link.send (0x00); link.endTransmission (); } void loop(){ Loop principale del link.requestFrom (0x52, 6); programma che si occupa while (link.available ()) { di decodificare il pacchetto outbuf[cnt] = decode(link.receive()); proveniente dal nunchuk cnt++; } cnt = 0; send_zero(); printNunchuckData(); delay(dtime); } Leggere dal Nunchuk (3/5) unsigned char decode ( unsigned char b ){ Funzione di decodifica di return ( b ^ 0x17 ) + 0x17; ogni byte proveniente dal } nunchuk void printNunchuckData(){ Procedura di stampa dei joy_x_axis = outbuf[0]; valori letti dal nunchuk 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; 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; case 2: c_button = 1; z_button = 0; break; case 3: c_button = 1; z_button = 1; break; } Lettura dei bit rappresentativi dello stato dei pulsanti presenti sul nunchuk 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(){ link.begin (); nunchuck_init (); Motor1.attach(9); } void nunchuck_init(){ ... } void send_zero(){ ... } Inclusione delle librerie Servo.h e Wire.h per il controllo rispettivamente del servomotore e nunchuk Istanza classi TwoWire e Servo Inizializzazione del nunchuk e collegamento software del servomotore al pin 9 dell'arduino Controllare il servomotore (2/3) void loop(){ Ricezione e decodifica di link.requestFrom (0x52, 6); ogni byte ricevuto dal nunchuk while (link.available ()) { outbuf[cnt] = decode(link.receive()); cnt++; } cnt = 0; send_zero(); Avvio della procedura per il controllo del servomotore control(); delay(dtime); } unsigned char decode ( unsigned char b ){ 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 [email protected] Riferimenti Riferimenti: www.arduino.cc ● Datasheet ATMega328P ● http://it.wikipedia.org ● www.atmel.com ● Luciano Esposito [email protected]