Esistono diverse proposte commerciali che permettono di sviluppare codice in ambiente ARM, in questo articolo è presentato l’ambiente MDK-ARM che ha il pregio di fornire una serie di utility attraverso un unico workbench.
La proposta MDK-ARM (Keil Microcontroller development kit) è un ambiente completo che comprende, oltre ai tool di debug (uVision), la cross factory (ARM Real View), un ker nel real-time, l’ambiente di simulazione e l’interfaccia verso lo JTAG. Inoltre, esiste la possibilità di utilizzare componenti aggiuntivi per sopperire ad ogni specifica esigenza, quali uno stack TCP/IP, un file system embedded o drivers CAN o USB. Non solo, questo ambiente di lavoro permette di aggiungere parti sviluppate da terzi in maniera del tutto trasparente. La figura 1 mostra l’architettura di questo ambiente di sviluppo.
Il microcontrollore
La famiglia LPC2000 propone, per ogni segmento di mercato, una vasta gamma di soluzioni ognuno copre differenti esigenze. Il core ARM offre, a parità di prestazioni, un minor consumo di potenza rispetto alle altre offerte presenti sul mercato. Così, per esempio, utilizzare un dispositivo come LPC2148 permette di sfruttare diverse soluzioni tecniche davvero interessanti; infatti, accanto al suo costo, che risulta essere abbastanza contenuto, il componente ha nel suo interno tutte le principali periferiche di uso generico: convertitori A/D, dispositivi di I/O e ben 512 Kbyte di memoria Flash. Inoltre è già integrato nel chip un comodo controller USB col quale interfacciare il sistema in maniera semplice e comoda. Nella figura 2 è possibile vedere lo schema a blocchi del microcontrollore.
Come vediamo sono presenti un gran numero di periferiche racchiuse in un package estremamente contenuto. Oltre ai classici componenti come contatori, convertitori e interfacce I/O, nel microcontrollore sono presenti caratteristiche del tutto particolari specifiche per quest’architettura. Il nucleo del microcontrollore è il core ARM7TDMI-S. Questo è connesso alle altre periferiche per mezzo del bus l’Advanced Hight Performance Bus (AHB). Le altre periferiche collegate direttamente al bus sono il vector Interrupt controller (VIC), responsabile della gestione degli interrupt e 8 Kbyte di memoria flash. Tutte le altre periferiche, al contrario, sono connesse alla CPU per mezzo di un altro bus connesso al primo con un bridge che include un divisore di frequenza, chiamato VPB divider. Questo permette all’ARM di lavorare al massimo delle sue potenzialità lasciando alle periferiche una frequenza di funzionamento più bassa al fine di risparmiare sui consumi. Esiste, per la verità, anche un terzo bus separato, il quale connette direttamente la CPU ai 512 Kbyte di memoria flash e ai 32 Kbyte di SRAM. Considerato che gli accessi in memoria durante l’esecuzione di un programma sono frequenti, in questo modo si riescono a evitare fastidiosi contenction che inevitabilmente rallenterebbero il processore. Un altro accessorio che permette di aumentare le prestazioni del microcontrollore è fornito dal modulo Memory Acceleretor Module (MAM). Una dei maggiori limiti nel design di sistemi embedded ad alta velocità è quello dell’accesso alla memoria Flash. Il chip ARM è capace di lavorare fino a 80 MHz e la comune memoria flash ha un tempo di accesso che si aggira intorno ai 50ns limitando la velocità della CPU a 20MHz. Il dispositivo LPC2148 risolve il problema con il MAM, controller che gestisce due banchi di memoria aventi ciascuno 128 bit. All’avvio del processore, il programma viene via caricato su questi due banchi, al fetch della CPU si hanno subito disponibili ben 4 istruzioni ARM a 32 bit o 8 THUMB a 16 bit, mentre queste istruzioni sono eseguite, il secondo banco è disponibile per un ulteriore fetch. Per concludere è possibile anche mettere in evidenza due PLL, Phase Locked Loop. Questi accettano in ingresso un intervallo di frequenze che può andare dai 10MHz ai 25MHz e riescono a fornirne una frequenza di uscita fino a 60MHz. Mentre il primo serve a fornire il clock di sistema, che dunque può essere settato fino a un massimo di 60MHz, il secondo regola la frequenza di lavoro del controller USB, quindi, come da protocollo, è buona norma settarlo sui 48 MHz.
µVision
L’assembler oggi è un lontano ricordo; oggi, per la maggior parte delle applicazioni è utilizzato il linguaggio C. Il codice C, essendo un linguaggio di più alto livello, consente una maggiore semplicità di utilizzo: con i puntatori si è in grado di lavorare comodamente con registri e locazioni di memoria; grazie alle macro, poi, possiamo scrivere codice riutilizzabile per gestire una quantità notevole di possibilità. Il problema del C, come di ogni linguaggio di programmazione ad alto livello, è di dover utilizzare un compilatore che riesca a tradurre, in maniera efficiente, il codice in linguaggio macchina. La proposta IDE µvision KEIL è sicuramente il giusto compromesso tra prezzo e affidabilità. Il software include l’ARM Real View compiler, un potente debugger software in grado di testare tutte quante le principali periferiche, una libreria avente le specifiche di tutti i microcontrollori CPU ARM e validi esempi di codice tra cui anche un kernel per lo sviluppo di un sistema RTOS (Real Time Operative System). Una volta scelto il tipo di dispositivo sul quale si intende lavorare, l’IDE fornisce, in automatico, un file di startup.s. Attraverso questo file si mette a punto la configurazione del sistema impostando: PLL e clock del sistema, il clock del bus (VPBDIV), il MAM e configurare la memoria di stack. Al termine di questa operazione è possibile iniziare a scrivere il nostro codice che verrà quindi eseguito dopo il file di startup. Una volta scritto il codice, è buona norma andarlo a simulare prima via software:
µVision dispone di un affidabile debugger software in grado di gestire tutte le componenti del microcontrollore. Infatti, l’interfaccia consente, oltre che effettuare classiche simulazioni degli stati logici dei pin esterni (Logic Analyzer), anche di visualizzare, in maniera real-time, gli stati dei registri interni alla CPU, gli accessi alla memoria flash e gli stati di tutte le periferiche e dei registri connessi, interni al microcontrollore. Non solo è anche possibile eseguire un Hardware Debugging; infatti l’IDE è in grado di gestire il modulo JTAG. Grazie a questo dispositivo esterno si è in grado di programmare la flash del dispositivo ed eseguire il proprio lavoro per verificarne i risultati su terminale.
Utilizziamo l’ambiente di lavoro: il codice di startup
Utilizzare MDK-ARM è una attività per nulla complessa. Infatti, attraverso la sua interfaccia grafica è possibile sfruttare tutte le prerogative in questo modo diventa un ottimo strumento di sviluppo e verifica di un prodotto. La figura 3 mostra come si presenta la struttura dell’IDE dopo la sua installazione.
Gli esempi del compilatore Real View si possono trovare in RV30. Gli esempi acclusi nella distribuzione permettono di prendere confidenza con l’ambiente e rappresentano un ottimo sussidio didattico. Esistono diversi esempi scritti in C, ma sicuramente il file che stuzzica maggiore attenzione è un file scritto in assembler: startup.s. Come è facilmente intuibile, il file startup è la routine software che deve essere posta al vettore di reset: in questo modo, al power on, questa porzione di codice è la prima ad essere eseguita. Il file for nisce, per esempio, la tabella delle eccezioni del microcontrollore, l’inizializzazione dello stack pointer. Il file deve anche inizializzare tutte le eventuali periferiche utilizzate prima di passare il controllo alla prima routine scritta in C: la funzione main(). Il contenuto, e le azioni previste dal modulo stesso, dipendono fortemente dal tipo di dispositivo ARM e dal compilatore utilizzato. Il file di startup per il compilatore Real View può essere trovato nella cartella c:\keil\ARM\RV30\startup\Philips, mentre per la variante GNU il file si trova in c:\keil\GNU\startup. Il comportamento del codice è così descritto. La prima parte del codice fornisce la tabella delle eccezioni. Questa tabella è posta all’indirizzo 0x00000000 e fornisce, per ogni vettore, il riferimento ad ogni ISR. Per assicurarsi che tutto l’intervallo di indirizzamento del processore è disponibile è utilizzata l’istruzione LDR (Load Register). Il codice del modulo di startup è mostrato nel listato 1.
; Startup Code must be linked first at Address at which it expects to run. AREA RESET, CODE, READONLY ARM ; Exception Vectors ; Mapped to Address 0. ; Absolute addressing mode must be used. ; Dummy Handlers are implemented as infinite loops which can be modified. Vectors LDR PC, Reset_Addr LDR PC, Undef_Addr LDR PC, SWI_Addr LDR PC, PAbt_Addr LDR PC, DAbt_Addr NOP ; Reserved Vector ; LDR PC, IRQ_Addr LDR PC, [PC, #-0x0FF0] ; Vector from VicVectAddr LDR PC, FIQ_Addr Reset_Addr DCD Reset_Handler Undef_Addr DCD Undef_Handler SWI_Addr DCD SWI_Handler PAbt_Addr DCD PAbt_Handler DAbt_Addr DCD DAbt_Handler DCD 0 ; Reserved Address IRQ_Addr DCD IRQ_Handler FIQ_Addr DCD FIQ_Handler Undef_Handler B Undef_Handler SWI_Handler B SWI_Handler PAbt_Handler B PAbt_Handler DAbt_Handler B DAbt_Handler IRQ_Handler B IRQ_Handler FIQ_Handler B FIQ_Handler ; Reset Handler EXPORT Reset_Handler Reset_Handler ; Setup VPBDIV IF VPBDIV_SETUP <> 0 LDR R0, =VPBDIV LDR R1, =VPBDIV_Val STR R1, [R0] ENDIF ; Setup PLL IF PLL_SETUP <> 0 LDR R0, =PLL_BASE MOV R1, #0xAA MOV R2, #0x55 ; Configure and Enable PLL MOV R3, #PLLCFG_Val STR R3, [R0, #PLLCFG_OFS] MOV R3, #PLLCON_PLLE STR R3, [R0, #PLLCON_OFS] STR R1, [R0, #PLLFEED_OFS] STR R2, [R0, #PLLFEED_OFS] ; Wait until PLL Locked PLL_Loop LDR R3, [R0, #PLLSTAT_OFS] ANDS R3, R3, #PLLSTAT_PLOCK BEQ PLL_Loop ; Switch to PLL Clock MOV R3, #(PLLCON_PLLE:OR:PLLCON_PLLC) STR R3, [R0, #PLLCON_OFS] STR R1, [R0, #PLLFEED_OFS] STR R2, [R0, #PLLFEED_OFS] ENDIF ; PLL_SETUP ; Setup MAM IF MAM_SETUP <> 0 LDR R0, =MAM_BASE MOV R1, #MAMTIM_Val STR R1, [R0, #MAMTIM_OFS] MOV R1, #MAMCR_Val STR R1, [R0, #MAMCR_OFS] ENDIF ; MAM_SETUP ; Memory Mapping (when Interrupt Vectors are in RAM) MEMMAP EQU 0xE01FC040 ; Memory Mapping Control IF :DEF:REMAP LDR R0, =MEMMAP IF :DEF:RAM_MODE MOV R1, #2 ELSE MOV R1, #1 ENDIF STR R1, [R0] ENDIF
Listato - Codice di startup |
La direttiva AREA è, invece, utilizzata dal linker per mettere la tabella dei vettori al suo corretto indirizzo di partenza. In modalità denominata single chip si utilizza sempre l’indirizzo 0x00000000, tuttavia, se si utilizza il bus esterno e si vuole fare il boot dalla memoria esterna, la tabella dei vettori deve necessariamente localizzata all’indirizzo 0x80000000. La tabella dei vettori usa, poi, una istruzione LDR per caricare una costante di 32 bit nel program counter da una tabella di costanti che è posta immediatamente dopo la tabella dei vettori. In conseguenza di questo, la tabella dei vettori richiede i primi 64 byte di memoria; in questo modo è possibile utilizzare una istruzione di salto in luogo di una istruzione LDR. La normale istruzione di salto permette solo di indirizzare un campo di valori nell’ordine di ±MB. Con l’istruzione LDR è possibile far saltare il program counter, invece, in qualsiasi posizione nell’intervallo di valori di 4 GB dell’ARM7. E’ possibile anche notare che il vettore 0x00000014 è riempito con l’opcode NOP. Ogni modo operativo ha un unico registro R13, anche se ci sono effettivamente sei stack nel core. Dopo il reset, il codice di startup inizializzare l’intero sistema prima di cedere il controllo alla parte scritta in C. La strategia utilizzata dal compilatore è quella di posizionare le variabili come mostrato in figura 4.
Il codice di startup configura ogni differente modo dell’ARM7 e carica il registro R13 con l’indirizzo di partenza di ogni stack, come è posto in evidenza nel listato 2.
LDR R0, =Stack_Top ; Enter Undefined Instruction Mode and set its Stack Pointer MSR CPSR_c, #Mode_UND:OR:I_Bit:OR:F_Bit MOV SP, R0 SUB R0, R0, #UND_Stack_Size ; Enter Abort Mode and set its Stack Pointer MSR CPSR_c, #Mode_ABT:OR:I_Bit:OR:F_Bit MOV SP, R0 SUB R0, R0, #ABT_Stack_Size ; Enter FIQ Mode and set its Stack Pointer MSR CPSR_c, #Mode_FIQ:OR:I_Bit:OR:F_Bit MOV SP, R0 SUB R0, R0, #FIQ_Stack_Size ; Enter IRQ Mode and set its Stack Pointer MSR CPSR_c, #Mode_IRQ:OR:I_Bit:OR:F_Bit MOV SP, R0 SUB R0, R0, #IRQ_Stack_Size ; Enter Supervisor Mode and set its Stack Pointer MSR CPSR_c, #Mode_SVC:OR:I_Bit:OR:F_Bit MOV SP, R0 SUB R0, R0, #SVC_Stack_Size ; Enter User Mode and set its Stack Pointer MSR CPSR_c, #Mode_USR MOV SP, R0 SUB SL, SP, #USR_Stack_Size
Listato 2 -Setup stack |
Di conseguenza, oltre alla definizione della tabella dei vettori l’utilizzatore deve anche configurare lo stack. Questa operazione può essere fatta modificando il codice di startup direttamente o per mezzo di un’interfaccia grafica fornita dalla Keil, figura 5.