Progettazione e realizzazione di un regolatore PID su architettura ...
Transcript of Progettazione e realizzazione di un regolatore PID su architettura ...
Capitolo 1 - Abstract
1
Capitolo 1
Abstract
Capitolo 1 - Abstract
2
Il presente capitolo ha lo scopo di fornire al lettore una visione d’insieme del progetto discusso in questa tesi.
1. IL PROGETTO
In primo luogo si vuole palesare quanto segue:
il progetto consiste nella progettazione, realizzazione e collaudo
di un regolatore PID digitale su piattaforma RCX.
Per comprendere appieno questa asserzione, è necessario introdurre i tre elementi-
cardine attorno ai quali ruota l’intera attività di sviluppo:
CONTROLLO
PID
La realizzazione di un regolatore industriale
richiede competenze specifiche, quali:
- principi, concetti e teorie che stanno alla
base del controllo modulante;
- caratteristiche della legge di controllo
PID;
- problematiche legate alla realizzazione di
un PID digitale.
Controllo PID
RCX BrickOS
Capitolo 1 - Abstract
3
RCX
L’RCX è un dispositivo costituito, internamente,
da un microcontrollore Hitachi corredato da una
memoria RAM; si interfaccia verso l’esterno
attraverso tre porte d’ingresso e tre d’uscita,
quattro pulsanti e un piccolo display LCD da
cinque cifre. Infine, è dotato di una porta ad
infrarossi che rende possibile la comunicazione
con un qualunque PC.
BRICKOS
BrickOS può essere considerato, allo stesso
tempo, come:
- un sistema operativo per l’RCX, che
permette di gestire a basso livello tutte le
risorse offerte da questo dispositivo;
- un ambiente di sviluppo che offre al
programmatore un insieme minimo di
funzionalità (API) su cui impostare
l’attività di implementazione del software.
Gli unici linguaggi di programmazione
supportati sono C/C++.
Ora che sono noti gli “attori” del progetto, è possibile scendere ad un livello di
dettaglio più basso, sostenendo quanto segue: l’obiettivo finale consiste nel realizzare
un’applicazione che, una volta scaricata sull’RCX, “trasformi” tale dispositivo in un
regolatore PID completamente autonomo; pertanto il software deve:
- essere in grado di leggere le informazioni provenienti dai sensori (porte
d’ingresso), eseguire l’algoritmo PID ed inviare il nuovo valore del controllo agli
attuatori (porte d’uscita);
- offrire un’interfaccia operatore (basata sull’uso del display LCD e dei pulsanti)
per consentire la visualizzazione/modifica dei parametri di sistema.
Infine, deve essere possibile gestire o un singolo regolatore o più regolatori in cascata.
Capitolo 1 - Abstract
4
2. ORGANIZZAZIONE DEL TESTO
Prima di entrare nel vivo della trattazione, si ritiene utile delineare la struttura logica
del testo, costituita da una successione di aree tematiche, a ciascuna delle quali possono
corrispondere uno o più capitoli.
2.1. Cenni teorici sul controllo PID
Sezione dedicata all’esposizione dei principi e concetti che stanno alla base del
controllo modulante; introduce gli elementi essenziali della legge di controllo PID,
soffermandosi sugli aspetti più interessanti per il progetto in questione:
- realizzazione digitale di un regolatore;
- conversione A/D e D/A;
- modulazione PWM.
2.2. Ambiente operativo
Parte rivolta alla presentazione degli strumenti di lavoro, hardware e software, su cui
si basa lo sviluppo del regolatore industriale; in particolare:
- si descrivono le caratteristiche principali dell’RCX e dei dispositivi fisici di cui è
dotato, con particolare riferimento a:
! microcontrollore Hitachi H8/3292;
! memoria RAM;
! ingressi analogici e convertitore A/D (multiplexaggio);
! uscite PWM.
- si introducono gli elementi essenziali del sistema operativo BrickOS:
! BrickOS come punto di contatto tra hardware e software;
! Descrizione del “kernel” del S.O.;
! Gestione delle periferiche (motori, sensori, display lcd e pulsanti).
2.3. Descrizione del progetto
A questo punto il lettore avrà, a propria disposizione, un pacchetto informativo tale
Capitolo 1 - Abstract
5
da poter affrontare, senza alcuna incertezza, le sezioni dedicate al vero e proprio
sviluppo dell’applicazione di controllo.
Il testo riflette completamente l’organizzazione del processo di sviluppo, basato sul
modello di vita a cascata; a ciascuna delle fasi seguenti è dedicato un intero capitolo:
- analisi e specifica dei requisiti: descrizione del funzionamento del prodotto da
realizzare e individuazione delle caratteristiche fondamentali del sistema;
- progettazione: studio delle soluzioni tecniche più opportune (in relazione alle
competenze dei relatori) per il raggiungimento degli obiettivi stabiliti nella fase
precedente;
- programmazione: discussione e motivazione delle principali scelte
implementative concernenti strutture dati, procedure di elaborazione e
modularizzazione;
- integrazione e test di sistema: integrazione dei moduli realizzati e verifica dei
meccanismi di interazione fra essi. Esecuzione dei test sull’applicazione
completa.
Capitolo 2 - Controllo PID
6
Capitolo 2
Controllo PID
Capitolo 2 - Controllo PID
7
In questo capitolo si presenteranno i regolatori industriali che attuano leggi di controllo PID rivolgendo particolare attenzione alla loro realizzazione digitale.
1. INTRODUZIONE
Un sistema di controllo industriale ha, come scopo primario, quello di migliorare sia
l’affidabilità nel funzionamento dell’impianto che le prestazioni del processo produttivo
(in termini di rendimento e/o quantità di prodotto).
Attualmente la scena è dominata dai regolatori a microprocessore, il cui successo è
dovuto a fattori quali l’elevato potenziale di elaborazione, la precisione e la stabilità
(invarianza) nel tempo; si tratta di prodotti con caratteristiche relativamente standard,
basilari nella regolazione e nell’automazione dei processi continui.
Il capitolo inizia con la presentazione della legge di controllo PID nella sua forma
più semplice per poi affrontare l’esposizione degli accorgimenti implementativi
comunemente adottati nella pratica industriale. Infine, si approda alla stesura di uno
pseudo-codice che modellizza il regolatore sottoforma di funzione (nell’accezione
informatica del termine).
E’ opportuno precisare che la locuzione “regolatore PID” può essere causa di
qualche ambiguità, essendo usata per indicare, da una lato, una legge di controllo
diffusamente utilizzata nel mondo industriale e, dall’altro, un prodotto elettronico che
implementa tale legge (spesso arricchita con una serie di funzionalità aggiuntive).
Nel seguito si adotterà la seguente simbologia:
v(t) indica un generico segnale tempo-continuo
V(s) indica la trasformata di Laplace del segnale v(t)
v*(t) indica un generico segnale tempo-discreto
V*(z) indica la trasformata Zeta del segnale v*(t)
Capitolo 2 - Controllo PID
8
2. LEGGE DI CONTROLLO PID
In questo paragrafo e nei successivi si farà essenzialmente riferimento allo schema
classico di controllo in retroazione, rappresentato dalla figura seguente:
Legenda
y°(t) Segnale di riferimento (Set-Point) w(t) Variabile controllata (o di processo) y(t) Misura della variabile controllata e(t) Errore u(t) Variabile di controllo d(t) Disturbo di carico n(t) Rumore di misura PID Regolatore PID P(s) Funzione di trasferimento del processo
La legge di controllo PID è costituita dalla combinazione di tre azioni elementari di
controllo: azione proporzionale, azione integrale, azione derivativa.
2.1. Azione proporzionale
L’azione proporzionale lega algebricamente l’ingresso e(t) e l’uscita u(t) secondo
una costante di proporzionalità Kp detta guadagno proporzionale:
)()( teKtu p ⋅=
Essa dipende dal solo valore attuale, istantaneo dell’errore.
Con un regolatore puramente proporzionale, a regime, con ingressi costanti, l’errore
Capitolo 2 - Controllo PID
9
di regolazione in generale non si annulla ma assume valori dipendenti dagli ingressi
stessi.
Tale azione è caratterizzata da un’elevata reattività all’errore; pertanto, dà il suo
contributo maggiore nelle fasi iniziali del transitorio, mentre perde efficacia a transitorio
esaurito.
2.2. Azione integrale
E’ particolarmente importante nelle applicazioni in quanto assicura un errore nullo a
regime per variazioni a scalino (o valori costanti) del set-point YSP e del disturbo d.
Si ha:
∫= dtteTKtu
I
P )()(
L’integratore non tiene conto del solo valore attuale, istantaneo dell’errore, bensì
anche del suo passato; se, ad esempio, l’errore fosse costante, la variabile di controllo
continuerebbe ad aumentare linearmente.
L’azione integrale è scarsamente reattiva, pertanto non da un contributo significativo
nelle fasi iniziali del transitorio, ma governa in maniera predominante il modo in cui il
sistema va a regime.
2.3. Azione derivativa
Serve a far dipendere il controllo dal futuro dell’errore, cioè dalla direzione in cui
esso si muove e dalla velocità con cui varia.
Si ha:
)()( tedtdTKtu DP=
Il parametro TD dà la misura di quanto lontano nel futuro si spinge la predizione. Tale
azione presenta un’elevata reattività e risulta efficace se TD non è troppo grande rispetto
alle dinamiche temporali dell’errore.
Si osservi la figura seguente:
Capitolo 2 - Controllo PID
10
Nel caso A, la predizione è buona, ovvero TD è correttamente dimensionato; nel caso B,
invece, TD è troppo grande rispetto alle dinamica dell’errore (vedi [1]).
Si intuisce, quindi, che l’azione derivativa, essendo caratterizzata da un’elevata
reattività, apporta un sostanziale ed utile contributo nelle sole fasi iniziali dei transitori.
Il problema principale è dovuto al fatto che amplifica anche gli eventuali rumori di
misura sulla variabile controllata; per questo motivo è necessario utilizzarla con estrema
cautela, in modo tale che non determini eccessive sollecitazioni agli organi attuatori o
inconvenienti al processo.
Ulteriori informazioni sull’azione derivativa possono essere trovate in [1] e [2].
3. ACCORGIMENTI IMPLEMENTATIVI
I regolatori industriali, nella totalità dei casi, implementano una legge di controllo
PID sensibilmente differente da quella ideale esposta nel precedente paragrafo. Tali
accorgimenti sono stati introdotti in risposta a varie esigenze: realizzabilità, flessibilità,
interazioni fra regolatori che compongono sistemi di controllo complessi, e così via. Di
seguito verranno presentati i più comuni accorgimenti implementativi; nel paragrafo
successivo si affronterà la loro realizzazione digitale.
3.1. Derivatore reale
Come introdotto in precedenza, l’azione derivativa deve essere utilizzata con estrema
cautela in quanto, derivando anche le componenti dei segnali ad alte frequenze, può
Capitolo 2 - Controllo PID
11
causare un’eccessiva sollecitazione degli organi attuatori. Pertanto, essa viene associata
ad un filtro passa-basso che ne limita il guadagno ad alta frequenza; il filtro, del primo
ordine, è caratterizzato dalla seguente funzione di trasferimento:
NsTsH
D+=
11)(
con N che, orientativamente, assume valori compresi fra 5 e 10. L’azione derivativa
filtrata amplifica, al più, di NK P .
3.2. Forma standard ISA
L’implementazione delle legge di controllo nei regolatori commerciali, spesso,
assume la seguente forma (si è omesso, per semplicità, d’indicare la dipendenza dalla
variabile complessa s):
( )
−
+++−= YcY
NsTsTE
sTYbYKU SP
D
D
ISPP /1
1
I coefficienti b e c assumono, di regola, valori compresi fra 0 e 1. Un PID in forma
standard ISA permette di elaborare in modo indipendente le risposte al set-point e al
disturbo di carico (vedi [2]). Si consideri:
+
++== cNsT
sTsT
bKYUG
D
D
IP
SPff /1
1
+
++==NsT
sTsT
KYUG
D
D
IPfb /1
11
Il sistema di controllo può essere descritto dal seguente schema a blocchi:
Capitolo 2 - Controllo PID
12
I parametri KP, TI e TD, oltre a N, intervengono nell’anello di retroazione e
consentono di aggiustarne la stabilità e la dinamica, e quindi la risposta al disturbo di
carico; i parametri b e c consentono di intervenire sulla risposta di Y a YSP.
Questa struttura del regolatore si dice “a due gradi di libertà”, intendendo con questo
che i cammini del segnale da yo ad u e da y ad u sono diversi; è possibile, pertanto,
separare i problemi relativi al grado di stabilità e alla reiezione dei disturbi da quelli
relativi all’inseguimento del set-point.
3.2.1. Risposta al Set-Point
Osservando lo schema di riferimento presentato all’inizio di questo capitolo, risulta
evidente che la variabile di controllo (u) risente subito della variazione del set-point (yo),
infatti i due segnali sono separati dalla sola dinamica del PID. A meno che la risposta
del processo sia praticamente istantanea, a fronte di una variazione a scalino del
riferimento l’azione proporzionale subisce una brusca variazione a scalino che andrà a
riflettersi sulla variabile di controllo.
Il peso sul set-point nell’azione proporzionale (parametro b nella forma standard
ISA) viene introdotto proprio per ovviare a questo inconveniente; la possibilità di
limitare gli sbalzi del controllo risulta particolarmente utile, ad esempio, negli anelli
interni dei controlli in cascata, nei quali il set-point non è imposto direttamente da un
operatore, ma fornito dal regolatore esterno. Nel caso in cui b sia diverso da 1, a regime
l’azione proporzionale non sarà nulla; comunque, non influenza le fasi avanzate del
transitorio poiché in esse prevale nettamente l’azione integrale. E’ bene notare come al
crescere di b la risposta al set-point diventi più pronta ma con possibilità di
sovraelongazione; scegliendo b nell’intervallo 0-1 si può trovare un buon compromesso
tra velocità di risposta e sovraelongazione: contenere la sovraelongazione nella risposta
al set-point senza penalizzare la banda del sistema retroazionato.
Un altro possibile accorgimento implementativo riguarda l’introduzione del peso sul
set-point nell’azione derivativa (corrispondente al parametro c nella forma standard
ISA); anche in questo caso l’obiettivo è quello di evitare che il controllo subisca sbalzi
troppo bruschi a fronte di variazioni repentine dell’errore. Il peso c ha influenza solo
negli istanti in cui yo varia (quando è costante la sua derivata è nulla); ne consegue che c
Capitolo 2 - Controllo PID
13
gioca un ruolo fondamentale, ad esempio, negli anelli interni dei controlli in cascata, il
cui set-point può variare di continuo.
Ulteriori informazioni possono essere trovate in [1].
3.2.2. Risposta al Disturbo di Carico
Si consideri lo schema di controllo di riferimento. A meno che la risposta del
processo non sia praticamente istantanea, un disturbo di carico a scalino non provoca
una variazione a scalino dell’errore, infatti la variabile di controllo dipende dal disturbo
di carico tramite la dinamica sia del processo che del PID (vedi [1]). Pertanto, nessuna
delle azioni di controllo subisce sbalzi bruschi. Il problema, in questo caso, è
determinato dal fatto che il regolatore inizia a reagire solo dopo che il processo ha
subito gli effetti del disturbo, ovvero solo quando tali effetti hanno prodotto variazioni
rilevabili dell’uscita y. Alla luce delle ultime considerazioni, si giustifica, almeno
intuitivamente, il fatto che l’inseguimento del set-point e la reiezione dei disturbi di
carico siano due problemi di controllo palesemente differenti.
3.3. Wind-up dell’integratore
Nella maggior parte delle applicazioni, la variabile di controllo di un anello di
regolazione è limitata superiormente e inferiormente. Se il sistema è ben progettato, essa
assume valori abbastanza lontani dai limiti di saturazione, ma può capitare che li
raggiunga in occasione di variazioni rilevanti del set-point o del disturbo di carico.
Quando la variabile di controllo è in saturazione, il processo evolve con ingresso
costante, come se fosse in anello aperto; anche il regolatore si trova ad evolvere in
anello aperto, infatti viene a mancare l’effetto della retroazione tra la sua uscita e
l’errore. Poiché l’integratore è un sistema dinamico non asintoticamente stabile, in
queste condizioni può allontanarsi di molto dal campo di valori utili per il controllo; se
ciò accade, occorre tempo, dopo che l’errore è tornato a valori tali da non richiedere più
la saturazione della variabile di controllo, prima che l’integratore, e con esso l’uscita del
regolatore, ritorni a valori utili per il controllo. La situazione descritta è illustrata nella
figura seguente (tratta da [1]), che riporta il transitorio della risposta ad una variazione a
scalino del set-point:
Capitolo 2 - Controllo PID
14
t1 t2 t3 t4 t5
Note:
- all’istante t1, l’operatore modifica il set-point.
- All’istante t2, il controllo effettivo satura, la variabile controllata assume un andamento costante
e l’errore si ferma ad un valore non nullo; il controllo calcolato, pertanto, continua a crescere.
- All’istante t3, l’operatore si accorge del problema e modifica di nuovo il set-point.
- All’istante t4, l’errore cambia segno ed il controllo calcolato inizia a scendere; il controllo
effettivo, comunque, non si muove finché quello calcolato non esce dalla saturazione.
- All’istante t5, il controllo calcolato torna nel range di valori utili.
Riassumendo, il grafico evidenzia un blocco dell’azione di controllo e un pessimo transitorio della
variabile controllata.
Un modo semplice e diffusamente utilizzato per evitare il wind-up è quello di
arrestare l’integrazione quando interviene la saturazione.
Per catene di regolazione complesse, a più livelli gerarchici o con anelli annidati, la
saturazione di un attuatore o di un regolatore comporta di arrestare l’integrazione in tutti
i regolatori della struttura a monte.
3.4. Commutazione manuale/automatico
Gli operatori di un impianto hanno la facoltà di mettere in qualsiasi momento un
anello di regolazione in manuale o di comandarne il passaggio in automatico. Nel primo
Capitolo 2 - Controllo PID
15
caso la variabile di controllo sarà governata direttamente dall’operatore, mentre nel
secondo caso essa sarà calcolata dal regolatore. Le commutazioni devono avvenire
senza brusche variazioni della variabile di controllo, per evitare transitori indesiderati e
danni agli attuatori.
Al passaggio da automatico a manuale, il controllo (ovvero l’uscita del regolatore
durante il funzionamento in automatico) si deve fermare al valore corrente e l’operatore
può comandare la variabile di controllo impostandone le variazioni
(incrementa/decrementa).
Al passaggio da manuale ad automatico il controllo, invece, deve muoversi a partire
dal valore corrente e verso quello richiesto dal regolatore affinché la variabile
controllata tenda al set-point senza salti.
Molti regolatori dispongono di un ingresso logico, detto forzamento in manuale, che
può causare una commutazione automatico/manuale anche in assenza di una diretta
richiesta dell’operatore; si può pensare, ad esempio, al caso del controllo in cascata: se il
regolatore interno va in manuale, sia l’anello interno che quello esterno risultano aperti;
sarà, pertanto, necessario che il regolatore esterno sia forzato in manuale in modo da
evitare una situazione di inconsistenza.
E’ importante sottolineare che, in una logica di controllo, i comandi locali (impartiti
dagli operatori tramite pannelli posti vicino all’impianto) devono sempre prevalere su
quelli remoti (provenienti da altri regolatori, dal sistema di supervisione, ... posti in zone
differenti da quella in cui è situato l’impianto); di conseguenza, se un operatore mette in
manuale un regolatore, i livelli superiori del controllo devono essere informati della
situazione affinché possano intraprendere opportune azioni di controllo, le quali non
dovranno mai prevaricare i comandi impartiti localmente dagli operatori.
Riassumendo:
- Se localmente si impone il manuale, l’ingresso di forzamento in manuale non ha
effetto;
- Se localmente il regolatore è in automatico, l’ingresso di forzamento in manuale
è attivo e la modalità di funzionamento sarà determinata dalle necessità del resto
del sistema di controllo.
Capitolo 2 - Controllo PID
16
4. REALIZZAZIONE DIGITALE
In questo paragrafo si affronta il problema della realizzazione fisica di un PID
tramite una funzione (nell’accezione informatica del termine) che riceve i valori del set-
point e della variabile controllata e produce il valore della variabile di controllo. Il
tempo che intercorre tra un’esecuzione di questa funzione e la successiva è costante e si
sceglie in sede di sintesi del regolatore stesso; questo ulteriore parametro prende il nome
di “tempo di campionamento” (nel seguito indicato con TS). Esso determina la
granularità del controllo, ovvero la cadenza temporale con cui il regolatore fa variare il
controllo.
4.1. Equivalente Discreto del PID
Per realizzare le azioni di un PID con un sistema di elaborazione digitale, è
necessario ottenerne equivalenti discreti. Il problema della discretizzazione consiste,
data una funzione di trasferimento a tempo continuo e dato un tempo di
campionamento, nel determinare i parametri di una funzione di trasferimento a tempo
discreto che ne approssimi abbastanza bene il comportamento (vedi [1]).
La discretizzazione può essere compiuta secondo vari metodi. Alcuni consistono
nell’applicazione di formule di calcolo numerico: la discretizzazione in avanti ( o
formula esplicita di Eulero), la discretizzazione all’indietro (o formula implicita di
Eulero), il metodo del trapezio (o di Tustin).
Un altro metodo, che fornisce l’equivalente discreto di un sistema dinamico lineare, è
quello dello Zero Order Hold (ZOH), che fornisce il valore esatto dell’uscita agli istanti
di campionamento, nell’ipotesi in cui l’ingresso sia costante nel periodo di
campionamento.
Di seguito si riporta, con riferimento alla forma standard ISA descritta in precedenza,
la discretizzazione del PID reale (nel caso a derivazione d’uscita, ovvero 0=c )
secondo la formula esplicita di Eulero:
Azione Proporzionale
( ))()()()( *** kykbyKkPKsP oPP −=⇔=
Capitolo 2 - Controllo PID
17
Azione Integrale
( ))()()1()()( **** kykyT
TKkIkIsTKsI o
I
SP
I
P −+−=⇔=
Si noti che per il calcolo al primo istante di campionamento è necessario conoscere
)0(*I . La discretizzazione mette in particolare evidenza il problema del wind-up;
infatti, se la variabile controllata satura senza riuscire a raggiungere il set-point e
l’errore si ferma ad un valore costante, si ha che l’equivalente discreto dell’azione
integrale tende a crescere indefinitamente a meno che non si prendano opportuni
provvedimenti.
Azione Derivativa
( ))1()()1()(1
)( **** −−+
−−+
=⇔+
= kykyNTT
NTKkDNTT
TkDNsT
TsKsDSD
DP
SD
D
D
DP
Si noti che per il calcolo al primo istante di campionamento è necessario conoscere
)0(*D e )0(*y .
L’equivalente discreto del PID, pertanto, sarà dato dalla somma degli equivalenti
delle tre azioni:
)()()()( **** kDkIkPkU ++=
4.1.1. Scelta della frequenza di campionamento
La scelta della frequenza di campionamento deve scaturire da un buon compromesso
tra esigenze diverse e, talvolta, contrapposte. Considerazioni relative al costo del
sistema spingono a scegliere un valore basso, affinché la potenza di calcolo necessaria
per eseguire gli algoritmi di controllo sia minima, e i convertitori A/D e D/A più
economici. L’analisi delle prestazioni del sistema di controllo impone, però, un limite
inferiore assoluto; difatti, il processo di sintesi del regolatore pone in essere un insieme
di requisiti dai quali non è possibile prescindere:
- la capacità del controllo di inseguire efficacemente il riferimento, esprimibile sia
in termini di banda passante, che di tempo di salita o di assestamento della
risposta ad una variazione a scalino;
Capitolo 2 - Controllo PID
18
- la capacità di compensare efficacemente i disturbi agenti sul processo;
- la capacità di filtrare adeguatamente i rumori associati ai segnali provenienti dai
sensori.
Si consideri l’inseguimento del riferimento. La condizione che il sistema di controllo
in anello chiuso debba inseguire il riferimento fino alla banda passante π
ω2
CCf =
impone un limite inferiore assoluto alla frequenza di campionamento: per il teorema di
Shannon essa deve essere almeno doppia di Cf . Allo stesso risultato si giunge se si
considera di dover acquisire senza perdita d’informazione le componenti di y in banda
passante. Il limite imposto dal teorema del campionamento è, tuttavia, teorico e non
adeguato alle applicazioni di controllo; in letteratura esistono varie indicazioni di
massima che permettono di individuare la frequenza di campionamento ottimale per il
singolo problema in esame.
Per quanto concerne la compensazione dei disturbi, il periodo di campionamento
rappresenta il valore limite del ritardo di reazione di un sistema di controllo digitale a un
disturbo di carico agente sul processo; pertanto, si tratta di un parametro critico ai fini
della prontezza con cui il controllo reagisce al disturbo.
Ulteriori informazioni sono disponibili in [2].
4.1.2. PID Posizionale e PID Incrementale
La legge di controllo PID può essere espressa in due modi:
- Posizionale, in cui si calcola direttamente, ad ogni passo di campionamento, il
nuovo valore del controllo;
- Incrementale, in cui si calcola, ad ogni passo di campionamento, la variazione del
controllo che, sommata al valore precedente, determina il nuovo valore del
controllo.
La forma incrementale è molto utilizzata nella pratica in quanto semplifica
notevolmente l’implementazione di funzionalità complementari alla mera legge di
controllo (anti-windup, tracking di un segnale, commutazione manuale/automatico,
logiche di blocco, etc… ) nel software che rappresenta l’equivalente discreto del PID.
Risulta immediato esprimere un PID in forma incrementale a partire dalla
Capitolo 2 - Controllo PID
19
discretizzazione sopra operata (vedi [1]).
Sia:
)1()()( *** −−=∆ kvkvkv
la variazione del generico segnale a tempo discreto )(* kv . Sulla base di questa
definizione è possibile formulare le tre azioni del regolatore nel modo seguente:
( )( )
( ))1()()1()(
)()()(
)()()(
****
***
***
−∆−∆+
−−∆+
=∆
−=∆
∆−∆=∆
kykyNTT
KNTkDNTT
TkD
kykyT
TKkI
kykybKkP
SD
D
SD
D
o
I
SP
oP
Si può osservare come le variazioni delle azioni proporzionale e integrale non
dipendano dai loro valori passati, mentre ciò si verifica nel caso dell’azione derivativa.
4.2. Pseudo-codice di un regolatore PID
L’algoritmo per il calcolo dell’equivalente discreto del PID, si compone
essenzialmente di due parti, sempre presenti in un sistema di controllo in tempo reale:
- l’inizializzazione, eseguita una sola volta all’accensione del regolatore;
- il codice ricorsivo, eseguito ad ogni periodo di campionamento; questa parte
presenta criticità rispetto ai tempi di calcolo e all’affidabilità di funzionamento,
per cui deve essere codificato e verificato con particolare cura. Al suo interno si
possono individuare quattro sezioni ben distinte:
! acquisizione degli ingressi e conversione A/D (y e ySP);
! calcolo della variabile di controllo (u);
! scrittura dell’uscita;
! aggiornamento dello stato.
Il codice ricorsivo può essere più o meno complesso, a seconda delle funzionalità
ausiliarie che si decide di implementare; la precedente trattazione teorica conduce alla
codifica dei seguenti servizi, realizzata considerando il PID nella sua forma
incrementale:
- anti-windup: l’implementazione proposta mantiene entro i limiti di saturazione
non solo l’azione integrale, ma tutta l’azione di controllo, il che, ai fini pratici,
Capitolo 2 - Controllo PID
20
non comporta alcuna sostanziale differenza (si ricorda, infatti, che il wind-up è
un fenomeno che interessa solo ed esclusivamente l’integratore). Il frammento di
codice (tratto da [1]) corrispondente è il seguente:
...
/* A questo punto si dispone:
- della variazione del controllo (DeltaCS)
- del valore precedente del controllo (CSold)
- dei limiti di saturazione (CSmax e CSmin) */
CSnew = CSold + DeltaCS;
if (CSnew >= CSmax) CSnew = CSmax;
if (CSnew <= CSmin) CSnew = CSmin;
CSold = CS new;
/* Segue l’attuazione del nuovo controllo CSnew */
...
- logiche di blocco: questa sezione di codice permette di gestire logiche di
controllo che scaturiscono dall’interconnessione di più regolatori; consideriamo,
ad esempio, il caso di due anelli annidati: si può ragionevolmente sostenere che,
quando il regolatore dell’anello interno (Ri) è in saturazione, si deve permettere a
quello dell’anello esterno (Re) di modificare la sua uscita soltanto nella direzione
che fa uscire Ri dalla saturazione. A tale scopo, i regolatori industriali sono dotati
di due uscite booleane (solitamente dette HIsat e LOsat) preposte a segnalare con
un livello logico alto, rispettivamente, le saturazioni superiore e inferiore.
Dispongono, inoltre, di due ingressi booleani (di solito individuati con F+ e F-)
che, se posti a livello logico alto, impediscono all’uscita del regolatore di
crescere o decrescere, rispettivamente. Una possibile implementazione (proposta
da [1]) è la seguente:
...
/* Fplus e Fminus sono i due ingressi
di blocco, HIsat e LOsat i segnali
di saturazione */
if ((DeltaCS>0 && Fplus==TRUE)||(DeltaCS<0 && Fminus==TRUE))
DeltaCS = 0;
CSnew = CSold + DeltaCS;
if (HIsat = (CSnew >= CSmax)) CSnew = CSmax;
Capitolo 2 - Controllo PID
21
if (LOsat = (CSnew <= CSmin)) CSnew = CSmin;
CSold = CSnew;
...
- commutazioni A/M e M/A bumpless: una volta definite le modalità di
comportamento del regolatore a fronte di tali commutazioni, la traduzione in
codice è pressoché immediata (vedi [1]):
...
/* Verifica dello stato del comando A/M locale e del forzamento remoto */
if ((ComandoAMlocale==AUTO)||(ComandoAMremoto==AUTO)
/* Il regolatore è in modalità Automatica */
…
/* Codice per il calcolo di DeltaCS */
…
/* Gestione logiche di blocco */
…
/* Aggiornamento del controllo in automatico */
else
/* Il regolatore è in modalità Manuale */
…
/* Verifica dello stato dei comandi locali d'incremento e decremento; IncrementoUnitarioCS è un parametro del regolatore */
if (ComandoIncrementoCS == TRUE) CSnew = CSold + IncrementoUnitarioCS;
if (ComandoDecrementoCS == TRUE) CSnew = CSold - IncrementoUnitarioCS;
/* Gestione dell'antiwindup (indipendentemente dalla modalità di funzionamento) ed eventuale segnalazione della saturazione */
if (HIsat = (CSnew >= CSmax)) CSnew = CSmax;
if (LOsat = (CSnew <= CSmin)) CSnew = CSmin;
/* Aggiornamento dello stato finale */
CSold = CSnew;
...
Di seguito si riporta il codice completo che costituisce la realizzazione digitale di un
PID in forma incrementale, con ovvio significato dei simboli:
Capitolo 2 - Controllo PID
22
void PID()
…
deltaSP = SPnew – SPold;
deltaPV = PVnew – Pvold;
…
/* Verifica dello stato del comando A/M locale e del forzamento remoto */
if ((ComandoAMlocale==AUTO)||(ComandoAMremoto==AUTO)
/* Il regolatore è in modalità Automatica */
/* Calcolo di DeltaCS */
deltaP = K*(b*deltaSP-deltaPV);
deltaI = K*Ts/Ti*(SPnew-PVnew);
Dnew=(Td*Dold+K*N*Td*(c*deltaSP-deltaPV))/(Td+N*Ts);
deltaD = Dnew-Dold;
deltaCS = deltaP+deltaI+deltaD;
/* Gestione delle logiche di blocco */
if ((DeltaCS>0 && Fplus==TRUE)||(DeltaCS<0 && Fminus==TRUE))
DeltaCS = 0;
/* Aggiornamento del controllo in automatico */
CSnew = CSold + DeltaCS;
else
/* Il regolatore è in modalità Manuale */
/* Verifica dello stato dei comandi locali d'incremento e decremento */
if (ComandoIncrementoCS==TRUE) CSnew = CSold + IncrementoUnitarioCS;
if (ComandoDecrementoCS==TRUE) CSnew = CSold - IncrementoUnitarioCS;
/* Gestione dell'antiwindup */
if (HIsat=(CSnew>=CSmax)) CSnew = CSmax;
if (LOsat=(CSnew<=CSmin)) CSnew = CSmin;
/* Aggiornamento dello stato finale */
CSold = CSnew;
Capitolo 2 - Controllo PID
23
SPold = SPnew;
PVold = PVnew;
Dold = Dnew;
/* Attuazione del nuovo controllo CSnew */
…
Capitolo 3 - Strumenti di lavoro
24
Capitolo 3
Strumenti di lavoro
Capitolo 3 - Strumenti di lavoro
25
In questo capitolo si presenta il dispositivo fisico ed il relativo sistema operativo che costituiscono il supporto del prodotto oggetto di questa tesi.
1. INTRODUZIONE
Il capitolo prende le mosse fornendo una definizione dell’RCX attraverso una
descrizione generale della sua architettura: si passano in rassegna i principali elementi,
cercando di non porre enfasi su tecnicismi hardware, ma di fornire informazioni utili a
formare un’idea di massima del funzionamento e delle potenzialità in esso racchiuse.
Sulla base di questa introduzione, si espongono le caratteristiche fondamentali del
sistema operativo impiegato in questo progetto: BrickOS. Anzitutto, s’introduce il
“kernel”, indicandone l’organizzazione, i principi di funzionamento e le tecniche
adottate per implementare le tipiche funzionalità presenti in un sistema operativo.
Successivamente, si discutono le modalità di interazione a basso livello fra hardware e
software. L’obiettivo principale è quello di comprendere come vengono eseguiti gli
applicativi utente e come essi possono sfruttare le potenzialità dell’RCX.
Si procede affrontando la gestione delle periferiche (display LCD, pulsanti, motori e
sensori); dopo una breve illustrazione dell’hardware, si indica come il S.O. gestisce tali
dispositivi, ponendo particolare attenzione all’interfaccia utente. Non viene presentata
una descrizione completa delle API di BrickOS, in quanto non è obiettivo di questo
capitolo fornire una guida alla programmazione.
Si riportano, infine, alcune considerazioni sul connubio RCX-BrickOS e si discute il
ruolo che il progetto trattato in questa tesi assume all’interno della “comunità BrickOS”.
2. RCX
L’RCX è un dispositivo costituito internamente da un microcontrollore corredato da
una memoria di tipo ROM ed una di tipo RAM, che si interfaccia verso l’esterno
attraverso tre porte d’ingresso e tre d’uscita, quattro pulsanti, una porta a infrarossi e un
piccolo display LCD. Può essere alimentato sia tramite batterie, sia attraverso la rete
elettrica per mezzo di un trasformatore, che, sul secondario, genera una tensione
Capitolo 3 - Strumenti di lavoro
26
continua a 10V.
Tutte le informazioni riguardanti l’hardware dell’RCX sono tratte da [8] e [11].
2.1. CPU
L'RCX utilizza il microprocessore Hitachi H8/3292 a 16 MHz, esteso con 32K di
memoria RAM esterna, alla quale si accede attraverso indirizzi da 16 bit. Il chip dispone
di 16 registri (R0L, R0H,..., R7L, R7H) da 8 bit ciascuno, che la CPU può anche gestire
come 8 registri (R1,…R7) da 16 bit per effettuare indirizzamenti. Sono presenti anche
due registri di controllo: un program counter (PC) da 16 bit e un condition code register
(CCR) da 8 bit. Il registro R7 viene utilizzato come stack pointer (SP). Ciascuna
operazione sullo stack avviene attraverso words di 2 byte.
Nel microcontrollore Hitachi H8/3292 sono presenti alcuni sottosistemi on-chip tra i
quali: un timer a 16 bit, un convertitore A/D (DAC) e porte I/O. Il timer è utilizzato per
generare gli interrupts che guidano il sistema operativo, mentre il convertitore A/D
serve per campionare e convertire i segnali analogici provenienti dai sensori collegati
alle porte d’ingresso dell’RCX.
2.2. ROM & firmware
La CPU dispone di una memoria ROM da 16K contenete software fornito dal
produttore, ovvero un driver che va in esecuzione quando l'RCX viene acceso.
Sostanzialmente tale driver è composto da routines per la gestione a basso livello
dell'RCX e dei suoi sottosistemi. Tra esse è presente anche un gestore degli interrupts, il
quale reagisce ad una chiamata puntando a stabilite locazioni nella memoria RAM, nelle
quali è possibile inserire dei comandi per la personalizzazione della risposta agli eventi.
Il driver nella ROM è responsabile anche dell'attivazione del firmware, che viene
caricato nella memoria RAM al momento dell’accensione. Quando si scarica BrickOS,
il firmware viene rimpiazzato da una immagine del kernel. BrickOS sfrutta solo alcune
delle funzioni presenti nella ROM: refreshing del display, procedure di on/off e sound
playing.
Capitolo 3 - Strumenti di lavoro
27
2.3. RAM esterna
La RAM esterna è indirizzata attraverso il range d’indirizzi che vanno da 0x8000
fino a 0xFFFF; tuttavia parte di questi indirizzi è riservata al Memory Mapped I/O, al
display LCD ed al Memory Mapped Motor Control. Lo spazio libero è suddiviso in una
“parte bassa” riservata al kernel (in cui viene memorizzata l'immagine del kernel di
BrickOS che occupa 16K) e una “parte alta” dedicata ai programmi utente. Oltre alla
RAM esterna, l’RCX non possiede nessun altro dispositivo di memoria, per cui non è
necessario alcun filesystem.
2.4. Porte d’ingresso
L’RCX dispone di tre porte d’ingresso analogiche, alle quali è possibile collegare
alcuni sensori (di luce, di contatto, di rotazione,…). Ciascuna porta è collegata al
convertitore A/D da 10 bit ad approssimazioni successive, il quale può accedere fino ad
8 canali d'ingresso analogici (AN0, AN1,..., AN7).
Il convertitore dispone di quattro registri dati (ADDRA,.....,ADDRD) da 16 bit
ciascuno, che sono mappati in memoria in corrispondenza degli indirizzi che vanno da
0xFFE0 sino a 0xFFE7. Tali registri sono di sola lettura e sono suddivisi in una parte
alta e una bassa. Su di essi vengono posti i risultati della conversione di un massimo di
4 canali d'ingresso (uno per ogni porta più uno per il livello di carica delle batterie).
Oltre a questi, vi è il registro a 8 bit ADCSR, il quale serve per controllare lo stato
del convertitore. Questo registro è sia leggibile che scrivibile, al fine di poter dare il
comando di start al convertitore e di rilevare il termine di una conversione. Quando la
conversione di un segnale analogico è finita viene invocato un interrupt (ADI).
2.5. Porte d’uscita
L’RCX dispone di tre porte d’uscita, alle quali è possibile collegare i motori forniti
dal produttore.
L’unico tipo di segnale che può essere generato su queste porte è di tipo PWM (Pulse
Width Modulation). Questo è tipicamente un segnale ad onda quadra, in cui il periodo e
l’ampiezza sono mantenuti costanti, mentre la durata degli impulsi varia a seconda
Capitolo 3 - Strumenti di lavoro
28
dell’informazione associata.
Il segnale PWM generato in uscita dall’RCX si discosta in parte dalla definizione
classica sopra citata. Infatti, al fine di ottimizzare il controllo operato sui motori, il
segnale applicato alle porte d’uscita mantiene costante il duty cycle, ma può variare in
frequenza. L’ampiezza dell’impulso è costante e pari a 10V.
La frequenza degli impulsi viene impostata attraverso una word da 8 bit, così da
ottenere 256 possibili diversi valori di frequenza. In corrispondenza del valore 0 il
motore resta fermo, mentre a 255 corrisponde la massima potenza erogabile al motore.
Sfortunatamente, non vi è una relazione di linearità fra l’impostazione della frequenza
(potenza erogata) e la velocità di rotazione, la quale dipende, inoltre, dal carico
applicato.
Infine, si può verificare molto facilmente che, applicando a due motori diversi lo
stesso segnale (stessa impostazione di frequenza), questi non girano esattamente alla
stessa velocità.
3. BRICKOS
BrickOS è un sistema operativo embedded per Lego Mindstorms RCX, proposto
come alternativa al firmware fornito dal produttore; si tratta di un progetto “open
source” rilasciato sotto licenza «Mozilla».
In estrema sintesi, si può sostenere che le principali caratteristiche siano: priority-
based preemptive multitasking, semafori POSIX, caricamento dinamico dei programmi
utente, gestione dinamica della memoria e IR networking. BrickOS fornisce delle API
per lo sviluppo di programmi utente in linguaggio C/C++; esse prevedono l’impiego di
un cross-compilatore gcc (derivato dall’omonimo compilatore di Linux). Pertanto, è
possibile sviluppare applicativi per BrickOS sia attraverso un approccio procedurale, sia
attraverso la tecnologia ad oggetti. In pratica, l’utente scrive e compila le proprie
applicazioni su personal computer e, in un secondo tempo, scarica il codice compilato
sull’RCX attraverso un trasmettitore IR.
Le caratteristiche tecniche di BrickOS sono trattate, in modo approfondito, in [6].
Capitolo 3 - Strumenti di lavoro
29
3.1. Il kernel di BrickOS
Il kernel di BrickOS è un blocco monolitico, in quanto l'intera sorgente viene
compilata in un’unica immagine binaria. Le interfacce per i programmi utente sono rese
disponibili in uno script linker dinamico.
L’attivazione del kernel avviene quando dalla ROM viene chiamata la funzione
kmain. Il kernel può lavorare sia in multitasking o in singletasking.
Durante l'inizializzazione del kernel vengono creati 3 tasks. Il primo, detto idle, è un
“task fantasma” con priorità minima, che viene eseguito quando nessun altro task è
attivo e ha il compito di "mettere a riposo" la CPU facendole eseguire l'istruzione sleep.
Gli altri due tasks, grazie ai quali è possibile eseguire il caricamento dinamico dei
programmi, sono chiamati packet-consumer e key-handler e vengono eseguiti con
priorità massima.
In breve, si può sostenere che il packet-consumer gestisce l'attività della porta IR,
mentre il key-handler si occupa della gestione dei pulsanti dell'RCX.
In seguito, viene attivato il task manager, il quale porta in esecuzione i tre tasks sopra
citati. Nuovi processi possono essere attivati scaricando ed eseguendo i programmi
utente.
3.1.1. Temporizzazioni
BrickOS è guidato da interrupts generati dal timer a 16 bit, il quale è configurato per
produrre un interrupt ogni millisecondo.
Gli interrupts vengono gestiti da una routine della ROM, la quale chiama la funzione
systime-handler (definita in kernel/systime.c). Tale funzione interroga in sequenza tutti i
vari sottosistemi dell’RCX, invocando, per ciascuno di essi, il relativo gestore. In
pratica, BrickOS utilizza la tecnica del polling per comunicare con le varie parti
dell'hardware.
L'ultima attività, svolta al termine di un giro di polling, è il controllo del timeslice
(20ms per default) del task in esecuzione, in modo da controllare se è scaduto il tempo a
sua disposizione. In caso affermativo viene chiamata la funzione tm-switcher che
eseguirà in cooperazione con lo scheduler un cambio di contesto della CPU.
Capitolo 3 - Strumenti di lavoro
30
3.1.2. Gestione dei Tasks
Con il termine “task manager” si intende l'insieme di funzioni, implementate in
kernel/tm.c, che si occupano della gestione dei tasks e dei loro risultati. Pertanto, non si
tratta di un processo separato o di un thread.
Ciascun programma in BrickOS è costituito da un'immagine binaria memorizzata
nella RAM; una volta entrato in esecuzione ha, come minimo, un processo attivo, ma
può crearne di nuovi grazie alla funzione execi. I processi creati per mezzo di questa
funzione condividono lo spazio di memoria del programma che li ha generati.
Il task manager implementa una politica priorità-based di tipo preemptive (vedi [8]);
a ciascun processo è associato un descrittore (di tipo pdata_t) che contiene informazioni
di stato ed informazioni necessarie per lo swithing dei tasks (livello di priorità, stack
pointer, ....).
Un processo si può trovare in cinque differenti stati (vedi [5]):
- Dead: il processo ha terminato la sua esecuzione ed il suo stack è stato
deallocato.
- Zombie: il processo ha terminato la sua esecuzione ma il suo stack non è stato
ancora liberato.
- Waiting: il processo non occupa la CPU ed è in attesa di un evento.
- Sleeping: il processo non occupa la CPU ma è pronto per essere eseguito.
- Running: il processo è in esecuzione.
Le informazioni di stato sono gestite dallo scheduler, il quale si occupa anche di
liberare la parte di stack occupata da un processo una volta che questo viene terminato.
L'esecuzione di un task può essere sospesa e posta in attesa di un evento a seguito
della chiamata alla procedura wait_event, i cui parametri permettono di specificare la
funzione di risveglio che deve essere invocata ogniqualvolta si debbano valutare le
condizioni di riattivazione. Il controllo della condizione di risveglio di un processo
viene eseguito ogni volta che lo scheduler, mentre è alla ricerca di un nuovo task da
eseguire, incontra un processo nello stato waiting. Se la funzione di risveglio ritorna un
valore non nullo, significa che la condizione di attesa non è più soddisfatta, così il
processo sospeso passa dallo stato waiting a quello sleeping.
Il problema di questa tecnica è che la condizione di attesa viene controllata solo
Capitolo 3 - Strumenti di lavoro
31
quando il sistema deve schedulare un nuovo processo. Così, può accadere che un
processo in attesa di un evento esterno resti a lungo bloccato se la sua condizione di
risveglio, una volta verificata, ritorni falsa prima che venga controllata nuovamente.
3.1.2.1. Linking dinamico dei programmi utente
Nelle prime versioni di BrickOS i programmi utente dovevano essere collegati
staticamente al kernel; pertanto, le applicazioni ed il kernel erano contenuti in un’unica
immagine binaria. Questo imponeva la ricompilazione dell’intero kernel ogni volta che
si apportavano modifiche ad un programma.
A partire dalla versione 0.2.4 è stata introdotta la possibilità di eseguire un
caricamento dinamico delle applicazioni utente, mediante il quale risulta possibile
caricare sino a 8 programmi utente nella memoria RAM. Le applicazioni sono
compilate su PC (con il compilatore gcc sopraccitato) in un formato rilocabile con
estensione .lx.
Il linker dinamico, chiamato dll, permette di eseguire il download delle applicazioni
utente (trasferimento da PC a RCX) e provvede, in automatico, alle operazioni di
linking con il kernel.
La directory boot (presente su PC) contiene due files: legOS.srec e legOS.lds; il
primo rappresenta l'immagine del kernel, mentre il secondo è uno script generato
durante la compilazione del kernel. Tale script ha la funzione di associare a ciascuna
funzione di libreria del kernel un indirizzo relativo di memoria. Quando si richiede la
compilazione di un programma utente, legOS.lds viene utilizzato nella fase di linking
per creare il file .lx. Il link finale agli indirizzi di memoria assoluti avviene durante il
download del programma utente sull'RCX. Al termine del download, il programma è
pronto per essere eseguito.
Le informazioni riguardanti tutti gli applicativi residenti sull’RCX sono contenute in
un array composto da 8 variabili di tipo program_t. Questa struttura contiene
informazioni riguardanti indirizzi e dimensioni dei vari segmenti di cui si compongono i
programmi. Inoltre, sono riportate informazioni relative alla dimensione dello stack, alla
priorità associata, al conteggio dei bytes scaricati (necessari per individuare errori
durante il download).
Per eseguire un applicativo sull’RCX, è sufficiente selezionarlo, premendo il
Capitolo 3 - Strumenti di lavoro
32
pulsante “prgm”, e, infine, premere il pulante “run”. A questo punto viene creato un
nuovo task per mezzo della chiamata alla funzione execi.
3.1.2.2. La funzione execi
Come detto in precedenza, questa funzione viene utilizzata per creare nuovi task che
condividono lo stesso spazio degli indirizzi del programma che li ha generati.
Richiede i seguenti parametri:
- un puntatore alla funzione che costituisce il corpo del nuovo task;
- il numero di argomenti richiesti da tale funzione;
- l’elenco dei parametri attuali con cui invocarla;
- il livello di priorità;
- la dimensione dello stack.
Execi cerca di inserire una nuova variabile di tipo pdata_t nella coda delle priorità
(vedere paragrafo successivo) e di allocare memoria per lo stack del task.
Il nuovo processo viene inserito nella struttura dei task nello stato sleeping, come se
la sua esecuzione fosse stata sospesa dallo scheduler; quando viene creato lo
stackframe, come primo valore viene inserito l'indirizzo della funzione exit, la quale ha
il compito di liberare la memoria allocata al task. In questo modo si garantisce che
ciascun task esegua, come ultima istruzione, un’invocazione alla suddetta funzione.
3.1.2.3. La struttura organizzativa dei task
A ciascun task (processo) è associata una priorità, la quale può assumere 20
differenti valori: 1 è la priorità minima, mentre 20 è quella massima. I processi sono
organizzati in una struttura composta da una lista ordinata, detta catena delle priorità.
A ciascun livello è della catena collegata una lista bidirezionale, i cui elementi sono di
tipo pdata_t. In pratica, tutti i processi che si trovano nella stessa lista hanno lo stesso
valore di priorità. Se ad un livello di priorità non è collegata alcuna lista (non c’è nessun
task con quel valore di priorità), allora il livello viene eliminato al fine di rendere più
efficiente la struttura.
L’accesso alla struttura avviene in mutua esclusione per mezzo di un semaforo;
operando in questo modo si garantisce, tra l’altro, che nessun nuovo task possa essere
inserito se lo scheduler sta selezionando un nuovo processo da eseguire.
Capitolo 3 - Strumenti di lavoro
33
La figura seguente schematizza la catena delle priorità:
Un nuovo task viene inserito nella struttura dalla funzione execi, la quale percorre la
catena dall'alto verso il basso: se esiste già una lista relativa al livello di priorità in cui
deve essere inserito il processo, allora esso viene collocato in fondo; in caso contrario,
si crea un nuovo anello nella catena delle priorità al quale si collega l’oggetto pdata_t
relativo al task da inserire.
3.1.2.4. Lo scheduler
L’alternanza in esecuzione dei processi è gestita dallo scheduler, il quale adotta uno
schema di tipo “round-robin” con priorità (vedi [7]). Il timeslice garantito a ciascun
processo è di 20ms.
Quando scade il quanto di tempo a disposizione di un processo o quando il processo
volontariamente libera la CPU, lo scheduler viene invocato per stabilire quale processo
deve essere eseguito nel prossimo timeslice. Per fare ciò, si esegue una “trywait” sul
semaforo che gestisce la coda dei task; se questo risulta bloccato (un altro task sta
aggiornando la struttura) il task manager attende che la risorsa ritorni disponibile.
Altrimenti, lo scheduler occupa il semaforo (che da questo istante risulterà bloccato per
PREVIOUS PRIORITY = 20 NEXT
Packet Consumer
Key Handler
PREVIOUS PRIORITY = 10 NEXT
PREVIOUS PRIORITY = 1 NEXT
User Task 1 User Task 2 User Task 3
Idle Task
Capitolo 3 - Strumenti di lavoro
34
gli altri processi) ed esamina lo stato del processo corrente. Se questo si trova nello
stato zombie dealloca la sua memoria e lo rimuove dalla struttura dei tasks. Nel caso in
cui sia l'ultimo processo in quel livello di priorità, allora viene eliminato dalla catena
anche l’anello ad esso corrispondente.
A questo punto, viene eseguita una scansione della struttura dei tasks al fine di
individuare un processo da portare in esecuzione.
Partendo dal livello di priorità più alto, si scandisce la coda dei tasks: se il processo
esaminato si trova nello stato sleeping; allora è pronto per essere attivato e quindi viene
schedulato per l'esecuzione impostando il suo stato a running. Lo scheduler a questo
punto abbandona il semaforo e salva lo stack pointer del nuovo task.
Se, invece, il processo considerato è nello stato waiting, la sua funzione di risveglio
viene valutata: nel caso in cui ritorni un valore non nullo il processo passa allo stato
running e lo scheduler procede come sopra descritto; in caso contrario si passa al
processo successivo oppure ai processi del livello di priorità inferiore.
Questa operazione procede tra i vari livelli di priorità finchè non viene individuato un
processo adatto per entrare in esecuzione. Nel caso estremo, in cui non vi sia alcun
processo pronto per l’esecuzione, la CPU verrà occupata dal task idle.
L'impiego simultaneo di semafori e di scheduling con priorità introduce il problema
dell'inversione di priorità, ovvero quella particolare situazione in cui un processo a
bassa priorità (task A) ne blocca un altro con priorità più alta (task B) poiché detiene
una o più risorse ad esso necessarie; per questo motivo, il task B, pur avendo diritto di
prelazione sul task A, non può procedere nella propria esecuzione. Per informazioni più
approfondite sul problema dell’inversione di priorità è possibile consultare [12].
3.1.3. Gestione della memoria
BrickOS alloca la memoria in maniera sequenziale, a partire dalla prima locazione
libera. Non esiste alcun supporto per una gestione più avanzata della memoria; essa,
infatti, è una risorsa scarsa ed un gestore più complesso comporterebbe solo un
overhead anziché reali benefici. Il gestore della memoria è costituito dall’insieme di
funzioni definite in kernel/mm.c.
Si è visto in precedenza che la memoria RAM è suddivisa in due parti: una dedicata
Capitolo 3 - Strumenti di lavoro
35
al kernel ed una ai programmi utente. Il codice del kernel e i suoi dati statici sono
allocati nella parte inferiore a partire dall'indirizzo 0x8000 sino all'indirizzo mm_start
(variabile globale di sistema).
Il Memory Management ha la responsabilità dello spazio di memoria compreso fra
mm_start e l'indirizzo 0xFFFF, il quale risulta organizzato in blocchi formati da una
intestazione di 4 byte (header) e da un campo dati (data field) a dimensione variabile.
I primi 2 byte dell'header contengono l'identifficatore del processo (process ID) al
quale il blocco è allocato. Se il blocco non è allocato il valore di questo campo è pari a
MM_FREE (impostato a 0 per default). Gli ultimi 2 byte dell'intestazione contengono la
dimensione del campo dati che la segue.
Un puntatore alla variabile globale mm_first_free è usato per indirizzare il primo
blocco di memoria libera. L'unità di memoria adottata internamente dal gestore della
memoria è una word di 2 byte.
L’accesso in mutua esclusione alla memoria avviene attraverso un apposito semaforo
(mm_semaphore).
3.1.3.1. Allocazione della memoria
I processi del kernel e quelli dei programmi utente possono allocare zone di memoria
chiamando la funzione malloc, la quale, per prima cosa, aspetta che il semaforo
mm_semaphore sia libero. Una volta garantito l'accesso alla regione critica, malloc
inizia a sondare la memoria a partire dall'indirizzo mm_first_free, finché non trova un
blocco di memoria libero avente dimensioni adeguate. Nel caso in cui raggiunga la fine
della memoria, restituisce il valore null. Se malloc ha successo ritorna un puntatore al
campo dati del blocco di memoria allocato.
3.1.3.2. Deallocazione della memoria
La deallocazione dinamica della memoria è attuata dalla funzione free, la quale non
fa altro che indicare come liberi i blocchi designati. Solo gli indirizzi non nulli verranno
considerati in questa operazione. Al termine dell'esecuzione viene aggiornato il
puntatore mm_first_free.
Quando un processo conclude la propria esecuzione deve liberare la memoria che
aveva allocato dinamicamente. Come detto in precedenza, al termine dell'esecuzione di
Capitolo 3 - Strumenti di lavoro
36
un task viene chiamata la funzione exit, che, a sua volta, invoca la funzione mm_reaper.
Quest'ultima esegue una scansione di tutti i blocchi di memoria, marcando come liberi
(MM_FREE) tutti i blocchi appartenenti al processo che termina l'esecuzione.
E’ importante evidenziare il fatto che lo stack non appartiene al processo che lo
utilizza, bensì al suo processo padre; pertanto, non viene deallocato da mm_reaper.
Nel caso in cui il processo che termina sia indicato come processo del kernel, allora i
blocchi di memoria ad esso relativi non vengono deallocati.
I segmenti (testo e dati) che compongono le applicazioni utente sono allocati dal task
packet_consumer, il quale è indicato come processo del kernel; in pratica, quando un
programma termina la sua esecuzione, giustamente, non viene rimosso.
Questo schema di gestione della memoria non fornisce alcuna forma di protezione:
tutti i processi possono scrivere in qualsiasi parte della RAM. Inoltre, gli applicativi
possono allocare dinamicamente la memoria, perciò è possibile che i programmi vadano
a scrivere oltre i limiti dello spazio di memoria ad essi assegnato (run out of memory).
Per queste ragioni, i programmatori devono gestire con particolare cura le operazioni di
allocazione/deallocazione dinamica, in modo da evitare il crash del sistema.
3.1.4. Comunicazioni Interprocessuali
BrickOS fornisce i semafori come unico meccanismo di sincronizzazione tra i vari
processi in esecuzione parallela. Non esistono primitive per lo scambio di messaggi fra
processi; ad ogni modo, i task creati da uno stesso programma condividono il medesimo
spazio di memoria, pertanto è possibile sfruttare tale caratteristica per realizzare scambi
informativi.
E’ bene sottolineare, però, che il kernel non fornisce alcun supporto per lo scambio
di dati fra processi generati da programmi diversi.
3.1.4.1. Semafori
BrickOS fornisce semafori che implementano la classica definizione fornita da
Dijkstra (vedi [4]). In sostanza, si tratta di semafori a conteggio, i quali aggiungono,
alle usuali operazioni wait e post, una chiamata non bloccante (trywait) ed una
operazione per la lettura del valore del semaforo (getvalue).
L'operazione wait è realizzata dalla funzione wait_event, la quale pone il processo
Capitolo 3 - Strumenti di lavoro
37
nello stato sleeping e definisce una funzione di risveglio, sem_event_wait, che testa il
valore del semaforo.
Se più processi sono bloccati su uno stesso semaforo che riceve un post, allora il
primo processo ad essere risvegliato sarà quello con priorità più alta. Nel caso ci siano
più processi con la stessa priorità, sarà l’ordine di arrivo a stabilire quale task avrà
accesso alla risorsa protetta.
3.1.5. IR Networking
Un’esposizione completa delle caratteristiche del protocollo di rete esula dagli scopi
di questa trattazione; di seguito vengono riportate solo alcune informazioni a carattere
introduttivo sull’argomento. Per ulteriori informazioni vedi [7].
LNP è il protocollo di rete per BrickOS ed è utilizzato dai programmi utente per
comunicare attraverso la porta IR dell’RCX. A partire dalla versione 0.2.4 possono
essere usati due tipi di pacchetti LNP: il primo, detto “integrity racket”, non contiene
informazioni sull'indirizzamento ed è utilizzato per comunicazioni broadcasting; il
secondo, detto “addressing racket”, aggiunge, rispetto al precedente, informazioni
relative all'indirizzamento, riportando l'indirizzo del mittente e quello del destinatario.
L'addressing packet fornisce un servizio simile all'UDP, ovvero non garantisce la
certezza d'arrivo dei pacchetti, ma è di tipo «error-free».
3.2. Gestione delle periferiche
BrickOS fornisce un insieme di primitive per l'interfacciamento con i principali
dispositivi di cui è corredato l’RCX: display LCD, pulsanti, motori e sensori.
La gestione delle periferiche è ampiamente discussa in [7].
3.2.1. Motori
I motori di Lego Mindstorms devono essere collegati ad una delle tre possibili porte
di uscita (indicate con lettere minuscole: a, b, c); è possibile collegare e pilotare fino a
tre motori contemporaneamente. Essi sono controllati con la tecnica del memory
mapped I/O.
Capitolo 3 - Strumenti di lavoro
38
In BrickOS lo stato di un motore è descritto da due elementi: direzione e velocità;
l'impostazione della direzione determina il comportamento del motore quando questo
riceve un impulso elettrico.
I comandi vengono forniti per mezzo di una combinazione del valore di due bit:
Azione Bit Comando BrickOS
Avanti 01 Fwd
Indietro 10 Rev
Stop 00 Off
Frena 11 Brake
La differenza sostanziale fra le modalità brake (11) ed off (00) consiste nel fatto che
la prima blocca la rotazione, mentre la seconda non oppone alcuna resistenza al moto.
Per controllare la potenza applicata al motore, si utilizza una variabile da 8 bit, detta
speed; il nome di questa variabile può creare confusione, infatti, a parità di potenza
erogata, la velocità di rotazione dipende dal carico applicato all’albero.
I motori, identificati attraverso il nome delle porte di output a cui sono collegati,
vengono gestiti per mezzo delle funzioni incluse nella libreria dmotor.h (dove x sta per
a, b o c):
- motor_X_dir(enum MotorDirection): permette di impostare la direzione. Riceve,
come unico parametro, uno dei seguenti valori predefiniti: off, fwd, rev e brake.
- motor_X_speed(int speed): permette di impostare la potenza erogata. Riceve un
solo parametro di tipo intero, con un valore compreso tra MIN_SPEED e
MAX_SPEED (pari a 0 e 255 rispettivamente).
Queste funzioni sono state create per agevolare l’utilizzo dei motori.
3.2.2. Sensori
Alle porte d’ingresso dell’RCX è possibile collegare i vari sensori che Lego
Mindstorms fornisce: sensori di luce, di contatto e di rotazione. In corrispondenza delle
misure che effettuano, i sensori generano dei segnali analogici con valori di tensione
Capitolo 3 - Strumenti di lavoro
39
compresi tra 0 e 5 V. Tali segnali vengono convertiti in digitale ed il risultato scritto in
appositi registri dati.
E’ possibile accedere direttamente a questi valori utilizzando la macro SENSOR_X
(dove X sta per 1,2 o 3 a seconda della porta cui è collegato il sensore) definita in
dsensor.h. La macro non fa altro che accedere alle sequenze di valori binari
continuamente fornite dal convertitore D/A e convertirle in valori di tipo intero. Poiché
il convertitore ha una risoluzione di 10 bit, SENSOR_X potrà assumere solo valori
compresi tra 0 e 1023.
Questa modalità di lettura dei sensori, detta “raw-mode”, è la più semplice e
generale possibile.
In aggiunta ad essa, per ogni tipo di sensore esiste una specifica macro, sviluppata
tenendo conto delle caratteristiche fisiche del sensore e dei valori che esso fornisce. Per
i sensori di contatto si utilizza TOUCH_X, per quelli di luce LIGHT_X e per quelli di
rotazione ROTATION_X.
3.2.2.1. Sensori di luce
I sensori di luce sono gestiti dalle librerie kernel/dsensor.c e dsensor.h. Questi
dispositivi consentono di operare la distinzione chiaro/scuro. In altre parole permettono
di distinguere fra aree di diversa luminosità, di individuare un oggetto scuro su sfondo
chiaro o di seguire una linea (pista) scura tracciata lungo un piano chiaro.
La lettura di tali sensori è gestita in modo sufficientemente accurato da poter
distinguere fra differenti colori. Questo utilizzo prevede, però, che l’utente eserciti uno
stretto controllo sulle condizioni di luce ed esegua degli esperimenti (campionamenti)
per determinare valori consistenti ed utilizzabili in relazione alle condizioni esterne ed
alle potenzialità del sensore.
I sensori di luce presentano due diverse modalità operative: attiva e passiva, che
possono essere selezionate per mezzo delle funzioni ds_active(&SENSOR_X) e
ds_passive(&SENSOR_X). In esse il parametro SENSOR_X (dove X sta per 1,2 o 3)
consente di specificare la porta, a cui è collegato il sensore. Per default i sensori si
trovano in modalità passiva.
Ovviamente a seconda della modalità il sensore offre delle prestazioni differenti e la
scelta di una o dell’altra dipende dal tipo di lettura/misura che si intende operare.
Capitolo 3 - Strumenti di lavoro
40
Fisicamente, i sensori sono costituiti da due parti: il sensore di luce vero e proprio ed
una piccola sorgente di luce.
In modalità attiva il sensore viene alimentato, così il piccolo “led” montato sul
sensore emette una luce rossa. Questa modalità risulta conveniente nei casi in cui si
voglia distinguere fra chiaro e scuro, o, comunque, individuare oggetti che determinano
una forte differenza nella riflessione della luce rispetto allo sfondo in cui sono
“immersi”. E’ necessario che il sensore sia ragionevolmente vicino alla superficie da
campionare: 10cm circa; solo in questo caso, infatti, la luce emessa amplificherà la
differenza di riflessione.
In modalità attiva i valori ottenuti in raw-mode (attraverso la macro SENSOR_X)
appartengono al range di valori compresi, approssimativamente, tra 50 e 300, mentre i
valori forniti dalla macro LIGHT_X appartengono al range 0-100.
La modalità passiva, invece, è da preferirsi nel caso in cui la finalità sia quella di
analizzare l’ambiente circostante (ad esempio individuare un punto bianco su una parete
ad una certa distanza). In tale situazione, l’eventuale attivazione dell’emettitore interno
potrebbe interferire nella misura e, pertanto, deve essere inibita. In questa impostazione
i valori di LIGHT_X coincidono con i valori grezzi di SENSOR_X e risultano essere
compresi, in modo approssimato, tra 60 e 280/300.
In generale, si può osservare una maggiore stabilità delle letture effettuate in
modalità attiva, ma va ricordato che il sensore non è lineare e che i valori forniti
risultano sensibilmente influenzati da una molteplicità di fattori: utilizzo di batterie o
dell’alimentazione di rete, stato di carica delle batterie, condizioni ambientali esterne.
3.2.2.2. Sensori di rotazione
Attraverso il sensore di rotazione è possibile rilevare l’angolo di rotazione percorso
da un albero. Può lavorare solo in modalità attiva e presenta una sensibilità (valore
minimo letto) pari a 1/16 di un giro completo (22,5°).
I sensori di rotazione hanno un proprio gestore specifico: ds_rotation incluso nelle
librerie kernel/dsensor.c e dsensor.h
Per effettuare delle misurazioni corrette è necessario, anzitutto, rendere attivo il
sensore attraverso la funzione ds_active(&SENSOR_X); successivamente si invoca la
funzione ds_rotation_set(&SENSOR_X, int i), la quale imposta il valore attualmente
Capitolo 3 - Strumenti di lavoro
41
campionato come punto di partenza. Se questa funzione non viene invocata, la posizione
attuale del sensore viene inizializzata con il valore 0.
A questo punto si può dare l’avvio all’operazione di misura attraverso la funzione
ds_rotation_on(&SENSOR_X); da questo istante in poi, il valore della posizione
istantanea, cui si accede attraverso la macro ROTATION_X, viene incrementato (o
decrementato) di una unità in corrispondenza di una rotazione pari ad 1/16 di giro in una
direzione (o in quella opposta).
Per terminare la misura è sufficiente invocare ds_rotation_off(&SENSOR_X).
3.2.2.3. Sensori di contatto
Come i sensori di luce, anche questi dispositivi sono gestiti dalle librerie
kernel/dsensor.c e dsensor.h.
Fisicamente possono essere considerati come degli “switch”; per conoscere il loro
stato attuale, si utilizza la macro TOUCH_X, la quale assume il valore 1 quando il
sensore rileva un contatto (pulsante premuto), mentre ha valore 0 quando non vi è
contatto. Per tali sensori è prevista la sola modalità passiva.
3.2.3. Display LCD
Il display di cui è munito l’RCX dispone di cinque cifre a 8 segmenti (uno è per il
punto) e di un elevato numero di simboli, così come schematizzato nella figura
seguente:
Il display viene controllato dalle funzioni definite nelle librerie conio.h e dlcd.h ed è
gestito per mezzo di interrupts temporizzati.
La funzione lcd_refresh_next_byte viene chiamata ogni 6ms (vedi [8]); poiché i dati
da visualizzare sono rappresentati da 9 bytes, l’aggiornamento completo si realizza ogni
54 ms (corrispondente a 18-19 operazioni di refresh al secondo). Questi dati sono
Capitolo 3 - Strumenti di lavoro
42
contenuti in due array: il primo, display_memory, contiene ciò che deve essere
visualizzato; il secondo, lcd_shadow, contiene ciò è appena stato visualizzato.
L’array lcd_shadow è impiegato per controllare se è necessario eseguire il refresh;
tale operazione viene eseguita solo sui bytes modificati, individuati dalla differenza tra
il contenuto di display_memory e quello di lcd_shadow.
Un programma utente può forzare l’aggiornamento del display utilizzando la
funzione lcd_refresh, la quale visualizza tutti i 9 bytes contenuti in display_memory.
Per un elenco completo delle funzioni di gestione del display consultare [7].
3.2.4. Pulsanti
I pulsanti disponibili sull’RCX sono quattro: “on/off”, “run”, “view” e “prgm”. Il
modulo del sistema operativo incaricato della loro gestione è dkey_handler; l’insieme
delle funzioni che ne fanno parte sono raccolte nelle librerie dkey.h e dbutton.h.
I pulsanti sono gestiti con una tecnica antirimbalzo; ciò significa che, a seguito di una
pressione, lo stato del bottone verrà testato di nuovo solo dopo che sarà trascorso un
certo lasso di tempo. In questo modo, si evita di leggere una singola pressione più volte.
Il tempo di attesa fra una lettura di stato e la successiva è impostato, di default, a 100ms.
A ciascuno dei quattro pulsanti è associato un bit che ne identifica lo stato
(premuto/non premuto); i valori dei quattro bit così ottenuti sono invertiti ed utilizzati
per creare una maschera. La variabile dkey_multi memorizza l'ultima maschera di bit
utilizzata e la variabile dkey mantiene l'attuale maschera.
Se dkey_multi e dkey hanno lo stesso valore (non sono stati premuti pulsanti), allora
dkey non viene aggiornata. Altrimenti, si rilevano le differenze tra le due al fine di
identificare quali bottoni sono stati premuti o rilasciati. In seguito, entrambe le variabili
vengono aggiornate con il valore dell’attuale maschera di bit ed il tempo di attesa viene
riportato a 100ms.
L’ interfaccia utente per la gestione dei pulsanti è rappresentata principalmente dalla
funzione getchar, la quale attende finché viene premuto un pulsante e ritorna
l’identificativo del pulsante premuto: KEY_ONOFF, KEY_RUN, KEY_VIEW,
KEY_PRGM o KEY_ANY.
Capitolo 3 - Strumenti di lavoro
43
3.3. Osservazioni
Rispetto al firmware fornito dal produttore, BrickOS espande notevolmente le
potenzialità dell’RCX, rendendolo un potente strumento didattico a livello accademico.
Per capire la differenza, basti pensare che il software originale di Lego Mindstorms
pone un limite al numero massimo di variabili utilizzabili (non più di 16!), mentre
BrickOS limita le applicazioni utente solo in relazione allo spazio di memoria
disponibile (32KB). In estrema sintesi, si può affermare che BrickOS offre un approccio
profondamente innovativo all’RCX; purtroppo, ciò va a discapito della semplicità di
utilizzo, in quanto si richiede all’utente la conoscenza del linguaggio di
programmazione C/C++ e la familiarità con strumenti teorici informatici non del tutto
banali: programmazione concorrente, sincronizzazione fra processi, accesso in mutua
esclusione a risorse condivise e concetti fondamentali che stanno alla base di un sistema
operativo. La sola operazione di “messa in opera” di BrickOS costituisce una notevole
barriera d’ingresso. Essa, infatti, avviene tramite una procedura piuttosto lunga che
prevede alcune competenze specifiche (ad esempio: personalizzazione e adattamento
delle procedure di compilazione). Inoltre, un utente Windows può accedere a BrickOS
solo tramite un emulatore dell’ambiente Linux (Cygwin).
Si ricorda, infine, che BrickOS è un prodotto software a livello “artigianale” (con
riferimento al significato che l’ingegneria del software attribuisce a tale definizione),
cioè un applicativo realizzato e mantenuto da persone che ne sono anche i primi
utilizzatori e che per tanto, plasmano il software per rispondere alle proprie esigenze.
La combinazione di BrickOS e RCX consente di realizzare, in modo relativamente
semplice, dei modelli utili per affrontare problemi e tematiche interdisciplinari che
spaziano dall’automazione all’informatica pura.
BrickOS, come già detto, è un prodotto open-source, che trova nell’ambiente
accademico la principale fonte di evoluzione ed applicazione. Le aree che
maggiormente costituiscono oggetto di ricerca sono lo sviluppo del protocollo di rete
(LNP) e la realizzazione di nuove funzionalità del sistema operativo, oltre che la
manutenzione di quelle già esistenti.
In questo contesto, il progetto trattato in questa tesi si prefigge di portare
nell’ambiente BrickOS un nuovo argomento: il controllo modulante.
Capitolo 4 - Analisi e specifica dei requisiti
44
Capitolo 4
Analisi e specifica dei requisiti
Capitolo 4 - Analisi e specifica dei requisiti
45
In questo capitolo si discuteranno i contenuti della prima fase di sviluppo: analisi e
specifica dei requisiti.
1. INTRODUZIONE
Il software viene sviluppato da un suo processo produttivo, definito ciclo di vita del
software, la cui struttura può avere una forte influenza sulla qualità del prodotto finale.
L’obiettivo ultimo è quello di trovare una soluzione di compromesso che sviluppi il
“miglior prodotto” non valutato in termini astratti e assoluti, ma in relazione alle risorse
e nel rispetto dei vincoli prestabiliti.
Nel corso della trattazione, spesso, si giustificherà la scelta di una soluzione
(progettuale o implementativa) a partire da un ventaglio di possibili alternative, alla luce
delle caratteristiche qualitative che si vogliono conferire al software. Si ritiene, pertanto,
opportuno distinguere i fattori di qualità in due categorie:
- Qualità esterne: percepibili da un osservatore esterno che esamina il prodotto
come se fosse una “scatola nera”; costituiscono l’interesse primario del
progettista. Affidabilità, correttezza, robustezza e sicurezza spiccano certamente
per importanza, soprattutto nel campo delle applicazioni di controllo industriale.
- Qualità interne: possono essere osservate solo esaminando la struttura interna del
prodotto, come se questo fosse una scatola trasparente. Tale prospettiva, pone in
primo piano caratteristiche quali verificabilità, manutenibilità, riusabilità e
comprensibilità.
Usando la terminologia tipica del software, si può affermare che le qualità interne
implementano (nel senso che sono un modo per realizzare) le qualità esterne. Entrambe
costituiscono sia una linea guida nella definizione degli obiettivi di qualità, sia uno
strumento di valutazione del prodotto sviluppato.
2. UN MODELLO PER IL PROCESSO DI SVILUPPO
La scelta di un modello su cui plasmare la soluzione ad un problema è, senza ombra
Capitolo 4 - Analisi e specifica dei requisiti
46
di dubbio, una delle fasi più critiche dell’intera attività progettuale. Non è possibile
affermare che un modello sia meglio di un altro in termini assoluti, ma la valutazione
dipende da caso a caso ed, in particolare, da un attento esame dei rischi che le diverse
alternative presentano.
Se il rischio fondamentale è dato dall’affidabilità dell’applicazione, mentre i requisiti
sono estremamente stabili e ben noti, allora il modello più ragionevole è il classico
modello a cascata, con rigorosi controlli per il passaggio da una fase all’altra. Se,
invece, i rischi primari stanno nell’instabilità e nell’incertezza dei requisiti, allora è bene
pianificare un processo iterativo basato su un modello a spirale. L’applicazione oggetto
di questo elaborato si pone a livello intermedio rispetto a questi due poli, in quanto
richiede elevati livelli di affidabilità, ma presenta anche forti connotazioni evolutive.
Questo giustifica la scelta progettuale di utilizzare il modello a spirale come meta-
modello per il quello a cascata; in altre parole: l’intero processo di sviluppo
dell’applicazione (basato sul modello a spirale) viene suddiviso in una serie di sotto-
processi (conformati al modello a cascata), ciascuno dei quali determina un’evoluzione
dell’applicazione stessa.
Prima di affrontare in modo specifico le varie fasi di progetto, si discutono
brevemente le peculiarità dei due modelli sopraccitati.
2.1. Modello a Spirale
Tale modello, come dice direttamente il nome ed evidenzia chiaramente la figura, è
essenzialmente di tipo ciclico; ogni ciclo è composto da quattro passi logici:
Fase I Fase II
Fase III Fase IV
Capitolo 4 - Analisi e specifica dei requisiti
47
- Fase I: determinazione di obiettivi, alternative e vincoli;
- Fase II: valutazione delle alternative; identificazione e risoluzione dei rischi;
- Fase III: sviluppo e verifica del prossimo livello del prodotto;
- Fase IV: pianificazione della fase successiva.
Il raggio della spirale rappresenta il costo accumulato durante lo svolgimento del
progetto; il ciclo si ripete sino ad esaurimento delle risorse a disposizione.
Per ulteriori informazioni vedi [5].
2.2. Modello a Cascata
Prevede che l’attività di sviluppo passi attraverso una serie di fasi poste in cascata, in
cui ciascuna di esse riceve ingressi dalla fase precedente e produce uscite (semilavorati)
che saranno, a loro volta, ingressi per quella successiva. Non sono previsti ritorni a fasi
già svolte (ricicli); essi sono addirittura visti come fattori di disturbo in quanto rendono
il processo difficilmente controllabile e gestibile.
In realtà il processo è costituito da un flusso continuo e la scomposizione in fasi è
semplicemente un’astrazione di comodo: le fasi sono importanti al fine di concentrare
l’attenzione, di volta in volta, su certe problematiche piuttosto che su altre; inoltre, in
questo modo è possibile controllare la progressione dello svolgimento del progetto.
Sulla base delle considerazioni precedenti, il processo di sviluppo risulta costituito
dalle seguenti fasi:
- Studio di Fattibilità: viene fatta una valutazione preliminare dei costi e dei
benefici dell’applicazione, con l’obiettivo primario di stabilire, per il progetto in
questione, quali siano le possibili alternative, le scelte più ragionevoli e le risorse
necessarie per l’attuazione del progetto. Tutto ciò sfocia nella definizione
preliminare del problema, delle possibili strategie risolutive nonché dei costi,
tempi e modalità di sviluppo ad esse relativi. In questa fase potrebbe risultare
molto utile valutare i punti di forza e di debolezza di eventuali prodotti simili già
esistenti.
- Analisi e Specifica dei Requisiti: viene svolta un’analisi completa dei problemi
dell’utente e della sua realtà applicativa al fine di poter specificare le
caratteristiche di qualità che il prodotto dovrà soddisfare. E’ importante, in questa
Capitolo 4 - Analisi e specifica dei requisiti
48
fase, concentrarsi solo sulle funzionalità da offrire e non su aspetti
implementativi; in questo modo si evita di confondere i due livelli di astrazione:
quello esterno, di interesse dell’utente e quello interno di interesse del progettista.
E’ possibile utilizzare linguaggi quali l’UML per la stesura dei semilavorati in
uscita da questa fase.
- Progettazione: si definisce come i requisiti specificati possano essere raggiunti
attraverso un’opportuna architettura software (scomposizione in moduli e loro
funzionalità, relazione fra i vari moduli,…). Si può affermare che tale fase
costituisce una formalizzazione degli obiettivi del codice.
- Programmazione e Test di Unità: i diversi moduli vengono realizzati con il
linguaggio di programmazione scelto e sottoposti a test per verificare la loro
correttezza ed adeguatezza rispetto alle specifiche di progetto. Con l’avvento di
linguaggi di programmazione di livello sempre più alto, la distinzione fra
progettazione e programmazione diventa sempre più sfumata, in quanto entrambe
vengono realizzate con il medesimo strumento; concettualmente, però,
rimangono sempre distinte in quanto la prima ha lo scopo di identificare i
“mattoni logici” (visti come black-box di cui sono invisibili i dettagli) che
vengono aggregati per formare il sistema complessivo, mentre la seconda
definisce la struttura interna di tali “mattoni”.
- Integrazione e Test di Sistema: ha lo scopo di integrare i diversi moduli che sono
realizzati e testati al fine di produrre il sistema completo e collaudato.
- Manutenzione: affinché un software possa facilmente evolvere è necessario che si
possa modificare in modo semplice e affidabile. Il processo a cascata è
intrinsecamente ostile ad una gestione ordinata e pianificata della manutenzione
in quanto non prevede i ritorni; l’esperienza insegna che tali ritorni esistono e
l’impreparazione a gestirli viene pagata duramente.
Il modello a cascata è trattato approfonditamente in [5].
3. ANALISI E SPECIFICA DEI REQUISITI
Questa fase è sicuramente la più critica in quanto il successo del processo di sviluppo
Capitolo 4 - Analisi e specifica dei requisiti
49
dipende, in ultima analisi, dalla misura in cui sono state colte le reali esigenze
dell’utente finale. L’attività è di tipo esplorativo: la realtà analizzata viene
progressivamente compresa e pertanto il livello di precisione e di dettaglio cresce in
maniera incrementale. L’obiettivo è quello di definire le caratteristiche di qualità del
prodotto e, in particolare, le proprietà funzionali.
Non esiste un unico strumento (linguaggio di specifica) per la descrizione analitica
dei requisiti che vada bene sempre e comunque: si tratta di scegliere in base al tipo di
applicazione che si deve sviluppare. Le applicazioni di controllo (regolazioni di
processo) elaborano insiemi di dati semplici (come, ad esempio, valori reali provenienti
dai sensori) applicando ad essi procedure di elevata complessità. Tale complessità
risiede nel modo in cui il controllo fluisce tra le diverse attività che si sincronizzano e
cooperano all’interno del sistema. Nel seguito, si utilizzerà il linguaggio naturale come
strumento espositivo di base, ricorrendo ad opportuni formalismi sono nel caso in cui
esso tenda ad essere impreciso, ambiguo o ridondante.
3.1. Realizzazione di un PID digitale
Il progetto consiste, essenzialmente, nel costruire ex-novo un PID digitale che possa
essere impiegato in situazioni di interesse pratico. Questa prospettiva pone in primo
piano la necessità di progettare un software che metta a disposizione le principali
funzioni di un regolatore industriale a microprocessore, ovvero:
- funzioni di controllo
- interfaccia verso il processo
- interfaccia operatore
L’applicativo deve rispettare vincoli e limiti imposti dall’hardware (RCX) su cui
deve essere eseguito; una volta scaricato sul dispositivo fisico, inoltre, deve poter
evolvere in completa autonomia, senza alcun supporto esterno (comunicazioni con PC,
etc…).
Nel caso si debba gestire una logica di controllo che prevede l’utilizzo di più PID
opportunamente interconnessi (controllo in cascata), la struttura del sistema può essere
definita solo staticamente; questo comporta che, una volta avvenuto il download
dell’applicazione sull’RCX, non è più possibile modificare le connessioni logiche fra i
Capitolo 4 - Analisi e specifica dei requisiti
50
PID. E’, però, possibile apportare modifiche ai parametri caratteristici di ciascun
regolatore.
3.1.1. Funzioni di controllo
Il software deve garantire un controllo efficace del processo in esame ed operare in
modo tale che la variabile controllata segua con prontezza e precisione il riferimento
imposto dall’operatore, compatibilmente con la dinamica e con le caratteristiche del
processo stesso. E’ richiesta l’implementazione di una serie di funzionalità ausiliarie,
quali:
- anti-windup;
- logiche di blocco;
- commutazioni automatico/manuale (e manuale/automatico) bumpless;
Infine, è necessario garantire che la frequenza di campionamento determinata in fase
di sintesi del regolatore sia sempre rispettata durante l’esecuzione dell’applicazione, a
patto che essa si mantenga all’interno di un range di valori ammissibili (da determinare
effettuando adeguate “prove di carico”).
3.1.2. Interfaccia verso il processo
Il sistema deve essere in grado di interagire con il mondo esterno per mezzo delle
porte di ingresso e uscita messe a disposizione dall’RCX.
3.1.2.1. Input
Il software deve fornire opportune funzionalità per l’acquisizione e l’elaborazione
delle informazioni provenienti dai sensori, agendo in maniera differente a seconda del
tipo di sensore e della modalità in cui esso opera (attiva/passiva). Si deve valutare la
possibilità di utilizzare un motore come dinamo tachimetrica per effettuare letture di
velocità.
3.1.2.2. Output
Le uscite del microcontrollore possono essere pilotate unicamente con un segnale di
tipo PWM; da questo punto di vista, l’unico requisito, peraltro insito in BrickOS,
riguarda la possibilità di gestire la potenza in uscita in un range 0-255 (dove 0
Capitolo 4 - Analisi e specifica dei requisiti
51
rappresenta la potenza minima, mentre 255 quella massima).
3.1.3. Interfaccia operatore
Il compito dell’interfaccia operatore è, fondamentalmente, quello di permettere
all’utente del sistema di controllo di impostare e/o visualizzare il valore dei parametri
che caratterizzano il regolatore digitale; dal punto di vista hardware, questa interazione
si realizza per mezzo di un semplice display e di un insieme minimo di pulsanti.
3.1.3.1. Display
Il display montato sul microcontrollore è in grado di visualizzare un massimo di
cinque cifre più il punto decimale. La modifica dei parametri numerici si effettua
agendo su ogni singola cifra che compone il valore stesso e modificando
opportunamente la posizione del punto decimale.
Non è possibile gestire, allo stato attuale, valori interi o frazionari costituiti da più di
cinque cifre.
3.1.3.2. Pulsanti fisici e pulsanti logici
L’RCX è equipaggiato di quattro pulsanti fisici, ma solo due di essi (ovvero View e
Program) possono essere utilizzati nelle applicazioni. Pertanto, si introduce il concetto
di “pulsante logico”, inteso come pulsante simulato per mezzo di opportune sequenze
temporizzate delle pressioni dei tasti fisici. A partire dai due pulsanti fisici (da questo
punto in poi identificati con KEY_1 e KEY_2), è possibile realizzare cinque pulsanti
logici, corrispondenti ad altrettanti eventi, definiti nel modo seguente:
- KEY_1 premuto e rilasciato (nel seguito identificato con KEY_1_short);
- KEY_1 premuto a lungo (nel seguito identificato con KEY_1_long);
- KEY_2 premuto e rilasciato (nel seguito identificato con KEY_2_short);
- KEY_2 premuto a lungo (nel seguito identificato con KEY_2_long);
- Pressione combinata di KEY_1 e KEY_2 (nel seguito identificato con KEY_1_2).
Una pressione “lunga” è caratterizzata da una durata almeno pari a 2 secondi.
3.1.3.3. Regole generali
Per aumentare la comprensibilità e la chiarezza delle specifiche, è utile presentare
Capitolo 4 - Analisi e specifica dei requisiti
52
alcune regole di validità generale.
Funzionalità dei tasti logici
- KEY_1_short assume, a seconda del contesto in cui viene impiegato, due
possibili funzioni:
! funzione di scrolling, ovvero di selezione ciclica degli elementi appartenenti
ad un insieme finito e ben determinato (ad esempio: scorrimento di menù,
etc…);
! funzione di incremento del valore di un parametro incrementale o di una
singola cifra.
- KEY_2_short assume, tendenzialmente, la funzione di annullamento delle
modifiche relative ad un singolo parametro oppure alla totalità dei parametri di
un regolatore;
- KEY_2_long ha un comportamento analogo a KEY_2_short ma, anziché
annullare, permette di confermare le modifiche apportate.
- KEY_1_2 assume la sola funzione di decremento del valore di un parametro
incrementale.
Criteri di conferma delle modifiche
Quando l’utente accede alla modalità di modifica del valore di un qualsiasi
parametro, prima di poter effettuare altre operazioni deve necessariamente confermare
(o annullare) le variazioni apportate a quel parametro (si parla di “conferma di primo
livello”); solo in questo modo può uscire dalla modalità di modifica.
Le variabili che si riferiscono all’anello di controllo richiedono solo la conferma di
primo livello, nel senso che essa è sufficiente affinché le modifiche possano diventare
attive.
Le variabili che caratterizzano il singolo regolatore, invece, richiedono un ulteriore
livello di conferma (si parla di “conferma di secondo livello”); esiste un’apposita voce
di menù che permette di abilitare in modo definitivo tutte le modifiche apportate e che
viene visualizzata solo nel caso in cui uno o più parametri abbiano subito modifiche. Da
ciò si evince che la conferma di secondo livello è un’operazione atomica che coinvolge
contemporaneamente tutti i parametri del regolatore scelto.
Capitolo 4 - Analisi e specifica dei requisiti
53
Alla luce delle considerazioni appena fatte, si passa a definire il comportamento
desiderato dell’interfaccia operatore.
3.1.3.4. Menù PID
Nel caso in cui la logica di controllo, definita staticamente, preveda l’utilizzo di più
PID, l’utente deve disporre di un menù attraverso il quale selezionare un qualsiasi
regolatore del sistema per modificare e/o visualizzare i parametri ad esso relativi. Tale
menù viene nascosto se la struttura di controllo è costituita da un unico anello di
regolazione.
Eventi
Quando il menù è attivo:
- KEY_1_short determina uno scorrimento verso il basso del menù; quando si
raggiunge l’ultimo PID della lista, si riparte dall’inizio. I nomi dei regolatori
rispettano il seguente formato: PID X (con ,...1,0=X ); il regolatore identificato
con “PID 0” è quello più interno del sistema.
- KEY_2_short permette di entrare nel menù di visualizzazione/modifica dei
parametri per il PID attualmente selezionato.
3.1.3.5. Menù parametri
Relativamente a ciascun regolatore, devono essere visualizzabili (ma non sempre
modificabili) i seguenti parametri:
Parametro Modificabile Guadagno (Kp) sempre
Tempo integrale (TI) sempre
Tempo derivativo (TD) sempre
Rapporto tra TD e la costante del derivatore reale (N) sempre
Peso sul segnale di riferimento nell’azione proporzionale (b) sempre
Peso sul segnale di riferimento nell’azione derivativa (c) sempre
Valore massimo del controllo (CSmax) sempre
Valore minimo del controllo (CSmin) sempre
Passo di incremento della variabile di controllo (deltaCSman) sempre
Para
met
ri de
l PID
Tempo di campionamento (TS) sempre
Capitolo 4 - Analisi e specifica dei requisiti
54
Segnale di riferimento (SP) sempre
Variabile controllata (PV) mai
Variabile di controllo (CS) solo in modalità
MANUALE Para
met
ri de
ll’an
ello
Modalità di funzionamento (AUTO/MAN) sempre
Eventi:
- KEY_1_short, come nel caso precedente, determina uno scorrimento ciclico delle
voci di menù; per ciascuna di esse la visualizzazione viene gestita in due tempi:
prima viene mostrato il nome associato alla voce e, dopo pochi secondi, il suo
valore.
- KEY_2_short permette di modificare il valore del parametro corrente, sempre se
tale operazione è consentita in riferimento allo stato del PID; ad esempio, la
variabile di controllo può essere modificata solo quando il regolatore funziona in
modalità manuale.
3.1.3.6. Modifica parametri PID
E’ possibile distinguere i parametri caratterizzanti ciascun PID in:
- Valori numerici: possono assumere infiniti valori appartenenti all’insieme dei
numeri reali (nell’accezione matematica del termine); è possibile modificarli
impostando direttamente il nuovo valore (agendo sulle singole cifre che li
compongono);
- Valori incrementali: sono valori numerici ai quali è possibile apportare modifiche
solo attraverso una sequenza di incrementi o decrementi di passo prestabilito; non
consentono una modifica diretta.
- Valori lessicali: possono assumere un numero finito di valori, definiti a priori dal
progettista.
La modifica di un parametro avviene secondo modalità differenti, in funzione del
tipo di valore che si sta considerando.
Valore Numerico
Quando l’utente entra nel contesto di modifica di una voce numerica, la prima delle
cinque cifre (a partire dalla sinistra del display) che la compongono inizia a
Capitolo 4 - Analisi e specifica dei requisiti
55
lampeggiare, indicando che è possibile modificarne il valore; a questo punto
KEY_1_short permette di impostare il valore (compreso fra 0 e 9) da applicare alla cifra,
mentre KEY_2_short permette di selezionare la cifra successiva.
Nella situazione in cui stia lampeggiando la quinta cifra, KEY_2_short determina il
lampeggiamento del punto decimale; in questo contesto, KEY_1_short permette di
posizionarlo correttamente.
Nella situazione in cui stia lampeggiando il punto decimale, KEY_2_short determina
il lampeggiamento di tutte le cifre e del punto decimale.
In questo contesto:
- KEY_2_short permette di annullare le eventuali modifiche apportate al valore
numerico;
- KEY_2_long permette, invece, di confermare le modifiche.
In entrambi i casi si ritorna al menù dei parametri; solo nel secondo caso viene attivato,
nella parte alta del display, un simbolo che segnala la presenza di modifiche per le quali
l’utente non ha ancora espresso la conferma definitiva.
Possibili esempi di voci numeriche sono dati da parametri quali guadagno, tempo
integrale, tempo derivativo, etc…
Valore Incrementale
La procedura di modifica è molto simile a quella prevista nel caso di valore
numerico; la differenza sostanziale è determinata dal fatto che non si può impostare
direttamente il valore di ciascuna cifra e la posizione del punto decimale, ma esse
diventano conseguenza della serie di incrementi o decrementi applicati. L’applicazione
prevede due soli valori di questo tipo: set-point incrementale e variabile di controllo (nel
caso in cui sia modificabile); il primo prevede un incremento unitario, mentre il passo di
incremento del secondo può essere impostato dall’utente (parametro deltaCS del
regolatore).
In questo ambito:
- KEY_1_short determina un incremento;
- KEY_1_2 determina un decremento;
- KEY_2_short annulla la modifica;
- KEY_2_long conferma la modifica.
Capitolo 4 - Analisi e specifica dei requisiti
56
Valore Lessicale
Una volta espressa, da parte dell’utente, la volontà di modificare una voce lessicale, è
possibile procedere nel seguente modo:
- KEY_1_short permette di visualizzare in modo sequenziale la lista dei valori
ammissibili fra i quali scegliere il nuovo valore da applicare;
- KEY_2_short determina un annullamento, mentre KEY_2_long determina una
conferma della modifica appena apportata.
Un esempio di voce lessicale è dato dalla modalità di funzionamento, la quale può
assumere solamente due valori: automatico o manuale (in futuro sarà resa disponibile
una terza voce, tuning, riferita alla possibilità di effettuare un’autotaratura del
regolatore)
3.1.3.7. Gestione variabile di controllo
Se il regolatore corrente evolve in modalità automatica, deve essere inibita la
possibilità di modificare direttamente la variabile di controllo; l’unica operazione
consentita è la visualizzazione del valore.
Se il regolatore è posto in modalità manuale, il sistema consente di modificare il
valore della variabile di controllo operando in base alla procedura descritta nel caso di
valore incrementale; l’utente ha facoltà, in qualsiasi momento, di modificare il passo di
incremento (deltaCS).
3.1.3.8. Gestione del set-point
Il segnale di riferimento viene gestito sia come valore numerico che come valore
incrementale, cosicché sia possibile imporre direttamente nuovi valori o apportare
variazioni a scalino; per raggiungere questo obiettivo, si utilizzano due differenti voci di
menù (SP e SPi) che vanno ad agire, secondo modalità differenti, sulla medesima
variabile.
3.1.3.9. Conferma o annullamento delle modifiche
Come osservato in precedenza, le modifiche ai parametri che descrivono l’anello di
controllo, vengono applicate nell’istante stesso in cui l’utente dà la conferma di primo
livello.
Capitolo 4 - Analisi e specifica dei requisiti
57
Per quanto riguarda i parametri descrittivi del singolo regolatore, invece, è necessario
un secondo livello di conferma; infatti, nel caso sia stato modificato almeno un
parametro, come ultimo elemento del menù viene visualizzata la voce di conferma, in
corrispondenza della quale:
- KEY_2_short: determina l’accesso alla funzione di conferma globale delle
modifiche. In questa situazione:
! KEY_2_short comporta l’annullamento di tutte le modifiche e l’eventuale
ritorno al menù PID;
! KEY_2_long comporta la conferma di tutte le modifiche e l’eventuale
ritorno al menù PID. Solo a fronte di questa seconda conferma, le modifiche
effettuate diventeranno operative a tutti gli effetti.
In entrambi i casi viene disattivato il simbolo che indica la presenza di
modifiche non ancora confermate.
- KEY_1_short: determina il ritorno alla prima voce del menù parametri per il
regolatore selezionato.
Nel caso in cui non sia stata apportata alcuna modifica ed il sistema sia composto da più
regolatori, in luogo della voce di conferma viene visualizzata la voce EXIT che consente
di tornare al menù PID. Sempre nel caso in cui non siano state apportate modifiche, ma
il sistema sia costituito da un singolo anello di controllo nessuna delle due voci
sopraccitate viene visualizzata.
Capitolo 5 - Progettazione
58
Capitolo 5
Progettazione
Capitolo 5 - Progettazione
59
In questo capitolo si affronta la seconda fase dell’attività di sviluppo: progettazione del sistema; si riportano le metodologie di lavoro applicate ed i risultati ottenuti.
1. INTRODUZIONE
Il capitolo si propone di descrivere la delicata fase di progettazione dell’applicativo,
non tanto come resoconto delle varie fasi in cui essa si articola, ma cercando
d’individuare le principali decisioni che si sono dovute affrontare e che maggiormente
caratterizzano il risultato finale. Seguendo la sequenza logica dell’attività progettuale, si
può notare come il livello di dettaglio dell’analisi aumenti man mano che si procede,
rivelando una logica di tipo top-down. Per ciascuna scelta operata, si cercherà di
riportare il ragionamento e le basi teoriche da cui è scaturita, sottolineando la necessità
di raggiungere un compromesso tra vari fattori quali: conoscenze degli autori, strumenti
a disposizione, necessità pratiche,… .
Infine, si vuole sottolineare che le descrizioni delle strutture dati riportate, non
vogliono anticipare alcun aspetto implementativo, ma, semplicemente, fornire
un’introduzione che aiuti a comprenderne la logica e l’utilità.
2. MODULARIZZAZIONE
Per modularizzazione si intende, in questa fase della realizzazione del progetto,
quell’attività che nasce dall’esigenza di scomporre un’entità complessa in varie
sottoparti al fine di poter circoscrivere le problematiche da affrontare in un ambito
ristretto, senza, però, perdere la visione d’insieme del progetto (vedi [3] e [5]).
L’obiettivo di questo strumento progettuale è l’identificazione di macroaree del
progetto, tra loro indipendenti, la cui unione deve portare alla costituzione del prodotto
finale. La suddivisione introdotta, però, non deve generare ulteriori vincoli oltre a quelli
già esistenti, né tantomeno deve essere fonte di complicazioni nelle fasi successive.
Per operare una buona modularizzazione è necessario procedere seguendo, in ordine,
le seguenti fasi:
Capitolo 5 - Progettazione
60
- Analisi del prodotto: è molto importante comprendere a fondo il tipo di prodotto
da realizzare ed in relazione a ciò valutare i requisiti e le specifiche richieste.
- Scelta del criterio di modularizzazione: è necessario porre particolare attenzione
alla scelta del criterio in base al quale operare la modularizzazione; tale scelta
deve essere operata sulla base delle valutazioni scaturite dalla fase precedente.
- Individuazione dei moduli: applicando il criterio scelto, si passa alla
scomposizione modulare del sistema.
Le dimensioni del progetto in questione sono tali da giustificare la scomposizione in
moduli al fine di implementare al meglio le caratteristiche funzionali e di ottenere una
più semplice ed ordinata gestione del processo di produzione (con riferimento al
significato che l’ingegneria del software attribuisce a questo termine).
2.1. Applicazione dei concetti di modularizzazione
In questo paragrafo, si ripercorrono le fasi d’implementazione della
modularizzazione contestualizzate nell’ambito del progetto da sviluppare.
Analisi del prodotto
A seguito di un’analisi operata sul tipo di prodotto da realizzare, si possono
individuare due principali aspetti caratterizzanti.
In primo luogo si tratta di produrre un regolatore PID, ovvero un software che
implementa un controllo modulante, la cui principale caratteristica sta nel fatto che deve
trattare delle strutture dati semplici, sulle quali eseguire un’elaborazione relativamente
complessa, anche se fortemente standardizzata. Inoltre, questo tipo di software deve
soddisfare precisi vincoli temporali e requisiti di robustezza
In secondo luogo il prodotto deve essere eseguito sull’RCX, quindi, ci si trova di
fronte alla necessità di dover realizzare un’applicazione “embedded”.
Una ulteriore valutazione, più approfondita, porta ad individuare nel prodotto due
macrofunzioni: il regolatore vero e proprio e l’interfaccia utente.
Scelta del criterio di modularizzazione
Alla luce di tutte le precedenti considerazioni, nel processo di sviluppo si è proceduto
considerando la modularizzazione da due differenti punti di vista: come strumento di
Capitolo 5 - Progettazione
61
organizzazione del progetto e come risposta alla necessità di produrre codice embedded.
Individuazione dei moduli
La visione prospettica così maturata ha portato a strutturare il sistema in tre moduli
distinti: esecuzione del PID, gestione degli eventi e visualizzazione dei dati.
Come è facile intuire, il modulo denominato “esecuzione del PID” racchiude in sé il
regolatore vero e proprio, ovvero il “nocciolo” dell’intero sistema; esso ricopre per
intero la macrofunzione relativa al controllo modulante.
L’altra macrofunzione (interfaccia utente) è ottenuta attraverso azioni coordinate sui
pulsanti e sul display; risulta, così, naturale individuare due moduli con distinte aree di
competenza sull’hardware. Il modulo denominato “gestione degli eventi” si occupa del
controllo dei pulsanti, attraverso i quali l’operatore può intervenire sui parametri e sulle
modalità di funzionamento del regolatore, mentre la presentazione di tali dati sul display
LCD avviene per mezzo delle funzionalità raccolte nell’area “visualizzazione dati”.
3. STRUTTURE DATI
La comunicazione tra i moduli individuati deve avvenire necessariamente attraverso
delle opportune strutture dati. Iniziando l’analisi dall’esecuzione del PID, si individua,
in questa macroarea, l’algoritmo del regolatore PID come il principale elemento
implementativo. Essendo questo fortemente standardizzato, è possibile stilare una lista
di parametri necessari alla taratura del regolatore, a cui si fa corrispondere la struttura
PIDPARAMS. La seguente tabella riporta i parametri principali che andranno a far
parte della struttura:
Parametro Tipo Descrizione
K Reale Guadagno (azione proporzionale)
TI Reale Tempo integrale
TD Reale Tempo Derivativo
N Reale Filtro sull’azione derivativa
Capitolo 5 - Progettazione
62
b Reale Peso sul set-point per l’azione proporzionale
c Reale Peso sul set-point per l’azione derivativa
Ts Reale Tempo di campionamento
CSmax Reale Limite superiore di saturazione della variabile di controllo
CSmin Reale Limite inferiore di saturazione della variabile di controllo
deltaCS Reale Passo d’incremento/decremento della variabile di controllo in
modalità manuale
L’informazione contenuta nella struttura precedente viene completata per mezzo di
una serie di variabili che descrivono il contesto di esecuzione del PID; esse vengono
raccolte nella struttura PIDLOOPDATA, di seguito tratteggiata:
Variabile Tipo Descrizione
SP Reale Valore del set-point
SPold Reale Valore del set-point al precedente passo di campionamento
PV Reale Valore del process value
PVold Reale Valore del process value al precedente passo di campionamento
CS Reale Valore del control signal (uscita del regolatore)
CSold Reale Valore precedente del control signal
Dold Reale Valore precedente dell’azione derivativa
MAN Booleano Indica la modalità di lavoro ( TRUE = manuale)
MANinc Booleano Se TRUE indica che CS è stata incrementata manualmente
MANdec Booleano Se TRUE indica che CS è stata decrementata manualmente
HIsat Booleano Se TRUE indica che è stato raggiunta la saturazione superiore
LOsat Booleano Se TRUE indica che è stato raggiunta la saturazione inferiore
Capitolo 5 - Progettazione
63
Noinc Booleano Se TRUE impedisce alla variabile CS di aumentare
Nodec Booleano Se TRUE impedisce alla variabile CS di diminuire
ForceMAN Booleano Se TRUE il PID viene forzato in modalità manuale
Per quanto riguarda l’interfaccia utente, si intende realizzarla in modo tale che
all’attivazione del sistema consenta all’operatore, nel caso sia presente più di un
regolatore, di selezionare un PID sul quale intervenire. Altrimenti, viene
automaticamente selezionato l’unico PID presente. In corrispondenza del regolatore
selezionato deve comparire il relativo menù, il quale è composto da una serie di voci a
cui si associano i parametri utili alla regolazione, le variabili del PID (Set-Point,
Control-Signal, Process-Value,…) e l’impostazione della modalità di lavoro (manuale /
automatico). Alcune voci possono essere modificate dall’operatore (ad esempio tutti i
parametri del regolatore), mentre altre possono solo essere lette (ad esempio il valore
della variabile Process-Value). Altre ancora, infine, possono essere modificate solo in
certe situazioni; questo è il caso della variabile Control-Signal, il cui valore può essere
modificato solo se il PID si trova in modalità manuale. Tranne nel caso
dell’impostazione del Set-Point e del Control-Signal (in modalità manuale), le
modifiche apportate non hanno effetto immediato sul controllo. Per renderle operative si
introduce alla fine del menu la voce “conferma”.
Per realizzare l’interfaccia utente, si individuano due strutture dati a due livelli logici
sovrapposti. A più basso livello, si pone la struttura denominata DISPLAY, la cui
funzione è quella di mappare in memoria il display fisico dell’RCX. Pertanto, in tale
struttura saranno presenti almeno i campi riportati nella seguente tabella:
Variabile Tipo Descrizione
Cifra_1 Carattere Prima cifra a sinistra del display
Cifra_2 Carattere Cifra del display
Cifra_3 Carattere Cifra del display
Cifra_4 Carattere Cifra del display
Capitolo 5 - Progettazione
64
Cifra_5 Carattere Ultima cifra a destra del display
Posizione punto Enumerazione Indica la posizione del punto decimale
Modifica Booleano Se TRUE si accende il simbolo di modifica
Nel livello logico superiore si pone la struttura che modellizza la voce di menù,
denominata VOCE_MENU.
Variabile Tipo Descrizione
Nome Stringa Etichetta del parametro da visualizzare
Valore Reale Valore numerico del parametro da visualizzare
Modificabile Booleano Se TRUE il parametro può essere modificato dall’utente
Con questa scelta è possibile costruire il menu di un PID come collezione delle
necessarie voci di menù.
3.1. Logica di funzionamento
Una volta introdotte le strutture dati, si vuole ora definire la logica con cui gestirle.
Le strutture relative ai dati del PID, oltre al loro impiego primario nell’algoritmo di
regolazione, sono utilizzate dall’interfaccia utente al fine di rendere disponibili i dati
all’operatore, sia per consultazioni che per impostazioni. Per quanto riguarda
PIDLOOPDATA, è necessario che sui dati in essa contenuti sia l’utente che il regolatore
abbiano un accesso diretto attraverso un’unica istanza. Per quanto riguarda
PIDPARAMS, tale soluzione, che può comportare dei rischi, non è necessaria. Pertanto,
per ragioni di sicurezza, di tale struttura si intende rendere disponibili due variabili:
PIDrun e PIDedit. La prima contiene i parametri utilizzati direttamente dal regolatore,
mentre la seconda viene utilizzata dall’interfaccia utente per le modifiche. Quest’ultime
avranno, dapprima, effetto solo sulla variabile PIDedit e, all’atto della conferma finale,
entreranno in elaborazione copiando i valori di PIDedit in PIDrun. In questo modo si
può garantire una maggiore sicurezza, consentendo all’operatore di eseguire delle
Capitolo 5 - Progettazione
65
modifiche e di poter, eventualmente, ritornare sui propri passi prima della conferma,
annullando le operazioni compiute.
4. ARCHITETTURA DELL’APPLICAZIONE
A questo punto dell’attività progettuale, si intende concretizzare le decisioni prese
nelle fasi precedenti sintetizzandole nella definizione dell’architettura del sistema.
Tenendo in considerazione le caratteristiche del supporto informatico a disposizione,
nella fattispecie BrickOS corredato dalle API per la programmazione in C/C++, si
giunge alla soluzione di organizzare l’applicativo come somma di più processi da
eseguire in pseudoparallelismo; sfruttando, quindi, il supporto fornito da BrickOS per il
multi-threading. Pertanto, si stabilisce di far corrispondere a ciascun modulo individuato
nella fase di modularizzazione un diverso processo di elaborazione indipendente.
Questa impostazione richiede necessariamente la definizione di appositi meccanismi
per la protezione dei dati. Di fronte agli inevitabili problemi di concorrenza che il multi-
threading introduce, si decide di sfruttare il supporto fornito dal sistema operativo. Così,
l’accesso in mutua esclusione alle strutture dati sarà garantito attraverso l’impiego di
semafori.
Si ritiene che questa soluzione possa contribuire ad implementare al meglio i
requisiti previsti, sia in termini di robustezza che di scalabilità. La struttura, così
progettata, presenta buone caratteristiche di flessibilità, mostrando pochi ostacoli ad
eventuali sviluppi futuri sia in termini di estensione delle funzionalità, che in termini di
riutilizzo dei singoli componenti, essendo essi fortemente indipendenti gli uni dagli
altri.
5. GESTIONE DEGLI EVENTI
Affinché la trattazione di questo argomento risulti il più possibile completa ed
esaustiva, si ritiene opportuno sintetizzare alcuni concetti e considerazioni
precedentemente sviluppati.
Capitolo 5 - Progettazione
66
La necessità di disporre di un’interfaccia operatore basata su criteri di usabilità ed
ergonomicità ha portato alla definizione dei pulsanti logici come astrazione dei due
pulsanti fisici effettivamente disponibili; da questo punto in poi, si farà riferimento solo
ed esclusivamente ai pulsanti logici, riferendosi ad essi semplicemente con il termine di
pulsanti (o bottoni).
L’applicazione deve permettere all’utente-operatore di visualizzare e modificare i
parametri di ciascuno dei PID che compongono il sistema di controllo. Le operazioni di
modifica avvengono secondo modalità differenti, in funzione del tipo di parametro. Da
questo punto di vista, è facile comprendere l’importanza del display come “strumento
guida” dell’interazione utente-controllore: in base allo stato del display, l’utente agisce
sui pulsanti resi disponibili dall’interfaccia e si aspetta che il sistema reagisca
esattamente nel modo previsto. Pertanto, è corretto sostenere che ad ogni evento
(pressione di un pulsante) l’interfaccia deve rispondere intraprendendo opportune azioni
(inclusa l’azione fittizia, corrispondente all’assenza di reazione).
Il sistema è in grado di rilevare cinque eventi, corrispondenti alla pressione dei
cinque tasti messi a disposizione dall’interfaccia operatore.
Queste prime osservazioni sono già sufficienti a giustificare la necessità di un
meccanismo che consenta di gestire gli eventi in modo flessibile e robusto. In altre
parole:
- nessun evento deve essere “perso”: il sistema deve garantire in ogni istante il
rilevamento fisico di tutti gli eventi.
- Un evento è considerato come un’entità “non durevole”: una volta consumata
non è possibile disporne nuovamente; per preservare la correttezza
dell’applicazione e la coerenza dei dati è necessario che ad un evento corrisponda
una ed una sola reazione.
- Il legame fra un evento e la corrispondente azione di risposta deve essere
dinamico, ovvero modificabile in base alle necessità. Infatti, uno stesso evento
rilevato in frangenti differenti può assumere significati profondamente differenti;
pertanto, le corrispondenti azioni di risposta dovranno essere conseguentemente
differenziate.
- Deve sempre essere possibile scegliere quali eventi rilevare. Infatti, può accadere
che, in un particolare frangente, un determinato evento non abbia alcun
Capitolo 5 - Progettazione
67
significato e, quindi, non debba comportare modifiche ai dati.
- In tutte le situazioni in cui un evento è di interesse per l’applicazione, è
necessario che ad esso sia associata un’azione (o un insieme di azioni) da
intraprendere in risposta all’evento stesso.
Questa serie di valutazioni costituisce una base informativa su cui impostare le
successive scelte progettuali.
5.1. Progettazione del gestore degli eventi
Il gestore degli eventi viene concepito come un blocco scomponibile in due entità
strettamente correlate ed interdipendenti:
- il rilevatore degli eventi;
- il registratore degli eventi.
Il concetto di base è semplice: si risponde ad un evento solo se si è dichiarato
esplicitamente di volerlo gestire.
Un meccanismo di questo tipo permette di raggiungere gli obiettivi di robustezza e
flessibilità di cui si è parlato in precedenza; inoltre, gode di una qualità molto
importante: la scalabilità. Nel seguito dell’esposizione risulteranno evidenti le ragioni di
tale affermazione.
5.1.1. Il rilevatore degli eventi
Sostanzialmente si tratta di un processo “demone”, ovvero sempre attivo, il cui
compito è quello di “mettersi in ascolto” di tutti gli eventi, indipendentemente dallo
stato del microcontrollore. Si può pensare ad esso come ad un elemento di confine tra il
mondo esterno e l’applicazione di controllo.
5.1.2. Il registratore degli eventi
In più occasioni, si è detto che:
- tutti gli eventi devono essere fisicamente rilevati;
- in un particolare frangente, non tutti gli eventi fisicamente rilevabili hanno
necessariamente un significato ai fini della corretta elaborazione dei dati.
Capitolo 5 - Progettazione
68
Pertanto, emerge la necessità di separare la fase di rilevamento da quella di reazione; il
registratore degli eventi assume proprio questa funzione.
Registrare un evento significa creare un legame fra esso e un’opportuna azione di
risposta costituita da una serie di operazioni da eseguire. Chiaramente, valgono le
seguenti regole:
- in ciascun istante temporale, ad un evento può essere associata una sola azione di
risposta;
- durante l’esecuzione dell’applicazione azioni differenti possono essere registrate
su uno stesso evento (ovviamente in istanti di tempo diversi);
- una stessa azione non può essere registrata contemporaneamente su due eventi
differenti.
Per preservare la coerenza e la correttezza dei dati, in alcune situazioni particolari (di
breve durata) sarà necessario disabilitare la risposta agli eventi, per fare in modo che il
sistema risulti insensibile a ciò che accade nel mondo esterno, salvo poi ripristinare gli
opportuni legami con le azioni di reazione. Ovviamente, tale servizio deve essere fornito
dal gestore degli eventi.
6. GESTIONE DEI CONTESTI
Anzitutto introduciamo il concetto di contesto, così come esso viene inteso nella
progettazione del software. Un contesto può essere definito come un legame univoco fra
uno stato del display e l’insieme degli eventi che per esso hanno significato; le azioni di
risposta associate a ciascun evento costituiscono parte integrante e caratterizzante del
contesto.
I contesti sono tra loro mutuamente esclusivi, nel senso che, ad ogni istante, di tempo
deve essere attivo uno ed un solo contesto (coerente con lo stato del display); inoltre,
l’applicazione di un contesto determina automaticamente la rimozione del contesto
precedente che, comunque, può essere nuovamente applicato in base alle future
necessità.
La combinazione data da gestione dinamica degli eventi e gestione dei contesti
permette di conferire al progetto dell’interfaccia operatore importanti proprietà di
Capitolo 5 - Progettazione
69
affidabilità, robustezza e, soprattutto, scalabilità.
6.1. Operare con i contesti
La creazione di un contesto può essere schematizzata in tre passi:
- Passo 1: anzitutto, è necessario stabilire quali eventi devono essere rilevati per
una corretta gestione del contesto (si ricorda che gli eventi fisicamente rilevabili
sono i cinque citati nella fase di analisi e specifica dei requisiti).
- Passo 2: per ciascuno degli eventi selezionati al passo precedente è necessario
definire un’opportuna azione di risposta, la quale può essere costituita da un
insieme di operazioni “elementari”; sostanzialmente ciascuna azione di risposta
verrà modellizzata come una funzione (intesa nel senso informatico del termine).
- Passo 3: si definisce una funzione che permetta di registrare, in modo atomico, le
azioni di risposta sui relativi eventi. A questo punto la struttura del singolo
contesto è completa.
Al momento opportuno sarà possibile applicare il contesto semplicemente richiamando
la funzione definita al passo 3.
6.2. Individuazione dei contesti
Rielaborando i requisiti dell’applicazione che sono stati presentati in precedenza, è
possibile individuare un primo insieme di contesti su cui basare l’attività di
implementazione. In questa fase non è possibile identificare tutti i contesti che
troveranno collocazione nella versione finale del software; alcuni di essi, infatti,
nasceranno da mere esigenze implementative e, pertanto, non sono prevedibili (né
giustificabili!) a livello di progetto.
Relativamente a ciascun contesto, viene di seguito riportata una breve descrizione
atta a qualificarlo, unita all’indicazione degli eventi per esso significativi (onde evitare
confusione, si precisa che per “selezione di un parametro” si intende l’operazione per
mezzo della quale l’utente vuole accedere alla modifica del parametro attualmente
visualizzato sul display. KEY_2_short è l’evento associato.)
Capitolo 5 - Progettazione
70
Nome Contesto Descrizione
Menù Selezione
PID
Attivazione
Viene applicato all’avvio del sistema ed ogni volta che si esce dal
menù parametri di uno dei regolatori.
Funzioni
Se la struttura di controllo si compone di più regolatori, è necessario
visualizzare e gestire il menù dei PID, offrendo funzionalità di:
- scorrimento del menù (→KEY_1_short);
- selezione del regolatore attualmente visualizzato sul display
(→KEY_2_long).
Nel caso in cui vi sia un solo regolatore, si applica automaticamente
il contesto di gestione del menù parametri PID.
Menù
Parametri PID
Attivazione
Viene applicato automaticamente all’avvio del sistema solo nel caso
di singolo regolatore. Inoltre, la sua attivazione è prevista quando:
- si seleziona un PID dal precedente menù, oppure
- si esce dal contesto di modifica di un generico parametro (sia
nel caso di conferma che di annullamento delle modifiche).
Funzioni
Per gestire correttamente il menù dei parametri è necessario disporre
di:
- scorrimento del menù (→KEY_1_short);
- selezione del parametro attualmente visualizzato sul display
(→KEY_2_short). A seconda del tipo di parametro e della
modalità di funzionamento del regolatore, la selezione di un
parametro determina l’applicazione di un particolare contesto
(modifica voce numerica, modifica voce incrementale,
modifica voce lessicale, visualizzazione variabile controllata).
Modifica
Valore
Lessicale
Attivazione
Applicabile solo a partire dal menù dei parametri, nel caso in cui il
parametro attualmente visualizzato (e selezionato dall’utente) sia una
Capitolo 5 - Progettazione
71
voce lessicale (ad esempio: modalità di funzionamento del
regolatore).
Funzioni
Una volta selezionata una voce lessicale deve essere possibile:
- far scorrere l’elenco dei possibili valori per quella voce
(→KEY_1_short);
- annullare l’eventuale modifica apportata (→KEY_2_short);
- confermare la modifica apportata (→KEY_2_long);
Negli ultimi due casi si torna al menù parametri del regolatore.
Modifica
Valore
Numerico
Attivazione
Applicabile solo a partire dal menù dei parametri, nel caso in cui il
parametro attualmente visualizzato (e selezionato dall’utente) sia una
voce numerica (ad esempio: guadagno, tempo integrale, tempo
derivativo).
Funzioni
Nel caso in cui l’utente decida di modificare una voce numerica
(seguendo la procedura già descritta) si vuole che:
- sia possibile selezionare (→KEY_2_short) singolarmente
ciascuna delle cinque cifre ed il punto decimale affinché sia
possibile modificarne (→KEY_1_short), rispettivamente, il
valore e la posizione;
- sia possibile annullare o confermare le modifiche apportate al
parametro.
Risulta evidente il fatto che le operazioni di annullamento o conferma
richiederanno un’ulteriore contesto affinché possano essere gestite in
modo efficace, ma ciò esula dalle competenze della progettazione per
ricadere nel dominio dell’implementazione; pertanto, si rimanda al
capitolo successivo la gestione di questo tipo di problematiche.
Modifica
Valore
Incrementale
Attivazione
Applicabile solo a partire dal menù dei parametri, nel caso in cui il
parametro attualmente visualizzato (e selezionato dall’utente) sia una
Capitolo 5 - Progettazione
72
voce incrementale (ad esempio: set-point incrementale).
Funzioni
Le uniche operazioni di modifica eseguibili su una voce incrementale
sono:
- incremento (→KEY_1_short) o decremento (→KEY_1_2) del
valore, in base al passo prestabilito;
- annullamento (→KEY_2_short) o conferma (→KEY_2_long)
delle modifiche.
Chiaramente, il passo di incremento/decremento è una caratteristica
del singolo parametro e, in genere, sarà diverso da caso a caso;
l’imposizione del passo corretto è, ancora una volta, un dettaglio
implementativo.
Evoluzione
Variabile
Controllata
Attivazione
Applicabile solo a partire dal menù dei parametri, solo quando il
parametro attualmente visualizzato e selezionato corrisponde alla
variabile controllata.
Funzioni
Si tratta di un contesto molto semplice giustificato dalla necessità di
visualizzare l’evoluzione temporale dell’uscita del processo da
controllare. L’unica funzione messa a disposizione è quella di uscita
dal contesto (→KEY_2_long).
Conferma o
Annullamento
Attivazione
Nel caso in cui siano state apportate modiche ai parametri del
regolatore selezionato, come ultimo elemento del menù dei parametri
viene visualizzata la voce di conferma; quando essa viene selezionata
dall’utente, si applica il presente contesto.
Funzioni
Permette di realizzare la “conferma di secondo livello” di cui si è già
parlato; da ciò si evince che le uniche funzioni disponibili sono la
conferma (→KEY_2_long) o l’annullamento (→KEY_2_short) di
tutte le modifiche apportate.
Capitolo 5 - Progettazione
73
Di seguito viene riportato un diagramma che fornisce la versione progettuale della
struttura dei contesti (le frecce sono rappresentative degli eventi che determinano il
passaggio da un contesto all’altro):
MENU’ SELEZIONE PID
MENU’ PARAMETRI PID
MODIFICA VOCE NUMERICA
MODIFICA VOCE LESSICALE
MODIFICA VOCE INCREMENTALE
EVOLUZIONE VARIABILE
CONTROLLATA
CONFERMA O ANNULLAMENTO
Capitolo 6 - Implementazione
74
Capitolo 6
Implementazione
Capitolo 6 - Implementazione
75
In questo capitolo si discute l’implementazione, basata sul linguaggio C, delle specifiche progettuali.
1. INTRODUZIONE
Nella fase di programmazione i singoli moduli vengono realizzati nel linguaggio
scelto e, successivamente, sottoposti a test per verificare la loro adeguatezza rispetto alla
specifica di progetto. I moduli devono poi essere integrati, al fine di produrre il sistema
completo e collaudato.
In questo capitolo si discute l’intera attività implementativa, ponendo l’accento su
tutti quegli aspetti che caratterizzano e qualificano il progetto in esame. Dal punto di
vista dell’organizzazione del testo, si ripercorre lo schema adottato nella disamina della
fase progettuale. Tutti gli aspetti relativi alla sincronizzazione dei thread sono trattati
nella parte finale, in quanto richiedono una buona conoscenza del codice da parte del
lettore.
Il capitolo successivo affronterà, invece, tutte le problematiche relative al test di
unità, all’integrazione ed al test di sistema.
Prima di procedere, è necessario precisare quanto segue:
- quando si parlerà di “funzioni”, si farà riferimento all’accezione informatica del
termine, salvo diversa indicazione;
- i termini “task”, “thread” e “processo” sono considerati sinonimi.
2. ACCORGIMENTI IMPLEMENTATIVI
La fase d’implementazione è preceduta da un’analisi di carattere generale sul
software da realizzare; l’obiettivo di tale analisi è quello di individuare quali
problematiche possono affliggere il prodotto, al fine di impostare delle linee guida per
l’attività d’implementazione che garantiscano di porsi al riparo da tali difficoltà.
Di fronte alla necessità di realizzare codice embedded ci si scontra con una serie di
problematiche legate all’interazione con l’hardware attraverso il sistema operativo. Già
in fase di progettazione si sono ipotizzate difficoltà legate a questa caratteristica;
Capitolo 6 - Implementazione
76
pertanto, nella suddivisione in moduli del progetto (vedi Capitolo 2) si sono adottati
degli accorgimenti volti a semplificare la fase d’implementazione. A questo punto del
progetto, è necessario affrontare la questione definendo metodologie di lavoro e di
organizzazione del codice volte a minimizzare le fonti di errore e le perdite di tempo.
La soluzione più semplice, e presumibilmente la più ovvia, consiste nell’organizzare
il codice in due parti distinte:
- la parte detta “kernel”, completamente indipendente dall’RCX, che esegue una
pura elaborazione dei dati. Può essere considerata come una sorta di scatola nera
che mette ha disposizione degli ingressi e delle uscite per interagire con
l’ambiente esterno;
- la seconda parte realizza l’interazione tra il kernel e il mondo esterno attraverso
l’RCX. Si occupa di rilevare gli input (pressione dei bottoni, lettura dei sensori),
di comunicare gli output (scrittura uscite, visualizzazione dei dati sul display) e di
fornire le corrette temporizzazioni.
Questa soluzione offre una buona risposta al seguente problema di carattere pratico:
ciascun programma che va eseguito sull’RCX deve essere redatto e compilato su PC e,
in seguito, scaricato sul dispositivo. Tale procedura, oltre ad essere lenta, non consente
alcuna forma di “debugging”, rendendo, così, estremamente lenta e complessa l’attività
implementativa di un codice non banale.
Con la suddivisione sopra descritta è possibile procedere ad implementare e testare il
“kernel” interamente su PC, utilizzando tutti gli strumenti messi a disposizione dai
moderni ambienti di sviluppo. Inoltre, relegando tutte le operazioni di interazione con
l’hardware in un ambito circoscritto, consente di individuare più facilmente e
velocemente i problemi che sorgono, rendendo più efficiente e sicura l’attività di
sviluppo del codice.
Per applicare al meglio questa scelta, è necessario operare una corretta suddivisione
delle funzionalità, individuando per ciascuna di esse l’area di appartenenza: “kernel” o
parte di interazione con l’RCX. Pertanto, ciascuno dei tre moduli in cui si è suddiviso il
progetto risulta composto da una sezione d’interazione con l’RCX e da una totalmente
indipendente da esso, come di seguito riportato:
Capitolo 6 - Implementazione
77
Modulo Funzionalità del Kernel Funzionalità di
interazione
Esecuzione del PID Algoritmo PID
- Lettura ingessi
- Scrittura uscite
- Temporizzazione
Gestione degli eventi Gestione di contesti
- Rilevazione degli
eventi (pressioni
dei pulsanti)
Visualizzazione dei dati Organizzazione interna dei dati - Scrittura sul
display dell’RCX
3. ESECUZIONE DEL PID
L’esecuzione dell’algoritmo di regolazione è sicuramente l’attività principale che il
sistema è chiamato a svolgere; oltre all’algoritmo vero e proprio, questa attività
comprende una serie di funzioni e funzionalità di corredo, indispensabili sia ai fini della
messa in opera che del funzionamento del regolatore. Tra queste, a titolo d’esempio, si
cita il controllo del rispetto della frequenza di campionamento, la lettura degli ingressi e
la scrittura delle uscite.
Nel precedente capitolo è stata definita, in maniera generale, l’architettura dell’intera
applicazione; in particolare si è giunti alla conclusione di associare al modulo
“esecuzione del PID” un processo di elaborazione indipendente. A questo punto, è bene
entrare in maniera più approfondita nel merito di questa scelta. Di fronte alle necessità
d’implementare un sistema in grado di gestire più di un anello di regolazione e di
soddisfare specifici vincoli, legati all’esecuzione di un singolo algoritmo, si è giunti alla
soluzione di associare un thread a ciascun regolatore presente nel sistema. Ciò consente
una realizzazione modulare ed altamente scalabile del sistema, per mezzo della quale
sarà sufficiente lanciare in esecuzione tanti thread quanti sono gli anelli nel sistema,
avendo cura di operare le corrette interconnessioni fra ingressi e uscite dei regolatori e
di realizzare un corretto interfacciamento verso il processo.
Capitolo 6 - Implementazione
78
Nel seguito del paragrafo si affronteranno alcuni aspetti caratteristici
dell’implementazione dell’algoritmo di regolazione, per poi passare alla descrizione del
thread d’esecuzione, definendo anche alcune funzioni ausiliarie alla pura regolazione.
3.1. Contesto d’esecuzione del regolatore
Prima di procedere con la descrizione delle funzioni realizzate, è utile conoscere il
contesto di esecuzione del PID, il quale, in buona sostanza, è costituito dalle variabili
globali utilizzate dalla funzione di regolazione. Ad ogni regolatore presente nel sistema
sono associate:
− una variabile di tipo PIDLOOPDATA chiamata PIDloop;
− due variabili di tipo PIDPARAMS chiamate PIDa e PIDb, alle quali si accede solo
attraverso una coppia di puntatori: PIDrun e PIDedit.
Dovendo gestire più di un regolatore, tali variabili sono raggruppate in vettori la cui
dimensione è stabilita staticamente dalla costante NUMBER_OF_PIDs; pertanto, nel
sistema sono definiti i seguenti array:
PIDPARAMS PIDa[NUMBER_OF_PIDs], PIDb[NUMBER_OF_PIDs],
*PIDedit[NUMBER_OF_PIDs], *PIDrun[NUMBER_OF_PIDs];
PIDLOOPDATA PIDloop[NUMBER_OF_PIDs];
Più precisamente, durante l’esecuzione, il PID i-esimo utilizza solo la variabile
PIDloop[i] e il puntatore PIDrun[i]. L’altro puntatore che gli è associato (PIDedit[i])
viene utilizzato per consentire all’utente la modifica dei parametri. Le modifiche
apportate ai parametri, a seguito della conferma di primo livello (vedi Capitolo 5),
vengono registrate nella variabile puntata da PIDedit[i]. A questo punto, attraverso la
conferma di secondo livello, l’utente può decidere di renderle operative oppure di
annullarle. Nel caso di conferma, viene invocata la funzione switchPIDData, di seguito
descritta, mentre in caso di annullamento non si fa altro che copiare il contenuto della
variabile puntata da PIDrun[i] in quella puntata da PIDedit[i].
3.1.1.1. Cambio del contesto di esecuzione
La funzione switchPIDData(int indexPID) si occupa di cambiare il contesto
d’esecuzione del regolatore; ciò consiste semplicemente nello scambio reciproco di
Capitolo 6 - Implementazione
79
indirizzi fra PIDrun[indexPID] e PIDedit[indexPID].
Per prima cosa, la funzione disabilita gli eventi invocando la funzione
disableEvents(); il sistema, a questo punto, non risponde più alla pressione dei pulsanti.
Il passo successivo consiste nel verificare lo stato della variabile
PIDloop[indexPID].IamRunning:
− se TRUE, significa che il cambio di contesto non è abilitato, e, pertanto, la funzione
si pone in attesa dell’abilitazione;
− se FALSE, il cambio di contesto è abilitato e la funzione può procedere con lo
scambio degli indirizzi, tale operazione avviene attraverso un puntatore d’appoggio
(PIDPARAMS *buffer).
Infine, viene spento il simbolo di modifica sul display (display.change = FALSE) e
gli eventi vengono ripristinati (setContextPID()).
Nei paragrafi successivi viene trattata la gestione della sicurezza nel cambio di
contesto attraverso la descrizione del meccanismo che ne controlla l’abilitazione per
mezzo del variabile PIDloop[i].IAmRunning.
3.2. Algoritmo di regolazione
Questo algoritmo è già stato discusso approfonditamente nel Capitolo 2, nel quale,
tra l’altro, si fornisce un’implementazione in pseudo-codice. In questo paragrafo si
vogliono affrontare solo alcuni aspetti caratterizzanti, utili a contestualizzare la funzione
di regolazione con il resto del progetto.
L’algoritmo si compone essenzialmente di due parti: l’inizializzazione, eseguita una
sola volta all’avvio del regolatore, e il codice ricorsivo, eseguito ad ogni passo di
campionamento. Si è deciso d’implementare queste due parti attraverso funzioni
distinte: setUpPid, per quanto riguarda l’inizializzazione, e PID per quanto concerne il
codice ricorsivo.
3.2.1. Funzione di inizializzazione
La funzione setUpPID(), per prima cosa, inizializza tutti i puntatori necessari, il cui
numero dipende da quanti regolatori sono presenti nel sistema. Questa fase avviene
attraverso il seguente frammento di codice:
Capitolo 6 - Implementazione
80
for (i=0;i<NUMBER_OF_PIDs;i++)
PIDrun[i]=&PIDa[i];
PIDedit[i]=&PIDb[i];
Non ha alcuna importanza quale dei due puntatori è associato alla variabile PIDa[i] o
alla PIDb[i], i due, infatti, continueranno a scambiarsi la variabile puntata a seguito di
ogni conferma di secondo livello.
La fase successiva consiste nell’inizializzazione del contesto d’esecuzione: per
ciascun regolatore si assegnano i valori di partenza alle variabili PIDa[i], PIDb[i] e
PIDloop[i]. A titolo d’esempio, di seguito si riporta il frammento di codice in cui viene
inizializzato il contesto del regolatore individuato dall’indice 0:
PIDa[0].K = PIDb[0].K = 1.00;
PIDa[0].Ti = PIDb[0].Ti = 0.30;
PIDa[0].Td = PIDb[0].Td = 0.00;
PIDa[0].N = PIDb[0].N = 5.00;
PIDa[0].b = PIDb[0].b = 0.50;
PIDa[0].c = PIDb[0].c = 0.00;
PIDa[0].Ts = PIDb[0].Ts = 0.01;
PIDa[0].CSmax = PIDb[0].CSmax = 100.00;
PIDa[0].CSmin = PIDb[0].CSmin = 0.00;
PIDa[0].deltaCSMAN = PIDb[0].deltaCSMAN = 0.50;
PIDloop[0].SP = PIDloop[0].SPold = 0.00;
PIDloop[0].PV = PIDloop[0].PVold = 0.00;
PIDloop[0].CS = PIDloop[0].CSold = 0.00;
PIDloop[0].Dold = 0.00;
PIDloop[0].MAN = FALSE;
PIDloop[0].MANinc = FALSE;
PIDloop[0].MANdec = FALSE;
PIDloop[0].HIsat = FALSE;
PIDloop[0].LOsat = FALSE;
PIDloop[0].NoInc = FALSE;
PIDloop[0].NoDec = FALSE;
PIDloop[0].ForceMAN = FALSE;
PIDloop[0].IAmRunning = FALSE;
Capitolo 6 - Implementazione
81
Come si può vedere, per ovvi motivi di coerenza dei dati, le variabili PIDa e PIDb sono
inizializzate con gli stessi valori.
3.2.2. Funzione di regolazione
La funzione PID(PIDPARAMS* R, PIDLOOPDATA* L) implementa l’algoritmo di
regolazione vero e proprio, al quale sono aggiunte funzioni ausiliarie quali anti wind-up,
logiche di blocco e commutazioni A/M e M/A bumpless.
Per realizzare tale algoritmo, si definiscono le seguenti variabili locali di tipo REAL:
deltaSP, deltaPV, deltaP, deltaI, D, deltaD, deltaCS. Per quanto riguarda
l’implementazione, in questa fase non si è fatto altro che tradurre in linguaggio C
l’algoritmo in pseudo-codice presentato nel capitolo 2.
Anzitutto, la funzione rileva le variazioni del set-point e del process-value:
deltaSP = L->SP - L->SPold;
deltaPV = L->PV - L->PVold;
In seguito viene rilevata la modalità di lavoro (manuale o automatico), controllando
l’impostazione dell’operatore e l’eventuale forzatura in manuale attraverso la seguente
condizione:
!L->MAN && !L->ForceMAN
Se il sistema si trova in automatico, vengono calcolate deltaP, deltaI, D, deltaD e
deltaCS (per l’esplicitazione dei calcoli si rimanda al Capitolo 2). Dopo di che si passa
alla gestione delle logiche di blocco, la quale si riassume nella seguente riga di comando
che chiude il segmento di codice eseguito esclusivamente per il controllo in automatico:
if ((deltaCS>0 && L->NoInc) || (deltaCS<0 && L->NoDec)) deltaCS=0;
Se, invece, il regolatore lavora in modalità manuale, per prima cosa la funzione pone
a zero il valore delle variabili locali deltaCS e D, poi passa alla verifica dello stato dei
comandi locali d’incremento e di decremento, aggiornando di conseguenza deltaCS:
if(L->MANinc && !L->MANdec) //incremento
deltaCS = R->deltaCSMAN;
L->MANinc = FALSE;
if(L->MANdec && !L->MANinc) //decremento
Capitolo 6 - Implementazione
82
deltaCS = -R->deltaCSMAN;
L->MANdec = FALSE;
Questi due blocchi chiudono la sezione dedicata puramente al controllo in modalità
manuale.
Ora, indipendentemente dalla modalità di lavoro, la funzione calcola il nuovo valore
del controllo: L->CS = L->CSold+deltaCS e su tale valore applica l’azione di controllo
che impedisce il wind-up:
if(L->CS > R->CSmax)
L->CS=R->CSmax;
if(L->CS < R->CSmin)
L->CS=R->Csmin ;
La funzione conclude la sua attività aggiornando lo stato finale del regolatore:
L->CSold = L->CS;
L->SPold = L->SP;
L->PVold = L->PV;
L->Dold = D;
L->HIsat = (L->CS==R->CSmax);
L->LOsat = (L->CS==R->CSmin);
3.3. Thread d’esecuzione del PID
Nel thread executePID sono eseguite sequenzialmente tutte le attività necessarie alla
regolazione: lettura degli ingressi, esecuzione della funzione PID e scrittura delle uscite.
A fianco di queste tre operazioni fondamentali è realizzato un controllo, che consiste
nella verifica del passo di esecuzione, il cui fine è quello di far rispettare la frequenza di
campionamento. Inoltre, viene applicata una protezione ai parametri del controllo, che
inibisce il cambio di contesto durante l’esecuzione dell’algoritmo di regolazione; a tale
scopo, nella struttura dati PIDLOOPDATA è stato introdotto un campo di tipo booleano
denominato IAmRunning.
Si passa ora a descrivere l’implementazione del thread, nella quale, per semplicità, si
fa riferimento al primo regolatore PID della struttura, quello individuato dall’indice 0.
Dopo l’inizializzazione di alcune variabili locali, si entra nel ciclo che viene posto in
esecuzione parallela agli altri processi. In esso, per prima cosa, si inibisce il cambio
Capitolo 6 - Implementazione
83
PIDedit → PIDrun settando a TRUE la variabile PIDloop[0].IamRunning. Una volta
protetto il contesto di esecuzione, si rileva l’istante di inizio ciclo attraverso la seguente
riga di codice: time_cycle = sys_time; in essa l’istante di tempo attuale, fornito da
BrickOS attraverso la variabile sys_time, viene memorizzato nel time-marker
time_cycle.
Il passo successivo consiste nella lettura degli ingressi: il valore letto dal sensore, che
misura la variabile controllata, viene scritto nella variabile Process Value:
PIDloop[0].PV. La macro da utilizzare per accedere alla misura fornita dal sensore
dipende dal tipo di sensore utilizzato; per i dettagli implementativi che riguardano
questa fase si rimanda il lettore al paragrafo ‘Interfacciamento verso il processo’.
Una volta letti gli ingressi si esegue l’elaborazione, ciò avviene semplicemente
attraverso la chiamata alla funzione di regolazione: PID(PIDrun[0],&PIDloop[0]).
Al termine di questa procedura, il nuovo valore del controllo viene scritto sull’uscita.
Anche per quanto riguarda i dettagli implementativi della scrittura sulle uscite, si fa
riferimento al paragrafo ‘Interfacciamento verso il Processo’. In questa fase, si può solo
introdurre che il motore è l’unico dispositivo attuatore per ora disponibile; pertanto la
scrittura delle uscite deve avvenire obbligatoriamente attraverso la funzione offerta da
BrickOS: motor_x_speed(int speed).
Dopo che il valore in uscita è stato scritto (l’attuale ciclo di elaborazione è concluso),
si riporta il livello logico della variabile PIDloop[0].IamRunning a FALSE. In questo
modo si rende possibile un eventuale cambio di contesto, che ora può avvenire in
sicurezza, senza che sia fonte d’inconsistenza dei dati.
L’ultimo passo del ciclo è la temporizzazione del PID, in cui si verifica che il passo
di campionamento sia rispettato. La verifica avviene attraverso la seguente riga di
codice: wait_event(&checkSampleTime,time_cycle). Con questo comando il thread
executePID viene posto in attesa che il tempo di campionamento sia trascorso. Per far
questo si utilizza come funzione di risveglio checkSampleTime, la cui implementazione
è la seguente:
wakeup_t checkSampleTime(wakeup_t data)
return ((sys_time - data) >= PIDrun[0]->Ts*1000);
A questa funzione viene passato come parametro il time-marker time-cycle acquisito
Capitolo 6 - Implementazione
84
all’inizio del ciclo. Seguendo la tecnica già descritta, dalla differenza fra l’istante di
tempo attuale e quello d’inizio ciclo, si misura la durata del loop fino ad ora. Se il tempo
trascorso è inferiore al Sample-time (PIDrun[0]->Ts*1000), il thread viene sospeso
sino a che il passo di campionamento non sia terminato. Solo quando il tempo trascorso
è maggiore o uguale al periodo di campionamento, il thread viene risvegliato ed il ciclo
riprende dall’inizio. La figura seguente schematizza l’andamento temporale della
variabile IAmRunning:
PID 0
Ts 0
IAmRunning = TRUE
Ts 1
PID 1
PIDloop[0].IAmRunning
PIDloop[1].IAmRunning
Questo meccanismo di temporizzazione non fornisce alcuna protezione di fronte ad
un’elaborazione che impiega un tempo superiore al Sample-time. Sarà compito
dell’utente impostare correttamente la durata di Ts, avendo cura di scegliere un valore
che il sistema sia in grado di rispettare.
3.4. Interfacciamento verso il processo
Per controllare un processo è necessario poter agire, in modo coordinato, sulle
attività elementari che lo compongono, al fine di modularne le uscite e di garantire le
condizioni fisiche di svolgimento più opportune. Pertanto, per il controllo automatico di
un processo, oltre ad un’unità di elaborazione, che calcoli con continuità le condizioni
di controllo, sono necessari dei dispositivi di misura (sensori) che rilevino con
continuità le condizioni di funzionamento e dei dispositivi d’attuazione (attuatori) che
consentano alle azioni di controllo di agire fisicamente sul processo. E’ necessario,
inoltre, un sistema di comunicazione tra l’unità di controllo e i dispositivi di misura e
Capitolo 6 - Implementazione
85
attuazione. L’interfaccia verso il processo risulta quindi essere formata da due catene di
dispositivi fisici e componenti software: una che si occupa dell’acquisizione delle
misure e l’altra dell’attuazione dell’azione di controllo.
Con riferimento ad un’unità di elaborazione digitale, in linea di massima nella catena
di acquisizione si possono individuare in sequenza i seguenti elementi (vedi [2]):
sensore, condizionamento del segnale, convertitore analogico/digitale (DAC) e ingresso
dell’unità di calcolo. Di seguito è riportato lo schema a blocchi che descrive la catena
d’acquisizione.
La catena d’attuazione, seguendo la direzione che va dal controllore verso il
processo, è costituita in linea di principio dai seguenti elementi: convertitore analogico
digitale, uscite di controllo e attuatore. La figura di seguito schematizza la sequenza dei
dispositivi.
Per quanto riguarda il progetto argomento di questa tesi, si è stabilito di realizzare
come “banco di prova” un anello per il controllo della velocità di rotazione di un
motore. Di seguito si riporta la descrizione della realizzazione della catena d’uscita e di
quella d’ingresso nel progetto.
3.4.1. Output
La catena delle uscite prevede come organo attuatore il motore. Essendo
Sensore
Sensore
Sensore
Sensore
Con
dizi
onam
ento
Con
vers
ione
D
ati
Elaborazione
Co
nver
sion
e
Dat
i
ElaborazioneUscite dicontrollo
Attuatore
Attuatore
Attuatore
Capitolo 6 - Implementazione
86
quest’ultimo l’unico dispositivo di output messo a disposizione dal produttore, tutte le
connessioni fisiche risultano essere standardizzate e non è necessario alcun elemento di
condizionamento del segnale né, tantomeno, di conversione digitale/analogico. Pertanto,
la realizzazione fisica della catena degli output si è risolta nella connessione dell’uscita
‘A’ dell’RCX con un motore, attraverso l’apposito cavo fornito dal produttore. Per
quanto riguarda la gestione software della catena, BrickOS fornisce tutte le funzioni per
operare un controllo completo sui motori (vedi Capitolo 3), di seguito è riportata la
descrizione dell’implementazione nel thread executePID della scrittura delle uscite, che
in precedenza era stata omessa.
Nella sezione che inizializza il ciclo, viene impostata la direzione di rotazione e si
pone a 0 la velocità del motore:
motor_a_dir(fwd);
motor_a_speed(0);
All’interno del ciclo vero e proprio, la scrittura degli output viene posta di seguito
alla chiamata della funzione PID; prima d’impostare il valore di output è necessario
operare una riduzione in scala della variabile CS, in modo tale che il controllo rientri nel
range di valori ammissibili dalla funzione motor_a_speed(int speed). Si imposta quindi
la seguente proporzione 255 : (CSMAX-CSMIN) = X : (CS - CSMIN), in cui CSMAX
e CSMIN rappresentano rispettivamente gli estremi superiore ed inferiore di saturazione
della variabile CS; tali valori vengono impostati dall’utente (nel caso in esame si è
scelto CSMAX = 100 e CSMIN = 0). La sezione di codice che esegue la scrittura delle
uscite viene riportata qui di seguito.
CSraw=255*(PIDloop[0].CS-PIDrun[0]->CSmin)/(PIDrun[0]->Csmax- PIDrun[0]->CSmin);
motor_a_speed((int)CSraw);
Come si può vedere il risultato della riduzione in scala viene posto in una variabile
locale di tipo REAL Csraw, per poi essere passato alla funzione di scrittura a seguito di
una operazione di casting.
3.4.2. Input
Per quanto riguarda la catena d’acquisizione della misura, la realizzazione non è
risultata semplice come nel caso dell’uscita, in quanto Lego Mindstorms non fornisce
Capitolo 6 - Implementazione
87
un sensore in grado di rilevare direttamente la velocità di rotazione di un albero. Ad una
prima analisi, il dispositivo che più si presta a tale scopo è parso essere il sensore di
rotazione, ma, nonostante i numerosi tentativi, anche al di fuori del progetto in esame,
non si è giunti ad una soluzione accettabile, in grado di fornire una misura abbastanza
realistica per un range di velocità accettabile.
Di conseguenza, per questo progetto si è sviluppata una soluzione “ad-hoc”, che
consiste nell’utilizzare un motore come sensore di rotazione, o, meglio, come una
dinamo tachimetrica. Per rendere operativa questa soluzione, si è progettato e costruito
un circuito di condizionamento in grado di adattare il segnale prodotto dalla dinamo
tachimetrica agli ingressi dell’RCX.
Ponendo in rotazione la dinamo, questa produce una sequenza di impulsi (molto
disturbati) di ampiezza 0÷10V la cui frequenza dipende dalla velocità con cui il
dispositivo è messo in rotazione; gli ingressi dell’RCX, dal canto loro, ammettono
segnali analogici 0÷5 V su cui viene operata una conversione A/D con un convertitore a
10 bit. Per operare gli adattamenti necessari si è realizzato un dispositivo in grado di
filtrare e di ridurre l’ampiezza del segnale proveniente dalla dinamo; qui di seguito è
riportato lo schema del circuito realizzato:
Il circuito è composto da un partitore resistivo, la cui funzione è quella di dimezzare
l’ampiezza del segnale; segue un filtro passivo passa basso (RC), che ha il compito di
stabilizzare il segnale e di eliminare la componente di rumore in alta frequenza. L’uscita
del filtro rappresenta il valor medio del segnale che vi è applicato in ingresso. Non
avendo inserito uno stadio separatore fra il partitore ed il filtro RC, la costante di tempo
R1 =10 KΩ R4 =100 Ω
R2 =10 KΩ C = 100 µF
R3 =660 KΩ
R 1
R 2
R 3
C
+Vcc
-Vcc
R 4
RCX
SENSORE
LM358
LN148
Capitolo 6 - Implementazione
88
risulta essere:
ssCRRRRR
C 06,00665,0321
21 ≅=⋅
+
+⋅
=τ
L’ultimo stadio, separato tramite un buffer dal precedente, è costituito da un ponte a
diodi (per ulteriori informazioni consultare [14]) che costituisce un’interfaccia general-
porpuse per segnali compresi tra 0÷5V; esso simula all’ingresso dell’RCX, cui è
connesso, il comportamento di un sensore di luce. Pertanto, l’utilizzo del ponte consente
di avere accesso alla misura tramite la macro LIGHT_X, che in modalità attiva (vedi
Capitolo 3) fornisce valori compresi tra 0 e 100. In particolare, in corrispondenza di un
segnale d’ingresso di ampiezza 5V la macro ritorna il valore 0, mentre in assenza di
segnale (0V) il valore della macro è 100.
Nella pratica la catena d’acquisizione è realizzata calettando, tramite una ruota
dentata, un motore con funzione di dinamo al motore con funzione di attuatore. Tramite
un cavo Lego-BNC, realizzato appositamente per questo progetto, si preleva il segnale
dalla dinamo e lo si porta al circuito di condizionamento realizzato su basetta millefori.
Il segnale condizionato viene prelevato in uscita dalla scheda e portato all’ingresso 1
dell’RCX attraverso un cavo BNC-Lego.
La lettura degli ingressi avviene semplicemente tramite la seguente riga di comando:
PIDloop[0].PV = (REAL)(100-LIGHT_1); in questo modo, in assenza di segnale PV è
pari 0, mentre con 5V in ingresso PV è uguale a 100.
4. GESTIONE DEGLI EVENTI
In precedenza, si è visto come la progettazione del gestore degli eventi abbia preso le
mosse dalle seguenti assunzioni:
- il sistema deve rispondere ad ogni evento (pressione di un pulsante)
intraprendendo opportune azioni (inclusa l’azione fittizia);
- il legame fra un evento e la corrispondente azione di risposta deve essere
dinamico.
L’esigenza di poter disporre di un meccanismo flessibile e robusto allo stesso tempo, ha
portato alla concezione di due entità interagenti: il rilevatore ed il registratore degli
Capitolo 6 - Implementazione
89
eventi.
La loro implementazione si basa sostanzialmente su due elementi chiave: la funzione
di risveglio wait_event fornita da BrickOS ed i puntatori a funzione previsti dal
linguaggio C; nei prossimi paragrafi si chiarirà l’impiego di tali funzionalità nella
realizzazione del gestore degli eventi.
Si precisa fin da ora che i pulsanti “KEY_1” e “KEY_2”, cui si farà riferimento nel
codice, sono un’astrazione dei pulsanti fisici BUTTON_VIEW e BUTTON_PROGRAM
dell’RCX e risultano così definiti:
#define KEY_1 BUTTON_VIEW
#define KEY_2 BUTTON_PROGRAM
4.1. Architettura implementativa
Lo scopo di questo paragrafo è quello di fornire una visione d’insieme della struttura
implementativa cosicché, durante la successiva presentazione analitica del codice, sia
possibile collocare ciascun tassello in un quadro più ampio del quale siano già noti
confini e regole. Questo modo di procedere permetterà di individuare i meccanismi
dominanti e le relative modalità di interazione.
Al fine di semplificare la trattazione, si esaminerà tale struttura limitatamente ad un
singolo evento; per estensione, sarà poi possibile applicare gli stessi ragionamenti agli
altri eventi di sistema.
Si consideri, a titolo di esempio, l’evento KEY_1_short; la figura seguente
schematizza la struttura implementativa ad esso relativa:
Puntatore alla funzione di risposta per KEY_1_short
Generica Funzione di Risposta
Rilevatore di eventi
Funzione di Registrazione
Capitolo 6 - Implementazione
90
Anzitutto, è necessario disporre di un puntatore atto a contenere l’indirizzo di memoria
di una generica funzione priva di parametri in ingresso; all’avvio dell’applicazione,
viene inizializzato con il riferimento ad una funzione fittizia (ovvero priva di istruzioni)
opportunamente definita. Ogniqualvolta il “meccanismo di ascolto” rileva l’evento in
questione, il sistema reagisce invocando la funzione il cui indirizzo è memorizzato nel
puntatore sopraccitato. Infine, nel caso in cui si voglia associare all’evento una nuova
funzione di risposta, è sufficiente invocare la funzione di registrazione; essa richiede,
come unico parametro, proprio il nome della funzione di risposta.
A questo punto dovrebbero risultare chiari gli aspetti fondamentali del gestore degli
eventi; sulla scorta di tali informazioni si può procedere alla presentazione del codice.
4.2. Il registratore degli eventi
Per ciascuno dei cinque eventi rilevabili dal sistema, si definisce un puntatore a
funzione; pertanto, si avranno le seguenti definizioni visibili a livello globale:
void (*keyOneQuick)(void); //Azione associata a KEY_1_short
void (*keyOneAtLength)(void); //Azione associata a KEY_1_long
void (*keyTwoQuick)(void); //Azione associata a KEY_2_short
void (*keyTwoAtLength)(void); //Azione associata a KEY_2_long
void (*keyOneTwo)(void); //Azione associata a KEY_1_2
Come si può facilmente notare, tutti questi puntatori sono strutturati in modo da
contenere il riferimento a funzioni prive di parametri, caratteristica comune a tutte le
azioni di risposta.
Il passo successivo consiste nello sviluppare altrettante funzioni che permettano di
impostare correttamente questi puntatori; si consideri, ad esempio, la seguente funzione
di registrazione relativa a KEY_1_short:
void addKeyOneQuickListener(void (*performAction)())
keyOneQuick=performAction;
Essa riceve come unico parametro il nome della funzione da eseguire in risposta
all’evento ed esegue, come unica operazione, l’aggiornamento del puntatore relativo a
KEY_1_short, ponendo in esso l’indirizzo di tale funzione. In modo del tutto analogo si
definiscono le funzioni di registrazione per gli altri quattro eventi:
Capitolo 6 - Implementazione
91
void addKeyOneAtLengthListener(void (*performAction)())
keyOneAtLength=performAction;
void addKeyTwoQuickListener(void (*performAction)())
keyTwoQuick=performAction;
void addKeyTwoAtLengthListener(void (*performAction)())
keyTwoAtLength=performAction;
void addKeyOneTwoListener(void (*performAction)())
keyOneTwo=performAction;
4.2.1. L’azione fittizia
Si è parlato più volte dell’azione fittizia, considerandola sostanzialmente come
“l’assenza di azione”; a scopo di completezza se ne riporta la definizione:
void nulla()
4.2.2. L’inibizione degli eventi
Alcune attività possono essere eseguite in modo sicuro e corretto solo escludendo gli
eventi, ovvero rendendo il sistema insensibile a ciò che accade all’esterno. Questa
funzionalità è realizzata dal seguente frammento di codice:
void disableEvents()
keyOneQuick = nulla;
keyOneAtLength = nulla;
keyTwoAtLength = nulla;
keyTwoQuick = nulla;
keyOneTwo = nulla;
Sostanzialmente, si fa in modo che ciascuno dei cinque puntatori a funzione relativi agli
eventi di sistema punti all’azione fittizia.
Una volta terminate le operazioni “critiche”, sarà necessario ripristinare la reazione
agli eventi mediante opportune chiamate alle funzioni di registrazione.
Capitolo 6 - Implementazione
92
4.3. Il rilevatore degli eventi
Seguendo l’approccio dei paragrafi precedenti, si offrirà, dapprima, una visione
d’insieme del servizio, per poi arricchire la trattazione con i dettagli implementativi.
In ultima analisi, il rilevatore è costituito da due thread in esecuzione concorrente,
ciascuno dei quali è legato in modo biunivoco ad uno dei due tasti fisici messi a
disposizione dell’RCX; in particolare:
- keyOne è il thread che si propone di rilevare le pressioni del pulsante fisico view;
- keyTwo è il thread che rileva le pressioni del pulsante program.
L’attività di questi thread è basata sulle funzioni wait_event di BrickOS; la loro
esecuzione combinata permette di gestire tutti gli eventi logici definiti in fase di analisi
e specifica dei requisiti.
4.3.1. Le funzioni di risveglio
Nel capitolo due, dedicato alla descrizione dell’RCX, è stato ampiamente discusso il
modello di gestione dei task adottato da BrickOS: nel caso si voglia sospendere un
processo in attesa di un evento esterno è necessario, anzitutto, definire una funzione di
risveglio che permetta di stabilire le condizioni sotto le quali il task bloccato possa
essere “risvegliato”. Il passo successivo consiste nell’invocazione della funzione
wait_event ogniqualvolta si desideri che il processo rimanga in attesa dell’evento;
quando le condizioni stabilite nella funzione di risveglio saranno soddisfatte, il processo
passerà allo stato di attesa (wait) pronto per essere eseguito.
Per non appesantire la trattazione successiva, vengono presentate ora le funzioni di
risveglio utilizzate dai due thread introdotti in precedenza; a ciascuna di esse è associata
una breve descrizione volta a chiarirne il compito.
Funzione di Risveglio Commento
wakeup_t push(wakeup_t data)
return PRESSED(dbutton(),data);
Determina il risveglio del processo alla pressione del pulsante fisico il cui riferimento è passato come parametro; gli unici valori significativi per il parametro “data” sono KEY_1 e KEY_2.
Capitolo 6 - Implementazione
93
wakeup_t pull(wakeup_t data)
return RELEASED(dbutton(),data);
Sblocca il processo solo a fronte del rilascio del pulsante il cui riferimento è passato come parametro; chiaramente, un evento di questo tipo è subordinato ad una precedente pressione dello stesso pulsante. Come nel caso precedente i valori significativi per il parametro formale sono KEY_1 e KEY_2.
wakeup_t combinationOneTwo(wakeup_t data)
return(PRESSED(dbutton(),KEY_2)||
RELEASED(dbutton(),KEY_1));
Questa funzione di risveglio può essere invocata solo nel caso in cui KEY_1 sia stato premuto, ma non ancora rilasciato. Il task viene risvegliato se viene premuto KEY_2 oppure se viene rilasciato KEY_1.
wakeup_t wakeUpProgram(wakeup_t data)
return PRESSED(dbutton(),KEY_2) &&
!PRESSED(dbutton(),KEY_1);
Determina il risveglio del processo nel caso in cui sia premuto KEY_2, ma non KEY_1.
wakeup_t pushAKey(wakeup_t data)
return (PRESSED(dbutton(),KEY_1) ||
PRESSED(dbutton(), KEY_2));
Permette di sbloccare il task se viene premuto KEY_1 oppure KEY_2.
wakeup_t caught(wakeup_t data)
return (RELEASED(dbutton(),KEY_2)||
(sys_time-data)>2000);
Questa funzione di risveglio può essere invocata solo nel caso in cui KEY_2 sia stato premuto, ma non ancora rilasciato. Il processo viene risvegliato quando KEY_2 viene rilasciato oppure quando sono trascorsi 2 secondi dall’istante in cui KEY_2 è stato premuto.
Le informazioni finora acquisite permettono di affrontare in maniera dettagliata il
vero o proprio nucleo del rilevatore degli eventi: i thread keyOne e keyTwo.
Capitolo 6 - Implementazione
94
4.3.2. Il thread KEYONE
Questo task ha il compito di sovrintendere a tutti gli eventi logici che coinvolgono
direttamente il pulsante KEY_1: KEY_1_short, KEY_1_long, KEY_1_2. Dal punto di
vista della mera struttura implementativa, è costituito da un unico ciclo (ripetuto
all’infinito) all’interno del quale sono opportunamente collocate le funzioni di attesa
(wait_event).
La logica di funzionamento, peraltro molto semplice, può essere efficacemente
schematizzata facendo riferimento ad una generica iterazione:
- Passo 1: il processo si mette in attesa della pressione di KEY_1.
- Passo 2: una volta che il tasto è stato premuto, possono verificarsi due eventi tra
loro mutuamente esclusivi:
! l’utente rilascia KEY_1, allora il thread invoca la funzione che in
quell’istante risulta associata all’evento KEY_1_short;
! mantenendo premuto KEY_1, l’utente preme e rilascia KEY_2, allora il
thread chiama la funzione attualmente associata a KEY_1_2. In questa
situazione, per evitare che BrickOS interpreti erroneamente il rilascio di
KEY_1, è indispensabile fare uso di una variabile booleana (flag) la quale
viene posta a TRUE solo nel caso di pressione combinata dei due tasti. In
questo modo il successivo rilascio di KEY_1 non determina alcuna reazione
del sistema, ma semplicemente riporta flag al suo valore predefinito
(FALSE).
- Ritorno al Passo 1.
Il codice che permette di fornire le funzionalità sopra descritte è il seguente:
void keyOne()
int flag = FALSE; //Inizializzazione del flag al valore predefinito
while(1)
wait_event(&push,KEY_1);
time_rest = sys_time;
wait_event(&combinationOneTwo,0);
if(RELEASED(dbutton(),KEY_1))
if(flag) flag=FALSE; //Situazione in cui
Capitolo 6 - Implementazione
95
KEY_1 viene rilasciato dopo una pressione combinata con KEY_2
else (*keyOneQuick)(); //Situazione in cui KEY_1 è stato premuto e rilasciato
else
wait_event(&pull, KEY_2); //Situazione in cui KEY_1 e KEY_2 sono premuti contemporaneamente e si attende il rilascio di KEY_2
(*keyOneTwo)();
flag=TRUE;
4.3.3. Il thread KEYTWO
Si tratta del thread che governa gli eventi logici relativi al tasto KEY_2:
KEY_2_short, KEY_2_long; anch’esso, come keyOne, è composto da un unico ciclo
ripetuto all’infinito.
Facendo riferimento ad una generica iterazione, la logica funzionale può essere così
sintetizzata:
- Passo 1: il task viene sospeso sino a quando viene premuto il solo tasto KEY_2
(la pressione contemporanea dei due pulsanti non determina un cambiamento di
stato del task); il sistema memorizza, nella variabile locale time, l’istante
temporale in cui è avvenuto l’evento.
- Passo 2: il processo viene nuovamente sospeso e può essere risvegliato a fronte
del verificarsi di due eventi:
! l’utente rilascia KEY_2 prima che siano trascorsi 2 secondi, allora viene
invocata la funzione connessa all’evento KEY_2_short e viene emesso un
singolo segnale acustico.
! L’utente tiene premuto KEY_2 per un intervallo di tempo superiore ai due
secondi, allora viene chiamata la funzione legata a KEY_2_long e viene
Capitolo 6 - Implementazione
96
emesso un doppio segnale acustico. In questa situazione si introduce anche
un ritardo di tempo di 500ms per evitare che un rilascio lento del tasto venga
interpretato dal sistema come una successiva pressione breve dello stesso.
Il codice completo che costituisce il corpo di keyTwo è il seguente:
void keyTwo()
time_t time;
while(1)
time_rest = sys_time;
wait_event(&wakeUpProgram,0);
time_rest = sys_time;
time = sys_time; //Memorizzazione dell'istante temporale di pressione del tasto
wait_event(&caught,time); //Il sistema attende che l’utente rilasci KEY_1 oppure che siano trascorsi 2 secondi dall’inizio della pressione
if((sys_time-time)>2000) //condizione che discrimina la pressione lunga o breve
//Pressione lunga
dsound_system(DSOUND_BEEP);
msleep(200);
dsound_system(DSOUND_BEEP);
(*keyTwoAtLength)();
msleep(500); //ritardo necessario per evitare che a causa di un rilascio lento si rilevi anche l'evento pressione breve
else
//Pressione breve
dsound_system(DSOUND_BEEP);
(*keyTwoQuick)();
Capitolo 6 - Implementazione
97
5. GESTIONE DEI CONTESTI
Nella fase di progettazione si è definito un contesto come un legame univoco
esistente fra un preciso stato del display e l’insieme degli eventi che per esso hanno
significato; tale definizione si estende sino ad includere le azioni di risposta associate
agli eventi. E’ stato detto che la creazione di un contesto si articola in tre fasi:
- individuazione degli eventi significativi;
- implementazione delle funzioni da invocare in risposta agli eventi scelti al passo
precedente;
- implementazione della funzione che permette di registrare le azioni di risposta sui
relativi eventi; ogni volta che si invoca questa funzione, si dice che si “applica il
contesto”.
La rielaborazione dei requisiti applicativi ha portato, infine, all’individuazione di un
primo insieme di contesti su cui basare l’attività implementativa.
Da un punto di vista prettamente tecnico, la gestione dei contesti non richiede
meccanismi particolarmente sofisticati; la realizzazione orbita sostanzialmente attorno
al nucleo di registrazione degli eventi.
5.1. Realizzazione dei contesti
L’implementazione dei vari contesti viene discussa rispettando l’ordine adottato per
la loro presentazione (vedi Capitolo 5), salvo eventuali integrazioni dettate da ragioni di
realizzabilità pratica. Ad ogni contesto è dedicato un paragrafo organizzato in due
sezioni:
- descrizione funzionalità;
- registrazione eventi e descrizione azioni di risposta.
5.1.1. «Menù Selezione PID»
Offre all’utente la possibilità di selezionare un determinato regolatore e di accedere
alla modalità di modifica/visualizzazione dei suoi parametri.
La funzione di registrazione di questo contesto viene invocata automaticamente
all’avvio del software. Se il sistema è costituito da un singolo regolatore, si passa
Capitolo 6 - Implementazione
98
direttamente all’applicazione del contesto «Menù Parametri PID»; in caso contrario si
effettuano due operazioni:
- il flag menu_PID viene impostato a TRUE; ciò permette di comunicare al modulo
di visualizzazione di gestire correttamente la nuova modalità operativa.
- si registrano gli eventi in base allo schema seguente:
Evento KEY_1_short
Variazione
di contesto Nessuna.
Descrizione
Funzione registrata: switchPID()
Il sistema associa a ciascun regolatore un indice (valore numerico intero)
che permette di identificarlo univocamente; il regolatore dell’anello più
interno ha indice pari a 0, mentre quello sull’anello più esterno ha indice
pari a NUMBER_OF_PIDs-1. La variabile currentPID, definita a
livello globale, memorizza l’indice del PID attualmente selezionato
dall’utente; chiaramente può assumere valori nel range
0…NUMBER_OF_PIDs-1. Ad ogni pressione di KEY_1 corrisponde un
incremento unitario di currentPID; quando si raggiunge l’ultimo
regolatore della lista, si riparte dal primo, imponendo 0=currentPID :
if (currentPID==NUMBER_OF_PIDs-1) currentPID = 0;
else currentPID++;
Evento KEY_2_long
Variazione
di contesto Applicazione del contesto «Menù Parametri PID»
Descrizione
Funzione registrata: selectPID()
L’evento corrisponde alla richiesta dell’utente di accedere alla modifica
Capitolo 6 - Implementazione
99
dei parametri del PID attualmente selezionato. Prima di applicare il
contesto «Menù Parametri PID»:
- si riporta a FALSE il flag menu_PID;
- si chiama la funzione encode per preparare il display alla
visualizzazione del primo parametro.
5.1.2. «Menù Parametri PID»
E’ uno dei contesti più importanti in quanto permette di accedere alla
visualizzazione, nonché alla modifica, dei parametri che caratterizzano ciascun
regolatore; in un’ipotetica organizzazione gerarchica può essere considerato un’entità di
livello superiore rispetto a tutti i contesti che gestiscono le modifiche vere e proprie.
Inoltre, offre due funzionalità particolarmente importanti:
- impedisce all’utente di apportare variazioni non autorizzate ai parametri (ad
esempio: se il PID si trova in modalità automatica l’utente non può accedere al
contesto di modifica della variabile di controllo);
- seleziona, in base al tipo di parametro, il contesto adatto a gestirne le modifiche.
Gli eventi vengono registrati in base allo schema seguente:
Evento KEY_1_short
Variazione
di contesto Nessuna.
Descrizione
Funzione registrata: menuItemToDisplay()
Questa funzione fa avanzare di un elemento il menù in modo che la
nuova voce così selezionata venga visualizzata sul display fisico
dell'RCX.
Si ricorda che, a livello di gestione dei dati, lo scorrimento del menù
parametri è governato dalla funzione nextItem().
Capitolo 6 - Implementazione
100
Evento KEY_2_long
Variazione
di contesto
Se il parametro corrente è modificabile, viene applicato il contesto di
modifica ad esso corrispondente. Inoltre, l’uscita dall’ultima voce di
menù (CONF o EXIT) determina l’applicazione di «Menu Selezione
PID»
Descrizione
Funzione registrata: changeMenuItem()
Questa funzione verifica, anzitutto, che la voce attuale del menu sia
modificabile dall'utente:
if (menu[currentPID][currentItem].modifiable==TRUE) …
In base all’indice del parametro corrente (currentItem), viene
selezionato il nuovo contesto che permetterà al sistema di processare gli
opportuni eventi:
if (currentItem==POS_CONF)
//Voce = "CONF" o "EXIT"
...
else if (currentItem==POS_MODE)
//Voce = "MODE"
setContestEnumeratedItem();
...
else if (currentItem==POS_SPi)
//Voce = "SPi"
setContextIncValue();
...
else if (currentItem==POS_CS)
//Voce = "CS"
setContextIncValue();
...
else if (currentItem==POS_PV)
//Voce = "PV"
setContextPVEvolution();
...
else setContextNumericalItem();
Capitolo 6 - Implementazione
101
(Nota: i simboli POS_CONF, POS_CS, POS_PV, POS_MODE e
POS_SPi rappresentano la posizione dei relativi parametri nella struttura
dati menu[][]).
Nel caso in cui venga raggiunta l’ultima voce del menù, il sistema
procede nel seguente modo:
- se sono state effettuate modifiche, viene applicato il contesto
«Conferma o Annullamento»;
- se l’utente non ha apportato modifiche, ma c’è più di un
regolatore, viene applicato il contesto «Exit» che consente di
tornare al menù di scelta dei PID.
5.1.3. «Modifica valore lessicale»
Si affronta, ora, il primo dei tre contesti che permettono effettivamente di agire sui
parametri apportando variazioni al loro valore corrente. Un valore lessicale, come detto
in precedenza, può assumere un numero finito di valori, definiti a priori dal progettista.
Per ogni parametro di questo tipo è necessario, quindi, caricare la lista dei possibili
valori e renderla disponibile all’utente affinché possa operare la propria scelta.
Il parametro MODE, che rappresenta la modalità di funzionamento del regolatore, è
l’unico a richiedere l’applicazione del contesto in esame; la registrazione degli eventi
rispetta lo schema seguente:
Evento KEY_1_short
Variazione
di contesto Nessuna.
Descrizione
Funzione registrata: changeEnumeratedItemValue()
Allo stato attuale del progetto, questa funzione fa scorrere i due possibili
valori (MAN e AUTO) assegnabili alla voce MODE, visualizzandoli sul
display dell'RCX:
Capitolo 6 - Implementazione
102
if (currentItem==POS_MODE)
manual = manual==TRUE ? FALSE : TRUE;
La variabile booleana manual è definita a livello globale e memorizza
l’attuale modalità di funzionamento; ad ogni pressione di KEY_1 si
complementa manual. In futuro sarà resa disponibile una nuova
modalità (TUNE) corrispondente alla funzione di autotuning del
regolatore. Inoltre, è sempre possibile espandere la funzione in modo
che possa gestire numerosi parametri di tipo lessicale, ciascuno con il
proprio insieme di valori predefiniti.
Evento KEY_2_long
Variazione
di contesto Applicazione del contesto «Menù Parametri PID»
Descrizione
Funzione registrata: confirmEnumeratedItemValue()
Tenendo conto del nuovo valore assegnato dall’utente al parametro
MODE, il sistema esegue le seguenti operazioni:
- decide se rendere (o meno) modificabile la variabile di controllo
(CS);
- salva la nuova modalità di funzionamento nella struttura dati
PIDloop associata al PID corrente.
Come ultima operazione, si torna al menù dei parametri:
if (currentItem==POS_MODE)
sem_wait(semMenu);
menu[currentPID][POS_CS].modifiable = manual;
PIDloop[currentPID].MAN = manual;
sem_post(semMenu);
display.change = TRUE;
setContextMenu();
Capitolo 6 - Implementazione
103
Le primitive di sincronizzazione che compaiono nel codice vengono
trattate in separata sede.
5.1.4. «Modifica valore numerico»
Il secondo contesto di attuazione delle modifiche è concepito per i parametri
numerici il cui valore può essere impostato dall’utente in modo diretto senza dover
sottostare a particolari vincoli.
L’obiettivo è quello di rendere operativa la procedura di modifica definita in modo
qualitativo dalle specifiche di progetto.
La procedura di applicazione del contesto agisce in due fasi:
- anzitutto si attiva il lampeggio della prima cifra a sinistra del display mediante le
istruzioni:
display.blinkingDigit=0;
display.blink=TRUE;
- in seconda battuta si registrano gli eventi in base allo schema che segue:
Evento KEY_1_short
Variazione
di contesto Nessuna.
Descrizione
Funzione registrata: changeCurrentDigitValue()
Il comportamento del sistema dipende dallo stato del display:
- se sta lampeggiando una delle cinque cifre, allora si applica ad essa
un incremento unitario; in questo modo è possibile far scorrere i
valori da 0 a 9 per ogni singola cifra del display.
- se sta lampeggiando il punto decimale, allora esso viene spostato
nella posizione immediatamente successiva.
Formalizzando in codice:
if(display.blinkingDigit>=0&&display.blinkingDigit<MAX_DIGIT)
Capitolo 6 - Implementazione
104
if(display.character[display.blinkingDigit]=='9')
display.character[display.blinkingDigit]='0';
else display.character[display.blinkingDigit]++;
else if (display.blinkingDigit==5)
if(display.point==4) display.point=0;
else if(display.point==0) display.point = 2;
else display.point++;
Per maggiori dettagli riguardanti la codifica delle cifre e delle posizioni del
punto decimale, si rimanda alla sezione dedicata alla struttura dati
DISPLAY.
Evento KEY_2_short
Variazione
di contesto Applicazione del contesto «Conferma Voce Numerica».
Descrizione
Funzione registrata: changeCurrentDigitValue()
Questa funzione permette di selezionare (far lampeggiare), una dopo
l’altra, ciascuna delle cifre del display affinché sia possibile modificarne
il valore. Successivamente alla quinta cifra, viene fatto lampeggiare il
punto decimale. In questa situazione, un’ulteriore pressione di KEY_2 fa
passare alla situazione in cui lampeggiano tutte le cifre ed il punto
decimale e comporta l’applicazione del contesto «Conferma Voce
Numerica» per consentire l'annullamento o la conferma delle modifiche
apportate al parametro:
if(display.blinkingDigit==6)
display.blinkingDigit=0;
else
display.blinkingDigit++;
Capitolo 6 - Implementazione
105
if (display.blinkingDigit==6) //Tutte le cifre
lampeggiano
setContextNumericalItemConfirm();
5.1.4.1. «Conferma Voce Numerica»
Può essere considerato un’estensione del contesto «Modifica Voce Numerica». La
sua creazione è dovuta semplicemente a fattori di ordine pratico.
Gli eventi vengono registrati in conformità allo schema che segue:
Evento KEY_2_short
Variazione
di contesto Applicazione contesto «Menù Parametri PID».
Descrizione
Funzione registrata: undoNumericalItem()
Torna al menù dei parametri ignorando le modifiche apportate
dall'utente alla voce numerica corrente. Prima di uscire dal contesto
viene anche disabilitata la modalità di visualizzazione intermittente del
display:
display.blink=FALSE;
setContextMenu();
Evento KEY_2_long
Variazione
di contesto Applicazione contesto «Menù Parametri PID»
Descrizione
Funzione registrata: confirmNumericalItem()
Registra le modifiche apportate dall'utente alla voce numerica attuale e
visualizza sul display il simbolo che indica la presenza di modifiche non
Capitolo 6 - Implementazione
106
ancora confermate a secondo livello. Anche in questo caso si disabilita
la modalità di lampeggiamento:
(menu[currentPID][currentItem].value) = (float)decode();
if(currentItem!=POS_SP) display.change = TRUE;
display.blink=FALSE;
setContextMenu();
Nel caso in cui l’utente abbia modificato la voce SP o CS, non risulta
necessario visualizzare il simbolo di modifica in quanto tale variazione è
immediatamente visibile al regolatore (si ricorda che SP e CS sono
parametri descrittivi dell’anello di regolazione).
Per ulteriori informazioni sulla funzione decode, si rimanda alla sezione
dedicata alla gestione della visualizzazione.
5.1.5. «Modifica Valore Incrementale»
Si tratta del terzo ed ultimo contesto di gestione delle modifiche. Un parametro
incrementale è caratterizzato dal fatto che il suo valore attuale può essere modificato
solo attraverso un’opportuna serie di incrementi o decrementi di passo predeterminato.
L’applicazione prevede due soli parametri di questo tipo:
- set-point incrementale (SPi): consente di applicare variazioni a rampa al segnale
di riferimento. Il passo di incremento/decremento è unitario e non può essere
modificato dall’utente;
- variabile di controllo (CS): quando il regolatore funziona in modalità manuale,
l’operatore può agire direttamente sull’uscita. Il passo di incremento/decremento
è impostato dall’utente stesso per mezzo del parametro deltaCSMAN.
All’applicazione del contesto, si memorizza nella variabile memoryChange (di tipo
REAL) il valore attuale del parametro, in modo tale che sia possibile annullare le
eventuali modifiche.
Gli eventi vengono registrati con le seguenti modalità:
Capitolo 6 - Implementazione
107
Evento KEY_1_short
Variazione
di contesto Nessuna.
Descrizione
Funzione registrata: increaseIncValue();
Il comportamento dipende dal parametro considerato:
- nel caso del set-point incrementale, si incrementa di un’unità il
valore precedente e si chiama la funzione encode per rendere
immediatamente visibile la modifica;
- nel caso della variabile di controllo, si porta semplicemente a
TRUE il parametro MANinc del PID; ciò è sufficiente a
determinare un incremento del controllo (laddove consentito) pari
a deltaCSMAN.
Il codice, a meno delle primitive di sincronizzazione, è il seguente:
if(currentItem==POS_SPi)
(*menu[currentPID][currentItem].value)++;
encode(*menu[currentPID][currentItem].value); ...
else if(currentItem==POS_CS)
PIDloop[currentPID].MANinc = TRUE; ...
Come si può notare dal codice, le modifiche sono rese subito visibili al
regolatore.
Evento KEY_1_2
Variazione
di contesto Nessuna.
Descrizione
Funzione registrata: decreaseIncValue()
Il comportamento è assimilabile a quello della funzione precedente,
Capitolo 6 - Implementazione
108
salvo il fatto che, in questo caso, si deve gestire una diminuzione del
valore:
- nel caso del set-point incrementale, si decrementa di un’unità il
valore precedente e si chiama la funzione encode;
- nel caso della variabile di controllo, si porta a TRUE il parametro
MANdec del PID per determinare un decremento del controllo
(laddove consentito) pari a deltaCSMAN.
Si evita di riportare il codice in quanto analogo al caso precedente.
Ancora una volta, le modifiche risultano immediatamente visibili al
regolatore.
Evento KEY_2_short
Variazione
di contesto Applicazione del contesto «Menù Parametri PID»
Descrizione
Funzione registrata: undoIncValue()
Annulla le modifiche ripristinando il valore iniziale del parametro; a
questo scopo utilizza la variabile memoryChange:
*menu[currentPID][currentItem].value=memoryChange;
Al termine dell’operazione si torna al menù dei parametri.
Evento KEY_2_long
Variazione
di contesto Applicazione del contesto «Menù Parametri PID»
Descrizione
Funzione registrata: confirmIncValue()
Si è detto che eventuali incrementi o decrementi del set-point e della
variabile di controllo vengono resi immediatamente visibili al
regolatore; proprio per questo motivo, in caso di conferma dei nuovi
Capitolo 6 - Implementazione
109
valori non è necessario effettuare alcuna operazione sulle strutture dati.
La reazione all’evento consiste esclusivamente nel ritorno al menù dei
parametri.
5.1.6. «Evoluzione variabile controllata»
Il contesto, peraltro molto semplice, risponde all’esigenza di visualizzare sul display
l’andamento temporale della variabile controllata. La logica implementativa si basa
sulla definizione della variabile booleana trackingPV, la quale risulta visibile sia al
gestore dei contesti (che ne imposta il valore) sia al gestore del display fisico (che ne
legge il valore). Quando trackingPV è TRUE, il thread di visualizzazione si comporta in
modo tale che, ad ogni ciclo di refresh, venga aggiornata la visualizzazione della
variabile controllata.
All’applicazione del contesto, si porta a TRUE la variabile discussa in precedenza e
si registra un unico evento:
Evento KEY_2_long
Variazione
di contesto Applicazione del contesto «Menù Parametri PID»
Descrizione
Funzione registrata: exitEvolutionPVContext()
Riporta semplicemente a FALSE la variabile trackingPV e torna al menù
dei parametri.
5.1.7. «Conferma o Annullamento»
Nel caso in cui siano state apportate modiche ai parametri del regolatore attualmente
selezionato, come ultimo elemento del menù viene visualizzata la voce di conferma.
Essa permette di realizzare quella “conferma di secondo livello”, discussa nella fase di
analisi e specifica dei requisiti. E’ bene sottolineare ancora una volta, che essa riguarda
solo ed esclusivamente i parametri che descrivono la legge di controllo PID del
regolatore (guadagno, tempo integrale, …); i parametri che descrivono l’anello di
Capitolo 6 - Implementazione
110
controllo (variabile controllata, set-point, …) non sono assolutamente coinvolti
nell’operazione.
La registrazione degli eventi avviene come specificato di seguito:
Evento KEY_1_short
Variazione
di contesto Applicazione del contesto «Menù Selezione PID»
Descrizione
Funzione registrata: undo()
Annulla tutte le modiche apportate ai parametri del PID che non hanno
ancora ricevuto la conferma di secondo livello. Tecnicamente,
l’annullamento si realizza copiando i valori della struttura PIDrun su
quelli della struttura PIDedit:
...
*PIDedit[currentPID]=*PIDrun[currentPID];
...
Prima di uscire dal contesto, viene nascosto il simbolo sul display che
indica la presenza di modifiche non ancora confermate (o annullate):
...
display.change = FALSE;
...
Evento KEY_2_long
Variazione
di contesto Applicazione del contesto «Menù Selezione PID»
Descrizione
Funzione registrata: confirm()
Conferma tutte le variazioni apportate ai parametri per mezzo della
chiamata alla funzione switchPIDData; anche in questo caso viene
Capitolo 6 - Implementazione
111
disabilitato il simbolo del display associato alle modifiche:
...
switchPIDData(currentPID);
display.change = FALSE;
...
5.1.8. «Uscita Menù Parametri»
Si consideri il caso in cui il sistema sia costituito da più PID e l’utente entri nel menù
dei parametri di un regolatore e giunga all’ultima voce senza apportare alcuna
variazione. Chiaramente non sarebbe di alcuna utilità proporre la voce di conferma
(CONF). In tale situazione, si preferisce visualizzare una voce (EXIT) che consenta
all’utente di tornare al menù di selezione dei PID. Questo contesto viene applicato
proprio a tale scopo.
Si registra un unico evento, in accordo al seguente schema:
Evento KEY_2_long
Variazione
di contesto Applicazione del contesto «Menù Selezione PID»
Descrizione
Funzione registrata: backToPIDMenu()
Permette di tornare al menù di scelta dei PID.
6. VISUALIZZAZIONE DEI DATI
Questo modulo concorre, con la gestione degli eventi, alla realizzazione
dell’interfaccia utente, il suo compito è di garantire all’operatore un feed-back visivo
alle azioni intraprese per mezzo dei pulsanti. Nella fase di progettazione, descritta nel
capitolo precedente, si sono definiti gli aspetti caratterizzanti della visualizzazione che,
in questa fase, rappresentano le linee guida per l’implementazione.
Per quanto riguarda l’organizzazione interna dei dati da presentare, si è stabilito di
Capitolo 6 - Implementazione
112
raggruppare tutti i parametri relativi ad un singolo PID all’interno di un unico menù,
concepito come una collezione di voci (vedi capitolo 5). I vari menù sono, a loro volta,
raggruppati in una apposita struttura, la cui dimensione viene definita staticamente in
base al numero di regolatori presenti nel sistema.
Per quanto concerne la presentazione delle informazioni, si è stabilito di realizzare
una struttura dati (DISPLAY), che mappa in memoria l’LCD. Un thread apposito,
denominato “video”, si occuperà di scrivere il contenuto di tale struttura sul display
fisico. Con questa soluzione sarà sufficiente scrivere ciò che si desidera far apparire a
video nell’apposita struttura, per ottenerne l’automatica visualizzazione sull’LCD.
L’architettura del modulo può essere riassunta visivamente nella seguente figura:
Tale architettura consente di mantenere totalmente indipendente la rappresentazione
interna dei dati dalla loro visualizzazione, ottenendo una netta separazione tra le
funzioni di competenza di ciascuna area; ciò permette di relegare all’interno del thread
video tutti i numerosi problemi che BrickOS presenta nella gestione del display.
Nel seguito si affronteranno individualmente le tre aree che costituiscono gli
elementi cardine dell’architettura del modulo in questione:
− Area “menù”: riguarda l’organizzazione interna dei dati e la tecnica prescelta per
la loro gestione.
− Area “display”: descrive la modellizzazione del display fisico, sottolineando il
Capitolo 6 - Implementazione
113
fatto che essa rappresenta il punto di contatto fra l’organizzzazione interna dei
dati e la loro visualizzazione.
− Area “video”: definisce il thread che si occupa di portare in visualizzazione sul
display fisico i dati posti nella struttura DISPLAY.
6.1. Menù
La descrizione interna dei dati da visualizzare si risolve nella definizione ed
implementazione di una struttura che rappresenti i menù, opportunamente corredata da
una serie di funzioni che su di essa operano.
In fase di progettazione si è stabilito d’organizzare il menù come una collezione di
“voci”.Ciascuna voce è un’istanza della struttura dati VOCE_MENU:
typedef struct
char *name;
REAL *value;
BOOL modifiable;
VOCE_MENU;
In essa il campo name rappresenta l’etichetta che verrà visualizzata sul display
quando si seleziona la voce, mentre il campo value è un puntatore al corrispettivo
parametro nella struttura PIDLOOPDATA o PIDPARAM, a seconda che la voce sia
relativa ad un parametro dell’anello o del regolatore. Infine, il campo modifiable indica
se la voce è modificabile o meno dall’utente.
Dovendo gestire la situazione in cui vi siano più menù (uno per ogni regolatore) si
stabilisce che essi siano rappresentati attraverso una matrice, in cui su una dimensione
vengono riportati i regolatori presenti nel sistema, mentre sull’altra vengono elencate le
voci corrispondenti al menù di ciascun regolatore. In particolare, tutti i menù avranno la
medesima struttura, qui di seguito riportata:
Indice Voce menù Tipo Descrizione
0 K Voce numerica modificabile
Valore dell’azione proporzionale,
necessita di conferma di secondo
livello.
Capitolo 6 - Implementazione
114
1 Ti Voce numerica modificabile
Valore del tempo integrale,
necessita di conferma di secondo
livello.
2 Td Voce numerica modificabile
Valore del tempo derivativo,
necessita di conferma di secondo
livello.
3 N Voce numerica modificabile
Stabilisce il polo in alta frequenza
relativo al filtro sull’azione
derivativa, necessita di conferma di
secondo livello.
4 b Voce numerica modificabile
Peso sul set point nell’azione
proporzionale, necessita di
conferma di secondo livello.
5 c Voce numerica modificabile
Peso sul set point nell’azione
derivativa, necessita di conferma di
secondo livello.
6 Ts Voce numerica modificabile
Tempo di campionamento,
necessita di conferma di secondo
livello.
7 CSmax Voce numerica modificabile
Limite superiore di saturazione,
necessita di conferma di secondo
livello.
8 CSmin Voce numerica modificabile
Limite inferiore di saturazione,
necessita di conferma di secondo
livello.
9 deltaCSman Voce numerica modificabile
solo in modalità manuale
Passo di variazione di CS in
modalità manuale, necessita di
conferma di secondo livello.
Capitolo 6 - Implementazione
115
10 SP Voce numerica modificabile
Set point del regolatore, non
necessita di conferma di secondo
livello.
11 SPi Voce numerica modificabile
incrementalmente
Set point del regolatore, non
necessita di conferma di secondo
livello.
12 PV Voce numerica non
modificabile Misura della variabile controllata
13 CS
Voce numerica modificabile
incrementalmente solo in
modalità manuale
Variabile di controllo del sistema,
uscita del regolatore; non necessita
di conferma di secondo livello.
14 MODE Voce lessicale modificabile
Permette d’impostare la modalità di
lavoro, non necessita di conferma di
secondo livello.
15 CONF Voce di conferma Viene visualizzata solo se sono
avvenute modifiche sulle altre voci.
15 EXIT Voce di ritorno al menù PID
Viene visualizzata se non ci sono
modifiche in un sistema con più
PID.
Come stabilito nella fase di analisi e specifica dei requisiti, le due voci CONF ed
EXIT sono in una relazione di mutua esclusione; per tanto, considerando solo gli aspetti
di visualizzazione, non nasce la necessità di creare nel menù due voci distinte, in quanto
sarà sufficiente far apparire a video delle etichette diverse.
Dalla schematizzazione della tabella, risulta evidente che le dimensioni della
struttura sono definite staticamente e non subiscono variazioni durante l’esecuzione;
pertanto, si decide di implementare la matrice dei menù utilizzando un array
bidimensionale. Questa soluzione consente una gestione efficiente dello spazio occupato
in memoria, la quale in questo progetto rappresenta sicuramente una risorsa scarsa.
Capitolo 6 - Implementazione
116
Inoltre, è noto che gli array permettono accessi diretti, molto rapidi, ai singoli elementi
della matrice.
L’implementazione di questa soluzione avviene, in primo luogo, definendo le
costanti che stabiliscono le dimensioni della matrice: SIZE_MENU, per quanto riguarda
il numero di voci, è definita univocamente pari a 16. Per quanto riguarda il numero dei
regolatori presenti nel sistema si definisce NUMBER_OF_PIDs, il cui valore può essere
impostato staticamente (prima di compilare ed eseguire l’applicativo) secondo le
esigenze del controllo da operare.
Della matrice viene creata una sola istanza tramite la seguente dichiarazione della
variabile menu:
VOCE_MENU menu[NUMBER_OF_PIDs][SIZE_MENU];
Ad essa sarà possibile accedere per mezzo di due indici: currentPID e currentItem;
in particolare, currentPID potrà assumere solo il valore 0 nel caso in cui nel sistema sia
presente un solo anello di regolazione.
Per la gestione della variabile menu sono messe a disposizione due funzioni:
loadMenu() e nextItem() qui di seguito descritte.
6.1.1. Inizializzazione del menù
La funzione loadMenu() ha il compito di inizializzare il menù; per ciascun regolatore
definito nel sistema vengono create le rispettive sedici voci, ciascuna costruita
impostando il nome della voce, l’eventuale riferimento al valore numerico ed indicando
se la voce in questione è modificabile o meno. A titolo d’esempio, di seguito si riporta
l’inizializzazione del parametro K del primo regolatore (PID 0):
menu[0][0].name="K";
menu[0][0].value=&(PIDedit[0]->K);
menu[0][0].modifiable = TRUE;
6.1.2. Scorrimento del menù
Nonostante l’implementazione per mezzo di array metta a disposizione la possibilità
di eseguire accessi random in maniera rapida ed efficiente, nel rispetto delle specifiche,
si fornisce all’utente solamente la possibilità di passare in rassegna sequenzialmente le
Capitolo 6 - Implementazione
117
varie voci che compongono un menù. Ciò è realizzato attraverso la funzione nextItem(),
la quale non fa altro che passare alla voce successiva del menù.
L’azione svolta si può ricondurre semplicemente all’incremento dell’indice
currentItem a cui vengono applicati una serie di controlli; fra essi, il più significativo è
certamente quello che permette di gestire l’eventuale visualizzazione dell’ultima voce di
menù.
Anzitutto, questa decisione viene presa solo quando si è giunti alla penultima voce
del menù (ovvero se currentItem = = SIZE_MENU-2) e l’utente chiede di visualizzare
la voce successiva. In questa situazione, si verifica se sono state apportate delle
modifiche ai parametri (display.change = = TRUE); in caso affermativo l’indice
currentItem viene incrementato e la voce CONF viene visualizzata. In caso negativo, si
verifica se nel sistema sono presenti più regolatori (NUMBER_OF_PIDs>1); in caso
positivo l’indice currentItem viene incrementato e si visualizzerà la voce EXIT,
altrimenti l’indice viene riportato a 0 e nessuna delle due precedenti voci verrà
visualizzata.
6.2. Display
Come già detto, la struttura DISPLAY mappa in memoria il dispositivo LCD e
costituisce, pertanto, il punto di contatto tra la rappresentazione interna dei dati e la
visualizzazione degli stessi; di essa è fornita in appendice al capitolo l’implementazione.
In questo paragrafo s’intende discuterne gli aspetti salienti, mostrando come alcune
logiche di funzionamento della visualizzazione dei dati siano state implementate a
partire dalla definizione di appositi campi nella struttura dati in questione.
Nel sistema è definita un’unica variabile di tipo DISPLAY, chiamata display, in
quanto vi è un solo dispositivo di visualizzazione da gestire.
Nella definizione dei campi della struttura si sono tenuti in considerazione due
aspetti: la modellizzazione di un dispositivo fisico e la necessità di fornire una serie di
informazioni ausiliarie, utili al thread video per una corretta visualizzazione. Nel
paragrafo che segue si prende in considerazione solo il primo aspetto, mentre il secondo
sarà affrontato nel paragrafo in cui si descrive il thread di scrittura sull’LCD.
Capitolo 6 - Implementazione
118
6.2.1. Modellizzazione del display fisico
Nell’attività di modellizzazione si è proceduto individuando sul display fisico gli
elementi necessari a comunicare le informazioni utili e a ciascuno di essi si è fatto
corrispondere un campo nella struttura dati:
− i cinque digit del display sono rappresentati attraverso un vettore di caratteri così
definito: char character[MAX_DIGIT].
− il punto decimale, che fisicamente può assumere solo tre posizioni, viene
mappato attraverso una variabile point che assume i valori definiti dalla seguente
enumerazione:
enum pointPosition left=4,mid_left=3,mid_right=2,right=1,none=0, in cui
vengono definite quattro possibili posizioni (oltre all’assenza del punto) in
previsione di poter utilizzare un ulteriore segmento del display come punto
decimale, posizionato tra l’ultimo e il penultimo digit a destra. Poiché questa
soluzione non è ancora possibile, si utilizzano come punti decimali i soli
segmenti che per tale fine sono stati introdotti dal costruttore; pertanto, il valore
right per ora non viene utilizzato.
− l’ultimo elemento fisico che è stato mappato è il simbolo di modifica, per esso si
è scelto un piccolo quadrato al di sopra delle cinque cifre a cui nella struttura
DISPLAY si fa corrisponde una variabile booleana: BOOL change.
Alla struttura appena descritta sono associate alcune funzioni, fra cui le due più
importanti sono encode e decode; la prima permette di scrivere sulla struttura, mentre la
seconda di leggere. Prima di entrare nel dettaglio implementativo di queste funzioni, se
ne vogliono definire i diversi ambiti applicativi e le differenti finalità d’impiego, in
modo tale che la successiva descrizione nel dettaglio sia più comprensibile.
6.2.2. Interazioni con la struttura DISPLAY
Per le modalità in base alle quali è stata definita l’architettura del modulo di
visualizzazione, sembrerebbe logico definire solamente una funzione di scrittura in
corrispondenza di un unico flusso di dati che, dalla struttura menù, porta al display i
valori dei parametri da visualizzare. In questo flusso si visualizzano dati già
memorizzati, cioè dati ai quali non sono state apportate delle modifiche. Da qui nasce
Capitolo 6 - Implementazione
119
l’esigenza d’individuare un nuovo flusso, che consenta all’operatore di vedere le
modifiche che sta apportando su un parametro; in altre parole si deve poter visualizzare
il valore di un parametro prima che avvenga la conferma di primo livello. I due flussi
sono schematizzati nella figura seguente e contraddistinti dalla lettera a) per quanto
riguarda i dati memorizzati, e dalla lettera b) per i dati a cui si apportano modifiche.
nextItem encode (b)
decode (b)
encode (a)
MENU ’ PID
D ISPLA Y
Alla luce delle considerazioni fatte, si passa a descrivere l’implementazione delle
funzioni che operano sulla variabile display.
6.2.2.1. Funzione di scrittura
Il compito della funzione encode(double value) è quello di scrivere nel vettore
character della variabile display il valore che le viene passato come parametro.
Il nome ‘encode’ chiarifica ciò che in pratica svolge questa funzione: codifica un
valore numerico in una sequenza di caratteri, occupandosi anche di impostare
correttamente la posizione del punto decimale (valore della variabile point); in questo
modo garantisce una corretta e completa visualizzazione del dato. Questa funzione non
fa nulla di particolarmente complesso, quindi in una situazione normale non
necessiterebbe di particolare attenzione. Tuttavia, nell’implementazione si sono
incontrate alcune difficoltà a causa delle risicate librerie di funzioni che corredano
BrickOS. Sostanzialmente il sistema operativo non fornisce alcuna funzione utile allo
scopo. Pertanto, si è dovuto sopperire alle mancanze di BrickOS attraverso la “re-
Capitolo 6 - Implementazione
120
implementazione” di funzioni C (si veda appendice “Librerie”). Nel far questo si è
sicuramente perso il vantaggio dell’elevato grado di ottimizzazione delle librerie
standard.
Alla luce di queste considerazioni, l’implementazione di encode assume una nuova
veste, degna di essere riportata in questa trattazione.
Per prima cosa la funzione converte il parametro value in una stringa di caratteri; il
risultato della conversione è scritto nella variabile locale string. Tale operazione avviene
per mezzo della funzione ecvt, la quale riceve, fra gli altri, un parametro di tipo intero:
dec; in cui viene scritto il numero di decimali della variabile value, secondo la regola
evidenziata nella seguente tabella, in cui X rappresenta una qualsiasi cifra compresa tra
1 e 9:
Tipo di numero Valore di dec
XXX,XX 3
XX,XXX 2
X,XXXX 1
0,XXXX 0
0,0XXX -1
0,00XX -2
0,000X -3
Utilizzando dec è possibile impostare correttamente la posizione dell’eventuale
punto decimale sul display. Se il valore di dec è maggiore di zero sarà sufficiente
eseguire il seguente assegnamento: display.point = MAX_DIGIT-dec, altrimenti la
variabile point assumerà il valore 4, che nell’enumerazione corrisponde a left.
Particolare attenzione va riposta nel caso in cui dec sia minore o uguale a zero;
in questa situazione, infatti, la funzione ecvt elimina tutte le cifre zero a sinistra della
prima cifra non nulla, rendendo così necessario reintrodurre tali cifre. Per far questo, si
trasla il contenuto della variabile string verso destra e si introduce nelle posizioni
lasciate vuote il carattere 0. In particolare, se dec è pari a 0, si trasla a destra di una
Capitolo 6 - Implementazione
121
posizione, se dec vale -1 si trasla a destra di due e così via. Tale operazione è svolta da
un’apposita funzione denominata shift.
In questo modo, tutte le informazioni necessarie alla visualizzazione di un
numero vengono immesse nella struttura DISPLAY.
6.2.2.2. Funzione di lettura
La funzione decode() consente di “leggere” il numero impostato nella struttura
DISPLAY; nella pratica svolge l’operazione inversa di encode: decodifica una sequenza
di caratteri in un valore numerico. Anche in questo caso, l’assenza di librerie utili ha
reso più complicata l’implementazione di una funzionalità, che, di per se, è banale.
Per prima cosa, tramite la funzione atof si converte il vettore di caratteri della
variabile display in valore numerico, in seguito il numero ottenuto viene diviso per la
potenza di dieci appropriata per ottenere il valore finale correttamente convertito:
value = value/pow(10,display.point)
6.2.2.3. Inizializzazione della variabile display
L’inizializzazione della variabile display è affidata alla funzione setUpDisplay(), la
quale imposta a zero tutte le cifre e disabilita la visualizzazione del simbolo di modifica.
Inoltre, si occupa di eseguire il set-up di tutti i campi di controllo che nella trattazione
della struttura DISPLAY non sono ancora stati introdotti, ma che verranno presentati nel
prossimo paragrafo.
void setUpDisplay()
display.character[0]='\0';
display.character[1]='\0';
display.character[2]='\0';
display.character[3]='\0';
display.character[4]='\0';
display.point=none;
display.sign=0;
display.change=FALSE;
display.swap=TRUE;
display.blinkingDigit=0;
display.blink=FALSE;
Capitolo 6 - Implementazione
122
6.3. Video
Conclusa la descrizione della rappresentazione interna dei dati, la quale considera
che un valore sia scritto a video nel momento stesso in cui viene impostato nella
variabile display, si affronta ora la descrizione del thread video, il cui compito è quello
di operare l’effettiva scrittura sul display LCD. All’interno del thread sono racchiuse
tutte le attività d’interazione con il dispositivo fisico che avvengono per mezzo delle
funzioni che BrickOS mette a disposizione.
Prima di affrontare nel dettaglio l’implementazione di video, si vuole concludere la
presentazione della struttura dati DISPLAY riportando la descrizione dei campi che
forniscono informazioni di servizio, utili alle operazioni di scrittura.
6.3.1. Campi di controllo nella struttura DISPLAY
Sostanzialmente le informazioni di controllo di cui necessita il task video sono
inerenti al lampeggio di cifre e simboli e relative alla sostituzione della voce
attualmente visualizzata.
In primo luogo, si affronta la necessità di far lampeggiare uno o più elementi del
display, siano essi cifre o punti decimali. A tale scopo si predispongono due campi nella
struttura DISPLAY:
− blink di tipo booleano, indica se il lampeggio deve avvenire o meno (se TRUE
avviene il blinking)
− blinkingDigit, di tipo unsigned int, indica quale (o quali) elemento del display deve
apparire ad intermittenza. La seguente tabella riporta l’indice che a ciascun elemento
viene associato nella variabile blinkingDigit.
Elementi del display Valore di blinkingDigit
Cifra_1 (prima a sinistra) 0
Cifra_2 1
Cifra_3 2
Cifra_4 3
Capitolo 6 - Implementazione
123
Cifra_5 4
Punto Decimale (vale per tutti e tre) 5
Tutti (5 cifre più l’eventuale punto) 6
L’ultimo campo di cui necessita il thread video è una variabile booleana denominata
swap, la quale, durante la consultazione dei parametri di un regolatore, indica il
passaggio alla successiva voce del menù. A questo punto della trattazione sarebbe
prematuro spiegare la necessità di questo campo; per ora basti sapere che swap viene
settata TRUE ogni volta che si passa da una voce di menù alla successiva e che viene
riportata a FALSE non appena video rileva l’evento.
E’ possibile a questo punto passare alla descrizione dell’implementazione del thread
video.
6.3.2. Thread Video
La funzione svolta da questo task, seppure importante e necessaria, non è
strettamente indispensabile ai fini della regolazione vera e propria operata su un
processo, anzi, si può vedere il tempo di CPU da essa occupato come una limitazione
imposta all’attività di regolazione. Infatti, video è posto in esecuzione concorrente con i
thread che si occupano di eseguire i vari regolatori PID. Se è vero, che nella
configurazione con un solo regolatore, l’esecuzione di video non ostacola l’attività
primaria del sistema è altrettanto vero che, nel caso di vari anelli di regolazione
contemporaneamente gestiti dal sistema, il thread di visualizzazione introduce una
limitazione sulla massima frequenza di campionamento dei diversi regolatori attivi.
Alla luce di queste considerazioni, nell’implementazione di video si é individuato
nella velocità d’esecuzione il principale requisito da soddisfare. Pertanto, solo
considerando la necessità d’ottenere un task molto efficiente, si possono giustificare
molte scelte implementative, che dal punto di vista dell’organizzazione logica del
codice presentano delle incongruenze.
Il thread video è organizzato in due diverse sezioni eseguite in muta esclusione. La
prima si occupa del “Menù PID”, ovvero permette all’utente di selezionare il PID ai cui
Capitolo 6 - Implementazione
124
parametri intende accedere. La seconda è incaricata di visualizzare il menù parametri
del regolatore selezionato, al quale si farà riferimento con il nome di “Menù Param”. La
mutua esclusione fra le due sezioni è garantita da una variabile globale booleana:
menù_PID, il cui valore viene impostato ad un livello logico superiore da parte del
gestore degli eventi.
6.3.2.1. Menu PID
Permette all’utente di selezionare un regolatore su cui intervenire, ovviamente
l’esecuzione di video può entrare in questa sezione solo se nel sistema sono presenti
almeno due regolatori, in caso contrario viene tralasciata. Menù PID presenta due
modalità di lavoro:
− modalità high-mode: attiva se l’utente è ‘operativo’, ovvero se effettivamente sta
intervenendo sui pulsanti dell’RCX. Più precisamente, la condizione che deve essere
soddisfatta per restare in high-mode è che l’ultima pressione di un pulsante sia
avvenuta da meno di 15 secondi. Per rilevare questa situazione, si utilizza un time
marker, rappresentato dalla variabile globale time_rest, in cui si fissa l’istante
temporale della pressione di uno dei due tasti. Dalla differenza tra l’istante di tempo
attuale (fornito da BrickOS tramite sys_time) e la variabile time_rest è possibile
misurare il tempo trascorso dall’ultima pressione. La condizione da soddisfare è
quindi la seguente: (sys_time-time_rest) < 15000; è da notare che l’unità di misura
temporale in BrickOS è il millesimo di secondo. In high-mode si visualizza
semplicemente l’etichetta relativa al regolatore attualmente selezionato, individuato
dall’indice currentPID. Tale visualizzazione avviene con un periodo di refresh di
500 ms.
− modalità low-mode: attivata se l’operatore non interviene sui pulsanti da almeno 15
secondi. In low-mode sul display viene visualizzata la scritta ‘LOW’ ed il thread
viene posto in attesa che un tasto qualsiasi venga premuto. La sospensione
dell’esecuzione avviene per mezzo della chiamata wait_event(&pushAKey,0), in cui
la condizione di risveglio puschAKey è definita dal seguente blocco di codice:
wakeup_t pushAKey(wakeup_t data)
return (PRESSED(dbutton(),KEY_1) ||
PRESSED(dbutton(), KEY_2));
Capitolo 6 - Implementazione
125
Alla pressione di uno dei due pulsanti, il sistema reagisce riattivando il thread video e
riportandolo in high-mode.
Quando il task entra in low-mode si ottiene un risparmio della capacità elaborativa
della CPU. In questo modo, il resto del sistema non risulta intralciato dal thread di
visualizzazione dei dati.
6.3.2.2. Menu Param
In questa modalità il thraed ha il compito primario di visualizzare il menù dei
parametri relativo al regolatore attualmente selezionato; tale menù è costituito
principalmente da una serie di voci numeriche alle quali si aggiungono solo due voci
lessicali. Oltre a questo deve poter far lampeggiare le varie voci (come descritto nella
fase di specifica) ed eseguire il tracking di un valore, che consiste nel far apparire in
tempo reale il valore di una variabile.
La descrizione del codice non seguirà l’ordine della sequenza di istruzioni che lo
compongono, si preferisce, infatti, discutere l’implementazione a seconda del tipo di
visualizzazione. In generale, si può affermare che questa sezione di codice è costituita
da una serie di controlli, in base ai quali si predispone il contenuto di una variabile
locale string, la quale, come ultima istruzione (attività) del ciclo, verrà scritta sul
display.
− Visualizzazione di una voce numerica
Consiste nel far apparire sul display, per alcuni istanti, l’etichetta che identifica la
voce e nel mostrare, successivamente, il valore numerico associato. Per fare ciò, in
primo luogo, si controlla se è stata selezionata una nuova voce (display.swap = =
TRUE); in caso positivo si riporta a FALSE la variabile display.swap, si pulisce lo
schermo attraverso la funzione cls() e, dopo una breve attesa, si scrive sul display
per alcuni istanti il nome della voce selezionata. La funzione che consente di
scrivere sul display è la seguente: cputs(menu[currentPID][currentItem].name). A
questo punto, per visualizzare il valore si copia il vettore display.character nella
variabile locale string, quest’ultima verrà visualizzata per mezzo della funzione
cputs(string).
Per quanto riguarda l’eventuale punto decimale la sua apposizione avviene tramite il
seguente segmento di codice:
Capitolo 6 - Implementazione
126
switch(display.point+offset)
case 4: dlcd_show(LCD_4_DOT);break;
case 3: dlcd_show(LCD_3_DOT);break;
case 2: dlcd_show(LCD_2_DOT);break;
Da notare l’impiego della funzione dlcd_show, una procedura di basso livello
messa a disposizione dal S.O. che consente di visualizzare un singolo segmento del
display dell’RCX. In questo caso si visualizza uno dei tre punti decimali, ai quali si
accede per mezzo delle macro LCD_4_DOT (punto a sinistra), LCD_3_DOT (punto
centrale) e LCD_2_DOT (punto a destra). L’utilità della variabile locale offset, che
in questa funzionalità non sarebbe necessaria, verrà spiegata in seguito descrivendo
il lampeggio di cifre e simboli.
Ad ogni ciclo di refresh, di periodo pari a circa 500ms, vengono ripetute tutte queste
operazioni. Nel caso in cui display.swap risultasse FALSE (l’evento nuova voce è
già stato rilevato) non viene visualizzata l’etichetta, ma semplicemente il valore
numerico della voce.
− Visualizzazione di una voce lessicale
Il thread video riconosce di dover scrivere una voce di questo tipo in base
all’indice currentItem.
Se questo risulta essere pari a POS_MODE si deve gestire la scelta fra la modalità di
funzionamento manuale o automatica. In questo caso si deve, anzitutto, eliminare il
punto decimale (display.point = 0); in seguito si testa la variabile globale manual, il
cui valore è gestito ad un livello logico superiore da parte della “Gestione dei
contesti”. Se manual è TRUE nella variabile string si compone la seguente etichetta:
‘MAN ?’, altrimenti si compone l’etichetta ‘AUTO?’. Queste scritte rappresentano
delle richieste di conferma alla scelta operata, alle quali l’operatore può rispondere
confermando oppure annullando l’impostazione.
Se l’indice currentItem è pari a POS_CONF, significa che l’operatore ha apportato
delle modifiche al menù ed ora è giunto alla richiesta di conferma di tali modifiche
(voce CONF), oppure non ha apportato modifiche, ed essendo presenti più
regolatori, è posto difronte alla scelta di uscire dall’attuale menù (voce EXIT). In
ogni caso, dopo aver eliminato l’eventuale punto decimale e visualizzato la corretta
Capitolo 6 - Implementazione
127
etichetta (CONF o EXIT), nella variabile string viene composta l’etichetta ‘CONF?’
come richiesta di conferma alla scelta fatta.
Anche per quanto riguarda la voce lessicale l’ultima operazione svolta è la
scrittura della variabile string.
A questo punto della trattazione, si vuol far notare che per quanto riguarda le
etichette, la visualizzazione non passa attraverso la struttura DISPLAY. Questa
scelta, che viene meno all’impostazione data dall’architettura del modulo, si è resa
necessaria, in quanto il passaggio attraverso la struttura che mappa il display fisico
sarebbe inutile (nessuna conversione stringa/numero e numero/stringa è necessaria)
e comporterebbe solo un overhead all’attività del task video.
− Lampeggio di una voce numerica
Questa funzionalità è attivata in relazione alla modifica di una voce numerica. Per
fare apparire ad intermittenza uno o più elementi del display si è deciso di adottare il
seguente procedimento: ad una prima iterazione di questo segmento di codice, sul
display sono visualizzati tutti gli elementi che compongono il numero in questione.
Al ciclo successivo, solo gli elementi che devono lampeggiare vengono cancellati,
mentre il resto del numero viene riscritto. Continuando ad eseguire in sequenza
queste due fasi si produce l’effetto di far apparire in maniera intermittente gli
elementi voluti per tutto il tempo necessario.
Sulla base della descrizione qui sopra riportata, si passa ora alla discussione
dell’implementazione di tale meccanismo.
In primo luogo si verifica la condizione per entrare in questa sezione di codice:
display.blink == TRUE && flag == TRUE. La prima variabile controlla se è
richiesto il lampeggio, mentre flag è utilizzata per ottenere la discriminazione tra le
due fasi che compongono l’intermittenza. Questa variabile viene complementata al
termine di ogni ciclo del thread. Quando la condizione è verificata, per prima cosa si
individua quale elemento del numero deve lampeggiare:
! se display.blinkingDigit è minore di 5, significa che deve lampeggiare un solo
carattere, così lo si cancella attraverso la seguente istruzione:
string[display.blinkingDigit] = blank, dove blank corrisponde allo spazio
vuoto (blank = ‘ ‘).
! se display.blinkingDigit è pari a 5 sarà il punto decimale a dover lampeggiare.
Capitolo 6 - Implementazione
128
Per far ciò si pone offset = - display.point, in questo modo la somma di offset
e display.point risulta zero ed il segmento di codice preposto alla scrittura non
rileverà nessun punto da visualizzare (tale segmento è stato riportato nella
sezione “Visualizzazione voce numerica”.
! se display.blinkingDigit è uguale a 6, significa che tutti gli elementi che
compongono il numero devono lampeggiare, pertento si cancellerà l’eventuale
punto, con la tecnica appena descritta e le cinque cifre ponendo ciascun
elemento di string pari blank.
La scelta di utilizzare le variabili locali string e offset trova in questa procedura
la sua spiegazione. Infatti, operando sulle due variabili si evita d’intervenire
direttamente sulla struttura DISPLAY consentendo all’operatore di modificare i
parametri a suo piacimento senza correre il rischio di perdere informazioni.
− Tracking di una variabile
Consentire il monitoraggio di una variabile è un’attività irrinunciabile in un
regolatore PID; nel progetto in questione si è stabilito di applicare questa
funzionalità alla Process Value. Per implementarla è necessario operare un continuo
aggiornamento della variabile display invocando ad ogni ciclo del thread la funzione
encode. Questa funzionalità viene attivata quando la variabile globale trackingPV
viene settata TRUE, il controllo di quest’ultima è operato ad un livello logico
superiore da parte della “Gestione dei contesti”. L’implementazione del tracking
avviene attraverso la seguente riga di codice:
if(trackingPV) encode((double)PIDloop[currentPID].PV);
Si conclude la trattazione delle funzionalità offerte da video, riportando
l’implementazione della visualizzazione del simbolo di modifica, che utilizza
l’istruzione dlcd_show di cui si è già parlato.
if(display.change)
dlcd_show(SYMBOL_OF_CHANGE);
else dlcd_hide(SYMBOL_OF_CHANGE);
Si è definito SYMBOL_OF_CHANGE in rappresentanza della macro LCD_DOT_0 che
individua il simbolo del display selezionato a tale scopo.
Capitolo 6 - Implementazione
129
7. LIBRERIE
Con il termine “libreria” si vuole indicare un insieme di funzioni accessorie,
generalmente basate su algoritmi standard, che offrono servizi largamente utilizzati dai
programmatori durante lo sviluppo delle applicazioni.
BrickOS, oltre ad essere un sistema operativo per l’RCX, rappresenta anche un
ambiente di sviluppo per il programmatore; pertanto, dovrebbe rendere disponibili una
serie di librerie volte a facilitare l’attività di implementazione e, al contempo, ad
aumentarne l’efficacia. Allo stato attuale del suo sviluppo, BrickOS fornisce una sola
libreria (Math.h) costituta da una sola funzione che permette di generare un numero
casuale (random()). Purtroppo, anche la realizzazione di nuove librerie risulta
problematica, in quanto non sono ancora stati definiti con precisione gli standard di
sviluppo, le procedure di messa in opera e le regole di visibilità. Inoltre, le librerie non
possono essere caricate dinamicamente in memoria, come avviene per i programmi
utente, ma devono essere compilate insieme all’intero sistema operativo e scaricate con
esso sull’RCX.
Per tutte queste ragioni, si è scelto di implementare le funzioni di libreria come
semplici funzioni definite nello stesso ambiente operativo dell’applicazione di controllo.
Alcune di esse sono state costruite a partire da algoritmi standard disponibili in
letteratura, altre sono state progettate ex-novo. E’ possibile classificarle in:
- Libreria Matematica: permette di eseguire operazioni di natura aritmetica o
algebrica. Include le seguenti funzioni:
! pow: calcola la potenza di un numero con esponente intero;
! modf: restituisce la parte intera e la parte decimale di un numero reale;
! idexp: riceve in ingresso due numeri e ritorna il prodotto del primo per il
quadrato del secondo.
- Libreria Stringhe: permette di manipolare singoli caratteri o sequenze di
caratteri. Include le seguenti funzioni:
! isdigit: riceve un parametro di tipo char e restituisce TRUE se si tratta di
una cifra (0…9), FALSE altrimenti;
! ecvt: converte un valore numerico di tipo double in una stringa di caratteri;
! atof: converte una sequenza di caratteri in un valore numerico di tipo
Capitolo 6 - Implementazione
130
double;
! strcat: concatena due stringhe eliminando gli eventuali spazi vuoti.
La disponibilità di altre librerie avrebbe certamente facilitato alcune fasi dello
sviluppo, ma il costo della loro realizzazione avrebbe abbondantemente superato gli
effettivi benefici.
8. AVVIO DELL’APPLICAZIONE
Quando l’utente preme il pulsante RUN dell’RCX, BrickOS invoca la funzione
main() del programma attualmente selezionato; essa, pertanto, assolve l’importante
compito di avvio del software di controllo.
Può essere logicamente scomposta in due sezioni:
- inizializzazione: comprende tutte le operazioni preliminari da eseguire affinché il
sistema sia pronto ad elaborare i dati e ad interagire con il mondo esterno.
Sostanzialmente, a ciascuna delle tre macroaree (gestione degli eventi, gestione
della visualizzazione, funzione di controllo) individuate in fase di progetto
corrisponde un’opportuna funzione di inizializzazione; inoltre, è necessario
impostare le condizioni iniziali dei due semafori utilizzati per la
sincronizzazione:
setUpPID();
setUpDisplay();
loadMenu();
setContextPID();
sem_init(semDisplay, mutexDisplay, 1);
sem_init(semMenu, mutexMenu, 1);
- avvio dei thread: crea e manda in esecuzione i thread per la gestione della
visualizzazione e degli eventi; ad essi si aggiunge un thread per ogni regolatore
che compone il sistema di controllo.
execi(&video,0,NULL,1,DEFAULT_STACK_SIZE); //Visualizzazione
execi(&keyOne,0,NULL,1,DEFAULT_STACK_SIZE); //Eventi
execi(&keyTwo,0,NULL,1,DEFAULT_STACK_SIZE); //Eventi
Capitolo 6 - Implementazione
131
execi(&executePID,0,NULL,1,DEFAULT_STACK_SIZE); //PID 0
...
9. SINCRONIZZAZIONE
Quando il software viene strutturato come un set di unità concorrenti (thread)
eseguite in parallelo, risulta necessario utilizzare alcune primitive di sincronizzazione
che permettano di vincolare l’ordine in cui vengono svolte le operazioni da parte dei
vari task (vedi [3]). I semafori rappresentano l’unico meccanismo di sincronizzazione
offerto da BrickOS; nell’ambito del progetto essi vengono utilizzati al solo scopo di
garantire l’accesso in mutua esclusione alle risorse condivise del sistema.
9.1. I semafori
I semafori sono meccanismi di sincronizzazione a basso livello, utilizzati
principalmente quando la comunicazione interprocessuale si realizza tramite variabili
condivise (vedi [3] e [4]). Si può pensare ad essi come a variabili che hanno un valore
intero (s) e tre operazioni associate:
- L’inizializzazione (con un valore non negativo);
- L’operazione “wait”: decrementa il valore del semaforo; se il valore diventa
negativo, il processo che esegue la wait viene sospeso. In termini di pseudo-
codice:
if (s>0)
then s = s-1;
else «sospendi il processo corrente»;
- L’operazione “post”: incrementa il valore del semaforo; se il valore non è
positivo allora uno dei processi sospesi sull’operazione wait viene risvegliato. La
definizione, in pseudo-codice, è la seguente:
if («c’è un processo sospeso sul semaforo»)
then «sveglia il processo»
else s = s+1;
Si consideri una generica risorsa condivisa. Per garantire l’accesso in mutua
Capitolo 6 - Implementazione
132
esclusione, si associa un semaforo a tale risorsa e lo si inizializza a 1; in questo modo, il
primo processo che esegue wait potrà accedere alla risorsa ed il valore verrà posto a 0.
In seguito, se un altro processo cercherà di accedere alla stessa risorsa, la troverà
occupata, verrà sospeso e il valore di s diventerà -1. Non c’è limite al numero di task
che possono essere sospesi in attesa della risorsa. Quando il processo iniziale rilascia la
risorsa, s viene incrementato e uno dei processi in attesa (se ce ne sono) verrà tolto dalla
coda dei processi sospesi associata al semaforo e potrà accedere alla risorsa non appena
sarà riattivato dal sistema operativo.
9.2. Risorse condivise
Si consideri la situazione in cui il sistema di controllo sia costituito da un singolo
regolatore. In questo caso, all’avvio dell’applicazione verranno lanciati in esecuzione:
- il thread di gestione del display (video);
- i due thread di gestione degli eventi (keyOne e keyTwo);
- il thread che realizza il controllo vero e proprio (executePID).
Un’attenta analisi permette di individuare le risorse condivise, ovvero le strutture dati
sulle quali si basa la comunicazione interprocessuale:
- la variabile display viene scritta dal gestore dei contesti (in risposta agli eventi
generati dall’utente) e letta dal gestore del display (ad ogni ciclo di
visualizzazione);
- le variabili che contengono i parametri del regolatore (PIDa e PIDb) e dell’anello
(PIDloop) possono essere lette e/o scritte sia dal gestore dei contesti (in risposta
agli eventi generati dall’utente) che dalla funzione di controllo vera e propria.
A partire da queste informazioni, è stata elaborata la seguente strategia di
sincronizzazione:
- si definisce un semaforo per la sola variabile display:
sem_t* semDisplay; //Definizione del semaforo
int mutexDisplay = 1; //Valore intero associato al semaforo
sem_init(semDisplay, mutexDisplay, 1); //Inizializzazione
Ogni accesso critico dal punto di vista della concorrenza dovrà rispettare il
seguente schema:
Capitolo 6 - Implementazione
133
sem_wait(semDisplay); //Accesso alla risorsa
... //Operazioni su display
sem_post(semDisplay); //Rilascio della risorsa
Un possibile esempio è dato proprio dal processo di visualizzazione: quando
parte un nuovo ciclo di refresh del display, il thread video esegue sem_wait sul
semaforo semDisplay; da questo punto in poi, nessuno può accedere a display.
Terminate tutte le operazioni necessarie, video rilascia la risorsa invocando la
funzione sem_post, dopodiché viene sospeso fino al prossimo ciclo di refresh.
- Le variabili PIDa, PIDb e PIDloop vengono protette definendo un semaforo sulla
variabile menu:
sem_t* semMenu;
int mutexMenu = 1;
sem_init(semMenu, mutexMenu, 1);
Questa scelta è giustificata dal fatto che tutte le operazioni di lettura e scrittura
dei parametri, critiche dal punto di vista della concorrenza, possono avvenire
solo tramite la struttura menu; in altri termini, essa costituisce l’unico punto di
accesso a tali parametri. Proteggendo ogni accesso critico a menu, si proteggono
indirettamente anche le variabili associate al regolatore.
A titolo di esempio si consideri la funzione invocata nel caso in cui l’utente
confermi le modifiche apportate ad una voce numerica:
void confirmNumericalItem()
...
sem_wait(semMenu);
*(menu[currentPID][currentItem].value) = (float)decode();
sem_post(semMenu);
setContextMenu();
Prima di concludere la sezione dedicata alla sincronizzazione, è utile precisare che
non tutti gli accessi alle variabili sopraccitate sono protetti mediante l’utilizzo dei
semafori; si è scelto, infatti, di agire solo ed esclusivamente sulle operazioni critiche in
un’ottica di concorrenza fra processi, al fine di preservare e garantire la correttezza
dell’applicazione.
Capitolo 6 - Implementazione
134
10. Appendice A: STRUTTURE DATI
******************************
* DEFINE *
******************************
#define NUMBER_OF_PIDs 1
#define SIZE_MENU 16
#define MAX_DIGIT 5
#define KEY_1 BUTTON_VIEW
#define KEY_2 BUTTON_PROGRAM
#define SYMBOL_OF_CHANGE LCD_DOT_0
#define POS_SP 10
#define POS_SPi 11
#define POS_PV 12
#define POS_CS 13
#define POS_MODE 14
#define POS_CONF 15
#define MAX_SP 99999
#define TRUE 1
#define FALSE 0
*****************************************
* DEFINIZIONE TIPI E STRUTTURE DATI *
*****************************************
typedef float REAL;
typedef unsigned char BOOL;
typedef struct
REAL K;
REAL Ti;
Capitolo 6 - Implementazione
135
REAL Td;
REAL N;
REAL b;
REAL c;
REAL Ts;
REAL CSmax;
REAL CSmin;
REAL deltaCSMAN;
PIDPARAMS;
typedef struct
REAL SP;
REAL SPold;
REAL PV;
REAL PVold;
REAL CS;
REAL CSold;
REAL Dold;
BOOL MAN;
BOOL MANinc;
BOOL MANdec;
BOOL HIsat;
BOOL LOsat;
BOOL NoInc;
BOOL NoDec;
BOOL ForceMAN;
BOOL IAmRunning;
PIDLOOPDATA;
typedef struct
char character[MAX_DIGIT];
char sign;
enum pointPosition
left=4,mid_left=3,mid_right=2,right=1,none=0 point;
BOOL change;
BOOL swap;
BOOL blink;
Capitolo 6 - Implementazione
136
unsigned int blinkingDigit;
DISPLAY ;
typedef struct
char *name;
REAL *value;
BOOL modifiable;
VOCE_MENU;
***************************************
* DICHIARAZIONE VARIABILI GLOBALI *
***************************************
PIDPARAMS PIDa[NUMBER_OF_PIDs],PIDb[NUMBER_OF_PIDs],
*PIDedit[NUMBER_OF_PIDs],*PIDrun[NUMBER_OF_PIDs];
PIDLOOPDATA PIDloop[NUMBER_OF_PIDs];
DISPLAY display;
VOCE_MENU menu[NUMBER_OF_PIDs][SIZE_MENU];
int currentItem=0;
int currentPID=0;
BOOL menu_PID = FALSE;
time_t time_rest = 0;
BOOL trackingPV = FALSE;
sem_t* semDisplay;
int mutexDisplay = 1;
sem_t* semMenu;
int mutexMenu = 1;
BOOL manual;
float memoryChange=0;
Capitolo 7 - Test e verifica
137
Capitolo 7
Test e verifica
Capitolo 7 - Test e verifica
138
In questo capitolo si discuterà la fase di test e verifica dell’applicazione.
1. INTRODUZIONE
L’analisi di correttezza dell’applicazione costituisce un problema concettualmente
diverso a seconda che si considerino i requisiti informali espressi dall’utente o le
specifiche rigorose di progetto. Nel primo caso si parla di convalida, ovvero quel
complesso di attività volte ad appurare la capacità del software sviluppato di soddisfare
le reali necessità dell’utente finale; nel secondo caso, invece, si attua una verifica del
prodotto, con l’obiettivo primario di controllarne la correttezza rispetto alle specifiche
progettuali.
Verifica e convalida vivono un legame di complementarietà; certamente, si tratta di
attività piuttosto complesse che coinvolgono numerosi aspetti del ciclo di vita del
software: dalla definizione dei fattori di qualità, allo studio di metodi per il controllo di
ciascun passo di sviluppo, alla formalizzazione di tecniche per il collaudo del prodotto
finale.
La convalida dell’applicazione, diffusamente trattata in questo capitolo, si basa su
due differenti tecniche di analisi:
- analisi statica: permette di rilevare anomalie basandosi unicamente
sull’osservazione diretta del codice. In altre parole, si tratta di verificare la
validità di determinate proprietà indipendentemente dall’esecuzione;
- analisi dinamica: prevede che il programma venga eseguito per particolari dati di
ingresso, considerati “critici” al fine di rilevare eventuali anomalie.
Nella fase di implementazione, le singole funzionalità di ciascun modulo sono state
sottoposte ad accurata sperimentazione; si ritiene poco opportuno descrivere tutti questi
micro-test, in quanto si appesantirebbe notevolmente la trattazione senza apportare un
reale valore aggiunto.
Nel corso della campagna di test, si è rivelata particolarmente vantaggiosa ed
efficace l’idea di organizzare ciascun modulo del sistema in due parti: la prima, detta
“kernel”, realizza una pura elaborazione dei dati in completa autonomia rispetto
all’RCX; la seconda, invece, realizza il collegamento fra il “kernel” ed il mondo esterno
Capitolo 7 - Test e verifica
139
per mezzo dell’RCX.
Il capitolo è organizzato in due sezioni principali:
- test di modulo: parte dedicata alla verifica dei singoli moduli che costituiscono il
software;
- test di integrazione e di sistema: parte dedicata al controllo delle modalità di
interazione fra i vari moduli e al test dell’intera applicazione da eseguire
sull’RCX.
2. TEST DI MODULO
Ciascuno dei tre moduli realizzati durante la fase di implementazione, viene
estrapolato dal contesto dell’intero sistema di controllo e considerato singolarmente;
l’esecuzione dei test richiede, pertanto, la creazione di un ambiente di lavoro “simulato”
che possa fornire al modulo gli input richiesti ed accettare gli output da esso prodotti,
così che si possa verificare la correttezza delle elaborazioni compiute.
2.1. Test esecuzione KERNEL
In questo paragrafo si presentano le prove sperimentali cui è stata sottoposta tutta
quella parte di codice che realizza la vera e propria elaborazione dei dati e che risulta
sostanzialmente indipendente dalla piattaforma hardware sulla quale può essere
eseguita. Questa caratteristica si traduce immediatamente nella possibilità di eseguire
buona parte dei test su PC, con tutti i vantaggi del caso; in particolar modo, si può
disporre degli strumenti avanzati di debug forniti dai moderni ambienti di sviluppo.
2.1.1. Accesso ai dati
Il software di controllo deve fornire un apposito menù attraverso il quale l’utente
possa agire, in visualizzazione o in modifica, sui parametri di ciascun regolatore. Le
parti di codice di cui s’intende accertare la correttezza sono:
- la struttura dati menu e le funzioni che operano su essa;
- le due strutture dati relative all'algoritmo di regolazione (PIDPARAMS e
Capitolo 7 - Test e verifica
140
PIDLOOP).
- il gestore dei contesti;
Per realizzare il test su PC è necessario simulare il contesto d’esecuzione delle
funzioni da verificare, al fine di creare una mini-applicazione che permetta di scegliere
un regolatore e di far scorrere a video il menù dei suoi parametri; l’applicazione, inoltre,
deve offrire la possibilità di modificare il valore delle voci numeriche, lessicali ed
incrementali, rispettando le procedure previste nelle specifiche di progetto.
Per fare questo è indispensabile simulare le attività svolte dal rilevatore degli eventi e
della visualizzazione dei dati. Per quanto riguarda la prima, i cinque possibili eventi da
rilevare sono simulati per mezzo della tastiera, con la seguente convenzione:
Per ciò che concerne la visualizzazione dei dati, invece, il monitor del PC viene
utilizzato per emulare il display fisico dell’RCX; pertanto, è necessario definire
un’apposita funzione (chiamata stampa()) che visualizzi, oltre al nome ed al valore di
ciascun parametro, una serie di informazioni di controllo: posizione del punto decimale,
attivazione/disattivazione della modalità di lampeggio, attivazione/disattivazione del
simbolo di modifica, modalità di funzionamento del regolatore. Per completezza se ne
riporta il codice:
void stampa()
if(display.swap)
printf("\nNome: ",menu[currentPID][currentItem].name);
printf("\nCifre: %s",display.character);
printf("\nPunto: %d",display.point);
Tasto Evento corrispondente (simulato)
“1” KEY_1_short
“2” KEY_2_short
“3” KEY_2_long
“4” KEY_1_2
“5” KEY_1_long
Capitolo 7 - Test e verifica
141
printf("\nModifica: %d",display.change);
if(display.blink)
printf("\nCifra blink%d",display.blinkingDigit);
printf("\nModalità effettiva:%d",PIDloop[currentPID].MAN);
printf("\n");
A questo punto è possibile presentare i vari casi di test; si partirà dalle situazioni più
semplici per arrivare, via via, a quelle più complesse e critiche ai fini della correttezza.
2.1.1.1. Scorrimento dei menù
Obiettivo
Verificare che il software gestisca in modo opportuno lo scorrimento ciclico dei vari
menù.
Modalità operativa
All’avvio dell’applicazione, si fa scorrere (tasto “1”) interamente il menù dei PID per
più volte, per appurare se il completamento di un ciclo determini comportamenti
indesiderati. Il secondo passo prevede la selezione (tasto “2”) di uno dei regolatori
disponibili e lo scorrimento (tasto “1”) del menù parametri ad esso relativo, con le
stesse modalità viste sopra. Non attuando alcuna modifica, ci si aspetta di vedere
“EXIT” come ultima voce; la pressione del tasto “2” in corrispondenza di essa deve
riportare alla lista dei regolatori.
Risultati
Data la semplicità delle azioni intraprese, è logico aspettarsi un buon comportamento
del sistema già alla prima esecuzione del test. L’unico problema riscontrato è quello
relativo alla visualizzazione della voce “EXIT”; l’anomalia è da ricondursi ad un’errata
formulazione della condizione di visualizzazione di tale etichetta nella funzione
nextItem(), la cui versione corretta risulta essere:
...
if(currentItem==SIZE_MENU-2)
if(display.change==TRUE)
currentItem++;
menu[currentPID][currentItem].name = "CONF ";
Capitolo 7 - Test e verifica
142
else
if(display.change==FALSE && NUMBER_OF_PIDs>1)
currentItem++;
menu[currentPID][currentItem].name="EXIT ";
else currentItem = 0;
...
2.1.1.2. Modifica voce numerica
Obiettivo
Verificare che il sistema rispetti la procedura prevista per questo tipo di parametro
(selezione e modifica di ogni singola cifra, impostazione punto decimale) e, soprattutto,
che la conferma/annullamento di primo livello operino correttamente.
Modalità operativa
Si seleziona un regolatore, si visualizza un parametro numerico (ad esempio “K”) e si
entra nella modalità di modifica (tasto “2”). A questo punto si selezionano, in sequenza,
ciascuna delle cinque cifre ed il punto decimale (tasto “2”), apportando le opportune
variazioni (tasto “1”). Infine, si conferma il nuovo valore per il parametro. Il simbolo di
modifica, ora, deve essere attivo.
Dopo aver riavviato l’applicazione, si eseguono operazioni analoghe sullo stesso
parametro, stavolta annullando le modifiche apportate. Il simbolo di modifica deve
rimanere disattivato.
Si riavvia nuovamente l’applicazione e si agisce su due distinti parametri numerici
(ad esempio “Ti” e “Td”): per il primo si confermano, mentre per il secondo si
annullano le modifiche.
Risultati
Per quanto possa sembrare contraddittorio, un test ha esito positivo quando permette
di rilevare anomalie nel codice, così come succede in questo caso. Si procederà per
ordine di individuazione:
Capitolo 7 - Test e verifica
143
- l’applicazione si blocca sempre al momento della conferma di primo livello. Il
problema è dovuto ad una errata gestione dell’incremento di blinkingDigit a
fronte dell’evento KEY_2_short; ciò impedisce al sistema di applicare il contesto
di conferma nella situazione in cui lampeggiano tutte le cifre ed il punto
decimale. L’istruzione seguente permette di risolvere la questione:
...
if(display.blinkingDigit==6) display.blinkingDigit=0;
else
display.blinkingDigit++;
if (display.blinkingDigit==6)
setContextNumericalItemConfirm();
...
- A seguito della conferma del nuovo valore non risulta possibile visualizzare i
parametri successivi; in altre parole, sembra che il sistema sia insensibile agli
eventi. Il problema non si verifica in caso di annullamento delle modifiche.
L’errore consiste nella mancata riapplicazione del contesto «Menù Parametri» al
termine della funzione confirmNumericalItem() che realizza la conferma di primo
livello. Anomalie di questo tipo sono facili da sanare, ma molto difficili da
individuare.
- La conferma di primo livello delle modifiche non ha alcun effetto; infatti,
facendo scorrere il menù sino a tornare al parametro precedentemente modificato,
il sistema ripropone il valore iniziale. La soluzione di questo problema ha
richiesto un investimento non trascurabile in termini di tempo; l’esecuzione
passo-passo del codice ha permesso di rilevare errori nella gestione dei puntatori
presenti nelle strutture dati PIDLOOP, PIDPARAMS e menu. Si tratta, perlopiù,
di errori che ricorrono frequentemente nelle applicazioni in cui si fa uso di
puntatori; per questo motivo se ne omette la descrizione dettagliata.
- Infine, sono emerse altre anomalie di secondaria importanza (ad esempio: la
mancata attivazione del simbolo di modifica laddove richiesto, l’impossibilità di
Capitolo 7 - Test e verifica
144
spostare il punto decimale) che non vengono presentate in quanto la loro matrice
non è di natura squisitamente concettuale; nella maggior parte dei casi, infatti, si
tratta di dimenticanze e/o errori di battitura.
2.1.1.3. Modifica voce lessicale
Obiettivo
Verificare la correttezza della procedura prevista per questo tipo di parametro. Allo
stato attuale, il menù dei parametri contiene una sola voce lessicale: la modalità di
funzionamento.
Modalità operativa
Una volta entrati nel contesto di modifica della voce MODE, il sistema deve proporre
due soli valori fra cui scegliere: MAN e AUTO. Trattandosi di un parametro descrittivo
dell’anello di controllo, la modifica risulta immediatamente visibile al regolatore (non è
necessaria la conferma di secondo livello); in base alla scelta operata, il sistema decide
se l’utente può accedere in modifica alla voce CS.
Risultati
Il test, così come il codice cui esso fa riferimento, non nasconde particolari insidie,
soprattutto alla luce delle correzioni sinora apportate. Non si rilevano, pertanto,
problemi degni di nota.
2.1.1.4. Modifica voce incrementale
Obiettivo
Verificare che il sistema rispetti la procedura prevista per questo tipo di parametro.
All’interno della struttura menu sono presenti due sole voci incrementali (SPi e CS),
ciascuna delle quali è caratterizzata da un passo d’incremento differente; pertanto, si
vuole valutare la capacità del sistema di impostare il passo di avanzamento corretto,
nonché l’operatività della conferma.
Modalità operativa
Dopo aver selezionato un PID, si accede alla voce SPi e si effettuano operazioni sia
Capitolo 7 - Test e verifica
145
d’incremento (tasto “1”) che di decremento (tasto “4”), facendo attenzione al fatto che il
valore del set-point rimanga nel range 0…MAXSP (dove MAXSP è una costante,
definibile in compilazione, che esprime il valore massimo assumibile dal set-point). Il
passo di incremento/decremento deve essere unitario.
In seguito, si riavvia l’applicazione, si porta il regolatore in modalità manuale e si
agisce sul parametro CS, verificando che il passo di incremento/decremento corrisponda
al valore attuale di deltaCSMAN. Lasciando il regolatore in modalità automatica, deve
essere bloccato l’accesso a CS in modalità di modifica.
Risultati
L’esecuzione del test non porta alla luce alcuna anomalia; le operazioni di
incremento/decremento, così come l’impostazione del passo di variazione, vengono
effettuate correttamente per entrambi i parametri. La logica di accesso alla voce CS
risponde perfettamente ai requisiti progettuali.
2.1.1.5. Conferma (o annullamento) di secondo livello
Obiettivo
Verificare che:
- in caso di conferma, le modifiche apportate dall’utente diventino operative
(visibili al regolatore);
- in caso di annullamento, per ciascun parametro vengano ripristinati i valori
precedenti alle modifiche.
In entrambi i casi il sistema deve tornare al menù di scelta dei PID.
Modalità operativa
Si seleziona un regolatore, si rettificano alcune voci e si confermano i nuovi valori; a
questo punto si accede nuovamente al menù dei parametri e, facendolo scorrere, si
devono leggere i valori precedentemente inseriti. Utilizzando strumenti di debug, si
accede direttamente alle variabili PIDedit e PIDrun associate al regolatore e si verifica
che esse contengano i valori corretti.
E’ evidente che, in caso di annullamento, il sistema deve ripristinare i valori iniziali e
nelle variabili sopra citate non deve rimanere alcuna traccia delle modifiche.
Capitolo 7 - Test e verifica
146
Risultati
Gli interventi correttivi effettuati in occasione dei test precedenti, permettono di
giungere a questo punto con una struttura elaborativa sufficientemente affidabile e
robusta; pertanto, le operazioni di conferma o annullamento vengono portate a termine
in modo regolare, senza sollevare eccezioni.
2.1.2. Algoritmo di controllo
L’algoritmo di controllo, che rappresenta il vero e proprio “cuore” del sistema, ha
coperto buona parte della trattazione: dall’introduzione teorica della legge PID, alla
progettazione del contesto di esecuzione, all’implementazione nell’ambiente BrickOS.
La funzione PID, che rappresenta l’equivalente discreto del regolatore, è stata
realizzata attraverso una semplice attività di codifica dell’algoritmo standard in
linguaggio C; per questo motivo non dovrebbero sorgere, nella fase di test, problemi di
natura concettuale.
Per convalidare la funzione PID è necessario disporre di un processo da controllare;
si può pensare di simulare tale processo discretizzando la sua funzione di trasferimento
e codificandola in linguaggio C. Si consideri il seguente sistema:
Nel dominio del tempo si avrà:
)()()( tetVdt
tdVCR =+
Ponendo RC=τ e passando nel dominio della trasformata di Laplace si trova la
funzione di trasferimento del processo ( )(sG ):
τsCRssEsVsG
sVsCRsVsE
+=
+==
+=
11
11
)()()(
)()()(
Per controllare il sistema è possibile utilizzare un regolatore PI, la cui funzione di
trasferimento risulta essere:
Capitolo 7 - Test e verifica
147
I
IP sT
sTKsR +=
1)( con
==
1P
I
KT τ
L’anello di regolazione assume la seguente configurazione:
A questo punto è necessario discretizzare la funzione di trasferimento del processo
utilizzando «Eulero all’indietro»
−=SzT
zs 1 :
ττ
τ
++−
=
−+=
S
S
S
S
TTkEkVkV
zzTzTzG
)()1()(
)1()(
***
*
Infine, si imposta: FCMR µ100,30 =Ω= ; da cui: 1103 −⋅=τ .
La funzione processo realizza l’equivalente discreto della rete RC:
...
REAL vprec = 0;
...
void processo(REAL controlSignal)
REAL v;
v = (vprec*0.3 + controlSignal*0.03)/(0.03 + 0.3);
vprec = v;
Il regolatore PI viene tarato nel modo seguente:
Parametro Valore
K 1.00
TI 0.30
TD 0.00
Capitolo 7 - Test e verifica
148
b 0.50
TS 0.03
Si dispone, ora, di tutte le informazioni necessarie per simulare il controllo del
sistema.
La simulazione viene eseguita sia su PC che su RCX. I risultati, riportati in
appendice, sentenziano un corretto funzionamento della procedura su entrambe le
piattaforme.
2.2. Test interazione KERNEL-RCX
In questa fase di test, il principale obiettivo è verificare il corretto funzionamento
della parte di codice che si occupa di mettere in comunicazione il “kernel”
dell’applicazione con il mondo esterno. Più precisamente, si verificano le operazioni
d’interazione con l’hardware, realizzate per mezzo delle API che BrickOS mette a
disposizione.
All’interno di ciascun modulo del sistema, si trova una parte di codice che si occupa
esclusivamente del controllo dell’hardware:
- Modulo di “Gestione degli eventi”: la rilevazione degli eventi costituisce
l’attività d’interazione con l’hardware, la quale si concretizza nella gestione dei
pulsanti dell’RCX per mezzo dei thread: keyOne e keyTwo.
- Modulo di “Visualizzazione dei dati”: il dispositivo hardware gestito è il display;
del controllo di tale dispositivo si occupa il thread video.
- Modulo di “Esecuzione del PID”: l’interazione con l’hardware è rappresentata
dalla lettura degli ingressi e dalla scrittura delle uscite. A queste due attività non è
dedicato, come negli altri casi, un task specifico, in quanto vengono
implementate come due sezioni di codice all’interno del thread executePID.
Per ognuna delle tre sezioni, qui sopra riportate, sono condotte delle prove specifiche
per verificarne la correttezza; nei paragrafi che seguono si riporta la descrizione di
tali prove.
Capitolo 7 - Test e verifica
149
2.2.1. Controllo dei pulsanti
Come già accennato, questa attività viene svolta dai thread keyOne e keyTwo, il cui
compito è quello di rilevare gli eventi.
Obiettivo
L’obiettivo della campagna di test sui due thread è di accertare che siano in grado di
rilevare tutti gli eventi previsti nella definizione del sistema:
- KEY_1_short → pressione breve tasto 1 (pulsante VIEW)
- KEY_1_long → pressione lunga tasto 1
- KEY_2_short → pressione breve tasto 2 (pulsante PRGM)
- KEY_2_long → pressione lunga tasto 2
- KEY_1_2 → pressione combinata tasto1 + tasto2
Pertanto, si vuole accertare sia il funzionamento individuale di ciascun task, sia il
loro funzionamento combinato.
Metodo operativo
Per realizzare il test si crea un apposito «main program», nel quale i due thread
vengono lanciati in esecuzione concorrente:
int main()
execi(&keyOne,0,NULL,1,DEFAULT_STACK_SIZE);
execi(&keyTwo,0,NULL,1,DEFAULT_STACK_SIZE);
Al fine di rendere più agevole l’esecuzione del test, si apportano delle piccole
aggiunte al codice dei task, che consentono la visualizzazione di etichette riportanti il
nome dell’evento rilevato.
Il test vero e proprio avviene scaricando il programma di prova sull’RCX e agendo
sui pulsanti al fine di generare tutti gli eventi previsti, più volte e in diverso ordine.
Risultati
I test condotti si dimostrano efficaci, infatti permettono sia di rilevare alcuni piccoli
errori nel codice, che di perfezionare alcuni meccanismi. Le correzioni apportate
portano alla realizzazione di un sistema di rilevazione degli eventi particolarmente
Capitolo 7 - Test e verifica
150
efficiente e robusto, che soddisfa appieno le esigenze progettuali.
2.2.2. Controllo del display
La verifica del funzionamento del thread video, preposto alla gestione del display, è
una attività complessa ed estesa rispetto a quella relativa ai pulsanti. Infatti, non è
possibile individuare un numero finito di eventi/scenari da esaminare, ma si può
soltanto individuare un insieme di casi significativi.
Obiettivo
L’attività di test è volta ad accertare che il task realizzi correttamente tutte le
funzionalità che gli sono richieste: visualizzazione dei due tipi di menù (menù PID e
menù Param), rispetto delle modalità di funzionamento (high-mode e low-mode),
visualizzazione di tutti i tipi di voci di menù (numerica e lessicale), visualizzazione
dell’etichetta al cambio di voce, esposizione del simbolo di modifica e lampeggio di
cifre e punti.
Metodo operativo
Per realizzare il test su video è necessario, in primo luogo, simulare il contesto di
esecuzione del task; per far questo si inizializzano le varibili menu e display. Si procede
poi ad implementare vari programmi di prova che, una volta in esecuzione, impegnino il
thread in una serie di attività che consentano di accertarne il corretto funzionamento.
Di seguito è riportato il codice di un programma di prova per la verifica delle
funzionalità di base del thread: visualizzazione di cifre numeriche e di etichette,
scrittura del punto in tutte le posizioni possibili, lampeggio, sia individuale che
collettivo, di tutti gli elementi e, infine, visualizzazione del simbolo di modifica.
int main()
//inizializzazione delle variabili display e menu
inizializzaDisplay();
caricaMenu();
//lancio del thread video
execi(&video,0,NULL,1,DEFAULT_STACK_SIZE);
Capitolo 7 - Test e verifica
151
//test sul punto decimale
display.punto=sinistra;
msleep(500); //attesa di 500 msec.
display.punto=centro_sinistra;
msleep(500);
display.punto= centro_destra;
msleep(500);
//test sul cambio di voce
display.cambio=TRUE; //deve essere visualizzata
//l’etichetta relativa
//test sul lampeggio
display.blink=TRUE;
display.cifraBlink=0; // prima cifra a sinistra
sleep(2); //attesa di 2 sec.
display.cifraBlink=1; // seconda cifra
sleep(2);
display.cifraBlink=2; // terza cifra
sleep(2);
display.cifraBlink=3; // quarta cifra
sleep(2);
display.cifraBlink=4; // quinta cifra
sleep(2);
display.cifraBlink=5; // punto in tutte le posizioni
sleep(2);
display.punto=sinistra;
sleep(2);
display.punto=centro_sinistra;
sleep(2);
display.punto= centro_destra;
sleep(2);
display.cifraBlink=6; // tutte le cifre più il punto
sleep(2);
display.blink=FALSE;
//test sul simbolo di modifica
display.modifica=TRUE;
Capitolo 7 - Test e verifica
152
Il seguente codice di prova serve per verificare altre funzionalità di video: dopo
l’inizializzazione del contesto e il lancio in esecuzione del thread, si verifica la corretta
visualizzazione dei menù dei PID e dei parametri. In particolare, in “menu PID” si
provano le due modalità di lavoro (high-mode e low-mode) mentre in “menu Param” si
verifica la possibilità di modificare il valore di una cifra mentre sta lampeggiando.
int main()
int i;
//inizializzazione delle variabili display e menu
inizializzaDisplay();
caricaMenu();
//lancio del thread video
execi(&video,0,NULL,1,DEFAULT_STACK_SIZE);
// test menu
// 1 menu PID
menu_PID=TRUE; //ingresso nel menu PID
currentPID=0; //PID da visualizzare
// 1.1 high-mode
for (i=1;i<NUMBER_OF_PIDs;i++)
currentPID=i; //scorrimento del menu
sleep(16); //attesa per entrare in low-mode
// 1.2 low-mode
sleep(3);
menu_PID=FALSE; //uscita dal menu PID
// 2 menu Param
//test modifica di cifre che lampeggiano
display.blink=TRUE;
sleep(3);
display.cifraBlink=0; //lampeggio prima cifra a sin.
Capitolo 7 - Test e verifica
153
sleep(2);
display.carattere[0]='7'; //modifica prima cifra a sin.
sleep(3);
display.cifraBlink=1;
sleep(2);
display.carattere[1]='7';
sleep(3);
display.cifraBlink=2;
sleep(2);
display.carattere[2]='7';
sleep(3);
display.cifraBlink=3;
sleep(2);
display.carattere[3]='7';
sleep(3);
display.cifraBlink=4;
sleep(2);
display.carattere[4]='7';
sleep(3);
Per verificare le restanti funzionalità del thread, si implementano altri programmi di
prova, di cui non si riporta il codice per evitare un eccessivo appesantimento della
trattazione. La modalità operativa rimane, comunque, invariata: si eseguono i
programmi di prova sull’RCX e, laddove la scritta sul display si discosta dalla
visualizzazione prevista, si individua un malfunzionamento, cui corrisponde nel codice
un errore da correggere.
Risultati
La campagna di test consente di individuare e correggere vari errori, portando, così,
alla stesura di un codice in cui è verificata la capacità minima del thread di realizzare le
funzionalità richieste. I casi di test implementati, però, non coprono tutta la casistica
necessaria a certificare la robustezza del codice; a livello di test di modulo, tuttavia, si
valuta più oneroso progettare e realizzare una campagna di test più approfondita e
completa, che relegare l’ulteriore verifica in una fase successiva, a livello di test di
sistema. In pratica, una volta accertata la sostanziale correttezza del task video, si è
Capitolo 7 - Test e verifica
154
preferisce testarlo in maniera più approfondita attraverso l’uso dell’applicazione
completa.
2.2.3. Lettura ingressi e scrittura uscite
Le operazioni di lettura degli ingressi e scrittura delle uscite possono essere
considerate su due differenti livelli di astrazione:
- da un punto di vista “software”, si riconducono a due semplici istruzioni: la
macro LIGHT_X (con 3,2,1=X ), che permette di leggere i dati provenienti dal
sensore X, e l’istruzione motor_X_speed, che consente di impostare la potenza
erogata all’organo attuatore (motore X). Trattandosi di operazioni banali, non si
ritiene utile eseguire una campagna di test solo su esse; inoltre, considerando il
fatto che eventuali errori run-time potranno essere rilevati nella successiva fase di
test del sistema, si ritiene sufficiente, a livello di test sui singoli moduli, eseguire
un’ispezione statica del codice.
- da un punto di vista “hardware”, tali operazioni si realizzano, rispettivamente, per
mezzo di una catena di acquisizione ed una di attuazione. Il corretto
funzionamento della catena di attuazione è garantito dal sistema operativo, in
quanto si tratta, in ultima analisi, di impostare la velocità di un motore. Di
conseguenza, si è deciso di eseguire un’indagine più approfondita sul
funzionamento della catena di acquisizione, la cui realizzazione non è stata
affatto semplice ed ha richiesto la costruzione di componenti hardware “ad-hoc”
e l’utilizzo di device non standard. Si rimanda il lettore alla consultazione
dell’Appendice B per quanto riguarda le prove condotte sulla catena di
acquisizione.
3. TEST DI INTEGRAZIONE E DI SISTEMA
Conclusa la fase di test e verifica condotta sui singoli moduli, si passa ora alla fase
d’integrazione, in cui si uniscono tutti i moduli per formare l’applicazione finita, la
quale verrà posta in esecuzione sull’RCX e testata in maniera approfondita al fine di
rilevarne eventuali malfunzionamenti.
Capitolo 7 - Test e verifica
155
3.1. Accesso ai dati
In questo paragrafo si vogliono descrivere i test condotti per accertare il corretto
funzionamento del meccanismo di gestione dei menù, attraverso il quale l’utente possa
accedere, in visualizzazione o in modifica, ai parametri di ciascun regolatore.
In buona sostanza, si vogliono ripercorrere le prove condotte nell’omonima fase a
livello di singolo modulo, questa volta con la finalità di rilevare malfunzionamenti
causati da problemi di interazione/integrazione fra i moduli.
Per realizzare queste prove si è inizializzato un sistema con più anelli di regolazione,
nessuno dei quali, però, viene collegato al relativo processo fisico.
3.1.1. Scorrimento dei menù
Obiettivo
Verificare che il software gestisca in modo opportuno lo scorrimento ciclico dei
menù e che le modalità operative del menù dei PID (high-mode e low-mode) vengano
rispettate.
Modalità operativa
All’avvio dell’applicazione, si fa scorrere (tasto “View”) interamente il menù dei PID
per più volte, per appurare se il completamento di un ciclo determini comportamenti
indesiderati. Verificata la modalità high-mode, non si interviene più sui pulsanti,
ponendosi in attesa che il sistema di visualizzazione entri in low-mode (visualizzazione
dell’etichetta “LOW”). Il passo successivo consiste nel premere un tasto qualsiasi per
riportare il sistema in high-mode, per poi procedere con la selezione (tasto “Prgm”) di
uno dei regolatori disponibili e lo scorrimento (tasto “View”) del menù parametri ad
esso relativo.
Non si attua alcuna modifica; pertanto ci si aspetta di vedere “EXIT” come ultima
voce; la pressione del tasto “Prgm” in corrispondenza di essa deve riportare alla lista dei
regolatori.
Risultati
Nonostante sia la prima attivazione del sistema completo, l’applicazione, aiutata
Capitolo 7 - Test e verifica
156
dalla semplicità delle operazioni, si comporta in modo soddisfacente svolgendo tutte le
attività secondo le previsioni. Va riportato che si riscontra una fastidiosa lentezza nello
scorrimento dei parametri, dovuta ad un tempo di visualizzazione delle etichette troppo
lungo. Pertanto, si interviene riducendo il tempo di esposizione dell’etichetta della voce
di menù selezionata.
3.1.2. Modifica voce numerica
Obiettivo
Verificare che il sistema esegua correttamente la procedura prevista per questo tipo
di parametro (selezione e modifica di ogni singola cifra, impostazione punto decimale),
prestando particolare attenzione agli aspetti di visualizzazione (lampeggio di cifre e
punto decimale).
Modalità operativa
Si seleziona un regolatore, si visualizza un parametro numerico (ad esempio “K”) e si
entra nella modalità di modifica (tasto “Prgm”). A questo punto la prima cifra a sinistra
deve apparire ad intermittenza; su di essa è ora possibile operare modifiche
(tasto”View”) al termine delle quali si passa alla cifra successiva (tasto “Prgm”).
Seguendo questo procedimento si scorrono, facendo lampeggiare una alla volta, tutte le
cinque cifre ed il punto decimale, fino a quando tutti gli elementi del display appaiono
ad intermittenza. A questo punto si conferma il nuovo valore per il parametro. Il
simbolo di modifica, ora, deve essere attivo.
Dopo aver riavviato l’applicazione, si eseguono operazioni analoghe sullo stesso
parametro, stavolta annullando le modifiche apportate. Il simbolo di modifica deve
rimanere disattivato.
Si riavvia nuovamente l’applicazione e si agisce su due distinti parametri numerici
(ad esempio “Ti” e “Td”): per il primo si confermano, mentre per il secondo si
annullano le modifiche.
Risultati
Il test si rivela efficiente, in quanto consente di individuare alcune anomalie che
minano la correttezza del comportamento del sistema. Fortunatamente, a questo punto
Capitolo 7 - Test e verifica
157
dello sviluppo, gli errori individuati nel codice non sono di natura concettuale, ma
semplicemente frutto della distrazione o di dimenticanze, perciò la correzione non
implica il ripensamento di alcuni meccanismi, come, invece, si era temuto in una prima
fase.
3.1.3. Modifica voce lessicale
Obiettivo
Verificare la correttezza della procedura prevista per questo tipo di parametro. Allo
stato attuale, il menù dei parametri contiene una sola voce lessicale: la modalità di
funzionamento. E’ necessario verificare, inoltre, che solo in corrispondenza della
modalità manuale sia possibile modificare il valore della voce CS.
Modalità operativa
Una volta entrati nel contesto di modifica della voce MODE, il sistema deve proporre
due soli valori fra cui scegliere: MAN e AUTO. Trattandosi di un parametro descrittivo
dell’anello di controllo, la modifica risulta immediatamente visibile al regolatore (non è
necessaria la conferma di secondo livello).
In un primo momento, si seleziona la modalità MAN, in corrispondenza di tale
impostazione, si scorre il menù fino a raggiungere la voce CS, che deve essere possibile
modificare (secondo la procedura “modifica di una voce incrementale”, trattata nel
prossimo paragrafo).
La seconda fase consiste nel ritornare alla voce MODE, reimpostare la modalità
AUTO ed in corrispondenza di tale selezione accertare che non sia più possibile
modificare la voce CS.
Risultati
Il test non mette in luce alcun malfunzionamento, il che suggerisce di ripetere le
prove seguendo un diverso ordine nell’eseguire le operazioni. Anche a seguito di queste
nuove procedure non si riscontrano malfunzionamenti. Si affida ai successivi test,
riguardanti un regolatore che interagisce con un processo reale, la verifica definitiva
della selezione della modalità operativa.
Capitolo 7 - Test e verifica
158
3.1.4. Modifica voce incrementale
Obiettivo
Verificare la corretta esecuzione della procedura prevista per questo tipo di
parametro. All’interno della struttura menu sono presenti due sole voci incrementali
(SPi e CS), ciascuna delle quali è caratterizzata da un passo di incremento differente;
pertanto, si vuole valutare la capacità del sistema di impostare il passo di avanzamento
corretto, nonché l’operatività della conferma.
Modalità operativa
Dopo aver selezionato un PID, si accede alla voce SPi e si effettuano operazioni sia
di incremento (tasto “View”) che di decremento (tasto “View” + tasto”Prgm”). Il passo
d’incremento/decremento deve essere unitario.
In seguito, si riavvia l’applicazione, si porta il regolatore in modalità manuale e si
agisce sul parametro CS, verificando che il passo d’incremento/decremento corrisponda
al valore attuale di deltaCSMAN.
Risultati
La prima esecuzione del test rivela una certa difficoltà nelle operazioni di modifica
del valore, in quanto il sistema non reagisce in modo deterministico agli eventi; la
situazione può essere descritta nel seguente modo:
- il semplice incremento unitario del set-point non va sempre a buon fine.
- alcune volte un generico decremento ha, come effetto collaterale, un successivo
incremento del valore.
Essendo sicuri della correttezza individuale di ciascun modulo che interviene
nell’esecuzione di questa procedura, si giunge ad individuare in un problema di
sincronizzazione la fonte dell’anomalia. Infatti, si è dimenticato di racchiudere le
operazioni di accesso alle risorse condivise in un blocco del tipo:
sem_wait(semMenu);
...
...
...
sem_post(semMenu);
Capitolo 7 - Test e verifica
159
A seguito delle modifiche apportate il sistema si comporta correttamente, anche per
quanto riguarda la voce CS, per la quale non si riscontra alcun problema.
3.1.5. Conferma (o annullamento) di secondo livello
Obiettivo
Verificare che:
- in caso di conferma, le modifiche apportate dall’utente diventino operative
(visibili al regolatore);
- in caso di annullamento, per ciascun parametro vengano ripristinati i valori
precedenti alle modifiche.
In entrambi i casi il sistema deve tornare al menù di scelta dei PID.
Modalità operativa
Si seleziona un regolatore, si apportano variazioni ad alcune voci e si confermano i
nuovi valori; a questo punto si accede nuovamente al menù dei parametri e, facendolo
scorrere, si devono leggere i valori precedentemente inseriti. In caso di annullamento, il
sistema deve ripristinare i valori iniziali e nelle variabili puntate da PIDrun e PIDedit
non deve rimanere alcuna traccia delle modifiche.
Risultati
A questo punto del test, quasi tutti i meccanismi legati alla gestione dei dati sono
testati. Quindi è lecito ritenere che l’assenza di anomalie durante la prova sia dovuta alla
sostanziale correttezza del codice in esame.
3.2. Test di carico
Questo test viene eseguito al fine di quantificare la potenza elaborativa dell’RCX. Sia
la progettazione che la successiva implementazione, sono state eseguite tenendo in
particolare considerazione il fatto che l’applicazione fa parte di un sistema “embedded”,
in cui la scarsità di alcune risorse di primaria importanza (ad esempio: tempo CPU,
memoria RAM, …) hanno imposto l’adozione di politiche di ottimizzazione e
risparmio. Esemplare, in tal senso, è il caso del thread video, concepito e strutturato per
Capitolo 7 - Test e verifica
160
minimizzare “l’overhead” relativo alla visualizzazione dei dati.
E’ lapalissiano che l’unico modulo veramente critico dal punto di vista delle
prestazioni temporali è quello che costituisce la realizzazione digitale dell’algoritmo
PID: un mancato rispetto dei vincoli da esso imposti determina l’instabilità dell’intero
sistema. L’attenzione, pertanto, è completamente rivolta al thread executePID, che
esegue in sequenza le tre fasi del ciclo di controllo:
- lettura degli ingressi;
- calcolo della variabile di controllo;
- scrittura delle uscite.
Detto TS il tempo di campionamento determinato in fase di sintesi del regolatore, è
tassativo che il ciclo sopraccitato venga eseguito esattamente ogni TS secondi, pena
l’instabilità e l’imprevedibilità del controllo. Il rispetto di questo vincolo deve
assolutamente essere garantito per ogni regolatore che fa parte del sistema.
Il carico di lavoro complessivo imposto all’RCX è direttamente proporzionale al
numero di regolatori presenti (si ricorda che ad ogni PID corrisponde un thread);
ovviamente, le prestazioni temporali offerte diminuiscono all’aumentare dei regolatori.
Nel seguito si discutono i risultati ottenuti eseguendo la prova di carico in due differenti
situazioni: singolo PID o coppia di PID.
3.2.1. Sistema comprendente un solo PID
In questo paragrafo vengono presentati due differenti test, eseguiti inserendo degli
opportuni “time marker” all’interno della funzione executePID; si ritiene opportuno, ai
fini della chiarezza espositiva, riproporre una schematizzazione di tale funzione:
void executePID()
//Inizializzazione
...
//Ciclo di controllo
while(1)
//1a fase: lettura ingressi
...
//2a fase: calcolo variabile di controllo
...
//3a fase: scrittura uscite
Capitolo 7 - Test e verifica
161
...
//Funzione WAIT_EVENT che temporizza l’esecuzione
//del PID
...
3.2.1.1. Test A: tempo di esecuzione
Obiettivo
Misurare il lasso di tempo che intercorre fra l’istante di inizio di lettura degli ingressi
e l’istante finale di scrittura delle uscite; in altre parole si vuole sapere quanto durano,
nel complesso, le tre fasi del ciclo (escluso il tempo di attesa imposto dalla funzione
wait_event).
Modalità operativa
Si definisce la variabile START (di tipo time_t) e vi si memorizza l’istante temporale
di inizio ciclo; prima dell’invocazione di wait_event si calcola la differenza fra sys_time
(macro definita in BrickOS che fornisce l’istante di tempo attuale) e START.
Risultati
Numerose esecuzioni del test portano a stabilire che il tempo richiesto per la sola
elaborazione dei dati (prime tre fasi del ciclo) può essere quantificato in 10-11ms.
3.2.1.2. Test B: periodo di campionamento
Obiettivo
Verificare che venga rispettato il periodo di campionamento imposto dal regolatore
ed individuare il valore limite di TS che il sistema è in grado di rispettare (TSmin)
Modalità operativa
Si intende contare il numero di cicli completi eseguiti nell’arco di un secondo da
parte del sistema; se il tempo di campionamento TS è effettivamente rispettato, allora il
valore ottenuto deve essere pari a quello teorico, così calcolato:
][][1000
msTms
S
Capitolo 7 - Test e verifica
162
La realizzazione del conteggio richiede semplicemente una variabile intera, inizializzata
a zero ed incrementata di un’unità all’inizio di ciascun ciclo; un “time-marker” permette
di bloccare il conteggio quando è trascorso 1 secondo dall’inizio del primo ciclo.
La prova ha inizio impostando la frequenza di campionamento pari al valore minimo
applicabile che, con la risoluzione attuale, è di 0.001 sec. Se tale valore è rispettato si
individua anche il limite di TS; altrimenti si calcola, sulla base del numero di cicli
compiuti, il valore di TSmin. In seguito, ulteriori prove vengono ripetute per confermare
la correttezza del dato calcolato.
Risultati ottenuti
Nelle condizioni di carico stabilite, l’RCX garantisce l’esecuzione di un massimo di
40 cicli al secondo, corrispondenti ad un periodo di campionamento pari a 25ms. Quelle
appena citate devono essere considerate prestazioni limite; per ragioni di sicurezza e
correttezza nell’elaborazione dei dati si ritiene opportuno considerare il valore di 30ms
come limite inferiore al periodo di campionamento.
3.2.2. Sistema comprendente due PID
Per effettuare il test di carico in questa nuova situazione, si possono seguire le
modalità operative appena descritte. La conclusione che se ne trae non è, purtroppo,
confortante; infatti, le prestazioni temporali offerte subiscono un brusco declino: per
evitare un sovraccarico del sistema, il limite inferiore al tempo di campionamento è pari
a circa 80ms per entrambi i regolatori. Questo, ovviamente, costituisce una forte
limitazione alle applicazioni pratiche effettivamente realizzabili.
3.3. Test finale di regolazione
L’obiettivo di questo test è quello di verificare se l’applicazione, nel suo complesso,
dispone di tutti i requisiti che caratterizzano un buon regolatore digitale: affidabilità,
sicurezza, robustezza, usabilità ed ergonomicità dell’interfaccia utente.
Nel capitolo dedicato all’implementazione, si è già accennato al fatto che si vuole
realizzare, come “banco di prova” del prodotto sviluppato, un anello per il controllo
della velocità di rotazione di un motore, al quale è applicata un’elica simmetrica rispetto
Capitolo 7 - Test e verifica
163
al suo asse; un secondo motore, coassiale al precedente, viene utilizzato come dinamo
tachimetrica.
Il sistema può essere schematizzato nel modo seguente:
Il test si snoda nelle seguenti fasi:
- taratura PID su RCX;
- risposta a variazioni a scalino del set-point;
- applicazione disturbi di carico ed analisi del comportamento;
- funzionamento in modalità manuale.
A conferma dei risultati ottenuti, si presentano alcuni grafici ottenuti dal rilevamento dei
valori assunti dalla variabile controllata; le componenti di rumore ed i disturbi introdotti
dalla catena di acquisizione impediscono una lettura “pulita” dei segnali, per cui gli
andamenti presentati sono affetti da evidenti “perturbazioni”.
Infine, si precisa che i grafici ed i relativi commenti hanno il solo scopo di
dimostrare la correttezza delle elaborazioni eseguite dal sistema; ulteriori dissertazioni
sulle dinamiche di processo e sull’opportunità di effettuare interventi volti ad un
affinamento del controllo esulano dagli scopi di questo capitolo.
3.3.1. Prima fase: taratura del regolatore
Per realizzare il controllo di velocità sopra descritto, si utilizza un regolatore PI, la
cui taratura ha, essenzialmente, base empirica:
Capitolo 7 - Test e verifica
164
Parametro Valore
K 3.00
TI 0.30
TD 0.00
b 0.50
TS 0.03
3.3.2. Seconda fase: variazione a scalino del set-point
Dopo aver impostato correttamente i parametri del regolatore, si impone una variazione
a scalino al set-point, portando il segnale di riferimento ad un valore pari a 100 (si
ricorda che tale parametro può variare nel range 0…MAXSP); la risposta del sistema è
sintetizzata dal grafico seguente (sull’asse delle ascisse è riportato il tempo, mentre
sull’asse delle ordinate la process-value):
Il controllo reagisce, come previsto, aumentando la potenza erogata al motore e questo
determina un aumento della velocità di rotazione. Se, una volta giunti a regime, si
riporta a 0 il set-point, si osserva un nuovo transitorio, al termine del quale l’elica risulta
essere priva di movimento:
Capitolo 7 - Test e verifica
165
3.3.3. Terza fase: disturbi di carico
In questo paragrafo si vuole studiare il comportamento del sistema in presenza di due
differenti disturbi di carico (ciascuno applicato separatamente): aumento dell’attrito e
disturbo sinusoidale.
3.3.3.1. Aumento dell’attrito
Si imposta il set-point al valore 60 e si attende che il sistema giunga a regime:
A questo punto, utilizzando un opportuno meccanismo ad ingranaggi, si simula un
aumento della forza di attrito che si oppone al moto di rotazione; il controllo risponde al
disturbo aumentando la potenza erogata al motore. L’attrito è tale da portare in
saturazione il regolatore; pertanto, la velocità effettiva non raggiunge il valore di
riferimento attualmente impostato:
Infine, rimuovendo il disturbo si dà vita ad un nuovo transitorio che riporta il sistema
nella situazione iniziale:
Capitolo 7 - Test e verifica
166
3.3.3.2. Disturbo sinusoidale
Come nel caso precedente, si imposta il segnale di riferimento al valore 100. A
questo punto si applica una zavorra ad una delle due estremità dell’elica; ciò da origine
ad un disturbo di carico sinusoidale:
Nella parte del percorso circolare in cui la forza di gravità favorisce la rotazione, il
regolatore deve diminuire la potenza erogata al motore, per evitare che il moto subisca
un’accelerazione indesiderata; nella parte rimanente del circuito, invece, il regolatore
deve aumentare la potenza in uscita per contrastare l’effetto della gravità ed evitare,
così, una decelerazione dell’elica. Alla luce di queste considerazioni, si comprende
l’andamento della variabile controllata:
Capitolo 7 - Test e verifica
167
3.3.4. Quarta fase: funzionamento in modalità manuale
Sino ad ora si è analizzato il funzionamento del regolatore in modalità automatica; in
questa fase, si vuole studiare la situazione in cui l’operatore, impostando la modalità
manuale, interviene sul processo tramite un’azione diretta sulla variabile di controllo.
Il test si suddivide in due parti:
- analisi delle commutazioni A/M e M/A;
- controllo del processo in manuale.
3.3.4.1. Commutazioni A/M e M/A «bumpless»
Anzitutto si deve appurare se le commutazioni manuale/automatico ed
automatico/manuale avvengono realmente senza “salti”, così come stabilito nelle
specifiche di progetto. Per rispondere a questo interrogativo è sufficiente operare nel
seguente modo:
- con il regolatore in automatico, si impone una variazione a scalino del set-point,
si attende che il sistema giunga a regime e si passa in modalità manuale; da
questo momento in poi, a meno di un intervento da parte dell’operatore, la
variabile di controllo deve rimanere bloccata al valore che essa aveva nell’istante
in cui è avvenuta la commutazione;
- a partire da questa situazione, l’operatore impone una variazione alla variabile di
controllo (ma lascia immutato il set-point) e verifica che essa abbia effetto sul
processo. Infine, si riporta il regolatore in automatico, prestando attenzione al
fatto che il controllo non subisca brusche variazioni. Al termine del transitorio
innescato dalla commutazione, la variabile controllata avrà raggiunto
nuovamente il set-point.
I dati emersi dall’esecuzione del test permettono di concludere che le commutazioni
della modalità di funzionamento avvengono nel pieno rispetto delle specifiche.
3.3.4.2. Controllo del processo
Si tratta di una prova molto semplice, volta ad accertare che le variazioni imposte
dall’operatore alla variabile di controllo abbiano realmente effetto sul processo. Dalla
semplice osservazione del sistema, risulta evidente che incrementando o decrementando
il valore del parametro CS si verifica, rispettivamente, un incremento o un decremento
Capitolo 7 - Test e verifica
168
della velocità di rotazione. Questa percezione trova, poi, conferma nelle misurazioni
effettuate lungo la catena di acquisizione e di attuazione per alcuni valori campione di
CS.
3.3.5. Osservazioni
A conclusione del test finale di regolazione si vogliono riassumere i principali
risultati ottenuti:
- l’applicazione aderisce in misura soddisfacente alle specifiche di progetto e
risponde in modo adeguato ai requisiti “formulati dall’utente”;
- il modulo di controllo appare affidabile e robusto. All’interno dei limiti
individuati è sempre garantito il rispetto dei vincoli temporali;
- l’interfaccia utente raggiunge un buon livello di ergonomicità ed usabilità.
In estrema sintesi, il prodotto sviluppato può essere considerato un “buon regolatore”
per alcune applicazioni di interesse pratico; è doveroso aggiungere che:
- allo stato attuale è stato implementato un insieme minimo di funzionalità; nel
prossimo futuro il sistema sarà arricchito con una funzione di autotaratura e si
pensa già ad un possibile collegamento in rete fra più RCX.
- il termine “buon regolatore” deve essere contestualizzato nello scenario delle
limitazioni imposte dall’uso combinato di BrickOS e RCX.
4. PROBLEMI NOTI
Si ritiene opportuno, a chiusura di questo capitolo, segnalare alcuni problemi rilevati
durante l’esecuzione dei vari test, per i quali non è ancora stata trovata una soluzione
efficace:
- MODIFICA PARAMETRI
! Frequenza: circa 1 caso su 100 (solo nel caso di singolo anello di controllo)
! Descrizione: all’atto della conferma di primo livello delle modifiche
apportate ad uno dei parametri che descrivono il regolatore, il sistema inizia
a comportarsi in modo anomalo; in particolare, l’azione di controllo sembra
perdere parte della sua efficacia, pur continuando a gestire il processo in
Capitolo 7 - Test e verifica
169
maniera accettabile. Il problema cessa di esistere all’atto della conferma di
secondo livello.
I parametri che descrivono l’anello di controllo non sono affetti da questo
tipo di anomalia.
! Azioni intraprese: non è ancora stato possibile identificare uno scenario
preciso, ovvero un insieme di condizioni al di sotto delle quali si verifica il
fenomeno; ciò è principalmente dovuto alla sua sporadicità. Le attività di
debug e di analisi statica del codice non hanno portato, sino ad ora, risultati
degni di nota.
- SCORRIMENTO MENU’ PARAMETRI
! Descrizione: si consideri il menù dei parametri per un generico regolatore;
se l’utente preme ripetutamente il tasto KEY_1 senza rispettare i tempi di
visualizzazione di ogni singola voce, il display entra in uno stato di
inconsistenza sino a quando tutti gli eventi generati dall’utente attraverso la
pressione del tasto non sono stati “consumati” dal sistema.
! Azioni intraprese: l’introduzione delle primitive di sincronizzazione non ha
permesso di risolvere il problema; esiste la possibilità di inibire gli eventi
nel passaggio da una voce all’altra, ma, data la struttura implementativa, si
tratta di una soluzione poco praticabile.
- FUNZIONAMENTO CONTINUO
! Descrizione: a causa di un bug presente in BrickOs, l’applicazione può
funzionare ininterrottamente per un periodo massimo di 49.7 giorni,
dopodiché l’RCX va in crash e il sistema deve essere riavviato.
5. APPENDICE A: funzione PID
5.1. Test su PC
5.1.1. L’applicazione
Anzitutto si ritiene opportuno presentare gli aspetti essenziali del codice che ha
Capitolo 7 - Test e verifica
170
permesso di verificare il corretto funzionamento dell’algoritmo di controllo.
Inizialmente il set-point viene impostato a 5.0; quando il sistema arriva a regime, si
riporta il segnale di riferimento a 0.
#include<stdio.h>
#include<conio.h>
#include<math.h>
#define TRUE 1
#define FALSE 0
typedef float REAL;
typedef unsigned char BOOL;
typedef struct ... PIDPARAMS;
typedef struct ... PIDLOOPDATA;
//Variabili globali
PIDPARAMS thePID;
PIDLOOPDATA theLOOP;
REAL vprec = 0;
//Funzione di controllo (vedi capitolo 6)
void PID(PIDPARAMS* R, PIDLOOPDATA* L)
...
//Funzione che simula la rete RC
void processo(REAL controlSignal)
REAL v;
v = (vprec*0.3 + controlSignal*0.03)/(0.03 + 0.3);
vprec = v;
theLOOP.PV = v;
void main(void)
int i = 0;
//Inizializzazione PID
Capitolo 7 - Test e verifica
171
thePID.K = 1.0;
thePID.Ti = 0.3;
thePID.Td = 0.0;
thePID.N = 5.0;
thePID.b = 0.5;
thePID.c = 0.0;
thePID.Ts = 0.03;
thePID.CSmax = 100.0;
thePID.CSmin = 0.0;
thePID.deltaCSMAN = 0.5;
theLOOP.SP = 0.0;
theLOOP.SPold = 0.0;
theLOOP.PV = 0.0;
theLOOP.PVold = 0.0;
theLOOP.CS = 0.0;
theLOOP.CSold = 0.0;
theLOOP.Dold = 0.0;
theLOOP.MAN = FALSE;
theLOOP.MANinc = FALSE;
theLOOP.MANdec = FALSE;
theLOOP.HIsat = FALSE;
theLOOP.LOsat = FALSE;
theLOOP.NoInc = FALSE;
theLOOP.NoDec = FALSE;
theLOOP.ForceMAN = FALSE;
clrscr();
//I ciclo di controllo: SP=5.0
theLOOP.SP = 5.0;
for(i=1;i<=100;i++)
PID(&thePID,&theLOOP);
processo(theLOOP.CS);
printf("\n%d:%f %f",i,theLOOP.PV,theLOOP.CS);
Capitolo 7 - Test e verifica
172
//II ciclo di controllo: SP=0
theLOOP.SP = 0.0;
for(i=1;i<=100;i++)
PID(&thePID,&theLOOP);
processo(theLOOP.CS);
printf("\n%d: %f %f",i,theLOOP.PV,theLOOP.CS);
5.1.2. I dati rilevati
Passo di campionamento Set-Point
Control Signal (CS)
Process Value (PV)
1 5 3,000000 0,272727 2 5 3,200000 0,538843 3 5 3,380000 0,797130 4 5 3,542000 1,046664 5 5 3,687800 1,286767 6 5 3,819020 1,516972 7 5 3,937118 1,736985 8 5 4,043406 1,946660 9 5 4,139066 2,145969
10 5 4,225159 2,334987 11 5 4,302643 2,513865 12 5 4,372379 2,682820 13 5 4,435141 2,842122 14 5 4,491627 2,992077 15 5 4,542464 3,133022 16 5 4,588217 3,265312 17 5 4,629395 3,389320 18 5 4,666456 3,505423 19 5 4,699811 3,614004 20 5 4,729830 3,715442 21 5 4,756847 3,810116 22 5 4,781162 3,898392 23 5 4,803046 3,980634 24 5 4,822742 4,057189 25 5 4,840467 4,128396 26 5 4,856421 4,194580 27 5 4,870779 4,256052 28 5 4,883701 4,313111 29 5 4,895331 4,366040 30 5 4,905798 4,415109 31 5 4,915218 4,460574
Capitolo 7 - Test e verifica
173
32 5 4,923697 4,502676 33 5 4,931327 4,541644 34 5 4,938194 4,577693 35 5 4,944375 4,611028 36 5 4,949937 4,641838 37 5 4,954944 4,670302 38 5 4,959449 4,696589 39 5 4,963504 4,720854 40 5 4,967154 4,743245 41 5 4,970438 4,763898 42 5 4,973394 4,782943 43 5 4,976055 4,800499 44 5 4,978449 4,816676 45 5 4,980605 4,831579 46 5 4,982544 4,845303 47 5 4,984289 4,857938 48 5 4,985860 4,869567 49 5 4,987274 4,880268 50 5 4,988547 4,890111
[omissis] 90 5 4,999830 4,996818 91 5 4,999847 4,997093 92 5 4,999863 4,997345 93 5 4,999876 4,997575 94 5 4,999888 4,997786 95 5 4,999899 4,997978 96 5 4,999909 4,998153 97 5 4,999918 4,998314 98 5 4,999927 4,998460 99 5 4,999934 4,998594
100 5 4,999941 4,998717
Variazione a scalino del set-point
101 0 1,999947 4,726101 102 0 1,799952 4,460088 103 0 1,619957 4,201894 104 0 1,457961 3,952446 105 0 1,312165 3,712420 106 0 1,180948 3,482286 107 0 1,062853 3,262338 108 0 0,956568 3,052722 109 0 0,860911 2,853467 110 0 0,774820 2,664499 111 0 0,697338 2,485666 112 0 0,627604 2,316751 113 0 0,564844 2,157487 114 0 0,508360 2,007566 115 0 0,457524 1,866653 116 0 0,411771 1,734391
Capitolo 7 - Test e verifica
174
117 0 0,370594 1,610410 118 0 0,333535 1,494330 119 0 0,300181 1,385771 120 0 0,270163 1,284352 121 0 0,243147 1,189697 122 0 0,218832 1,101437 123 0 0,196949 1,019211 124 0 0,177254 0,942669 125 0 0,159529 0,871475 126 0 0,143576 0,805302 127 0 0,129218 0,743840 128 0 0,116296 0,686791 129 0 0,104667 0,633870 130 0 0,094200 0,584809 131 0 0,084780 0,539352 132 0 0,076302 0,497257 133 0 0,068672 0,458294 134 0 0,061805 0,422250 135 0 0,055624 0,388920 136 0 0,050062 0,358115 137 0 0,045056 0,329655 138 0 0,040550 0,303373 139 0 0,036495 0,279111 140 0 0,032846 0,256723 141 0 0,029561 0,236072 142 0 0,026605 0,217030 143 0 0,023944 0,199476 144 0 0,021550 0,183301 145 0 0,019395 0,168401 146 0 0,017455 0,154678 147 0 0,015710 0,142045 148 0 0,014139 0,130417 149 0 0,012725 0,119718 150 0 0,011453 0,109876
[omissis] 190 0 0,000169 0,003182 191 0 0,000152 0,002906 192 0 0,000137 0,002654 193 0 0,000123 0,002424 194 0 0,000111 0,002214 195 0 0,000100 0,002022 196 0 0,000090 0,001846 197 0 0,000081 0,001686 198 0 0,000073 0,001539 199 0 0,000066 0,001405 200 0 0,000059 0,001283
Capitolo 7 - Test e verifica
175
5.1.3. Il grafico
0,000000
1,000000
2,000000
3,000000
4,000000
5,000000
6,000000
1 10 19 28 37 46 55 64 73 82 91 100 109 118 127 136 145 154 163 172 181 190 199
Passo di campionamento
PVCSSP
Il grafico, che riassume efficacemente i dati presentati in tabella, evidenzia un
funzionamento corretto dell’algoritmo di controllo. Ulteriori commenti sulla dinamica
del processo e sull’andamento dei segnali esulano dagli scopi di questa trattazione.
Il codice completo dell’applicazione, la tabella dei dati ed il grafico sono disponibili
nella cartella TEST_PID contenuta nel CD-ROM allegato.
5.2. Test su RCX
5.2.1. L’applicazione
Per verificare il funzionamento dell’algoritmo di controllo sull’RCX, è sufficiente
inserire nel codice la funzione processo vista in precedenza e modificare la funzione
executePID nel seguente modo:
void executePID()
...
while (1)
//Simulazione processo
PID(PIDrun[0],&PIDloop[0]);
Capitolo 7 - Test e verifica
176
processo(PIDloop[0].CS);
//Questo ritardo è inserito al fine di consentire la //lettura dei valori di PV e CS dal display dell'RCX
msleep(3000);
5.2.2. I dati rilevati
E’ quasi superfluo dire che dal display dell’RCX si leggono gli stessi dati rilevati
durante la simulazione su PC, infatti:
- le funzioni PID e processo, che rappresentano, rispettivamente, l’equivalente
digitale del regolatore e della rete RC, sono assolutamente deterministiche: a
fronte degli stessi dati in ingresso propongono sempre gli stessi dati in uscita;
- non essendoci catene di acquisizione e di attuazione, le due funzioni si scambiano
valori esatti, ovvero privi di quegli errori e di quelle imprecisioni che fatalmente
affliggono ogni misura di un fenomeno fisico.
Per completezza, si precisa che i valori prodotti da questa simulazione hanno una
risoluzione minore rispetto a quelli prodotti dal PC; infatti, l’RCX può visualizzare un
massimo di quattro cifre decimali.
6. APPENDICE B: catena di acquisizione
Per verificare il corretto funzionamento della catena di acquisizione si introduce una
prova specifica al di fuori della campagna condotta sull’intero sistema. Il principale
obiettivo, oltre che rilevare malfunzionamenti, è la raccolta di dati utili al fine di
valutare la bontà del meccanismo realizzato per l’acquisizione della velocità di
rotazione del motore.
6.1. Descrizione del test
Si utilizza un semplice processo costituito da due motori calettatti sullo stesso asse:
uno assume funzione di motore, l’altro funzione di dinamo tachimetrica. Trascurando la
Capitolo 7 - Test e verifica
177
resistenza opposta dalla dinamo, si può considerare che il motore evolva senza carico.
Per il controllo di tale processo si è implementato il seguente programma di prova:
int main()
/* INIZIALIZZAZIONI */
int vel=0;
ds_active(&SENSOR_1);
motor_a_dir(fwd);
motor_a_speed(vel);
/* LANCIO DEL THREAD DI VISUALIZZAZIONE */
execi(&leggi,0,NULL,1,DEFAULT_STACK_SIZE);
while(1)
wait_event(&push,0); //ATTESA PRESSIONE TASTO
vel++; // INCREMENTO VELOCITA’
lcd_int(vel); // SCRITTURA VELOCITA’ MOTORE
msleep(300); // ATTESA DI 300 msec.
cls(); // PULIZIA DISPLAY
wait_event(&pull,0); //ATTESA RILASCIO TASTO
motor_a_speed(vel); //IMPOSTAZIONE VELOCITA’
/* FUNZIONE CHE RILEVA LA PRESSIONE DEL TASTO VIEW */
wakeup_t push(wakeup_t data)
return PRESSED(dbutton(),BUTTON_VIEW);
/* FUNZIONE CHE RILEVA IL RILASCIO DEL TASTO VIEW */
wakeup_t pull(wakeup_t data)
return RELEASED(dbutton(),BUTTON_VIEW);
/* THREAD CHE SI OCCUPA DI LEGGERE E VISUALIZZARE IL VALORE MISURATO DAL SENSORE */
void leggi()
Capitolo 7 - Test e verifica
178
while(1)
lcd_int(LIGHT_1);
msleep(500);
Il programma consente, ad ogni pressione del tasto VIEW, d’incrementare
unitariamente il valore corrispondente alla potenza applicata al motore (inizializzata a 0
all’avvio dell’applicazione). Durante il normale funzionamento, la misura della velocità
di rotazione viene visualizzata sul display; solo durante la pressione del tasto viene
scritto il valore della potenza erogata al motore.
Il test consiste nell’applicare al motore tutti i possibili valori di potenza (0÷255) e, in
corrispondenza di ciascuno di essi, rilevare la misura riportata sul display dell’RCX ed
il valore di tensione prelevato in uscita dal filtro RC del circuito di condizionamento. I
dati raccolti sono riportati nel successivo paragrafo.
6.2. Risultati del test
Potenza Vf [V] Misura
0 0 95 1 0 95 2 0 95 3 0 95 4 0,1 94 5 0,2 93 6 0,4 87 7 0,5 81 8 0,6 77 9 1 70
10 1,1 65 11 1,5 62 12 1,6 57 13 1,7 55 14 1,9 52 15 2 50 16 2,1 48 17 2,2 46 18 2,3 45 19 2,3 44 20 2,5 41
21 2,5 40 22 2,5 39 23 2,6 36 24 2,7 36 25 2,8 36 26 2,8 35 27 2,8 35 28 2,8 35 29 2,8 35 30 2,8 35 31 2,8 35 32 2,9 33 33 3 33 34 3 33 35 3 33 36 3 33 37 3,1 32 38 3,2 25 39 3,3 20 40 3,3 20 41 3,3 20 42 3,5 19
43 3,5 19 44 3,5 19 45 3,5 18 46 3,5 17 47 3,5 17 48 3,5 17 49 3,5 16 50 3,5 16 51 3,6 15 52 3,6 15 53 3,6 15 54 3,7 14 55 3,7 14 56 3,7 14 57 3,7 14 58 3,8 12 59 3,8 12 60 3,8 12 61 3,8 12 62 3,8 11 63 3,8 11 64 3,8 11
Capitolo 7 - Test e verifica
179
65 3,9 11 66 3,9 11 67 3,9 11 68 3,9 10 69 3,9 10 70 3,9 10 71 3,9 10 72 4 9 73 4 9 74 4 9 75 4 9 76 4 9 77 4 9 78 4 9 79 4 9 80 4 9 81 4 9 82 4 9 83 4,1 7 84 4,1 7 85 4,1 7 86 4,1 7 87 4,1 7 88 4,1 7 89 4,1 7 90 4,1 7 91 4,1 7 92 4,1 7 93 4,1 7 94 4,1 7 95 4,1 7 96 4,1 6 97 4,1 6 98 4,1 6 99 4,1 6 100 4,1 6 101 4,1 6 102 4,1 6 103 4,1 6 104 4,1 5 105 4,1 5 106 4,1 5 107 4,2 5 108 4,2 5 109 4,2 5 110 4,2 5 111 4,2 5 112 4,2 5 113 4,2 5 114 4,2 5 115 4,2 5
116 4,2 5 117 4,2 5 118 4,2 5 119 4,2 5 120 4,2 5 121 4,2 5 122 4,2 5 123 4,2 5 124 4,2 5 125 4,2 5 126 4,2 5 127 4,2 5 128 4,2 5 129 4,2 5 130 4,2 5 131 4,2 5 132 4,2 5 133 4,2 5 134 4,2 5 135 4,2 4 136 4,2 4 137 4,2 4 138 4,2 4 139 4,2 4 140 4,2 4 141 4,2 4 142 4,2 4 143 4,3 4 144 4,3 4 145 4,3 4 146 4,3 4 147 4,3 4 148 4,3 4 149 4,3 4 150 4,3 4 151 4,3 4 152 4,3 4 153 4,3 4 154 4,3 4 155 4,3 4 156 4,3 4 157 4,3 4 158 4,3 4 159 4,3 4 160 4,3 4 161 4,3 4 162 4,3 4 163 4,3 4 164 4,3 4 165 4,3 4 166 4,3 4
167 4,3 4 168 4,3 4 169 4,3 4 170 4,3 4 171 4,3 4 172 4,3 4 173 4,3 4 174 4,3 4 175 4,3 4 176 4,3 4 177 4,3 4 178 4,3 4 179 4,3 4 180 4,3 4 181 4,3 4 182 4,3 4 183 4,3 4 184 4,3 4 185 4,3 4 186 4,3 4 187 4,3 4 188 4,3 4 189 4,3 4 190 4,3 4 191 4,3 4 192 4,3 4 193 4,3 4 194 4,3 4 195 4,3 4 196 4,3 4 197 4,3 4 198 4,3 4 199 4,3 4 200 4,3 4 201 4,3 4 202 4,4 3 203 4,4 3 204 4,4 3 205 4,4 3 206 4,4 3 207 4,4 3 208 4,4 3 209 4,4 3 210 4,4 3 211 4,4 3 212 4,4 3 213 4,4 3 214 4,4 3 215 4,4 3 216 4,4 3 217 4,4 3
Capitolo 7 - Test e verifica
180
218 4,4 3 219 4,4 3 220 4,4 3 221 4,4 3 222 4,4 3 223 4,4 3 224 4,4 3 225 4,4 3 226 4,4 3 227 4,4 3 228 4,4 3 229 4,4 3 230 4,4 3
231 4,4 3 232 4,4 3 233 4,4 3 234 4,4 3 235 4,4 3 236 4,4 3 237 4,4 3 238 4,4 3 239 4,4 3 240 4,4 3 241 4,4 3 242 4,4 3 243 4,4 3
244 4,4 3 245 4,4 3 246 4,4 3 247 4,4 3 248 4,4 3 249 4,4 3 250 4,4 3 251 4,4 3 252 4,4 3 253 4,4 3 254 4,4 3 255 4,4 3
Il seguente grafico esprime la relazione fra la potenza applicata al motore e la
velocità acquisita attraverso la catena degli input.
0102030405060708090
100
0 50 100 150 200 250 300Potenza Applicata
Mis
ura
Acq
uisi
ta
Dall’osservazione dei dati raccolti emerge, in maniera evidente, un fatto già
introdotto nel Capitolo 3: attraverso la funzione motor_X_speed(int speed) non si
imposta direttamente un valore di velocità di rotazione, ma bensì la potenza applicata
dal motore. Il grafico aiuta a mettere in evidenza questo fatto; in esso, infatti, si può
vedere che per valori bassi di potenza, a fronte di piccole variazioni della potenza
Capitolo 7
181
applicata, si riscontrano significative variazioni della velocità rilevata, mentre
significativi incrementi, applicati a valori di potenza elevati, non producono rilevanti
variazioni nella misura di velocità. Tale andamento è reso ancor più evidente dal fatto
che al motore è applicato un carico ridotto.
Per concludere, si può affermare che, anche se le misure realizzate non sono
particolarmente stabili, complessivamente la catena di acquisizione soddisfa le esigenze
progettuali. Bisogna, infatti, tenere presente che il sistema di misura della velocità,
realizzato per questo progetto, è unico nell’ambiente BrickOS.
Capitolo 8
182
Capitolo 8
Conclusioni
Capitolo 8
183
1. CONCLUSIONI
Alla luce degli esiti dei test e di ulteriori prove, svolte al di fuori della campagna di
verifica e convalida, si possono considerare raggiunti gli obiettivi iniziali del progetto. Il
sistema realizzato ha dimostrato di poter regolare in maniera soddisfacente un processo
reale, fornendo un’interfaccia utente che, pur essendo assai spartana, risulta essere
efficiente. Inoltre, gli accorgimenti implementativi, adottati nella realizzazione
dell’applicazione, consentono alla stessa di poter essere sviluppata ulteriormente
attraverso l’aggiunta di nuove funzionalità (ad esempio autotuning) e di poter essere
inserita all’interno di un sistema di controllo più vasto, costituito da una rete di
dispositivi RCX.
Il prodotto realizzato, ovviamente, non può, e non deve, essere posto a diretto
confronto con prodotti “professionali” analoghi, a causa delle forti limitazioni introdotte
dal supporto hardware, ma soprattutto perché non è questa la finalità per cui è stato
realizzato. In realtà, più che il prodotto stesso (unico nell’ambiente RCX-BrickOS),
sono le attività di progettazione e realizzazione del regolatore ad assumere importanza,
in quanto hanno costituito per gli autori una valida opportunità per mettere in pratica le
conoscenze acquisite nel loro percorso formativo, rivelandosi un importante momento
di apprendimento e crescita. Sotto questo punto di vista, il progetto descritto assume
grande importanza, in particolare tenendo conto del carattere fortemente
interdisciplinare delle attività svolte, che spaziano dall’ingegneria del software
(organizzazione e gestione metodica di un progetto) sino all’automazione (fondamenti e
tecnologia dei sistemi di controllo), passando attraverso argomenti diversi tra loro quali:
sistemi operativi (sincronizzazione interprocessuale e gestione delle risorse condivise)
ed elettronica (progettazione e costruzione di un circuito di condizionamento).
A fronte di tutte le valutazioni precedenti, non si può che valutare in maniera
positiva il progetto, il che è vero, a maggior ragione, se si considera il fatto che esso
rientra di diritto in una vivace comunità ricca di spunti come quella di BrickOS, oltre
che nell’ambito accademico. Pertanto, il lavoro svolto si propone anche come un punto
di appoggio per nuovi progetti, nella speranza che siano anch’essi così proficui.
Bibliografia
184
Bibliografia
Bibliografia
185
1. Testi
[1] A. Leva, “Introduzione al PID industriale”, aprile 2000.
[2] G. Magnani, “Tecnologie dei sistemi di controllo”, Italia, McGraw-
Hill, marzo 2000.
[3] C. Ghezzi, M. Jazayeri, “Programming language concepts”, USA,
John Wiley & Sons, 1998.
[4] W. Stallings, “Sistemi Operativi”, Jackson Libri.
[5] A. Fuggetta, C. Ghezzi, S. Morasca, A. Morzenti, M. Pezzè,
“Ingegneria del software”, Italia, Mondadori Informatica.
2. Siti web e documentazione
[6] LegOS Homepage, http://legOS.sourceforge.net
[7] Luis Villa, “The LegOS HOWTO”, http://legos.sourceforge.net/HOWTO
! versione .ps: http://legos.sourceforge.net/HOWTO/HOWTO.ps
[8] S. Nilsson, “Introduction to the LegOS Kernel”,
! versione .ps:
http://www.docs.uu.se/docs/undergrad/instances/spring2001/rt_systems_dvp
_mnp/assignments/intro_to_LegOS_kernel.ps
! versione .pdf:
http://www.docs.uu.se/docs/undergrad/instances/spring2001/rt_systems_dvp
_mnp/assignments/intro_to_LegOS_kernel.pdf
[9] LegOS Command Reference 10-0.2.4,
http://legos.sourceforge.net/docs/CommandRef.html
[10] Gruppo di discussione Lugnet (Lego User's Group NETwork),
http://www.lugnet.com
[11] K. Proudfoot, “RCX Internals”, Technical report,
http://graphics.stanford.edu/~kekoa/rcx, 1998-1999.
Bibliografia
186
[12] M. K. Christiansen, M. H. Pedersen, T. Glaesner, “Solving the
priority inversion problem in legOS”, AUC, 2000
[13] Hitachi Single-Chip Microcomputer H8/3297 Series hardware
manual.
[14] Raccolta progetti hardware per RCX,
http://www.plazaearth.com/usr/gasperi/lego.htm
Sommario
187
Sommario
Sommario
188
Capitolo 1 __________________________________________________________ 1
1. IL PROGETTO_____________________________________________________ 2
2. ORGANIZZAZIONE DEL TESTO ____________________________________ 4 2.1. Cenni teorici sul controllo PID ___________________________________________ 4 2.2. Ambiente operativo ____________________________________________________ 4 2.3. Descrizione del progetto ________________________________________________ 4
Capitolo 2 __________________________________________________________ 6
1. INTRODUZIONE___________________________________________________ 7
2. LEGGE DI CONTROLLO PID _______________________________________ 8 2.1. Azione proporzionale___________________________________________________ 8 2.2. Azione integrale_______________________________________________________ 9 2.3. Azione derivativa______________________________________________________ 9
3. ACCORGIMENTI IMPLEMENTATIVI ______________________________ 10 3.1. Derivatore reale ______________________________________________________ 10 3.2. Forma standard ISA ___________________________________________________ 11
3.2.1. Risposta al Set-Point______________________________________________ 12 3.2.2. Risposta al Disturbo di Carico ______________________________________ 13
3.3. Wind-up dell’integratore _______________________________________________ 13 3.4. Commutazione manuale/automatico ______________________________________ 14
4. REALIZZAZIONE DIGITALE ______________________________________ 16 4.1. Equivalente Discreto del PID____________________________________________ 16
4.1.1. Scelta della frequenza di campionamento _____________________________ 17 4.1.2. PID Posizionale e PID Incrementale _________________________________ 18
4.2. Pseudo-codice di un regolatore PID_______________________________________ 19
Capitolo 3 _________________________________________________________ 24
1. INTRODUZIONE__________________________________________________ 25
2. RCX _____________________________________________________________ 25 2.1. CPU _______________________________________________________________ 26 2.2. ROM & firmware_____________________________________________________ 26 2.3. RAM esterna ________________________________________________________ 27 2.4. Porte d’ingresso ______________________________________________________ 27 2.5. Porte d’uscita ________________________________________________________ 27
Sommario
189
3. BRICKOS ________________________________________________________ 28 3.1. Il kernel di BrickOS ___________________________________________________ 29
3.1.1. Temporizzazioni_________________________________________________ 29 3.1.2. Gestione dei Tasks _______________________________________________ 30
3.1.2.1. Linking dinamico dei programmi utente _____________________________ 31 3.1.2.2. La funzione execi ______________________________________________ 32 3.1.2.3. La struttura organizzativa dei task__________________________________ 32 3.1.2.4. Lo scheduler __________________________________________________ 33
3.1.3. Gestione della memoria ___________________________________________ 34 3.1.3.1. Allocazione della memoria _______________________________________ 35 3.1.3.2. Deallocazione della memoria _____________________________________ 35
3.1.4. Comunicazioni Interprocessuali _____________________________________ 36 3.1.4.1. Semafori _____________________________________________________ 36
3.1.5. IR Networking __________________________________________________ 37 3.2. Gestione delle periferiche ______________________________________________ 37
3.2.1. Motori_________________________________________________________ 37 3.2.2. Sensori ________________________________________________________ 38
3.2.2.1. Sensori di luce_________________________________________________ 39 3.2.2.2. Sensori di rotazione_____________________________________________ 40 3.2.2.3. Sensori di contatto______________________________________________ 41
3.2.3. Display LCD____________________________________________________ 41 3.2.4. Pulsanti ________________________________________________________ 42
3.3. Osservazioni_________________________________________________________ 43
Capitolo 4 _________________________________________________________ 44
1. INTRODUZIONE__________________________________________________ 45
2. UN MODELLO PER IL PROCESSO DI SVILUPPO ____________________ 45 2.1. Modello a Spirale_____________________________________________________ 46 2.2. Modello a Cascata ____________________________________________________ 47
3. ANALISI E SPECIFICA DEI REQUISITI _____________________________ 48 3.1. Realizzazione di un PID digitale _________________________________________ 49
3.1.1. Funzioni di controllo _____________________________________________ 50 3.1.2. Interfaccia verso il processo ________________________________________ 50
3.1.2.1. Input ________________________________________________________ 50 3.1.2.2. Output _______________________________________________________ 50
3.1.3. Interfaccia operatore______________________________________________ 51 3.1.3.1. Display ______________________________________________________ 51
Sommario
190
3.1.3.2. Pulsanti fisici e pulsanti logici ____________________________________ 51 3.1.3.3. Regole generali ________________________________________________ 51 3.1.3.4. Menù PID ____________________________________________________ 53 3.1.3.5. Menù parametri ________________________________________________ 53 3.1.3.6. Modifica parametri PID _________________________________________ 54 3.1.3.7. Gestione variabile di controllo ____________________________________ 56 3.1.3.8. Gestione del set-point ___________________________________________ 56 3.1.3.9. Conferma o annullamento delle modifiche ___________________________ 56
Capitolo 5 _________________________________________________________ 58
1. INTRODUZIONE__________________________________________________ 59
2. MODULARIZZAZIONE____________________________________________ 59 2.1. Applicazione dei concetti di modularizzazione ______________________________ 60
3. STRUTTURE DATI ________________________________________________ 61 3.1. Logica di funzionamento _______________________________________________ 64
4. ARCHITETTURA DELL’APPLICAZIONE ___________________________ 65
5. GESTIONE DEGLI EVENTI ________________________________________ 65 5.1. Progettazione del gestore degli eventi _____________________________________ 67
5.1.1. Il rilevatore degli eventi ___________________________________________ 67 5.1.2. Il registratore degli eventi__________________________________________ 67
6. GESTIONE DEI CONTESTI ________________________________________ 68 6.1. Operare con i contesti _________________________________________________ 69 6.2. Individuazione dei contesti _____________________________________________ 69
Capitolo 6 _________________________________________________________ 74
1. INTRODUZIONE__________________________________________________ 75
2. ACCORGIMENTI IMPLEMENTATIVI ______________________________ 75
3. ESECUZIONE DEL PID ____________________________________________ 77 3.1. Contesto d’esecuzione del regolatore______________________________________ 78
3.1.1.1. Cambio del contesto di esecuzione _________________________________ 78 3.2. Algoritmo di regolazione _______________________________________________ 79
3.2.1. Funzione di inizializzazione _______________________________________ 79 3.2.2. Funzione di regolazione ___________________________________________ 81
3.3. Thread d’esecuzione del PID ____________________________________________ 82 3.4. Interfacciamento verso il processo________________________________________ 84
Sommario
191
3.4.1. Output_________________________________________________________ 85 3.4.2. Input __________________________________________________________ 86
4. GESTIONE DEGLI EVENTI ________________________________________ 88 4.1. Architettura implementativa ____________________________________________ 89 4.2. Il registratore degli eventi ______________________________________________ 90
4.2.1. L’azione fittizia _________________________________________________ 91 4.2.2. L’inibizione degli eventi___________________________________________ 91
4.3. Il rilevatore degli eventi ________________________________________________ 92 4.3.1. Le funzioni di risveglio ___________________________________________ 92 4.3.2. Il thread KEYONE _______________________________________________ 94 4.3.3. Il thread KEYTWO ______________________________________________ 95
5. GESTIONE DEI CONTESTI ________________________________________ 97 5.1. Realizzazione dei contesti ______________________________________________ 97
5.1.1. «Menù Selezione PID»____________________________________________ 97 5.1.2. «Menù Parametri PID»____________________________________________ 99 5.1.3. «Modifica valore lessicale» _______________________________________ 101 5.1.4. «Modifica valore numerico» ______________________________________ 103
5.1.4.1. «Conferma Voce Numerica»_____________________________________ 105 5.1.5. «Modifica Valore Incrementale» ___________________________________ 106 5.1.6. «Evoluzione variabile controllata»__________________________________ 109 5.1.7. «Conferma o Annullamento» ______________________________________ 109 5.1.8. «Uscita Menù Parametri» _________________________________________ 111
6. VISUALIZZAZIONE DEI DATI ____________________________________ 111 6.1. Menù _____________________________________________________________ 113
6.1.1. Inizializzazione del menù _________________________________________ 116 6.1.2. Scorrimento del menù____________________________________________ 116
6.2. Display____________________________________________________________ 117 6.2.1. Modellizzazione del display fisico __________________________________ 118 6.2.2. Interazioni con la struttura DISPLAY _______________________________ 118
6.2.2.1. Funzione di scrittura ___________________________________________ 119 6.2.2.2. Funzione di lettura_____________________________________________ 121 6.2.2.3. Inizializzazione della variabile display _____________________________ 121
6.3. Video _____________________________________________________________ 122 6.3.1. Campi di controllo nella struttura DISPLAY __________________________ 122 6.3.2. Thread Video __________________________________________________ 123
6.3.2.1. Menu PID ___________________________________________________ 124 6.3.2.2. Menu Param _________________________________________________ 125
Sommario
192
7. LIBRERIE_______________________________________________________ 129
8. AVVIO DELL’APPLICAZIONE ____________________________________ 130
9. SINCRONIZZAZIONE ____________________________________________ 131 9.1. I semafori __________________________________________________________ 131 9.2. Risorse condivise ____________________________________________________ 132
10. Appendice A: STRUTTURE DATI ________________________________ 134
Capitolo 7 ________________________________________________________ 137
1. INTRODUZIONE_________________________________________________ 138
2. TEST DI MODULO _______________________________________________ 139 2.1. Test esecuzione KERNEL _____________________________________________ 139
2.1.1. Accesso ai dati _________________________________________________ 139 2.1.1.1. Scorrimento dei menù __________________________________________ 141 2.1.1.2. Modifica voce numerica ________________________________________ 142 2.1.1.3. Modifica voce lessicale _________________________________________ 144 2.1.1.4. Modifica voce incrementale _____________________________________ 144 2.1.1.5. Conferma (o annullamento) di secondo livello _______________________ 145
2.1.2. Algoritmo di controllo ___________________________________________ 146 2.2. Test interazione KERNEL-RCX ________________________________________ 148
2.2.1. Controllo dei pulsanti ____________________________________________ 149 2.2.2. Controllo del display ____________________________________________ 150 2.2.3. Lettura ingressi e scrittura uscite ___________________________________ 154
3. TEST DI INTEGRAZIONE E DI SISTEMA __________________________ 154 3.1. Accesso ai dati ______________________________________________________ 155
3.1.1. Scorrimento dei menù____________________________________________ 155 3.1.2. Modifica voce numerica__________________________________________ 156 3.1.3. Modifica voce lessicale __________________________________________ 157 3.1.4. Modifica voce incrementale _______________________________________ 158 3.1.5. Conferma (o annullamento) di secondo livello_________________________ 159
3.2. Test di carico _______________________________________________________ 159 3.2.1. Sistema comprendente un solo PID _________________________________ 160
3.2.1.1. Test A: tempo di esecuzione _____________________________________ 161 3.2.1.2. Test B: periodo di campionamento ________________________________ 161
3.2.2. Sistema comprendente due PID ____________________________________ 162 3.3. Test finale di regolazione______________________________________________ 162
3.3.1. Prima fase: taratura del regolatore __________________________________ 163
Sommario
193
3.3.2. Seconda fase: variazione a scalino del set-point________________________ 164 3.3.3. Terza fase: disturbi di carico ______________________________________ 165
3.3.3.1. Aumento dell’attrito ___________________________________________ 165 3.3.3.2. Disturbo sinusoidale ___________________________________________ 166
3.3.4. Quarta fase: funzionamento in modalità manuale ______________________ 167 3.3.4.1. Commutazioni A/M e M/A «bumpless» ____________________________ 167 3.3.4.2. Controllo del processo__________________________________________ 167
3.3.5. Osservazioni ___________________________________________________ 168
4. PROBLEMI NOTI ________________________________________________ 168
5. APPENDICE A: funzione PID ______________________________________ 169 5.1. Test su PC _________________________________________________________ 169
5.1.1. L’applicazione _________________________________________________ 169 5.1.2. I dati rilevati ___________________________________________________ 172 5.1.3. Il grafico ______________________________________________________ 175
5.2. Test su RCX________________________________________________________ 175 5.2.1. L’applicazione _________________________________________________ 175 5.2.2. I dati rilevati ___________________________________________________ 176
6. APPENDICE B: catena di acquisizione _______________________________ 176 6.1. Descrizione del test __________________________________________________ 176 6.2. Risultati del test _____________________________________________________ 178
Capitolo 8 ________________________________________________________ 182
1. CONCLUSIONI __________________________________________________ 183
Bibliografia ______________________________________________________ 184
1. Testi ____________________________________________________________ 185
2. Siti web e documentazione __________________________________________ 185
Sommario ________________________________________________________ 187