Come lavorano i microprocessori

Nota: Per semplificare la descrizione non faccio per ora nessuna distinzione tra 
      microprocessore e microcontrollore, ne tratto i vari tipi. 
      Invece penso a chi vuole sapere qualche cosa di più sui modi e principi di 
      come queste macchine elettroniche riescono a funzionare con i programmi a 
      livello macchina, per capire la cosidetta intelligenza artificiale.
      Naturalmente il linguaggio assembler ha delle abbreviazioni legate al tipo 
      di microprocessore usato, e poi in pratica, si deve sucessivamente 
      assemblare o trasformare le istruzioni e i dati in modo che diventi un 
      codice binario eseguibile.

NOTA(*): Per avere una descrizione completa degli argomenti si deve naturalmemte 
         consultare dei manuali tecnici specifici.   
 
Contenuto(*):

1)  Introduzione
2)  Il sistema binario
3)  Le funzioni logiche
4)  Le conversioni numeriche, (binario-decimale-esadecimale-ascii)
5)  Le funzioni aritmetiche
6)  I registri del microprocessore Zilog Z80 e del microcontrollore Atmel AT89C2051 (8051)
7)  Gli indirizzi e i dati in binario
8)  La lettura e scrittura in memoria
9)  L'uso delle porte esterne, (ingresso e uscita I/O)
10) Il salvataggio temporaneo dei registri nello stack
11) La trasformazione di una Word in due byte
12) Lo scambio tra i registri
13) Le istruzioni dirette al singolo bit
14) Le istruzione di chiamata ai sottoprogrammi e del salto ai programmi
15) Le istruzioni NOP e HALT
16) Cosa sono le interruzioni
17) Il registro di guardia (WATCH DOG)
18) L'acquisizione dei dati analogici (A/D)
19) La conversione dei dati numerici in analogici (D/A)
20) Il sistema a multiprocessore

                          (1) Introduzione

Per capire il linguaggio assembler usato per programmare i microprocessori, 
con tutte le funzioni di controllo in ingresso e uscita come pure le operazioni 
logiche e aritmetiche, rappresentate di solito con abbreviazioni mnemoniche della 
lingua inglese, è necessario avere una nozione di base possibilmente nel campo 
della logica elettronica. 

E' utile a questo scopo, comprendere cosa in pratica succede in un'operazione 
binaria, tenendo a mente che i microprocessori e le macchine numeriche in 
generale, essendo costruite con logica elettronica in effetti conoscono solo 
due valori: lo zero (0) e l'uno (1), corrispondenti a mancanza o presenza di 
tensione su un conduttore, rispetto alla massa comune che è sempre il 
riferimento di zero volt.
                         (2) Il sistema binario

