Tasking VX un ambiente di sviluppo per i micro ST10

La famiglia di micro ST10 si compone i dispositivi dotati di una DSP-MAC e di un accumulatore a 40 bit. Lo sviluppo delle applicazioni è facilitato grazie all’ambiente Tasking VX, un tool basato sul framework Eclipse. Ecco le sue caratteristiche ed un esempio di applicazione.

I MICROCONTROLLORI  ST10

I dispositivi della famiglia ST10 sono dei microcontrollori a 16 bit disponibili in due categorie principali: con memoria programma Flash o senza (ROMless). Tutti i componenti della famiglia, tranne due (ST10R167 ed ST10R172) sono inoltre  dotati  di  una  unità  DSP-MAC  (Digital Signal Processor-Multiply and ACcumulate). Il DSP-MAC si avvale di un moltiplicatore hardware da 16x16 bit, di una ALU e di un accumulatore da 40 bit. I micro ST10 possiedono un sistema di interrupt sofisticato che, insieme ad un context switching a singolo ciclo, ne facilitano l’uso con sistemi operativi Real-Time (RTOS). La frequenza di clock massima varia tra 25 e 64 MHz; come in molti micro di concezione recente, l’architettura è un misto di quelle tipiche RISC e CISC, con una pipeline a 4 stadi che permette di ottenere un ciclo istruzione pari a 2 cicli di clock: il throughput massimo è quindi di 32 MIPS. I micro ST10 sono utilizzati soprattutto nel settore automotive e negli hard-disk drive. Nello schema in figura 1 è raffigurato il diagramma a blocchi di un dispositivo tipico della famiglia, il micro ST10F276, che utilizzeremo nel progetto di esempio.

Figura 1. Diagramma a blocchi ST10F276

Figura 1. Diagramma a blocchi ST10F276

Nella tabella in figura 2 sono riassunte le caratteristiche principali dei dispositivi della famiglia.

Figura 2. Caratteristiche principali dei micro della famiglia ST10

Figura 2. Caratteristiche principali dei micro della famiglia ST10

TASKING VX-TOOLSET

L’ambiente di sviluppo Tasking VX-toolset for C166/ST10 (nel seguito semplicemente Tasking VX) di Altium è un completo insieme di strumenti per lo sviluppo ed il test di codice per i microcontrollori  ST10.  Supporta  anche  i  micro Infineon C166 (hanno in comune le funzionalità di base del core ST10) più le evoluzioni delle famiglie ST Super10 ed Infineon XC22xx. Questo toolset rappresenta un salto generazionale rispetto  ai toolset  precedenti in quanto basata sul framework Eclipse. Eclipse è uno standard de facto tra gli ambienti di sviluppo open source, tant’è che ne sono state realizzate implementazioni per i più svariati linguaggi, è inoltre multipiattaforma in quanto basato su Java. Come tutte le applicazioni Java based, è piuttosto esigente in termini di risorse hardware, per cui è raccomandato l’uso su di un PC almeno di classe Pentium IV con 1 GB o più di RAM. Poiché l’IDE è basato su Eclipse, sarà sicuramente familiare a chi avrà lavorato in ambienti simili come PHP-Eclipse. L’IDE permette una completa integrazione tra l’assembler, i compilatori C e C++, il debugger. L’editor del codice fornisce il sintax highlighting, il completamento automatico e l’help on-line. Fornisce inoltre supporto per i sistemi di controllo di versione del codice. Vedremo più in dettaglio alcune caratteristiche dell’IDE quando illustreremo l’esempio d’uso di Tasking VX.

Il compilatore C

Il compilatore  C fornito con Tasking VX, è stato appositamente sviluppato per i micro ST10, e risulta conforme allo standard ISO C99. Si tratta di un compilatore ottimizzante in grado di ridurre le dimensioni del codice ed aumentarne la velocità di esecuzione.

Tra le ottimizzazioni eseguite dal compilatore vi sono le seguenti:

  • PRE (Partial Redundancy Elimination): eliminazione di espressioni ripetute
  • Ottimizzazione di cicli e salti
  • Controllo di flusso con rimozione di codice inutilizzato
  • Inlining di piccole funzioni per ridurre l’overhead, così come la sua inversa reverse inlining per compattare il codice
  • Allocazione dei registri ottimizzata tra più procedure
  • Generazione automatica di istruzioni per l’unità DSP-MAC. Opzionalmente il DSP-MAC può essere programmato direttamente tramite codice Assembly inline.

