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