Le operazioni si fanno usando il parallelo di questi singoli conduttori,
il cui valore unitario ha il nome di bit (dall' Inglese).
Più conduttori in parallelo possono contenere in totale dei numeri più
grandi di 0 e 1, in quanto ponendo la base 2 per ogni singolo bit,
come in decimale usiamo la base 10, vi è il seguente parallelo:

   Numero  DECIMALE    Corrispondente a numero    BINARIO (4 bit)

Esempio:  15                                      1   1   1   1
     (Decina=1 Unità=5)     Scomposizione        (8)+(4)+(2)+(1)

          12                "     "     "         1   1   0   0
     (Decina=1 Unità=2)                          (8)+(4)+(0)+(0)

Come si vede allo stesso modo del decimale il peso (valore) del singolo
bit cresce secondo la comune direzione da destra verso sinistra.

I termini usati per raggruppare più bit insieme sono i seguenti: 
4 bit = Nibble, 8 bit (2 x 4) = Byte, 16 bit (8 x 4) = Word
 
             Peso (valore numerico) dei singoli bit di una Word

Bit 15    14    13    12    11    10    9    8    7   6   5   4   3  2  1  0
(32768)(16384)(8192)(4096)(2048)(1024)(512)(256)(128)(64)(32)(16)(8)(4)(2)(1)

Con 4 bit il numero massimo in decimale che si può usare è 15, ossia 16 
conbinazioni tenendo conto anche dello zero.
Con 8 bit le combinazioni passano a 16 x 16 ossia 256, per cui il numero
massimo è 255, e se lo usiamo come contatore diventa naturalmente 256.
Così con 16 bit passiamo a numeri più grandi 256 x 256 ossia 65536 
combinazioni, il numero massimo sarà 65535 e il contatore 65536. 

Si usa anche il bit più alto per identificare i numeri negativi, esempio con 8 bit:
127 = 01111111B, -127 = 11111111B, -128 = 10000000B, -32 = 10100000B
con 16 bit: 32767 = 0111111111111111B, -32767 = 1111111111111111B 

Naturalmente come capita per noi ogni giorno, più trattiamo con numeri di 
piccole dimensioni e più troviamo facile fare anche mentalmente le comuni
quattro operazioni, ugualmente possiamo seguire meglio gli esempi con 
numeri piccoli.

Nei microprocessori le quattro operazioni sono spesso semplificate con
le due operazioni base, la somma e la sottrazione (mentre moltiplicazione
e divisione si ricavano dalla combinazione delle prime due) ; in più ci 
sono poi le operazioni logiche legate ai circuiti elettronici da cui derivano.
Oltre al risultato, in tali operazioni si controlla anche un particolare
valore o condizione (Flag), che dopo l'operazione può avere i due classici 
valori di 0 o 1.
Di solito il valore uno ha il significato di conferma (Sì)  e naturalmente 
lo zero l'opposto (Nò), e da queste due condizioni si possono poi eseguire
delle sucessive scelte dal programma. 

                (3) Le operazioni o funzioni logiche

Se comprendiamo per prima queste operazioni logiche, siamo già a buon punto 
nella comprensione del linguaggio macchina dei microprocessori.

Allora proviamo prima a fare qualche operazione logica con soli 4 bit.

                      La funzione logica (AND) 

E' quella dove in pratica comanda lo zero, ossia se prendiamo due fili,
uno a livello (1) e l'altro a livello (0) e li colleghiamo insieme, se
il circuito elettronico esegue un'operazione AND significa che entrambi
i conduttori andranno a livello logico zero (0).

Esempio con numeri a 4 bit e il flag Z (zero): 

1111 AND 0000 = 0000 (Z = 1), 1010 AND 1111 = 1010 (Z = 0)
1111 AND 1100 = 1100 (Z = 0), 1001 AND 0111 = 0001 (Z = 0)

La funzione AND si usa anche per testare il valore di un singolo bit, 
servendosi del flag Z.
Esempio: si vuole controllare la condizione variabile del bit 3 (8 decimale),
di un ingresso binario (X) a 4 bit per poi eseguire una scelta con il 
programma sucessivo.
(X = 1111) AND 1000 = 1000 (Z = 0), (X = 0111) AND 1000 = 0000 (Z = 1).
               (8)                                 (8)

Molti microprocessori o microcontrollori hanno anche istruzioni dirette
per testare singoli bit.

                      La funzione logica (OR)

In questo caso è l'uno che comanda, prendiamo i soliti due fili se uno
si trova a livello (0) e l'altro a livello (1), collegandoli insieme e
il circuito elettronico è predisposto per fare una funzione OR, il
risultato sarà che entrambi i conduttori si troveranno a uno (1).

Esempio con numeri a 4 bit e il flag Z (zero):

1111 OR 0000 = 1111 (Z = 0), 1010 OR 1000 = 1010 (Z = 0)
0000 OR 0000 = 0000 (Z = 1), 1100 OR 0011 = 1111 (Z = 0)

Come si vede negli esempi solo quando, entrambi i numeri sono a zero
il flag Z è uguale a uno; con l'OR si può così testare i numeri sconosciuti,
per fare una determinata scelta quando sono uguali a zero, (Z = 1).  

La funzione OR può eseguire anche una somma esatta fra due numeri binari,
a patto che non ci siano due bit dello stesso peso a uno.

1000 OR 0100 = 1100, 0011 OR 0100 = 0111, 1010 OR 0011 = 1011 (11) Errore!   
(8)  +  (4)  = (12)  (3)  +  (4)  = (7)   (10) +  (3)  = (13)

Questa funzione in elettronica si presta facilmente nel controllo degli
allarmi multipli; se abbiamo ad esempio 16 ingressi (16 bit) a livello zero, 
e vogliamo far scattare un'allarme quando uno di questi va a uno, si usa 
la funzione OR.  

                 La funzione logica (NOT o CPL Complemento)

E' l'opposto del segnale in ingresso, se abbiamo un filo ad uno dopo il NOT 
o CPL sarà a zero e viceversa se è a zero diverrà a uno.

Esempio con numeri a 4 bit:

1111 NOT/CPL = 0000, 0000 NOT/CPL = 1111, 0110 NOT/CPL = 1001, 1010 NOT/CPL = 0101

Eseguendo due volte il NOT o CPL su un certo numero o ingresso elettronico,
lo si riporta al valore iniziale.     
Quando questa funzione (NOT) è usata in abbinamento alle precedenti funzioni,
AND e OR esse vengono chiamate NAND e NOR.

Il complemento di un numero binario è uguale al suo reciproco. 
Per trovare il reciproco si deve sottrarre da (1111 per 4 bit)  il numero da convertire.
Esempio: il complemento di 1001 (9) è uguale a 1111 (15) meno 1001 (9) ossia 0110 (6).

Lo stesso principio vale con numeri più grandi, il complemento di 11001111 (207) è
uguale a 11111111 (255) meno 11001111 (207) che da come risultato 00110000 (48).

                  La funzione logica (XOR) OR esclusivo 

Questa funzione XOR (disgiunzione) è come l'OR, eccetto che quando i due nostri 
conduttori hanno lo stesso valore (non zero) , ossia presenza entrambi di tensione, 
invece di avere come risultato sempre i due fili a uno (1), questi passano immediatamente 
a zero (0).

Esempio con numeri da 4 bit:

1111 XOR 1111 = 0000 (Z = 1), 1111 XOR 0001 = 1110 (Z = 0), 1110 XOR 0001 = 1111 (Z = 0)

La funzione logica XOR è molto usata nei circuiti a PLL, dove esiste un controllo 
numerico di un dato oscillatore VCO la cui frequenza viene a sua volta controllata 
con una tensione filtrata proveniente da un circuito XOR, dove entrano le frequenze 
dell'oscillatore variabile VCO e quella di campionatura proveniente da un'oscillatore 
al quarzo entrambi in precedenza opportunamente divisi di frequenza.

                  La funzione logica (CP) confronto o comparazione

Più che operazione logica si direbbe aritmetica, in quanto il confronto viene fatto 
attraverso una sottrazione, solo che non si tiene conto del risultato e quindi i dati
rimangono in'alterati. Modificati, invece i flag di cui i principali  sono lo Z (zero)
e il C (carry) bit di riporto, necessario per la corretta aritmetica.

Esempio con numeri a 4 bit:

1011 CP 1010 = (Z = 0) e (C = 0), 1011 CP 1011 = (Z = 1) e (C = 0)
 (11) > (10)                       (11) = (11)
1011 CP 1100 = (Z = 0) e (C = 1)
 (11) < (12)

Come si vede negli esempi, Z è uguale a uno solo quando i dati confrontati sono identici, 
quindi questo sistema è usato per la ricerca rapida di uno o più dati conosciuti all'interno
di un particolare blocco di dati da esaminare.
Ugualmente quando per esempio facciamo la ricerca di una determinata parola dentro un testo
che stiamo esaminando con il nostro pc, attiviamo un programma di ricerca per comparazione 
o confronto.

                  La funzione logica di scorrimento e rotazione dei bit
 
Queste funzioni usano anche il bit di riporto C (carry) delle operazioni aritmetiche. 
Esempio se sommiamo due numeri a 4 bit, come 13 (1101) + 10 (1010), otteniamo
il totale di 23 (10111), dove il quinto bit il carry è a uno (1).
In questo esempio dove abbiamo come capacità di calcolo solo dati a 4 bit, vediamo che 
si possono tranquillamente trattare cifre molto più grandi con l'uso del flag (C) carry, 
e quando necessario si possono eseguire anche più operazioni consecutive e complesse. 

Detto questo, le funzioni di scorrimento e rotazione dei bit sia verso sinistra che 
verso destra servono a molteplici scopi. 
Alcuni dispositivi hanno diversi comandi in tal senso, tratto però solamente le 
principali di cui la prima è lo scorrimento (SHIFT).
Esempio con numeri da 4 Bit:        

1101 SHIFT verso sinistra = (C) <-(1)1010 <-(0), Entra lo zero da destra e l'uno di
sinistra esce e va nel carry, se ripetiamo l'operazione, perdiamo il bit che era nel 
carry (C). Questo è uno scorrimento chiamato (aperto), facendolo verso destra;
1101 SHIFT verso destra = (0)-> 0110(1)-> (C), avviene l'opposto.

Come si capisce dagli esempi si spostano i pesi dei bit per cui verso destra si divide
il numero binario per due, e viceversa lo si moltiplica per due verso sinistra.
Se lo scorrimento è ad anello (circolare) con l'attraversamento o meno del carry, allora 
si tratta di una rotazione a destra o a sinistra.
Esempio di rotazione circolare attraverso il bit (C) carry:

1101 Ruota verso sinistra = (C)-------(X)-->---*  Risultato (C=1) e dato = 101X
                             ^                 |
                             |                 | 
                             *---<(1) 101X<----*
1101 Ruota verso destra   = (C)<-----(1)-------*  Risultato (C=1) e dato = X110
                             |                 |
                             |                 |
                             *--(X)->X110--->--* 

Ad uso aritmetico decimale alcuni processori hanno delle istruzioni di shift, che
coinvolgono gruppi di 4 bit (nibble) in modo da trattare numeri in BCD. 

Uno dei tanti scopi di queste operazioni logiche, oltre a quello matematico è
la trasformazione di dati paralleli in dati seriali e viceversa con l'uso del flag (C). 
Malgrado si perda di velocità i dati seriali permettono una semplicità notevole
del circuito, e pure l'uso di componenti attivi con meno terminali. Un'esempio fra i
tanti; una memoria eprom parallela da 8 Kbyte usa uno zoccolo con 28 pin (terminali),
mentre una eeprom (cancellazione elettrica) seriale corrispondente, usa solo 8 pin.
(Naturalmente in certi casi la prima non può essere sostituita dall'altra).
Sovente queste semplificazioni permettono un notevole risparmio di lavoro e ingombro 
con un evidente vantaggio pratico ed economico.       

                       (4) Le conversioni numeriche 

Come sappiamo i microprocessori vedono solo dei numeri nel loro linguaggio binario,
ma noi come essere umani usiamo i numeri decimali, e la comune tastiera del pc,
dove ci sono tutti i classici simboli alfanumerici, programmabili secondo la lingua 
che usiamo.   
Per questo, quando parliamo di operazioni su numeri binari a 4 bit, se vogliamo
usare questo nibble per calcoli con risultato decimale (BCD), abbiamo da fare una 
conversione in quanto ogni nibble può contenere solo una cifra decimale.
Per cui 4 nibble (16 bit) in binario arrivano a 65535, ma in decimale solo a 9999.

E' necessario a questo punto trattare un'attimo il codice esadecimale (Hex), che
in pratica per racchiudere in un unico simbolo i numeri binari oltre la cifra 9,
si è dato ai numeri superiori le prime lettere dell'alfabeto, e quindi A = 1010 (10), 
B = 1011 (11), C = 1100 (12), D = 1101 (13), E = 1110 (14), F = 1111 (15).
La conversione esadecimale in binario e viceversa è facile in quanto ogni cifra,
o lettera corrisponde come abbiamo visto sempre ad un nibble.
Esempio : 255(D) = 11111111(B) = FF(H), 100 = 01100100 = 64, 222 = 11011110 = DE

Per ultimo trattiamo il codice Ascii, usato da molto tempo per rappresentare i 
caratteri alfabetici nei computer, stampanti, ecc. Di base è un codice a 7 bit, con
cui si rappresentano i numeri, le lettere e i principali segni di punteggiatura
(interpunzione).
In questo modo, avendo la possibilità di leggere nella nostra lingua un testo o un
messaggio con allegato il risultato di una certa operazione, rendiamo il colloquio 
persona-macchina facilmente accessibile a tutti.
Anche quando si introducono dei dati che poi il processore dovrà elaborare, se usiamo 
una tastiera alfanumerica, significa che utilizziamo già un processo di
conversione uomo-macchina; il più comune di questi sistemi è certamente l'uso
di programmi per il trattamento dei testi su foglio elettronico.

Ritornando al linguaggio binario le conversioni più usate, che naturalmente si possono
usare anche all'inverso, sono: 

 Binario -> Decimale (BCD), 1100(12) = 0001 0010 (BCD)  
                                         (1)  (2)
 Binario -> Esadecimale (Hex), 1100(12) = C(H)

 Binario -> Ascii, 1100(12) = 00110001  00110010  (Ossia il numero decimale + 48)
                                    (1)       (2)

                       (5) Le funzioni aritmetiche

Queste funzioni molto importanti sono anche molto semplici, e naturalmente possono 
far uso del bit di riporto C (carry) nello stato che è prima dell'operazione. 
La somma o addizione che come mnemonico può avere ADD o ADC; la prima non tiene conto
del carry che comunque viene sempre modificato, mentre la seconda ne tiene conto.
Con i soliti numeri da 4 bit alcuni esempi:

(C=1) 1100 ADD 1100 = (1)1000, (C=1) 1100 ADC 1100 = (1)1001
      (12)  +  (12) = (24)       1 + (12)  +  (12) = (25)

Allo stesso modo le sottrazioni sono con o senza carry; SBC e SUB.
Vediamo ancora alcuni esempi a 4 bit:

(C=1) 1100 SUB 1000 = 0100, (C=1) 1100 SBC 1000 = 0011 
      (12)  -  (8)  = (4)         (12) - (8)- 1 = (3)

Queste operazioni modificano alcuni Flag, il più importante è il riporto C.

Alcuni microprocessori hanno pure istruzioni più complesse come la moltiplicazione
e la divisione. 
Nella famiglia dei microcontrollori a 8 bit (Intel) tipo 8051 vi è infatti MUL e DIV,
moltiplica e dividi, anche microc. più moderni come l'AVR AT90S8515 (Atmel) hanno
simili istruzioni, mentre per microcontrollori più piccoli spicca il tipo
AT89C2051 (derivato dall'8051) che possiede entrambi le istruzioni.

Avere a disposizione istruzioni potenti, significa poter scrivere programmi più
corti che allo stesso tempo diventano più veloci nell'esecuzione.
La moderna tecnologia però, ha anche ridotto i meccanismi dinamici all'interno 
dei microprocessori portando questi ad elevate velocità e ad operare con istruzioni 
più semplici (RISC istruzioni a codice ridotto) ; con il sacrificio di allungare 
i programmi. 

                     L'Incremento (INC) e il Decremento (DEC)
      
Si tratta della somma o della sottrazione di uno, molto usati come istruzioni
di conteggio nei cicli multipli di programmi più o meno complessi. 
IL flag Z è quello interessato, per questo motivo è stata fatta pure l'istruzione
DJNZ N,X che significa decrementa di uno N e salta all'indirizzo X fino a che 
Z è diverso da uno ossia N diventa zero.
Spesso i microp. che lavorano con numeri doppi da 8 bit, eseguono INC e DEC 
direttamente a 16 bit, ma il controllo del valore di zero è sempre fatto su 
numeri a 8 bit, a motivo della loro struttura aritmetica interna.

Il concetto dei salti condizionati lo riprenderemo poi quando parleremo dell'uso 
nei programmi dei registri di memoria di un microprocessore. 

    (6) I registri del microprocessore Z80 e del microcontrollore AT89C2051

A questo punto è necessario parlare dei famosi registri, che sono delle memorie a
8 - 16 bit e per quelli più potenti anche 64 bit. Queste memorie chiamate (Registri),
perchè sono usate dal programma per eseguire tutte le funzioni e che poi trattengono
i dati necessari, per il corretto funzionamento. 
Secondo il tipo di microP. o microC. e pure l'età di costruzione, abbiamo molteplici usi. 
Per semplificare descrivo i più importanti:

L'Accumulatore (A), è il registro che esegue tutte le operazioni logiche e aritmetiche,
in alcuni microcontrollori, (esempio lo Zilog Z8) non è specificato, in quanto tutti i
registri di uso comune sono pure in grado di fare queste operazioni, mentre nell'8051
invece oltre all'accumulatore vi è il registro B usato in coppia con A per la 
moltiplicazione e divisione.
Il registro di Flag, di solito a 8 bit che tiene conto di tutti i fattori implicati,
carry, mezzo carry (per 4 bit),zero, parità, segno, ecc. 
I registri indice (IX) e (IY) di solito a 16 bit, possono contenere dati come invece 
puntare insieme ad uno scarto fissato, a specifiche locazioni di memoria sia di programma 
che di ram. 
I registri di uso comune, da 8 o 16 bit che vengono utilizzati per contatori, puntatori,
locazioni temporanee di dati, ecc.
I registri specifici come (PC) puntatore di programma, (SP) puntatore di stack che serve 
per ritenere gli indirizzi di rientro del registro (PC) dopo le chiamate ai sottoprogrammi.
Poi ci sono dei registri specifici per l'uso delle porte parallele, della porta seriale, 
il timer, il controllo delle interruzioni con le priorità, il controllo di blocco, ecc. 
 
A questo punto è bene fare una breve e semplice descrizione della differenza fra un 
microprocessore e un microcontrollore.
Il primo di solito più potente nell'elaborare i dati, ma che a differenza del secondo,
è incompleto, ossia non possiede delle porte interne come seriali o parallele, come
pure la memoria del programma e dei dati volatili è esterna.
Viceversa il microcontrollore si può chiamare microcomputer, perchè possiede tutto
al suo interno, naturalmente a volte in misura ridotta per contenere il prezzo e 
anche perché l'uso è limitato e specifico.

Fra i tanti descrivo la struttura del vecchio e famoso microprocessore della Zilog lo Z80.

Coppie di registri da 8 bit: (AF) Accumulatore con logica e aritmetica 8 bit, più i Flag
                             (BC) Uso comune o speciale, puntatore porte   
                             (DE) uso comune o speciale con HL
                             (HL) Aritmetica a 16 bit senza le funzioni logiche
                            (AF') Ausiliario per uso alternato al principale  
                            (BC') Blocco di tre coppie di registri per uso  
                            (DE') alternato con i principali
                            (HL') "          "          "
Registri singoli 8 bit        (I) Indice vettore delle interruzioni
   "       "      "           (R) Rinfresco memorie dinamiche (automatico)
Registri a 16 bit           *(IX) Aritmetica parziale a 16 bit, puntatore dati
   "       "      "         *(IY) "               "               " 
   "       "      "          (PC) Puntatore del programma
                             (SP) Puntatore dello Stack
                          
(*) Con alcuni Z80 si può usare i registri indice (IX) e (IY), come se fossero due 
da 8 bit. E' sufficente scrivere il codice di riferimento a X o Y e poi la specifica 
istruzione diretta ai registri H o L.

Ora vediamo invece uno dei tanti microcontrollori l'AT89C2051, derivato dall'8051,
usa solo due porte delle quattro e 16 registri di uso comune, oltre ad avere due
ingressi del comparatore per i segnali analogici.

Registri principali da 8 bit:  (A) Accumulatore con logica e aritmetica delle 4 operazioni
                               (B) Ausiliario dell'accumulatore e uso comune
                           (R0-R7) 8 registri in due banchi di cui (R0,R1) puntatori memoria
 Coppia registri 8 bit   (DPH-DPL) (DPTR) Registro puntatore dati programma 
 Porta bidirezionale          (P1) P1.0-P1.7 (P1.0-P1.1) ingressi del comparatore
 "        "        "          (P3) P3.0-P3.7 (P3.6 lettura interna comparatore)
 Altri registri a 8 bit       (SP) Puntatore dello stack  
                             (PSW) Registro dei flag e banco (R0-R7) in uso
                              (IP) Priorità interruzioni
                              (IE) Abilitazione interruzioni
                            (TMOD) Controllo modo dei due timer a 16 bit
                            (TCON) Controllo contatori timer
                             (TH0) Byte alto timer 0 (TR0)
                             (TL0) Byte basso   "     "
                             (TH1) Byte alto timer 1 (TR1)
                             (TL1) Byte basso   "     "
                            (SCON) Controllo porta seriale
                            (SBUF) Buffer dati porta seriale
                            (PCON) Controllo consumo alimentazione

I registri A,B,PSW,P1,P3,IP,IE,TCON,SCON, hanno i bit indirizzabili singolarmente

La memoria di programma è di 2048 Byte riscrivibili (4096 per AT89C4051).
La memoria dati (RAM) è di 128 Byte di cui i primi 16 occupati dai registri (R0-R7) per 
i due banchi e il resto per uso comune.

Oggi microcontrollori più moderni come gli AVR e altri, hanno anche la ram Flash
riscrivibile, e registri con funzioni aggiunte sui timer (controllo di blocco ) e pure 
il controllo di segnali in PWM ossia, la modulazione d'impulso a variazione di fase.  

                   (7) Gli indirizzi e i dati in binario

Gli indirizzi contrassegnati con la lettera A e il numero del bit vicino, servono
come si capisce già dal nome, ad indirizzare un dispositivo da cui prelevare o scrivere
dei dati. Di solito per avere la massima velocità il microp. lavora con gli indirizzi
e i dati in parallelo, ma per memorizzare programmi e grandi quantità d'informazioni, 
l'uso più comune è il seriale, a motivo della semplicità di funzionamento e quindi al 
limitato costo (vedi i CD e i DVD). 
Invece i dati in binario si scrivono con la lettera D e il numero del bit vicino. 
(Indirizzo a 16 bit = A15 <---> A0 e dato a 8 bit = D7 <---> D0)

Esempio d'istruzione con l'uso di dati a 8 bit:

MOV 20,#10010000B ;Muovi nella locazione della memoria numero 20 (Dec.)
                  ;il numero binario 10010000 ossia 128 + 16 = 144

I dati e gli indirizzi possono essere scritti anche in forma esadecimale, anzi di
solito gli assemblatori trasformano il listato in un file formato (.Hex) che poi 
può essere scritto nella memoria contenente il programma del microp.   
                   
                   (8) La lettura e scrittura in memoria

Lo Z80 è stato il primo microprocessore ad avere delle istruzioni automatiche potenti, 
nel trattamento e nella ricerca dei dati. Ad esempio per spostare un blocco di byte,
si carica in BC la lunghezza del blocco, in DE la destinazione ed in HL l'indirizzo
di partenza, con una solo comando (LDIR) il microp. trasferisce tutto in un'attimo.
Allo stesso modo il comando CPIR esegue una ricerca di un valore in un blocco di memoria 
e quando lo ha trovato si ferma all'esatto indirizzo.
Ci sono struzioni automatiche anche con l'uso dei dispositivi (IN/OUT) d'ingresso e uscita.   

Ecco alcuni esempi di trasferimento di dati.
LD A,(ADR16)   ;Carica in accumulatore il dato dall'indirizzo ADR 16 bit (Assembler Z80)
MOV ACC,ADR8   ;Stessa istruzione per 8051 con indirizzo a 8 bit
LD (ADR16),A   ;Carica il valore di A nella memoria all'indirizzo ADR  (Z80)
MOV ADR8,ACC   ;Stessa istruzione per 8051
LD A,(HL)      ;Carica in accumulatore il dato dall'indirizzo a 16 bit di HL (Z80)
LD (BC),A      ;Carica in memoria all'indirizzo di BC il dato dell'accumulatore
MOV ACC,@R0    ;Carica in accumulatore il dato dall'indirizzo a 8 bit di R0 (8051) 
MOV @R1,ACC    ;Carica nella memoria dell'indirizzo R1 il dato dell'accumulatore (8051)
MOVC A,@A+DPTR ;Carica in accumulatore il dato dell'indirizzo di A sommato a DPTR (8051)

IL movimento dei dati avviene anche tra gli stessi registri e fra la memoria e i registri.

                (9) L'uso delle porte esterne, (ingressi ed uscite I/O)

Lo Z80 ha 256 indirizzi di I/O oltre ai 65536 indirizzi di memoria, altri microprocessori
vedono gli I/O come memoria. In effetti usare un'indirizzo di memoria per gli ingressi e
uscite ne aumenta la velocità notevolmente. Di solito i dispositivi esterni sono più
lenti della memoria e allora si è costruito un banco fisico proprio per questo uso.
I microp. dei personal computer hanno 65536 indirizzi di I/O.

IN A,(ADR8)   ;Leggi la porta all'indirizzo ADR8 e metti in accumulatore
OUT (ADR8),A  ;Scrivi il valore dell'accumulatore all'indirizzo ADR8 in uscita
MOV P1,ACC    ;Scrivi nella porta P1, il valore dell'accumulatore (8051)
MOV ACC,P3    ;Leggi P3 e mettilo in A (8051) 
                    
            (10) Il salvataggio temporaneo dei registri nello Stack

Questi comandi servono a salvare momentaneamente il contenuto dei registri d'uso
comune oltre all'accumulatore.

PUSH AF       ;Salva nello stack il valore dei due registri 
POP AF        ;Riprendi dallo stack i valori salvati 
PUSH DE       ;Salva DE
POP BC        ;Metti in BC il valore salvato di DE
PUSH PSW      ;Salva il registro di stato nello stack (8051)
POP PSW       ;Riprendi il registro di stato
PUSH 7        ;Salva il registro R7 (banco 1) nello stack (8051)
POP 10        ;Carica nel registro R2 (banco 2) il valore dello stack (8051)

               (11) La trasformazione di una Word in due byte

Come abbiamo visto i registri anche se lavorano con dati a 8 bit, leggono indirizzi
a 16 bit, questo significa che in pratica la parola a 16 bit deve essere divisa in
due parti, quando viene memorizzata. Se vogliamo convertire un numero decimale in 
due byte occorre fare la seguente trasformazione:
Convertire il numero NX in byte alto = INT (NX/256), ossia il risultato 
intero della divisione, mentre per il byte basso = NX - (INT (NX/256)x 256),
ossia sottrarre la parte intera del byte alto moltiplicata 256 da NX e così ciò che
rimane è il byte basso. 
Esempio pratico con 63333: INT (63333/256) = 247 che è il byte alto, per il resto 
si fa 63333 - (247 x 256) = 101 che è il byte basso.
Se ora dovessimo caricare i due valori in un registro doppio come DPTR (8051), facciamo
MOV DPH,#247
MOV DPL,#101
Tuttavia questo caso è immaginario, perché l'istruzione corretta è: MOV DPTR,#63333
e ci pensa l'assemblatore poi, formando il listato esadecimale a scomporre in due byte 
il numero a 16 bit. 
Però è basilare saper fare queste semplici conversioni, quando ci si trova a lavorare 
con i microp. a livello di codice macchina. 
                         
                     (12) Lo scambio dei registri

Queste istruzioni permettono di rendere un programma più flessibile e veloce, in
quanto eseguono uno spostamento multiplo tra il contenuto di due o più registri.
Nello Z80 l'istruzione EX AF,AF' scambia la coppia di registri a 16 bit, ugualmente
EX DE,HL mentre  EX (SP),HL scambia il contenuto dello stack con HL.
L'istruzione EXX scambia addirittura 6 blocchi da 16 bit, ossia BC DE HL con BC' DE' HL'.

Per l'8051 XCH A,R5 scabio dati tra A e R5 poi XCH A,100 scambio di A con la locazione 
di menoria numero 100, XCH A,@R1 scambio tra A e la memoria puntata da R1, XCHD A,@R0
scambio dei due nibble bassi tra l'accumulatore e l'indirizzo della memoria puntata da R0. 
C'è anche SWAP A, che esegue lo spostamento del nibble basso al posto di quello alto e
viceversa; anche questa è un'istruzione molto comoda per trattare dati da scomporre.

                 (13) Le istruzioni dirette al singolo bit           

Utili per testare una certa condizione o preparare un singlo bit come flag ausiliario,
per l'uso in programmi più o meno complessi. Con lo Z80 le istruzioni sono: 

SET b,R      ;Mette a uno il bit b (7-0) del registro R a 8 bit
RES b,R      ;Mette a zero "            "           "
BIT b,R      ;Testa il bit b del registro R e modifica di conseguenza il flag Z

Oppure con il registro A e le funzioni logiche già viste:

AND A,64     ;Test bit 6, Z = 0 se D6 di A = 1 
OR A,64      ;Mette a uno il bit 6, ossia 01000000B
AND A,191    ;Mette a zero il bit 6, 191 = 10111111B

Ecco alcuni esempi dalle istruzioni dell'8051 che sono molto semplici e pratiche.

SETB P3.4    ;Mette a uno il bit 4 della porta P3
SETB P1.0    ;"        "         0  "          P1
CLR P3.4     ;Azzera il bit P3.4
CPL P3.4     ;Esegue il complemento del valore del bit P3.4
SETB TR0     ;Attiva il clock del timer 0
CLR  TR1     ;Ferma il clock del timer 1
CLR EA       ;Disabilita le interruzioni
SETB EA      ;Abilita le interruzioni 
JB P1.0,X    ;Salta all'indirizzo di X se P1.0 è a uno
JNB P1.0,X   ;Salta all'indirizzo di X se P1.0 è a zero
JB RI,X      ;Salta all'indirizzo di X se la porta seriale ha ricevuto un byte
JNB TI,$     ;Ferma il programma, e prosegue solo quando un byte è stato interamente 
             ;trasmesso dalla porta seriale
JBC ACC.7,X  ;Salta all'indirizzo di X se il bit 7 di A è a uno e poi lo mette a zero 

NOTA: Poichè l'8051 non ha dei registri di memoria in ingresso alle porte, i pin di queste
      porte P1 e P3, se per qualche motivo sono cortocircuitati verso massa o comunque
      con un valore resistivo basso, (rispetto alla resistenza interna che è di 27 K 
      verso VCC) quando si vanno a leggere anche se messi a uno in uscita, possono 
      dare valore zero in entrata.

      (14) Le istruzioni delle chiamate a sottoprogrammi e dei salti di programma

Anche queste istruzioni sono essenziali per abbreviare e velocizzare la stesura e 
l'esecuzione dei programmi. Possono essere immediate o condizionate dai flag.
Quando viene eseguita una chiamata (CALL) il microp. salva nello stack l'indirizzo di
rientro , per cui dopo ogni CALL si deve trovare alla fine della routine chiamata, il
comando RET che comanda di ritornare di nuovo al programma principale lasciato 
temporaneamente, secondo l'indirizzo salvato nello stack.

Alcuni esempi dello Z80:

Otto chiamate restart (RST X)
RST 8          ;Chiamata indirizzo basso di memoria (8) con una istruzione da un byte
RST 32         ;"               "             "     (32)  "             "
CALL 32500     ;Chiamata diretta all'indirizzo della memoria  
CALL C,32500   ;Chiamata a condizione che il flag C è uguale a uno
CALL NC,32500  ;"           "         "         "   è uguale a zero
CALL Z,32500   ;"           "         "   il flag Z è uguale a uno
CALL NZ,32500  ;"           "         "         "   è uguale a zero
JP (HL)        ;Salta all'indirizzo a 16 bit della memoria, puntato dal registro HL
JP 32500       ;Salta all'indirizzo della memoria
JP C,32500     ;Salta all'indirizzo della memoria a condizione che C = 1  
JP NZ,32500    ;Salta "         "               "           "      Z = 0
JR 32500       ;Salto relativo con spiazzamento, ossia la massima distanza è di 127 byte
               ;in avanti e 128 byte indietro, questo permette di avere l'indirizzo del
               ;salto in un unico byte. Inoltre se in un programma ci sono solo salti
               ;relativi, questo si può riallocare comodamente senza ricalcolare gli
               ;indirizzi; il microp. 8085 dell'M10 manca di questa versatile istruzione.
JR Z,32500     ;Salto relativo controllato dal flag Z = 1
DJNZ B,X       ;Significa B-1 e salta a X fino a che B <> 0

Alcune istruzioni simili per l'8051:

ACALL XX       ;Chiamata a sottoprogramma con istruzione abbreviata 2 Byte per XX a 11 bit 
LCALL XX       ;"                         "              normale 3 byte per XX a 16 bit 
AJMP  XX       ;Salto distante al massimo 11 bit (2KB)
LJMP  XX       ;Salto con distanza oltre i 2KB
JMP X          ;Salto corto relativo + 127 o - 128 byte
JMP @A+DPTR    ;Salto indicizzato dalla somma di A con DPTR
JC XX          ;Salto relativo con C = 1
JNZ XX         ;"           "      Z = 0
JNB P1.7,X     ;"           "  con il bit P1.7 = 0
JBC ACC.5,X    ;"           "  "   il bit 5 di A = 1 e poi lo mette a zero
CJNE A,#N,X     ;Confronta A con N, e se sono diversi salta a X
CJNE @R0,#N,X  ;"    " il contenuto della memoria puntata da R0 con N e salta a X se diverso 
DJNZ 50,X      ;Decrementa la locazione 50 e salta a X se non è uguale a zero

                     I ritorni da sottoprogrammi con l'uso dello stack

RET           ;Dopo una CALL               (Anche per 8051)
RETI          ;Dopo una interruzione        "     "      "
RETN          ;Dopo "      "      " non mascherabile
RET C         ;Con C = 1
RET NZ        ;Con Z = 0

                        (15) Le istruzioni NOP e HALT

Per tutti i microp. NOP significa non fare niente e quindi lascia passare un determinato
tempo; per lo Z80 sono quattro cicli di clock della frequenza del microp. e per l'8051
un solo clock.
Halt per lo Z80, ferma il microp. in attesa di una interruzione, l'8051 non possiede
questa istruzione ma lo si può fermare controllando il registro PCON (controllo di potenza).
PCON.0 se uguale a uno limita il consumo del microc. e lascia attive le interruzioni, con
PCON.1 uguale a uno invece si ferma il microc. porta il consumo a circa 20 uA e riprende a 
funzionare solo con un reset fisico.   
  
                        (16) Cosa sono le interruzioni                             

Come dice la stessa parola, interrompono l'esecuzione del programma in corso, ogni qual
volta avviene una richiesta urgente, per cui il microp. da la priorità a questa, altrimenti
si potrebbero perdere dei dati o comunque generare un errore più o meno grave del sistema.
Appena il programma dell'interruzione è finito, il processore ritorna ad eseguire quello
che aveva temporaneamente interrotto. Data l'elevata velocità di esecuzione delle 
interruzioni, sovente queste non rallentano visibilmente il programma principale. 
Un'esempio di comune interruzione è la gestione dell'orologio interno, il microp. viene
interrotto ad esempio 20 volte al secondo per gestire i contatori del tempo, ma tutte queste 
interruzioni possono rallentare il programma principale di circa 100-200 microsecondi, 
davvero ben poca cosa. 

Nota: Le singole istruzioni sono eseguite in nanosecondi; con un quarzo da 10 Mhz
      uno Z80 vecchio tipo impiega da 0,4 uS a circa un microsecondo. Per l'8051 con un
      quarzo da 24 Mhz, esegue le singole operazioni da 0,5 o un microsecondo. 
      Processori più recenti (AVR, PIC, ecc.) lavorano con una tecnica all'interno parallela 
      nelle funzioni e così possono raggiungere notevoli velocità con quarzi da pochi Mhz. 
      Personalmente sono riuscito a far lavorare l'AT89C2051 con una frequenza limite di 
      84 Mhz, corrispondente ad un massimo di 7 milioni d'istruzioni al secondo, 
      una ogni 140 nanosecondi.  

Nello Z80 le interruzioni sono comandate con due ingressi fisici a priorità assoluta NMI 
e secondario INT, secondo il programma le interruzioni secondarie possono far uso del 
registro I che sommato ad un dato a 8 bit indica al programma un'indirizzo completo a 16 bit.
Questo sistema permette un veloce servizio, quando i dispositivi periferici collegati al
microp. fanno una chiamata di interruzione.  

Con l'8051 si hanno cinque tipi di interruzione. Due sono generati dall'esterno e tre
interni (i due timer e la porta seriale). All'inizio del programma ci sono degli indirizzi
dedicati dove possono essere memorizzate poche istruzioni con i vettori d'interruzione, 
dove in pratica poi viene eseguito il programma. 
Gli indirizzi partono da 0003H fino a 0023H, e i primi quattro hanno 8 byte a disposizione,
mentre l'ultimo, (porta seriale) teoricamente non ha limite.

                      (17) Il registro di guardia (WATCH DOG)

Nei microc. moderni hanno aggiunto un registro di guardia, il quale serve ad evitare che
per qualche causa accidentale il programma e quindi il microcontrollore si blocchi.
In pratica il registro è un timer fisso, che deve essere ogni tanti millisecondi caricato
al numero massimo (255), perché altrimenti si azzera e genera quindi un reset.
Se il microc. durante il suo funzionamento ogni tanto rinfresca il registro di guardia,
tutto procede bene, ma se per cause accidentali si blocca l'esecuzione del programma, 
quando il registro di guardia è a zero, il tutto si resetta e il microc. riprende a 
lavorare come prima. A questo punto se la causa del blocco (interferenze radio, di 
alimentazione, ecc.) scompare il sistema ha avuto solo un blocco temporaneo.  

                      (18) L'acquisizione dei dati analogici (A/D)

I microp. e microc. lavorano con dati numerici in forma parallela, quando si deve leggere
un valore analogico presente ad esempio su un determinato ingresso, si fa uso della
conversione analogica-numerica e viceversa. 
In inglese A/D (Analogico/Digitale) e D/A (Digitale/Analogico), questi convertitori un
tempo esterni ora si trovano montati direttamente nei microcontrollori di un certo livello.
In'altri più economici si fa uso del principio del comparatore interno o esterno, il microc.
della Zilog Z86E08 monta due comparatori all'interno mentre l'AT89C2051 ne monta uno.

Un esempio di come eseguire la lettura di un valore analogico con il comparatore è facile 
da capire, basta pensare ad un contatore del tempo e ad una rampa di tensione, dalla minima
alla massima.
 
                   Principio di lettura con comparatore analogico

                                (+) 5 Volt                
                  R1 o IC        |                         Vr = Tensione rampa
      /     *-----\/\/\/---------*                         Vx = Tensione da misurare
(Vr) /      |    Comparatore     /                         R1 = 1 Mohm
    /       |     __________     \ R2      ____________    C1 = 10 nF
      C1    |    |          \    /        |Ingresso n.100  Costante di tempo R1xC1 = 10 mSec.   
  *---||-*--*----|(+)        \   \        |Lettura uP      R2 = 10 Kohm
  |      |       |      (Usc) )--*------>(D0)              IC = Generatore corrente costante
/////    |(Vx)<--|(-)        /            |____________    D0 = Uscita comparatore
         |       |__________/                              D7 = Comando scarica C1
         |            |                    
         |          /////                  ____________
         |                                |Uscita n.110
         |       Diodo per scarica C1     |Scrittura uP
         *-------------->|-------------->(D7) 
                                          |____________
                                         
Programma con lo Z80:

                     ORG   60000          ;Locazione di partenza
                   
                           XOR A          ;A = 0 (oppure OUT 110,0)
                           OUT 110,A      ;Scarica C1 attraverso il diodo
                           LD BC,700      ;Carica in BC numero di base 700
                           CALL RIT       ;Aspetta 22 msec. 
                           LD A,128       ;A con D7 = 1 (oppure OUT 110,128)
                           OUT 110,A      ;Inizia la rampa di Vr
                  LEGGE:   IN A,100       ;Legge la porta 
                           AND 1          ;Controlla D0
                           INC BC         ;BC = BC + 1 se Vr < Vx
                           JR Z,LEGGE     ;D0 = 0 (D0 = 1 con Vr = Vx)
                           RET            ;Fine con il registro BC proporzionale a Vx

                    RIT:   LD DE,1000     ;Ritardo di circa 22 ms, clock 4 Mhz
                    ASP:   DEC DE              
                           LD A,D
                           OR E
                           JR NZ,ASP
                           RET 

In pratica la tensione Vx misurabile in questo caso (visto la semplicità del circuito) 
non va da 0 a 5 volt ma da 1 a circa 4,2 volt, e al posto del diodo è meglio usare un 
transistor che scarica meglio C1. 
Senza il generatore (IC invece di R1) della corrente costante, la linearità della rampa 
di Vr nel tempo è inferiore; tuttavia fra 2,5 e 3,5 volt va bene lo stesso. 
Se usiamo ad esempio una sonda per la temperatura atmosferica come l'LM135, che dà una 
tensione di 2,73 volt con 0 C° e che sale a 3,73 volt a 100 C°, la lettura è abbastanza 
precisa anche con variazioni di un solo millivolt, corrispondente ad un decimo di grado. 

                19) La conversione dei dati numerici in analogici (D/A)

Permette di trasformare il valore (peso) dei bit, in un segnale analogico con cui poi si 
può controllare attraverso sucessiva elaborazione, qualsiasi dispositivo elettronico.   
Ci sono dispositivi interfacciabili con i microp. che eseguono ambedue le conversioni
(A/D D/A) con opportuna alimentazione duale.
                   
                             
     Porta uscita            20 Kohm            20 Kohm
     a 4 bit.          D0)---\/\/\/------*------\/\/\/------/////
     Indirizzo 110                       /    
                                         \ 10 Kohm
                             20 K        /
                       D1)---\/\/\/------*
                                         /
                                         \ 10 K
                             20 K        /
                       D2)---\/\/\/------*
                                         /
                                         \ 10 K    
                             20 K        /        
                       D3)---\/\/\/------*---> VX Uscita <---/////
                                              
Programma dimostrativo con lo Z80, per generare in VX, una tensione con andamento variabile 
a gradini di forma triangolare.
 
                     ORG  60000          ;Inizio programma
                          
                          LD C,15        ;Valore base 00001111B in C       
                          LD A,C         ;A = C
                   ESCE1: OUT 110,A      ;Esce A da porta 110
                          DEC A          ;A = A-1 (Rampa in discesa)
                          NOP            ;Ritardo d'equilibrio rampa
                          JRNZ,ESCE1     ;Continua fino a che A = Zero
                   ESCE2: OUT 110,A      ;Esce A da porta 110
                          INC A          ;A = A+1 (Rampa in salita) 
                          CP C           ;Controllo se A = 15
                          JRNZ,ESCE2     ;Continua fino a che A < 15
                          JR,ESCE1       ;Ritorna inizio A = 15
                          
                                       + ------------------------ + VCC
                                         \    /\    /\    /\    / 
                      Andamento nel       \  /  \  /  \  /  \  /
                      tempo di VX          \/    \/    \/    \/
                                       0 -----------------------> Tempo

Nota: Tutte le porte in uscita hanno in pratica un registro di memoria su cui il microp.
      scrive il dato in binario e i valori vengono presentati in uscita sui terminali
      corrispondenti ai singoli bit . (Vedi la differenza dell'8051 - Nota argomento n.13).

                           (20) Il sistema a multiprocessore

Sia per motivi di velocità, come pure per praticità di gestione e quindi di potenza, a volte
si richiede che più microp. o microc. debbano lavorare insieme.
Sia che i microprocessori siano due o di più, esiste sempre una gerarchia di configurazione
fisica, dove uno solamente è il controllore primario (Master) e gli altri sono i controllori
secondari (Slave); questa disposizione è necessaria in modo da evitare un conflitto di 
comandi o ordini, in tal modo si è esenti da errore. 
Come l'esempio del direttore d'orchestra, senza di lui, anche se tutti i componenti 
dell'orchestra suonano alla perfezione il loro strumento, ci saranno ugualmente degli errori
durante l'esecuzione musicale, in quanto manca la coordinazione principale del direttore. 

Le configurazioni base sono in modo parallelo o in modo seriale. 
La più comune è la seriale, per cui questi microcontrollori hanno dei registri per il dialogo
e il controllo esterno seriale, ciò permette anche una dislocazione fisica diversa fra i vari 
dispositivi intelligenti. 
Di solito il sistema si chiama (SPI), abbreviazione dall'inglese che in italiano significa: 
Interfaccia di Periferia Seriale; che viene usata sia per dialogare con altri microp. o 
microcontrollori, come pure per lo scambio di dati con dispositivi di periferia più semplici
come porte di I/O, memorie Eeprom, ecc.                                                   

              Esempio di sistema (SPI) minimo a due microcontrollori

          Primario (Master)                            Secondario (Slave)
       ______________________                        ________________________
                             |     Sincronismo      |  
            (Segnale di Clock)------------------>---(Ingresso Clock)
        (Segnale di controllo)------------------>---(Ingresso segnale controllo)
         (Uscita dati seriali)------------------>---(Ingresso dati seriali)
       (Ingresso dati seriali)----<-----------------(Uscita dati seriali            
       ______________________|                      |_______________________

I microcontrollori dell'Atmel del tipo AVR, usano il sistema SPI anche per la programmazione,
in quanto volendo si possono programmare non solo con il sistema tradizionale, ma anche senza 
estrarli fisicamente dal circuito dove sono installati.
Nella famiglia derivante dall'8051, la porta seriale può comunicare con un bit in più il
nono, il quale volendo attraverso la configurazione del registro di comunicazione SCON, si 
può usare nel programma in modo che controlli l'interruzione seriale. 
Così facendo è possibile usare anche l'8051 in un sistema a multiprocessore.


Continua................
      
 Saluti, Emilio ik1wjq
 
Vai a pagina1