Il compilatore fornisce funzionalità di profiling, utili per determinare eventuali colli di bottiglia nell’esecuzione del codice. In particolare può stabilire quante volte viene eseguita una data funzione e per quanto tempo, o in alternativa mostrare un grafo delle chiamate delle funzioni. Se richiesto, mediante l’aggiunta di codice apposito, permette di effettuare l’error checking a runtime facilitando il debug nei casi di buffer overflow, errata allocazione/deallocazione di puntatori, stack overflow ed altri. Il compilatore può opzionalmente eseguire il controllo del codice rispetto alle linee guida MISRA-C. Queste, emanate dalla Motor Industry Research Association, specificano una serie di regole da soddisfare nella realizzazione di programmi C in ambienti critici come quello automotive. Il compilatore Tasking VX segue (anche se non completamente) le specifiche MISRA-C2004, segnalando in fase di compilazione come warning o errori le eventuali carenze del codice.

Il compilatore C++/EC++

Anche nei sistemi embedded la tendenza attuale è quella di utilizzare linguaggi object-oriented, dati i vantaggi di maggiore information hiding e riutilizzo del codice. Il compilatore C++, disponibile nella versione Premium, è conforme allo standard ISO C++98  e  permette  di  sviluppare  applicativi miscelando se richiesto moduli C++ con moduli C o Assembly. È opzionalmente compatibile anche con lo standard EC++ (Embedded C++): questo standard esclude alcune caratteristiche del C++ -come l’ereditarietà multipla, i template ed altre non essenziali nell’ambito dei sistemi embedded, permettendo una migliore efficienza del codice.

Altri tools di Tasking VX

Tasking VX include diversi altri tool per lo sviluppo ed il deployment del codice, tra essi:

  • Macro assembler;
  • Linker, con un linguaggio di script per l’allocazione di codice e dati;
  • Librarian, per la creazione di librerie di moduli;
  • Librerie run-time e floating point C/C++, complete di codice sorgente;
  • Download dell’applicazione nella memoria flash del micro, anche dal debugger.

Il debugger

Il debugger è integrato nell’IDE come plug-in  di Eclipse. Permette di utilizzare tre diversi ambienti di esecuzione:

  • simulatore: è in grado di simulare il set di istruzioni e le periferiche dei micro, senza quindi la necessità di un hardware reale. Permette di settare breakpoint, ispezionare il codice, le variabili ed i registri del micro. Vedremo l’utilizzo pratico del simulatore nell’esempio d’uso di Tasking VX.
  • RAM/ROM monitor:  può  essere  usato  con schede di valutazione/sviluppo sia standard che custom. Il debugger controlla l’esecuzione del codice tramite un programma monitor caricato sul micro e l’interfaccia RS232 o CAN. Con la versione Premium è possibile anche effettuare il debug tramite un PC remoto connesso ad una rete TCP/IP.
  • OCDS (On Chip  Debug  Support),  solo  per  i micro Infineon C166 e derivati.

Il modulo  RADM (RTOS Aware Debug  Module) aggiunge al debugger la capacità di effettuare il debug di applicazioni che utilizzano un RTOS sia commerciale che proprietario. In particolare il RADM permette di:

  • Esaminare le strutture dati del kernel;
  • Visualizzare le caratteristiche dei task in esecuzione;
  • Esaminare il contenuto degli oggetti di IPC per lo scambio di messaggi (mailbox, pipe, code);
  • Visualizzare lo stato dei meccanismi di sincronizzazione;
  • Visualizzare lo stato delle routine di interrupt

ESEMPIO D’USO DI TASKING VX

