LABORATORIO DI ARCHITETTURA DEGLI ELABORATORI
UNI-PD-PXA
Scheda basata su processore Intel PXA255
Descrizione del sistema UNI-PD-PXA ed esercizi
Indice
Introduzione
CAPITOLO I
1.1
1.2
1.3
1.4
Architettura ARM e architettura Xscale
Nota storica
Il consorzio ARM
L’architettura ARM
L’architettura Intel Xscale
Caratteristiche principali del core Xscale
CAPITOLO II
2.1
2.2
2.3
2.4
2.5
2.6
2.7
2.8
2.9
2.10
2.11
2.12
2.13
2.14
2.15
2.16
2.17
2.18
2.19
2.20
2.21
2.22
2.23
5
Il chip Intel PXA255
Il PXA255 – “System on chip”
Il PXA255 – Il memory controller
Il PXA255 – I clock e la gestione dell’alimentazione
Il PXA255 – Il modulo universal serial bus client (USB)
Il PXA255 – Il controller DMA (DMAC)
Il PXA255 – Il controller LCD
Il PXA255 – Il controller AC97
Il PXA255 – Il controller Inter-IC Sound (I2S)
Il PXA255 – Il controller Multimedia Card (MMC)
Il PXA255 – La porta a infrarossi (Fast Infra Red - FIR)
Il PXA255 – Il controller ‘Synchronous Serial Protocol’ (SSPC)
Il PXA255 – L’interfaccia Inter Integrated Circuit (I2C)
Il PXA255 – I pin General Purpose I/O (GPIO)
Il PXA255 – Le porte UART
Il PXA255 – La porta Full Function UART (FFUART)
Il PXA255 – La porta Bluetooth UART (BTUART)
Il PXA255 – La porta standard UART (STUART)
Il PXA255 – La porta hardware UART (HWUART)
Il PXA255 – Il Real Time Clock (RTC)
Il PXA255 – Gli OS timer
Il PXA255 – Il Pulse Width Modulator (PWM)
Il PXA255 – L’Interrupt control
Il PXA255 – La porta Synchronous Serial Protocol (SSP)
7
7
8
8
11
13
15
15
16
16
17
17
17
17
18
18
18
18
18
19
19
19
19
19
19
20
20
20
20
20
2
CAPITOLO III
3.1
3.2
3.3
3.4
3.5
3.6
3.7
3.8
3.9
3.10
L’architettura della scheda
Connessioni e preparazione della scheda al power on
Power on
La memoria residente on board
Il CODEC audio UCB1400
La Compact Flash
La Multimedia Card
Le porte seriali
I connettori di espansione (J6, J7)
L’interfaccia LCD
CAPITOLO IV
4.1
4.2
4.3
La Scheda UNI-PD-PXA
L’Ambiente di sviluppo su PC
21
23
23
23
24
25
25
25
26
27
27
29
L’ambiente di sviluppo del software
30
Gli strumenti di sviluppo del software
30
Interazione con il target e fase di debug del codice sviluppato 43
CAPITOLO V
Esercizi
5.1
Primo programma (somma di due numeri)
5.2 Secondo programma (somma di due numeri)
5.3 Terzo programma (somma di due numeri)
5.4 Somma degli elementi di un vettore
5.5 Somma degli elementi di un vettore (subroutine)
5.6 Somma degli elementi di un vettore (subroutine ricorsiva)
5.7 Rovesciamento di una stringa di caratteri
5.8 Rovesciamento di una stringa di caratteri (con sp allineato)
5.9 Ordinamento di un vettore (merge-sort)
5.10 Il pannello frontale: i led e gli switch
5.11 Il pannello frontale: i pulsanti
5.12 Il display LCD
5.12.1 Disegnare a 16 colori
5.12.2 Disegnare a 256 colori
5.12.3 Scrivere caratteri sul display
5.13 Libreria per l’uso del display LCD
53
53
56
58
60
62
65
68
71
73
77
80
83
84
87
89
95
3
BIBLIOGRAFIA
111
APPENDICE: Mappa di memoria, layout e schemi elettrici della scheda
113
4
Introduzione
Questo testo intende fornire al lettore le conoscenze di base per poter operare in
completa autonomia su una stazione di sviluppo in cui il target è costituito da una
scheda basata su processore Intel PXA255 connessa ad un host computer tramite un
link seriale RS232 in ambiente GDB.
Si esamineranno le varie parti costituenti la stazione di sviluppo dando ovviamente
maggiore enfasi al target, ma senza trascurare i vari ambienti software su cui ci si
basa per la scrittura e il debug dei vari programmi.
Dopo aver fornito le informazioni di base necessarie, relative alle diverse parti, si
prenderanno in esame alcuni esempi di cui, oltre a fornire la descrizione, si analizzerà
il codice (normalmente scritto in linguaggio assembly) e si descriveranno le operazioni
di download sul target e di debug.
Si ritiene utile iniziare presentando una figura rappresentativa della stazione di sviluppo. Nei capitoli successivi si identificheranno e analizzeranno le varie parti componenti la stazione.
Poiché questo intende essere un manuale d’uso di una scheda basata su core ARM,
piuttosto che una descrizione tecnica dei suoi componenti, il livello di dettaglio della
descrizione sarà inevitabilmente tale da richiedere a volte approfondimenti da effettuare su testi di cui si forniranno le indicazioni bibliografiche.
Anche se la suddivisione del testo è stata fatta in 5 capitoli, gli obiettivi sono essenzialmente due:
-
il primo obiettivo (cap. I, II, III) è acquisire familiarità con la stazione di lavoro
(PC + scheda UNI-PD-PXA) e con l’ambiente di sviluppo (GNU – GDB);
-
il secondo obiettivo (cap. IV e V) è di guidare il lettore nell’uso in laboratorio della
scheda UNI-PD-PXA, fornendo sia le informazioni relative agli strumenti software
da utilizzare, sia alcuni esempi da collaudare.
5
Eliminato:
Stazione di Lavoro
Il sistema è composto da:
• PC di mercato con
Windows 98/2000/XT
• Scheda UNI-PD_PXA
• Alimentatore ac/dc per la
scheda (non visualizzato)
Vista posteriore PC
Connessioni PC:
• Cavo Jtag per programmazione
• Cavo Rs232 per programmazione e
debug
• Cavo di alimentazione AC
• Cavi per mouse, keyboard e CRT
Vista posteriore UNI-PD-PXA
Connessioni PC:
• Cavo Rs232 per programmazione e debug
• Alimentazione 9V DC
Porte disponibili:
• USB tipo B
• RS232
• RS485
6
CAPITOLO I
Architettura ARM e architettura Xscale
Nota storica
Il core Xscale è il primo RISC ARM nato in casa Intel.
Il primo RISC in assoluto è nato nel 1985 dalla ACORN Computer Group. Nel 1997 il
RISC della ACORN debutta come primo processore RISC utilizzato in computer a
basso costo. Il termine RISC sta per ‘Reduced Instruction Set Computer’. Nelle macchine RISC ogni istruzione viene decodificata per pilotare direttamente le risorse
hardware e mediamente ogni istruzione viene eseguita in un solo ciclo di clock.
Nel novembre 1990 viene costituito il consorzio ‘ADVANCED RISC MACHINES’
(ARMTM) dalla APPLE, ACORN e VLSI technology. Nel 1991 la ARM introduce il suo
primo core RISC embedded: l’ARM6.
Gli obiettivi di ARM sono le prestazioni elevate, l’alta integrazione ed il basso consumo
dei componenti.
Nel 1998 la Intel acquisisce la Digital Equipment Corporation, ereditando così la tecnologia StrongarmTM ideata dalla Digital successivamente ad un accordo con la ARM,
dalla quale aveva ottenuto il permesso di modificare il core ARM mantenendone la
compatibilità software.
Il core Xscale è il primo core ARM completamente disegnato ed ottimizzato dalla Intel in tecnologia .18μm. Basate su questo core, la Intel ad oggi ha realizzato oltre
quattordici famiglie di prodotti diversi, ognuna delle quali è stata pensata per una fascia diversa del mercato. Il componente che è alla base del target descritto in questo
manuale è uno di questi quattordici ed è siglato PXA255.
7
Eliminato: costituiti
1.1
Il consorzio ARM
Il consorzio ARM progetta sistemi e microprocessori innovativi che poi fornisce alle
maggiori compagnie mondiali del settore, che a loro volta le implementano nel proprio
silicio.
La architettura ARM negli anni ha monopolizzato il mercato giungendo, nel 2001, a coprire oltre il 75% delle applicazioni RISC a 32 bit.
1.2
L’architettura ARM
Il set di istruzioni della architettura ARM è evoluto significativamente da quando è
stato introdotto la prima volta e continuerà ad evolvere in futuro.
Si sono succedute negli anni 5 versioni del set di istruzioni, denotate da un numero incrementale da 1 a 5.
Oltre al numero della versione, a volte si evidenziano delle lettere aggiuntive che indicano la presenza di altre istruzioni addizionali.
Le 5 versioni del set di istruzioni della architettura ARM sono:
Versione 1: Questa versione non è mai stata usata in prodotti commerciali. Essa contiene:
• Le istruzioni di base (non include la moltiplicazione)
• Istruzioni di lettura e scrittura per byte, word e multi-word
• Istruzioni di salto
• Istruzione di interrupt software
La prima versione del set di istruzioni ARM aveva indirizzi da 26 bit.
Versione 2: Con questa versione si sono avute le seguenti estensioni:
• Istruzioni di moltiplicazione e di moltiplicazione con accumulo
• Supporto per coprocessore
• Due registri extra per il fast interrupt mode
• Istruzioni atomiche (indivisibili) di lettura-scrittura, chiamate SWP e SWPB
Anche la versione 2 e 2a avevano indirizzi da 26 bit.
Le versioni fino alla 2 sono oggi obsolete.
Versione 3: Con questa versione gli indirizzi sono da 32 bit e, di conseguenza, lo spazio di indirizzamento è stato esteso a 4GByte. Le informazioni di stato che precedentemente venivano memorizzate nel registro R15 sono state spostate su un nuovo registro ‘Current Program Status Register’ (CPSR) e sui ‘Saved Program Status Register’
(SPSR), che sono stati introdotti per preservare il contenuto di CPSR quando si veri8
fica una eccezione. Ne consegue che con la versione 3 si è avuto il seguente cambiamento:
•
Sono state introdotte due nuove istruzioni (MRS e MSR) per permettere
l’accesso a CPSR e agli SPSR
La versione 3 include anche due nuovi modi di operare, necessari a rendere possibile
l’uso delle eccezioni di data abort, prefetch abort e undefined instruction direttamente dal sistema operativo.
Versione 4: Con questa versione sono state create le seguenti estensioni:
• Istruzioni di lettura e scrittura di halfword (16 bit)
• Istruzioni di lettura con estensione del segno, di byte e halfword
• Nella versione T è stata introdotta una istruzione necessaria al trasferimento
in thumb mode
• Un nuovo modo privilegiato che usa i registri del modo user
Versione 5: Con questa versione sono state create le seguenti estensioni:
• E’ stato migliorato il passaggio dal modo normale al modo thumb
• Consente l’uso delle stesse tecniche di generazione del codice sia nella modalità
thumb che nella modalità non thumb
• E’ stato introdotta una istruzione di breakpoint software
• Sono state introdotte più opzioni per l’uso di coprocessori
Oltre alle precedenti versioni, sono stati introdotte anche delle varianti (di volta in
volta indicate con delle lettere).
Il set di istruzioni Thumb (variante T): Il set di istruzioni thumb è un subset del
set di istruzioni ARM. Le istruzioni thumb hanno una dimensione che è pari alla metà
della dimensione delle istruzioni ARM (16 bit invece che 32). Il risultato che ne deriva
è che normalmente può essere ottenuta una maggiore densità di codice utilizzando il
set Thumb invece che il set ARM.
Utilizzando il set di istruzioni Thumb si hanno due limitazioni:
•
Per la realizzazione della stessa funzione, il codice thumb usa più istruzioni del
codice ARM e quindi il codice ARM è meglio indicato per massimizzare le prestazioni di un codice con criticità di tempi di esecuzione.
•
Il set di istruzioni Thumb non include alcune istruzioni necessarie per la gestione delle eccezioni.
La presenza del set di istruzioni Thumb è denotata dalla lettera T (non si applica alle
versioni antecedenti alla versione 4).
Istruzioni di moltiplicazione con risultato a 64 bit (variante M): La variante M al
set di istruzioni ARM include 4 istruzioni che realizzano le operazioni 32x32 Æ 64 e
32x32 + 64 Æ 64 con accumulo. La presenza di queste istruzioni è denotata dalla lettera M.
9
La prima introduzione di queste istruzioni si è vista a partire dalla versione 3 del set
di istruzioni ARM.
Istruzioni DSP (variante E): La variante E del set di istruzioni ARM include un numero di istruzioni extra che migliorano le prestazioni del processore ARM, tipicamente in
applicazioni di digital signal processing.
Fig. 1.1 – Sommario delle architetture ARM
10
1.3
L’architettura Intel XScale
L’architettura Intel Xscale è compatibile con il core ARM v5TE. Basate su questo
core, esistono oggi diverse famiglie di prodotti Intel, come mostra la seguente figura:
Fig 1.2 - Prodotti Intel Xscale
Le caratteristiche principali di questo core possono essere così elencate:
• Tecnologia Intel pipelined RISC
• 32Kbyte di cache istruzioni, 32 Kbyte di cache dati e 2 Kbyte di mini-data
cache
• Tecnologia Intel ‘Media Processing’
• Tecnologia Intel wireless MMX
• Memory management unit dedicata (MMU)
• Monitoraggio delle prestazioni interno al core
• Power management e clock control unit interne al core
• Connessione full JTAG
• Istruzioni SIMD per l’elaborazione di dati multimediali
• Traslazioni veloci dagli indirizzi logici a quelli fisici con controllo della cache
• Miglior rapporto prestazioni/consumo grazie alle varie modalità di funzionamento con low power ottimizzato
11
Fig. 1.3 – Schema a blocchi del core Xscale
Fig. 1.4 – Rapporto Mips per Watt del core Xscale
12
1.4
Caratteristiche principali del core Xscale
Come già accennato, il core Xscale è compatibile ARM v5TE e può operare in uno fra
sette modi diversi: User, System, Supervisor, Abort, Undefined instruction, Fast Interrupt e Normal Interrupt. Esso comprende 16 registri a 32 bit (R0..R15), di cui R13
è lo stack pointer, R14 il link register e R15 il program counter.
Supporta sia la rappresentazione big endian che la rappresentazione little endian (nella versione del core integrata nel PXA255 è stata eliminata la rappresentazione big
endian). Il passaggio dall’una all’altra modalità di funzionamento è determinato impostando il bit B del Control Register (Coprocessore 15, registro 1, bit 7). Lo stato di
default al power on è little endian.
Rispetto allo standard ARM v5TE, il core Xscale implementa delle estensioni:
• E’ stato aggiunto un coprocessore DSP (CP0) che contiene un accumulatore a 40
bit e 8 nuove operazioni nello spazio coprocessore.
• Sono state aggiunte delle funzionalità al coprocessore 15 ed è stato creato il
coprocessore 14.
Ulteriori dettagli sulla architettura Xscale, nonché sul suo set di istruzioni aggiunte,
possono essere trovati nel documento Intel 278525-001.pdf, che può essere scaricato
direttamente dal sito Intel.
13
14
CAPITOLO II
Il chip Intel PXA255
2.1 Il PXA255 – “System on chip”
Lo scopo di questo capitolo è di fornire una breve descrizione di tutti i moduli presenti all’interno del PXA255. Per approfondimenti maggiori ci si può riferire ai documenti Intel 278796.pdf e 278693.pdf, direttamente scaricabili dal sito.
Fra i vari componenti basati su core Xscale, uno dei più diffusi è sicuramente il
PXA255.
Il PXA255 può essere definito un ‘sytem on chip’ che integra un elevato numero di
periferiche grazie alle quali risulta particolarmente adatto all’uso nel settore dei
cosiddetti ‘handheld devices’: palmari, telefoni cellulari e applicazioni portatili lowpower.
Fig. 2.1 – Architettura interna del PXA255
15
Come indicato in figura 2.1, l’architettura interna del PXA255, basato su core
Xscale, integra i seguenti componenti:
• Memory controller
• Clock e gestione dell’alimentazione
• Universal serial bus client (USB)
• DMA controller
• LCD controller
• Interfaccia AC97
• Interfaccia I2S (Inter IC Sound)
• Interfaccia MMC (Multimedia Card)
• Interfaccia FIR (Fast Infra Red)
• Interfaccia seriale sincrona
• Interfaccia I2C (Inter Integrated Circuit)
• General purpouse I/O
• 4 UARTs
• Real Time Clock
• OS timers
• PWM (Pulse Width Modulation)
• Interrupt control
2.2 Il PXA255 – Il memory controller
Il memory controller interno al PXA255 fornisce tutti i segnali di controllo
(senza alcun bisogno di elettronica aggiuntiva) per la gestione della maggior
parte delle memorie esterne che ad esso si volessero interfacciare. Tutti i
timing sui bus sono programmabili al fine di meglio adattarli alle varie esigenze
esterne. Esso supporta fino a 4 banchi di SDRAM. Sono presenti anche 6 segnali di chip select statici per interfacciare SRAM, SSRAM, Flash, ROM, SROM e
companion chip. Supporta anche due slot PCMCIA o, in alternativa, due slot per
compact flash.
2.3 Il PXA255 – I clock e la gestione dell’alimentazione
Tutti i blocchi interni al PXA255 sono pilotati dai clock derivati da un cristallo
di 3.6864 MHz e da un cristallo opzionale da 32.768 kHz.
Il cristallo da 3.6864 MHz pilota un PLL centrale ed uno periferico. Lo scopo di
equesti due PLL è di generare le frequenze necessarie ai vari blocchi interni.
Il cristallo da 32.768 kHZ fornisce una sorgente di clock esterna che deve essere abilitata dopo un reset hardware. Questo clock pilota un RTC interno, la
power control unit e l’interrupt controller.
16
Il cristallo da 32.768 kHZ è collegato ad una alimentazione separata, al fine di
garantire il clock attivo in condizioni di sleep mode della CPU.
Il modulo di gestione dell’alimentazione (power management) controlla la transizione tra le modalità turbo, normale, idle e sleep.
2.4 Il PXA255 – Il modulo universal serial bus client (USB)
Il modulo USB client è basato sulla specifica dello standard USB revisione 1.1.
Esso supporta fino a 6 connessioni endpoint e fornisce una frequenza da 48MHz
generata internamente.
Il controller USB implementa code FIFO con accesso in modalità DMA da e per
la memoria.
2.5 Il PXA255 – Il controller DMA (DMAC)
Il DMAC fornisce 16 canali per il servizio di richieste di trasferimento dati da
e per periferiche e fino a due richieste di trasferimento dati da companion chip
esterni.
Esso opera in flow-through mode quando realizza trasferimenti da periferiche
a memoria, da memoria a memoria e da memoria a periferiche. E’ compatibile
con periferiche che usano trasferimenti a word, halfword e byte.
2.6 Il PXA255 – Il controller LCD
Il controller LCD supporta sia LCD passivi (DSTN) che attivi (TFT) con un massimo di risoluzione di 640x480 con una profondità di 16 bit di colori.
Esso ha due canali DMA dedicati per supportare sia single che dual panel
display. Il modo passivo monocromatico supporta fino a 256 livelli di grigio mentre il modo passivo a colori supporta fino a 65536 colori. Il modo attivo ne supporta fino a 65536.
2.7 Il PXA255 – Il controller AC97
Il controller AC97 supporta i CODEC in revisione 2.0. Questi CODEC possono
operare fino ad una frequenza di campionamento di 48KHz. Il controller fornisce 16 canali indipendenti per PCM stereo in, PCM stereo out, Modem in, Modem
out e microfono mono. Ogni canale include una FIFO che supporta accessi in
modalità DMA alla memoria.
17
2.8 Il PXA255 – Il controller Inter-IC Sound (I2S)
Il controller I2S fornisce un collegamento seriale a CODEC standard per suono
digitale stereo. Esso supporta sia I2S in formato normale che I2S in formato
MSB-giustificato e fornisce 4 segnali per connessioni a un CODEC I2S.
I segnali del controller I2S sono multiplexati con i pin del controller AC97. Il
controller include una FIFO che supporta accessi in memoria in modalità DMA.
2.9 Il PXA255 – Il controller Multimedia Card (MMC)
Il controller MMC fornisce una interfaccia seriale per memory card standard.
Esso supporta fino a due card in MMC o in SPI mode con un transfer rate fino a
20 Mbps. Anche il controller MMC ha una FIFO che supporta accessi in memoria in modalità DMA.
2.10 Il PXA255 – La porta a infrarossi (Fast Infra Red - FIR)
La porta di comunicazione FIR è basata sullo standard ‘4 Mbps Infrared Data
association (IrDA) Specification’. Essa opera in half duplex ed ha una FIFO per
accessi in memoria in modalità DMA. La porta di comunicazione FIR usa i pin di
trasmissione e ricezione della porta STUART per la connessione ai transceiver
IrDA.
2.11 Il PXA255 – Il controller ‘Synchronous Serial Protocol’ (SSPC)
Il controller SSP implementa una interfaccia seriale sincrona che può operare
fino ad un transfer rate di 1.84MHz a partire da 7.2kHz.
Essa supporta i seguenti standard:
• National Semiconductor’s Microwire
• Texas Instruments Synchronous Serial Protocol
• Motorola’s Serial Peripheral Interface
Anche il controller SSPC ha una FIFO che supporta accessi in memoria in modalità DMA.
2.12 Il PXA255 – L’interfaccia Inter Integrated Circuit (I2C)
L’interfaccia I2C fornisce una connessione a due pin. Essa usa il primo pin per i
dati e gli indirizzi ed il secondo pin per il clock.
18
2.13 Il PXA255 – I pin General Purpose I/O (GPIO)
Ogni pin GPIO (General Purpose I/O) può essere programmato come uscita o
come ingresso. Gli input possono generare interrupt programmabili sul fronte di
salita o sul fronte di discesa. I GPIO primari non sono condivisi con altre periferiche mentre i GPIO secondari hanno una funzione alternativa che può essere
rimappata verso le periferiche.
2.14 Il PXA255 – Le porte UART
Il PXA255 integra 3 porte UART. Ogni porta UART può essere usata come slow
infrared port (SIR).
2.15 Il PXA255 – La porta Full Function UART (FFUART)
Il baud rate della porta FFUART è programmabile fino a 230 Kbps. Essa integra
il set completo di pin per il controllo del modem.: nCTS, nRTS,nDSR, nDTR, nRI
e nDCD. Anche questa porta ha una FIFO per accessi in modalità DMA in memoria.
2.16 Il PXA255 – La porta Bluetooth UART (BTUART)
Il baud rate della porta BTUART è programmabile fino a 921Kbps. La porta
BTUART integra un set parziale di pin per il controllo di un modem: nCTS e
nRTS. Gli altri pin di controllo di una eventuale porta modem possono essere implementati attraverso pin GPIO. Anche questa porta ha una FIFO per accessi in
modalità DMA in memoria.
2.17 Il PXA255 – La porta standard UART (STUART)
Il baud rate della porta standard STUART è programmabile fino a 230Kbps.
Questa porta non ha nessun pin di controllo per modem. Essi possono essere implementati tutti attraverso dei general purpouse I/O. Anche questa porta ha
una FIFO per accessi in modalità DMA in memoria.
2.18 Il PXA255 – La porta hardware UART (HWUART)
Il PXA255 ha anche una porta UART con controllo di flusso hardware. Questa
porta ha un set parziale di pin di controllo modem: nCTS e nRTS. Attraverso
questi pin di controllo c’è la possibilità di fornire un ‘full hardware control’. Il
baud rate di questa porta è programmabile fino a 921.6 Kbps. I pin di questa
porta sono multiplexati con alcuni pin di controllo della PCMCIA. Per questa ra19
gione la porta HWUART opera alla stessa tensione del bus. Inoltre, poiché il pin
nPWE della porta PCMCIA è usato per la realizzazione dell’I/O a latenza variabile (VLIO), se questo pin si usa per la porta HWUART, VLIO non è utilizzabile.
2.19 Il PXA255 – Il Real Time Clock (RTC)
Il real time clock può essere agganciato ad entrambi i cristalli di quarzo.
Comandando il real time clock con il quarzo da 32.768 kHz, si ha un minor consumo durante la modalità sleep mode. Comandando il real time clock con il quarzo da 3.68 Mhz si perde questo vantaggio ma si usa un solo quarzo invece di due.
L’RTC fornisce una frequenza d’uscita costante a cui è associato un registro di
allarme programmabile. Questo registro può essere utilizzato per ‘risvegliare’ il
processore dallo sleep mode.
2.20 Il PXA255 – Gli OS timer
Gli OS timer possono essere utilizzati per realizzare un contatore con frequenza di ingresso di 3.68 MHz a cui sono associati 4 registri di comparazione. Questi ultimi possono essere configurati per generare interrupt. Uno di loro può essere utilizzato anche per la realizzazione di un watch dog hardware.
2.21 Il PXA255 – Il Pulse Width Modulator (PWM)
Il PWM ha due uscite indipendenti che possono essere programmate per pilotare due GPIO. Sia la frequenza che il duty cycle possono essere programmati indipendentemente. Ad esempio, uno può essere utilizzato per il controllo del
contrasto dell’LCD e l’altro per il controllo della luminosità.
2.22 Il PXA255 – L’Interrupt control
Il controllore degli interrupt dirige gli interrupt del processore agli ingressi
IRQ e FIQ. La maschera degli interrupt abilita o disabilita individualmente le
sorgenti di interrupt.
2.23 Il PXA255 – La porta Synchronous Serial Protocol (SSP)
Il PXA255 ha una porta SSP ottimizzata per connessioni ad altri network ASICs. E’ quindi possibile mandare il pin TX in alta impedenza e controllare la direzione dello stesso facendolo passare dalla funzione TX alla funzione RX.
Questa porta non è multiplexata con altre periferiche.
20
CAPITOLO III
La scheda UNI-PD-PXA
La scheda UNI-PD-PXA è stata ideata e progettata con lo scopo di rendere disponibile, presso i laboratori didattici del Dipartimento di Ingegneria
dell’Informazione dell’Università di Padova, un target basato su un core RISC
all’avanguardia e di grande diffusione nei sistemi embedded: il core ARM. Il
componente scelto è il PXA255 che, oltre ad integrare il più moderno core ARM
oggi disponibile (XScale), presenta un elevato numero di interfacce interne per
dispositivi periferici che lo rendono particolarmente adatto ad un ambiente didattico, perché consente di realizzare applicazioni sui più diffusi dispositivi digitali oggi disponibili: controller LCD, PCMCIA, AC97, USB, seriali sincrone, I2C
bus ecc.
Al fine di consentirne un facile uso in laboratorio, la scheda è stata inserita in
un box e munita di alcune interfacce e di alcuni dispositivi che possono essere
utili allo studente nella realizzazione sia di piccoli esempi che di eventuali applicazioni più complesse: display LCD STN 320x240 con 16 bit di profondità di colore, 16 microswitch mappati in memoria e direttamente utilizzabili, 16 LED, 2
pulsanti direttamente connessi a dei pin general purpouse I/O, un CODEC audio,
una interfaccia per schede compact flash, 4 porte seriali, un USB client, due
connettori a cui si può accedere rimovendo il coperchio in plexiglas presente sul
box e ai quali afferiscono tutti i segnali di bus del PXA255, nonché i segnali delle interfacce che non sono state rese direttamente disponibili all’esterno del
box.
La seguente figura presenta una immagine della scheda. Nei paragrafi successivi se ne descriveranno le varie parti.
21
22
3.1 L’architettura della scheda
LCD BUS
J LCD
J SPEAKER
AC97
AUDIO
AMPL
J JTAG
FF/BUS
J AUDIO IN
1
J 232
J PWR IN
J INVERTER
CODEC
J MIC IN
J AUDIO OUT
VCC
VCORE
3V3
SD CARD
LDO-REG
CODEC
LDO-REG
CF
LDO-REG
J TOUCH
J EXT
BT/BUS
J 485
PXA
255
SD
RAM
FLASH
SRAM
245
J 232
244
2
374
CONTROL
REG
(2X) 232 XCEIVER
RESET
SWITCH
485 XCEIVER
MMC/BUS
DATA
BUS
USB/BUS
ADD
BUS
J SD CARD
FLASH
SRAM
IR/BUS
J IRDA
BUS L
BUS H
J BATT
J CF
J DIP SWITCH
J DIP SWITCH
SD
RAM
245
SSP/BUS
J RADIO
244
J USB B
245
DIP
SW
374
LED
SCHEDA IO
Da come appare dallo schema a blocchi, il cuore di tutta la scheda è il PXA255. Va notato che tutta l’elettronica esistente esternamente al PXA255 è ‘passiva’ (non richiede alcuna configurazione) ad eccezione del CODEC audio UCB1400.
3.2 Connessioni e preparazione della scheda al power on
Per alimentare la scheda, bisogna individuare il connettore di alimentazione posto sul
retro del box. A questo connettore deve pervenire una tensione continua nel range
7.5V-15V e con il positivo interno.
Nota tecnica: Qualora venissero utilizzati alimentatori diversi da quelli forniti in dotazione, si suggerisce di verificare che non abbiano un eccessivo ripple di tensione (0.4V
max) e che siano in grado di erogare una corrente continua di circa 1.5 A.
3.3 Power on
Una volta predisposto quanto descritto al punto 3.2, si è pronti ad accendere la scheda agendo sull’apposito interruttore. Si descrive ora sinteticamente quanto avviene
subito dopo aver dato alimentazione.
•
Portando l’interruttore sulla posizione ‘on’, partendo da una tensione in ingresso
che può variare nel range 7.5V-15V, si alimenta la scheda secondo le temporiz23
zazioni richieste dai dispositivi afferenti alle varie sezioni di alimentazione (5V,
3.3V e 1.4V). Si può prendere visione della avvenuta accensione, verificando che
il led verde posto sul frontale sia acceso.
•
Al power on, dopo il fronte di salita del segnale di reset, viene iniziata
l’esecuzione della istruzione situata all’indirizzo 0 di memoria. A questo stesso
indirizzo è mappato il segnale chip select CS0, per cui l’accesso all’indirizzo 0 da
parte del processore (per il fetch della istruzione da eseguire) ha anche
l’effetto di attivare il segnale select CS0. Questo segnale gestisce le memorie
flash e, tramite le impostazioni dei jumper JP2, JP3 e JP4, il PXA255, al reset,
viene informato sul tipo di flash utilizzate (16 bit, 32 bit, asincrone, sincrone,
ecc.). In questa fase non è ancora disponibile alcun tipo di RAM e quindi non si
possono utilizzare istruzioni che ad esempio dovessero fare uso dello stack
pointer. La scheda può essere riportata in questo stato o spegnendola o agendo
sul pulsante di reset. Per evitare reset accidentali, questo pulsante è interno e
va raggiunto con una punta (ad esempio la punta di una matita).
•
Secondo quanto dettato dallo standard ARM, l’indirizzo zero contiene
l’istruzione di salto all’entry point del programma di inizializzazione dei vari dispositivi interni ed esterni al PXA255, il quale deve provvedere poi a passare il
controllo della CPU all’eventuale sistema operativo o, come nel caso delle schede
UNI-PD-PXA presenti in laboratorio, al monitor ‘REDBOOT’ di cui si analizzeranno in seguito le caratteristiche.
3.4 La memoria residente on board
La memoria con cui è stata equipaggiata la scheda è costituita da 32Mbyte di strataflash organizzata per 32 bit, 64Mbyte di SDRAM organizzata per 32 bit e 512Kbyte
di SRAM organizzata per 32 bit.
La RAM statica è stata gestita attraverso il chip select CS1 e può essere tenuta sotto
tampone collegando il jumper J1 ad una batteria nel range (2.9V..3.6V).
Al power-on l’unica memoria disponibile è la flash, in quanto per poter utilizzare sia la
SDRAM che la SRAM è necessario eseguire prima (su memoria flash) il codice che
provvede ad effettuare tutte le impostazioni necessarie per definire le modalità
(programmabili) di accesso del processore alle memorie e ai dispositivi interni al
PXA255. Per gli scopi di questo manuale è sufficiente sapere che attualmente, sulle
stazioni installate, le memorie e tutti gli altri dispositivi vengono impostati dal monitor
Redboot che li rende immediatamente utilizzabili, anche se non agli indirizzi fisici ma
a quelli virtuali dettati dalla table vector della MMU:
FLASH:
SDRAM:
SRAM:
0x50000000..0x51FFFFFF
0x00000000..0x03FFFFFF
0x06000000..0x0607FFFF
(32MB)
(64MB)
(512KB)
24
Per quanto riguarda la SDRAM va considerato che, pur essendo eseguito su memoria
flash, il monitor Redboot ne fa uso di una piccola sezione e quindi si consiglia all’utente
di collocare i propri programmi nella regione (situata al di sopra del primo MB di
SDRAM), compresa tra i seguenti indirizzi:
0x00100000..0x03FFFFFF
3.5 Il CODEC audio UCB1400
L’UCB1400 è un CODEC audio stereo equipaggiato con un touch screen. L’interfaccia
verso la CPU è standard AC97 Rev. 2.1. Il suo ingresso stereo può essere connesso direttamente ad un microfono o ad un lettore CD. Sulla scheda UNI-PD-PXA l’ingresso è
stato direttamente collegato al connettore presente sul frontale. L’uscita può pilotare
una cuffia ma nel nostro caso è stato interposto un amplificatore audio del tipo
LM4881MM ed è stato inserito nel box scheda un piccolo altoparlante (mono). Oltre
al touch screen a 4 fili di tipo resistivo è anche presente un convertitore analogico digitale a 10 bit del tipo ad approssimazioni successive. La versione attuale del firmware
presente sulle schede UNI-PD-PXA non integra il driver di questo componente per cui
il lettore non ne può far uso per esercizi a meno che non lo configuri preventivamente.
Coloro che volessero approfondire le conoscenze di questo dispositivo o volessero
maggiori informazioni su come programmarlo, possono far riferimento al documento
numero 939775009611 (order number Philips) che può essere gratuitamente richiesto
alla Philips o scaricato dal sito.
3.6 La Compact Flash
Sulla scheda UNI-PD-PXA l’interfaccia Compact Flash è stata ricavata direttamente
dal PXA255 con l’esclusivo ausilio di due transceiver secondo quanto previsto nel documento intel 278694-001.pdf al paragrafo 2.6.6 nella modalità single slot. I segnali
di controllo non previsti direttamente sul PXA255 sono stati ricavati utilizzando dei
pin GPIO. Il connettore CF è presente sul lato destro del box scheda. Il firmware di
scheda non integra attualmente il driver CF.
Per ulteriori dettagli su come gestire lo standard CF, il lettore può far riferimento al
documento ‘CF+ and CompactFlash Specification Revision 1.4’.
3.7 La Multimedia Card
Il controller MMC presente nel PXA255 è compatibile con la specifica multimedia
card system versione 2.1 con l’unica eccezione che il trasferimento a singolo byte ed a
3 byte non è supportato. Sulla scheda UNI-PD-PXA è presente un socket per SD card
che però non presenta la relativa fessura sul bordo scheda. Qualora il lettore volesse
fare uso della interfaccia multimedia card può derivare i segnali necessari dai connet25
tori di espansione di cui parleremo più avanti. Il firmware di scheda non integra attualmente il driver MMC.
DC3P3V
SD Socket
J2
Bottom Mount
R99
5
7
5
DNI IF MMC
SA_MMCCLK
R102
6
0
DC3P3V
SA_MMDAT
8
CD
COMM
MMC_PWR
4
DC3P3V
R108
DNI
IF
SD
10
12
WP
11
DAT1
3
47K
DNI
IF
SD
C46
+
4.7uF 35V
CHECK !!
DAT0
U13
SA_MMCMD
0.1UF C61
CLK
DC3P3V
2
MIC5207-3.3BM5
3.3V LDO REG
180ma
VOUT
VIN
GND
4
BYP
EN
VCC
1
2
3
MMC_ON
LE33
C47
270PF
MIC5207-3.3BM5
100K
VDD
VSS2
MMC_CS0
R107
VSS1
T7
1
0
CMD
4.7K
9
R105
DAT2
CD_DAT3
nMMC_DETECT
MMC_WP
100K
DNI
IF
MMC
R113
100K
R112
DC3P3V
3.8 Le porte seriali
RS485
RS232BT
RS232FF
Le porte seriali asincrone presenti nel PXA255 sono 4; per 3 di esse i relativi segnali
sono stati riportati su altrettanti connettori posti nella parete posteriore del box
contenente la scheda UNI-PD-PXA. La porta seriale full modem (FFUART) è stata
connessa al connettore a vaschetta (CANON a 9 pin - Standard RS232) posto sul retro del box in prossimità dell’interruttore di accensione. Fra i segnali del PXA255 ed il
connettore è stato interposto un ICL3244ECAI al fine di adattare i livelli di tensione
allo standard RS232. Attualmente la connessione con l’host computer avviene attraverso questa porta. La seconda porta è anche denominata BTUART dove BT sta per
blutooth, per indicare che il suo transfer rate può essere programmato esattamente
uguale al transfer rate di una connessione blutooth. Sulla scheda UNI-PD-PXA anche
questa porta è stata connessa ad un secondo connettore a vaschetta posto sul retro.
Mentre la FFUART presenta tutti i segnali di controllo necessari ad esempio per la
connessione con un modem, la BTUART ha solo due segnali di controllo il CTS e l’RTS.
26
I connettori a vaschetta sono stati collegati secondo lo standard RS232 e quindi possono essere utilizzati dei cavi per connessioni seriali standard PC.
Con la terza porta seriale del PXA255 è stata realizzata una connessione RS485 resa
disponibile sul retro della scheda.
La quarta porta seriale (HWUART) è stata diretta sui connettori di espansione della
scheda.
3.9 I connettori di espansione (J6, J7)
La scheda UNI-PD-PXA è stata munita di due connettori di espansione (J6,J7) facilmente raggiungibili rimovendo il coperchio trasparente in plexiglas. Attraverso questi
connettori viene reso disponibile il bus del PXA255 opportunamente isolato e le periferiche non esternate sul box.
J6
GFX_IRQ_CF_BVD2
DC3P3V
EXT_D15
EXT_D13
EXT_D11
EXT_D9
EXT_D7
EXT_D5
EXT_D3
EXT_D1
EXT_A0
EXT_A2
EXT_A4
EXT_A6
EXT_A8
EXT_A10
EXT_nOE
EXT_RD_nWR
SA_MMDAT
DC3P3V
MMC_ON
nMMC_DETECT
DC3P3V
1
3
5
7
9
11
13
15
17
19
21
23
25
27
29
31
33
35
37
39
41
43
45
47
49
J7
2
4
6
8
10
12
14
16
18
20
22
24
26
28
30
32
34
36
38
40
42
44
46
48
50
STRIP 25X2 FEMMINA
nPCD1
nPCD0
EXT_D14
EXT_D12
EXT_D10
EXT_D8
EXT_D6
EXT_D4
EXT_D2
EXT_D0
RS232_VALID
SA_FF_RI
SA_FF_DCD
SA_FF_DSR
SA_FF_DTR
SA_FF_RTS
SA_FF_CTS
SA_FF_TXD
SA_FF_RXD
DC3P3V
DC3P3V
EXT_A1
EXT_A3
EXT_A5
EXT_A7
EXT_A9
EXT_A11
EXT_nWR
GPIO_0
GPIO_1
GPIO_10
GPIO_11
GPIO_19
GPIO_20
GPIO_21
GPIO_22
SA_nCS_3
DC3P3V
MMC_CS0
SA_MMCMD
SA_MMCCLK
VCC
DC3P3V
SA_nCS_4
PADDR_ENn
nCHRGR_PRESENT
VCC
1
3
5
7
9
11
13
15
17
19
21
23
25
27
29
31
33
35
37
39
41
43
45
47
49
2
4
6
8
10
12
14
16
18
20
22
24
26
28
30
32
34
36
38
40
42
44
46
48
50
SA_PWM_0
SA_SYNC
SA_SDATA_OUT
SA_SDATA_IN
SA_BITCLK
SA_nPOE
SA_nPWE
SA_nPIOR
SA_nAC97_RESET
DC3P3V
AC97_IRQ
SA_nPIOW
SA_nPCE_1
SA_nPCE_2
SA_PSKTSEL
SA_nPREG
SA_nPWAIT
SA_DQM_0
SA_DQM_1
DC3P3V
PRDY_BSYn
SA_nIOIS16
SA1111_IRQ_CF_BVD1
VCC
STRIP 25X2 FEMMINA
Grazie a ciò è possibile realizzare delle schede plug-in, non previste inizialmente, idonee alle applicazioni più generiche e utilizzabili direttamente sulla scheda UNI-PDPXA.
3.10 L’interfaccia LCD
La scheda UNI-PD-PXA presenta una interfaccia LCD general purpouse che consente
l’uso diretto di quasi tutti gli LCD disponibili sul mercato:
• STN single panel
• STN dual panel
• TFT
• Monocromatici
• Colori (fino a 16 bit di profondità)
• Risoluzione fino a 640x480
Nei box installati presso l’università di Padova è stato utilizzato un LCD STN
320x240 con 65536 colori collegato al connettore JP5.
27
Oltre al connettore JP5 utilizzato per l’LCD attuale, è presente il connettore J15 che
rende disponibile l’interfaccia LCD nella sua completezza, per cui sarebbe semplice
sostituire l’LCD attuale con altri tipi di LCD.
LCD Connector
J15
L_DD_0
L_DD_1
L_DD_2
L_DD_3
L_DD_4
DC3P3V
JP5
16
L_DD_7
L_DD_6
DC3P3V
L_DD_5
L_DD_4
XX
L_DD_3
L_DD_2
L_DD_1
POT
L_DD_0
15
14
13
12
11
10
9
8
7
10K LIN
6
DC3P3V
LCD_PWR_ON
YY
L_DD_5
L_DD_6
L_DD_7
L_DD_8
L_DD_9
L_DD_10
GND
CK
Hsync
Vsync
GND
R0
R1
R2
R3
R4
R5
GND
G0
G1
G2
G3
G4
G5
GND
B0
B1
B2
B3
B4
B5
GND
ENAB
Vcc
Vcc
R/L
U/D
V/Q
GND
R169
L_DD_11
L_DD_12
L_DD_13
L_DD_14
L_DD_15
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
R170
L_PCLK
L_LCLK
L_FCLK
L_PCLK
L_LCLK
L_FCLK
5
4
3
2
1
LCD-TFT
28
CAPITOLO IV
L’ambiente di sviluppo su PC
Lo scopo di questo capitolo è di descrivere:
-
l’ambiente di sviluppo del software,
gli strumenti utilizzati in questo ambiente e
l’interazione con il target in fase di debug del codice sviluppato.
Prima di iniziare la descrizione dei tre punti sopraccitati è conveniente fare una premessa introduttiva, per giustificare alcune scelte:
Gli strumenti di sviluppo del software, ovvero compilatore, assemblatore e linker fanno parte dei tool della famiglia “GNU” della quale fanno parte anche altri tipi di tool,
quali editor, parser, file-utility ecc… . Questi tool sono stati creati originariamente
per il sistema operativo “Linux” e, nel corso del tempo, sono stati “portati” (adattati)
ad altri sistemi operativi quali QNX e “win32”. In questo contesto particolare ci si riferisce alla versione per Win32; da notare però che le modalità di utilizzo ed i risultati ottenuti dai tool di sviluppo su un altro sistema operativo in sostanza non cambiano.
Gli strumenti “GNU” portati su sistemi operativi basati su “win32”, ovvero Windows
95-98-ME e windows-NT-2000-XP si adattano molto bene ad essere utilizzati in un
ambiente chiamato “CYGWIN”, nel quale sono stati “portati” un gran numero di tool
della GNU. Si ricorda comunque che l’utilizzo dell’ambiente “CYGWIN” non è strettamente necessario, ma è fortemente consigliato proprio per la presenza di questi ulteriori tool frequentemente utilizzati per lo sviluppo del software, non ultimo ad esempio il programma “make”.
29
4.1
L’ambiente di sviluppo del software
Come già accennato, l’ambiente in cui si opera è “CYGWIN”. CYGWIN permette di emulare l’ambiente LINUX in sistemi operativi tipo WIN32 ed è composto sostanzialmente da due entità:
-
Una libreria DLL (cygwin1.dll), la quale ha le funzioni di emulare le chiamate al sistema operativo “LINUX”.
Una serie di tool GNU , sviluppati in LINUX ed adattati all’ambiente WIN32.
In questo ambiente è possibile sviluppare software che fa uso delle API standard
WIN32 e/o delle API di CYGWIN; questo aspetto permette il “porting” di software
sviluppato in Linux o, più in generale, in Unix.
L’ambiente mette a disposizione uno shell “Bash” del tutto simile a quello che si trova
comunemente in Linux.
L’ambiente CYGWIN è supportato da tutte le versioni a 32 bit di Windows, tranne che
Windows CE.
Da esperienze pregresse si consiglia comunque di installare CYGWIN su un Sistema
Operativo Windows 2000 o Windows XP, dove si hanno prestazioni migliori in termini
di stabilità.
4.2
Gli strumenti di sviluppo del software
Di seguito si riporta una lista degli strumenti maggiormente utilizzati per lo sviluppo
del software di tipo “embedded”:
-
Compilatore;
Linker;
Assemblatore;
Debugger;
Make;
Il compilatore C della GNU, conosciuto come gcc (che è il comando per richiamare il
compilatore) è uno dei più versatili ed evoluti; in particolare, questo compilatore è stato adattato a moltissime piattaforme per esempio:
-
x86 per ambiente linux;
x86 per ambiente WIN32;
Solitamente gcc è utilizzato per sviluppo del software “nativo”: ad esempio, mediante
il compilatore gcc su x86 per Linux si sviluppa del codice destinato ad essere eseguito
sul medesimo processore x86 (per esempio pentium) equipaggiato con sistema operativo Linux.
30
Diversamente si procede, di solito, per lo sviluppo di software di tipo embedded: per
le schede UNI-PD-PXA, ad esempio, c’è la necessità di sviluppare del codice per un
processore tipo Xscale su un normale PC, ovvero su un host x86 equipaggiato con sistema operativo tipo WIN32 (per es. Windows 2000). Questa operazione viene chiamata “cross-compilazione”; anche la cross-compilazione viene supportata egregiamente
dai compilatori GNU.
Esistono diverse distribuzioni del compilatore gcc, tutte sostanzialmente equivalenti.
Con riferimento al cross-compilatore per target Xscale, su Host Win32 basato su
x86, sono di interesse le seguenti due distribuzioni, tra loro equivalenti:
-
i686-pc-cygwin-x-xscale-elf.zip ;scaricabile dal sito GnuPro toolKit;
XscaleElfInstallation.exe
;scaricabile dal sito della OCDEMON;
La prima versione è un file compresso tipo “zip”: la decompressione genera un
directory “xscale-020523” nel quale è contenuto tutto.
La seconda versione viene rilasciata da Ocdemon, produttore di tool hardware (quali
In Circuit Debugger, emulatori ecc… ). Questa versione consiste di un file eseguibile
il quale provvede automaticamente all’installazione (self-extracting). Questa versione
richiede l’esplicita presenza di CygWin, anche se, come già detto, non sarebbe strettamente necessaria.
Per entrambe le versioni, il comando per richiamare il compilatore è
xscale-elf-gcc
Nella prima versione, il directory in cui si trova il compilatore è:
C:\cygwin\xscale-020523\H-i686-pc-cygwin\bin
nella seconda versione, il directory è:
C:\cygwin\usr\local\xscale-elf\H-i686-pc-cygwin\bin
In questi directory, oltre al file “xscale-elf-gcc.exe”, si trovano i file contenenti i
programmi eseguibili di tutti gli altri tool,.
Il comando che richiama il cross-compilatore ha il seguente significato:
xscale-elf-gcc
il compilatore è per target Xscale
produce codice rilocabile tipo “ELF”
questo tool è il compilatore
(Gnu C Compiler)
31
Alcuni degli altri tool che interessano, sono:
Assemblatore:
Archiver:
Linker:
Listing symbol from obj file:
Display info from obj file:
xscale-elf-as
xscale-elf-ar
xscale-elf-ld
xscale-elf-nm
xscale-elf-objdump
Di seguito vengono riportati alcuni esempi di utilizzo dei tool partendo da codice sorgente fornito come esempio nella versione OCDEMON (vedi contenuto del directory
/usr/local/xscale-elf/H-i686-pc-cygwin/xscaleDemoLubbock), in modo da
illustrare le opzioni più utilizzate per poter iniziare a sviluppare del codice.
Si voglia compilare il programma situato nel file test.c, contenente il seguente codice sorgente in linguaggio C:
int varc, vard;
__gccmain()
{
testcode();
}
testcode()
{
int i, vara, varb, *ptr;
ptr = (int *)0xa00c000;
varc = 0x12345678;
for (i = 0; i < 0x100; i++)
{
vara = varb + 2;
varb = vara;
inc_var();
}
}
inc_var()
{
varc++;
vard = varc;
}
exit()
{
while(1);
}
atexit()
{
while(1);
}
32
Per generare il modulo oggetto a partire dal modulo sorgente contenuto nel file
test.c, si invoca il compilatore C, tramite il comando gcc con le seguenti opzioni:
xscale-elf-gcc
-c test.c
il modulo oggetto rilocabile, di tipo ELF viene generato nel file test.o.
E’ possibile constatare l’effettivo formato ELF analizzando il file mediante un editor
esadecimale e verificando che l’header riporti, nei primi 4 byte, la seguente sequenza:
7f 45 4c 46 (gli ultimi 3 byte rappresentano la stringa ELF in codifica ASCII).
Per ottenere il modulo eseguibile, nel file test, si invoca il linker in questo modo:
xscale-elf-ld –o test
test.o
si può constatare che il linker produce il seguente messaggio di warning:
Warning: cannot find entry symbol _start; defaulting to 00008000
Questo messaggio segnala che si è tentato di generare un file contenente del codice
assoluto, eseguibile dal processore, ma senza aver specificato ove si trova la prima istruzione da eseguire e senza aver dato alcuna indicazione di dove allocare la sezione
contenente il codice stesso.
Spesso si introduce un segmento di codice aggiuntivo con cui viene preparato
l’ambiente in grado di ospitare il codice prodotto dal compilatore C. Questo segmento
di codice è scritto in linguaggio assembly ed è contenuto in un file individuato di consuetudine con il nome
crt0.s
Di seguito si riporta il contenuto di crt0.s:
/* Sample initialization file */
.extern
.extern
__gccmain
exit
/* .text is used instead of .section .text so it works with arm-aout too*/
.text
.code 32
.align
0
.global
_mainCRTStartup
.global
_start
.global
start
start:
_start:
_mainCRTStartup:
/* Start by setting up a stack */
/* Set up the stack pointer to end of bss */
ldr r3, .LC2
mov sp, r3
sub sl, sp, #512
/* Still assumes 512 bytes below sl */
33
mov
mov
mov
a2, #0
fp, a2
r7, a2
/* Second arg: fill value */
/* Null frame pointer */
/* Null frame pointer for Thumb */
ldr
ldr
sub
a1, .LC1
a3, .LC2
a3, a3, a1
/* First arg: start of memory block */
/* Second arg: end of memory block */
/* Third arg: length of block */
mov
mov
r0, #0
r1, #0
/*
/*
bl
bl
mov
mcr
__gccmain
exit
/* Should not return */
r0,r1
p15,0,r0,c7,cr5,0
/* INVALIDATE ICACHE & BTB */
no arguments */
no argv either */
/* For Thumb, constants must be after the code since only
positive offsets are supported for PC relative addresses. */
.align 0
.LC1:
.word __bss_start__
.LC2:
.word __bss_end__
In questo segmento di codice normalmente sono specificate le seguenti fasi:
-
dichiarazione del label che individua il punto di partenza del segmento di codice “_start”;
inizializzazione dello stack-pointer del processore, normalmente alla fine del
segmento “bss”;
preparazione dei parametri da passare alla prima chiamata alla funzione ‘C’;
salto alla funzione ‘C’ e, nella maggior parte dei casi, senza ritornare a questo segmento.
Il file assembly crt0.s deve essere opportunamente assemblato in modo da creare
un modulo oggetto (nel file ctr0.o) e poi, mediante il linker, deve essere collegato al
modulo oggetto preparato precedentemente (in test.o), in modo da ottenere un modulo eseguibile utilizzabile dal processore. I comandi con cui si ottiene ciò sono i seguenti:
fase di assemblaggio:
xscale-elf-as
-o crt0.o
crt0.s
fase di costruzione del modulo eseguibile (nel file all) tramite l’unione (linking) dei 2
moduli oggetto:
xscale-elf-ld –o all
crt0.o test.o
Adesso il linker non genera più il messaggio di “warning”, in quanto i due file crt0.s e
test.c non lasciano irrisolto alcun label o riferimento, nemmeno il punto di partenza
del codice, _start, che si trova in crt0.s. L’indirizzo fisico di _start è stato asse34
gnato dal linker per default ancora al valore 0x8000, come si può verificare
dall’header ELF del file “all”:
Questi 4 byte indicano l’indirizzo
di allocazione del codice contenuto
nel file (secondo la convenzione
Little Endian)
In effetti la fase di linking necessita delle informazioni riguardanti le allocazioni in
memoria dei segmenti di codice e dati. Per questo motivo, usualmente, al linker viene
fornito anche un file contenente queste informazioni.
Si riporta di seguito un esempio di un tale file (file: ldscript):
SECTIONS
{
. = 0xa0000000;
.text : { *(.text) }
. += 0x10;
.data : { *(.data) }
. += 0x10;
.bss : { *(.bss) }
__bss_start__ = .;
. += 0x1000;
__bss_end__ = .;
. += 0x1000;
PROVIDE (__stack = .);
_end = .;
.debug_info
0 : {
.debug_abbrev
0 : {
.debug_line
0 : {
.debug_frame
0 : {
/*
.debug_str
0 : {
/*
.debug_loc
0 : {
/*
.debug_macinfo 0 : {
}
*(.debug_info) }
*(.debug_abbrev) }
*(.debug_line) }
*(.debug_frame) }
*(.debug_str) } */
*(.debug_loc) } */
*(.debug_macinfo) } */
In questo codice è possibile individuare l’informazione che specifica l’indirizzo iniziale
del segmento “text”, cioè del codice eseguibile, che viene definito all’indirizzo assoluto 0xa0000000;
Con il seguente comando viene eseguita la fase di linking utilizzando le informazioni
contenute in “ldscript”:
35
xscale-elf-ld
-o all –Tldscript crt0.o test.o
Esaminando il contenuto del file “all”, si può constatare l’effettiva allocazione del
codice all’indirizzo 0xa0000000:
0xa0000000 (Little Endian)
Nei casi in cui la struttura di allocazione delle sezioni “text”, “data” e “bss” è più semplice, è possibile usare le opzioni –Ttext ADDRESS, -Tdata ADDRESS e –Tbss ADDRESS per specificare gli indirizzi iniziali delle varie sezioni. Si riporta di seguito un
esempio:
xscale-elf-ld
-o all –Ttext 0xa0000000 crt0.o test.o
Come ulteriore verifica della correttezza dell’allocazione del segmento “text”, è possibile disassemblare il modulo eseguibile contenuto nel file “all”, mediante il comando:
xscale-elf-objdump
–D all
> all.asm
Con questo comando si ricostruiscono le istruzioni assembly simboliche, a partire dal
codice binario del modulo eseguibile.
Si ottiene come risultato il file all.asm, il cui contenuto è riportato qui di seguito:
all:
file format elf32-littlearm
Disassembly of section .text:
a0000000 <_mainCRTStartup>:
a0000000:
e59f3038
ldr
a0000004:
e1a0d003
mov
a0000008:
e24dac02
sub
a000000c:
e3a01000
mov
a0000010:
e1a0b001
mov
a0000014:
e1a07001
mov
a0000018:
e59f001c
ldr
a000001c:
e59f201c
ldr
a0000020:
e0422000
sub
a0000024:
e3a00000
mov
a0000028:
e3a01000
mov
a000002c:
eb000004
bl
a0000030:
eb000035
bl
a0000034:
e1a00001
mov
a0000038:
ee070f15
mcr
a000003c:
a0000174
andge
a0000040:
a0001174
andge
r3, [pc, #38]
; a0000040 <_mainCRTStartup+0x40>
sp, r3
r10, sp, #512
; 0x200
r1, #0
; 0x0
r11, r1
r7, r1
r0, [pc, #1c]
; a000003c <_mainCRTStartup+0x3c>
r2, [pc, #1c]
; a0000040 <_mainCRTStartup+0x40>
r2, r2, r0
r0, #0
; 0x0
r1, #0
; 0x0
a0000044 <__gccmain>
a000010c <exit>
r0, r1
15, 0, r0, cr7, cr5, {0}
r0, r0, r4, ror r1
r1, r0, r4, ror r1
36
a0000044 <__gccmain>:
a0000044:
e1a0c00d
a0000048:
e92dd800
a000004c:
e24cb004
a0000050:
eb000000
a0000054:
e91ba800
mov
stmdb
sub
bl
ldmdb
a0000058 <testcode>:
a0000058:
e1a0c00d
a000005c:
e92dd800
a0000060:
e24cb004
a0000064:
e24dd010
a0000068:
e3a0340a
a000006c:
e2833903
a0000070:
e50b301c
a0000074:
e59f3048
a0000078:
e59f2048
a000007c:
e5832000
a0000080:
e3a03000
a0000084:
e50b3010
a0000088:
e51b3010
a000008c:
e35300ff
a0000090:
da000000
a0000094:
ea000009
a0000098:
e51b3018
a000009c:
e2832002
a00000a0:
e50b2014
a00000a4:
e51b3014
a00000a8:
e50b3018
a00000ac:
eb000006
a00000b0:
e51b3010
a00000b4:
e2832001
a00000b8:
e50b2010
a00000bc:
eafffff1
a00000c0:
e91ba800
a00000c4:
a000016c
a00000c8:
12345678
mov
r12, sp
stmdb sp!, {r11, r12, lr, pc}
sub
r11, r12, #4
; 0x4
sub
sp, sp, #16 ; 0x10
mov
r3, #167772160
; 0xa000000
add
r3, r3, #49152
; 0xc000
str
r3, [r11, -#28]
ldr
r3, [pc, #48]
; a00000c4 <testcode+0x6c>
ldr
r2, [pc, #48]
; a00000c8 <testcode+0x70>
str
r2, [r3]
mov
r3, #0
; 0x0
str
r3, [r11, -#16]
ldr
r3, [r11, -#16]
cmp
r3, #255
; 0xff
ble
a0000098 <testcode+0x40>
b
a00000c0 <testcode+0x68>
ldr
r3, [r11, -#24]
add
r2, r3, #2 ; 0x2
str
r2, [r11, -#20]
ldr
r3, [r11, -#20]
str
r3, [r11, -#24]
bl
a00000cc <inc_var>
ldr
r3, [r11, -#16]
add
r2, r3, #1 ; 0x1
str
r2, [r11, -#16]
b
a0000088 <testcode+0x30>
ldmdb r11, {r11, sp, pc}
andge r0, r0, r12, ror #2
eornes
r5, r4, #125829120
; 0x7800000
a00000cc <inc_var>:
a00000cc:
e1a0c00d
a00000d0:
e92dd800
a00000d4:
e24cb004
a00000d8:
e59f2024
a00000dc:
e59f3020
a00000e0:
e59f201c
a00000e4:
e5921000
a00000e8:
e2812001
a00000ec:
e5832000
a00000f0:
e59f3010
a00000f4:
e59f2008
a00000f8:
e5921000
a00000fc:
e5831000
a0000100:
e91ba800
a0000104:
a000016c
a0000108:
a0000170
mov
stmdb
sub
ldr
ldr
ldr
ldr
add
str
ldr
ldr
ldr
str
ldmdb
andge
andge
r12, sp
sp!, {r11, r12, lr,
r11, r12, #4
;
r2, [pc, #24]
;
r3, [pc, #20]
;
r2, [pc, #1c]
;
r1, [r2]
r2, r1, #1 ; 0x1
r2, [r3]
r3, [pc, #10]
;
r2, [pc, #8]
;
r1, [r2]
r1, [r3]
r11, {r11, sp, pc}
r0, r0, r12, ror #2
r0, r0, r0, ror r1
a000010c <exit>:
a000010c:
e1a0c00d
a0000110:
e92dd800
a0000114:
e24cb004
a0000118:
e1a00000
a000011c:
ea000000
a0000120:
ea000000
mov
stmdb
sub
nop
b
b
r12, sp
sp!, {r11, r12, lr, pc}
r11, r12, #4
; 0x4
(mov r0,r0)
a0000124 <exit+0x18>
a0000128 <exit+0x1c>
r12, sp
sp!, {r11, r12, lr, pc}
r11, r12, #4
; 0x4
a0000058 <testcode>
r11, {r11, sp, pc}
pc}
0x4
a0000104 <inc_var+0x38>
a0000104 <inc_var+0x38>
a0000104 <inc_var+0x38>
a0000108 <inc_var+0x3c>
a0000104 <inc_var+0x38>
37
a0000124:
a0000128:
eafffffc
e91ba800
a000012c <atexit>:
a000012c:
e1a0c00d
a0000130:
e92dd800
a0000134:
e24cb004
a0000138:
e1a00000
a000013c:
ea000000
a0000140:
ea000000
a0000144:
eafffffc
a0000148:
e91ba800
b
a000011c <exit+0x10>
ldmdb r11, {r11, sp, pc}
mov
stmdb
sub
nop
b
b
b
ldmdb
r12, sp
sp!, {r11, r12, lr, pc}
r11, r12, #4
; 0x4
(mov r0,r0)
a0000144 <atexit+0x18>
a0000148 <atexit+0x1c>
a000013c <atexit+0x10>
r11, {r11, sp, pc}
Disassembly of section .glue_7t:
Disassembly of section .glue_7:
Disassembly of section .data:
Esaminando il codice disassemblato, è possibile ricavare alcune informazioni sulla correttezza di ciò che si è ottenuto: per esempio la colonna di sinistra (contenente gli indirizzi di memoria) consente di constatare subito che il codice è effettivamente allocato a partire dall’indirizzo 0xa0000000.
Si può verificare la correttezza dell’inizializzazione dello stack-pointer effettuata
nelle prime due istruzioni assembly contenute in crt0.s :
/* Set up the stack pointer to end of bss */
ldr r3, .LC2
mov sp, r3
che, disassemblate, sono rappresentate nel file all.asm come segue:
a0000000:
a0000004:
e59f3038
e1a0d003
ldr
mov
r3, [pc, #38]
sp, r3
; a0000040 <_mainCRTStartup+0x40>
Viene caricato in r3 il valore contenuto all’indirizzo 0xa0000040 (label .LC2) corrispondente al valore 0xa0001174 (ovvero __bss_end__):
a0000040:
a0001174
andge r1, r0, r4, ror r1
(si tenga presente che il disassemblatore interpreta come istruzione anche ciò che istruzione non è, per cui il disassemblaggio di a0001174, che è un indirizzo di memoria,
produce un’istruzione non significativa).
Successivamente il registro sp viene caricato con il valore contenuto in r3.
Il valore 0xa0001174 viene calcolato nel processo di linking in funzione delle informazioni di allocazione contenute nel file ldscript.
Un ulteriore strumento di verifica consiste nella generazione dei simboli dal file
“all”: mediante il seguente comando:
xscale-elf-nm all > all.sym
Il risultato, contenuto nel file all.sym, è riportato qui di seguito:
38
a0000044 t .gcc2_compiled.
a0001174 A __bss_end__
a0000174 A __bss_start__
a0000044 T __gccmain
a0002174 A _end
a0000000
a0000000
a000012c
a000010c
a00000cc
a0000000
a0000058
a000016c
a0000170
T
T
T
T
T
T
T
B
B
_mainCRTStartup
_start
atexit
exit
inc_var
start
testcode
varc
vard
Indirizzo della fine del segmento BSS, dove è
stato inizializzato lo stack-pointer 0xa0001174
Entry point, come previsto, a 0xa0000000
Una ulteriore verifica molto interessante può essere fatta mediante la generazione
del mapping-file, che si ottiene aggiungendo al comando di linking l’opzione –M e la ridirezione da stdout verso un file (per esempio all.map):
xscale-elf-ld –o all –Tldscript crt0.o test.o –M > all.map
Il file risultante contiene le seguenti informazioni:
Allocating common symbols
Common symbol
size
file
varc
vard
0x4
0x4
test.o
test.o
Origin
0x00000000
Length
0xffffffff
Memory Configuration
Name
*default*
Page
0
Attributes
Linker script and memory map
0xa0000000
.text
*(.text)
.text
.text
.=0xa0000000
0xa0000000
0x14c
0xa0000000
0xa0000000
0xa0000000
0xa0000000
0xa0000044
0xa0000044
0xa00000cc
0xa0000058
0xa000012c
0xa000010c
0x44 crt0.o
_mainCRTStartup
_start
start
0x108 test.o
__gccmain
inc_var
testcode
atexit
exit
.glue_7t
0xa000014c
0x0
.glue_7
0xa000014c
0xa000015c
0x0
.=(.+0x10)
39
.data
*(.data)
0xa000015c
0x0
0xa000016c
.bss
*(.bss)
COMMON
.=(.+0x10)
0xa000016c
0x8
0xa000016c
0x8 test.o
0x0 (size before relaxing)
varc
vard
__bss_start__=.
.=(.+0x1000)
__bss_end__=.
.=(.+0x1000)
PROVIDE (__stack, .)
_end=.
0xa000016c
0xa0000170
0xa0000174
0xa0001174
0xa0000174
0xa0001174
0xa0000174
0xa0000174
.debug_info
*(.debug_info)
.debug_abbrev
*(.debug_abbrev)
.debug_line
*(.debug_line)
.debug_frame
*(.debug_frame)
LOAD crt0.o
LOAD test.o
OUTPUT(all elf32-littlearm)
Normalmente nello sviluppo di software tutte queste fasi sono eseguite in modo automatico mediante il tool “make” ed il corrispondente file “makefile” in cui sono specificate, mediante una opportuna sintassi, le varie fasi di sviluppo ed i risultati che si vogliono ottenere.
Nel makefile di esempio riportato di seguito si possono riconoscere facilmente tutte
le fasi descritte precedentemente:
# Makefile for Xscale Demo
PROC=xscale
TYPE=elf
PATH=/usr/local/$(PROC)-$(TYPE)/H-i686-pc-cygwin/bin:/usr/bin
LIBPATH=/usr/local/$(PROC)-$(TYPE)/H-i686-pc-cygwin/$(PROC)-$(TYPE)/lib
INCPATH=/usr/local/$(PROC)-$(TYPE)/H-i686-pc-cygwin/$(PROC)$(TYPE)/include
CC=$(PROC)-$(TYPE)-gcc
AS=$(PROC)-$(TYPE)-as
AR=$(PROC)-$(TYPE)-ar
LD=$(PROC)-$(TYPE)-ld
NM=$(PROC)-$(TYPE)-nm
OBJDUMP=$(PROC)-$(TYPE)-objdump
LDSCRIPT=ldscript
40
Compilazione di test.c
test.c
# Make little endian code.
test_le: test.c Makefile $(LDSCRIPT) crt0.o
Assemblaggio di crt0.s
$(CC) -g -c test.c
$(CC) -g -c -o crt0.o crt0.S
$(NM) test.o
#
Linking di test.o e crt0.o; info per alloc. in ldscript
$(LD) -g -v -T $(LDSCRIPT) -o test crt0.o test.o
$(NM) test > test.map
/bin/cp gdbinit_le gdb.ini
Generazione simboli dall’eseguibile test
#make big endian code.
test_be: test.c Makefile $(LDSCRIPT) crt0.o
$(CC) -mbig-endian -g -c test.c
$(CC) -mbig-endian -g -c -o crt0.o crt0.S
#
$(NM) test.o
$(LD) -EB -g -v -T $(LDSCRIPT) -o test crt0.o test.o
$(NM) test > test.map
/bin/cp gdbinit_be gdb.ini
dump:
dis:
$(OBJDUMP) --all-headers test
$(OBJDUMP) --disassemble test
clean:
rm
rm
rm
rm
*.o
test
*.map
gdb.ini
Perciò quando si richiama il seguente comando:
make test_le
avviene la generazione di un file chiamato test contenente codice eseguibile per il
processore, oltre ovviamente ai file risultanti dalle fasi intermedie e ai vari map-file e
symbol-file.
Un ulteriore tool che può essere utile è quello che fornisce la dimensione (in byte) delle varie sezioni contenute nel modulo eseguibile:
xscale-elf-size all
viene generato il seguente report:
text
332
Dimensione del
segmento “text”
data
0
Dimensione del
segmento “data”
bss
8
Dimensione del
segmento “bss”
dec
340
Dimensione totale in base 10
hex
154
filename
all
Dimensione totale in base 16
41
Naturalmente tutti i tool riportati prevedono moltissime opzioni; per approfondimenti
si consiglia sia di fare riferimento alla grande quantità di documentazione reperibile in
internet, sia di utilizzare il manuale in linea di cygwin per la versione nativa x86:
man gcc
man as
man ld
tenendo presente che la maggior parte delle opzioni vale in tutte le versioni di target.
Esiste anche un help in linea dei tool per xscale, richiamabile nel seguente modo:
xcale-elf-gcc
xcale-elf-as
xcale-elf-ld
----------
--help
--help
--help
In modo simile per tutti gli altri tool disponibili.
Di seguito si riporta un riassunto schematico delle varie fasi di sviluppo utilizzate
nell’esempio riportato precedentemente:
crt0.s
xscale-elf-as –o crt0.o crt0.s
test.c
xscale-elf-gcc –c test.c
crt0.o
test.o
ldscript
xscale-elf-ld –o all-Tldscript crt0.o test.o –M > all.map
all.map
all
xscale-elf-objcopy –D all > all.asm
xscale-elf-nm all > all.sym
all.asm
all.sym
42
4.3
Interazione con il target e fase di debug del codice sviluppato
Il debugger “gdb” (Gnu–DeBugger) viene utilizzato per individuare e correggere eventuali errori presenti nel codice in via di sviluppo. Esso permette di analizzare il flusso
del codice eseguibile impostando breakpoint e di controllare lo stato delle variabili,
dei registri e degli stack.
Informazioni dettagliate e complete sul debugger gdb si possono trovare all’indirizzo:
http://www.gnu.org/softwre/gdb/gdb.html
qui ci si limita a riportare solo alcune delle informazioni che servono per l’uso di gdb.
Gdb si presenta, nella sua forma più tradizionale, come un programma a linea di comando testuale, ma nelle versioni più recenti è presente anche una interfaccia grafica.
È utile tenere presenti le differenze tra le tre diverse modalità di funzionamento del
debugger gdb: local debugging, stand-alone simulator, remote debugging.
- Local debugging:
Il “local debugging” avviene quando il software sviluppato su un host deve essere eseguito e controllato sullo stesso processore host. In questo caso il processore host ed il processore target coincidono, il tool gdb utilizzato deve essere
di tipo “nativo” per quel tipo di host.
- Stand-alone simulator:
Questa modalità di funzionamento del gdb è utilizzata quando non si ha la disponibilità dal target. Per attuare questa modalità è necessario specificare,
all’avvio di una sessione di gdb, che la connessione è verso il simulatore “sim”.
Il gdb è in grado di simulare l’architettura del core Xscale, ma, come avviene
per tutti i simulatori, non sono rispettati i tempi reali di esecuzione delle istruzioni; anzi questi tempi sono fortemente dipendenti dalla macchina host sulla
quale avviene la simulazione; un’altra limitazione del simulatore è che di fatto
esso non è un simulatore a livello di scheda, ovvero non è possibile simulare il
succedersi di eventi esterni come le richieste di interrupt o le richieste di
DMA, se non con artifici molto complessi e difficili da mettere in atto e soprattutto non vengono simulate le periferiche esterne al core Xscale, come ad esempio MMU, UART ecc…
- Remote debugging:
Quello che avviene spesso nello sviluppo di software di tipo embedded è che il
processore host non coincide con il processore target, quindi emerge la necessita di effettuare un “remote debugging” che consenta di eseguire e controllare
un codice su un processore diverso da quello su cui è stato sviluppato.
43
Nella modalità “remote debugger” il software gdb interagisce con il target mediante una linea di comunicazione: tipicamente viene utilizzata una linea UART o
un bus ethernet; spesso nelle fasi iniziali di sviluppo del firmware di base viene
utilizzata anche la connessione JTAG (standard IEEE 1149.1).
Nel caso vengano utilizzate linee UART o bus ethernet, il target deve essere
equipaggiato di software che provveda, oltre alla normale inizializzazione della
scheda, anche alla inizializzazione della interfaccia che gestisce il canale di comunicazione e che attivi poi un segmento di codice che gestisca la comunicazione tra target e host:
gdb-stub
nel caso in cui il target sia equipaggiato di un semplice
firmware tipo Bootloader, per esempio RedBoot;
oppure
gdb-server nel caso in cui il target sia già dotato di sistema
operativo multitasking, ad esempio Linux.
Nel contesto particolare di questa esposizione, interessano le modalità “remote debugging” e “stand-alone simulator”.
Le procedure descritte in seguito valgono per entrambe le modalità di funzionamento
a meno, per ovvie ragioni, di alcuni aspetti particolari riguardanti l’inizializzazione della connessione verso il target.
Di seguito viene illustrato come richiamare una sessione gdb per target “xscale”:
xscale-elf –gdb
(apertura di una sessione con interfaccia grafica)
xscale-elf –gdb –nw (apertura di una sessione a linea di comando testuale)
L’apertura di una sessione del gnu-debugger necessita normalmente di una grande
quantità di parametri. Alcuni di questi parametri possono essere forniti sulla linea di
comando mentre viene invocato il gdb, come si è fatto per l’apertura di una sessione a
linea di comando testuale, oppure si possono fornire mediante la console di gdb, digitandoli ad uno ad uno; ma il modo più comodo è quello di creare un file nominato
“gdb.ini” contenente i parametri ed i comandi che si vuole siano eseguiti all’avvio di
gdb. Se gdb viene avviato nello stesso directory dove è presente il file gdb.ini, i
comandi contenuti in questo file verranno letti e conseguentemente eseguiti.
Se il nome del file contenente i parametri è diverso da gdb.ini allora è necessario
informare il gdb mediante il comando:
xscale-elf –gdb –command=FILE
dove FILE è il nome del file contenente i parametri ed i comandi.
Di seguito si riporta un esempio di apertura di sessione gdb con un target Xscale collegato mediante linea seriale; quindi viene effettuato un “remote debugging” del codice presentato come esempio nel paragrafo precedente.
44
Come già accennato, sul target deve essere presente un firmware che comprende
gdb-stub oppure gdb-server e i driver per il colloquio con la seriale.
In questo caso viene utilizzato il pacchetto “RedBoot”; questo è un bootloader che
permette il caricamento su target di file eseguibili attraverso linea seriale in modalità
Xmodem; permette inoltre di effettuare un debug minimale mettendo a disposizione
alcuni comandi tipo “Read Memory” e “Modify Memory”. Redboot comprende anche il
gdb-stub che permette di usare gdb su host per effettuare il debugger remoto.
Dopo aver predisposto la connessione seriale tra Host (PC) e target con un opportuno
cavo, e dopo aver aperto una sessione VT100 (per esempio HyperTerminal) su host,
configurata alla velocità del target (che in questo caso è 115200 Baud), al power-up
del target vengono presentate su VT100 le seguenti informazioni:
+
RedBoot(tm) bootstrap and debug environment [ROM]
Non-certified release, version v2_0b1 - built 10:36:35, May 11 2003
Platform: PXA2xx (XScale)
Copyright (C) 2000, 2001, 2002, Red Hat, Inc.
RAM: 0x00000000-0x04000000, 0x0000fcc8-0x02000000 available
RedBoot>
Di particolare interesse è l’informazione sulla zona di RAM disponibile allo User.
La zona di RAM disponibile è 0x0000fcc8 Æ0x02000000. Quindi sarà opportuno allocare il codice visto precedentemente, per esempio all’indirizzo 0x10000, modificando
il file ldscript in questo modo:
SECTIONS
{
/*
. = 0xa0000000; */
. = 0x00010000;
.text : { *(.text) }
. += 0x10;
.data : { *(.data) }
. += 0x10;
.bss : { *(.bss) }
__bss_start__ = .;
. += 0x1000;
__bss_end__ = .;
. += 0x1000;
PROVIDE (__stack = .);
_end = .;
.debug_info
0 : {
.debug_abbrev
0 : {
.debug_line
0 : {
.debug_frame
0 : {
/*
.debug_str
0 : {
/*
.debug_loc
0 : {
/*
.debug_macinfo 0 : {
}
*(.debug_info) }
*(.debug_abbrev) }
*(.debug_line) }
*(.debug_frame) }
*(.debug_str) } */
*(.debug_loc) } */
*(.debug_macinfo) } */
45
E’ consigliabile cancellare i file prodotti dalla compilazione precedente con il comando:
make clean
e successivamente effettuare la compilazione:
make test_le
È importante che la compilazione dei file avvenga con l’opzione –g: questo permette
che il codice eseguibile risultante sia controllabile dal debugger gdb. Per esempio:
xscale-elf-gcc –g –c
test.c
in questo modo vengono aggiunte delle informazioni supplementari al file oggetto
test.o, permettendo al codice eseguibile di essere sottoposto alla procedura di debugging.
È utile disporre di un file gdb.ini che operi le necessarie inizializzazioni all’avvio di
gdb; di seguito se ne riporta un esempio:
# file gdb.ini
# Set up the environment for xscale gdb
# in "REMOTE DEBUGGER" mode
# Little endian mode.
set endian little
set prompt (xscale-gdb)
# This connects to a UART
set remotebaud 115200
target remote COM2
Impostazione della velocità e della porta sulla
quale avviene la comunicazione con il target
set output-radix 16
set input-radix 16
dir .
# Create "setup" Macro to Set Registers to default values and load image
# Open a Console Window in gdb and at the (xscale-gdb) prompt type
"setup"
# to run this macro
define setup
set $pc = 0x00010000
set $lr = 0x0
load test
symbol-file test
b __gccmain
Definizione di una macro richiamabile da console
Caricamento del codice eseguibile nel target
e caricamento dei simboli.
Impostazione di un breakpoint alla funzione
__gccmain (in test.c)
46
Una volta creato il file gdb.ini come appena descritto, è possibile richiamare gdb mediante il comando:
xscale-elf-gdb.
In una nuova finestra (Source Window) viene presentata l’interfaccia grafica di gdb:
Apertura finestra
“Console”
Se target e host sono connessi correttamente, i pulsanti contenuti nel “Toolbar” Step,
Next, Finish, Continue, Step asm e Next asm (evidenziati nella figura precedente) sono attivi e viene visualizzato il valore del Program Counter nell’apposita area; altrimenti la situazione del “Toolbar” si presenta come nella figura seguente:
Può essere utile aprire anche una finestra “console”: la modalità “console” viene utilizzata per fornire comandi particolari (che non sono tutti disponibili nell’interfaccia
grafica). A fronte del prompt (xscale-gdb), richiamando la macro “setup” ( definita
nel file gdb.ini), appaiono i seguenti messaggi:
(xscale-gdb)setup
Loading section .text, size 0x14c lma 0x10000
Start address 0x10000 , load size 332
Transfer rate: 2656 bits in <1 sec, 166 bytes/write.
Breakpoint 1 at 0x10050: file test.c, line 5.
47
(xscale-gdb)
Questi messaggi informano sulla corretta esecuzione del download del codice eseguibile nel target.
A questo punto è possibile avviare l’esecuzione del codice, mediante il comando “continue” (o brevemente “c”).
Appare la seguente situazione:
La linea di codice evidenziata in verde indica l’istruzione su cui l’esecuzione del programma si è interrotta a causa del breakpoint impostato nel file gdb.ini.
Da questa situazione è possibile continuare la procedura di debug del programma, facendone proseguire l’esecuzione con i comandi step, next o con ulteriori impostazioni
di breakpoint.
Questi (ed altri) comandi sono disponibili nel toolbar di “Source Windows”, come indicato nella seguente figura:
48
Next Asm
Step Asm
Register
Memory
Stack
Continue
Watch Expr.
Finish
Local Variables
BreakPoint
Program
Counter
Next
Console
Step
49
PULSANTI UTILIZZATI PER IL CONTROLLO DELL’ESECUZIONE DEL CODICE:
- Run:
avvia l’esecuzione del programma; se il programma è già in esecuzione, lo
fa ripartire dall’inizio;
- Step:
esegue una sola istruzione;
- Next:
esegue una sola istruzione, ma se l’istruzione è una chiamata a subroutine,
esegue l’intera subroutine (posto che non vi siano breakpoint al suo interno);
se è in corso l’esecuzione di una subroutine, ne prosegue l’esecuzione e si
arresta subito dopo il ritorno al programma chiamante (posto che non si
incontrino breakpoint prima);
- Finish:
- Continue: prosegue l’esecuzione del programma e si arresta al prossimo (eventuale)
breakpoint;
- Step-asm: esegue una sola istruzione assembly;
- Next-asm: esegue una sola istruzione assembly, ma se l’istruzione è una chiamata a
subroutine, esegue l’intera subroutine (posto che non vi siano breakpoint
al suo interno).
PULSANTI UTILIZZATI PER ATTIVARE LE FINESTRE AUSILIARIE:
- Register:
visualizza il contenuto dei registri e permette all’utente di modificarne il valore;
- Memory:
visualizza porzioni di aree di memoria e permette all’utente di modificarne il contenuto;
- Stack:
visualizza la lista delle subroutine attive, con i livelli di chiamata;
- Local Variable:
visualizza le variabili locali e permette all’utente di modificarne il
valore;
- BreakPoint:
visualizza i breakpoint impostati e ne permette l’abilitazione, la disabilitazione e la rimozione;
- Console:
apre una finestra con l’una interfaccia GDB a linea di comando.
HELP IN LINEA PER L’USO DELL’INTERFACCIA GRAFICA
Maggiori informazioni su questi (e sugli altri) pulsanti presenti nell’interfaccia grafica,
si possono ottenere utilizzando l’help in linea, cui si accede dal menù Help della finestra Source Window.
La lettura delle informazioni contenute in questo help in linea è molto utile per poter
usare efficacemente il debugger gdb.
50
USO DELL’INTERFACCIA A LINEA DI COMANDO
Tutte le possibilità messe a disposizione dall’interfaccia grafica sono, ovviamente, disponibili anche a livello di linea di comando nella finestra console; a questo proposito,
di seguito sono elencati i comandi più comunemente utilizzati:
- list:
- continue:
visualizza il codice sorgente di una funzione o di un file;
prosegue l’esecuzione del programma e si arresta al prossimo (eventuale) breakpoint;
- break:
imposta un breakpoint ad un indirizzo o ad una particolare linea di
codice;
- step:
esegue una sola istruzione;
- next:
esegue una sola istruzione, ma se l’istruzione è una chiamata a subroutine, esegue l’intera subroutine (posto che non vi siano breakpoint al suo interno);
- print:
visualizza una variabile locale, o una o più locazioni di memoria;
- info register:
visualizza il contenuto dei registri;
- set variable nome_variabile=nuovo_valore: modifica il valore di una variabile locale.
Dalla linea di comando è possibile accedere anche ad un help in linea digitando:
help
questo comando provoca la visualizzazione della lista di comandi disponibili; è anche
possibile ottenere le informazioni su un particolare comando, per esempio “step”, digitando:
help step
verrà visualizzato:
Step program until it reaches a different source line.
Argument N means do this N times (or till program stops for another reason).
51
ESEMPIO SINTETICO DI UNA SEMPLICE SESSIONE DI LAVORO
Si riporta qui di seguito, sinteticamente, la sequenza di operazioni e di comandi con cui
si costruisce un semplice modulo sorgente assembly, lo si assembla, si crea il modulo
eseguibile e se ne collauda l’esecuzione sulla scheda UNI-PD-PXA :
1. Per scrivere un programma assembly è necessario usare un “editor” (“Blocco
Note” è più che sufficiente per programmi semplici; in alternativa si può anche
usare “word”, con l’avvertenza di operare il salvataggio in formato text); il file
in cui si salva il modulo sorgente deve avere l’estensione .s (es: prova.s).
2. Controllare che l’alimentatore sia collegato alla rete elettrica e alla scheda;
controllare che il cavo seriale sia collegato al PC e alla scheda (porta seriale adiacente alla porta USB); azionare l’interruttore principale sul retro della scheda: si accende il led “ON” sul pannello frontale; il software di boot della scheda
inizializza la memoria e la connessione seriale.
3. Assemblare il modulo sorgente (nell’es. contenuto in prova.s) con il comando:
xscale-elf-as -al -gstabs -o prova.o prova.s > prova.l
(che produce il modulo oggetto in prova.o e il listing in prova.l).
4. Costruire il modulo eseguibile (nell’es. contenuto in prova.o) con il comando:
xscale-elf-ld -g -Ttext 0x00100000 -o prova prova.o
(che produce il modulo eseguibile in prova).
5. Attivare il GDB con il comando:
xscale-elf-gdb prova
6. Utilizzare l’interfaccia grafica (Source window: File → Target settings) per
impostare: Target: Remote/Serial; Baud rate: 115200; port: COM1); poi (source window: Run → Connect to target).
7. Sempre sulla Source window: selezionare prova.s dal menu a discesa sulla sinistra e impostare almeno un breakpoint (sulla prima istruzione del programma,
sull’ultima e nei punti intermedi di interesse) facendo click con il mouse sul corrispondente trattino a sinistra delle istruzioni stesse.
8. Sempre sulla Source window: selezionare il comando Run (tramite l’apposita icona oppure Run → Run).
9. Si usano i pulsanti della Source windows per controllare l’esecuzione del codice.
USO DEL SIMULATORE: se si intende usare il simulatore (modalità Stand-alone
simulator del gdb), si può ignorare il precedente punto 2 e, al punto 6, ci si limita ad
impostare (Source window: File → Target settings) Target: Simulator. Tutti gli altri
punti rimangono invariati.
52
CAPITOLO V
Esercizi
5.1
Primo programma (somma di due numeri)
Conviene iniziare con un primo semplice programma in linguaggio assembly, che calcola
la somma di due numeri:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/*add1.s****************************************************
* somma di due numeri :
*
* addendi immediati, risultato su registro
*
***********************************************************/
5.1.1
Il codice
.text
.global _start
_start:
mov r0, #10
mov r1, #15
add r2, r1, r0
b _end
_end:
@
@
@
@
carica il primo operando
carica il secondo operando
esegue la somma
trappola
.end
/**********************************************************/
/* Suggerimento/variante:
*/
/* in esecuzione passo-passo cambiare il valore contenuto */
/* nei registri r0, r1 prima di eseguire la somma
*/
/**********************************************************/
Si esaminano, prima di tutto, le linee di testo che non contengono istruzioni eseguibili.
Le linee di testa (1-4) e di coda (17-21) sono commenti. L'assemblatore GNU per ARM
supporta due tipi di commenti: quelli in stile linguaggio C, che cominciano con /* e
terminano con */ e possono essere composti da più righe, come nel primo blocco in cui
le righe da 1 a 4 sono un unico commento, oppure come nel blocco di coda, che è costituito da cinque linee ognuna delle quali è costituita da un commento aperto e chiuso; un
53
secondo modo in cui è possibile inserire dei commenti nel codice, tipico della programmazione in linguaggio assembly, prevede di iniziare il commento con il carattere @: tutti i caratteri successivi, fino alla fine della riga corrente, verranno trattati come
commento; un esempio sono le righe 10, 11, 12.
Alcune righe di testo contengono direttive, caratterizzate dal fatto che di solito cominciano con un punto. Si ricorda che le direttive sono istruzioni particolari in quanto
specificano comandi destinati ad essere eseguiti dall’assemblatore, nella fase di traduzione in linguaggio macchina (modulo oggetto) delle istruzioni assembly simboliche
(modulo sorgente); ciò contraddistingue le direttive dalle altre istruzioni assembly,
che specificano invece operazioni destinate ad essere eseguite, dal processore, nella
fase di esecuzione del programma.
La prima direttiva che si incontra, alla riga 6, è .text: il suo effetto è di indicare
(all’assemblatore) che le istruzioni che seguono andranno a far parte del segmento
text del programma finale; nel segmento text di norma sono situate le istruzioni destinate ad essere eseguite dal processore.
Alla riga successiva si trova la direttiva .global: lo scopo di questa direttiva è di dare visibilità globale, al di fuori del modulo corrente, ad uno o più simboli. Questa informazione è utilizzata nella fase di linking per risolvere i riferimenti tra i simboli definiti e usati in moduli diversi. Si vedrà più avanti il perché sia necessario definire globale il simbolo _start.
Ultima direttiva presente è .end alla riga 15: indica semplicemente la fine del file assembly; tutto ciò che segue non è più elaborato dall'assemblatore, anche se si tratta
di codice.
Un particolare tipo di direttiva è costituito dai label, definiti da simboli seguiti da un
due punti ':' (non sono permessi spazi tra il simbolo e i due punti). Una direttiva label
associa al simbolo il valore del location counter (si ricorda che il location counter è una
variabile, interna all'assemblatore, nella quale l’assemblatore, durante la scansione del
modulo sorgente, mantiene l’indirizzo di memoria in cui verrà collocata la prossima istruzione di macchina che verrà generata). I simboli definiti da label sono spesso usati come operandi per indicare, in modo simbolico, gli indirizzi all’interno di un programma.
Il resto del codice è molto semplice: le istruzioni alle linee 10 e 11 specificano il caricamento di due valori immediati nei registri r0 e r1; l’istruzione alla linea 12 provoca il
calcolo della somma di quei due valori (r1 + r0) e il suo caricamento in r2. L'ultima riga
di codice effettivo è una trappola: infatti contiene una istruzione di salto incondizionato alla istruzione stessa; per cui il processore, quando arriva ad eseguirla, continuerà ad eseguirla indefinitamente (se non lo si interrompe).
54
5.1.2
Compilazione
Per ottenere un programma eseguibile partendo dai sorgenti in assembly, sono necessari due passaggi: prima creare, tramite l'assemblatore, un file contenente il modulo
oggetto (.o) e successivamente, tramite il linker, dal modulo oggetto ottenere il programma eseguibile.
Posto che le istruzioni assembly (modulo sorgente) siano contenute nel file add1.s, il
comando per l'invocazione dell'assemblatore è:
xscale-elf-as -gstabs -o add1.o add1.s
All'assemblatore vengono passati tre parametri:
-gstabs: specifica che si vogliono includere, nel modulo oggetto, informazioni di
debug (utili per le operazioni che si possono eseguire con il debugger),
-o <file>.o: specifica il file di output (in cui verrà prodotto il modulo oggetto),
<file>.s: specifica il file di input (contenente il modulo sorgente da tradurre).
L'invocazione del linker avviene con il comando:
xscale-elf-ld -Ttext 0x00100000 -o add1 add1.o
Anche qui sono presenti tre parametri: -Ttext 0x00100000 per specificare
l’indirizzo di memoria a partire dal quale si vuole che venga caricato il segmento text
e, come per l'assemblatore, il file di output (add1) e quello di input (add1.o).
5.1.3
Debugging
Per invocare il debugger si usa il comando:
xscale-elf-gdb add1
Per verificare il corretto funzionamento del programma, si possono eseguire le istruzioni ad una ad una (modalità step) fino ad arrivare a quella situata alla riga 13 (oppure
collocare un breakpoint su quest’ultima) e constatare che nel registro r2 viene prodotta la somma (25, ovvero 0x19) dei due operandi.
In questo semplice programma, ad ogni esecuzione viene calcolata la somma degli stessi addendi ottenendo sempre lo stesso risultato. È possibile però utilizzare lo stesso
modulo eseguibile per calcolare altre somme, utilizzando alcune delle possibilità aggiuntive che il debugger mette a disposizione: eseguendo le istruzioni con modalità
step (oppure definendo un breakpoint), si arrivi ad avere evidenziata la riga 12 del codice, ovvero quella con l'istruzione add. In questa situazione l’istruzione evidenziata
deve essere ancora eseguita. Operando sulla finestra dei registri è possibile modificare il contenuto dei registri r0 e r1, (facendo clic con il mouse sul valore del registro,
se ne può modificare il contenuto), ad esempio 0x12 e 0x23. Tornando sulla finestra
principale ed avanzando di un altro passo l'esecuzione, il risultato nel registro r2 non è
più 25 ma 53.
55
5.2
Secondo programma (somma di due numeri)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*add2.s***************************************************/
/* somma di due numeri
*/
/* addendi in memoria, risultato su registro
*/
/**********************************************************/
.text
.global _start
5.2.1
Il codice
_start:
_end:
ldr
ldr
ldr
ldr
add
b
in1:
in2:
.data
.long
.long
r9, =in1
r0, [r9]
r9, =in2
r1, [r9]
r2, r1, r0
_end
@
@
@
@
@
@
indirizzo
carica in
indirizzo
carica in
esegue la
trappola
del primo operando
r0 il primo operando
del secondo operando
r1 il secondo operando
somma
0x00000012 @ primo operando
0x00000034 @ secondo operando
.end
Il secondo semplice programma è una evoluzione del precedente; la differenza
consiste nel fatto che i valori degli addendi vengono caricati dalla memoria anziché
essere definiti come operandi immediati.
Per caricare in un registro un valore contenuto in memoria bisogna procedere in due
passi: prima caricare in un registro l’indirizzo della locazione di memoria e poi
caricarne in un registro il contenuto con un'istruzione del tipo ldr Rn, [Rm]. Viste
anche le limitazioni presenti negli indirizzamenti immediati non è di norma possibile
caricare un indirizzo in un registro tramite una istruzione del tipo mov Rn, #imm. A
questo scopo conviene utilizzare la pseudo-istruzione (o meglio lo pseudoindirizzamento) ldr Rn, =imm nella quale spesso l’operando immediato è un simbolo
definito come label.
Oltre alla pseudo-istruzione appena vista, nel codice sono presenti anche due nuove
direttive: .data e .long. La prima (.data) serve a specificare l'utilizzo, da quella
riga del programma in poi, del segmento data: tutto quello che verrà generato dalle
righe successive (si tratterà ovviamente di dati) verrà collocato nel segmento data. La
direttiva .long riserva uno spazio di 4 byte (un long word) in memoria e lo inizializza
con il valore specificato. Nell’esempio, usando questa direttiva, è stato definito lo
spazio in memoria per i due addendi e se ne sono definiti anche i valori (0x12 e 0x34).
56
5.2.2
Compilazione
Posto di aver salvato il codice sorgente nel file add2.s, i comandi sono gli stessi
dell’esempioo precedente, cambiando ovviamente i nomi dei file:
xscale-elf-as -gstabs -o add2.o add2.s
xscale-elf-ld -Ttext 0x00100000 -o add2 add2.o
5.2.3
Debugging
Per mandare in esecuzione il programma, i passi da seguire sono gli stessi dell'esempio
precedente.
In questo secondo esempio gli addendi si trovano in memoria ed è interessante vedere
come il debugger gdb consenta di modificare il contenuto delle locazioni di memoria:
con il programma in esecuzione e bloccato (ad esempio con un breakpoint) alla prima
istruzione, si apra la finestra di visualizzazione della memoria (menu: View->Memory);
si prosegua poi l’esecuzione della sola istruzione alla riga 9 del programma, in modo da
ottenere, nel registro r9, l'indirizzo di memoria corrispondente al label in1; si inserisca poi questo indirizzo nel campo 'Address' della finestra della memoria; in questa
situazione il primo word di memoria visualizzato è quello corrispondente al label in1 e
il secondo è quello relativo a in2 (nel programma sorgente i 2 word sono stati posti in
posizioni consecutive); con modalità analoghe a quelle utilizzate per modificare il contenuto dei registri, è ora possibile modificare il contenuto della memoria.
NOTA: Ad ogni caricamento del programma tutte le zone di memoria di competenza
del programma vengono reinizializzate, per cui le modifiche fatte “manualmente”, con
le modalità descritte sopra, vengono perse, anche senza essere usciti dal debugger.
57
5.3
Terzo programma (somma di due numeri)
Si procede con un terzo semplice esempio, relativo ancora alla somma di due numeri,
questa volta prevedendo che anche il risultato venga collocato in memoria.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/*add3.s***************************************************/
/* somma di due numeri
*/
/* addendi in memoria, risultato in memoria
*/
/**********************************************************/
.text
.global _start
5.3.1
Il codice
_start:
_end:
ldr
ldr
ldr
ldr
add
ldr
str
b
in1:
in2:
.data
.long
.long
out:
.bss
.align
.space
.end
r9, =in1
r0, [r9]
r9, =in2
r1, [r9]
r0, r0, r1
r9, =out
r0, [r9]
_end
@
@
@
@
@
@
@
@
indirizzo
carica in
indirizzo
carica in
esegue la
indirizzo
memorizza
trappola
del primo operando
r0 il primo operando
del secondo operando
r1 il secondo operando
somma
del risultato
il risultato
0x00000012
0x00000034
@ primo operando
@ secondo operando
4
4
@ spazio per il risultato
Il risultato della operazione di somma viene memorizzato non nel segmento data, ma
nel segmento bss, che è destinato a contenere, appunto, dati non inizializzati (cioè
dati che, a differenza di quelli del segmento data, non hanno un valore assegnato
quando il programma viene caricato in memoria; questi dati assumeranno dei valori solo
durante l’esecuzione del programma, in seguito ad operazioni di scrittura).
La direttiva .bss indica l'utilizzo del segmento bss per cui il successivo label out
individua un indirizzo situato in tale segmento.
La direttiva .space serve per riservare dello spazio libero (non inizializzato) nel
segmento bss; l’argomento della direttiva (4 nell’esempio) indica l’estensione, in byte,
dello spazio da riservare.
La direttiva .align 4 serve ad allineare l’indirizzo di memoria ad un valore multiplo
di 4, in modo da consentire l’accesso corretto ai word (4 byte) collocati a partire da
quell’indirizzo.
58
5.3.2
Compilazione
Avendo salvato il codice sorgente nel file add3.s, i comandi sono gli stessi
dell’esempio precedente cambiando solo, come ovvio, i nomi dei file:
xscale-elf-as -gstabs -o add3.o add3.s
xscale-elf-ld -Ttext 0x00100000 -o add3 add3.o
5.3.3
Debugging
Per questo terzo esempio ci si può limitare a controllare solo se il risultato viene
scritto correttamente. Secondo le indicazioni dell'esempio precedente, per conoscere
l'indirizzo corrispondente al label out, si dovrebbe arrivare ad eseguire l’istruzione
alla linea 14; ma non sempre questo è accettabile: se è necessario conoscere fin
dall'inizio l'indirizzo di memoria corrispondente ad un label si può utilizzare un altro
dei tool disponibili nell’ambiente di sviluppo: 'nm'. Tramite il comando:
xscale-elf-nm add3
è possibile ottenere l'indirizzo corrispondente a tutti i simboli definiti come label
all'interno del modulo contenuto nel file specificato, tipicamente un modulo oggetto o
un modulo eseguibile. Il risultato ottenuto con quel comando è:
00100150
00100134
00100134
0010012c
00100150
00100150
00100134
0010001c
00100150
00080000
00100000
0010012c
00100130
00100140
A
A
A
D
A
A
A
t
A
?
T
d
d
b
__bss_end__
__bss_start
__bss_start__
__data_start
__end__
_bss_end__
_edata
_end
_end
_stack
_start
in1
in2
out
la prima colonna indica l'indirizzo (in esadecimale), la seconda, costituita da un solo
carattere, il tipo di simbolo ed infine la terza il nome del simbolo. Una piccola legenda
per i tipi di simbolo
a : simbolo corrispondente ad un indirizzo assoluto
d : simbolo corrispondente ad un indirizzo situato nel segmento data
t : simbolo corrispondente ad un indirizzo situato nel segmento text
b : simbolo corrispondente ad un indirizzo situato nel segmento bss
con le lettere maiuscole (A, D, T, B) vengono contrassegnati i simboli che hanno
visibilità globale; le lettere minuscole si riferiscono, invece, a simboli locali.
Il simbolo out corrisponde all’indirizzo 0x100140, che si trova nel segmento bss.
Questo è il valore da inserire come indirizzo nella finestra per il controllo della
memoria nel debugger.
59
5.4
Somma degli elementi di un vettore
Si prosegue ora con tre ulteriori esempi, leggermente più complessi dei precedenti,
anche questi intesi a raggiungere il medesimo obiettivo, il calcolo della somma degli
elementi di un vettore non vuoto di numeri interi naturali da 32 bit, ma realizzati in
tre modi diversi.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/*sum1.s***************************************************/
/* somma di un vettore di numeri
*/
/**********************************************************/
.text
.global _start
5.4.1
Il codice
.equ
elem, 10
@ numero di elementi del vettore
mov
ldr
ldr
sub
add
ldr
add
cmp
bpl
ldr
str
b
r0, #0
@ r0 conterrà la somma
r9, =vec
@ indirizzo del primo elemento
r8, =elem
@ contatore (da decrementare)
r8, r8, #1
@ indice dell’ultimo elemento
r8, r9, r8, lsl #2 @ indirizzo dell’ultimo elem.
r1, [r9], #4 @ carica un elemento
r0,r0,r1
@ lo somma in r0
r8, r9
@ era l’ultimo?
loop
@ no: carica il prossimo
r9, =out
@ indirizzo del risultato
r0, [r9]
@ memorizza il risultato
_end
@ trappola
_start:
loop:
_end:
vec:
out:
.data
.long
.bss
.align
.space
.end
10,56,89,35,67,54,9,29,37,72 @ elementi
4
4
@ spazio per il risultato
Conviene chiarire dapprima lo scopo con cui vengono utilizzati i vari registri.
r0: destinato a contenere la somma degli elementi del vettore
r1: contiene, di volta in volta, il singolo elemento da sommare
r8: contiene un puntatore all’ultimo elemento del vettore
r9: contiene il puntatore all’elemento da sommare
Le righe dalla 10 alla 14 inizializzano i vari registri utilizzati: r0 viene azzerato; in r9
viene messo l'indirizzo del primo elemento del vettore; in r8 viene prima inserito il
numero di elementi del vettore, poi viene decrementato in modo da ottenere l’indice
dell'ultimo elemento (che è pari alla dimensione del vettore meno uno), infine questo
indice viene trasformato in indirizzo, moltiplicandolo per 4 (lsl #2) poiché ogni elemento è di 4 byte e sommandolo poi all'indirizzo iniziale del vettore.
60
Le righe da 15 a 18 costituiscono il ciclo (loop), che viene eseguito tante volte quanti
sono gli elementi del vettore: ad ogni iterazione viene caricato in r1 l’elemento da
sommare (il word puntato da r9), contestualmente r9, usato con post-incremento incremento immediato, viene incrementato di 4 e passa a puntare all’elemento successivo; il valore caricato in r1 viene aggiunto alla somma parziale in r0; dopo la verifica che
r9 non abbia superato l’ultimo elemento del vettore, viene effettuata un'altra interazione.
Alla fine, il risultato viene salvato in memoria.
5.4.2
Compilazione
I comandi sono i soliti:
xscale-elf-as -gstabs
-o sum1.o sum1.s
xscale-elf-ld -Ttext 0x00100000
5.4.3
-o sum1 sum1.o
Debugging
Per questo esempio può essere interessante provare a modificare in memoria i valori
degli elementi del vettore al fine di constatare la correttezza del programma.
61
5.5
Somma degli elementi di un vettore (subroutine)
Lo stesso scopo dell’esempio precedente, il calcolo della somma degli elementi di un
vettore, può essere ottenuto definendo una subroutine (una funzione), che presenta il
vantaggio di poter essere facilmente riutilizzata da altri programmi.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/*sum2.s***************************************************/
/* somma di un vettore di numeri
*/
/**********************************************************/
.text
.global _start
.equ
elem, 10
@ numero di elementi del vettore
ldr
ldr
ldr
bl
ldr
str
b
@
@
@
@
@
@
@
_start:
_end:
sp, =stack
r2, =elem
r1, =vec
somma
r9, =out
r0, [r9]
_end
inizializza lo stack pointer
numero di elementi
indirizzo del primo elemento
chiama la subroutine somma
indirizzo del risultato
memorizza il risultato
trappola
.func somma
/*--------------------------------------------------------*/
/* calcola la somma degli elementi di un vettore
*/
/* input:
*/
/* R1: indirizzo del primo elemento del vettore
*/
/* R2: numero di elementi del vettore
*/
/* output:
*/
/* R0: somma
*/
/*--------------------------------------------------------*/
somma:
stmfd sp!, {r2,r3}
@ salva nello stack i registri
bic
r0, r0, r0
sub
r2, r2, #1
@ indice dell’ultimo elemento
somma_l: ldr
r3, [r1, r2, lsl #2] @ carica un elemento
add
r0,r0,r3
@ lo somma in r0
subs r2, r2, #1
@ decrementa l’indice
bge
somma_l
@ se >=0, carica il prossimo
ldmfd sp!, {r2,r3}
@ ripristina i registri salvati
mov
pc, lr
@ ritorna al programma chiamante
.endfunc
.data
vec:
.long
1,2,3,4,5,6,7,8,9,10 @ elementi
.bss
.align 4
out:
.space
.space
stack:
.space
4
4096
4
@ spazio per il risultato
@ spazio per lo stack
@ base dello stack
.end
62
5.5.1
Il codice
In questo esempio appaiono per la prima volta due elementi, lo stack e la chiamata a
subroutine.
Con riferimento allo stack, si osserva che, essendo esso di tipo full-descending, le
direttive che lo definiscono (nel segmento bss) comprendono un label (stack) che
individua l’indirizzo successivo allo spazio di memoria riservato allo stack stesso (base
dello stack), Nella riga 10 allo stack pointer viene assegnato il valore iniziale (quando
uno stack full-descending è vuoto, lo stack pointer punta proprio all’indirizzo
successivo all’area di memoria riservata allo stack).
La necessità di definire uno stack è legata al fatto che questo esempio comprende una
subroutine e, come è noto, è buona disciplina di programmazione prevedere che ogni
subroutine salvi all’inizio, con operazioni di push nello stack appunto, il contenuto dei
registri che vengono da essa modificati (purché non contengano parametri di uscita,
cioè valori restituiti dalla subroutine stessa) e che ne ripristini il contenuto, con
altrettante operazioni di pop, prima di ritornare al programma chiamante.
Si può constatare che il corpo principale del programma, quello compreso tra i label
_start e _end, è piuttosto semplice: le operazioni svolte sono solo l’inizializzazione
dello stack pointer, l’inserimento nei registri r1 ed r2 dei parametri di ingresso alla
subroutine somma, la chiamata della subroutine stessa e la memorizzazione del
risultato da essa restituito in r0.
Si segnala l’importanza delle righe da 19 a 26 che contengono, sotto forma di
commento, le informazioni che specificano l'interfaccia della subroutine: la funzione
che essa realizza, i parametri di ingresso, quelli di uscita e quali registri vengono
eventualmente da essa modificati. L’interfaccia di una subroutine contiene le
informazioni che devono essere note al programmatore che intende utilizzare la
subroutine stessa.
Nel codice sono presenti due nuove direttive per l'assemblatore (.func e .endfunc),
le quali servono a delimitare il codice appartenente ad una funzione. Non sono
necessarie per la definizione di una subroutine, ma sono utili in fase di debugging per
isolare il codice delle varie routine presenti nel programma.
La subroutine somma è organizzata in modo abbastanza simile al codice dell'esempio
precedente: all’interno di un ciclo, ad ogni iterazione, un nuovo elemento viene aggiunto
alla somma parziale contenuta in r0; a differenza dell’esempio precedente, però, ora
gli elementi vengono scanditi dall'ultimo al primo (sono individuati dall’indice contenuto
in r2, che viene decrementato ad ogni iterazione), e l’istruzione ldr alla riga 30 non
usa metodi di indirizzamento che modificano i registri coinvolti. Nell’istruzione che
decrementa l’indice, alla riga 32, viene usato il suffisso 'S' che abilita la modifica dei
bit di condizione nel registro di stato (CPSR): ciò consente di risparmiare l’uso di
un’istruzione di confronto per stabilire quando terminare le iterazioni.
63
Il ritorno al programma chiamante, dopo che r0 contiene la somma richiesta e dopo
aver ripristinato i valori originari di r2 ed r3, viene ottenuto con l’istruzione alla riga
35, che rimette in pc il valore che era stato salvato in lr dall’istruzione di chiamata
(riga 13),
Si noti che i registri r2 ed r3, che subiscono modifiche nel corso della esecuzione
della subroutine, vengono salvati nello stack all’inizio e ripristinati alla fine della
subroutine, mentre r1, che non viene modificato, non necessita di venir salvato.
5.5.2
Compilazione
I comandi sono i soliti:
xscale-elf-as -gstabs
-o sum2.o sum2.s
xscale-elf-ld -Ttext 0x00100000
5.5.3
-o sum2 sum2.o
Debugging
Si suggerisce di verificare, con il debugger gdb, sia le variazioni dei valori contenuti
nei registri lr, pc ed sp in corrispondenza, rispettivamente, delle istruzioni di
chiamata a subroutine (riga 13), di ritorno dalla subroutine (riga 35) e delle istruzioni
di push (riga 27) e pop (riga 34).
Inoltre anche per questo esempio può essere interessante provare a modificare in
memoria i valori degli elementi del vettore al fine di constatare la correttezza del
programma.
64
5.6
Somma degli elementi di un vettore (subroutine ricorsiva)
Lo stesso esempio che calcola la somma degli elementi di un vettore può essere
realizzato anche tramite un algoritmo ricorsivo, ottenuto con una subroutine che
prevede di richiamare se stessa.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/*sum3.s***************************************************/
/* somma di un vettore di numeri
*/
/**********************************************************/
.text
.global _start
.equ
elem, 10
@ numero di elementi del vettore
ldr
ldr
ldr
bl
ldr
str
b
@
@
@
@
@
@
@
_start:
_end:
sp, =stack
r2, =elem
r1, =vec
somma
r9, =out
r0, [r9]
_end
inizializza lo stack pointer
numero di elementi
indirizzo del primo elemento
chiama la subroutine somma
indirizzo del risultato
memorizza il risultato
trappola
.func somma
/*--------------------------------------------------------*/
/* funzione ricorsiva per la somma degli el. di un vettore*/
/* input:
*/
/* R1: indirizzo del primo elemento del vettore
*/
/* R2: numero di elementi del vettore
*/
/* output:
*/
/* R0: somma
*/
/*--------------------------------------------------------*/
somma:
stmfd sp!, {r1-r3,lr} @ salva i registri (anche lr)
cmp
r2, #1
@ c’è un solo elemento?
bhi
somma_split
@ no: procede con l’algoritmo
ldr
r0, [r1]
@ si: la somma è l’elemento e ...
b
somma_end
@ la subr. termina restituendolo
somma_split:
@ divide a metà il vettore
mov
r3, r2, lsr #1
@ n. di elem. della seconda metà
sub
r2, r2, r3
@ n. di elem. della prima metà
bl
somma
@ somma della prima metà in r0
add
r1, r1, r2, lsl #2 @ indirizzo della seconda metà
mov
r2, r3
@ n. di elem. della seconda metà
mov
r3, r0
@ in r3 la somma della prima metà
bl
somma
@ somma della seconda metà in r0
add
r0, r0, r3
@ somma delle due metà
somma_end:
ldmfd sp!, {r1-r3,pc} @ ripristina i registri (lr->pc)
.endfunc
.data
vec:
.long
1,2,3,4,5,6,7,8,9,10 @ elementi
.bss
.align 4
65
50
51
52
53
out:
5.6.1
Il codice
stack:
.end
.space 4
.space 4096
.space 4
@ spazio per il risultato
@ spazio per lo stack
@ base dello stack
La subroutine somma ha la stessa interfaccia della omonima subroutine dell’esempio
precedente, ma, come si può constatare, l’organizzazione del codice è completamente
diversa. L'idea alla base dell'algoritmo è di spezzare in due il vettore, calcolare le
somme degli elementi di ciascuna delle due metà e sommarle tra loro per ottenere la
somma dell'intero vettore. Allo stesso modo (ricorsivamente) si procede per calcolare
la somma di ciascuna metà e così via, ricorsivamente, fino ad ottenere vettori di un
solo elemento il cui valore coincide con la somma del vettore.
Per prima cosa (riga 27) la subroutine provvede a salvare nello stack i registri (diversi
da r0, in cui viene restituito il parametro di uscita) che vengono modificati nel corso
della propria esecuzione. Si osservi che la subroutine contiene istruzioni di chiamata a
subroutine, le quali modificano il registro lr (link register): per questo motivo è
necessario che lr, che contiene l’indirizzo di ritorno dalla subroutine, sia incluso nella
lista dei registri da salvare nello stack.
Le righe (28..31) della subroutine verificano se il vettore è costituito da un solo
elemento, nel qual caso il valore dell’elemento viene restituito in r0 come risultato,
altrimenti si procede a spezzare in due il vettore e a richiamare ricorsivamente la
subroutine somma stessa (due volte, una per ciascuna metà del vettore).
A questo scopo vengono dapprima calcolate le lunghezze (il numero di elementi) di
ciascuna delle due parti del vettore (attenzione: se il vettore originario ha un numero
di elementi dispari, le due parti non hanno la stessa lunghezza); le due lunghezze
vengono messe in r2 e r3 (righe 33 e 34). In r1 e r2 vi sono ora i parametri da passare
alla stessa subroutine somma, che viene chiamata (riga 35) per calcolare la somma
degli elementi della prima metà del vettore; successivamente in r1 e r2 vengono messi
i parametri (indirizzo del primo elemento e numero di elementi) relativi alla seconda
parte del vettore (righe 36 e 37) per la seconda chiamata alla subroutine somma (riga
39); per ottenere l’indirizzo del primo elemento della seconda parte, all’indirizzo
iniziale del vettore, contenuto in r1, viene sommato la lunghezza della prima metà
moltiplicata per 4 poiché gli elementi sono da 4 byte; prima della seconda chiamata
alla subroutine somma (che restituirà in r0 la seconda somma), la prima somma viene
spostata da r0 ad r3, altrimenti verrebbe sovrascritta. Al ritorno dalla seconda
chiamata, in r3 e in r0 vi sono le somme delle due parti, sommando le quali (riga 40) si
ottiene la somma complessiva da restituire in r0.
Si osservi che le operazioni di ripristino degli altri registri modificati e di ritorno al
programma chiamante sono ottenute con un’unica istruzione (riga 42) con la quale il
valore di lr precedentemente salvato nello stack viene caricato nel pc.
66
5.6.2
Compilazione
I comandi sono i soliti:
xscale-elf-as -gstabs
-o sum3.o sum3.s
xscale-elf-ld -Ttext 0x00100000
-o sum3 sum3.o
67
5.7
Rovesciamento di una stringa di caratteri
Questo esempio presenta una subroutine che opera il rovesciamento di una stringa di
caratteri. L’esempio fornisce l’occasione per vedere come sia possibile usare lo stack
(con operazioni di push e di pop) come area di lavoro (per collocarvi dati locali alla
subroutine stessa).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/*strrev.s*************************************************/
/* rovesciamento di una stringa di caratteri
*/
/**********************************************************/
.text
.global _start
_start:
_end:
ldr
ldr
bl
b
sp, =stack
r1, =str
strrev
_end
@
@
@
@
inizializza lo stack pointer
indirizzo del primo elemento
chiama la subroutine strrev
trappola
.func strrev
/*--------------------------------------------------------*/
/* funzione che rovescia una stringa: la stringa sia in
*/
/* input che output è terminata da un byte nullo:"\0"(EOS)*/
/* input/output:
*/
/* R1: indirizzo primo carattere della stringa
*/
/*--------------------------------------------------------*/
strrev:
stmfd sp!,{r0-r3,lr} @ salva i registri (anche lr)
mov
r3, r1
@ salva l’indirizzo della stringa
mov
r2, sp
@ salva sp
strrev_l:
ldrb r0, [r1], #+1 @ preleva un carattere, post-inc
strb r0, [sp, #-1]! @ lo inserisce nello stack, pre-dec
tst
r0,#0xff
@ si tratta di EOS?
bne
strrev_l
@ no: preleva il car. successivo
mov
r1,r3
@ si: ripristina r1 (ind. stringa)
add
sp,sp, #1
@ elimina dallo stack l’EOS
strrev_l2:
ldrb r0, [sp], #+1 @ pop un car. dallo stack, post-inc
strb r0, [r1], #+1 @ lo inserisce nella str., post-inc
cmp
sp,r2
@ sp è tornato al val. originario?
blo
strrev_l2
@ no: pop dallo stack il prossimo
ldmfd sp!,{r0-r3,pc} @ ripristina i registri (lr->pc)
/*--------------------------------------------------------*/
.endfunc
.data
str:
.ascii
"Architettura degli elaboratori \0"
.bss
.align 4
.space
4096
@ spazio per lo stack
stack:
.space 4
@ base dello stack
.end
68
5.7.1
Il codice
La subroutine strrev opera il rovesciamento di una stringa inserendone dapprima i
caratteri (escluso l’ultimo EOS), ad uno ad uno, nello stack con operazioni di push:
essendo lo stack di tipo full-descending, in questo modo in cima allo stack, puntata da
sp, si troverà la stringa rovesciata; successivamente i caratteri di questa stringa
vengono estratti dallo stack, con operazioni di pop, e collocati (dall'ultimo al primo) al
posto della stringa di input, sovrascrivendola (EOS rimane al suo posto).
Alle righe 23 e 24 vengono salvati in altri due registri i valori iniziali di r1 e dello
stack pointer (sp), perché serviranno più avanti; nel loop dalla riga 25 alla 29, viene
effettuato il push sullo stack, ad uno ad uno, dei successivi caratteri della stringa,
fino a che non viene incontrato il carattere 0x00 ovvero '\0', che segnala la fine della
stringa (EOS); viene poi ripristinato r1 al valore iniziale (indirizzo della stringa) e viene
incrementato lo sp in modo da eliminare dallo stack l’ultimo carattere inserito (EOS)
che non va copiato come primo carattere della stringa e che si trova già al suo posto
nella stringa; nel loop dalla riga 32 alla 36, i caratteri della stringa vengono estratti
dallo stack e inseriti al posto della stringa di input in ordine opposto a quello con cui
erano stati inseriti, dall’ultimo al primo, in modo da ottenere la stringa rovesciata. Il
ciclo termina quando sp torna ad avere lo stesso valore che aveva all'inizio della
subroutine.
L’uso dello stack fatto in questo esempio, seppur corretto, non è consigliato in quando
inserendo nello stack un byte alla volta, lo stack pointer può assumere anche valori
dispari o comunque non multipli di 4: ciò impedirebbe di operare correttamente il push
sullo stack di un word (4 byte), in quanto i word in memoria possono essere collocati
solo ad indirizzi che siano multipli di 4. Conviene quindi che lo stack pointer sia sempre
allineato ad indirizzi multipli di 4.
5.7.2
Compilazione
I comandi sono i soliti:
xscale-elf-as -gstabs
-o strrev.o strrev.s
xscale-elf-ld -Ttext 0x00100000
5.7.3
-o strrev strrev.o
Debugging
Per verificare la correttezza del programma conviene utilizzare due finestre per
monitorare la memoria: una in cui sia visualizzato lo spazio nel segmento dati occupato
dalla stringa iniziale (e che conterrà anche la stringa rovesciata alla fine
dell'esecuzione); una seconda in cui sia visualizzato il contenuto dello stack utilizzato
come area di lavoro per collocarvi i caratteri della stringa.
Per mettere in evidenza il problema dell'allineamento, si può inserire tra le righe 31 e
69
32 due operazioni inutili come
stmfd sp!,{r0}
ldmfd sp!,{r0}
che effettuano il push sullo stack e, subito dopo, il pop del word (4 byte) contenuto
nel registro r0.
Eseguendo sulla scheda UNI-PD-PXA il programma modificato in questo modo, si
verificherà un errore (eccezione SIGBUS) a seguito dell'esecuzione della stmfd,
dovuta al fatto che lo sp ha un valore non multiplo di 4; eseguendo il programma sul
simulatore, invece, il problema non viene rilevato (ciò conferma che l’esecuzione
tramite simulatore non rispecchia completamente quanto avviene con l’esecuzione
“vera” da parte del processore).
70
5.8
Rovesciamento di una stringa di caratteri (con sp allineato)
Si presenta ora una seconda versione del programma che opera il rovesciamento di una
stringa di caratteri.
Il codice che segue elimina il problema riscontrato nella versione precedente,
mantenendo allineato ad un multiplo di 4 l'indirizzo contenuto nello stack pointer.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/*strrev2.s************************************************/
/* rovesciamento di una stringa di caratteri
*/
/**********************************************************/
.text
.global _start
_start:
_end:
ldr
ldr
bl
b
sp, =stack
r1, =str
strrev
_end
@
@
@
@
inizializza lo stack pointer
indirizzo del primo elemento
chiama la subroutine strrev
trappola
.func strrev
/*--------------------------------------------------------*/
/* funzione che rovescia una stringa: la stringa sia in
*/
/* input che output è terminata da un byte nullo:"\0"(EOS)*/
/* input/output:
*/
/* R1: indirizzo primo carattere della stringa
*/
/*--------------------------------------------------------*/
strrev:
stmfd sp!,{r0-r4,lr} @ salva i registri (anche lr)
mov
r2, r1
@ in r2 l’indirizzo della stringa
strrev_l0:
ldrb r0, [r2], #+1 @ preleva un carattere, post-inc
tst
r0, #0xff
@ si tratta di EOS?
bne
strrev_l0
@ no: preleva il car. successivo
sub
r2, r2, r1
@ si: in r2 n. car. str. (con EOS)
bic
r4, r2, #3
@ azzera i bit 0 ed 1
cmp
r2, r4
@ se uguali: r2 è un multiplo di 4
addne r4, r4, #4
@ se no: in r4 il mult. successivo
mov
r3, sp
@ in r3 lo sp
sub
sp, sp, r4
@ alloca area riservata sullo stack
@ r3 punta alla base di quest’area
@ sp rimane allineato a mult. di 4
strrev_l1:
ldrb
strb
tst
bne
add
sub
sub
strrev_l2:
ldrb
r0, [r1], #+1
r0, [r3, #-1]!
r0, #0xff
strrev_l1
r3, r3, #1
r1, r1, r2
r2, r2, #1
@
@
@
@
@
@
@
preleva un carattere, post-inc
lo inserisce nello stack, pre-dec
si tratta di EOS?
no: preleva il car. successivo
elimina l’EOS
r1 punta di nuovo al primo car.
n. di car. da copiare (senza EOS)
r0, [r3], #+1
@ preleva car. da stack, post-inc
71
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
strb r0, [r1], #+1 @ lo inserisce nella str., post-inc
subs r2, r2, #1
@ decrementa il contatore di car.
bne
strrev_l2
@ preleva il prossimo se cont ≠ 0
add
sp, sp, r4
@ rimuove l’area dallo stack
ldmfd sp!,{r0-r4,pc} @ ripristina i registri (lr->pc)
/*--------------------------------------------------------*/
.endfunc
5.8.1
Il codice
.data
str:
.ascii
.bss
.align 4
.space
stack:
.space
.end
"Architettura degli elaboratori \0"
4096
4
@ spazio per lo stack
@ base dello stack
Rispetto alla versione del paragrafo precedente, la subroutine dapprima calcola, in r2,
la lunghezza della stringa, (righe 25-28); viene poi calcolata, in r4, la lunghezza dello
spazio da riservare sullo stack in modo che sia un multiplo di 4 byte (righe 29-31): r4
rimane uguale ad r2 se questo è multiplo di 4, altrimenti viene posto uguale al multiplo
di 4 immediatamente superiore; viene quindi allocato sullo stack tale spazio
sottraendo da sp la linghezza calcolata in r4 (riga 33).
Per il resto l’organizzazione del codice è simile a quella della versione precedente.
5.8.2
Compilazione
I comandi sono i soliti:
xscale-elf-as -gstabs
-o strrev2.o strrev2.s
xscale-elf-ld -Ttext 0x00100000
5.8.3
-o strrev2 strrev2.o
Debugging
Come per l'esercizio precedente, si suggerisce di utilizzare due finestre per il
monitoraggio della memoria.
Si può anche verificare che adesso l’inserzione delle istruzioni di push e pop di un
word non provoca problemi.
72
5.9
Ordinamento di un vettore (merge-sort)
In questo esempio viene presentata la realizzazione, in linguaggio assembly,
dell'algoritmo di merge-sort per ordinare gli elementi di un vettore: si farà uso di 2
subroutine: la prima (sort) ricorsiva che ordina le due metà del vettore; la seconda
(merge) che fonde in un unico vettore ordinato due vettori già ordinati.
L’esempio prevede che gli elementi dei vettori da ordinare siano numeri naturali della
dimensione di un singolo byte.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/*msort_b.s************************************************/
/* merge-sort di un vettore di naturali (da un byte)
*/
/**********************************************************/
.text
.global _start
.equ
elem, 10
@ numero di elementi del vettore
ldr
sp, =stack
@ inizializza lo stack pointer
ldr
ldr
bl
b
r1, =vec
r2, =elem
sort
_end
@
@
@
@
_start:
_end:
indirizzo del vettore
numero di elementi del vettore
chiama la subroutine sort
trappola
.func sort
/*--------------------------------------------------------*/
/* funzione che ordina i componenti di un vettore
*/
/* input/output:
*/
/* R1: indirizzo del primo elemento del vettore
*/
/* R2: lunghezza del vettore
*/
/*--------------------------------------------------------*/
sort:
stmfd sp!, {r1-r6,lr} @ salva i registri (anche lr)
cmp
r2, #1
@ c’è un solo elemento?
beq
sort_end
@ si: fine subr. (il vet. è ordinato)
sort_split:
@ no: procede con l’algoritmo
@ r1,r2 indirizzo e lunghezza della prima metà del vettore
@ r3,r4 indirizzo e lunghezza della seconda metà del vettore
mov
r4, r2
mov
r2, r2, lsr #1 @ r2=r2/2 (n. el. della prima metà)
sub
r4, r4, r2
@ n. di elementi della seconda metà
add
r3, r1, r2
@ ind. del primo el. seconda metà
@ ordina la prima metà:
bl
sort
mov
r5, r1
@ salva r1, r2 in r5, r6
mov
r6, r2
mov
r1, r3
@ copia r3, r4 in r1, r2
mov
r2, r4
@ ordina la seconda metà:
bl
sort
mov
r1, r5
@ ripristina r1, r2 (prima metà)
73
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
mov
r2, r6
@ fonde le due metà già ordinate
bl
merge
sort_end:
ldmfd sp!, {r1-r6,pc} @ ripristina i registri (lr->pc)
/*--------------------------------------------------------*/
.endfunc
.func merge
/*--------------------------------------------------------*/
/* funzione che fa il merge di due vettori contigui gia` */
/* ordinati mantenendo l'ordinamento
*/
/* input:
*/
/* R1: indirizzo del primo elemento del primo vettore
*/
/* R2: lunghezza del primo vettore
*/
/* R3: indirizzo del primo elemento del secondo vettore */
/* R4: lunghezza del secondo vettore
*/
/* output:
*/
/* R1: indirizzo del primo elemento del vettore ordinato */
/* R2: lunghezza del vettore ordinato
*/
/*--------------------------------------------------------*/
merge:
stmfd sp!,{r0-r7,lr} @ salva i registri (anche lr)
sub
r0, sp, #1
@ r0: ind. primo byte sopra lo stack
add
r7, r2, r4
@ lunghezza del vettore ordinato
sub
sp, sp, r7
@ area di lavoro sullo stack
bic
sp, sp, #3
@ allinea sp al multiplo di 4
@ immediatamente inferiore o uguale
sub
r2, r2, #1
@ indice dell’ultimo el. del 1° vett.
sub
r4, r4, #1
@ indice dell’ultimo el. del 2° vett.
ldrb r5, [r1, r2]
@ preleva l’ultimo el. del 1° vett.
ldrb r6, [r3, r4]
@ preleva l’ultimo el. del 2° vett.
merge_loop:
cmp
r5, r6
@ confronta i due elementi prelevati
blo
merge_v2
@ salta se r5 < r6 (cmp tra naturali)
merge_v1:
@ copia nell’area di lavoro l’elemento del 1° vettore
strb r5, [r0], #-1
@ copia, post-dec
subs r2, r2, #1
@ indice el. precedente del 1° vett.
ldrgeb
r5, [r1, r2] @ lo preleva, posto che ci sia
blt
copy_v2
@ se non c’è, copia il resto del 2°
b
merge_loop
@ confrontalo con quello del 2° vet.
merge_v2:
@ copia nell’area di lavoro l’elemento del 2° vettore
strb r6, [r0], #-1
@ copia, post-dec
subs r4, r4, #1
@ indice el. precedente del 2° vett.
ldrgeb
r6, [r3, r4] @ lo preleva, posto che ci sia
blt
copy_v1
@ se non c’è, copia il resto del 1°
b
merge_loop
@ confrontalo con quello del 1° vet.
copy_v1:
@ copia nell’area di lavoro gli elementi restanti del 1°
strb r5, [r0], #-1
@ copia, post-dec
subs r2, r2, #1
@ indice el. precedente del 1° vett
ldrgeb
r5, [r1, r2] @ lo preleva, posto che ci sia
bge
copy_v1
@ se c’è, prova a copiarne un altro
b
merge_end
@ altrimenti il merge è finito
copy_v2:
@ copia nell’area di lavoro gli elementi restanti del 2°
strb r6, [r0], #-1
@ copia, post-dec
subs r4, r4, #1
@ indice el. precedente del 2° vett
ldrgeb
r6, [r3, r4] @ lo preleva, posto che ci sia
74
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
5.9.1
bge
copy_v2
@ se c’è, prova a copiarne un altro
b
merge_end
@ altrimenti il merge è finito
merge_end: @ ricopia il vett. ordinato al posto del vett. di input
add
r2, r0, #1
@ r2 punta all’ultimo el. inserito
mov
r3, #0
@ r3 è l’offset
mcopy_l: ldrb r0, [r2, r3]
@ carica l’elem. dall’area di lavoro
strb r0, [r1, r3]
@ lo ricopia nel vettore di input
add
r3, r3, #1
@ incrementa l’offset
cmp
r3, r7
@ ricopiato tutto?
blo
mcopy_l
@ no: ricopia il prossimo
add
sp, sp, r7
@ si: rimuove l’area di lavoro
tst
sp, #3
@ sp è allineato a multiplo di 4?
bicne sp, sp, #3
@ no: multiplo immediatamente infer.
addne sp, sp, #4
@ adesso è quello giusto
ldmfd sp!,{r0-r7,pc}
@ ripristina i registri (lr->pc)
/*--------------------------------------------------------*/
.endfunc
.data
vec:
.byte
8,6,5,9,7,3,4,1,10,2 @ elementi
.bss
.align 4
.space 4096
stack:
.space 4
.end
@ spazio per lo stack
@ base dello stack
Il codice
Il codice rispecchia la nota organizzazione dell'algoritmo di merge-sort: la prima parte (subroutine sort), ricorsiva, dimezza il vettore fino ad arrivare a un vettore di un
solo elemento e quindi già ordinato; la seconda parte (subroutine merge) effettua il
merge di due vettori ordinati mantenendo l'ordinamento.
Si può constatare, nella subroutine merge, la particolare attenzione posta nell'allocare sullo stack l’area di lavoro mantenendo lo stack pointer allineato a multiplo di 4.
Sempre nella subroutine merge, i vettori vengono scanditi dall'ultimo elemento al primo: i due elementi scanditi, uno del primo vettore e uno del secondo, vengono confrontati (merge_loop) e, di volta in volta, viene copiato nell’area di lavoro il maggiore dei
due, mantenendo l'ordinamento, (merge_v1 e merge_v2); quando poi uno dei due vettori è terminato, la rimanente parte dell'altro viene ricopiata senza ulteriori confronti, (copy_v1 e copy_v2).
Infine il vettore ordinato costruito nell’area di lavoro sullo stack, viene ricopiato
nell’area di memoria che contiene i due vettori ricevuti in input; si osservi che ciò presuppone che i due vettori in input alla subroutine merge siano contigui in memoria: l'uso della subroutine all'interno del sort garantisce il rispetto di questo requisito.
75
5.9.2
Compilazione
I comandi sono i soliti:
xscale-elf-as -gstabs
-o msort_b.o msort_b.s
xscale-elf-ld -Ttext 0x00100000
5.9.3
-o msort_b msort_b.o
Debugging
Il debugging può essere complesso a causa dell'elevato numero di chiamate ricorsive:
conviene esaminare la situazione del vettore parzialmente ordinato all'inizio della
chiamata a merge ed eventualmente seguire solo alcune delle istanze in cui i vettori in
input a merge hanno più di un elemento.
Si suggerisce anche per questo esempio di utilizzare due finestre per monitorare la
memoria: una in cui sia visualizzato lo spazio nel segmento dati occupato dal vettore
iniziale (e che conterrà il vettore ordinato alla fine dell'esecuzione); una seconda in
cui sia visualizzato il contenuto dello stack utilizzato come area di lavoro.
76
5.10
Il pannello frontale: i led e gli switch
Si esamina ora un semplice programma inteso ad illustrare l’uso di alcuni dispositivi
presenti sul pannello frontale della scheda: i led e gli switch.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/*led1.s***************************************************/
.include "front.inc"
.global _start
5.10.1
Il codice
_start:
ldr
sp, =stack
bl
init_front
ldr
r1,=LED_ADDR
ldrh r0, [r1]
strh r0, [r1]
b loop
b _end
loop:
_end:
.bss
.space
stack:
.space
4096
4
@
@
@
@
@
@
@
inizializza lo stack pointer
chiama la subroutine init_front
indirizzo dei led e degli switch
legge lo stato degli switch
lo scrive sui led
indefinitamente
trappola
@ spazio per lo stack
@ base dello stack
.end
Conviene esaminare dapprima la nuova direttiva .include presente alla riga 2.
Essa è usata per includere il contenuto di un altro file sorgente in modo simile a quello
che la direttiva #include fa per il linguaggio C/C++.
In un contesto di moduli sorgenti assembly, di norma, i file di tipo include (contenenti
codice sorgente destinato ad essere incluso in altri file) hanno estensione .inc.
Nel caso in esame il file front.inc definisce alcuni simboli utili nell'utilizzo del
pannello frontale. Ecco il contenuto del file :
1
2
3
4
5
6
/*front.inc*************************************************/
LED_ADDR = 0x70000000 @ indirizzo dei led e switch (halfword)
BUT_ADDR = 0x40e00000 @ indirizzo dei pin GPIO
LEF_MASK = 0x00000800 @ bit corrispondente al pulsante sinistro
RIG_MASK = 0x00000400 @ bit corrispondente al pulsante destro
BUT_MASK = 0x00000C00 @ maschera per i bit di entrambi i puls.
Va precisato che l’indirizzo 0x70000000, associato al simbolo LED_ADDR (riga 2 di
front.inc), individua un halfword (16 bit) sul quale sono mappati sia i 16 switch sia i 16
led presenti sul pannello frontale della scheda: con un’operazione di lettura si ottiene
lo stato degli switch; con un’operazione di scrittura si imposta lo stato dei led.
L’indirizzo 0x40e00000, associato al simbolo BUT_ADDR (riga 3), individua un word sul
quale sono mappati i pin GPIO: il bit di indice 11 di quel word (maschera 0x00000800)
77
corrisponde al pulsante sinistro del pannello frontale; il bit di indice 10 (maschera
0x00000400) corrisponde al pulsante destro,
Tornando al codice presente nel file led1.s, dopo aver inizializzato lo stack (riga 6),
viene chiamata la subroutine init_front (riga 7) la quale, come suggerisce il nome,
provvede alle inizializzazioni necessarie per operare sui dispositivi del pannello
frontale: non essendo presente nel modulo sorgente, si tratta di una subroutine
esterna, definita in un altro modulo sorgente che dovrà essere collegata al modulo in
esame ad opera del linker. Dopo aver caricato in r1 l'indirizzo dello halfword in cui
sono mappati in memoria i led e gli switch (riga 8), viene eseguito un ciclo infinito nel
quale viene ripetutamente letto lo stato degli switch (riga 9) e riscritto il medesimo
valore per impostare lo stato dei led (riga 10).
Si riporta, qui di seguito, il codice della subroutine init_front, che si suppone
contenuto nel file front.s:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
37
38
39
/*front.s**************************************************/
.include "front.inc"
.global init_front
init_front:
stmfd sp!,{r0,r1}
@ imposta GPIO_15 = nCS_1 per abilitare l’accesso alla SRAM
ldr
r1,=0x40e0000c
ldr
r0,[r1]
orr
r0,r0,#0x00008000
str
r0,[r1]
ldr
ldr
orr
str
r1,=0x40e00054
r0,[r1]
r0,r0,#0x80000000
r0,[r1]
@ imposta il memory timing relativo a nCS_4 e nCS_5
ldr
r0,=0x26e026e0
ldr
r1,=0x48000010
str
r0,[r1]
@ imposta GPIO_33 = nCS_5 per abilitare l’accesso al front panel
@ direzione output
ldr
r1,=0x40e00010
ldr
r0,[r1]
orr
r0,r0,#0x00000002
str
r0,[r1]
@ alternate function 2
ldr
r1,=0x40e0005c
ldr
r0,[r1]
orr
r0,r0,#0x00000008
str
r0,[r1]
ldmfd sp!,{r0,r1}
mov
pc,lr
.end
78
5.10.2
Compilazione
In questo esempio il modulo eseguibile viene costruito a partire da due moduli
sorgente, contenuti in file separati: questi due file vanno quindi assemblati
separatamente per produrre i corrispondenti moduli oggetto; questi ultimi devono poi
essere collegati dal linker.
Ecco i comandi con cui si ottiene quanto indicato :
xscale-elf-as -gstabs
-o led1.o led1.s
xscale-elf-as -gstabs
-o front.o front.s
xscale-elf-ld -Ttext 0x00100000
-o led1 led1.o front.o
Nel corso dell’assemblaggio di led1.s l'assemblatore non trova la definizione del
simbolo init_front, che viene pertanto lasciato irrisolto: al linker è demandato il
compito di risolvere, nella costruzione del modulo eseguibile, tutti i riferimenti
esterni di ciascun modulo oggetto, cercandone la definizione negli altri moduli.
5.10.3
Debugging
Solo una nota: dato che il programma è costituito essenzialmente da un ciclo che viene
eseguito indefinitamente (righe 9-11) si suggerisce di collocare un breakpoint in
almeno una delle istruzioni del ciclo stesso.
79
5.11
Il pannello frontale: i pulsanti
Sul pannello frontale sono presenti due pulsanti, collegati a due dei pin GPIO (general
purpose I/O) del PXA255.
Le possibilità di utilizzazione dei pulsanti sono molte (ad es. possono essere generare
interruzioni sul livello, oppure su un fronte); in questo esempio ci si limita a leggerne lo
stato e a rilevare se sono premuti o meno: il programma fa scorrere verso destra o
verso sinistra l’illuminazione di un led, in seguito alla pressione del pulsante
corrispondente.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/*led2.s***************************************************/
.include "front.inc"
.global _start
_start:
loop:
ldr
bl
ldr
movs
strh
ldr
bl
ldr
tst
moveq
tst
moveq
sp, =stack
init_front
r1, =LED_ADDR
r0, #1
r0, [r1]
r3, =BUT_ADDR
delay
r2, [r3]
r2, #LEF_MASK
r0, r0, ror #31
r2, #RIG_MASK
r0, r0, ror #1
@ inizializza lo stack pointer
@ chiama la subr. init_front
@ indirizzo di led e switch
@
@
@
@
@
@
@
@
illumina il led di sinistra
indirizzo dei pin GPIO
lascia passare un po’ di tempo
legge lo stato dei pulsanti
è premuto quello di sinistra?
si: ruota verso sinistra
è premuto quello di destra?
si: ruota verso destra
@ azzera i bit che escono dall’area visibile (word meno signif.)
bics
r0, r0, #0x00010000 @ bit uscito a sinistra
bicnes r0, r0, #0x80000000 @ bit uscito a destra
strh
r0, [r1]
@ riscrive sui led
bne
loop
@ continua se non nullo
_end:
b _end
@ trappola
.func delay
delay:
@ introduce un ritardo (~ 0.1 s)
stmfd
sp!, {r0}
@ 20.185.088 * 2 / 400MHz ~= 0.1 sec
mov
r0, #0x01340000 @ 0x01340000 = 20.185.088
d_loop: subs
r0, r0, #1
bne
d_loop
ldmfd
sp!, {r0}
mov
pc, lr
.endfunc
.bss
.space
stack:
.space
4096
4
.end
80
5.11.1
Il codice
L'inizializzazione (righe 6-8) è identica a quella dell'esercizio precedente; si parte poi
con il led più a sinistra illuminato.
Il corpo del programma è costituito dal loop compreso tra le righe 12 e 23: viene
caricato in r2 il word contenente lo stato dei pulsanti; con le istruzioni tst (righe 14 e
16), che effettuano un AND bit a bit, si esamina lo stato dei bit corrispondenti ai
pulsanti di interesse (sinistro e destro): se viene rilevato un bit nullo, si opera la
rotazione di una posizione verso sinistra o, rispettivamente, verso destra (si ricordi
che il bit corrispondente ad un pulsante viene azzerato quando questo è premuto); con
le due istruzioni bic (righe 20 e 21) si azzera l'eventuale bit 1 che fosse fuoriuscito
dai 16 bit visibili sui led, in seguito ad una rotazione verso sinistra del bit più
significativo, oppure verso destra del bit meno significativo; viene inoltre abilitato
l'aggiornamento dei bit di stato in modo da rilevare se il risultato è nullo: in tal caso,
dopo aver scritto il valore sui led (riga 22), si termina il loop.
Il loop comprende, come prima istruzione (riga 12), una chiamata alla subroutine delay,
il cui unico scopo è di far trascorrere un po’ di tempo (circa 0.1 s.), al fine di rendere
percepibile lo scorrimento della illuminazione dei led: in assenza di questo ritardo lo
scorrimento sarebbe talmente veloce da essere non percepibile.
5.11.2
Compilazione
Come nel caso precedente, è necessario assemblare separatamente i due moduli
sorgente e collegare tramite il linker i corrispondenti moduli oggetto. I comandi sono:
xscale-elf-as -gstabs
-o led2.o led2.s
xscale-elf-as -gstabs
-o front.o front.s
xscale-elf-ld -Ttext 0x00100000
5.11.3
-o led2 led2.o front.o
Debugging
In questo programma le istruzioni che vengono eseguite dipendono da un evento
esterno (la pressione di uno o dell’altro pulsante).
Se si volessero eseguire ad una ad una le istruzioni del loop (righe 12-23), sarebbe
consigliabile utilizzare la modalità next, anziché step (per evitare di eseguire ad una
ad una anche le numerosissime istruzioni della subroutine delay).
Si suggerisce di impostare un breakpoint alla riga 13, ove viene rilevato l'evento: se
l’evento provoca una modifica di r2, si può procedere con modalità step per verificare
l’effetto della modifica, altrimenti conviene lasciar proseguire l’esecuzione del
programma.
81
Può essere interessante provare ad eliminare la chiamata alla subroutine delay (commentando parte della riga 12), oppure provare a modificare il numero delle iterazioni
da essa eseguite (impostando un valore immediato diverso alla riga 30) e constatare
l’effetto di queste modifiche.
82
5.12
Il display LCD
Si passa ora a fornire alcuni esempi intesi a descrivere le possibilità di utilizzare il
display LCD collegato alla scheda. Per abilitare l'uso del display, è necessario eseguire
preventivamente una serie di operazioni di inizializzazione: allo scopo nel file sorgente
lcd.s (vedi paragrafo 5.13) sono presenti alcune subroutine con le quali è possibile
inizializzare in diversi modi il controller LCD del PXA255.
Il principio di funzionamento del controller LCD è abbastanza semplice: l’insieme dei
pixel che costituiscono l’immagine da visualizzare sul display va “scritto”, con le
modalità previste, in un’apposita area di memoria (qui chiamata framebuffer); il
controller LCD interpreta il contenuto di quell’area di memoria e lo trasferisce sul
display a formare la corrispondente immagine.
Nel file lcd.s (vedi paragrafo 5.13) sono presenti tre subroutine di inizializzazione
(lcd_init_4bpp, lcd_init_8bpp, lcd_init_16bpp) che inizializzano il display
controller in modo che esso operi prevedendo che ciascun pixel sia rappresentato, nel
framebuffer, con 4, 8 o 16 bit, rispettivamente.
Nella modalità con 4 bit per pixel (bpp) ciascun punto dell’immagine può assumere uno
di 24 = 16 colori diversi; i 16 colori sono definiti da una palette (tavolozza) costituita
da una tabella di 16 elementi da 16 bit; i 16 bit di ciascun elemento definiscono il
corrispondente colore secondo la codifica RGB 565 (in cui i 5 bit più significativi
indicano la componente rossa, i 6 bit centrali la componente verde e i 5 bit meno
significativi la componente blu del colore stesso); i 4 bit di ciascun pixel vengono
interpretati dal controller LCD come l’indice dell’elemento nella palette che ne
definisce il colore.
Nella modalità con 8 bpp ciascun pixel può assumere uno di 28 = 256 colori diversi; la
palette contiene 256 elementi da 16 bit (con codifica RGB 565).
Nella modalità con 16 bpp non è presente una palette, in quanto ciascun pixel è
direttamente rappresentato nel framebuffer tramite le sue componenti RGB 565.
Le caratteristiche delle tre diverse modalità sono riassunte nella seguente tabella:
subroutine
colori
bpp
palette
lcd_init_4bpp
lcd_init_8bpp
lcd_init_16bpp
16
256
65536
4
8
16
SI
SI
NO
Tutte tre le subroutine di inizializzazione richiedono, come parametri di ingresso, nei
registri r8 e r9 gli indirizzi rispettivamente del framebuffer e della palette.
Se in r8 e r9 vengono passati valori nulli, le subroutine usano i valori di default:
0x01000000 per il framebuffer, e le palette a 8 e 16 colori predefinite nel segmento
.data nel file lcd.s; come parametri di uscita le subroutine ritornano, in r8 e r9, gli
indirizzi del framebuffer e della palette.
83
5.12.1
Disegnare a 16 Colori
Il primo esempio prevede di inizializzare il display a 16 colori e di visualizzare sullo
schermo 16 rettangoli colorati ciascuno con uno diverso dei 16 colori della palette.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/*color1.s*************************************************/
.global _start
.text
_start:
ldr
sp, =stack
@ inizializza lo stack pointer
bic
bic
bl
r8,r8,r8
@ r8=0 (framebuffer predefinito)
r9,r9,r9
@ r9=0 (palette predefinita)
lcd_init_4bpp @ in r8: ind. del FB; in R9 ind. PAL
mov
mov
mov
r3, #320/4
r4, #240/4
r0, #0
bic
mul
mov
mul
bl
add
cmp
blt
r5, r0, #0x0C
r1, r5, r3
r5, r0, lsr #2
r2, r5, r4
draw_box_4bpp
r0, r0, #1
r0, #16
m_loop
@
@
@
@
@
@
@
@
b
_end
@ trappola
@ 1/4 della larghezza dello schermo
@ 1/4 della altezza dello schermo
@ indice del colore nella palette
m_loop:
_end:
2 LSb dell’indice colore: 0,1,2,3
ascissa vertice del rettangolo
2 MSb dell’indice colore: 0,1,2,3
ordinata vertice del rettangolo
disegna il rettangolo
incrementa l’indice del colore
era l’ultimo colore?
no: disegna un altro rettangolo
.func draw_box_4bpp
/*************************************************************/
/* r0 : colore
*/
/* r1 : ascissa x (in pixel) del vertice superiore sinistro */
/* r2 : ordinata y (in pixel) del vertice superiore sinistro */
/* r3 : larghezza (in pixel) del rettangolo
*/
/* r4 : altezza (in pixel) del rettangolo
*/
/* r8 : indirizzo del framebuffer (FB)
*/
/*************************************************************/
draw_box_4bpp:
stmfd sp!,{r0,r3-r8}
mov
r7, #320
@ n. pixel in 1 riga dello schermo
mul
r5, r2, r7
@ n. pixel in y righe (da saltare)
add
r5, r5, r1
@ + x pixel della riga y
mov
r5, r5, lsr #1
@ 1 pixel occupa solo 1/2 byte
add
r8, r8, r5
@ r8 = indirizzo di memoria del
@ vertice superiore sinistro del
@ rettangolo (nel framebuffer)
add
r0, r0, r0, lsl #4 @ 2 pixel uguali nel LSB di r0
sub
r3, r3, #1
@ contatore di colonne (pixel/riga
sub
r4, r4, #1
@ contatore di righe del box
mov
r6, r3
@ salva il contatore di colonne
db_loop:
@ scrive nel FB: parte dal vertice inf dx
mul
r5, r7, r4 @ indice (pxl) di riga (parte dall’ultima)
add
r5, r5, r3 @ indice (pxl) di colonna (dall’ultima)
strb r0, [r8, r5, lsr #1]@ scrive nel FB 2 pxl alla volta
84
52
53
54
55
56
57
58
59
60
61
62
63
64
65
subs
bpl
mov
subs
bpl
r3, r3, #1
@ decrementa l’indice di
db_loop
@ scrivi altri pxl della
r3, r6
@ fine riga: ripristina il cont.
r4, r4, #1
@ decrementa l’indice di
db_loop
@ scrivi un’altra riga
colonna
riga
di col.
riga
ldmfd sp!,{r0,r3-r8}
mov
pc,lr
.endfunc
.bss
stack:
.end
5.12.1.1
.space 4096
.space 4
@ spazio per lo stack
@ base dello stack
Il codice
Il codice è relativamente semplice: nel corpo principale del programma, dopo aver
chiamato la subroutine di inizializzazione (modalità 4 bpp) e avendo ottenuto da essa
in r8 ed r9 gli indirizzi (predefiniti) del framebuffer (FB) e della palette (PAL),
conoscendo le dimensioni dello schermo (320 × 240 pixel) e volendo disegnare 16
rettangoli uguali e contigui posizionati come una matrice 4 per 4, vengono preparate le
coordinate del vertice superiore sinistro di ciascuno dei rettangoli e ne viene
comandata la visualizzazione chiamando la subroutine draw_box_4bpp.
All'interno della subroutine draw_box_4bpp viene, per prima cosa (righe 37-41),
calcolato l'effettivo indirizzo di memoria dell'angolo superiore sinistro del rettangolo
da disegnare; successivamente, nel doppio loop principale (righe 48-56), viene
disegnato il rettangolo per righe successive, partendo dall'angolo inferiore destro.
5.12.1.2
Compilazione
I comandi sono i soliti:
xscale-elf-as -gstabs
-o color1.o color1.s
xscale-elf-as -gstabs
-o lcd.o lcd.s
xscale-elf-ld -Ttext 0x00100000
5.12.1.3
-o color1 color1.o lcd.o
Debugging
Impostando un breakpoint in corrispondenza dell’istruzione di chiamata alla subroutine
draw_box_4bpp (riga 19) è possibile seguire la visualizzazione dei singoli rettangoli e
verificare, nei registri r0-r4 ed r8, i valori dei parametri passati alla subroutine.
Per verificare la modalità di colorazione di ciascun rettangolo si può definire un
breakpoint nel loop esterno della subroutine draw_box_4bpp (ad es. alla riga 56),
oppure nel loop interno (ad es. alla riga 53) se si volesse controllare, molto più
85
lentamente, la colorazione di una coppia di pixel alla volta.
Può essere interessante verificare l’uso della palette: dopo che il programma è stato
completamente eseguito ed è “intrappolato” nella riga 24, si apra una finestra di
visualizzazione della memoria all'indirizzo della palette (indirizzo contenuto in r9): i
primi 16 half-word (da 16 bit) visualizzati sono i 16 elementi della palette
correntemente attiva; si può provare a modificare questi valori e constatare le
variazioni di colore dei rettangoli sullo schermo.
Si osservi che ciò avviene anche se il processore non esegue alcuna istruzione (ad
esempio avendolo fermato con un breakpoint alla riga 24). Si ha così conferma del
fatto che il trasferimento sullo schermo del contenuto del framebuffer utilizzando i
colori specificati dalla palette, è un'operazione che viene compiuta autonomamente dal
controller LCD, via DMA, senza l’intervento del processore.
86
5.12.2
Disegnare a 256 Colori
Il secondo esempio, simile al primo, prevede di inizializzare il display a 256 colori e di
visualizzare sullo schermo 256 rettangoli, disposti a matrice 16 per 16, con i 256
colori della palette.
Il programma è simile al precedente salvo le modifiche legate al fatto che ora un
singolo pixel è contenuto in un intero byte.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/*color2.s*************************************************/
.global _start
.text
_start:
ldr
sp, =stack
@ inizializza lo stack pointer
bic
bic
bl
r8,r8,r8
@ r8=0 (framebuffer predefinito)
r9,r9,r9
@ r9=0 (palette predefinita)
lcd_init_8bpp @ in r8: ind. del FB; in R9 ind. PAL
mov
mov
mov
r3, #320/16
r4, #240/16
r0, #0
bic
mul
mov
mul
bl
add
cmp
ble
r5, r0, #0xf0
r1, r5, r3
r5, r0, lsr #4
r2, r5, r4
draw_box_8bpp
r0, r0, #1
r0, #256
m_loop
@
@
@
@
@
@
@
@
b
_end
@ trappola
@ 1/4 della larghezza dello schermo
@ 1/4 della altezza dello schermo
@ indice del colore nella palette
m_loop:
_end:
4 LSb dell’indice colore: 0..15
ascissa vertice del rettangolo
4 MSb dell’indice colore: 0..15
ordinata vertice del rettangolo
disegna il rettangolo
incrementa l’indice del colore
era l’ultimo colore?
no: disegna un altro rettangolo
.func draw_box_8bpp
/*************************************************************/
/* r0 : colore
*/
/* r1 : ascissa x (in pixel) del vertice superiore sinistro */
/* r2 : ordinata y (in pixel) del vertice superiore sinistro */
/* r3 : larghezza (in pixel) del rettangolo
*/
/* r4 : altezza (in pixel) del rettangolo
*/
/* r8 : indirizzo del framebuffer (FB)
*/
/*************************************************************/
draw_box_8bpp:
stmfd sp!,{r3-r8}
mov
r7, #320
@ n. pixel in 1 riga dello schermo
mul
r5, r2, r7
@ n. pixel in y righe (da saltare)
add
r5, r5, r1
@ + x pixel della riga y
add
r8, r8, r5
@ r8 = indirizzo di memoria del
@ vertice superiore sinistro del
@ rettangolo (nel framebuffer)
sub
r3, r3, #1
@ contatore di colonne (pixel/riga)
sub
r4, r4, #1
@ contatore di righe del box
mov
r6, r3
@ salva il contatore di colonne
87
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
db_loop:
mul
add
strb
subs
bpl
mov
subs
bpl
@
r5, r7, r4 @
r5, r5, r3 @
r0, [r8, r5]
r3, r3, #1
db_loop
r3, r6
r4, r4, #1
db_loop
scrive nel FB: parte dal vertice inf dx
indice (pxl) di riga (parte dall’ultima)
indice (pxl) di colonna (dall’ultima)
@ scrive nel FB 2 pxl alla volta
@ decrementa l’indice di colonna
@ scrivi altri pxl della riga
@ fine riga: ripristina il cont. di col.
@ decrementa l’indice di riga
@ scrivi un’altra riga
ldmfd sp!,{r3-r8}
mov
pc,lr
.endfunc
.bss
stack:
.end
5.12.2.1
.space 4096
.space 4
@ spazio per lo stack
@ base dello stack
Compilazione
I comandi sono i soliti:
xscale-elf-as -gstabs
-o color2.o color2.s
xscale-elf-as -gstabs
-o lcd.o lcd.s
xscale-elf-ld -Ttext 0x00100000 -o color2 color2.o lcd.o
5.12.2.2
Debugging
Le operazioni di debug che si consiglia di eseguire sono le stesse dell’esempio
precedente, tenendo presente il fatto che adesso la palette è una tabella di 256
halfword.
88
5.12.3
Scrivere caratteri sul display
Questo terzo esempio si propone di descrivere l’uso del display a 16bpp con un
obiettivo un po' più complesso rispetto ai due esempi precedenti: scrivere dei
caratteri sullo schermo.
Per fare ciò è necessario avere a disposizione le immagini dei caratteri, nel formato
richiesto, da poter ricopiare nelle posizioni desiderate all’interno del framebuffer.
Per questo esempio ci si è limitati a rendere disponibile l'immagine contenente i soli
caratteri corrispondenti alle cifre da 0 a 9 e alle lettere maiuscole da A a F, con le
quali è possibile visualizzare un numero esadecimale. L’immagine di questi caratteri
(nel formato RGBA a 32bpp, che necessiterà di essere convertito al formato RGB 565
a 16bpp) si trova nel file font_tab.s, che va assemblato separatamente e collegato
tramite il linker.
Il programma fa uso anche un file di tipo include (font_tab.inc), riportato qui
sotto, nel quale sono definite le dimensioni (in pixel) dell'immagine e dei singoli
caratteri.
1
/*font_tab.inc*********************************************/
2 FONT_LINE = 160
3
4
FONT_H
FONT_W
@ full pixmap lenght in pixel
= 15
= 10
@ altezza, in pixel, di un carattere
@ larghezza, in pixel, di un carattere
Il programma completo, contenuto nel file font1.s, riempie l’intero schermo (240 ×
320 pixel) di cifre esadecimali, disposte in 16 righe da 32 caratteri.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*font1.s**************************************************/
.global _start
.include "font_tab.inc"
.text
_start:
l1:
l2:
_end:
ldr
mov
bl
mov
mov
mov
add
bic
bl
add
cmp
ble
add
cmp
ble
sp, =stack
r8, #0
lcd_init_16bpp
r0, #0
r2, #0
r1, #0
r0, r1, r2
r0, r0, #0x0ff0
putnibble
r1, r1, #1
r1, #320/FONT_W-1
l2
r2, r2, #1
r2, #240/FONT_H-1
l1
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
inizializza lo stack pointer
r8=0 (framebuffer predefinito)
in r8: indirizzo del FB
cifra esadec. (nei 4 LSb di r0)
posiz. verticale (indice di riga)
posiz. orizzont. (indice di col.)
incrementa la cifra esadecimale
isola i 4 bit (nibble) meno sign.
scrive il carattere sullo schermo
incrementa la posizione orizzont.
oltre l’ultima pos. nella riga?
no: prosegue con car. successivo
si: incrementa la posizione vert.
oltre l’ultima pos. nella col.?
no: prosegue con riga successiva
b
_end
@ trappola
89
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
.func putnibble
/******************************************************************/
/* calcola la pos. nel FB (240 righe × 320 colonne) ove scrivere */
/* input : R0 – valore (esadecimale) da scrivere
*/
/*
R1 – posizione orizzontale [0..319] (indice di col.)
*/
/*
R2 - posizione verticale [0..239] (indice di riga)
*/
/*
R8 – indirizzo del framebuffer
*/
/******************************************************************/
putnibble:
stmfd sp!,{r1-r4,lr}
cmp
r1, #320/FONT_W-1 @ verifica se ci sta nella riga
bhi
pnb_end
@ fuori schermo (lateralmente)
cmp
r2, #240/FONT_H-1 @ verifica se ci sta nella col.
bhi
pnb_end
@ fuori schermo (verticalmente)
cmp
r0, #FONT_LINE/FONT_W-1
bhi
pnb_end
@ valore fuori range
mov
ldr
mul
r3, r1
r4, =FONT_W
r1, r3, r4
@
@
@
@
ldr
r4, =FONT_H*320
@
mul
r3, r2, r4
@
@
add
r1, r1, r3
@
add
r1, r8, r1, lsl #1 @
@
bl
drawnibble
pnb_end: ldmfd sp!,{r1-r4,pc}
.endfunc
posiz. oriz. (in larghezze car.)
larg. dei caratteri (in pixel)
r1 = offset orizz. (in pixel)
del carattere da scrivere
n. di pixel in una riga di car.
r3 = offset verticale (in pixel)
dell’inizio riga in cui scrivere
pos. (pixel) del car. da scriver
indir. di mem. in cui scrivere
il carattere (2 byte/pixel)
.func drawnibble
/**************************************************************/
/* disegna un carattere sullo schermo
*/
/* input : R0 - valore (esadecimale) da scrivere
*/
/*
R1 – indirizzo di mem. in cui scrivere il carattere*/
/**************************************************************/
drawnibble:
stmfd sp!,{r0,r2-r6,lr}
ldr
r3, =FONT_W*4 @ larg. car. (in byte: RGBA a 32 bit)
mul
r2, r0, r3
@ offset orizz.(byte) del car. in TAB
ldr
r3, =font_tab @ inizio della tabella (TAB)
add
r2, r2, r3
@ indir. I riga del car. (in TAB)
ldr
r3, =FONT_H-1 @ indice ultima riga car (di pixel)
ch_1:
ldr
r4, =FONT_W-1 @ indice ultima colonna (di pixel)
ch_2:
ldr
r6, =FONT_LINE @ n. pixel di una riga dei 16 car
mul
r5, r3, r6
@ offset vert.(pixel) dell’ultima riga
@ r5=offset vert. (pxl in TAB) dell’inizio ultima riga dei 16 car.
mov
r6, r3, lsl #8
@ r6 = (r3*256...
add
r6, r6, r3, lsl #6 @ ...+r3*64) = r3 * 320
@ r6=offset vert. (pxl su schermo) inizio ultima riga dei 16 car.
add
r6, r6, r4 @ r6=offset su sch. r4-esimo pxl del car.
add
r5, r5, r4 @ r5=offset in TAB r4-esimo pxl del car.
@ inizia dal vertice inferiore destro (VID) del carattere
90
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
ldr
bl
mov
strh
subs
bge
subs
bge
ldmfd
r0, [r2, r5, lsl #2]
rgba2h
r6, r6, lsl #1
r0, [r1, r6]
r4, r4, #1
ch_2
r3, r3, #1
ch_1
sp!,{r0,r2-r6,pc}
@
@
@
@
@
@
@
@
carica pixel del car. da TAB
lo converte da RGBA a 16 bit
offset su schermo (2byte/pxl)
lo scrive nel FB
decrementa indice di colonna
cicla se non era l’ultima
decrementa indice di riga
cicla se non era l’ultima
.endfunc
.func rgba2h
/**********************************************************/
/* convert a pixel from RGBA 32bpp format to a RGB 16bpp */
/* input/output : R0 - pixel value
*/
/**********************************************************/
rgba2h:
stmfd sp!,{r1,r2}
bic
r0, r0, #0xff000000
@ drop alpha
mov
r2, r0, lsr #8
bic
r2, r2, #0x00ff
bic
r2, r2, #0x0700
bic
r0, r0, #0xff0000
mov
r1, r0, lsr #5
bic
r1, r1, #0x001f
orr
r2, r2, r1
bic
r0, r0, #0x00ff00
mov
r1, r0, lsr #3
orr
r0, r2, r1
ldmfd sp!,{r1,r2}
mov
pc, lr
.endfunc
.bss
stack:
.end
5.12.3.1
.space
.space
4096
4
Il codice
Il programma è costituito, oltre che dal corpo principale, da tre subroutine:
putnibble, drawnibble e rgba2h.
Per comprendere il codice è necessario tenere presente l’organizzazione del file
font_tab.s: in questo file i pixel che definiscono le 16 cifre esadecimali si
riferiscono ad un’unica immagine dei 16 caratteri (0123456789ABCDEF), la cui altezza
FONT_H è di 15 pixel (coincidente con l’altezza di ciascun singolo carattere) e la cui
larghezza complessiva è di 16*FONT_W = 160 pixel (essendo FONT_W = 10 pixel la
larghezza di ciascun singolo carattere). Questa immagine può essere pensata come una
tabella (TAB) di pixel, con 15 righe e 160 colonne, in cui ciascun pixel è rappresentato
con 4 byte (nel formato RGBA a 32bpp). Un pixel all’interno della tabella può essere
91
individuato dall’indice di riga IR (0..14) e dall’indice di colonna IC (0..159) (la riga di
indice 0 è quella superiore; la colonna di indice 0 è quella più a sinistra.
Per ricopiare nel framebuffer il carattere corrispondente alla cifra i-esima (i=0..15),
bisogna estrarre da TAB le 15 sequenze di 10 pixel (corrispondenti alle 15 righe
dell’immagine del carattere) situate a partire dai pixel di indice IR=i*10, i*10+160,
i*10+160*2, ..., i*10+160*14. In sostanza gli indici iniziali delle 15 sequenze che
costituiscono la cifra i si ottengono come somma di due componenti: una componente
(i*10) è fissa e costituisce l’offset “orizzontale” (in pixel) della (prima riga di pixel
della) cifra i in TAB; l’altra componente è variabile (160*K, con k=0..14) e costituisce
l’offset “verticale” della riga k-esima di pixel in TAB (intendendo per offset verticale
la somma dei pixel di tutte le k righe precedenti).
Nel framebuffer (e nel display) i pixel sono rappresentati da 2 byte (16 bpp)
codificati nel formato RGB 565: nel ricopiare i pixel dalla tabella TAB al framebuffer
è quindi necessario operare la conversione da formato RGBA a formato RGB.
Per individuare la posizione dei pixel di ciascuna cifra nel framebuffer (e nel display)
è utile tenere presente che il display può essere pensato sia come una tabella di pixel
(da 240 righe e 320 colonne), sia come una tabella di caratteri (da 240/15 = 16 righe
di caratteri e 320/10 = 32 colonne di caratteri, essendo FONT_H = 15 e FONT_W = 10).
Dopo aver inizializzato il controller LCD a 16 bpp e aver ottenuto in r8 l’indirizzo del
framebuffer (righe 8 e 9), il programma inserisce: in r0 il valore della cifra
esadecimale (0..15) da visualizzare (riga 10), in r1 ed r2 l’indice di riga (0..15) e,
rispettivamente, l’indice di colonna (0..31) della posizione in cui il carattere va
collocato sullo schermo (inteso come tabella di 16 × 32 caratteri) (righe 11 e 12). Nel
loop interno (righe 13-18) viene effettuata la visualizzazione (tramite chiamata alla
subroutine putnibble) di una riga di 32 caratteri sullo schemo, incrementando
l’indice di colonna ad ogni iterazione; nel loop più esterno (righe 12-21) viene
incrementato l’indice di riga in modo da visualizzare le 16 righe; si può osservare che il
valore della cifra esadecimale da visualizzare è ottenuto (righe 13 e 14) come somma
dei suoi indici di riga e di colonna: ciò fa sì che, nelle diverse righe, le stesse cifre non
siano incolonnate, ma sfalsate orizzontalmente.
La subroutine putnibble, viene chiamata per visualizzare sullo schermo la cifra
esadecimale il cui valore è contenuto nel nibble (4 bit) meno significativo del registro
r0; la posizione in cui trasferire, nel framebuffer, i pixel della cifra (o in cui
visualizzare il carattere nel display) è passata alla subroutine nei registri r1 e r2, che
contengono gli indici di colonna (di caratteri) (0..31) e, rispettivamente, di riga (di
caratteri) (0.. 15) della posizione della cifra stessa: in questo modo è semplice
scrivere le cifre in modo allineato sul display.
Compito specifico della subroutine putnibble è di calcolare l'indirizzo di memoria
(nel framebuffer) in cui copiare l'immagine corrispondente alla cifra: tenendo conto
del fatto che ogni pixel occupa 2 byte nel framebuffer, questo indirizzo è ottenuto
92
(riga 52) sommando all’indirizzo iniziale del framebuffer (r8) un offset pari a 2 volte
la distanza (in pixel) tra il vertice superiore sinistro del rettangolo (VSSR) che
conterrà il carattere e il vertice superiore sinistro del display; questa distanza è
calcolata (riga 51) come somma di due componenti: una componente corrisponde alla
posizione “verticale” della riga di pixel che contiene il VSSR ed è pari al numero di
righe di pixel dal bordo superiore del display (dato dal prodotto dell’indice di riga r2
per l’altezza FONT_H, in pixel, di un carattere) moltiplicato per il numero (320) di
pixel di una riga (righe 48 e 49); l’altra componente fornisce la posizione “orizzontale”
del VSSR, cioè la sua distanza dal bordo sinistro del display ed è pari all’indice di
colonna r1 moltiplicato per la larghezza FONT_W, in pixel, di ciascun carattere (righe
44-46).
La seconda subroutine, drawnibble, ha il compito di scrivere sul framebuffer (e
quindi sul display) la porzione dell'immagine contenuta in TAB corrispondente al
carattere i-esimo scelto (costituita dalle 15 sequenze di 10 pixel che iniziano dai pixel
di indice IR= i*10+160*k, con k=0..14); nel framebuffer queste 15 sequenze di 10 pixel
vanno scritte, previa conversione da RGBA a RGB (ottenuta chiamando la subroutine
rgba2h) a partire dalle posizioni corrispondenti ai pixel di indice i*10+320*k, per la
prima serie di 16 caratteri visualizzati; 160+i*10+320*k, per la seconda serie di 16
caratteri (che vengono visualizzati di seguito, nella stessa riga); 320*15+i*10+320*k,
per la terza serie; 160+320*15+i*10+320*k per la quarta, e così via.
Il trasferimento dei pixel di ciascun carattere da TAB al framebuffer viene
effettuato a partire dal vertice inferiore destro.
Con le istruzioni alle righe 67 e 68 viene inserito in r2 l’offset (in byte) all’interno di
TAB (dell’inizio della prima riga di pixel) del carattere i-esimo (essendo l’indice i
contenuto in r0); questo offset, aggiunto all’indirizzo iniziale di TAB (font_tab),
viene trasformato nel corrispondente indirizzo di memoria (righe 69 e 70); in r3 ed r4
vengono poi inseriti gli indici (di riga e di colonna) del pixel corrispondente al vertice
inferiore destro (VID) del carattere (righe 71 e 72); nel ciclo compreso tra le righe
73 e 87, a partire dal pixel VID e decrementando ad ogni iterazione l’indice di colonna
(riga 86), si trasferiscono nel framebuffer i 10 pixel di una riga del carattere: si
calcolano (in r5) l’offset del pixel in TAB (righe 73, 74 e 80), e (in r6) l’offset del
corrispondente pixel nel framebuffer (righe 76, 77 e 79), si prelevano i 4 byte che
rappresentano il pixel (formato RGBA) in TAB (riga 82), si ottiene il pixel convertito
in formato RGB (2 byte) (riga 83), si moltiplica per 2 l’offset di pixel contenuto in r6
per ottenere l’offset di byte nel framebuffer (riga 84) (questa operazione va fatta
con un’istruzione apposita perché la successiva istruzione strh, a differenza della
str, non prevede lo scorrimento dell’operando), si scrive lo halfword contenente la
codifica RGB del pixel alla posizione calcolata nel framebuffer (riga 85); ad ogni
iterazione del ciclo più esterno (righe 72..89), si rieseguono le 10 iterazioni del ciclo
interno, dopo aver decrementato l’indice di riga (riga 88), per trasferire nel
framebuffer i pixel di un’altra riga.
93
L'ultima subroutine, rgba2h, serve per convertire un singolo pixel dal formato RGBA
a 32 bit in cui l'immagine è salvata, al formato RGB 16bit in cui va disegnata.
5.12.3.2
Compilazione
I comandi sono i soliti:
xscale-elf-as -gstabs
-o font1.o font1.s
xscale-elf-as -gstabs
-o font_tab.o font_tab.s
xscale-elf-as -gstabs
-o lcd.o lcd.s
xscale-elf-ld -Ttext 0x00100000
5.12.3.3
-o font1 font1.o font_tab.o lcd.o
Note
È abbastanza semplice creare un nuovo font, usando il programma di grafica Gimp
disponibile per Linux e Windows: bisogna dapprima, come si è fatto per l’esempio qui
presentato, creare un'immagine contenente, in un’unica riga, tutti caratteri che si
vogliono usare, rappresentati con un font di tipo non proporzionale (in cui tutti i
caratteri abbiano la stessa larghezza), come ad esempio il courier; questa immagine va
poi salvata nel formato C-Source, facendo attenzione ad utilizzare il formato dati
RGBA (save alpha channel); successivamente il file .c ottenuto va copiato nel directory
di lavoro con il nome font_tab.c; usando infine il comando:
make font_tab.s
posto che sia presente il Makefile distribuito con i sorgenti, viene generato appunto
il file font_tab.s che potrà poi essere utilizzato da un programma simile a font1.
94
5.13
Libreria per l’uso del disply LCD
Si riporta qui sotto il listing completo di 3 file utilizzati negli esercizi sul display LCD
(vedi paragrafo 5.12):
1. lcd.s: contenente, oltre alle 3 subroutine di inizializazione del controller LCD (a
4bpp, a 8bpp, a 16bpp), alcune altre subroutine, utilizzate per impostare i
parametri operativi del controller LCD, e i dati relativi alla tavolozza (palette) a 16
colori (4bpp);
2. palette256.s: file di dati contenente la palette a 256 colori (8bpp);
3. font_tab.s: file di dati contenente l’immagine delle 6 cifre esadecimali (nel
formato RGBD a 32bpp).
L’insieme di queste subroutine e di questi file dati può essere considerato come una
“libreria” per l’uso del display, cioè una raccolta di moduli software utilizzabili per la
programmazione di applicazioni che fan uso del display LCD.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/*lcd.s****************************************************/
.global lcd_init_4bpp, lcd_init_8bpp, lcd_init_16bpp
.text
.func lcd_init_4bpp
/**********************************************************/
/* lcd_init_4bpp: inizializza il display per 16 colori
*/
/* input: R8 – indirizzo del framebuffer
*/
/* input: R9 – indirizzo della palette
*/
/*
entrambi gli indirizzi devono essere multiplo di 16 */
/*
se R8 e/o R9 = 0x00, vengono usati valori predefiniti*/
/* output: R8 - indirizzo del framebuffer
*/
/* output: R9 - indirizzo della palette
*/
/**********************************************************/
lcd_init_4bpp:
stmfd sp!, {r0,r1,r2,lr}
cmp
ldreq
cmp
ldreq
r8,
r8,
r9,
r9,
#0x00
@ se = 0, usa val. predefinito
=0x01000000
#0x00
@ se = 0, usa val. predefinito
=palette_4bpp
@ accensione del display LCD
ldr
r1,=0x64000000
mvn
r0,#0x0000
bic
r0,r0,#0x0080
strh r0,[r1]
bl
bl
lcd_disable
lcd_gpio_init
@ catena di descrittori DMA per FB + PAL
ldr
r1, =frame_descr
ldr
r2, =pal_descr
ldr
r0, [r1]
add
r0, r0, r2
str
r0, [r1]
ldr
add
str
r0, [r2]
r0, r0, r1
r0, [r2]
ldr
add
str
r0, [r1, #4]
r0, r0, r8
r0, [r1, #4]
ldr
r0, [r2, #4]
95
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
.endfunc
add
str
r0, r0, r9
r0, [r2, #4]
ldr
add
str
r0, [r1, #12]
r0, r0, #(320*240/2)
r0, [r1, #12]
ldr
add
str
r0, [r2, #12]
r0, r0, #(16*2)
r0, [r2, #12]
mov
bl
r0, r1
lcd_dma_init
ldr
bl
r2, =lccrx_4bpp
lcd_timing_init
ldmfd sp!,{r0,r1,r2,lr}
mov
pc,lr
.func lcd_init_8bpp
/**********************************************************/
/* lcd_init_8bpp: inizializza il display per 256 colori
*/
/* input: R8 – indirizzo del framebuffer
*/
/* input: R9 – indirizzo della palette
*/
/*
entrambi gli indirizzi devono essere multiplo di 16 */
/*
se R8 e/o R9 = 0x00, vengono usati valori predefiniti*/
/* output: R8 - indirizzo del framebuffer
*/
/* output: R9 - indirizzo della palette
*/
/**********************************************************/
lcd_init_8bpp:
stmfd sp!, {r0,r1,r2,lr}
cmp
ldreq
cmp
ldreq
r8,
r8,
r9,
r9,
#0x00
@ se = 0, usa val. predefinito
=0x01000000
#0x00
@ se = 0, usa val. predefinito
=palette_8bpp
@ accensione del display LCD
ldr
r1,=0x64000000
mvn
r0,#0x0000
bic
r0,r0,#0x0080
strh r0,[r1]
bl
bl
lcd_disable
lcd_gpio_init
@ catena di descrittori DMA per FB + PAL
ldr
r1, =frame_descr
ldr
r2, =pal_descr
ldr
r0, [r1]
add
r0, r0, r2
str
r0, [r1]
ldr
add
str
r0, [r2]
r0, r0, r1
r0, [r2]
ldr
add
str
r0, [r1, #4]
r0, r0, r8
r0, [r1, #4]
ldr
add
str
r0, [r2, #4]
r0, r0, r9
r0, [r2, #4]
ldr
add
str
r0, [r1, #12]
r0, r0, #(320*240)
r0, [r1, #12]
ldr
add
str
r0, [r2, #12]
r0, r0, #(256*2)
r0, [r2, #12]
mov
bl
r0, r1
lcd_dma_init
96
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
ldr
bl
.endfunc
r2, =lccrx_8bpp
lcd_timing_init
ldmfd sp!,{r0,r1,r2,lr}
mov
pc,lr
.func lcd_init_16bpp
/**********************************************************/
/* lcd_init_16bpp: inizializza il display per 65536 colori*/
/* input: R8 – indirizzo del framebuffer
*/
/*
l’indirizzo devo essere multiplo di 16
*/
/*
se R8 = 0x00, viene usato il valore predefinito
*/
/* output: R8 - indirizzo del framebuffer
*/
/**********************************************************/
lcd_init_16bpp:
stmfd sp!, {r0,r1,r2,lr}
cmp
r8, #0x00
ldreq r8, =0x01000000
@ se = 0, usa val. predefinito
@ accensione del display LCD
ldr
r1,=0x64000000
mvn
r0,#0x0000
bic
r0,r0,#0x0080
strh r0,[r1]
bl
bl
lcd_disable
lcd_gpio_init
@ descrittore DMA per FB
ldr
r1, =frame_descr
ldr
r0, [r1]
add
r0, r0, r1
str
r0, [r1]
ldr
add
str
r0, [r1, #4]
r0, r0, r8
r0, [r1, #4]
ldr
add
str
r0, [r1, #12]
r0, r0, #(320*240*2)
r0, [r1, #12]
mov
bl
r0, r1
lcd_dma_init
ldr
bl
r2, =lccrx_16bpp
lcd_timing_init
ldmfd sp!,{r0,r1,r2,lr}
mov
pc,lr
.endfunc
.func lcd_disable
/**********************************************************/
/* lcd_disable: disattiva il controller LCD
*/
/**********************************************************/
LCCR0 = 0x44000000
LCSR = 0x44000038
lcd_disable:
ldr
r1, =LCCR0
ldr
r0, [r1]
tst
r0, #0x0001
@ se non è già disattivo
beq
disabled
w_done:
orr
str
r0, r0, #0x0400
r0, [r1]
ldr
ldr
tst
beq
r1, =LCSR
r0, [r1]
r0, #0x0001
w_done
mvn
str
r0, #0
r0, [r1]
@ imposta il campo DIS
@ attende completamento
@ della disattivazione
@ reset dello stato
disabled:
97
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
.endfunc
mov
pc, lr
.func lcd_gpio_init
/**********************************************************/
/* Setup GPIO 58:65,74:76 to act as LCD interface
*/
/* direction output
*/
/* alternate function 2
*/
/**********************************************************/
GPDR1 = 0x40E00010
GPDR2 = 0x40E00014
GAFR1U = 0x40E00060
GAFR2L = 0x40E00064
lcd_gpio_init:
ldr
r1,=GPDR1
ldr
r0,[r1]
orr
r0,r0,#0xFC000000
str
r0,[r1]
ldr
ldr
orr
orr
str
r1,=GPDR2
r0,[r1]
r0,r0,#0x00000003
r0,r0,#0x00001C00
r0,[r1]
ldr
ldr
orr
orr
str
r1,=GAFR1U
r0,[r1]
r0,r0,#0x0AA00000
r0,r0,#0xA0000000
r0,[r1]
ldr
ldr
orr
orr
str
r1,=GAFR2L
r0,[r1]
r0,r0,#0x0000000A
r0,r0,#0x02A00000
r0,[r1]
mov
pc,lr
.endfunc
.func lcd_timing_init
/**********************************************************/
/* Setup the LCD Controller Register to generate
*/
/* consistent timings for LCD dispay.
*/
/* input:
*/
/*
R2: address of values to be stored in LCCR[0:3]
*/
/**********************************************************/
LCCR0 = 0x44000000
LCCR1 = 0x44000004
LCCR2 = 0x44000008
LCCR3 = 0x4400000C
lcd_timing_init:
ldr
r1, =LCCR3
ldr
r0, [r2, #12]
str
r0, [r1]
.endfunc
ldr
ldr
str
r1, =LCCR2
r0, [r2, #8]
r0, [r1]
ldr
ldr
str
r1, =LCCR1
r0, [r2, #4]
r0, [r1]
ldr
ldr
str
r1, =LCCR0
r0, [r2]
r0, [r1]
ldr
orr
str
r1, =LCCR0
r0, r0, #0x00000001
r0, [r1]
mov
pc,lr
98
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
.func lcd_dma_init
/**********************************************************/
/* lcd_dma_init : setup dma registers
*/
/* input: r0 - start of chain dma structure
*/
/*
(must be in DRAM)
*/
/**********************************************************/
FDADR0
= 0x44000200
lcd_dma_init:
ldr
r1, =FDADR0
@ virtual to physical address traslation
add
r0, r0, #0xa0000000
str
r0,[r1]
.endfunc
mov
pc,lr
.data
lccrx_4bpp:
.long
.long
.long
.long
0x0030f878
0x0001013f
0x000000ef
0x0240ff09
lccrx_8bpp:
.long
.long
.long
.long
0x0030f878
0x0001013f
0x000000ef
0x0340ff09
lccrx_16bpp:
.long 0x0030f878
.long 0x0001013f
.long 0x000000ef
.long 0x0440ff09
.align 4
frame_descr:
.long
0x00000000 + 0xa0000000
.long
0x00000000 + 0xa0000000
.long
0x00000000
.long
0x00000000
.align 4
pal_descr:
.long
0x00000000 + 0xa0000000
.long
0x00000000 + 0xa0000000
.long
0x00000000
.long
0x04000000
.align 4
palette_4bpp:
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
0x0000
0x7800
0x01e0
0x79e0
0x000f
0x780f
0x03ef
0x39e7
0x7bef
0xf800
0x07e0
0xffe0
0x001f
0xf81f
0x07ff
0xffff
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
black
dark red
dark green
dark yellow
dark blue
dark magenta
dark cyan
dark grey
gray
red
green
yellow
blue
magenta
cyan
white
.align 4
palette_8bpp:
.include "palette256.s"
.end
99
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
/* palette256.s**********************************************/
.short
0x0420 @
0 33
0
.short
0x0380 @
0 28
0
.short
0x02c0 @
0 22
0
.short
0x0200 @
0 16
0
.short
0x0160 @
0 11
0
.short
0x28a0 @
5
5
0
.short
0x5800 @ 11
0
0
.short
0x8000 @ 16
0
0
.short
0x03e0 @
0 31
0
.short
0x0320 @
0 25
0
.short
0x0260 @
0 19
0
.short
0x01a0 @
0 13
0
.short
0x0100 @
0
8
0
.short
0x2840 @
5
2
0
.short
0x5800 @ 11
0
0
.short
0x8000 @ 16
0
0
.short
0x0380 @
0 28
0
.short
0x02c0 @
0 22
0
.short
0x0200 @
0 16
0
.short
0x0160 @
0 11
0
.short
0x00a0 @
0
5
0
.short
0x2800 @
5
0
0
.short
0x5800 @ 11
0
0
.short
0x8000 @ 16
0
0
.short
0x0320 @
0 25
0
.short
0x0260 @
0 19
0
.short
0x01c0 @
0 14
0
.short
0x0100 @
0
8
0
.short
0x0040 @
0
2
0
.short
0x2800 @
5
0
0
.short
0x5800 @ 11
0
0
.short
0x8000 @ 16
0
0
.short
0x02c0 @
0 22
0
.short
0x0220 @
0 17
0
.short
0x0160 @
0 11
0
.short
0x00a0 @
0
5
0
.short
0x0000 @
0
0
0
.short
0x2800 @
5
0
0
.short
0x5800 @ 11
0
0
.short
0x8000 @ 16
0
0
.short
0x0287 @
0 20
7
.short
0x01c7 @
0 14
7
.short
0x0107 @
0
8
7
.short
0x0047 @
0
2
7
.short
0x0007 @
0
0
7
.short
0x2807 @
5
0
7
.short
0x5807 @ 11
0
7
.short
0x8007 @ 16
0
7
.short
0x022e @
0 17 14
.short
0x016e @
0 11 14
.short
0x00ae @
0
5 14
.short
0x000e @
0
0 14
.short
0x000e @
0
0 14
.short
0x280e @
5
0 14
.short
0x580e @ 11
0 14
.short
0x800e @ 16
0 14
.short
0x01d5 @
0 14 21
.short
0x0115 @
0
8 21
.short
0x0075 @
0
3 21
.short
0x0015 @
0
0 21
.short
0x0015 @
0
0 21
.short
0x2815 @
5
0 21
.short
0x5815 @ 11
0 21
.short
0x8015 @ 16
0 21
.short
0x0620 @
0 49
0
.short
0x0580 @
0 44
0
.short
0x04c0 @
0 38
0
.short
0x1400 @
2 32
0
.short
0x4360 @
8 27
0
.short
0x6aa0 @ 13 21
0
.short
0x99e0 @ 19 15
0
.short
0xc120 @ 24
9
0
.short
0x05e0 @
0 47
0
.short
0x0520 @
0 41
0
.short
0x0460 @
0 35
0
.short
0x13a0 @
2 29
0
.short
0x4300 @
8 24
0
100
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
0x6a40
0x9980
0xc0e0
0x0580
0x04c0
0x0400
0x1360
0x42a0
0x69e0
0x9940
0xc080
0x0520
0x0460
0x03c0
0x1300
0x4240
0x69a0
0x98e0
0xc020
0x04c8
0x0428
0x0368
0x12a8
0x4208
0x6948
0x9888
0xc008
0x048f
0x03cf
0x030f
0x124f
0x41af
0x68ef
0x982f
0xc00f
0x0436
0x0376
0x02b6
0x1216
0x4156
0x6896
0x9816
0xc016
0x03dd
0x031d
0x027d
0x11bd
0x40fd
0x685d
0x981d
0xc01d
0x07e0
0x0780
0x26c0
0x5600
0x8560
0xaca0
0xdbe0
0xfb20
0x07e0
0x0720
0x2660
0x55a0
0x8500
0xac40
0xdb80
0xfae0
0x0781
0x06c1
0x2601
0x5561
0x84a1
0xabe1
0xdb41
0xfa81
0x0728
0x0668
0x25c8
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
13
19
24
0
0
0
2
8
13
19
24
0
0
0
2
8
13
19
24
0
0
0
2
8
13
19
24
0
0
0
2
8
13
19
24
0
0
0
2
8
13
19
24
0
0
0
2
8
13
19
24
0
0
4
10
16
21
27
31
0
0
4
10
16
21
27
31
0
0
4
10
16
21
27
31
0
0
4
18
12
7
44
38
32
27
21
15
10
4
41
35
30
24
18
13
7
1
38
33
27
21
16
10
4
0
36
30
24
18
13
7
1
0
33
27
21
16
10
4
0
0
30
24
19
13
7
2
0
0
63
60
54
48
43
37
31
25
63
57
51
45
40
34
28
23
60
54
48
43
37
31
26
20
57
51
46
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
8
8
8
8
8
8
8
8
15
15
15
15
15
15
15
15
22
22
22
22
22
22
22
22
29
29
29
29
29
29
29
29
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
1
1
1
1
1
1
1
1
8
8
8
101
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
0x5508
0x8448
0xaba8
0xdae8
0xfa28
0x06d0
0x0630
0x2570
0x54b0
0x8410
0xab50
0xda90
0xf9d0
0x0697
0x05d7
0x2517
0x5457
0x83b7
0xaaf7
0xda37
0xf997
0x063e
0x057e
0x24be
0x541e
0x835e
0xaa9e
0xd9fe
0xf93e
0x05df
0x051f
0x247f
0x53bf
0x82ff
0xaa5f
0xd99f
0xf8df
0x0fe0
0x3fe0
0x67e0
0x97e0
0xc760
0xeea0
0xfde0
0xfd20
0x0fe2
0x3fe2
0x67e2
0x97a2
0xc702
0xee42
0xfd82
0xfce2
0x0fe9
0x3fe9
0x67e9
0x9769
0xc6a9
0xede9
0xfd49
0xfc89
0x0ff0
0x3ff0
0x67d0
0x9710
0xc650
0xedb0
0xfcf0
0xfc30
0x0ff8
0x3ff8
0x6778
0x96b8
0xc618
0xed58
0xfc98
0xfbd8
0x0fff
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
10
16
21
27
31
0
0
4
10
16
21
27
31
0
0
4
10
16
21
27
31
0
0
4
10
16
21
27
31
0
0
4
10
16
21
27
31
1
7
12
18
24
29
31
31
1
7
12
18
24
29
31
31
1
7
12
18
24
29
31
31
1
7
12
18
24
29
31
31
1
7
12
18
24
29
31
31
1
40
34
29
23
17
54
49
43
37
32
26
20
14
52
46
40
34
29
23
17
12
49
43
37
32
26
20
15
9
46
40
35
29
23
18
12
6
63
63
63
63
59
53
47
41
63
63
63
61
56
50
44
39
63
63
63
59
53
47
42
36
63
63
62
56
50
45
39
33
63
63
59
53
48
42
36
30
63
8
8
8
8
8
16
16
16
16
16
16
16
16
23
23
23
23
23
23
23
23
30
30
30
30
30
30
30
30
31
31
31
31
31
31
31
31
0
0
0
0
0
0
0
0
2
2
2
2
2
2
2
2
9
9
9
9
9
9
9
9
16
16
16
16
16
16
16
16
24
24
24
24
24
24
24
24
31
102
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
.short
0x3fdf
0x671f
0x965f
0xc5bf
0xecff
0xfc3f
0xfb9f
0x0fff
0x3f7f
0x66bf
0x961f
0xc55f
0xec9f
0xfbff
0xfb3f
0x0fdf
0x3f1f
0x667f
0x95bf
0xc4ff
0xec5f
0xfb9f
0xfadf
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
@
7
12
18
24
29
31
31
1
7
12
18
24
29
31
31
1
7
12
18
24
29
31
31
62
56
50
45
39
33
28
63
59
53
48
42
36
31
25
62
56
51
45
39
34
28
22
31
31
31
31
31
31
31
31
31
31
31
31
31
31
31
31
31
31
31
31
31
31
31
103
/* font_tab.s**********************************************/
/*
*/
/* immagine delle 16 cifre esadecimali 0123456789ABCDEF
*/
/* nel formato RGBA (32 bpp)
*/
/**********************************************************/
.global font_tab
font_tab:
.ascii "\377\377\377\377\377\377\377\377\354\354\354\377\306\306\306\377\303\303"
.ascii "\303\377\357\357\357\377\377\377\377\377\377\377\377\377\377\377\377\377"
.ascii "\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
.ascii "\377\377\362\362\362\377\356\356\356\377\377\377\377\377\377\377\377\377"
.ascii "\377\377\377\377\377\377\377\377\372\372\372\377\320\320\320\377\216\216"
.ascii "\216\377mmm\377sss\377\252\252\252\377\365\365\365\377\377\377\377\377\377"
.ascii "\377\377\377\377\377\377\377\377\377\377\377\365\365\365\377\325\325\325"
.ascii "\377\274\274\274\377\306\306\306\377\361\361\361\377\377\377\377\377\377"
.ascii "\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
.ascii "\377\377\377\377\377\377\377\377\377\346\346\346\377\277\277\277\377\316"
.ascii "\316\316\377\371\371\371\377\377\377\377\377\377\377\377\377\377\377\377"
.ascii "\377\345\345\345\377\276\276\276\377\274\274\274\377\274\274\274\377\274"
.ascii "\274\274\377\274\274\274\377\331\331\331\377\377\377\377\377\377\377\377"
.ascii "\377\377\377\377\377\377\377\377\377\377\377\377\377\354\354\354\377\306"
.ascii "\306\306\377\277\277\277\377\323\323\323\377\362\362\362\377\377\377\377"
.ascii "\377\377\377\377\377\344\344\344\377\274\274\274\377\274\274\274\377\274"
.ascii "\274\274\377\274\274\274\377\274\274\274\377\274\274\274\377\274\274\274"
.ascii "\377\366\366\366\377\377\377\377\377\377\377\377\377\377\377\377\377\375"
.ascii "\375\375\377\321\321\321\377\275\275\275\377\323\323\323\377\367\367\367"
.ascii "\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
.ascii "\377\377\377\365\365\365\377\315\315\315\377\274\274\274\377\336\336\336"
.ascii "\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
.ascii "\377\377\377\377\377\377\377\373\373\373\377\322\322\322\377\274\274\274"
.ascii "\377\321\321\321\377\372\372\372\377\377\377\377\377\377\377\377\377\367"
.ascii "\367\367\377\314\314\314\377\274\274\274\377\274\274\274\377\274\274\274"
.ascii "\377\274\274\274\377\302\302\302\377\351\351\351\377\375\375\375\377\377"
.ascii "\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
.ascii "\377\370\370\370\377\316\316\316\377\274\274\274\377\304\304\304\377\351"
.ascii "\351\351\377\375\375\375\377\377\377\377\377\313\313\313\377\274\274\274"
.ascii "\377\274\274\274\377\274\274\274\377\304\304\304\377\347\347\347\377\377"
.ascii "\377\377\377\377\377\377\377\377\377\377\377\373\373\373\377\323\323\323"
.ascii "\377\274\274\274\377\274\274\274\377\274\274\274\377\274\274\274\377\274"
.ascii "\274\274\377\274\274\274\377\274\274\274\377\336\336\336\377\377\377\377"
.ascii "\377\322\322\322\377\274\274\274\377\274\274\274\377\274\274\274\377\274"
.ascii "\274\274\377\274\274\274\377\274\274\274\377\274\274\274\377\314\314\314"
.ascii "\377\377\377\377\377\375\375\375\377\304\304\304\377???\377\30\30\30\377"
.ascii "\26\26\26\377888\377\275\275\275\377\377\377\377\377\377\377\377\377\377"
.ascii "\377\377\377\377\377\377\377\370\370\370\377\336\336\336\377\200\200\200"
.ascii "\377>>>\377\271\271\271\377\377\377\377\377\377\377\377\377\377\377\377\377"
.ascii "\377\377\377\377\337\337\337\377>>>\377\15\15\15\377\34\34\34\377\15\15\15"
.ascii "\377\14\14\14\377ooo\377\367\367\367\377\377\377\377\377\377\377\377\377"
.ascii "\342\342\342\377;;;\377\40\40\40\377\22\22\22\377\30\30\30\377BBB\377\277"
.ascii "\277\277\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
.ascii "\377\377\377\377\377\377\377\377\377\365\365\365\377ooo\377\24\24\24\377"
.ascii "QQQ\377\350\350\350\377\377\377\377\377\377\377\377\377\377\377\377\377\241"
.ascii "\241\241\377\31\31\31\377\22\22\22\377\22\22\22\377\22\22\22\377\22\22\22"
.ascii "\377xxx\377\377\377\377\377\377\377\377\377\377\377\377\377\372\372\372\377"
.ascii "\275\275\275\377222\377\30\30\30\377\24\24\24\377\37\37\37\377|||\377\377"
.ascii "\377\377\377\377\377\377\377\236\236\236\377\22\22\22\377\22\22\22\377\22"
.ascii "\22\22\377\22\22\22\377\22\22\22\377\22\22\22\377\22\22\22\377\340\340\340"
.ascii "\377\377\377\377\377\377\377\377\377\350\350\350\377fff\377\36\36\36\377"
.ascii "\23\23\23\377\37\37\37\377UUU\377\354\354\354\377\377\377\377\377\377\377"
.ascii "\377\377\377\377\377\377\326\326\326\377MMM\377\34\34\34\377\22\22\22\377"
.ascii "(((\377\227\227\227\377\366\366\366\377\377\377\377\377\377\377\377\377\377"
.ascii "\377\377\377\377\377\377\377\351\351\351\377PPP\377\22\22\22\377TTT\377\352"
.ascii "\352\352\377\377\377\377\377\377\377\377\377\344\344\344\377HHH\377\22\22"
.ascii "\22\377\22\22\22\377\22\22\22\377\22\22\22\377\26\26\26\377+++\377\224\224"
.ascii "\224\377\372\372\372\377\377\377\377\377\377\377\377\377\377\377\377\377"
.ascii "\301\301\301\377MMM\377\34\34\34\377\22\22\22\377\26\26\26\377+++\377\217"
.ascii "\217\217\377\377\377\377\377EEE\377\22\22\22\377\22\22\22\377\22\22\22\377"
.ascii "\27\27\27\377000\377\211\211\211\377\357\357\357\377\377\377\377\377\360"
.ascii "\360\360\377aaa\377\22\22\22\377\22\22\22\377\22\22\22\377\22\22\22\377\22"
.ascii "\22\22\377\22\22\22\377\22\22\22\377\213\213\213\377\377\377\377\377^^^\377"
104
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
"\22\22\22\377\22\22\22\377\22\22\22\377\22\22\22\377\22\22\22\377\22\22\22"
"\377\22\22\22\377III\377\377\377\377\377\342\342\342\377DDD\377\5\5\5\377"
"QQQ\377PPP\377\0\0\0\377222\377\356\356\356\377\377\377\377\377\377\377\377"
"\377\332\332\332\377hhh\377\35\35\35\377\0\0\0\377\0\0\0\377\261\261\261"
"\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\332"
"\332\332\377111\377\222\222\222\377\326\326\326\377\235\235\235\377\35\35"
"\35\377\36\36\36\377\303\303\303\377\377\377\377\377\377\377\377\377\326"
"\326\326\377\0\0\0\377;;;\377\212\212\212\377888\377\0\0\0\377\"\"\"\377"
"\375\375\375\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\262\262\262\377\7\7\7\377\0\0\0\377CCC\377\346\346"
"\346\377\377\377\377\377\377\377\377\377\377\377\377\377\231\231\231\377"
"\22\22\22\377000\377000\377000\377000\377\211\211\211\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\300\300\300\377!!!\377\15\15\15\377hhh\377"
"yyy\377\17\17\17\377aaa\377\377\377\377\377\377\377\377\377\252\252\252\377"
"000\377000\377000\377000\377&&&\377\2\2\2\377$$$\377\346\346\346\377\377"
"\377\377\377\373\373\373\377\207\207\207\377\7\7\7\377<<<\377\212\212\212"
"\377\40\40\40\377\0\0\0\377\207\207\207\377\377\377\377\377\377\377\377\377"
"\367\367\367\377FFF\377\0\0\0\377000\377|||\377\31\31\31\377\16\16\16\377"
"\244\244\244\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\324\324\324\377\22\22\22\377\0\0\0\377\30\30\30\377\321\321\321"
"\377\377\377\377\377\377\377\377\377\362\362\362\377\212\212\212\377\2\2"
"\2\377\0\0\0\377\204\204\204\377\220\220\220\377KKK\377\0\0\0\377\22\22\22"
"\377\304\304\304\377\377\377\377\377\377\377\377\377\307\307\307\377\31\31"
"\31\377\0\0\0\377CCC\377\212\212\212\377___\377\10\10\10\377bbb\377\377\377"
"\377\377\232\232\232\377\0\0\0\377\4\4\4\377qqq\377ppp\377\14\14\14\377\0"
"\0\0\377yyy\377\370\370\370\377\370\370\370\377\265\265\265\377;;;\377\0"
"\0\0\377,,,\377\220\220\220\377\220\220\220\377\200\200\200\377\"\"\"\377"
"\202\202\202\377\377\377\377\377\263\263\263\377BBB\377\0\0\0\377...\377"
"\206\206\206\377\220\220\220\377\215\215\215\377<<<\377:::\377\377\377\377"
"\377\234\234\234\377\2\2\2\377+++\377\331\331\331\377\332\332\332\377)))"
"\377\6\6\6\377\215\215\215\377\377\377\377\377\377\377\377\377\206\206\206"
"\377333\377^^^\377\2\2\2\377\0\0\0\377\261\261\261\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\377\377\332\332\332\377555\377\313\313"
"\313\377\377\377\377\377\370\370\370\377SSS\377\1\1\1\377\227\227\227\377"
"\377\377\377\377\377\377\377\377\333\333\333\377\36\36\36\377\300\300\300"
"\377\376\376\376\377\325\325\325\377\0\0\0\377\0\0\0\377\305\305\305\377"
"\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\353\353"
"\353\377888\377\23\23\23\377\7\7\7\377CCC\377\346\346\346\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\231\231\231\377@@@\377\370\370\370\377"
"\370\370\370\377\370\370\370\377\370\370\370\377\373\373\373\377\377\377"
"\377\377\377\377\377\377\376\376\376\377666\377\0\0\0\377\233\233\233\377"
"\374\374\374\377\355\355\355\377ooo\377\206\206\206\377\377\377\377\377\377"
"\377\377\377\374\374\374\377\370\370\370\377\370\370\370\377\370\370\370"
"\377\365\365\365\377\210\210\210\377\21\21\21\377\227\227\227\377\374\374"
"\374\377\377\377\377\377\354\354\354\377<<<\377\14\14\14\377\307\307\307"
"\377\376\376\376\377\232\232\232\377\0\0\0\377HHH\377\377\377\377\377\377"
"\377\377\377\254\254\254\377\17\17\17\377\11\11\11\377\311\311\311\377\375"
"\375\375\377\216\216\216\377\0\0\0\377\36\36\36\377\376\376\376\377\377\377"
"\377\377\377\377\377\377\377\377\377\377\254\254\254\377\30\30\30\377\10"
"\10\10\377\1\1\1\377\261\261\261\377\377\377\377\377\377\377\377\377\377"
"\377\377\377\307\307\307\377\4\4\4\377\0\0\0\377\352\352\352\377\377\377"
"\377\377\332\332\332\377!!!\377\12\12\12\377\240\240\240\377\377\377\377"
"\377\334\334\334\377555\377\0\0\0\377SSS\377\364\364\364\377\376\376\376"
"\377\346\346\346\377FFF\377bbb\377\377\377\377\377\346\346\346\377\0\0\0"
"\377\7\7\7\377\310\310\310\377\375\375\375\377\216\216\216\377\10\10\10\377"
"\24\24\24\377\262\262\262\377\377\377\377\377\377\377\377\377hhh\377\0\0"
"\0\377NNN\377\377\377\377\377\377\377\377\377\346\346\346\377RRR\377\220"
"\220\220\377\377\377\377\377\377\377\377\377vvv\377\0\0\0\377RRR\377\356"
"\356\356\377\377\377\377\377\372\372\372\377kkk\377:::\377\377\377\377\377"
"III\377\0\0\0\377EEE\377\374\374\374\377\357\357\357\377TTT\377\0\0\0\377"
"YYY\377\361\361\361\377\377\377\377\377\337\337\337\377\342\342\342\377\372"
"\372\372\377\5\5\5\377\0\0\0\377\261\261\261\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\377\365\365\365\377\312\312\312\377"
"\361\361\361\377\377\377\377\377\375\375\375\377YYY\377\1\1\1\377\227\227"
"\227\377\377\377\377\377\377\377\377\377\370\370\370\377\323\323\323\377"
"\366\366\366\377\377\377\377\377\356\356\356\377\0\0\0\377\3\3\3\377\312"
"\312\312\377\377\377\377\377\377\377\377\377\377\377\377\377\370\370\370"
"\377\210\210\210\377\36\36\36\377lll\377\24\24\24\377CCC\377\346\346\346"
"\377\377\377\377\377\377\377\377\377\377\377\377\377\231\231\231\377BBB\377"
"\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
105
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
"\377\377\377\377\377\377\377\377\377\377\316\316\316\377\0\0\0\377\22\22"
"\22\377\356\356\356\377\377\377\377\377\375\375\375\377\360\360\360\377\361"
"\361\361\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\327\327\327\377222\377OOO\377\362\362"
"\362\377\377\377\377\377\377\377\377\377\352\352\352\377111\377\6\6\6\377"
"\217\217\217\377\373\373\373\377\241\241\241\377\0\0\0\377kkk\377\377\377"
"\377\377\377\377\377\377\177\177\177\377\2\2\2\377%%%\377\332\332\332\377"
"\377\377\377\377\270\270\270\377\0\0\0\377\0\0\0\377\320\320\320\377\377"
"\377\377\377\377\377\377\377\377\377\377\377eee\377PPP\377HHH\377\0\0\0\377"
"lll\377\377\377\377\377\377\377\377\377\377\377\377\377\307\307\307\377\4"
"\4\4\377\0\0\0\377\352\352\352\377\377\377\377\377\334\334\334\377&&&\377"
"\16\16\16\377\261\261\261\377\376\376\376\377\240\240\240\377\7\7\7\377\30"
"\30\30\377\271\271\271\377\377\377\377\377\377\377\377\377\357\357\357\377"
"```\377ppp\377\377\377\377\377\346\346\346\377\0\0\0\377\7\7\7\377\310\310"
"\310\377\377\377\377\377\327\327\327\377///\377\0\0\0\377vvv\377\371\371"
"\371\377\377\377\377\377hhh\377\0\0\0\377NNN\377\377\377\377\377\377\377"
"\377\377\372\372\372\377\335\335\335\377\351\351\351\377\377\377\377\377"
"\377\377\377\377vvv\377\0\0\0\377RRR\377\356\356\356\377\377\377\377\377"
"\376\376\376\377\330\330\330\377\314\314\314\377\377\377\377\377\34\34\34"
"\377\0\0\0\377sss\377\377\377\377\377\377\377\377\377{{{\377\0\0\0\377::"
":\377\342\342\342\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\377\5\5\5\377\0\0\0\377\261\261\261\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\336\336\336\377777\377\24\24\24\377\264"
"\264\264\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\232\232\232\377\0\0\0\377EEE\377\376"
"\376\376\377\377\377\377\377\377\377\377\377\377\377\377\377\316\316\316"
"\377\33\33\33\377___\377\262\262\262\377\24\24\24\377CCC\377\346\346\346"
"\377\377\377\377\377\377\377\377\377\377\377\377\377\231\231\231\377---\377"
"\246\246\246\377\307\307\307\377\370\370\370\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\377\223\223\223\377\0\0\0\377666\377"
"\331\331\331\377\223\223\223\377\200\200\200\377\306\306\306\377\375\375"
"\375\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\377xxx\377\11\11\11\377\310\310\310\377\377"
"\377\377\377\377\377\377\377\377\377\377\377\364\364\364\377```\377\0\0\0"
"\377\40\40\40\377\252\252\252\377777\377\23\23\23\377\321\321\321\377\377"
"\377\377\377\377\377\377\377www\377\0\0\0\377)))\377\334\334\334\377\377"
"\377\377\377\270\270\270\377\0\0\0\377\0\0\0\377\234\234\234\377\377\377"
"\377\377\377\377\377\377\377\377\377\377000\377|||\377\214\214\214\377\0"
"\0\0\377222\377\377\377\377\377\377\377\377\377\377\377\377\377\307\307\307"
"\377\4\4\4\377\0\0\0\377\352\352\352\377\377\377\377\377\214\214\214\377"
"\0\0\0\377:::\377\360\360\360\377\372\372\372\377jjj\377\0\0\0\377<<<\377"
"\346\346\346\377\377\377\377\377\377\377\377\377\374\374\374\377\343\343"
"\343\377\346\346\346\377\377\377\377\377\346\346\346\377\0\0\0\377\7\7\7"
"\377\310\310\310\377\377\377\377\377\363\363\363\377RRR\377\0\0\0\377FFF"
"\377\357\357\357\377\377\377\377\377hhh\377\0\0\0\377NNN\377\377\377\377"
"\377\277\277\277\377\230\230\230\377\360\360\360\377\377\377\377\377\377"
"\377\377\377\377\377\377\377vvv\377\0\0\0\377RRR\377\356\356\356\377\377"
"\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\0\0\0\377\0\0\0\377|||\377\377\377\377\377\377\377\377\377{{{\377\0"
"\0\0\377'''\377\331\331\331\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\5\5\5\377\0\0\0\377\261\261\261\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\376\376\376\377\230\230\230\377\12\12\12\377UUU"
"\377\363\363\363\377\377\377\377\377\377\377\377\377\377\377\377\377\326"
"\326\326\377vvv\377<<<\377\20\20\20\377ccc\377\342\342\342\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\372\372\372\377[[[\377000\377\334\334"
"\334\377\262\262\262\377\24\24\24\377CCC\377\346\346\346\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\244\244\244\377\36\36\36\377\13\13\13"
"\377\15\15\15\377...\377\240\240\240\377\377\377\377\377\377\377\377\377"
"\377\377\377\377```\377\0\0\0\377111\377\40\40\40\377\4\4\4\377\1\1\1\377"
"\26\26\26\377\246\246\246\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\327\327\327\377\12\12\12\377aaa\377"
"\370\370\370\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\335\335\335\377888\377\0\0\0\377\12\12\12\377\12\12\12\377\223\223"
"\223\377\375\375\375\377\377\377\377\377\377\377\377\377\216\216\216\377"
"\7\7\7\377\4\4\4\377\272\272\272\377\377\377\377\377\216\216\216\377\0\0"
"\0\377\0\0\0\377\211\211\211\377\377\377\377\377\377\377\377\377\330\330"
"\330\377'''\377\255\255\255\377\327\327\327\377\5\5\5\377\22\22\22\377\345"
"\345\345\377\377\377\377\377\377\377\377\377\307\307\307\377\4\4\4\377\0"
106
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
"\0\0\377ggg\377777\377\14\14\14\377<<<\377\316\316\316\377\377\377\377\377"
"\364\364\364\377999\377\0\0\0\377QQQ\377\375\375\375\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\346\346\346\377\0\0\0\377\7\7\7\377\310\310\310\377\377\377\377"
"\377\377\377\377\377bbb\377\0\0\0\377000\377\352\352\352\377\377\377\377"
"\377hhh\377\0\0\0\377'''\377~~~\377BBB\377999\377\341\341\341\377\377\377"
"\377\377\377\377\377\377\377\377\377\377vvv\377\0\0\0\377JJJ\377\326\326"
"\326\377\252\252\252\377111\377\316\316\316\377\377\377\377\377\377\377\377"
"\377\0\0\0\377\0\0\0\377|||\377\377\377\377\377\377\377\377\377{{{\377\0"
"\0\0\377'''\377\331\331\331\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\5\5\5\377\0\0\0\377\261\261\261\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\345\345\345\377\30\30\30\377$$$\377\331\331\331"
"\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\306"
"\306\306\377:::\377\24\24\24\377\11\11\11\377JJJ\377\270\270\270\377\377"
"\377\377\377\377\377\377\377\376\376\376\377\247\247\247\377\27\27\27\377"
"\223\223\223\377\374\374\374\377\262\262\262\377\24\24\24\377CCC\377\346"
"\346\346\377\377\377\377\377\377\377\377\377\377\377\377\377\352\352\352"
"\377\311\311\311\377\254\254\254\377000\377\0\0\0\377\27\27\27\377\303\303"
"\303\377\377\377\377\377\377\377\377\377OOO\377\0\0\0\377\0\0\0\377@@@\377"
"\240\240\240\377(((\377\0\0\0\377,,,\377\350\350\350\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\374\374\374\377iii\377\25\25\25\377\262"
"\262\262\377\376\376\376\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\376\376\376\377\303\303\303\377,,,\377\16\16\16\377\10\10\10\377\3\3"
"\3\377000\377\304\304\304\377\376\376\376\377\377\377\377\377\321\321\321"
"\377\37\37\37\377\0\0\0\377111\377sss\377###\377\2\2\2\377\0\0\0\377\211"
"\211\211\377\377\377\377\377\377\377\377\377\245\245\245\377+++\377\321\321"
"\321\377\345\345\345\377(((\377\11\11\11\377\247\247\247\377\377\377\377"
"\377\377\377\377\377\307\307\307\377\4\4\4\377\0\0\0\377\24\24\24\377\20"
"\20\20\377\5\5\5\377333\377\271\271\271\377\376\376\376\377\364\364\364\377"
"555\377\0\0\0\377RRR\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\346\346"
"\346\377\0\0\0\377\7\7\7\377\310\310\310\377\377\377\377\377\377\377\377"
"\377bbb\377\0\0\0\377000\377\352\352\352\377\377\377\377\377hhh\377\0\0\0"
"\377\5\5\5\377\17\17\17\377\10\10\10\377999\377\341\341\341\377\377\377\377"
"\377\377\377\377\377\377\377\377\377vvv\377\0\0\0\377\21\21\21\377000\377"
"%%%\377\16\16\16\377\312\312\312\377\377\377\377\377\377\377\377\377\0\0"
"\0\377\0\0\0\377|||\377\377\377\377\377\377\377\377\377{{{\377\0\0\0\377"
"'''\377\331\331\331\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\377\5\5\5\377\0\0\0\377\261\261\261\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\352\352\352\377QQQ\377\40\40\40\377\275\275\275\377\376\376\376\377\377"
"\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\363\363\363\377\222\222\222\377\11\11\11\377\12\12\12"
"\377\330\330\330\377\377\377\377\377\343\343\343\377???\377\24\24\24\377"
"www\377\205\205\205\377]]]\377\12\12\12\377###\377zzz\377\344\344\344\377"
"\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\320\320\320\377\25\25\25\377\2\2\2\377kkk\377\370\370\370\377\377"
"\377\377\377OOO\377\0\0\0\377\0\0\0\377\323\323\323\377\377\377\377\377\254"
"\254\254\377\0\0\0\377\21\21\21\377\264\264\264\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\304\304\304\377\35\35\35\377MMM\377\364\364\364"
"\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\353"
"\353\353\377999\377\14\14\14\377\304\304\304\377\235\235\235\377\11\11\11"
"\377\0\0\0\377,,,\377\330\330\330\377\377\377\377\377\377\377\377\377\237"
"\237\237\377\25\25\25\377\0\0\0\377\14\14\14\377hhh\377\33\33\33\377\0\0"
"\0\377\245\245\245\377\377\377\377\377\377\377\377\377www\377$$$\377\256"
"\256\256\377\243\243\243\377777\377\3\3\3\377xxx\377\377\377\377\377\377"
"\377\377\377\307\307\307\377\4\4\4\377\0\0\0\377\347\347\347\377\330\330"
"\330\377ppp\377\0\0\0\377\22\22\22\377\301\301\301\377\365\365\365\377=="
"=\377\0\0\0\377JJJ\377\365\365\365\377\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\346\346\346"
"\377\0\0\0\377\7\7\7\377\310\310\310\377\377\377\377\377\377\377\377\377"
"bbb\377\0\0\0\377777\377\353\353\353\377\377\377\377\377hhh\377\0\0\0\377"
"AAA\377\323\323\323\377nnn\377999\377\341\341\341\377\377\377\377\377\377"
"\377\377\377\377\377\377\377vvv\377\0\0\0\377\32\32\32\377LLL\377:::\377"
"\21\21\21\377\312\312\312\377\377\377\377\377\377\377\377\377'''\377\0\0"
"\0\377mmm\377\377\377\377\377\373\373\373\377ppp\377\0\0\0\377AAA\377\346"
"\346\346\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\5\5\5\377\0\0\0\377\261\261\261\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\377\377\377\373\373\373\377~~~\377\15"
107
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
"\15\15\377\253\253\253\377\373\373\373\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\366\366\366\377KKK\377\0\0\0\377ggg\377\377\377"
"\377\377\324\324\324\377%%%\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0"
"\0\0\377\0\0\0\377\4\4\4\377\307\307\307\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\377\377\377\377\356\356\356\377RRR\377"
"\0\0\0\377KKK\377\352\352\352\377\377\377\377\377vvv\377\0\0\0\377\0\0\0"
"\377\362\362\362\377\377\377\377\377\312\312\312\377\0\0\0\377\15\15\15\377"
"\244\244\244\377\377\377\377\377\377\377\377\377\373\373\373\377ccc\377\1"
"\1\1\377\220\220\220\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\235\235\235\377\2\2\2\377999\377\375\375"
"\375\377\374\374\374\377\225\225\225\377\13\13\13\377\3\3\3\377\233\233\233"
"\377\377\377\377\377\377\377\377\377\373\373\373\377\333\333\333\377\263"
"\263\263\377\315\315\315\377\351\351\351\377\5\5\5\377\0\0\0\377\323\323"
"\323\377\377\377\377\377\350\350\350\377HHH\377\2\2\2\377\13\13\13\377\13"
"\13\13\377\4\4\4\377\0\0\0\377FFF\377\350\350\350\377\377\377\377\377\307"
"\307\307\377\4\4\4\377\0\0\0\377\352\352\352\377\377\377\377\377\344\344"
"\344\377:::\377\1\1\1\377bbb\377\360\360\360\377nnn\377\0\0\0\377111\377"
"\332\332\332\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\377\346\346\346\377\0\0\0\377\7\7\7"
"\377\310\310\310\377\377\377\377\377\351\351\351\377EEE\377\0\0\0\377ccc"
"\377\365\365\365\377\377\377\377\377hhh\377\0\0\0\377NNN\377\377\377\377"
"\377\327\327\327\377\275\275\275\377\365\365\365\377\377\377\377\377\377"
"\377\377\377\377\377\377\377vvv\377\0\0\0\377OOO\377\343\343\343\377\301"
"\301\301\377YYY\377\327\327\327\377\377\377\377\377\377\377\377\377VVV\377"
"\0\0\0\377AAA\377\375\375\375\377\355\355\355\377PPP\377\0\0\0\377aaa\377"
"\365\365\365\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\5\5\5\377\0\0\0\377\261\261\261\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\377\377\377\377\322\322\322\377\22\22"
"\22\377MMM\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\365\365\365\377\300\300\300\377\362"
"\362\362\377\377\377\377\377\373\373\373\377kkk\377\0\0\0\377OOO\377\377"
"\377\377\377\360\360\360\377\263\263\263\377\246\246\246\377\246\246\246"
"\377\246\246\246\377ttt\377\15\15\15\377,,,\377\230\230\230\377\354\354\354"
"\377\377\377\377\377\377\377\377\377\331\331\331\377\333\333\333\377\377"
"\377\377\377\360\360\360\377VVV\377\0\0\0\377KKK\377\352\352\352\377\377"
"\377\377\377\245\245\245\377\0\0\0\377\0\0\0\377\360\360\360\377\377\377"
"\377\377\311\311\311\377\0\0\0\377\20\20\20\377\260\260\260\377\377\377\377"
"\377\377\377\377\377\347\347\347\377\15\15\15\377\1\1\1\377\333\333\333\377"
"\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377jjj\377\0\0\0\377JJJ\377\377\377\377\377\377\377\377\377\334\334"
"\334\377555\377\1\1\1\377\230\230\230\377\377\377\377\377\377\377\377\377"
"\366\366\366\377\371\371\371\377\377\377\377\377\377\377\377\377\310\310"
"\310\377\0\0\0\377\34\34\34\377\376\376\376\377\377\377\377\377\325\325\325"
"\377'''\377|||\377\246\246\246\377\246\246\246\377uuu\377\16\16\16\377))"
")\377\324\324\324\377\377\377\377\377\307\307\307\377\4\4\4\377\0\0\0\377"
"\352\352\352\377\377\377\377\377\360\360\360\377VVV\377\0\0\0\377JJJ\377"
"\351\351\351\377\241\241\241\377\7\7\7\377\12\12\12\377\235\235\235\377\376"
"\376\376\377\377\377\377\377\377\377\377\377\377\377\377\377\376\376\376"
"\377\377\377\377\377\346\346\346\377\0\0\0\377\7\7\7\377\310\310\310\377"
"\377\377\377\377\323\323\323\377+++\377\6\6\6\377\224\224\224\377\376\376"
"\376\377\377\377\377\377hhh\377\0\0\0\377NNN\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\267\267\267\377zzz\377\351\351\351\377\377\377\377"
"\377vvv\377\0\0\0\377RRR\377\356\356\356\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\377\377\377\377\254\254\254\377\11\11"
"\11\377\37\37\37\377\306\306\306\377\317\317\317\377\35\35\35\377\13\13\13"
"\377\240\240\240\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\5\5\5\377\0\0\0\377\261\261\261\377\377\377"
"\377\377\377\377\377\377\377\377\377\377\377\377\377\377\364\364\364\377"
"OOO\377\12\12\12\377\240\240\240\377\332\332\332\377\332\332\332\377\332"
"\332\332\377\360\360\360\377\377\377\377\377\377\377\377\377\326\326\326"
"\377\0\0\0\377\301\301\301\377\377\377\377\377\356\356\356\377)))\377\0\0"
"\0\377{{{\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\262\262\262\377\24\24\24\377CCC\377"
"\346\346\346\377\377\377\377\377\377\377\377\377\377\377\377\377fff\377:"
"::\377\377\377\377\377\326\326\326\377!!!\377\4\4\4\377\203\203\203\377\377"
"\377\377\377\377\377\377\377\363\363\363\377\21\21\21\377\0\0\0\377\251\251"
"\251\377\377\377\377\377\213\213\213\377\0\0\0\377(((\377\345\345\345\377"
"\377\377\377\377\377\377\377\377\227\227\227\377\0\0\0\377$$$\377\374\374"
"\374\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
108
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
"\377\377\377\377\203\203\203\377\0\0\0\377\15\15\15\377\322\322\322\377\377"
"\377\377\377\301\301\301\377!!!\377\32\32\32\377\275\275\275\377\377\377"
"\377\377\377\377\377\377&&&\377hhh\377\367\367\367\377\345\345\345\377II"
"I\377\5\5\5\377\206\206\206\377\377\377\377\377\377\377\377\377\255\255\255"
"\377\16\16\16\377\344\344\344\377\377\377\377\377\377\377\377\377\343\343"
"\343\377,,,\377\7\7\7\377\263\263\263\377\377\377\377\377\307\307\307\377"
"\4\4\4\377\0\0\0\377\352\352\352\377\377\377\377\377\332\332\332\377///\377"
"\0\0\0\377UUU\377\360\360\360\377\347\347\347\377CCC\377\0\0\0\377\25\25"
"\25\377\263\263\263\377\365\365\365\377\372\372\372\377\323\323\323\377\236"
"\236\236\377\377\377\377\377\346\346\346\377\0\0\0\377\7\7\7\377\310\310"
"\310\377\367\367\367\377ddd\377\0\0\0\377888\377\343\343\343\377\377\377"
"\377\377\377\377\377\377hhh\377\0\0\0\377NNN\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\220\220\220\377333\377\335\335\335\377\377\377\377"
"\377vvv\377\0\0\0\377RRR\377\356\356\356\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\377\377\377\377\351\351\351\377UUU\377"
"\4\4\4\377222\377111\377\2\2\2\377LLL\377\364\364\364\377\377\377\377\377"
"\377\377\377\377\261\261\261\377```\377```\377\2\2\2\377\0\0\0\377CCC\377"
"```\377}}}\377\377\377\377\377\377\377\377\377\334\334\334\377+++\377\4\4"
"\4\377\30\30\30\377\36\36\36\377\36\36\36\377\36\36\36\377\242\242\242\377"
"\377\377\377\377\377\377\377\377\326\326\326\377\0\0\0\377%%%\377```\377"
"555\377\2\2\2\377///\377\341\341\341\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\311\311\311\377iii\377CCC\377\10\10\10\377"
"\31\31\31\377[[[\377\377\377\377\377\377\377\377\377\377\377\377\377fff\377"
"\16\16\16\377QQQ\377555\377\0\0\0\377222\377\344\344\344\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\213\213\213\377\16\16\16\377\37\37\37"
"\377OOO\377\26\26\26\377\12\12\12\377\216\216\216\377\377\377\377\377\377"
"\377\377\377\377\377\377\377PPP\377\0\0\0\377EEE\377\374\374\374\377\377"
"\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\335\335\335\377(((\377\3\3\3\377!!!\377UUU\377(((\377\6\6\6\377ooo\377"
"\367\367\367\377\377\377\377\377\377\377\377\377&&&\377\27\27\27\377TTT\377"
"666\377\7\7\7\377SSS\377\342\342\342\377\377\377\377\377\377\377\377\377"
"???\377\20\20\20\377xxx\377\377\377\377\377\370\370\370\377\230\230\230\377"
"\25\25\25\377\0\0\0\377;;;\377\355\355\355\377ooo\377\2\2\2\377\0\0\0\377"
"XXX\377```\377)))\377\3\3\3\377\21\21\21\377\246\246\246\377\376\376\376"
"\377\377\377\377\377\327\327\327\377\37\37\37\377\2\2\2\377\20\20\20\377"
"(((\377***\377\33\33\33\377///\377\377\377\377\377yyy\377\0\0\0\377\3\3\3"
"\377LLL\377FFF\377\6\6\6\377\35\35\35\377\276\276\276\377\377\377\377\377"
"\365\365\365\377\225\225\225\377(((\377\0\0\0\377\36\36\36\377```\377```"
"\377```\377777\377111\377\335\335\335\377\223\223\223\377,,,\377\0\0\0\377"
"\37\37\37\377fff\377\341\341\341\377\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\377\377\334\334\334\377[[[\377\17\17"
"\17\377\12\12\12\377```\377\334\334\334\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\202\202\202\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377"
"\0\0\0\377\0\0\0\377...\377\377\377\377\377\377\377\377\377\332\332\332\377"
"***\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\225\225\225\377"
"\377\377\377\377\377\377\377\377\344\344\344\377FFF\377\27\27\27\377\0\0"
"\0\377\33\33\33\377bbb\377\325\325\325\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\377\377\377\251\251\251\377\17\17\17"
"\377\0\0\0\377\0\0\0\377\0\0\0\377\7\7\7\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\244\244\244\377,,,\377\0\0\0\377\30\30\30\377ggg\377"
"\321\321\321\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\364\364\364\377\236\236\236\377\36\36\36\377\0\0\0\377***\377\214"
"\214\214\377\364\364\364\377\377\377\377\377\377\377\377\377\361\361\361"
"\377===\377\0\0\0\377rrr\377\376\376\376\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\377\377\377\377\376\376\376\377\322"
"\322\322\377ZZZ\377\25\25\25\377\1\1\1\377...\377\204\204\204\377\364\364"
"\364\377\377\377\377\377\377\377\377\377\377\377\377\377jjj\377\33\33\33"
"\377\0\0\0\377\26\26\26\377lll\377\352\352\352\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\17\17\17\377\0\0\0\377333\377\377\377\377\377\363"
"\363\363\377\\\\\\\377\0\0\0\377\0\0\0\377\10\10\10\377\342\342\342\377:"
"::\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\11\11\11\377EEE\377\240\240"
"\240\377\371\371\371\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\324\324\324\377sss\377\34\34\34\377\0\0\0\377\13\13\13\377FFF\377\250\250"
"\250\377\377\377\377\377777\377\0\0\0\377\0\0\0\377\0\0\0\377###\377nnn\377"
"\320\320\320\377\375\375\375\377\377\377\377\377\357\357\357\377TTT\377\0"
"\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377\0\0\0\377000\377"
"\335\335\335\377QQQ\377\0\0\0\377\0\0\0\377\0\0\0\377\23\23\23\377\316\316"
"\316\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
109
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
.ascii
"\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"
"\377\377\377\377\377\377\377\377\377\377",
110
BIBLIOGRAFIA
1. Intel PXA255 Processor – Developer’s Manual (278693-001.pdf)
2. Intel PXA255 Processor – Design Guide (278694-001.pdf)
3. Intel PXA255 Processor – Specification update (278732-002.pdf)
4. ARM Architecture Reference Manual (ddi0100e_arm_arm.pdf)
5. XScale_architecture.pdf
6. Intel XScale Core – Developer’s Manual - (273471-001.pdf)
7. ARM9TDMI – Technical Reference Manual – (ddi0180a_9tdmi_trm.pdf)
8. GNUPro Toolkit user guide – gnupro_userguide.pdf
111
APPENDICE
112
Tabella rimappatura memoria
La MMU viene abilitata col bit 0 del registro 1 del Coprocessore 15 dell’Xscale Core.
Actual
Virtual
base
base
xxx00000 xxx00000
000
500
040
600
080
640
0C0
680
100
6C0
Size attribute
(MB)
64
64
64
64
64
Cached
Buffered
Access
pemission
NO
NO
NO
NO
NO
NO
NO
NO
NO
NO
RW
RW
RW
RW
RW
140
700
64
NO
NO
RW
400
400
192
NO
NO
RW
A00
A00
C00
0
A00
C00
64
256
128
SI
NO
SI
NO
NO
SI
RW
RW
RW
What
FLASH EPROM
RAM STATICA
Servizi interni (Vedi U14)
BUS DI ESPANSIONE
CS[4]=
LED FRONTALI /
SWITCHES
Memory mapped registers
(Peripherals, LCD &
Memory Controller )
Sdram bank0
Unmapped memory
- reserved -
NOTA: l’indirizzo di memoria 0xA0000000 è stato riallocato in due zone distinte, ognuna con attributi diversi.
113
Scarica

x03b - Manuale UNI-PD-PXA