Real-time software development



Il progetto


Lo scopo di questo progetto è programmare il microcontrollore che gestisce il funzionamento (semplificato) di un treno:



Specifiche


Per le specifiche complete si rimanda al pdf del progetto.

Riassumendo brevemente, i compiti del "controller" sono:

  • Rispondere alla richiesta di frenata di emergenza
  • Rispondere ai comandi di stop
  • Attivare e disattivare i normali comandi di frenata emessi dal conducente
  • Aumentare o diminuire la velocità in base ai comandi del conducente
  • Gestire le comunicazioni tra la "stazione" dei treni e l'autista

Il sistema deve gestire gli input che provengono dal mondo esterno, reagendo di conseguenza e modificando gli output.


INPUT:

  • Leva autista (7 posizioni)
  • Segnale di emergenza
  • Segnale di stop
  • Sistema di messaggi

OUTPUT:

  • Sistema frenante
  • Sistema di accellerazione
  • Segnale di emergenza


Sistema

Il sistema lo possiamo descrivere come una macchina a stati.

Se non ci sono interruzioni, il sistema è nella situazione di "working". In questa situazione ogni input che viene letto dalla leva dei comandi viene riscritto nel registro di output.

Quando viene letto il segnale di stop (es. ci si avvicina ad una stazione) il controller fa rallentare gentilmente il treno e decellera la sua corsa.

Il macchinista per poter riprendere la marcia, una volta che il comando di stop viene disinserito, deve riportare la leva in posizione 0 per poter riprendere la normale corsa del treno.

Quando viene letto il segnale di emergenza (es. viene tirata la leva dell'emergenza) il controller fa rallentare bruscamente il treno e decellera la sua corsa.

Una volta entrati nello stato di emergenza per poterne uscire bisogna resettare il controller.



Per implementare questa macchina a stati sarà necessario avere una variabile stato di tipo "state" che si occuperà di salvare in quale stato del sistema siamo.

Per invece salvare lo stato della leva si usa la variabile leva di tipo "lever_position"

Il class diagram del progetto è rappresentato da questa immagine:





Viste la poche interazioni nel modello, la classe "controller" è praticamente l'unica responsabile della gestione del sistema.

Le variabili della classe sono:

> stato di tipo "state" che indica lo stato del sistema.

> leva di tipo "lever_position" che indica in che stato è la leva nel treno. Questo valore viene letto ad ogni loop del ciclo (circa 10ms)

> is_stop indica se il sistema deve entrare nello stato di stop, è settato a True nella funzione ReadGPIOx()

> is_emergency indica se il sistema deve entrare nello stato di emergenza, è settato a True nella funzione ReadGPIOx()

> system_tick e is_max_speed sono utilizzate per controllare se il sistema è da troppo tempo alla massima velocità


Le funzioni di questa classe sono:

> WriteGPIO viene chiamata per leggere dal registro B gli eventuali input

> ReadGPIO viene chiamata per scrivere nel registro C gli output

> ReadUSART viene chiamata per leggere dalla porta USART gli eventuali messaggi che vengono inviati dalla stazione



Design


Tasks

Nei requisiti del progetto viene chiesto di poter:

  • 1) testare il programma
  • 2) gestire la comunicazione con la stazione con una bassa priorità

Per questi motivi il programma viene diviso in 3 task

  • 1) Simulatore
  • 2) Controller
  • 3) Gestore dei messaggi

Dividere il programma in 3 task, è stato ritenuto il giusto compromesso tra semplicità nello sviluppo e testabilità del codice.

La gestione dei task avviene grazie alla libreria RTL.h in cui si può specificare la priorità del singolo task.


Task 1

Simulatore: si occupa di inviare (scrivere sul registro GPIO_B) gli input al nostro sistema. Ad ogni invio il processo si addormenta permettendo la gestione dell'input al programma.

Nel simulatore viene creata una possibile sequenza di input per controllare come il sistema risponde. Questo tipo di approccio facilita la fase di debug e test.




Task 2

Controller: è il programma che effettivamente dovrebbe girare in produzione nel microcontrollore. Si occupa della lettura degli input, della scrittura degli output e di controllare che il sistema sia sempre corretto.

In questo task, in base allo stato, si decide cosa fare:


STATO WORKING: sono nello stato di "non emergenza". Si legge l'input e si scrive l'output in base alla leva.

requisito