Nel seguito  ci  riferiamo alla versione 2.1r2 di Tasking VX. Una volta installato il sistema di sviluppo Tasking VX, per eseguirlo selezioniamo  (su piattaforma  Windows) Avvio>TASKING VX-toolset  for  C166 v2.1r2>TASKING VX-toolset for C166. Al lancio viene richiesto di selezionare un workspace, lo spazio di lavoro che può contenere uno o più progetti: selezioniamo la directory che preferiamo e premiamo OK. Se la directory non esiste, Tasking VX la creerà per noi. Qualche parola sull’organizzazione dell’ambiente di lavoro in Eclipse: questo è denominato workbench ed è suddiviso in diverse perspective, che possiamo immaginare come spazi di lavoro per un’attività specifica del processo di sviluppo. Avremo così una perspective C/C++ per lo sviluppo del codice, una perspective Debug per il debugging, una per il profiling, ed altre. Nell’ambito di una data perspective, Eclipse mette a disposizione un insieme di views, vale a dire di pannelli ognuno dedicato ad una funzione particolare, ad esempio l’editor del codice, il pannello di gestione dei progetti e così via.  Tutto  ciò  è  personalizzabile  secondo  le nostre esigenze. Si tratta di un ambiente di sviluppo che può apparire ridondante a chi sia abituato ad IDE più spartane (ed in effetti a volte lo è, specie nella perspective di debug), tuttavia nell’insieme costituisce uno strumento di ottimo livello, superiore alla media degli IDE per sistemi embedded. Alla potenza dell’ambiente di sviluppo non corrisponde al momento una sempre adeguata documentazione, soprattutto per quanto riguarda il debugger. Altium comunque è al corrente di tali carenze e dovrebbe risolverle in futuro. Al primo lancio di Tasking VX ci apparirà la sola Welcome view della C/C++ perspective: chiudiamola  cliccando  sulla  crocetta  e  appariranno  le altre viste della perspective. In questo spazio di lavoro possiamo creare e gestire i progetti, editare il codice e compilarlo.

Creazione del progetto

Per creare un nuovo progetto, lanciamo il wizard selezionando la voce di menu File>New>Tasking VX-toolset for C166 C/C++ Project, immettiamo il nome del progetto -ad esempio ST10_taskinge clicchiamo Next: verrà creata la subdirectory ST_tasking nella directory del workspace che abbiamo scelto in precedenza. La schermata successiva ci permette di scegliere se creare un eseguibile (C166 absolute ELF file) o un file di libreria, inoltre possiamo scegliere se creare una configurazione di debug e/o di release. Lasciamo le impostazioni di default e clicchiamo Next. Nella dialog box successiva selezioniamo la scheda C/C++ indexer e dalla list box selezioniamo full C/C++ indexer: questo attiverà delle funzioni di indicizzazione che permettono di navigare meglio tra i file del progetto. Next per continuare: nella schermata successiva selezioneremo la CPU su cui svilupperemo il progetto: dalla list box scegliamo il micro ST10F276E. Spuntiamo le check box che ci permettono di aggiungere il codice di startup C ed il file di configurazione del linker (che sarà relativo al micro selezionato). Scegliamo invece di non far generare uno scheletro di applicazione Hello world (figura 3) e premiamo Finish. Nella view C/C++ projects vedremo i file che costituiscono il nostro progetto.

Figura 3. Project wizard - scelta del processore e codice di startup

Figura 3. Project wizard - scelta del processore e codice di startup

Dobbiamo ora aggiungere al progetto il file del codice d’esempio. Copiamolo nella directory del progetto, quindi clicchiamo col tasto destro del mouse sul nome del progetto nella vista C/C++ projects e clicchiamo su Refresh: il file sarà così aggiunto al progetto. In figura 4 possiamo vedere la C/C++ perspective dell’applicazione.

Figura 4. C/C++ perspective di Tasking VX

Figura 4. C/C++ perspective di Tasking VX

Il codice dell’applicazione d’esempio è il classico lampeggio di un LED: utilizza l’interrupt del Timer2 GPT1 per far lampeggiare il LED1 (pin 0 di Port2) con un periodo di 200 ms, se il pulsante SW1 (pin 1 di Port2) è premuto. Il codice è riportato nel listato 1.

