Sistemi di sviluppo per LPC2000

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.

Figura 1: architettura di questo ambiente di sviluppo

Figura 1: 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.

Figura 2: schema a blocchi del microcontrollore

Figura 2: 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.

Figura 3: la struttura dell’IDE dopo la sua installazione.

Figura 3: 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.

Figura 4: posizionamento delle variabili da parte del compilatore.

Figura 4: posizionamento delle variabili da parte del compilatore.

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.

Figura 5: interfaccia grafica.

Figura 5: interfaccia grafica.

 

 

Scrivi un commento

EOS-Academy