Guida veloce alla programmazione della scheda LandTiger V2.0 LPC17XX
ALCUNE REGOLE DEL C ............................................................................................................................................................ 1
LED ......................................................................................................................................................................................... 6
BUTTON_EINT ......................................................................................................................................................................... 8
INTERRUPT CONTROLLER....................................................................................................................................................... 13
CLOCK ................................................................................................................................................................................... 17
TIMER ................................................................................................................................................................................... 24
JOYSTICK ............................................................................................................................................................................... 39
RIT ........................................................................................................................................................................................ 40
ADC E POTENZIOMETRO ........................................................................................................................................................ 46
ASSEMBLY ............................................................................................................................................................................. 48
ESERCIZI ................................................................................................................................................................................ 62
FUNZIONI RIUTILIZZABILI ARM .............................................................................................................................................. 76
APPENDICE ............................................................................................................................................................................ 90
Alcune regole del C
• Tipo signed int (interi predefiniti) è formata da un certo numero di bit, rappresentati in complemento a due.
• Tipo unsigned int è formata da un certo numero di bit in binario naturale
• Tipo char (signed o unsigned) è formata da un certo numero di bit in complemento a due o in binario
naturale. In genere sono otto bit, ma non è un'affermazione assoluta. Quindi non serve solo per i caratteri
ASCII
• Tipo long int è formata da un certo numero di bit, in quantità maggiore o uguale a quelli presenti in un int
Il C non definisce la lunghezza di nessuno di ques interi; per esempio un int, a seconda del compilatore (e della sua
configurazione...) potrebbe per esempio avere una lunghezza variabile tra 8, 16, 32 o 64 bit. Utile la funzione sizeof().
uint8_t
uint16_t
uint32_t = unsigned int
int8_t
int16_t
int32_t
__int24 (non standard, specifico di XC8)
__uint24 (non standard, specifico di XC8)
Esempio d'uso:
int8_t simple_byte; // simple_byte potrà contenere valori compresi tra -128 e +127
uint16_t simple_word; // simple_word potrà contenere valori compresi tra 0 e 65535
Tipo C Bit Byte (spostamento Istruzione Load Istruzione Store (Salva) -
puntatore) (Carica) - ARM ARM
/ 8 1 (Byte) (Byte)
char int8_t LDRB STRB
/ 16 2 (Half-word) (Half-word)
short int16_t LDRH STRH
/ /float/ 32 4 (Word) (Word)
int long int32_t LDR STR
double LDRD STRD
64 8
Floating point single NON C’È IN ARM IL
32 4
precision FLOATING POINT
1
Floating point double 64 8
precision LDR STR
(es. ) 32 4
pointer int*
L’ondina si fa con AltGr + ì oppure Alt + 126
Variabili
Volatile
Dire al compilatore che una variabile è volatile significa: "Non ottimizzare questa variabile, perché il suo valore può
cambiare all'improvviso all'esterno del flusso normale del programma".
Normalmente, il compilatore cerca di rendere il codice più veloce. Se vede che leggi una variabile molte volte senza
mai modificarla esplicitamente nel main, il compilatore potrebbe decidere di copiarla in un registro della CPU (molto
veloce) e non andarla più a leggere nella RAM (più lenta).
Usando volatile, obblighi la CPU a rileggere il valore dalla memoria RAM ogni singola volta che viene usata,
garantendo che il valore sia sempre quello più aggiornato.
Static
A differenza delle variabili locali comuni (automatiche), che vengono distrutte al termine di una funzione, una variabile
dichiarata come static mantiene il suo valore tra una chiamata e l'altra. Viene allocata in una zona di memoria fissa
(Segmento Dati) e non nello stack e viene inizializzata una sola volta all'avvio del programma. È estremamente utile
nelle ISR (Interrupt Service Routines): ad esempio, se vuoi contare quante volte è stato premuto un pulsante o quante
volte è scattato un timer senza usare una variabile globale che sia visibile a tutto il progetto.
2
L'uso della parola chiave static serve anche a limitare l'accesso ai dati, seguendo il principio dell'incapsulamento:
• Static locale: Definita all'interno di una funzione, è visibile solo lì, ma "ricorda" lo stato precedente.
• Static globale: Definita all'inizio di un file .c, rende la variabile (o la funzione) privata per quel file. Nessun altro
file del progetto potrà vederla o modificarla tramite la parola chiave extern.
Maschere e shift
Nei sistemi embedded, spesso riceviamo dati a 32 bit, ma abbiamo bisogno di isolare solo un "pezzo" (un byte) per
controllare periferiche come i LED. Per farlo, usiamo due strumenti principali: lo Shift e la Maschera.
Lo shift a destra serve a "spostare" il byte che ci interessa verso le posizioni più basse (a destra), in modo che diventi
l'LSB (Least Significant Byte, ovvero gli ultimi 8 bit a destra).
- Se il byte è tra i bit 15-8, facciamo num >> 8.
- Se il byte è tra i bit 31-24, facciamo num >> 24.
Anche dopo lo shift, potrebbero esserci dei bit "residui" a sinistra. Usando l'operazione & 0xFF (che in binario è
11111111), "cancelliamo" tutto ciò che si trova oltre l'ottavo bit, tenendo solo il valore puro del byte scelto.
Per azzerare si usa l’AND (&): Se vogliamo azzerare i 16 bit meno significativi mantenendo invariati i 16 bit più
significativi, usiamo la maschera 0xFFFF0000. L'AND con F (tutti 1) mantiene il valore originale, l'AND con 0 impone
lo 0. Esempio: 0xABCDFEDE & 0xFFFF0000 = 0xABCD0000.
Per impostare a 1 si usa l’OR (|): Se vogliamo forzare a 1 gli ultimi 8 bit senza toccare il resto, usiamo la maschera
0x000000FF. L'OR con 0 mantiene il valore originale, l'OR con 1 forza il bit a 1.
Esempio: 0xABCDCABB | 0x000000FF = 0xABCDCAFF.
Operazione Simbolo Scopo Principale Effetto con la Maschera
&
AND Reset (Azzera); (Mantiene)
x & 0 = 0 x & F = x
|
OR Set (Setta a 1); (Mantiene)
x | F = F x | 0 = x
^
XOR Invert Inverte i bit dove la maschera è 1
⟷ 1)
~
NOT Reverse Ribalta l'intero byte (0
Esempio: LED_Out(~(num & 0xFF)); //Contenuto negato dell'LSB
Questa riga di codice isola l’LSB (gli ultimi 8 bit del numero) e inverte tutti i bit del risultato con ~.
Perché si nega? Sulla scheda LandTiger (e molte altre), i LED sono spesso collegati in logica negata: si accendono
quando il segnale è 0 e si spengono quando è 1. Negando il valore con ~, facciamo in modo che se il bit è 1, il LED
corrispondente si accenda effettivamente.
Numeri in complemento a due −1
−2
Per un numero a N bit il MSB ha peso negativo (pari a ) e gli altri bit hanno peso positivo. Ne consegue che il
MSB indica sempre il segno (0=+, 1=-).
2
Numeri rappresentabili con N bit: -1
Il più grande numero negativo rappresentabile è 1 seguito da tutti zeri (100..0), che ha un contributo positivo pari a
zero. ̿ ̅
= 2 − = +1.
Dato un numero binario A in N bits, il suo complemento a due si esprime con:
3
I numeri positivi sono identici alla rappresentazione binaria standard. Invece, per ottenere un numero negativo -X, si
prende il binario di X, si invertono tutti i bit e si aggiunge 1.
Esempio: su 8 bit 5 è 00000101
Inverto: 11111010 →
Aggiungo 1: 11111011 corrisponde a -5 in CA2
Non bisogna fare calcoli manuali per convertire i numeri, se ne occupa il processore, a patto di usare le istruzioni di
salto corrette:
- Confronto: Quando usi CMP R1, R2, il processore aggiorna i flag di stato.
- Salto per numeri con segno: Per i numeri int (con segno), devi usare:
o BGT (Branch if Greater Than) invece di BHI.
o BLT (Branch if Less Than) invece di BLO.
o BGE / BLE (Greater/Less or Equal).
- Estensione del segno: Se carichi un byte o un half-word e vuoi mantenere il segno, usa LDRSB o LDRSH.
Caricando un int a 32 bit con LDR, il segno è già preservato correttamente.
La scheda
Quando dobbiamo leggere un segnale da un pin (ad esempio la pressione di un tasto), abbiamo due strategie
principali: il Polling e l'Interruzione Esterna (EINT).
• Il Polling è un processo software sincrono in cui la CPU verifica periodicamente lo stato dei registri delle
periferiche. In questa modalità, configuriamo il pin come funzione GPIO (impostando i bit a 00 nel registro
PINSEL) e specifichiamo che la sua direzione è in ingresso agendo sul registro FIODIR (bit a 0). Dal punto di
vista del codice, il processore entra in un ciclo infinito (come un while(1)) e interroga continuamente il
registro FIOPIN per vedere se il valore è cambiato.
o Sebbene semplice da implementare, risulta inefficiente in termini di consumo energetico e
prestazioni, a causa dell'alta latenza e dell'impossibilità di gestire carichi annidati.
• L'interruzione (EINT) è un meccanismo hardware asincrono in cui le periferiche (UART, GPIO, Timer)
interagiscono direttamente con la CPU. Questo permette al processore di entrare in modalità Idle e di
attivarsi solo quando è richiesto un servizio specifico, garantendo efficienza e bassa latenza.
o In questo caso, non usiamo il pin come un semplice GPIO, ma attiviamo una sua Funzione Speciale
tramite il registro PINSEL (ad esempio selezionando EINT0, EINT1, ecc.). È l'hardware stesso che
"osserva" il pin; non appena avviene un cambiamento di stato (un fronte di salita o di discesa), il
modulo EINT invia un segnale immediato al processore.
Al reset tutti I pin sono impostati GPIO 4
Questa tabella decide "chi comanda il pin". Ogni pin fisico ha 2 bit dedicati in questo registro per scegliere tra 4 funzioni
possibili.
• Valore 00: Il pin è un GPIO (General Purpose I/O). Lo controlli tu manualmente tramite i registri della seconda
tabella.
• Valore 01, 10, 11: Il pin viene "ceduto" a una periferica interna (PWM, UART, o EINT per le interruzioni esterne).
• Nota importante: Se selezioni una funzione diversa da GPIO, il controllo della direzione (input/output) diventa
automatico e gestito dalla periferica stessa.
I seguenti registri funzionano soltanto se in PINSEL hai scelto la modalità GPIO (00).
• FIODIR: Imposta se il pin è un'entrata (bit a 0) o un'uscita (bit a 1).
o Ad es per usare i LED, che stanno sulla porta 2, usiamo il registro FIO2DIR. Anche i pulsanti sono sulla
porta 2.
• FIOSET: Scrivere '1' qui porta il pin a livello ALTO (3.3V).
• FIOCLR: Scrivere '1' qui porta il pin a livello BASSO (0V). Comodo perché non devi preoccuparti di fare
operazioni logiche (AND/OR) per spegnere un solo bit.
• FIOPIN: Serve per leggere lo stato attuale del pin o scriverlo direttamente.
5
LED
Lib_led.c
volatile unsigned char led_value;
void LED_init(void)
Il codice configura i pin dei led come uscite digitali.
LPC_PINCON->PINSEL4 &= 0xFFFF0000;
Azzera i primi 16 bit del registro PINSEL4. Gli ultimi 16 bit (quelli con F) rimangono invariati. Guardando la Tabella 84,
i bit da 0 a 15 controllano i pin da P2.0 a P2.7. Mettendoli a 00, stai dicendo al chip: "Voglio che questi 8 pin siano
normali GPIO, non PWM o altro".
LPC_GPIO2->FIODIR |= 0x000000FF; 6
Mette a '1' i primi 8 bit del registro di direzione della PORTA 2 (Gli altri bit dal 9 al 31 rimangono come erano prima).
Secondo la tabella dei registri GPIO, il bit a '1' in FIODIR imposta il pin come Output. Ora i LED possono ricevere
corrente.
LPC_GPIO2->FIOSET = 0x000000FF;
Scrive '1' sui primi 8 bit del registro SET della PORTA 2. Questa operazione porta i pin corrispondenti (P2.0...P2.7) a
livello logico ALTO (3.3V), accendendo tutti i LED contemporaneamente. A differenza del registro FIOPIN, scrivere '0'
in questo registro non ha alcun effetto sui pin, il che permette di accendere bit specifici senza modificare lo stato
degli altri.
LPC_GPIO2->FIOCLR = 0x000000FF;
Scrive '1' sui primi 8 bit del registro CLEAR. Spegne tutti i LED portando i pin a 0V (assumendo che i LED siano
collegati verso massa). Nota che scrivi 1 per pulire (portare a 0) lo stato del pin.
void LED_deinit(void)
LPC_GPIO2->FIODIR &= 0xFFFFFF00;
Riporta i primi 8 bit a '0'. Trasforma i pin da Output a Input. In modalità input, il pin è "inerte" (alta impedenza) e non
pilota più il LED, risparmiando energia o evitando stati elettrici indesiderati quando il driver viene spento.
Funct_led.c
const unsigned long led_mask[] = { 1UL<<0, 1UL<<1, 1UL<<2, 1UL<< 3, 1UL<< 4,
1UL<< 5, 1UL<< 6, 1UL<< 7 };
Crea una "mappa" dei bit. 1UL<<0 significa "prendi il numero 1 e spostalo di 0 posizioni" (resta 1), 1UL<<1 lo sposta
di 1 posizione (diventa 2), e così via. Questo array serve a identificare esattamente quale pin della Porta 2
corrisponde a quale LED.
void LED_On(unsigned int num)
Accende il LED numero num (da 0 a 7).
Comando Indice (num) Bit Maschera Pin LPC1798 Nome LED sulla scheda
LED_On(0) 0 1 << 0 P2.0 LD11 è quello più a destra
LED_On(1) 1 1 << 1 P2.1 LD10
LED_On(2) 2 1 << 2 P2.2 LD9
LED_On(3) 3 1 << 3 P2.3 LD8
LED_On(4) 4 1 << 4 P2.4 LD7
LED_On(5) 5 1 << 5 P2.5 LD6
LED_On(6) 6 1 << 6 P2.6 LD5
LED_On(7) 7 1 << 7 P2.7 LD4
LPC_GPIO2->FIOPIN |= led_mask[num];
Usa l'operatore |= (OR) per mettere a '1' solo il bit corrispondente al LED scelto nel registro FIOPIN. Questo porta il
pin a 3.3V senza spegnere gli altri LED che sono già accesi.
led_value = LPC_GPIO2->FIOPIN; 7
Legge lo stato attuale di tutti i pin della porta e aggiorna la variabile globale led_value.
void LED_Off(unsigned int num)
Spegne il LED numero num.
LPC_GPIO2->FIOPIN &= ~led_mask[num];
Qui usa l'operatore &= ~ (AND NOT). Prima "inverte" la maschera (tutti 1 tranne il bit del LED scelto) e poi fa l'AND. Il
risultato è che solo il bit scelto diventa '0', spegnendo il LED, mentre gli altri restano invariati.
void LED_Out(unsigned int value)
Questa funzione permette di visualizzare un numero binario (da 0 a 255) sugli 8 LED.
for (i = 0; i < LED_NUM; i++): Un ciclo che controlla ogni singolo bit del numero value.
if (value & (1<<i)): Controlla se l'i-esimo bit del numero passato è '1'. Se lo è, chiama LED_On(i),
altrimenti chiama LED_Off(i).
In pratica, se passi il valore 3 (che in binario è 00000011), si accenderanno i LED 0 e 1, cioè i LED11 e LED10.
void LED_Out_Inv(unsigned int value)
Fa esattamente la stessa cosa di LED_Out, ma inverte l'ordine dei LED.
LED_On (LED_NUM - i - 1): Invece di accendere il LED i, accende quello dalla parte opposta.
Se il bit 0 è '1', invece di accendere il LED11 (P2.0), accenderà il LED4 (P2.7).
Button_EINT
Quando usi un pulsante con le interruzioni, non ti limiti a leggere "se il tasto è premuto", ma chiedi al processore di
reagire a un evento di transizione (il passaggio da 0 a 1 o viceversa).
Per i pulsanti sulla tua scheda, devi prima di tutto "dire" al pin di non comportarsi più come un normale GPIO, ma
come un ricevitore di interruzioni.
• Pulsante INT0: Si trova sul pin P2.10. Guardando la tabella PINSEL4, devi impostare i bit 21:20 al valore 01
per attivare la funzione EINT0.
• Pulsante KEY1: Si trova su P2.11. Devi impostare i bit 23:22 di PINSEL4 a 01 per attivare EINT1.
• Pulsante KEY2: Si trova su P2.12. Devi impostare i bit 25:24 di PINSEL4 a 01 per attivare EINT2.
Nota: Se lasciassi 00, il pin sarebbe un GPIO e dovresti usare il Polling che abbiamo visto prima.
I pulsanti della tua scheda lavorano in logica negativa:
• Rilasciato: Il pin legge 1 (3.3V).
• Premuto: Il pin viene collegato a massa e legge 0 (0V).
Per gestire questo, nel registro di configurazione dell'interruzione (EXTMODE e EXTPOL), devi decidere se
l'interruzione deve scattare sul Livello (mentre tieni premuto) o sul Fronte (nell'istante esatto in cui premi). Di solito
si sceglie il Fronte di Discesa (Falling Edge), perché è il momento in cui il segnale passa da 1 a 0 (ovvero quando
premi il tasto).
Il registro EXTINT funge da "bandierina" (flag). Quando premi il tasto e avviene la transizione, l'hardware mette a 1 il
bit corrispondente in questo registro. Importante: Finché non "pulisci" questa bandierina scrivendoci sopra un 1, il
processore crederà che il tasto sia ancora in fase di pressione e continuerà a generare interruzioni.
8
Il registro E
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
-
Architetture degli elaboratori - appunti
-
Architetture degli elaboratori
-
Architetture Sistemi Elaborazione
-
Architetture dei sistemi elettronici