#define FALSE 0
#define TRUE 1
typedef _Bool bool;
// ISR di Timer2
void __interrupt (0x22) GPT1_Timer2_ISR(void)
{
        T2 = 34286;     // Setta il Timer:
                            // Overflow dopo 100 ms
        if (P2_0==0)
               P2_0=1;     // accende LED1
        else
               P2_0=0; // spegne LED1
}
// configura Timer2
void GPT1_Timer2_Setup(void)
{
       T2CON=0x004; // Timer 2 in Timer mode;
                        // prescaler T2I = 100b -> Fck/128
       T2IC=0x78;   // Timer 2 interrupt priority;
                       // abilita interrupt
}
/****************************************************** Funzione principale:
* -configura GPT1 Timer2
* -abilita Interrupt generali
* -se SW1 viene premuto attiva Timer2
******************************************************/
void main(void)
{
       volatile bool TMR2_ON;
       DP2_0 = 1;         // Port2.0: uscita
       DP2_1 = 0;         // Port2.1: input
       P2_0=0;                // spegne LED1
       GPT1_Timer2_Setup();
       TMR2_ON = FALSE;   // Timer2 non e’ attivo
       IEN=1;             // Abilita gli Interrupt
       while(1) {
               if(P2_1) {        // SE SW1 premuto
                       if (!TMR2_ON) {
                             P2_0=1;         // accende LED1
                             TMR2_ON = TRUE;
                             T2 = 34286;    // Setta il Timer
                             T2R=1;         // Attiva Timer2
                       }
                  }
                  else {                 // SW1 NON PREMUTO
                        if (TMR2_ON) {
                               T2R=0;        // Disattiva Timer2
                               TMR2_ON = FALSE;
                               P2_0=0;            // spegne LED1
                        }
                  }
       } // Fine while
} // Fine main
Listato 1

Build del progetto

Per poter effettuare il build ed il debug del progetto è necessario che venga selezionato un ambiente di esecuzione target. Avendo creato il progetto tramite il wizard, questo è già stato configurato ed è stata creata automaticamente una configurazione di debug per il simulatore (il file ST10_tasking.simulator.launch). Una data configurazione determina tra l’altro lo script del linker (ST10_tasking.lsl) ed il codice di startup (cstart.c): questi differiscono  a seconda che il target sia il simulatore  od   una  scheda  con capacità di RAM/ROM debugging od OCDS. Se necessario possiamo modificare la configurazione del progetto selezionando la voce di menu Project>Properties:  nella finestra Properties  che appare in figura 5 vediamo che la configurazione attualmente attiva è la Debug.

Figura 5. Configurazione delle proprietà del progetto

Figura 5. Configurazione delle proprietà del progetto

In questa finestra possiamo  modificare  le opzioni  del  compilatore C/C++ (ad esempio abilitare le linee guida MISRAC),  dell’assembler,  del  linker,  ed  altro.  Per  il momento possiamo lasciare tutto com’è. Eseguiamo  il  build  del  progetto  selezionando Project>Build Project: alla fine del processo potremo vedere il risultato nella vista Console. Molto comodo il modo in cui nella finestra dell’editor vengono evidenziati eventuali warning ed errori: la linea in cui si verificano viene evidenziata con una icona e, ponendo il cursore del mouse sulla stessa, verrà segnalato con un tooltip  la natura del problema. Inoltre sulla barra di destra viene raffigurata una outline degli errori.

Debug dell’applicazione

Per eseguire il Debug dell’applicazione, selezioniamo Run>Debug: comparirà la finestra di dialogo Debug (figura 6).

Figura 6. Configurazione di debug

Figura 6. Configurazione di debug

In questa finestra possiamo selezionare la configurazione di debug: nel nostro caso ci sarà solo quella di default creata dal Project wizard, indicata nel pannello di sinistra come ST10_tasking.simulator. Se necessario, possiamo customizzare la configurazione: nel nostro caso selezioniamo) la scheda Debugger, quindi la scheda Initialization dove spuntiamo (se già non lo è) la check box Goto Main, in modo che al lancio del debugger verrà processato solo il codice di startup e l’esecuzione dell’applicazione si fermerà in corrispondenza della prima istruzione della funzione main(). Lasciamo tutte le altre opzioni come da default e premiamo Apply, quindi Debug per iniziare il debug. Tasking chiederà se vogliamo aprire la Debug perspective, clicchiamo OK per proseguire. In seguito potremo passare direttamente dalla Debug perspective alla C/C++ perspective e viceversa, cliccando sull’icona relativa presente nell’angolo a destra della toolbar. Nella debug perspective, raffigurata in figura 7, distinguiamo in particolare le seguenti viste:

Debug, nella quale potremo controllare l’esecuzione del programma;

  • Editor, dove vengono visualizzati i sorgenti del programma;
  • Variables, che mostra le variabili dell’applicazioni;
  • Tasking registers, per visualizzare il contenuto dei registri del micro.