Il requisito da rispettare in questa fase è il seguente: "se la leva dei comandi è per più di 4 secondi in posizione di massima velocità, si deve diminuire in automatico la velocità."

Il requisito può essere rappresentato dalla seguente immagine:

Per soddisfare il requisito il codice è il seguente:

Questo task viene eseguito ogni 10ms circa. Se sono in posizione di max speed, aumento di 1 la variabile "count_max_speed_limt_tick". Se ho raggiunto i 24,000 tick (4 secondi circa) allora devo forzare il sistema e diminuire la velocità. Ogni altro segnale della leva resetta il contatore.

E' stata scelta questa soluzione al posto di un timer perchè è estremamente semplice da implementare e facilmente comprensibile.



STATO STOP: sono nello stato di "stop". Il treno deve frenare gentilmente.

requisito

Il treno può ripartire solamente se il segnale di stop scompare e la leva viene posta sullo stato "folle"

Per soddisfare il requisito il codice è il seguente:

Se c'è il segnale di stop, lo stato viene settato a "stop" e si esce dal ciclo.

Se sono nello stato di stop e il segnale di stop non è più presente allora devo attendere che la leva venga posizionata in posizione di "folle".

Una volta che anche la leva è stata posizionata a folle, lo stato cambia in "working".



STATO EMERGENZA: sono nello stato di "emergenza". Il treno deve frenare bruscamente. Non si può uscire dallo stato di emergenza se non resettando il dispositivo.



Task 3

Gestore dei messaggi: questo task legge i messaggi che arrivano dalla stazione. Visto che il task viene creato con bassa priorità, non viene schedulato spesso.

E' impossibile testare automaticamente l'invio dei messaggi, quindi bisogna scrivere manualmente nel tab "USART" una stringa.



Test

Per testare il programma si è creato il task "Simulatore". Ci sono 6 requisiti da soddisfare:

  • Se ci sono segnali contrastanti il treno si ferma (massima prudenza)
  • Ad ogni input della leva si produce il desiderato output
  • Segnale di stop -> treno frena dolcemente
  • Massima velocità per troppo tempo -> si diminuisce la velocità
  • Segnale di emergenza -> output segnale di emergenza
  • Controllare che il messaggio dalla stazione sia letto correttamente
Test 1

Se ci sono diversi input contrastanti

Il treno si deve fermare




Test 2

Controllo che gli input siano letti correttamente e gli output siano scritti di conseguenza.

Se ricevo il segnale di leva in "folle", l'output è il pin 5

Se ricevo il segnale di leva in "minima velocità", l'output è il pin 3

Se ricevo il segnale di leva in "media velocità", l'output è il pin 2

E così via per tutte le posizioni della leva ...




Test 3

Questo test serve per controllare quando arriva il segnale di stop. Inizialmente il treno viaggia a velocità media.

In questa immagine arriva il segnale di stop

Lo stato diventa "stop" e il treno frena a livello medio. Viene quindi spostata la leva su livello medio ma il sistema rimane nello stato di "stop"

Il tentativo successivo è rimuovere il segnale di stop e spostare la leva su livello medio.

Il sistema però rimane nello stato di stop con il pin di output su "frenata media"

Successivamente bisogna provare a riportare il sitema a lavorare normalmente.

Si sposta la leva su "folle".

Lo stato viene quindi riportato correttamente su "WORKING" e il treno può riprendere la sua corsa.




Task 4

Questo test serve per controllare se la velocità massima viene superata per più di 4 secondi. All'inizio di questo test viene spostata la leva su "max_speed".

Quando la velocità è massima la variabile 'count_max_speed_limt_tick' viene incrementata di 1 ogni 10ms.

Quando la variabile 'count_max_speed_limt_tick' raggiunge il limite, la velocità del treno viene decrementata in automatico.

[OUTPUT] da pin 0 a pin 1




Task 5

Questo test serve per controllare il segnale di allarme.

Quando arriva il segnale di allarme ...

il sistema si sposta nella fase di allarme e setta il pin di emergenza.

Una volta entrati nello stato di emergenza è impossibile uscirne se non viene resettato il dispositivo.




Task 6

Questo test serve per controllare il "gestore dei messaggi". I messaggi vengono letti dal registro GPIO_C.

Per testare la lettura bisogna scrivere nel tab "USART" direttamete su uvision.







UML

  • https://online.visual-paradigm.com/app/diagrams/#