Physical Computing: a case study
Matteo Pessina
[email protected]
matricola 759315
Relatore: Andrea Trentini
[email protected]
Università degli Studi, Milano
Facoltà di scienze Matematiche, Fisiche e Naturali
Corso di laurea in Informatica
Anno accademico
2011-2012
Abstract
Questo documento mostra nel dettaglio il percorso seguito per
la realizzazione di un progetto riconducibile, per la maggior parte,
all’area del physical computing. Si parte introducendo il test di Hess/Lancaster, di ambito ortottico, andando a sottolineare i requisiti che
il dispositivo vuole soddisfare. Si suddivide il progetto in moduli indipendenti e si descrive la realizzazione di questi, suddividendo ancora
hardware e software. Si tenta di affrontare il tema della calibrazione
del dispositivo raggiungendo un modello matematico teorico corretto
ma non riuscendo ad ottenere risultati tanto precisi nella pratica. Si riformulano i requisiti e si conclude il progetto delegando la calibrazione
a terzi.
1
Contents
1 Introduzione
1.1 Test di Lancaster-Hess: finalità e modalità . . . . . . . . .
1.1.1 Contesto . . . . . . . . . . . . . . . . . . . . . . . .
1.1.2 Esecuzione del test . . . . . . . . . . . . . . . . . .
1.1.3 Hess, Lancaster: modalità comuni e differenze . . .
1.1.4 Scopo . . . . . . . . . . . . . . . . . . . . . . . . .
1.2 Formulazione dei requisiti e modularizzazione del progetto
1.2.1 Requisiti . . . . . . . . . . . . . . . . . . . . . . . .
1.2.2 Suddivisione in moduli . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
4
4
4
4
5
6
7
7
8
2 Realizzazione dell’hardware
2.1 Scelte di progetto . . . . . . . . . . . . . . . . . .
2.2 Arduino . . . . . . . . . . . . . . . . . . . . . . .
2.2.1 Che cos’è e perchè è ”bello” . . . . . . . .
2.2.2 Panoramica . . . . . . . . . . . . . . . . .
2.2.3 Arduino IDE e la programmazione dei chip
2.3 Servomotori . . . . . . . . . . . . . . . . . . . . .
2.3.1 PWM . . . . . . . . . . . . . . . . . . . .
2.3.2 Leggere il datasheet . . . . . . . . . . . . .
2.3.3 Installazione . . . . . . . . . . . . . . . . .
2.4 Laser e altoparlanti . . . . . . . . . . . . . . . . .
2.4.1 Laser . . . . . . . . . . . . . . . . . . . . .
2.4.2 Transistor . . . . . . . . . . . . . . . . . .
2.4.3 Altoparlante . . . . . . . . . . . . . . . . .
2.5 Wii Nunchuck . . . . . . . . . . . . . . . . . . . .
2.6 Schema circuitale completo . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
9
9
9
9
10
12
12
13
14
15
16
16
16
16
17
17
.
.
.
.
.
.
.
.
.
.
.
20
20
20
21
23
25
26
28
28
29
30
31
3 Realizzazione del software
3.1 Drivers . . . . . . . . . . . . . . . . . . .
3.1.1 Introduzione . . . . . . . . . . . .
3.1.2 Servomotori . . . . . . . . . . . .
3.1.3 Nunchuck . . . . . . . . . . . . .
3.1.4 Altoparlante . . . . . . . . . . . .
3.2 La comunicazione seriale . . . . . . . . .
3.3 La calibrazione e i metodi di conversione
3.3.1 Premessa . . . . . . . . . . . . .
3.3.2 Il problema . . . . . . . . . . . .
3.3.3 Interpolazione . . . . . . . . . . .
3.3.4 Approccio trigonometrico . . . .
2
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. . . .
. . . .
. . . .
. . . .
AVR .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
3.4
4 R
4.1
4.2
3.3.5 Problemi e riformulazione dei requisiti . . . . . .
3.3.6 I risultati . . . . . . . . . . . . . . . . . . . . . .
3.3.7 Approccio per fasce . . . . . . . . . . . . . . . . .
Lo sketch finale . . . . . . . . . . . . . . . . . . . . . . .
3.4.1 Inclusioni, macro, allocazioni statiche permanenti
3.4.2 Setup . . . . . . . . . . . . . . . . . . . . . . . .
3.4.3 Il controller dell’applicazione . . . . . . . . . . . .
3.4.4 La comunicazione . . . . . . . . . . . . . . . . . .
3.4.5 Calibrate . . . . . . . . . . . . . . . . . . . . . .
3.4.6 Wait for user . . . . . . . . . . . . . . . . . . . .
3.4.7 Wait for nunchuck . . . . . . . . . . . . . . . . .
3.4.8 Execute . . . . . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
33
34
36
38
38
40
40
42
43
44
44
45
47
Perchè R . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
Creare un ponte tra R e Arduino . . . . . . . . . . . . . . . . 47
5 Installazione e tutorial
5.1 Prerequisiti . . . . . . . . . . . .
5.2 RXTX . . . . . . . . . . . . . . .
5.2.1 OSX . . . . . . . . . . . .
5.2.2 Linux . . . . . . . . . . .
5.2.3 Windows . . . . . . . . . .
5.2.4 Verifica . . . . . . . . . .
5.3 ArduBridge . . . . . . . . . . . .
5.3.1 OSX e Linux . . . . . . .
5.3.2 Windows . . . . . . . . . .
5.3.3 Verifica . . . . . . . . . .
5.4 Compilazione Sketch . . . . . . .
5.4.1 OSX . . . . . . . . . . . .
5.4.2 Linux . . . . . . . . . . .
5.4.3 Windows . . . . . . . . . .
5.5 Tutorial per l’uso . . . . . . . . .
5.6 Troubleshooting . . . . . . . . . .
5.6.1 Could not find COM port
6 Conclusioni
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
53
53
53
53
54
54
54
55
55
55
55
56
56
56
56
56
58
58
58
3
1
1.1
1.1.1
Introduzione
Test di Lancaster-Hess: finalità e modalità
Contesto
Il movimento degli occhi rispetto al cranio è garantito da 12 muscoli, 6 per
lato, che prendono il nome di muscoli extrabulbari. Nella figura 1 possiamo
vederne uno schema. Non è semplice diagnosticare un malfunzionamento o
Figure 1: Schema muscoli occhi. Il Retto Interno (RI) è adduttore. Il Retto
Esterno (RE) abduttore. Il Retto Superiore (RS) è Elevatore. Il Retto
Inferiore (RI) è abbassatore. Il Grande Obliquo (GO) è ruotatore interno. Il
Piccolo Obliquo (PI) è ruotatore esterno.
la paralisi di questi, infatti, in caso di deficit, gli altri muscoli tendono a
compensare la disfunzione. Al fine di valutare correttamente quale muscolo
è in deficit e di quanto, in ortottica, vengono usati di preferenza il test di
Hess e quello di Lancaster dei quali segue una breve descrizione.
1.1.2
Esecuzione del test
Il paziente indossa un paio di occhiali con una lente verde e una rossa ed
impugna una torcia, emanante un fascio di luce verde, mentre l’operatore
(medico ortottista) impugna una torcia con luce rossa. L’operatore posiziona
lo spot della sua torcia su una serie di punti predefiniti a griglia sullo schermo
(di Hess o Lancaster), il paziente deve sovrapporre lo spot della sua torcia
a quella dell’operatore. Lo scarto (errore) del paziente viene riportato manualmente dall’operatore su una scheda che riproduce in piccolo la griglia.
4
Figure 2: Schermo di Hesse. Questo è il prospetto dove il medico segna i
punti mirati dal paziente. Uno schema per occhio.
Terminata la prima serie di punti s’invertono le torce o le lenti degli occhiali (rosso al posto di verde) in modo da testare l’occhio controlaterale.
La diagnosi viene effettuata analizzando l’insieme di tutti gli scarti effettuati
dal paziente. A seconda della disposizione della griglia dei punti-test sullo
schermo, l’esame prende il nome di test di Hess o di Lancaster.
1.1.3
Hess, Lancaster: modalità comuni e differenze
Il test si effettua generalmente con una distanza tra il paziente e lo schermo
di 50cm. Il paziente inoltre ha la testa appoggiata su un apposito sostegno
che la posiziona sulla normale al centro dello schermo. La differenza tra il
test di Hess e quello di Lancaster si vede nelle figure 2, 3: nella prima la
distanza tra i punti è il risultato della riduzione della sfera avente raggio 50
cm su un piano bidimensionale. Solo nella sfera i punti sono equidistanti. Nel
secondo invece i punti hanno la medesima distanza sul piano. Nella figura 3
viene mostrato il risultato del test e la relativa diagnosi.
5
Figure 3: Esempio di prospetto compilato su schermo di Lancaster. La
diagnosi in questo caso è miopia unilaterale.
1.1.4
Scopo
Il test, per come è effettuato ora, ha il limite di calcolare lo scostamento per
via empirica, introducendo l’errore umano. Inoltre l’uso di uno schermo fisico
rende l’apparato piuttosto ingombrante. Scopo di questo tirocinio è quello
di creare, in collaborazione con il dottor Mario Cigada (www.mariocigada.
com), un dispositivo in grado di effettuare il test in maniera automatica,
aumentando quindi la precisione delle misure sullo scostamento del puntatore
del paziente e rendendo il sistema più versatile, in quanto in grado di eseguire
indifferentemente il test di Hess, quello di Lancaster o uno “custom”.
6
1.2
1.2.1
Formulazione dei requisiti e modularizzazione del
progetto
Requisiti
1. Il dispositivo deve fornire una chiara interfaccia al linguaggio R in esecuzione su una macchina collegata ad esso. Questa interfaccia deve
dare la possibilità all’operatore di eseguire il test con diversi parametri
e di riceverne i risultati.
2. Il dispositivo deve avere due puntatori laser, rosso e verde, che, date
delle coordinate piane, possano mirare un qualsiasi punto di uno schermo
verticale. Con coordinate piane si intende una coppia (x, y) di valori
che esprimano la distanza di un punto p obiettivo da un riferimento
preimpostato sullo schermo.
3. Il dispositivo deve essere in grado di eseguire il test di Hess-Lancaster:
ricevuta una sequenza di punti espressi in coordinate piane, inizia a mirare il primo punto con uno dei due laser lasciando il controllo dell’altro
al paziente. Il dispositivo dovrà quindi attendere un segnale di conferma
da parte di questo, registrare quindi le coordinate piane del punto mirato, emettere un suono e procedere con il punto successivo. Finita la
lista dei punti deve emettere un suono diverso dal precedente e ricominciare il test scambiando il controllo dei laser. Finito il test, deve restituire due sequenze di punti rappresentanti quelli mirati dal paziente.
4. Uno dei due laser deve essere controllato in modo semplice dal paziente.
Il nostro scopo non sarà quindi di occuparci dell’interezza del progetto ma
solamente della parte che riguarda il dispositivo ed il software relativo ad
esso. Le ulteriori valutazioni sui dati grezzi, la creazione del prospetto e la
relativa diagnosi saranno a carico del medico collaboratore.
7
1.2.2
Suddivisione in moduli
Ecco una rappresentazione ad albero dei moduli e dei sottomoduli del progetto.
Project
Dispositivo
Realizzazione HW
Sistema di puntamento
Controllo paziente
Realizzazione SW
Gestione delle periferiche
Calibrazione e traduzione sistemi di coordinate
Progetto del codice principale
Interfaccia R
Comunicazione Seriale
Accesso interfaccia dispositivo
Realizzazione funzioni utili elaborazione dati
L’approccio blackbox scelto richiede quindi di identificare i moduli principali
e di definire delle chiare interfacce per essi. Le due black box saranno il
Dispositivo e l’Interfaccia R. Il primo sarà accessibile dall’esterno tramite
seriale e attenderà una stringa che specifichi tutti i parametri relativi al
test e restituirà ancora una stringa contenente i punti mirati dal paziente; il
secondo dovrà fornire una funzione lancaster(...) che accetta una coppia
di matrici (2 ∗ n, ogni colonna rappresenta un punto) di coordinate virtuali
rispetto ad un determinato centro e restituisce la coppia di matrici relative ai
punti effettivamente mirati dal paziente. Oltre a questa renderà disponibili
altre funzioni come la scrittura su file dei dati e la differenza tra due liste di
matrici.
8
2
Realizzazione dell’hardware
2.1
Scelte di progetto
Il dispositivo deve poter comunicare con:
• Servomotori: avrà quindi bisogni di un microcontrollore con interfaccia
PWM (Pulse Width Modulation);
• Laser: semplice pin up and down da 5-3.3 volt;
• Wii nunchuck: interfaccia I 2 C;
• Altoparlante: PWM;
• Computer: comunicazione seriale.
Per un dispositivo con queste necessità la scelta più comoda è utilizzare
Arduino, un ambiente hardware di sviluppo che consiste in una board, una
porta usb, un microcontrollore e tutto quello che esso necessita per funzionare
e comunicare con il mondo esterno. Esso semplifica di molto il progetto
di dispositivi riducendo il tutto al collegamento di componenti esterni, al
massimo mediati da qualche resistenza. Al fine di permettere ai laser di
mirare un qualsiasi punto su uno schermo si utilizzeranno una coppia di
servomotori, detti Pan e Tilt. Questi sono dei motori con un ”braccetto” che,
controllati in PWM, assumono determinati angoli tra 0 e π. Per il controllo
da parte del paziente del puntatore laser da lui comandato, è stato scelto il
Nintendo Wii Nunchuck. Infatti il suo joypad può comandare comodamente
il movimento, sia verticale che orizzontale, del laser. Facile infine confermare
con i pulsanti di questa semplice periferica i punti mirati.
2.2
2.2.1
Arduino
Che cos’è e perchè è ”bello”
Come abbiamo precedentemente detto, Arduino è un’insieme di componenti
preassemblati che facilita la programmazione e l’interfacciamento ad un microcontrollore. Questo non è altro che un piccolo processore che ha all’interno
tutto il necessario al suo funzionamento: una memoria RAM, una Flash, delle
interfacce per la comunicazione con il mondo esterno, et cetera. Nell’immagine
4 viene mostrata la versione Arduino UNO, equipaggiata con un microcontrollore ATmega328 (indicato dal numero 1). Prima che ci fossero questo
tipo di sistemi preassemblati, un progetto di questo genere avrebbe comportato innanzitutto la scelta di un chip, la costruzione di un ambiente adatto
9
al suo funzionamento (condensatori, resistenze, pin, socket, etc), l’acquisto
di un programmer, dispositivo in grado di programmarlo, e tutto un ampio corredo di conoscenze che solo un esperto del settore avrebbe potuto
possedere. Proprio in questo possiamo dire che Arduino è ”bello”: chiunque
può utilizzarlo con semplicità collegandolo con un cavo USB al proprio computer e scaricando l’ambiente di sviluppo Arduino IDE.
Figure 4: Arduino: panoramica
2.2.2
Panoramica
Nella figura 4 vengono numerati i principali componenti di cui è indispensabile una minima conoscenza.
Come abbiamo già detto, al punto 1 si trova il microcontrollore, il ”cervello”
di tutta la board. Una domanda sorge spontanea: ”Ma come fa ad esserci
dentro tutto quello che nei computer di utilizzo comune viene distribuito
sulla scheda madre?”. La risposta è semplice. Quello a cui ci troviamo davanti è un microcontrollore, in quanto tale, il processore che ha all’interno
è centinaia di volte meno potente di quelli utilizzati per i dispositivi general
purpose, quindi meno complesso; anche la memoria di cui è equipaggiato è
decisamente minore, si parla di 2 KB di SRAM e 32 KB di flash memory.
Essendo tali, con la tecnologia moderna è possibile inserire all’interno tutto
il necessario. (System on Chip).
Con il numero 2 è evidenziata una porta USB tipo B; non dobbiamo far
altro che ottenere un cavo USB A-B per collegarlo al nostro computer. Il
microcontrollore ATmega328 nativamente non supporta questo tipo di comunicazione, essa è possibile in quanto simula una comunicazione seriale.
10
Figure 5: Collegamente dispositivo
Per mettere in condizione Arduino di controllare attuatori o di ricevere
dati da sensori, dobbiamo fisicamente collegarli con dei fili elettrici. Questi
saranno messi nei socket mostrati dal punto 3 o 4. Al punto 3 abbiamo quelli
collegati ai pin digitali del microchip. Un pin digitale può essere utilizzato
come output o come input. Nel primo caso può applicare una tensione di 5
volt, che rappresenta l’1 in elettronica digitale (a volte si può utilizzare anche
3.3 volt), oppure comportarsi da massa (GND), ovvero portare la tensione
a 0 volt. E’ importante sapere che l’intensità di corrente emessa da uno
qualsiasi dei pin digitali è di 40mA; quando andremo a collegarci dei componenti sarà indispensabile abbassare questo valore con delle resistenze secondo
l’intensità adatta, specificata nel datasheet di ogni componente, pena bruciarlo. Nel caso sia invece utilizzato come input allora riceverà una tensione
determinata da un altro componente e potrà leggere 1, se la tensione è 5 volt,
o 0 in caso contrario. I pin sono numerati e alcuni hanno un ∼ prima del
numero. Questi hanno la possibilità di essere usati in PWM, di cui parleremo
nella sezione 2.3.1. Il pin GND è il pin di massa, ovvero è sempre a 0 volt;
i pin 0,1 possono essere utilizzati anche per la comunicazione seriale (oltre a
11
quella simulata dall’USB). I socket indicati dal numero 4 sono collegati ai pin
analogici del chip. Anch’essi possono essere utilizzati sia passivamente, per
leggere il valore delle tensioni applicate, o attivamente per applicarle (range:
0-5 volt). La conversione A/D avviene con 10 bit di precisione, di cui 8
leggibili.
L’alimentazione della board può avvenire sia tramite USB che tramite la
porta indicata dal numero 5. E’ possibile utilizzarle anche assieme, come
faremo nella nostra applicazione. Questo è necessario quando si alimentano
tanti sensori e attuatori, infatti, l’intensità di corrente diminuisce drasticamente il tal caso e può causare un reset del microcontrollore. Questo accade
spesso se si alimenta Arduino solo tramite usb collegata ad un notebook non
alimentato a sua volta. L’alimentazione della board non deve comunque mai
superare i 20 volt, pena il danneggiamento della stessa.
Con il numero 6 si indicano infine i pin 5V, 3.3V, che non sono altro che pin
digitali, in modalità output, costantemente a 1. Il primo però eroga 5 volt
mentre il secondo 3.3 volt.
2.2.3
Arduino IDE e la programmazione dei chip AVR
Arduino IDE è un software multipiattaforma che permette di programmare il
microcontrollore all’interno di Arduino. I microcontrollori AVR normalmente
si programmano in C ma Arduino IDE permette di utilizzare il linguaggio
Processing, decisamente più semplice per i non esperti di programmazione.
Processing permette l’uso degli oggetti. Il codice viene tradotto dall’IDE
in C++ con però alcune limitazioni: ad esempio non è possibile creare e
deallocare dinamicamente oggetti (keyword new, delete). Perchè utilizzare
gli oggetti dunque? Semplicemente per rendere più semplice la comprensione
del codice e la consultazione delle librerie. Il codice C++ viene compilato con
il compilatore g++ appositamente settato per la programmazione su AVR.
Quanto c’è da sapere è molto semplice: tutto il codice è contenuto in due funzioni: setup, chiamata all’accensione del chip, loop, eseguita continuamente
dopo la prima. Di default vengono incluse le definizioni delle librerie avr.h e
Arduino.h. Per la documentazione di queste e altre librerie disponibili fare
riferimento a http://arduino.cc/en/Tutorial/HomePage. Consigliato il
learning-through examples.
2.3
Servomotori
Un servo motore altri non è che un motore capace di ruotare in un dato range
(generalmente [0, π]). Esso è un attuatore poco costoso se non si hanno particolari pretese di potenza e precisione. Quelli analogici vengono comandati
12
Figure 6: Arduino IDE
tramite PWM (Pulse Width Modulation, figura 8).
2.3.1
PWM
In elettronica digitale, essendoci 2 soli valori possibili di tensione, per comunicare con alcuni attuatori o per alzare la tensione ad un arbitrario livello
compreso tra 0-5 volt si usa emettere un’onda quadra. Un onda quadra è una
funzione del tempo f (t) che ha un periodo T , ovvero f (t) = f (t + iT ), i =
{0, ..., n} e un duty cycle τ tali che f (t) = 1 per iT ≤ t ≤ iT + τ, i =
{0, ..., n}.
La tensione risultante V sarà:
V =
τ
5 volt
T
(1)
Nel nostro caso però ci servirà per dire al servomotore quanto deve ruotare.
Lo standard vorrebbe che ad un angolo di 0 rad corrispondesse una PWM
con τ = 1000µs e a π τ = 2000µs. I nostri economici servomotori hanno
però range diverso.
13
Figure 7: Servo motore
Figure 8: Pulse Width Modulation
La PWM di Arduino ha frequenza 500Hz quindi T = 2000µs.
Una coppia di servomotori può essere utilizzata in modo tale da permettere
ad un laser montato su di essi di mirare un range ampio su di una parete.
Quando vengono usati in tale modo si nomina pan il servomotore che muove
orizzontalmente il laser, tilt quello che lo muove verticalmente.
2.3.2
Leggere il datasheet
Tutti i componenti elettronici sono dotati di un documento che ne dà le specifiche. Nota però che per saperli leggere è necessaria una minima conoscenza
del pezzo in questione.
Nella figura 9 è mostrato una parte del datasheet dei servomotori acquistati
per questo progetto.
Le informazioni di nostro interesse sono:
• control system: specifica a quanti microsecondi di duty cycle il servo
motore si posiziona al centro del suo range (neutral), ovvero a 1500
µs;
• operating voltage range: indica l’intervallo di tensione adatta al
componente: [4.8, 6]volt;
14
Figure 9: Datasheet dei servo motori acquistati.
• operating angle: ci informa del range d’azione del servomotore e
della sua mappatura lineare. 45 gradi corrispondono a 400µs e il range
d’azione è di soli π2 . Con questa informazione siamo in grafo di definire
la mappa lineare m : RAD → M ICROS, che associa ad un angolo
in radianti i microsecondi di duty cycle equivalenti, e la sua inversa
m−1 : M ICROS → RAD.
2.3.3
Installazione
Per installare un servo su Arduino è necessario conoscerne il cavo di collegamento. Con una veloce ricerca su internet è facile capire che i tre filamenti
che lo costituiscono, rosso, nero e giallo, vanno rispettivamente collegato
all’alimentazione, alla messa a terra e ad un pin PWM, tramite cui lo controlleremo.
Figure 10: Simbolo servo nello schema
15
2.4
2.4.1
Laser e altoparlanti
Laser
I due laser che useremo nel progetto, uno rosso e uno verde, possiamo vederli
come black box attivabili con una certa tensione. Per quanto riguarda il laser
rosso, esso necessita di 5 volt per accendersi, quindi ci basterà collegare il
filo rosso ad uno qualsiasi dei pin digitali; mentre il laser verde ha bisogno di
∼ 3.3 volt. La soluzione è quindi mettere un transistor comandato da un pin
digitale, alimentato dal pin sempre acceso a 3.3 volt presente su Arduino.
Figure 11: Laser nel nostro schema: resistenza interna + LED
2.4.2
Transistor
Il transistor è uno dei componenti attivi più importanti dell’elettronica digitale. Il suo funzionamento è complesso; ci limitiamo qui a dire l’indispensabile
per il suo utilizzo. Innanzitutto quello utilizzato nel progetto è un transistor
NPN. Nella figura 12 viene mostrato il suo aspetto più comune e il simbolo
circuitale. Viene da noi utilizzato come un semplice interruttore tra la tensione applicata al collettore (+) e all’emettitore (-), comandata dalla base,
collegata quindi ad uno dei pin digitali di Arduino tramite una resistenza.
Infatti una corrente eccessiva applicata alla base può danneggiare il transistor.
2.4.3
Altoparlante
Un altoparlante è un componente in grado di trasformare un onda elettromagnetica in un onda sonora. Collegandolo ad un pin PWM trasformerà
l’onda quadra in un segnale udibile. In questo modo saremo in grado di dare
un feedback al paziente che sta eseguendo il test sull’andamento dello stesso.
Modificando il duty cycle della PWM emetteremo note differenti.
16
Figure 12: Transistor npn
Figure 13: Altoparlante
2.5
Wii Nunchuck
Il nunchuck della Nintendo è un comodo ed economico dispositivo comprendente un set di sensori; in questo caso risultano perfetti per l’interazione del
paziente con Arduino. Abbiamo a disposizione due pulsanti, B e Z, un joypad
ed un accelerometro. Questo comunica con l’interfaccia I 2 C, protocollo di
comunicazione sincrono, presente in Arduino. Il nunchuck è dotato di un connettore proprietario ovviamente non collegabile direttamente con la board;
dovremo quindi tagliare il cavo e collegare direttamente i fili ai pin corretti.
Su internet è semplice reperire informazioni su questo: una volta tagliato si
vedranno 4 fili di colore diverso. Rosso corrisponde all’alimentazione (+),
bianco alla massa, giallo al clock (pin A5 sulla board), green al data (pin
A4).
2.6
Schema circuitale completo
Per la realizzazione del dispositivo è stata utilizzata una prototype shield,
ovvero una piccola board che è possibile installare direttamente su Arduino
e che permette saldature direttamente su di essa. Nella figura 15 viene
17
Figure 14: Nintendo Wii Nunchuck
mostrato lo schema completo del nostro dispositivo.
Nota: nell’immagine il cavo bianco del nunchuck è disegnato con colore nero.
18
Figure 15: Schema circuitale completo
19
Figure 16: Dispositivo completo
3
3.1
3.1.1
Realizzazione del software
Drivers
Introduzione
Realizzare i driver dei vari componenti collegati ad Arduino non ha niente a
che vedere con la creazione degli stessi sui moderni sistemi operativi.
Arduino infatti non ha SO: semplicemente avvia una funzione setup all’accensione
e poi continua ad ciclare con la funzione loop. Non avendo meccanismi di
protezione per l’accesso all’ISA (Instruction Set Architecture) come User e
Protected Mode, abbiamo direttamente a disposizione primitive in grado di
controllare i pin digitali e analogici. Realizzare un driver sarà equivalente a
creare delle funzioni che determinino un interfaccia ad un certo attuatore o
sensore.
In genere un driver viene scritto sotto forma di libreria in c, inclusa dal main
di un programma e aggiunta al compile path dell’IDE. Tuttavia può essere
creato anche uno sketch in Processing, modalità forse più semplice ma meno
20
adatta al riutilizzo.
3.1.2
Servomotori
Fortunatamente Arduino fornisce un’interfaccia generica per i servomotori
controllati in PWM, Servo.h, di cui si trova la documentazione al seguente
link: http://arduino.cc/en/Reference/Servo.
Listing 1: esempio di uso
1
2
3
4
5
Servo servo ;
// a l l o c o s t a t i c a m e n t e l ’ o g g e t t o
servo . attach (3 ,700 ,2300);
// p i n 3 e mappa l i n e a r e
servo . write (75);
// a n g o l o 75 g r a d i
delay (1000);
// w a i t f o r 1 s e c
s e r v o . w r i t e M i c r o s e c o n d s ( 1 4 0 0 ) ; // a n g o l o e q u i v 1 4 0 0
Il listato 1 è un semplice esempio d’uso. L’oggetto servo viene allocato staticamente poichè non è possibile farlo dinamicamente, infatti non è presente la
keyword new nel compilatore customizzato per microcontrollori AVR. Con
il metodo attach costituiamo una nuova mappa lineare a partire dai valori
relativi a 0, π, che nel nostro caso sono 700, 2300µs; infatti 1500µs corrispondono al centro π2 e uno spostamento equivalente ad un angolo di π4 viene
effettuato con un decremento o incremento di 400µs del τ della PWM, come
scritto nel datasheet. Per meglio capire il relazione tra microsecondi, angoli
e la funzione di mappatura qui sotto è riportata una sezione di codice presa
del file Servo.cpp.
Listing 2: Dai gradi ai microsecondi
286
287
288
289
290
291
292
293
294
295
void S e r v o : : w r i t e ( i n t v a l u e )
{
i f ( v a l u e < MIN PULSE WIDTH)
{
i f ( value < 0) value = 0 ;
i f ( value > 180) value = 180;
v a l u e = map ( v a l u e , 0 , 1 8 0 , SERVO MIN ( ) ,
}
t h i s −>w r i t e M i c r o s e c o n d s ( v a l u e ) ;
}
SERVO MAX ( ) ) ;
Quando si passa al metodo write un angolo in gradi sessagesimali, viene
trasformato l’angolo nel valore corrispondente in microsecondi secondo la
mappa di quello specifico servomotore. Servo motori diversi hanno spesso
mappatura differente. Qui di seguito la definizione della funzione map.
Listing 3: Funzione di mappatura scritta in c
long map ( long x , long i n m i n , long in max , long out min , long out max )
{
return ( x − i n m i n ) ∗ ( out max − o u t m i n ) / ( in max − i n m i n ) + o u t m i n ;
}
Poichè i servo motori sono in coppia si è creata la libreria Aimer.h (4) che
è semplicemente un wrapper di due servo; per la gestione di punti da mirare
invece la libreria Point.h (5). In questo modo si potrà operare ad alto livello
21
come mostrato nel listato 6.
Si noti che l’allocazione dinamica della memoria per le strutture è consentita,
infatti le funzioni malloc, free sono disponibili.
Listing 4: ottica/Sketch/Libraries/Aimer/Aimer.h
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#i f n d e f AIMER H
#d e f i n e AIMER H
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#i f n d e f POINT H
#d e f i n e POINT H
#include ” Arduino . h”
#include ” P o i n t . h”
#include ” S e r v o . h”
c l a s s Aimer
{
public :
Aimer ( ) ;
/∗ t h e f o l l o w i n g f u n c t i o n s a r e j u s t w r a p p e r s ∗/
void a t t a c h ( i n t panPin , i n t t i l t P i n ) ;
void a t t a c h ( i n t panPin , i n t t i l t P i n , i n t panMin , i n t panMax , i n t t i l t M i n ,
void w r i t e M i c r o s e c o n d s ( i n t mpan , i n t m t i l t ) ;
/∗ t a k e s a P o i n t p and a i m s i t ∗/
void w r i t e M i c r o s e c o n d s ( P o i n t p ) ;
/∗ r e a d s t h e p o s i t i o n s o f t h e s e r v o s and w r i t e s them i n c o n t a i n e r ∗/
void r e a d M i c r o s e c o n d s ( P o i n t c o n t a i n e r ) ;
private :
S e r v o pan ;
Servo t i l t ;
};
#e n d i f
Listing 5: ottica/Sketch/Libraries/Point/Point.h
#include <s t d i o . h>
#include < s t d l i b . h>
#include < s t r i n g . h>
#d e f i n e NSSIZE 7
struct point {
int x , y ;
struct point ∗ next ;
};
typedef s t r u c t p o i n t ∗ P o i n t ;
/∗ i t t a k e s a c o u p l e o f i n t and r e t u r n s a p o i n t e r t o s t r u c t p o i n t ∗/
P o i n t p o i n t B u i l d e r ( int , i n t ) ;
/∗ i t t a k e s a p o i n t e r t o s t r u c t p o i n t and d e l e t e s t h a t p o i n t and
a l l l i n k e d i n i t ( ∗ n e x t ) ∗/
void d e s t r o y ( P o i n t ) ;
/∗ i t t a k e s t w o p o i n t e r t o s t r u c t p o i n t and add t h e f o r m e r
t o t h e l a t t e r ( ∗ n e x t ) ∗/
void add ( P o i n t , P o i n t ) ;
/∗ i t t a k e s a p o i n t e r t o s t r u c t p o i n t and a p o i n t e r t o a b u f f e r ;
p r i n t i n i t t h e v a l u e s i n s i d e t h e p o i n t i n t h i s way .
e x a m p l e : ” 1 0 0 , 2 0 0 ” ∗/
void t o S t r i n g ( P o i n t , char ∗ ) ;
/∗ t h e same a s a b o v e b u t i t p r i n t s a l l t h e p o i n t s l i n k e d t h r o u g h
∗ n e x t . b e s u r e t h e b u f f e r i s b i g e n o u g h ∗/
void a l l T o S t r i n g ( P o i n t , char ∗ ) ;
/∗ i t t a k e s a p o i n t e r t o a b u f f e r w i t h i n , f o r e x a m p l e : ” 5 2 , 1 2 0 ”
and c r e a t e s t h e r e l a t i v e p o i n t ∗/
P o i n t c r e a t e P o i n t ( char ∗ ) ;
/∗ t h e same a s a b o v e b u t i t t a k e s more t h a n one p o i n t .
f o r e x a m p l e ” 5 2 , 1 3 0 , 5 0 0 , 2 5 0 ” ∗/
P o i n t c r e a t e P o i n t s ( char ∗ ) ;
/∗ a u x i l i a r y f u n c t i o n ∗/
char ∗ s t r t r i m ( char ∗ ) ;
#e n d i f
22
int tiltMax ) ;
Listing 6: Utilizzo combinato di Aimer.h e Point.h
Aimer a i m e r 1 ;
aimer1 . attach ( 3 , 4 , 7 0 0 , 2 3 0 0 , 7 0 0 , 2 3 0 0 ) ;
P o i n t head = c r e a t e P o i n t s ( ” 1 4 0 0 , 1 5 0 0 , 1 3 0 0 , 1 4 0 0 , 1 5 0 0 , 1600 ” ) ;
P o i n t p = head ;
while ( p!=NULL) {
aimer1 . writeMicroseconds ( p ) ;
// aim p
delay (1000);
// w a i t 1 s e c
p = p−>n e x t ;
// n e x t p o i n t
}
d e s t r o y ( head ) ;
// f r e e p o i n t l i s t memory
3.1.3
Nunchuck
Abbiamo già accennato nella sezione riguardante l’HW che il Nunchuck utilizza il protocollo di comunicazione sincrono I 2 C. Nell’environment Arduino
la libreria Wire.h (http://arduino.cc/en/Reference/Wire) fornisce delle
primitive per la comunicazione tramite questo protocollo. Fortunatamente si
è trovato sulla rete uno sketch capace di ricevere tutte le informazioni utili
dai sensori. La versione non era recente quindi non compilava. Lavorando
sul porting di questo sketch si è arrivati al risultato sperato, mostrato sotto.
Listing 7: ottica/Sketch/Rino/Nunchuck.ino
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <Wire . h>
#include <P o i n t . h>
#d e f i n e JOYPAN 132
#d e f i n e JOYTILT 126
#d e f i n e POLLING DELAY2 15
//
// Nunchuck
//
static
functions
uint8 t
nunchuck buf [ 6 ] ;
// a r r a y
to
store
nunchuck data ,
// i n i t i a l i z e t h e I2C s y s t e m , j o i n t h e I2C b u s ,
// and t e l l t h e n u n c h u c k we ’ r e t a l k i n g t o i t
void n u n c h u c k i n i t ( )
{
Wire . b e g i n ( ) ;
// j o i n i 2 c b u s a s m a s t e r
Wire . b e g i n T r a n s m i s s i o n ( 0 x52 ) ; // t r a n s m i t t o d e v i c e 0 x 5 2
Wire . w r i t e ( 0 x40 ) ;
// s e n d s memory a d d r e s s
Wire . w r i t e ( 0 x00 ) ;
// s e n d s s e n t a z e r o .
Wire . e n d T r a n s m i s s i o n ( ) ; // s t o p t r a n s m i t t i n g
}
// Send a r e q u e s t f o r d a t a t o t h e n u n c h u c k
// was ” s e n d z e r o ( ) ”
void n u n c h u c k s e n d r e q u e s t ( )
{
Wire . b e g i n T r a n s m i s s i o n ( 0 x52 ) ; // t r a n s m i t t o
Wire . w r i t e ( 0 x00 ) ;
// s e n d s one b y t e
Wire . e n d T r a n s m i s s i o n ( ) ; // s t o p t r a n s m i t t i n g
}
d e v i c e 0 x52
// R e c e i v e d a t a b a c k f r o m t h e n u n c h u c k ,
int nunchuck get data ( )
{
i n t c n t =0;
Wire . r e q u e s t F r o m ( 0 x52 , 6 ) ; // r e q u e s t d a t a f r o m n u n c h u c k
while ( Wire . a v a i l a b l e ( ) ) {
// r e c e i v e b y t e a s an i n t e g e r
n u n c h u c k b u f [ c n t ] = n u n c h u k d e c o d e b y t e ( Wire . r e a d ( ) ) ;
c n t ++;
}
nunchuck send request ( ) ;
// s e n d r e q u e s t f o r n e x t d a t a p a y l o a d
// I f we r e c i e v e d t h e 6 b y t e s , t h e n g o p r i n t them
i f ( c n t >= 5 ) {
return 1 ;
// s u c c e s s
}
23
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
return 0 ; // f a i l u r e
}
// Encode d a t a t o f o r m a t t h a t most w i i m o t e d r i v e r s e x c e p t
// o n l y n e e d e d i f you u s e one o f t h e r e g u l a r w i i m o t e d r i v e r s
char n u n c h u k d e c o d e b y t e ( char x )
{
x = ( x ˆ 0 x17 ) + 0 x17 ;
return x ;
}
// I ’m u s i n g P o i n t l i b r a r y
Point getJoy (){
nunchuck get data ( ) ;
d e l a y (POLLING DELAY2 ) ;
i n t d i f f p a n = n u n c h u c k b u f [ 0 ] − JOYPAN;
i n t d i f f t i l t = n u n c h u c k b u f [ 1 ] − JOYTILT ;
return p o i n t B u i l d e r ( d i f f p a n , d i f f t i l t ) ;
}
int getZ ( ) {
nunchuck get data ( ) ;
d e l a y (POLLING DELAY2 ) ;
i f ( ! ( ( n u n c h u c k b u f [ 5 ] >> 0 ) & 1 ) ) return 1 ;
return 0 ;
}
i n t getC ( ) {
nunchuck get data ( ) ;
d e l a y (POLLING DELAY2 ) ;
i f ( ! ( ( n u n c h u c k b u f [ 5 ] >> 1 ) & 1 ) ) return 1 ;
return 0 ;
}
La funzione nunchuck init effettua l’handshake mentre nunchuck send request
inivia al nunchuck la richiesta di una nuova rilevazione. Tramite nunchuck get data
avviene la ricezione dei dati richiesti e la bufferizzazione all’interno dell’array
nunchuck buf.
Sono state poi dai noi aggiunte delle funzioni che permettono, con semplicità,
di leggere il valore dei due bottoni Z, C e gli spostamenti del joystick.
La funzione getJoy restituisce, all’interno dei una struttura point, i valori
x, y di scostamento dalla posizione neutrale del joystick. Per far ciò si chiama
subito nunchuck get data, si attende per 15 ms, misura introdotta per stabilità del driver, e si sottraggono i primi due byte del buffer ai rispettivi valori
neutrali definiti nelle macro JOYPAN e JOYTILT (x, y).
Le due funzioni getZ, getC non fanno altro che restituire 1 se il relativo
tasto è premuto, 0 altrimenti. Nonostante le informazioni per scrivere queste
funzioni siano state apprese leggendo lo sketch di esempio, tutto poteva essere
compreso tramite il datasheet relativo.
24
3.1.4
Altoparlante
Abbiamo già detto nella sezione HW che possiamo emettere differenti tonalità
a seconda del valore del duty cycle τ della PWM che controlla l’altoparlante.
Sfruttando quindi la funzione tone, già fornita dalle Arduino API, si sono
create delle funzioni per dare un feedback in risposta a certi eventi.
25
Listing 8: ottica/Sketch/Rino/Sound.ino
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include ” p i t c h e s . h”
#d e f i n e ALTO 8
void f e e d b a c k ( ) {
t o n e (ALTO, NOTE C6 ,
}
300);
void e r r o r ( ) {
t o n e (ALTO, NOTE E2 ,
}
500);
void c h a n g e l a s e r ( ) {
t o n e (ALTO, NOTE A5 , 3 0 0 ) ;
t o n e (ALTO, NOTE B5 , 3 0 0 ) ;
}
void end ( ) {
t o n e (ALTO, NOTE B5 ,
t o n e (ALTO, NOTE A5 ,
}
3.2
300);
300);
La comunicazione seriale
Per poter scambiare informazioni tra il computer e il microprocessore all’interno
di Arduino dobbiamo stabilire una comunicazione seriale.
Figure 17: comunicazione seriale.
Tuttavia i moderni computer difficilmente sono equipaggiati di una porta
seriale. (figura 18)
La comunicazione con le periferiche avviene ormai prevalentemente tramite
l’USB. L’Universal Serial Bus è però un protocollo ”pesante” da gestire e
l’ATmega328 non ha un controller build-in al suo interno.
Per semplificare il tutto, Arduino utilizza una comunicazione seriale simulata tramite USB. Questo significa che sulla board il protocollo USB viene
tradotto in seriale e letto da pin specifici del microcontrollore. Grazie a questo
artefatto, saremo in grado di comunicare con il microcontrollore esattamente
come se avessimo una porta seriale RS232 nel nostro computer. Nei sistemi
operativi discendenti da UNIX, quindi OSX e Linux, l’I/O è permesso dal
sistema operativo tramite dei file speciali, detti device files, presenti nella directory /dev/. Questi si dividono in characters e blocks special files. I primi
26
Figure 18: Porta seriale RS232.
servono alla comunicazione con i terminali e i vecchi modem e stampanti; i
secondi con dischi fissi e memorie di vario tipo. La seriale simulata tramite
USB renderà disponibile un device file di tipo characters.
Per meglio capire come funziona la comuncazione seriale tramite questi file,
vediamo un esempio con quelli associati ai terminali.
Listing 9: ttys000
1
2
3
4
$tty
/ dev / t t y s 0 0 0
h e l l o world !
$
1
2
3
$tty
/ dev / t t y s 0 0 1
$ e c h o −e ” h e l l o
Listing 10: ttys001
w o r l d ! \ n” >> / dev / t t y s 0 0 0
Si aprano due terminali, in seguito rappresentati dai listati 9 e 10.
Eseguendo il comando tty viene stampato il nome del device file associato
a quel terminale. Nel nostro caso (OSX) vengono fuori i nomi alle righe 2
di entrambi i terminali, ma possono essere differenti. Si vada nel secondo
terminale ed si esegua il comando alla riga 3 del 10 cambiando il nome del
file finale di modo da andare a scrivere sul file relativo all’altro terminale. Il
comando va semplicemente ad appendere sul file specificato la stringa passata come argomento. Fatto questo si guardi cosa è successo nel listato 9.
Ciò che è stato scritto sul device file /dev/ttys000 viene riportato dal terminale. Una volta collegato Arduino in /dev/ apparirà un file relativo, con
un nome diverso per sistema operativo, che ci permetterà di comunicare col
microcontrollore esattamente in questo modo.
Nel dettaglio, ogni volta che si connette un usb ad una porta del computer,
per prima cosa viene comunicato un codice produttore e un codice prodotto.
Solo se il kernel ha al suo interno un driver associato viene creato lo special
file.
Per agevolare questo tipo di comunicazione, usare un programma adatto.
(es. picocom, minicom, screen)
Proviamo adesso a comunicare direttamente con Arduino.
27
Listing 11: ottica/Sketch/SerialReceiver/SerialReceiver.ino
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int
led = 13;
void s e t u p ( ) {
S e r i a l . begin (9600);
pinMode ( l e d , OUTPUT) ;
}
void l o o p ( ) {
i f ( S e r i a l . a v a i l a b l e ( ) > 0) {
char c = S e r i a l . r e a d ( ) ;
d i g i t a l W r i t e ( l e d , HIGH ) ;
delay (500);
d i g i t a l W r i t e ( l e d , LOW) ;
delay (500);
Serial . print ( c );
}
}
Caricare lo sketch 11 in Arduino. Aprire il terminale e iniziare una comunicazione seriale con esso.
$ p i c o c o m / dev / t t y . usbmodem411
Scrivete ”hello world”. I caratteri verranno inviati a Arduino; questo, per
ognuno di essi, farà lampeggiare un led su di esso e rimanderà indietro il
carattere. Avrete quindi l’impressione che ciò che scrivete venga mostrato a
schermo molto lentamente. Si noti che, per ricevere ed inviare caratteri da
Arduino si fa uso della classe Serial.
http://arduino.cc/en/Reference/Serial
3.3
3.3.1
La calibrazione e i metodi di conversione
Premessa
Le figure 19, 20 mostrano dei valori registrati senza alcun tipo di manipolazione relativi ad una stessa serie di 16 punti, disposti in quattro colonne
sullo schermo di Hess/Lancaster. Questi indicano i µs di duty cycle τ equivalenti agli angoli assunti per poterli mirare. La figura 19 rappresenta i valori
estrapolati dai servomotori che guidano il laser sinistro (verde), la figura 20
quelli del laser destro (rosso). Il test è stato effettuato ad una distanza di
60cm. L’osservazione di questi valori dà un’idea della difficoltà di tradurre
questi angoli in coordinate piane.
28
1253
10cm
1341
1419
1504
981
976
983
1253
1339
1417
1501
1039
1030
1029
1032
1252
1336
1418
1504
1097
1093
1088
1093
1252
1339
1415
1501
1158
1154
1154
1154
987
10cm
n
Figure 19: Insieme di valori m
, dove n rappresenta il valore pan mentre m
tilt, relativi a 16 punti disposti a griglia, come sopra. I valori sono espressi
in microsecondi equivalenti. Laser sinistro.
1556
10cm
1644
1721
1801
1065
1073
1087
1554
1636
1718
1797
1114
1121
1127
1138
1551
1636
1712
1792
1171
1172
1182
1190
1547
1629
1706
1787
1239
1239
1249
1254
1062
10cm
n
Figure 20: Insieme di valori m
, dove n rappresenta il valore pan mentre m
tilt, relativi a 16 punti disposti a griglia, come sopra. I valori sono espressi
in microsecondi equivalenti. Laser destro.
3.3.2
Il problema
Affinché i laser possano mirare punti determinati da coordinate piane sullo
schermo di Hess/Lancaster e affinchè si possa risalire alle coordinate piane
29
dei punti mirati a partire dagli angoli assunti, è necessario trovare un appropriato metodo di conversione ed effettuare una appropriata calibrazione
dello strumento.
La figura 21 ci mostra la situazione attuale. O rappresenta uno dei due laser.
O0 è la proiezione ortogonale di O sul piano yz. Questa verrà chiamata origine reale ed è differente per ogni laser. P è il generico punto che desideriamo
mirare.
Innanzitutto abbiamo bisogno di un sistema di coordinate comune ai due
laser. E’ stato quindi introdotto il sistema coordinate virtuali, le quali hanno
origine in D, detto origine virtuale, per entrambi i laser. P avrà coordinate
(px , py ), ovvero la distanza in mm tra P e D nelle due componenti. La
distanza tra D e O0 sarà invece etichettata con (cx , cy ).
Il nostro obiettivo è quello di trovare una funzione φ : Z 2 → [mlb, mub]2 che,
date le coordinate virtuali (px , py ), restituisca gli angoli (α, β), espressi nei µs
equivalenti, e la funzione inversa φ−1 : [mlb, mub]2 → Z 2 che date invece una
coppia di angoli (α, β) espressi in µs restituisca le coordinate virtuali (px , py )
del punto mirato. Con mlb, mub si indicano i valori in microsecondi relativi
a 0 e π di un dato servomotore. Ogni puntatore otterrà risultati diversi a
seconda dei parametri caratterizzanti dati alle sopracitate funzioni.
Nei successivi paragrafi verranno mostrati i vari tentativi per trovare questa
funzione e la sua inversa.
3.3.3
Interpolazione
Con il termine interpolazione si intende un metodo per definire una funzione
incognita a partire da alcuni punti campione di cui si conosce l’immagine.
La funzione incognita φ : Z 2 → [mlb, mub]2 è particolarmente complessa e
non è riducibile a due funzioni φx : Z → [mlb, mub], φy : Z → [mlb, mub] che
si occupino delle due componenti poichè esse sono una dipendente dall’altra.
Questa dipendenza si vede dalle seguenti relazioni.
φx (px ) = α000 6= α
φy (py ) = β 00 6= β
(2)
(3)
La non indipendenza può essere formalizzata come segue.
φ(px , py ) 6= (φx (px ), φy (py ))
(4)
A questo si aggiunge il fatto di dover trovare l’inversa φ−1 .
Non si è in nessun modo riusciti a risolvere questi problemi, ammesso che
esistano delle soluzioni; si è quindi cambiata strategia.
30
z
x
P
py
cx
D
px
α
y
cy
β 00
O’
β
α
000
y
O
x
Figure 21: Situazione attuale. Un solo puntatore.
3.3.4
Approccio trigonometrico
Siano noti i seguenti parametri.
d := OO0
(cx , cy )
(5)
(6)
Per semplicità espositiva definiamo le seguenti abbreviazioni.
x := cx + px
y := cy + py
(7)
(8)
Riusciamo allora a definire delle funzioni simili a φ, φ−1 ma che misurano gli
angoli in radianti.
φ0 : Z 2 → [0, π]2
φ0−1 : [0, π]2 → Z 2
31
(9)
(10)
x
y
φ0 (x, y) = (arctan( p
), arctan( √
)) = (α, β)
d 2 + x2
d2 + y 2
s
s
1 + tan2 α
) = (x, y)
1 − tan2 α tan2 β
(12)
Per completezza seguono le definizioni senza le abbreviazioni 7 e 8; saranno
quelle che noi useremo.
φ0−1 (α, β) = (d tan α
1 + tan2 β
, d tan β
1 − tan2 β tan2 α
(11)
p x + cx
cy + p y
φ0 (px , py ) = (arctan( p
), arctan( p
)) = (α, β)
d2 + (py + cy )2
d2 + (cx + px )2
(13)
s
s
1 + tan2 α
−cy ) = (px , py )
1 − tan2 α tan2 β
(14)
Si noti che nella 11, 12, 13, 14 gli angoli sono considerati rispetto alla normale
al piano yz mentre ogni servomotore ha l’angolo 0 più o meno parallelo a
quel piano. Necessitano quindi di una trasformazione. Nel nostro caso i
servomotori tilt partono a calcolare gli angoli dall’alto, di conseguenza al
posto di prendere α abbiamo preso il complementare di α. I servomotori
pan invece hanno l’angolo 0 diretto completamente a sinistra; è stato scelto
di sommare ad ogni β π2 . In questo modo, con α0 , β 0 > 0, dove gli angoli
0
sono gli angoli trasformati, si mira il primo quadrante, in alto a destra
rispetto all’origine virtuale D. Queste trasformazioni dipendono da come si
sono montati i servomotori e da quale quadrante si voglia considerare quello
positivo.
Dobbiamo ora trovare delle funzioni m, m−1 che ci permettano di passare dai
radianti ai microsecondi e viceversa.
φ0−1 (α, β) = (d tan α
2
1 + tan β
−cx , d tan β
1 − tan2 β tan2 α
map(x) =
out max − out min
x + out min
in max − in min
(15)
L’equazione 15 è la riscrittura matematica e parametrica della funzione presente nel listato 3. Sostituendo ai parametri di questa quelli dei nostri servomotori siamo in grado di trovare le funzioni di mappatura che fanno al caso
nostro.
m : [0, π] → [mlb, mub]
m : [mlb, mub] → [0, π]
−1
32
(16)
(17)
m(x) =
mub − mlb
x + mlb
π
(18)
π
x
(19)
mub − mlb
dove mub, mlb sono rispettivamente i microsecondi relativi all’angolo π e 0 dei
nostri servomotori. Sono stati lasciati parametrici perchè abbiamo bisogno,
affinchè i puntatori mirino effettivamente il punto P desiderato, che dato un
angolo π2 mirino esattamente la loro proiezione ortogonale O0 .
Per far ciò bisogna innanzitutto trovarla con precisione; un trucco è quello
di mettere uno specchio sulla parete e spostare il puntatore affinchè il raggio
riflesso non illumini esattamente se stesso. Una volta trovato O0 è necessario
prendere nota dei valori (µα , µβ ), ovvero degli angoli assunti espressi in microsecondi. Le corrette funzioni di mappatura, una per il servomotore pan
e l’altra per il servomotore tilt, si troveranno a partire dai seguenti valori di
mub, mlb.
m−1 (x) =
mubα
mlbα
mubβ
mlbβ
= µα + 800
= µα − 800
= µβ + 800
= µβ − 800
(20)
(21)
(22)
(23)
dove 800 sono i µs equivalenti ad un angolo π2 per i nostri servomotori.
Diversi servomotori avranno valori differenti.
Questo procedimento va effettuato per entrambe le coppie di servomotori
ovvero per i due puntatori. Abbiamo ora tutto l’occorrente matematico per
il nostro obiettivo; mancano solo da trovare i parametri d, cx , cy per ogni
puntatore. Il parametro d è la distanza del puntatore dallo schermo. Precedentemente avevamo trovato la posizione di O0 . Un metodo empirico efficace
è quello di attaccare a O0 , con dello scotch, un filo, quindi allungarlo fino al
puntatore e tagliarlo su di esso. Misurare quindi il filo con un metro. Per
trovare cx , cy basta misurare con un metro sulla parete le due componenti
della distanza tra D e O0 . Entrambi i procedimenti vanno ripetuti per i due
puntatori.
3.3.5
Problemi e riformulazione dei requisiti
Nonostante quanto detto nel paragrafo sopra sia stato implementato (si trova
nel branch coordinate virtuali del repository del progetto), non si è riusciti, con precisione, a mirare i punti voluti. L’errore massimo è stato misurato
33
in 4cm; tale rende il dispositivo inadatto alle applicazioni mediche. Crediamo che il modello matematico sottostante sia corretto ma che fattori come
l’inclinazione dovuta al montaggio manuale dei servomotori e l’imprecisione
degli strumenti stessi, che ricordo essere economici, siano causa del fallimento. Infatti, un minimo errore nell’assunzione di un angolo da parte di un
servomotore comporta un’ampio scostamento sullo schermo.
Sono due le soluzioni possibili:
• Un azienda, specializzata in strumenti di misurazione, crea un dispositivo perfettamente calibrato e montato a macchina e applica le funzioni
citate in questa sezione.
• I punti da mirare vengono mirati prima dall’operatore. All’esecuzione
del test Arduino mira i punti precedentemente presi e registra non le
coordinate virtuali ma i microsecondi relativi agli angoli assunti dai
servomotori per mirare i punti del paziente. Vengono ritornati questi
la cui traduzione e correzione viene affidata ad un programma in R.
Sappiamo che ciò è possibile e nella sezione 3.3.7 diamo un’idea di
come si potrebbe fare.
Si è scelti la seconda delegando però l’applicazione del metodo di correzione
e traduzione a terzi.
I requisiti posso essere quindi riformulati come segue. Il dispositivo dovrà
accettare due modalità di esecuzione. La prima è quella relativa alla calibrazione, ove, specificando un numero di punti, l’operatore potrà mirare i
punti di riferimento per il test successivo con entrambi i laser. Fatto ciò
vengono restituiti i microsecondi equivalenti agli angoli assunti. La seconda
modalità è quella di esecuzione del test. Arduino si aspetterà di ricevere
gli angoli da far assumere ai due laser per mirare i punti di riferimento,
dopodichè comincerà l’esecuzione del test che dovrà registrare gli angoli assunti nei punti mirati dal paziente e restituirli alla fine del test.
3.3.6
I risultati
Si anticipano qui i risultati ottenuti effettuando un test ad una distanza di
50 cm, puntando i vertici di un quadrato di lato 30 cm. I punti mirati sono
quattro. Con un apice si fa riferimento ai risultati ottenuti dal puntatore
sinistro, con due a quello destro. I dati sono espressi in microsecondi relativi
agli angoli assunti.
34
Punti di riferimento.
1215
R = 1134
1531
R00 = 1209
0
1210 1464 1465
967 946 1117
1536 1787 1780
1045 1065 1223
(24)
(25)
Test paziente sano.
1215 1210 1463 1467
A = 1135 966 945 1120
1533
1539
1790
1782
A00 = 1209 1047 1065 1225
0 0 1 2
0
0
|R − A | = 1 1 1 3
2 3 3 2
00
00
|R − A | = 0 2 0 2
0
Ideale paziente che sbaglia a mirare tutti i punti di 5cm verso l’alto.
1212 1209 1465 1466
0
B =
1094 943 922 1080
1530 1540 1791 1780
00
B =
1174 1019 1037 1188
3 1 1 1
0
0
|R − B | = 40 24 24 37
1 4 4 0
00
00
|R − B | = 35 26 28 35
(26)
(27)
(28)
(29)
(30)
(31)
(32)
(33)
Ideale paziente che sbaglia a mirare tutti i punti di 5 cm in orizzontale.
1176 1176 1410 1412
0
C = (34)
1139 973 947 1119
1485 1490 1741 1732
00
C = (35)
1211 1048 1056 1220
39 34 54 53
0
0
|R − C | = (36)
5 6 1 2
46 46 46 48
00
00
|R − C | = (37)
2 3 9 3
I test sono coerenti con quanto aspettato ed evidenziano gli errori di mira.
Tuttavia, a pari scostamento, si nota una grande differenza tra i punti (espressi
35
dalle colonne) 1,4 e 2,3 nelle matrici 32, 33, 36, 37. Questo significa che la
correzione dei dati è assolutamente necessaria e non rimovibile, a meno che
non si faccia un’analisi puramente quantitativa.
3.3.7
Approccio per fasce
Figure 22: Spreadsheet - approccio fasce
Nella figura 22 viene mostrata una griglia relativa i dati estrapolati dal puntamento, da parte di uno solo dei due laser, di 25 punti, disposti in righe da
5, tutti a 10 cm di distanza dai vicini. I dati sono puri, non vi è quindi stata
fatta alcuna correzione.
Nelle colonne B,E,H,K,N, alle righe 7,13,18,24,30, i valori mostrati con sfondo
bianco rappresentano gli angoli assunti dal servomotore pan, espressi in µs;
mentre nelle colonne C,F,I,L,O, alle righe 8,14,19,25,31, sempre con sfondo
36
bianco, sono rappresentati i corrispondenti valori tilt. Le celle a sfondo blu
rappresentano il punto D, origine virtuale (0, 0).
La griglia viene divisa in quattro fasce orizzontalmente e quattro fasce verticalmente. Per far rifermento ad una fascia useremo la notazione F (X, Y ),
dove X, Y saranno le lettere o i numeri corrispondenti alle colonne o alle
righe nello spreadsheet.
I valori con sfondo verde chiaro si trovano tra le misure tilt e ne calcolano
la differenza, mentre quelli in giallo fanno lo stesso per quelle pan. I valori
grigi mostrano le medie, per riga e per colonna, di questi scostamenti e le
chiameremo δF (XY ) , mentre quelli in arancione la media dei valori pan, tilt,
che referenzieremo con X, dove X sarà una lettera relativa ad una colonna
o un numero relativo ad una riga.
Le celle in verde scuro mostrano le deviazioni standard dei valori della riga
o della colonna corrispondente.
Il test è stato effettuato ad una distanza di 60 cm e la griglia di punti aveva
la riga inferiore ad un’altezza maggiore di quella del dispositivo, mentre la
colonna H si trovava approssimativamente al centro relativo della parete.
Questa disposizione della griglia è subito riscontrata nei dati, infatti, se
guardiamo le deviazioni standard prese sulle colonne, notiamo che queste sono
molto basse, se invece guardiamo quelle delle righe, ci accorgiamo che queste
sono molto maggiori e aumentano più ci si sposta lontani dalla proiezione
ortogonale dei laser sulla parete.
Nella tabella B35 - K38, vengono mostrati i risultati della conversione empirica dei valori pan, tilt in coordinate virtuali. Per verificare se il metodo è
corretto, sono stati presi dei punti campione p0 , p1 , p2 (celle B36,B37,B38) a
distanze note dall’origine virtuale (colonne px , py della stessa tabella). Sono
stati poi presi i valori degli angoli αp , βp , sempre mostrati nelle colonne denominate pan ms, tilt ms. Le colonne p0x , p0y mostrano i valori ottenuti dal
processo di conversione di angoli in coordinate piane che spieghiamo qui con
un esempio.
Il punto p0 appartiene ad una fascia orizzontale e ad una fascia verticale come
mostrato dalle seguenti proposizioni.
(B ≤ αp0 ≤ E) → p0 ∈ F (B, E)
(8 ≤ βp0 ≤ 14) → p0 ∈ F (8, 14)
(38)
(39)
Sappiamo che la colonna E dista da H, quella dell’origine, 10cm. Poichè p0
sta nella nella fascia F (B, E) significa che la distanza tra p0 e l’origine è
≥ 10cm. Per sapere il valore da sommare a dieci prendiamo lo scostamento
tra αp0 e la colonna E , ovvero dp0 E = |αp0 − E|. In questa fascia sappiamo
che 10 cm sono equivalenti a δF (BE) quindi per trovare questa distanza in
37
d
0E
centimetri facciamo δFp(BE)
∗ 10. Il risultato si trova nella cella H36. Per il
tilt si segue un procedimento analogo. Il segno è stato deciso intuitivamente,
determinato il quadrante positivo.
Nelle colonne etichettate |p0x − px | |p0y − py | si valuta la bontà di queste
conversioni rispetto alle distanze reali.
Possiamo osservare che mentre le misure pan risultano buone, quelle di
tilt non lo sono. Questo accade proprio perchè la griglia è posizionata ad
un’altezza maggiore del dispositivo; il punto p2 , infatti, che si trova più in
basso, e quindi più vicino alla proiezione ortogonale del laser, presenta una
differenza dalla distanza reale < 1cm mentre quelle di p1 , p0 sono > 1.5cm.
Concludiamo quindi che questo metodo, sopra mostrato molto empiricamente, può funzionare a patto che:
• Il punto centrale dello schermo di Hess/Lancaster si trovi il più possibile
vicino alle proiezioni ortogonali dei laser;
• Le fasce siano fitte;
• La distanza e il dispositivo non vengano spostati poichè altrimenti si
dovrebbe ricostruire l’intero modello di correzione e conversione degli
angoli in coordinate piane.
3.4
3.4.1
Lo sketch finale
Inclusioni, macro, allocazioni statiche permanenti
Nel listato 12 viene mostrata la parte iniziale dello sketch Rino.ino (nome
scelto per il dispositivo) in cui vengono fatte delle inclusioni di librerie, delle
definizioni di macro e allocazione di risorse statiche.
Listing 12: ottica/Sketch/Rino/Rino.ino
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include
#include
#include
#include
#include
” P o i n t . h”
” S e r v o . h”
” p i t c h e s . h”
” MemoryFree . h”
” Aimer . h”
#d e f i n e DEBUG MODE 0
#d e f i n e SERIALFREQ 9600
// c a s e s w i t c h
#d e f i n e RED 0
#d e f i n e GREEN 1
#d e f i n e
#d e f i n e
#d e f i n e
#d e f i n e
#d e f i n e
#d e f i n e
RIGHTPAN 2
RIGHTTILT 3
LEFTPAN 4
LEFTTILT 5
REDLASER 11
GREENLASER 12
#d e f i n e BPSIZE 14
#d e f i n e BTSIZE 200
38
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// d e l a y
#d e f i n e
#d e f i n e
#d e f i n e
#d e f i n e
per s t a b i l i t a ’ : senza l a
DELAY BETWEEN TESTS 15
DELAY BETWEEN POINTS 250
SOUND DELAY 300
POLLING DELAY 15
seriale
crasha
#d e f i n e WHO STARTS 0
#d e f i n e JOYPAD SENSIBILITY 20
Aimer r i g h t A i m e r ;
Aimer l e f t A i m e r ;
P o i n t p1 , p2 , r e s 1 , r e s 2 ;
char ∗ b u f f e r p a r z ;
char ∗ b u f f e r t o t ;
Vengono incluse le due librerie da noi create Point.h, Aimer.h al fine di
operare a più alto livello sui puntatori e sulle coordinate dei punti. Viene caricata inoltre MemoryFree.h, da noi solo impacchettata. La libreria Servo.h
è già inclusa nell’Arduino IDE e viene utilizzata da Aimer.h. pitches.h
non è altro che un insieme di macro che definiscono le frequenze corrette
per emettere determinate note tramite altoparlante; non è rilasciata come
libreria Arduino ma è semplicemente un file copiato nel nostro progetto.
Vengono quindi definite una serie di macro di cui la prima è DEBUG MODE; se
settata ad 1 vengono mandate via seriale un insieme di informazioni utili al
debug come la memoria libera in un determinato istante o il punto mirato appena il paziente ne ha dato conferma. E’ molto importante infatti verificare
di aver deallocato quanto allocato dinamicamente; ciò può essere fatto controllando la quantità di memoria all’inizio dell’esecuzione di ogni iterazione
del loop. Si è di fronte ad una corretta gestione della memoria solo quando
questa rimane costante. Di seguito è definita la velocità di comunicazione
seriale. 9600 è il valore di default ma può agevolmente essere cambiato.
Molte procedure vengono ripetute per entrambi i laser come calibrate o
execute; per comunicargli quindi quale dei due laser utilizzare gli passiamo
come parametro un intero, infatti le definizioni RED, GREEN associano ogni
laser ad un numero. Le successive macro associano un dispositivo al pin
relativo che lo controlla, ad esempio, RIGHTPAN viene controllato dal pin 2
come GREENLASER dal pin 12. Con BPSIZE, BTSIZE definiamo le dimensioni
di due buffer i cui puntatori vengono dichiarati a linea 38,39. La necessità di creare dei buffer nasce da diversi fatti. Per un oculato utilizzo delle
risorse si è preferito utilizzare gli array di caratteri, C-style, anzichè la classe
String fornita da Processing. Allocare e deallocare però lo spazio necessario
all’invio o alla ricezione di un messaggio via seriale aumenta la possibilità
di dimenticarsi di deallocare qualcosa; poichè la comunicazione avviene frequentemente, soprattutto in debug mode, si è pensato di creare due buffer
SW sempre disponibili: un buffer di dimensioni molto limitate, buffer parz,
e uno di dimensioni più elevate, buffer tot. All’utilizzatore il compito di
39
ripulire il buffer dopo averne fatto uso. Un’altra ragione che ha motivato la
creazione dei buffer SW è stata la limitatezza di quello HW, 64 byte; infatti,
quando dobbiamo comunicare i punti da mirare o quelli mirati, si eccede
spesso questo limite.
Quando si scrive codice a bassissimo livello, effettuando comandi o richieste
di dati a periferiche, spesso è necessario introdurre dei tempi di delay: una
periferica potrebbe non tollerare una frequenza di polling troppo elevata.
Le quattro macro definite tra la riga 26 e 29 hanno proprio questo scopo
all’interno del codice. Con la macro POLLING DELAY ci si riferisce al polling
effettuato sul nunchuck, mentre con WHO STARTS si decide con quale laser si
vuol partire. Dell’ultima macro definita JOYPAD SENSIBILITY si parlerà più
avanti. (vedi 3.4.7) Ci sono variabili che non ha senso allocare e deallocare
ogni volta che si cicla, per questa ragione ne sono definite qui alcune. Vengono
allocati staticamente i puntatori laser, aimer, e alcune variabili teste di liste
di punti: p1,p2 quelle definite dall’operatore, res1,res2 quelle mirate dal
paziente.
3.4.2
Setup
Nel codice sotto (13) viene inizializzata la seriale e la comunicazione I 2 C col
nunchuck, settati i pin relativi ai laser per essere usati in modalità output
digitale, collegati i puntatori ai pin dei servo motori che li compongono,
allocati i buffer SW e ripuliti.
Listing 13: ottica/Sketch/Rino/Rino.ino
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
void s e t u p ( ) {
S e r i a l . b e g i n (SERIALFREQ ) ;
// h a n d s h a k e n u n c h u c k
nunchuck init ( ) ;
// l a s e r s
pinMode (REDLASER, OUTPUT) ;
pinMode (GREENLASER, OUTPUT) ;
// b i n d i n g
r i g h t A i m e r . a t t a c h (RIGHTPAN, RIGHTTILT ) ;
l e f t A i m e r . a t t a c h (LEFTPAN, LEFTTILT ) ;
// a l l o c a t i n g and p r e p a r i n g b u f f e r s
b u f f e r p a r z = ( char ∗ ) m a l l o c ( BPSIZE ) ;
b u f f e r t o t = ( char ∗ ) m a l l o c ( BTSIZE ) ;
memset ( b u f f e r p a r z , 0 , BPSIZE ) ;
memset ( b u f f e r t o t , 0 , BTSIZE ) ;
}
3.4.3
Il controller dell’applicazione
Il loop non fa altro che chiamare la funzione launcher, decisiva nello sketch,
e applicare un delay tra un’iterazione e l’altra.
40
Listing 14: ottica/Sketch/Rino/Rino.ino
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
void l o o p ( ) {
i f (DEBUG MODE) S e r i a l . p r i n t l n ( ” S t a r t e d ! ” ) ;
launcher ( ) ;
d e l a y (DELAY BETWEEN TESTS ) ;
}
void l a u n c h e r ( ) {
i n t pnt nmb ;
i f (DEBUG MODE) {
S e r i a l . p r i n t ( ” i n i t i a l f r e e memory v a l u e : ” ) ;
S e r i a l . p r i n t l n ( freeMemory ( ) ) ;
}
creceive ( buffer parz );
switch ( b u f f e r p a r z [ 0 ] ) {
case ’ c ’ :
i f (DEBUG MODE) S e r i a l . p r i n t l n ( ” c a l i b r a t e ” ) ;
pnt nmb = a t o i ( b u f f e r p a r z + 1 ) ;
c l e a n ( b u f f e r p a r z , BPSIZE ) ;
r e s 1 = c a l i b r a t e (WHO STARTS, pnt nmb ) ;
d e l a y (SOUND DELAY ) ;
change laser ( ) ;
r e s 2 = c a l i b r a t e ( 1 − WHO STARTS, pnt nmb ) ;
d e l a y (SOUND DELAY ) ;
end ( ) ;
allToString ( res1 , b u f f e r t o t ) ;
Serial . println ( buffer tot );
c l e a n ( b u f f e r t o t , BTSIZE ) ;
destroy ( res1 ) ;
allToString ( res2 , b u f f e r t o t ) ;
Serial . println ( buffer tot );
c l e a n ( b u f f e r t o t , BTSIZE ) ;
destroy ( res2 ) ;
break ;
case ’ s ’ :
i f (DEBUG MODE) S e r i a l . p r i n t l n ( ” t e s t ” ) ;
t u r n o n l a s e r (RED ) ;
t u r n o n l a s e r (GREEN) ;
c l e a n ( b u f f e r p a r z , BTSIZE ) ;
creceive ( buffer tot );
p1 = c r e a t e P o i n t s ( b u f f e r t o t ) ;
c l e a n ( b u f f e r t o t , BTSIZE ) ;
creceive ( buffer tot );
p2 = c r e a t e P o i n t s ( b u f f e r t o t ) ;
c l e a n ( b u f f e r t o t , BTSIZE ) ;
i f (DEBUG MODE) {
a l l T o S t r i n g ( p1 , b u f f e r t o t ) ;
Serial . println ( buffer tot );
c l e a n ( b u f f e r t o t , BTSIZE ) ;
a l l T o S t r i n g ( p2 , b u f f e r t o t ) ;
Serial . println ( buffer tot );
c l e a n ( b u f f e r t o t , BTSIZE ) ;
}
r e s 1 = e x e c u t e ( p2 , WHO STARTS ) ;
d e s t r o y ( p2 ) ;
change laser ( ) ;
r e s 2 = e x e c u t e ( p1 , 1 − WHO STARTS ) ;
d e s t r o y ( p1 ) ;
t u r n o f f l a s e r (RED ) ;
t u r n o f f l a s e r (GREEN) ;
end ( ) ;
allToString ( res1 , b u f f e r t o t ) ;
Serial . println ( buffer tot );
c l e a n ( b u f f e r t o t , BTSIZE ) ;
destroy ( res1 ) ;
allToString ( res2 , b u f f e r t o t ) ;
Serial . println ( buffer tot );
c l e a n ( b u f f e r t o t , BTSIZE ) ;
destroy ( res2 ) ;
break ;
default :
i f (DEBUG MODE) S e r i a l . p r i n t l n ( ”command n o t r e c o g n i z e d ” ) ;
c l e a n ( b u f f e r p a r z , BPSIZE ) ;
}
}
Questa funzione è il controller dell’applicazione, ha infatti il compito di dialogare con il computer e lanciare i test da effettuare. Chiamando la funzione
creceive, linea 75, riceve dal computer un primo messaggio. Se questo è
41
”cn”, dove n è un numero intero, calibrerà lo strumento per x punti, se invece
riceve ”s” inizierà l’esecuzione del test vero e proprio.
La calibrazione ha il fine di lasciar mirare all’operatore i punti sullo schermo
di Hess/Lancaster, in modo da avere un riferimento per quando dovrà eseguire il test vero e proprio. Alla riga 79 si ottiene il numero di punti da mirare, per laser. Per questa breve comunicazione si è usato il buffer parziale che
adesso viene ripulito con clean. Successivamente con la funzione calibrate
si effettua la prima calibrazione sul laser specificato in pwhich. Il delay successivo è stato introdotto per distinguere il suono prodotto dall’ultima conferma del paziente e quello che segnala il cambio laser, emesso dalla funzione
change laser. La calibrazione viene poi ripetuta per l’altro laser. Con la
funzione end si emette un suono che indica la conclusione della calibrazione.
Quanto fatto dopo è solamente l’invio delle liste di coordinate puntate servendosi del buffer tot, e la deallocazione delle liste di punti con la funzione
destroy che dealloca un’intera lista dato un puntatore ad essa.
Nel caso in cui si è dato comando di iniziare il test, ovvero si è inviato ”s” ad
Arduino, entriamo nel secondo case dello switch (linea 96). Vengono innanzitutto accesi entrambi i laser e ripulito, come nell’altro case, il buffer parz,
come da convenzione. Vengono poi, in due momenti differenti, ricevute le liste
di punti nel buffer tot e create due liste concatenate di essi che saranno
puntate da p1,p2. Se il programma si trova in debug mode vengono mandate
via seriale le liste create per verificare che non ci siano errori nella ricezione.
Dopo questo viene eseguito il test con la funzione execute. Si noti che viene
selezionato il primo laser, pwhich, mentre viene passata la seconda lista di
punti p2. Ciò accade perchè pwhich verrà controllato dall’utente mentre
1 − pwhich da Arduino, sarà quest’ultimo a mirare p2. Con destroy viene
deallocata la lista p2, poichè non è più utile. Si segnala il cambio di laser e si
procede a rieseguire il test invertendo le parti. Alla fine si segnala il termine
del test e si restituiscono i risultati al computer.
Vediamo ora le funzioni chiamate da launcher nel dettaglio.
3.4.4
La comunicazione
La comunicazione seriale tra computer e Arduino è spesso motivo di problemi, poichè il protocollo è connectionless, ovvero non viene confermata la
ricezione di un messaggio da parte del ricevente. Si è scelto di implementare
cosı̀ un protocollo connection lato periferica, significa quindi che ogni volta
che il computer invia un messaggio ad Arduino, si mette in attesa dell’ack
di quest’ultimo. La funzione creceive ha il compito di bufferizzare quanto
inviato dal computer. Per far ciò chiama la funzione bufferize che a sua
volta chiama blocking read. Quest’ultima si mette in busy waiting finchè
42
non è presente un carattere nel buffer HW, dopodichè restituisce tale carattere. bufferize continua a chiamare blocking read e a riempire il buffer
finchè non viene letto $, segnale che il computer ha terminato il suo messaggio. Per confermare l’avvenuta ricezione della stringa, si invia tramite la
funzione ack il carattere %. L’implementazione di questo meccanismo ha
reso stabile e robusto lo sketch.
Listing 15: ottica/Sketch/Rino/Rino.ino
161
162
163
164
165
166
167
168
169
170
void c r e c e i v e ( char ∗ b u f f e r ) {
bufferize ( buffer );
ack ( ) ;
}
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
void b u f f e r i z e ( char ∗ b u f f e r ) {
i f (DEBUG MODE) S e r i a l . p r i n t l n ( ” b u f f e r : ” ) ;
int i ;
char c = b l o c k i n g r e a d ( ) ;
f o r ( i =0; c != ’ $ ’ ; i ++) {
i f (DEBUG MODE) S e r i a l . p r i n t l n ( c ) ;
i f ( c >= 0 ) b u f f e r [ i ] = c ;
c = blocking read ( ) ;
}
i f (DEBUG MODE) S e r i a l . p r i n t l n ( ” e x i t b u f f e r : ” ) ;
}
void ack ( ) {
S e r i a l . p r i n t l n ( ’% ’ ) ;
}
Point execute ( Point p ,
i n t whoiswho ) {
Listing 16: ottica/Sketch/Rino/Rino.ino
char b l o c k i n g r e a d ( ) {
char c ;
while ( ( c=S e r i a l . r e a d ())== −1);
return c ;
}
3.4.5
Calibrate
La funzione calibrate prende l’intero relativo al laser che si vuole calibrate
e il numero di punti richiesti, restituisce quindi la lista dei punti mirati.
Chiamando wait for user aspetta che l’operatore sposti il nunchuck sul
punto desiderato e che ne dia conferma. In current viene messo il punto da
lui mirato. Viene qui distinta la prima mira dalle altre, tramite la condizione
n == NULL poichè nel primo caso si assegna a n, testa della futura lista,
l’indirizzo di current, mentre negli altri casi si adopera la funzione add che
automaticamente va a mettere il punto in coda.
Listing 17: ottica/Sketch/Rino/Rino.ino
300
301
302
303
304
305
306
307
308
P o i n t c a l i b r a t e ( i n t who , i n t pnt nmb ) {
P o i n t n = NULL ;
Point current ;
int i ;
i f (DEBUG MODE) S e r i a l . p r i n t l n ( ” c a l i b r a t i o n : ” ) ;
t u r n o n l a s e r ( who ) ;
f o r ( i =0; i < pnt nmb ; i ++) {
i f (DEBUG MODE) S e r i a l . p r i n t l n ( ” i t e r ” ) ;
i f ( n == NULL) {
43
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
c u r r e n t = w a i t f o r u s e r ( who ) ;
i f (DEBUG MODE) S e r i a l . p r i n t l n ( ” j u s t
n = current ;
after
wait for user ” );
}
else {
c u r r e n t = w a i t f o r u s e r ( who ) ;
add ( n , c u r r e n t ) ;
}
i f (DEBUG MODE) {
S e r i a l . print ( ” paziente value : ” ) ;
toString ( current , b u f f e r p a r z ) ;
Serial . println ( buffer parz );
memset ( b u f f e r p a r z , 0 , BPSIZE ) ;
}
}
t u r n o f f l a s e r ( who ) ;
return n ;
}
3.4.6
Wait for user
La seguente funzione prende come parametro il numero associato al laser e
restituisce il punto mirato dal paziente dopo che esso ha spostato il puntatore
e dato conferma. Con la chiamata alla funzione wait for nunchuck il controllo viene ceduto a chi manovra il nunchuck e la funzione non ritorna fino
a che non sia data conferma con la pressione del tasto Z. Fatto ciò vengono
lette le inclinazioni degli angoli dei servomotori relativi al puntatore scelto
e vengono scritte all’interno di container. Infine questo viene restituito al
chiamante.
Listing 18: ottica/Sketch/Rino/Rino.ino
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
P o i n t w a i t f o r u s e r ( i n t which ) {
// 0 , 0 d e f a u l t ;
Point c o n t a i n e r = p o i n t B u i l d e r ( 0 , 0 ) ;
// don ’ t r e m o v e i n i t i a l i z a t i o n : i t c r a s h e s
// i t i s n ’ t an Obj , j u s t a s t r u c t u r e ! !
without
w a i t f o r n u n c h u c k ( which ) ;
i f (DEBUG MODE) S e r i a l . p r i n t l n ( ” a f t e r w a i t f o r n u n c h u c k ” ) ;
switch ( which ) {
case RED:
// r i g h t A i m e r . readVC ( c o n t a i n e r ) ;
rightAimer . readMicroseconds ( container ) ;
break ;
case GREEN:
// l e f t A i m e r . readVC ( c o n t a i n e r ) ;
leftAimer . readMicroseconds ( container ) ;
break ;
}
i f (DEBUG MODE) S e r i a l . p r i n t l n ( ” r e a d t h e v a l u e s ” ) ;
return c o n t a i n e r ;
}
3.4.7
Wait for nunchuck
Questa funzione effettua un polling sul pulsante Z del nunchuck. Finchè
esso non viene premuto continua a prendere i valori del joystick, getJoy, e
aggiornare, update values, il punto mirato dal laser selezionato tramite il
parametro which. Si noti la variabile nunchuck flag: essa viene messa per
evitare che un’unica pressione del tasto Z dia conferma a più punti anzichè
solo ad uno. Con l’introduzione di questo meccanismo ciò viene impedito.
44
Con update values si aggiorna la posizione di un laser passandogli un punto
che contiene lo spostamento del joypad. Con JOYPAD SENSIBILITY si scala
il valore al fine di aumentare o diminuire lo spostamento subito in seguito
ad un certo scostamento del joypad; abbassando il valore viene aumentata
la sensibilità, alzandolo diminuita. Si leggono quindi i valori correnti dei
servomotori relativi al puntatore che si vuole spostare e gli si sommano gli
incrementi/decrementi.
Listing 19: ottica/Sketch/Rino/Rino.ino
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
void w a i t f o r n u n c h u c k ( i n t which ) {
int l a s t , nunchuck flag ;
Point p ;
char ∗ s t r ;
nunchuck flag = 1;
/∗ p e r e v i t a r e c h e una p r e s s i o n e d e l t a s t o a t t i v i p i u ’ e v e n t i c o n s e c u t i v i ,
d e v e v e n i r e l e t t o a l m e n o uno 0 p r i m a d i a t t i v a r e un e v e n t o con
un 1 . p o s s o q u i n d i m e t t e r e l a d i c h i a r a z i o n e d i n u n c h u c k f l a g a l l ’ i n t e r n o
q u e s t a f u n z i o n e i n i z i a l i z z a t o a 1 ∗/
while ( ( ( l a s t = g e t Z ( ) ) ! = 1 ) | | n u n c h u c k f l a g ) {
nunchuck flag = l a s t ;
p = getJoy ( ) ;
u p d a t e v a l u e s ( p , which ) ;
free (p ) ;
d e l a y (POLLING DELAY ) ;
}
// e m i t t i n g s o u n d s
feedback ( ) ;
}
void u p d a t e v a l u e s ( P o i n t q , i n t which ) {
struct point c o n t a i n e r ;
i n t panValue = q−>x / JOYPAD SENSIBILITY ;
i n t t i l t V a l u e = q−>y / JOYPAD SENSIBILITY ;
switch ( which ) {
case RED:
r i g h t A i m e r . r e a d M i c r o s e c o n d s (& c o n t a i n e r ) ;
panValue += c o n t a i n e r . x ;
tiltValue = container . y − tiltValue ;
r i g h t A i m e r . w r i t e M i c r o s e c o n d s ( panValue , t i l t V a l u e ) ;
break ;
case GREEN:
l e f t A i m e r . r e a d M i c r o s e c o n d s (& c o n t a i n e r ) ;
panValue += c o n t a i n e r . x ;
tiltValue = container . y − tiltValue ;
l e f t A i m e r . w r i t e M i c r o s e c o n d s ( panValue , t i l t V a l u e ) ;
break ;
}
}
3.4.8
Execute
Data una lista di punti p e un intero relativo al laser che si vuol far controllare
al paziente whoiswho, execute fa puntare la lista di punti p al laser 1 −
whoiswho, aspettando, per ogni punto, che il paziente tenti di sovrapporvi il
laser whoiswho, da lui controllato. La funzione restituisce la lista di punti
mirata dal paziente.
Listing 20: ottica/Sketch/Rino/Rino.ino
170
171
172
173
174
175
Point execute ( Point p ,
Point t = p ;
P o i n t n = NULL ;
Point current ;
i n t whoiswho ) {
while ( t !=NULL) {
45
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
if
(DEBUG MODE) {
S e r i a l . p r i n t ( ” I ’m gonna aim : ” ) ;
toString (t , buffer parz );
Serial . println ( buffer parz );
memset ( b u f f e r p a r z , 0 , BPSIZE ) ;
}
aim ( t , 1 − whoiswho ) ;
i f ( n == NULL) {
c u r r e n t = w a i t f o r u s e r ( whoiswho ) ;
n = current ;
}
else {
c u r r e n t = w a i t f o r u s e r ( whoiswho ) ;
add ( n , c u r r e n t ) ;
}
i f (DEBUG MODE) {
S e r i a l . p r in t ( ” Valore paziente : ” ) ;
toString ( current , b u f f e r p a r z ) ;
Serial . println ( buffer parz );
memset ( b u f f e r p a r z , 0 , BPSIZE ) ;
}
t = t−>n e x t ;
d e l a y (DELAY BETWEEN POINTS ) ;
}
return n ;
}
46
4
4.1
R
Perchè R
R è un potente linguaggio e framework finalizzato principalmente a calcoli
statistici e alla generazione di espressivi grafici relativi a questi. Esso è, a
differenza di framework simili come MatLab e Mathematica, rigorosamente
opensource e multipiattaforma.
Figure 23: Esempi di grafici R
R è semplice e veloce da imparare e contiene nei suoi moduli base, importati
di default, un ampia e completa gamma di funzioni. E’ un linguaggio interpretato, non tipato, che astrae completamente dalla gestione della memoria,
quindi comodo e immediato. Sito di riferimento sia per i primi passi che
come reference è www.r-project.org.
Per tutte queste ragioni esso è adatto all’utilizzo finale del dispositivo da
parte di un qualsiasi utente non particolarmente esperto di informatica.
4.2
Creare un ponte tra R e Arduino
Punto a sfavore nell’utilizzo del framework R in questo progetto sta nel fatto
di essere un ambiente chiuso, che difficilmente permette l’accesso a risorse
47
gestite dal sistema operativo. Nel nostro caso abbiamo bisogno di accedere
alla porta seriale per poter comunicare con Arduino.
Come si è gia detto nella sezione 3.2, ogni sistema operativo fornisce delle funzioni wrapper, solitamente scritte in C, che permettono di accedere a risorse
del computer, come la porta seriale. Poichè vogliamo che l’applicazione finale
sia eseguibile sui più noti sistemi operativi (Windows, OSX, Linux), teoricamente dovremmo cercare delle librerie per ognuno di essi e creare quindi
dei pacchetti R differenziati almeno per le due grandi famiglie Unix e Windows, completamente diverse. Ricordo che OSX e Linux derivano entrambi
da Unix. Per rendere semplice lo sviluppo di un unico pacchetto che funzioni su tutte le piattaforme menzionate, si è ricorsi al linguaggio Java e al
pacchetto rJava che permette la comunicazione alla JVM tramite R.
Java delega alla JVM il compito di tradurre nel sistema operativo su cui è
installato, chiamate a basso livello. Ogni sistema operativo menzionato ha
una JVM ad hoc. La libreria RXTX http://rxtx.qbang.org/ fornisce delle
classi che permettono di accedere alle porte seriali da java. Una volta installato java, la libreria RXTX, il pacchetto rJava, l’utente potrà comunicare
da R ad Arduino. Definito quanto abbiamo bisogno, possiamo ora procedere
alla creazione di un package unico.
Chiameremo il pacchetto ArduBridge perchè esso funge da ponte tra Arduino e R. La cartella contenente il pacchetto deve avere lo stesso nome del
pacchetto, che deve essere capital letter.
Creiamo poi le cartelle inst, dove andrà il codice java, R, dove risiederanno
gli script, man, manuale, e i file DESCRIPTION, NAMESPACE, che contengono
rispettivamente la descrizione del pacchetto e le direttive riguardo le dipendenze e le funzioni che vogliamo rendere accessibili all’utente.
ArduBridge
inst
javasrc
java
R
man
Listing 21: ottica/R/packages/ArduBridge/inst/javasrc/Arduino.java
1
2
3
4
5
6
7
8
9
10
11
import j a v a . i o . ∗ ;
import
import
import
import
import
gnu .
gnu .
gnu .
gnu .
java
i o . CommPortIdentifier ;
io . SerialPort ;
io . SerialPortEvent ;
io . SerialPortEventListener ;
. u t i l . Enumeration ;
public c l a s s Arduino {
SerialPort
serialPort ;
48
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
/∗ ∗ The p o r t we ’ r e n o r m a l l y g o i n g t o u s e . ∗/
p r i v a t e s t a t i c f i n a l S t r i n g PORT NAMES [ ] = {
” / dev / t t y . usbmodem411 ” , // Mac OS X
” / dev / t t y . usbmodem641 ” ,
” / dev / t t y S 3 3 ” , // L i n u x
” / dev / ttyUSB0 ” ,
”COM3” , // Windows
”COM9” ,
};
/∗ ∗ B u f f e r e d i n p u t s t r e a m f r o m t h e p o r t ∗/
private BufferedReader i n p u t ;
/∗ ∗ The o u t p u t s t r e a m t o t h e p o r t ∗/
private PrintStream output ;
/∗ ∗ M i l l i s e c o n d s t o b l o c k w h i l e w a i t i n g f o r p o r t o p e n ∗/
p r i v a t e s t a t i c f i n a l i n t TIME OUT = 2 0 0 0 ;
/∗ ∗ D e f a u l t b i t s p e r s e c o n d f o r COM p o r t . ∗/
p r i v a t e s t a t i c f i n a l i n t DATA RATE = 9 6 0 0 ;
/∗ ∗ M i l l i s e c o n d s t o w a i t w h i l e A r d u i n o s e t s up s e r i a l c o m m u n i c a t i o n ∗/
p r i v a t e s t a t i c f i n a l i n t SETUP TIME = 2 0 0 0 ;
public void i n i t i a l i z e ( boolean debug mode ) {
CommPortIdentifier portId = null ;
Enumeration portEnum = C o m m P o r t I d e n t i f i e r . g e t P o r t I d e n t i f i e r s ( ) ;
// i t e r a t e t h r o u g h , l o o k i n g f o r t h e p o r t
while ( portEnum . hasMoreElements ( ) ) {
C o m m P o r t I d e n t i f i e r c u r r P o r t I d = ( C o m m P o r t I d e n t i f i e r ) portEnum . n e x t E l e m e n t ( ) ;
f o r ( S t r i n g portName : PORT NAMES) {
i f ( debug mode ) System . o u t . p r i n t l n ( c u r r P o r t I d . getName ( ) + ” =?= ” + portName ) ;
i f ( c u r r P o r t I d . getName ( ) . e q u a l s ( portName ) ) {
portId = currPortId ;
break ;
}
}
}
if
( p o r t I d == n u l l ) {
System . o u t . p r i n t l n ( ” Could n o t
return ;
f i n d COM p o r t . ” ) ;
}
else {
System . o u t . p r i n t l n ( ”RXTX works ” ) ;
}
try {
// o p e n s e r i a l p o r t , and u s e c l a s s name f o r t h e appName .
s e r i a l P o r t = ( S e r i a l P o r t ) p o r t I d . open ( t h i s . g e t C l a s s ( ) . getName ( ) ,
TIME OUT ) ;
// s e t p o r t p a r a m e t e r s
s e r i a l P o r t . s e t S e r i a l P o r t P a r a m s (DATA RATE,
S e r i a l P o r t . DATABITS 8 ,
S e r i a l P o r t . STOPBITS 1 ,
S e r i a l P o r t . PARITY NONE ) ;
// o p e n t h e s t r e a m s
i n p u t = new B u f f e r e d R e a d e r (new I n p u t S t r e a m R e a d e r ( s e r i a l P o r t . g e t I n p u t S t r e a m ( ) ) ) ;
o u t p u t = new P r i n t S t r e a m ( s e r i a l P o r t . g e t O u t p u t S t r e a m ( ) ) ;
// s e t up t i m e
Thread . s l e e p (SETUP TIME ) ;
} catch ( E x c e p t i o n e ) {
System . e r r . p r i n t l n ( e . t o S t r i n g ( ) ) ;
}
}
/∗ ∗
∗ T h i s s h o u l d b e c a l l e d when you s t o p u s i n g t h e p o r t .
∗ T h i s w i l l p r e v e n t p o r t l o c k i n g on p l a t f o r m s l i k e L i n u x .
∗/
public synchronized void c l o s e ( ) {
i f ( s e r i a l P o r t != n u l l ) {
serialPort . close ();
}
}
public void w r i t e ( S t r i n g
output . p r i n t ( s ) ;
}
s ) throws E x c e p t i o n {
public S t r i n g r e a d ( ) throws E x c e p t i o n {
while ( ! i n p u t . r e a d y ( ) ) ;
return i n p u t . r e a d L i n e ( ) ;
49
95
96
97
98
99
100
101
102
103
104
105
}
public s t a t i c void main ( S t r i n g [ ] a r g s ) throws E x c e p t i o n {
boolean dmode = f a l s e ;
Arduino main = new Arduino ( ) ;
i f ( a r g s . l e n g t h > 0 && a r g s [ 0 ] . e q u a l s ( ”−d” ) ) dmode=true ;
main . i n i t i a l i z e ( dmode ) ;
main . c l o s e ( ) ;
}
}
Nel listato 21 è mostrato il codice java che permette la comunicazione seriale fornendo le interfacce read, write. Esso è stato preso da http://
playground.arduino.cc/Interfacing/Java e opportunamente modificato.
In particolare sono state aggiunte delle porte nell’array PORT NAMES (linee 1319), delle stampe di debug attivate solo in debug mode e le funzioni read,
write. Il main ha solo funzione di debug e non viene usato nel package.
Nella cartella java si trova Arduino.jar, che è l’archivio java di Arduino.java,
ed è quello effettivamente usato dal codice R.
Nella cartella R si trova innanzitutto il file che permette l’inclusione del .jar,
ovvero onLoad.R
Listing 22: ottica/R/packages/ArduBridge/R/onLoad.R
. onLoad <− function ( l i b n a m e , pkgname ) {
. j p a c k a g e ( pkgname , l i b . l o c = l i b n a m e )
}
Inoltre è presente il codice R che definisce sia i wrapper alle funzioni read,write
del codice del .jar, sia le funzioni che usando le due citate implementano
l’interfaccia del package R richiesto dai requisiti di progetto.
Listing 23: ottica/R/packages/ArduBridge/R/ArduBridge.R
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
c r t A r d u <− function ( ) {
a r d <− . jnew ( ” Arduino ” )
return ( a r d )
}
s r l I n i t <− function ( a r d ) {
. j c a l l ( ard , ”V” , ” i n i t i a l i z e ” , FALSE)
}
s r l R e a d <− function ( a r d ) {
i n p u t <− . j c a l l ( ard , ”S” , ” r e a d ” )
return ( i n p u t )
}
s r l W r i t e <− function ( ard , o u t S t r ) {
. j c a l l ( ard , ”V” , ” w r i t e ” , o u t S t r )
}
s r l C l o s e <− function ( a r d ) {
. j c a l l ( ard , ”V” , ” c l o s e ” )
}
waitForAck <− function ( a r d ) {
ack <− s r l R e a d ( a r d )
i f ( ack == ”%” ) {
p r i n t ( ” ack r e c e i v e d ” )
} e l s e print ( ” e r r o r ” )
}
c a l i b r a t e <− function ( ard , pntNmb ) {
50
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
s r l W r i t e ( ard , paste ( ” c ” , t o S t r i n g ( pntNmb ) , ”$” , s e p=” ” ) )
waitForAck ( a r d )
t o k e n 1 <− s r l R e a d ( a r d )
t o k e n 2 <− s r l R e a d ( a r d )
l s t 1 <− eval ( parse ( text = paste ( ’ c ( ’ , t o k e n 1 , ’ ) ’ ) ) )
l s t 2 <− eval ( parse ( text = paste ( ’ c ( ’ , t o k e n 2 , ’ ) ’ ) ) )
inMtx1 <− matrix ( l s t 1 , nrow = 2 )
inMtx2 <− matrix ( l s t 2 , nrow = 2 )
r e s u l t s <− l i s t ( inMtx1 , inMtx2 )
return ( r e s u l t s )
}
s t a r t T e s t <− function ( ard , o u t L s t ) {
s r l W r i t e ( ard , ” s $” )
waitForAck ( a r d )
o u t S t r 1 <− t o S t r i n g ( o u t L s t [ [ 1 ] ] )
o u t S t r 2 <− t o S t r i n g ( o u t L s t [ [ 2 ] ] )
o u t S t r 1 <− paste ( o u t S t r 1 , ”$” , s e p=” ” )
o u t S t r 2 <− paste ( o u t S t r 2 , ”$” , s e p=” ” )
s r l W r i t e ( ard , o u t S t r 1 )
waitForAck ( a r d )
s r l W r i t e ( ard , o u t S t r 2 )
waitForAck ( a r d )
t o k e n 1 <− s r l R e a d ( a r d )
t o k e n 2 <− s r l R e a d ( a r d )
l s t 1 <− eval ( parse ( text = paste ( ’ c ( ’ , t o k e n 1 , ’ ) ’ ) ) )
l s t 2 <− eval ( parse ( text = paste ( ’ c ( ’ , t o k e n 2 , ’ ) ’ ) ) )
inMtx1 <− matrix ( l s t 1 , nrow = 2 )
inMtx2 <− matrix ( l s t 2 , nrow = 2 )
r e s u l t s <− l i s t ( inMtx1 , inMtx2 )
return ( r e s u l t s )
}
b r i d g e W r i t e <− function ( l s t , f i l e n a m e , ncol )
{
f o r ( i i n 1 : length ( l s t ) ) {
write ( t ( l s t [ [ i ] ] ) , f i l e = f i l e n a m e , s e p =” \ t ” , append = TRUE,
write ( c ( ” ” ) , f i l e = f i l e n a m e , append = TRUE)
}
}
b r i d g e W r i t e D i f f <− function ( l s t 1 , l s t 2 , f i l e n a m e , ncol )
{
f o r ( i i n 1 : length ( l s t 1 ) ) {
write ( t ( abs ( l s t 1 [ [ i ] ] − l s t 2 [ [ i ] ] ) ) , f i l e = f i l e n a m e ,
write ( c ( ” ” ) , f i l e = f i l e n a m e , append = TRUE)
}
}
b r i d g e D i f f <− function ( l s t 1 , l s t 2 )
{
l <− l i s t ( abs ( l s t 1 [ [ 1 ] ] − l s t 2 [ [ 1 ] ] ) ,
return ( l )
}
ncolumns = ncol )
s e p =” \ t ” , append = TRUE,
ncolumns = ncol )
abs ( l s t 1 [ [ 2 ] ] − l s t 2 [ [ 2 ] ] ) )
Per comunicare con la JVM dobbiamo usare le funzioni forniteci dal package rJava. Per creare oggetti si usa .jnew con parametro una stringa
contenente il nome dell’oggetto. La funzione crtArdu semplicemente crea
un oggetto Arduino. Per chiamare metodi si usa la funzione .jcall che
prende come parametri, l’oggetto, una stringa indicante il tipo ritornato,
una stringa con il nome del metodo e i vari parametri di questo. Un esempio si vede nella funzione srlInit che, dato un oggetto Arduino, chiama il
suo metodo initialize con parametro false, ovvero per una esecuzione
normale, non in debug mode. Questa inizializza la comunicazione seriale.
srlRead, srlWrite sono i wrapper dei metodi java read, write mentre
srlClose di close che termina la connessione. waitForAck attende di ricevere via seriale il carattere di ack %, secondo quanto definito nel protocollo
di comunicazione implementato.
51
calibrate prende l’oggetto e il numero di punti che si vogliono calibrare e
restituisce una lista di due matrici 2*n rappresentante i microsecondi equivalenti agli angoli assunti dai servomotori per mirare i punti. Nel dettaglio
manda via seriale la stringa ”cn$” dove n è il numero di punti e $ è il carattere di termine del messaggio come definito dal nostro protocollo. Attende
poi l’ack da parte di Arduino, riceve le due liste di punti che trasforma prima
in vettori e poi in matrici, ritorna una lista di queste.
startTest riceve l’oggetto e una lista di due matrici e restituisce la lista
delle matrici risultato. Prima di tutto invia ad Arduino ”s$” e attende un
ack da esso, trasforma le matrici in stringhe e ci aggiunge il carattere $ alla
fine, le manda quindi al dispositivo e si mette in attesa. Una volta pronte
le stringhe finali rappresentanti i punti le trasforma prima in vettori e poi
in matrici come nella funzione calibrate. bridgeWrite prende una lista di
matrici, una stringa specificante il nome del file su cui scrivere e il numero
di punti presenti in ognuna delle due matrici. Scrive sul file specificato il
contenuto di esse. bridgeWriteDiff è simile alla precedente ma accetta due
liste di matrici e ne scrive sul file i valori assoluti delle differenze. bridgeDiff
come bridgeWriteDiff ma restituisce il risultato anzichè scriverlo su file.
Nel file NAMESPACE qui sotto, vediamo scritte le direttive che ci permettendo
di importare package ed esportare, e quindi rendere accessibili, le funzioni
definite in ArduBridge.R all’utente che importa il pacchetto.
Listing 24: ottica/R/packages/ArduBridge/NAMESPACE
import ( ” rJava ” )
export ( ” crtArdu ” )
export ( ” s r l I n i t ” )
export ( ” srlRead ” )
export ( ” srlWrite ” )
export ( ” s r l C l o s e ” )
export ( ” c a l i b r a t e ” )
export ( ” startTest ” )
export ( ” bridgeWrite ” )
export ( ” bridgeWriteDiff ” )
export ( ” bridgeDiff ” )
In DESCRIPTION invece una descrizione minimale del pacchetto.
Listing 25: ottica/R/packages/ArduBridge/DESCRIPTION
Package : ArduBridge
Type : Package
T i t l e : A b r i d g e between Arduino and R
Version : 1.0
Date : 2013−01−07
Author : Matteo P e s s i n a
M a i n t a i n e r : <matteo . p e s s i n a 3 @ s t u d e n t i . u n i m i . i t >
D e s c r i p t i o n : T h i s p a c k a g e p r o v i d e s p r i m i t i v e f u n c t i o n s f o r communication between
Arduino and R . I t p r o v i d e s a l s o some f u n c t i o n s f o r t h e e x e c u t i o n o f
Hess / L a n c a s t e r t e s t .
L i c e n s e : GPL
Depends : r J a v a
52
5
Installazione e tutorial
5.1
Prerequisiti
E’ necessario installare:
• Una recente versione del Java Runtime Environment (JRE) e conoscerne
la directory di installazione. Il pacchetto rJava infatti utilizza questo
come ponte per accedere alla seriale e comunicare cosı̀ con Arduino.
• Una recente versione dell’Arduino IDE; questo fornisce i driver necessari alla comunicazione con il chip controller USB-RS232 all’interno
dell’Arduino Board.
• Git, source code management system, per ottenere le build e il codice
sorgente del progetto.
• Il software di statistica R, il nostro frontend per l’operatore che vuole
comunicare con il dispositivo, calibrarlo ed eseguire i test.
Ci si posizioni ora in una directory dove si vuol far risiedere il materiale
relativo al progetto e si esegua il seguente comando al fine di scaricare il
repository del progetto.
$git
clone
g i t o l i t e @ s l −l a b . i t : o t t i c a
Nella directory /ottica/Archive/ si trova il necessario per l’installazione del
software che permette di comunicare con il dispositivo Rino. Nelle successive
sezioni è spiegato nel dettaglio le operazioni da svolgere.
5.2
5.2.1
RXTX
OSX
Listing 26: Installazione RxTX in OSX
1
2
3
$ u n z i p / A r c h i v e / r x t x −2.1−7− b i n s −r 2 . z i p
$cp / A r c h i v e / r x t x −2.1−7− b i n s −r 2 /RXTXComm. j a r / L i b r a r y / Java / E x t e n s i o n s /
$cp / A r c h i v e / r x t x −2.1−7− b i n s −r 2 /Mac OS X/ l i b r x t x S e r i a l . j n i l i b / L i b r a r y / Java / E x t e n s i o n s /
Innanzitutto viene scompattato l’archivio contente la RXTX java library per
la comunicazione seriale; si procede quindi copiando il file .jar (indipendente
dalla piattaforma utilizzata) e lo si pone nella cartella dove java cerca le
estensioni; si fa infine lo stesso con il file dipendente dal sistema operativo
utilizzato.
53
5.2.2
Linux
Listing 27: Installazione RXTX in Linux
1
2
3
4
5
$ u n z i p / A r c h i v e / r x t x −2.1−7− b i n s −r 2 . z i p
$cp / A r c h i v e / r x t x −2.1−7− b i n s −r 2 /RXTXComm. j a r / u s r / l i b / jvm / j a v a −7− o r a c l e / j r e / l i b / e x t /
$cp / A r c h i v e / r x t x −2.1−7− b i n s −r 2 / Linux / i 6 8 6 −unknown−l i n u x −gnu / l i b r x t x S e r i a l . s o
/ u s r / l i b / jvm / j a v a −7− o r a c l e / j r e / l i b / i 3 8 6 /
$ l n −s / dev /ttyACM0 / dev / t t y S 3 3
Viene estratto l’archivio e vengono copiati il .jar generico e la libreria .so in
cartelle specifiche del jre. Quelle scritte sopra potrebbero non corrispondere
con il jre di una differente macchina; l’importante è mantenere invariato il
postfisso da jre/ in poi. Infine viene creato un link simbolico allo special
file /dev/ttyACM0 (relativo ad Arduino) chiamato /dev/ttyS33. Questa
operazione è necessaria poichè la libreria RXTX, in ambiente linux, controlla
solo gli special file ttyS.
5.2.3
1
2
3
4
Windows
A r c h i v e \ r x t x −2.1−7− b i n s −r 2 \RXTXComm. j a r
C: \ Programmi \ j a v a \ j r 7 \ l i b \ e x t \
A r c h i v e \ r x t x −2.1−7− b i n s −r 2 \Windows\ r x t x s e r i a l . d l l
C: \ Programmi \ j a v a \ j r 7 \ l i b \ b i n \
Si copi il file alla riga 1 nel percorso alla riga 2 e si faccia lo stesso con il file
a riga 3 e il percorso a riga 4.
5.2.4
Verifica
Per verificare il corretto funzionamento della libreria RXTX:
• Si colleghi il dispositivo ad una qualsiasi porta usb;
• Si lanci il file Arduino.class tramite java.
$cd A r c h i v e /R/ p a c k a g e s / ArduBridge / i n s t / j a v a /
$ j a v a − j a r Arduino . j a r
Listing 28: RXTX funzionante
Experimental :
JNI OnLoad c a l l e d .
Stable Library
=========================================
N a t i v e l i b V e r s i o n = RXTX−2.1−7
Java l i b V e r s i o n
= RXTX−2.1−7
RXTX works
Listing 29: RXTX non trova la porta Arduino
Experimental :
JNI OnLoad c a l l e d .
Stable Library
=========================================
N a t i v e l i b V e r s i o n = RXTX−2.1−7
Java l i b V e r s i o n
= RXTX−2.1−7
Could n o t f i n d COM p o r t .
54
Se viene stampato a schermo il listato 28 allora la libreria ha trovato la porta
corrispondente ad Arduino; se viene mostrato il listato 29 semplicemente la
porta seriale non è inserita all’interno di Arduino.jar e necessita una veloce
modifica di cui si parla nella sezione 5.6.1. Nel caso di errori differenti si faccia
riferimento al wiki http://rxtx.qbang.org/wiki/index.php/Main_Page.
5.3
ArduBridge
5.3.1
OSX e Linux
Listing 30: show .libPaths
$R
>. l i b P a t h s ( )
Nel listato 30 viene avviato R e dato il comando che mostra i path delle
librerie. E’ necessario sceglierne uno dove installare il pacchetto.
Listing 31: Installazione ArduBridge
$cd o t t i c a /R/ p a c k a g e s /
$R CMD INSTALL − l <p e r c o r s o s c e l t o p r e c e d e n t e m e n t e > ArduBridge
Portarsi nella cartella packages come indicato in 31. Compilare quindi il
pacchetto con il comando seguente.
5.3.2
Windows
In Windows è possibile installare il pacchetto direttamente dalla GUI caricando l’archivio ArduBridge.zip presente nella cartella ottica/Archive/.
Nota che in questo caso non si compila il pacchetto. La compilazione di un
pacchetto R su Windows necessita di tools aggiuntivi di cui non parleremo.
5.3.3
Verifica
Arduino deve essere collegato tramite usb durante la verifica.
Listing 32: Verifica ArduBridge
$R
> l i b r a r y ( ArduBridge )
>a r d <− c r t A r d u ( )
> s r l I n i t ( ard )
Se il pacchetto è installato e settato correttamente, a seguito dei comandi
qui sopra, apparirà il listato 28.
55
5.4
Compilazione Sketch
Al fine della compilazione dello sketch ottica/Sketch/Rino/Rino.ino, che
contiene il codice per fare eseguire ad Arduino il test in questione, è necessario
copiare le librerie Aimer, Point e FreeMemory situate in Sketch/Libraries/
nella cartella delle librerie user-defined di Arduino, dipendente dal sistema
operativo.
5.4.1
OSX
La cartella delle librerie user-defined è ∼/Documents/Arduino/libraries/;
è possibile spostarle manualmente o utilizzare lo script restore libraries mac.sh.
5.4.2
Linux
Su Arduino IDE 1.0 si verificano dei problemi nella compilazione della libreria
Wire a causa di un bug. Si consiglia quindi di installare una versione ≥ 1.0.3.
La locazione della cartella libraries dipende dal tipo di installazione, si faccia
quindi riferimento al sito arduino.cc nel caso non se ne sia a conoscenza.
5.4.3
Windows
Come per OSX, basta spostare le librerie Aimer, Point, MemoryFree nella
cartella nomeutente/Documenti/Arduino/libraries.
5.5
Tutorial per l’uso
Listing 33: Esempio d’uso
> l i b r a r y ( ArduBridge )
Carico i l pacchetto r i c h i e s t o : rJava
> a r d <− c r t A r d u ( )
> s r l I n i t ( ard )
Experimental :
JNI OnLoad c a l l e d .
Stable Library
=========================================
N a t i v e l i b V e r s i o n = RXTX−2.1−7
Java l i b V e r s i o n
= RXTX−2.1−7
RXTX works
> v a l u e s <− c a l i b r a t e ( ard , 4 )
[ 1 ] ” ack r e c e i v e d ”
> values
[[1]]
[ ,1] [ ,2] [ ,3] [ ,4]
[ 1 , ] 1283 1473 1473 1283
[ 2 , ] 1013
999 1138 1144
[[2]]
[1 ,]
[2 ,]
[ ,1] [ ,2] [ ,3] [ ,4]
1610 1791 1783 1599
1091 1105 1235 1219
> r e s <−
[ 1 ] ” ack
[ 1 ] ” ack
[ 1 ] ” ack
> res
s t a r t T e s t ( ard ,
received ”
received ”
received ”
values )
56
[[1]]
[1 ,]
[2 ,]
[ ,1] [ ,2] [ ,3] [ ,4]
1290 1473 1475 1286
1013
999 1138 1142
[[2]]
[1 ,]
[2 ,]
[ ,1] [ ,2] [ ,3] [ ,4]
1607 1792 1784 1599
1093 1104 1236 1220
> b r i d g e D i f f ( res , values )
[[1]]
[ ,1] [ ,2] [ ,3] [ ,4]
[1 ,]
7
0
2
3
[2 ,]
0
0
0
2
[[2]]
[ ,1] [ ,2] [ ,3] [ ,4]
[1 ,]
3
1
1
0
[2 ,]
2
1
1
1
> bridgeWrite ( values , ” values . txt ” , 4)
> bridgeWrite ( res , ” r e s . txt ” , 4)
> bridgeWriteDiff ( res , values , ” d i f f . txt ” , 4)
Si apra R e si esegua quanto visto nel listato 33. Per prima cosa vengono importate le funzioni del pacchetto ArduBridge con il comando library. Viene
poi creato un oggetto Arduino e viene inizializzata la seriale come mostrato
nella sezione 5.3.3. Fatto ciò viene chiamata la funzione calibrate che
prende come parametri l’oggetto Arduino e il numero di punti che si vogliono
calibrare, in questo caso 4. A questo punto l’operatore mira i punti di riferimento che vuole fissare per il test. I valori vengono salvati nella lista values
che contiene due matrici 2 ∗ n, dove ogni colonna rappresenta un punto: in
prima riga il valore pan, in seconda tilt. Dando alla funzione startTest
l’oggetto Arduino e i valori dei punti di riferimento, viene cominciato effettivamente il test, il paziente dovrà quindi comandare il dispositivo tramite il
nunchuck. Il risultati del test vengono messi nella variabile res, dello stesso
tipo di values. Per vedere subito la differenza tra i punti di riferimento e
quelli mirati dal paziente si utilizza la funzione bridgeDiff che, prese due
liste di matrici, restituisce una lista di matrici rappresentanti le differenze
delle prime due in valore assoluto. Vengono poi salvati i punti di riferimento
e i punti mirati dal paziente, su un file, tramite la funzione bridgeWrite;
questa prende tre argomenti: la lista di matrici che si vuole salvare, il nome
del file, e il numero di punti mirati. Vengono poi salvati gli scostamenti tra
i punti mirati con la funzione bridgeWriteDiff che prende le due liste, il
nome del file e i punti mirati. Si noti che sarebbe stato anche possibile salvare
in una variabile gli scostamenti tramite la funzione bridgeDiff e scriverli poi
con bridgeWrite. bridgeWriteDiff altri non è che la combinazione delle
due. I file vengono creati se non sono presenti, nel caso invece lo fossero, i
dati vengono semplicemente ”appesi”. In questo modo è possibile scrivere
anche tutti i risultati su un unico file.
Un ulteriore tutorial nella forma di screencast è disponibile al seguente indirizzo.
57
http://sl-lab.it/dokuwiki/doku.php?id=tesi:phsclcmptnglncstr
5.6
5.6.1
Troubleshooting
Could not find COM port
Viene mostrato Could not find COM port quando la porta seriale a cui è
connesso Arduino non è tra quelle in lista nel codice sorgente del listato 21
linee 13-20.
Listing 34: debug mode
$cd A r c h i v e /R/ p a c k a g e s / ArduBridge / i n s t / j a v a /
$ j a v a − j a r Arduino . j a r −d
Ci sono vari modi per capire il nome del device file a cui è associato Arduino;
uno di questi consiste nell’eseguire in debug mode Arduino.jar come nel listato 34. Verrà quindi stampato a schermo quanto si trova alla linea 40 del
listato 21: sulla sinistra le porte attive, sulla destra quelle inserite nel codice.
Ci sono una lista di porte che sono sempre attive ma che non sono relative
ad Arduino:
OSX /dev/cu.Bluetooth-Modem, /dev/tty.Bluetooth-Modem,
/dev/cu.Bluetooth-PDA-Sync, /dev/tty.Bluetooth-PDA-Sync;
Win COM1.
Ne apparità un’altra nella lista che non è tra queste; questa sarà quella cercata. Aggiungerla quindi nella lista presente nel codice di Arduino.java a riga
19; ricompilare Arduino.java e ricreare l’archivio Arduino.jar; dopodichè ricompilare il pacchetto ArduBridge seguendo le istruzioni nella sezione 5.3.1.
(la compilazione è spiegata solo su OSX e Linux)
Su Linux non ha senso effettuare questo test, infatti abbiamo definito noi il
nome del device file relativo ad Arduino (sezione 5.2), ovvero /dev/ttyS33.
6
Conclusioni
Il progetto è stato fin dall’inizio molto ambizioso poiché si voleva costruire un apparecchio medico in grado di dare risultati precisi con una bassa
disponibilità economica e senza l’aiuto di esperti nei settori della misura e
della valutazione degli errori.
Abbiamo imparato che non a caso esistono tantissime branche della tecnologia, e un progetto completo, con importanti esigenze in campo medico,
necessita di una forte modularizzazione, affidando ad ogni persona coinvolta
58
compiti che riguardino le discipline di sua competenza, e di buone disponibilità economiche.
Il modulo che non si è riusciti a portare a termine completamente è stato
quello della misura e della calibrazione; su questo si è fatto un passo indietro
e si è delegato a terzi.
Pensiamo comunque che nella sezione relativa si sia costruito correttamente
un modello matematico che possa risolvere i problemi almeno in via teorica;
riteniamo quindi che quella possa essere utile a qualsiasi persona interessata
alla risoluzione di problemi di questo tipo.
Per quanto riguarda le restanti parti del progetto, si può dire con soddisfazione di essere riusciti a portarle a termine rendendo la comunicazione,
l’esecuzione e le interfacce, stabili e robuste. Poichè l’approccio seguito è
quello blackbox, diverse librerie e funzioni all’interno dello sketch saranno
utilizzabili anche in altri progetti poichè assolutamente aspecifiche e rilasciate sotto licenza GPL.
In conclusione diciamo di essere riusciti a portare a termine il progetto comprendente i moduli inerenti al percorso di studi di un corso di informatica.
References
[1] J.-B. Weiss, Squilibri oculomotori e coordimetria. CERES Editeurs, Paris,
1st Edition, 1988.
[2] Roodhooft J. M., Screen tests used to map out ocular deviations. US
National Library of Medicine National Institutes of Health, http://www.
ncbi.nlm.nih.gov/pubmed/18018429, 1997.
[3] Massimo Banzi, Arduino: la guida ufficiale. Tecniche Nuove, Varese, 2a
Edizione, 2012.
[4] Charles Platt, Make: Electronics. O’Reilly, Canada, 1st Edition, 2009.
[5] Tobias Verbeke, Hello Java World! A Tutorial for Interfacing to
Java Archives inside R Packages. http://cran.r-project.org/web/
packages/helloJavaWorld/vignettes/helloJavaWorld.pdf , 2010.
[6] Sito ufficiale Arduino, http://arduino.cc.
[7] Sito ufficiale R, http://www.r-project.org.
59
Scarica

Physical Computing: a case study - SL-Lab