Figura 7. Debug perspective di Tasking VX

Figura 7. Debug perspective di Tasking VX

Notiamo che l’esecuzione del programma si è arrestata in corrispondenza della prima istruzione del main(). Nella finestra Variables sono mostrati i valori delle variabili locali ed a richiesta le variabili globali. Per cambiare il valore di una variabile possiamo  cliccare  di  destra sul nome della stessa, scegliere Change Value ed immettere il nuovo valore. Eseguiamo il programma passo passo con Run>Step Into o F5 (o cliccando sull’icona corrispondente nella Debug view). Per uscire da una funzione usiamo Run>Step Return o F7, mentre per eseguire interamente una funzione usiamo Run>Step Over o F6. È importante evidenziare come il comportamento del debugger sia influenzato fortemente dalle eventuali ottimizzazioni effettuate dal compilatore: queste possono comportare una mancata corrispondenza tra le istruzioni effettivamente eseguite e quelle presenti nel codice, ed anche rendere invisibili delle variabili nella Variables view. Prendiamo ad esempio il seguente spezzone di codice

int i=2;

if ((i%2)==0) {

printf(“i pari: “);

printf(“%i\n”, i);

}

else {

printf(“i dispari: “);

printf(“%i\n”, i);

}

poichè la variabile i è in realtà una costante (allo scopo ad esempio di semplificare il debug del codice) e dato che il primo if è sempre verificato, il codice effettivamente compilato sarà il seguente:

printf(“i pari: “);

printf(“2\n”);

dove inoltre la seconda printf corrisponderà a quella  contenuta  nel  blocco  else  (le  istruzioni uguali dei due blocchi vengono accorpate). Infine la variabile i sarà invisibile nella Variables view. Per semplificare il debug può essere quindi utile disabilitare le ottimizzazioni del compilatore modificando la configurazione del progetto: nella finestra Properties, richiamata dal menu Project>Properties, selezioniamo nel pannello di sinistra la voce C/C++ Build, quindi nella scheda Tool Settings selezioniamo la voce Optimization e nella list box Optimization Level selezioniamo l’opzione 0-None. Alternativamente possiamo scegliere l’opzione Custom e selezionare le sole ottimizzazioni che non dovrebbero affliggere il debugger. Una volta completata la fase di debug possiamo riattivare le ottimizzazioni. Teniamo presente che in questo caso i timing del programma potranno cambiare di conseguenza. Nella Debug perspective possiamo visualizzare il contenuto dei registri del micro in viste apposite facendo doppio click sul nome del registro nella Tasking register view: nel nostro caso è utile visualizzare il registro P2 di Port2 (fa parte del gruppo PORT)  ed i registri del gruppo  SIMULATOR. Tra questi ultimi vi è in particolare il registro CCNT che visualizza il conteggio dei cicli di clock. Vediamo un esempio d’uso di un breakpoint: per simulare il funzionamento del timer possiamo settare un breakpoint alla prima istruzione della routine di interrupt GPT1_Timer2_ISR() facendo doppio click sullo spazio a sinistra della linea corrispondente. Il breakpoint verrà raffigurato da una icona come si vede in figura 8.

Figura 8. Abilitazione di un breakpoint

Figura 8. Abilitazione di un breakpoint

Dobbiamo ora simulare la pressione del pulsante SW1: nel pannello  che  visualizza il  registro  P2 immettiamo  il valore 1 per il Bit1 del registro e premiamo Update. Eseguiamo il programma con Run>Resume o F8: l’esecuzione si arresterà in corrispondenza del break-point. Notiamo in particolare che il Bit0 di P2 ha cambiato valore (il LED è acceso) mentre il registro CCNT ha un valore pari a circa 4000000 (infatti alla frequenza di clock di 40 MHz questo corrisponde  al  periodo  del  timer  di 100ms), come si può vedere in figura 9.

Figura 9. Arresto dell’esecuzione tramite breakpoint

Figura 9. Arresto dell’esecuzione tramite breakpoint

Premiamo nuovamente F8: il programma si arresta di nuovo al breakpoint ed il Bit0 di P2 diventa 0 confermando il toggle del pin 0.

 

 

Scrivi un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *