release 3 Il microcontrollore PIC16F877A di Ippolito Perlasca Presentazione



Documenti analoghi
A/D CON PIC 16F877. Sommario INTRODUZIONE... 2 SELEZIONARE I BANCHI... 2 ADCON ADCS1, ADCS CH2, CH1 E CH GO/DONE... 6 ADON...

Lezione 8. Figura 1. Configurazione del registro INTCON

Architettura del PIC 18F452

introduzione I MICROCONTROLLORI

8 Microcontrollori PIC

Introduzione ai microcontrollori PIC

Architettura hardware

MICROCONTROLLORE PIC16F84A

Dispensa di Informatica I.1

INTRODUZIONE alla PROGRAMMAZIONE di MICROCONTROLLORI

I microcontrollori. In sostanza i pic hanno le seguenti caratteristiche:

C. P. U. MEMORIA CENTRALE

CALCOLATORI ELETTRONICI A cura di Luca Orrù. Lezione n.7. Il moltiplicatore binario e il ciclo di base di una CPU

CPU. Maurizio Palesi

Laboratorio di Informatica

Struttura del calcolatore

Architettura del calcolatore

Ing. Paolo Domenici PREFAZIONE

Esame di INFORMATICA

Corso di Sistemi di Elaborazione delle informazioni

ARCHITETTURE MICROPROGRAMMATE. 1. Necessità di un architettura microprogrammata 1. Cos è un architettura microprogrammata? 4

Lezione 1 Caratteristiche principali del PIC16C84 L'hardware

CIRCUITO DI TEST E SOFTWARE UTILIZZATI

Lezione 4. Figura 1. Schema di una tastiera a matrice di 4x4 tasti

Quinto Homework. Indicare il tempo necessario all'esecuzione del programma in caso di avvio e ritiro fuori ordine.

Architettura della CPU e linguaggio assembly Corso di Abilità Informatiche Laurea in Fisica. prof. ing. Corrado Santoro

Input/Output. Moduli di Input/ Output. gestiscono quantità di dati differenti a velocità diverse in formati diversi. n Grande varietà di periferiche

Introduzione all'architettura dei Calcolatori

Introduzione. Classificazione di Flynn... 2 Macchine a pipeline... 3 Macchine vettoriali e Array Processor... 4 Macchine MIMD... 6

LABORATORIO DI SISTEMI

Con il termine Sistema operativo si fa riferimento all insieme dei moduli software di un sistema di elaborazione dati dedicati alla sua gestione.

Università di Roma Tor Vergata Corso di Laurea triennale in Informatica Sistemi operativi e reti A.A Pietro Frasca.

ESERCIZI SUI SISTEMI DI NUMERAZIONE

4 3 4 = 4 x x x 10 0 aaa

Laboratorio di Informatica

FONDAMENTI di INFORMATICA L. Mezzalira

SISTEMI DI NUMERAZIONE E CODICI

Capitolo Quarto...2 Le direttive di assemblaggio di ASM Premessa Program Location Counter e direttiva ORG

DMA Accesso Diretto alla Memoria

Architettura dei computer

Circuiti sequenziali e elementi di memoria

Il Sistema Operativo. C. Marrocco. Università degli Studi di Cassino

La somma. Esempio: Il prodotto. Esempio:

INSTALLAZIONE NUOVO CLIENT TUTTOTEL (04 Novembre 2014)

Il processore. Il processore. Il processore. Il processore. Architettura dell elaboratore

SISTEMI OPERATIVI. Prof. Enrico Terrone A. S: 2008/09

Introduzione. Corso di Informatica Applicata. Università degli studi di Cassino

Calcolo numerico e programmazione Architettura dei calcolatori

PROCEDURA INVENTARIO DI MAGAZZINO di FINE ESERCIZIO (dalla versione 3.2.0)

INFORMATICA CORSO DI INFORMATICA DI BASE ANNO ACCADEMICO 2015/2016 DOCENTE: SARRANTONIO ARTURO

La memoria centrale (RAM)

Gestione della memoria centrale

4. Operazioni aritmetiche con i numeri binari

Memorie ROM (Read Only Memory)

Chapter 1. Circuiti sequenziali: macchine a stati

ARCHITETTURA DELL ELABORATORE

Laboratorio di Ingegneria del software Sistema di controllo di un ascensore Requisisti preliminari

PROCESSOR 16F84A. ;configurazione FUSES: oscillatore XT, WDT disabilitato PWRT abilitato, CP disabilitato config 0x3FF1

CLASSE III A I.T.I. (ABACUS) SISTEMI DI ELABORAZIONE E TRASMISSIONE DEI DATI VERIFICA DI RECUPERO

Convertitori numerici in Excel

PROGRAMMA DI SISTEMI TERZA AET 2014/2015

Informatica B a.a 2005/06 (Meccanici 4 squadra) PhD. Ing. Michele Folgheraiter

I sistemi di numerazione

Scopo della lezione. Informatica. Informatica - def. 1. Informatica

Il Processore: i registri

La tecnica proporzionale

Dispense di Informatica per l ITG Valadier

STRUTTURE DEI SISTEMI DI CALCOLO

Tipi primitivi. Ad esempio, il codice seguente dichiara una variabile di tipo intero, le assegna il valore 5 e stampa a schermo il suo contenuto:

Il calcolatore elettronico. Parte dei lucidi sono stati gentilmente forniti dal Prof. Beraldi

Airone Gestione Rifiuti Funzioni di Esportazione e Importazione

COME È FATTO IL COMPUTER

Esempio: aggiungere j

EasyPrint v4.15. Gadget e calendari. Manuale Utente

Informatica - A.A. 2010/11

Reti sequenziali sincrone

Sistemi Operativi. 5 Gestione della memoria

Reti sequenziali. Esempio di rete sequenziale: distributore automatico.

APPUNTI DI MATEMATICA LE FRAZIONI ALGEBRICHE ALESSANDRO BOCCONI

I SISTEMI DI NUMERAZIONE

Il microprocessore 8086

Excel. A cura di Luigi Labonia. luigi.lab@libero.it

MODELLO CLIENT/SERVER. Gianluca Daino Dipartimento di Ingegneria dell Informazione Università degli Studi di Siena

Arduino: Programmazione

Pronto Esecuzione Attesa Terminazione

Categorie di sistemi Digitali

Architettura di un sistema di calcolo

CONTATORI ASINCRONI. Fig. 1

Capitolo 11 La memoria cache

La gestione di un calcolatore. Sistemi Operativi primo modulo Introduzione. Sistema operativo (2) Sistema operativo (1)

Funzioni in C. Violetta Lonati

Architettura del computer (C.Busso)

Strutturazione logica dei dati: i file

Capitolo. Interfacciamento di periferiche I/O con il PC. 1.1 Il BUS di espansione del PC

LA TRASMISSIONE DELLE INFORMAZIONI QUARTA PARTE 1

Lezione 2 OPERAZIONI ARITMETICHE E LOGICHE ARCHITETTURA DI UN ELABORATORE. Lez2 Informatica Sc. Giuridiche Op. aritmetiche/logiche arch.

La macchina programmata Instruction Set Architecture (1)

Il memory manager. Gestione della memoria centrale

1.4b: Hardware. (Memoria Centrale)

I Thread. I Thread. I due processi dovrebbero lavorare sullo stesso testo

Transcript:

release 3 Il microcontrollore PIC16F877A di Ippolito Perlasca Presentazione Questa è una dispensa sul microcontrollore PIC16F877A. Non è completa: manca la descrizione di alcune periferiche, ma le più importanti ci sono. Le informazioni valgono anche per gli altri microcontrollori della famiglia PIC16, tenendo presente che alcune periferiche sono assenti nei modelli di fascia più bassa e la mappa di memoria può essere ridotta. Premessa Con il termine microcontrollore (microcontroller) si intende comunemente un sistema a microprocessore integrato su un unico chip, che comprende, oltre alla CPU, una memoria di programma non volatile (nei modelli più vecchi una EPROM o una EEROM, in quelli più recenti una FLASH), una memoria RAM, generalmente di dimensioni ridotte, per gestire i risultati intermedi dell'elaborazione e allocare lo stack, e periferici di I/O vari (porte seriali e/o parallele, contatori/timer, convertitori A/D ecc.). Con queste caratteristiche, è evidente che i microcontrollori sono stati concepiti soprattutto per applicazioni industriali di controllo, in cui il programma di gestione, una volta messo a punto, non ha più la necessità di essere modificato (o di esserlo raramente). Sono applicazioni che gli anglosassoni chiamano embedded, cioè "incorporate" in prodotti e apparati finiti, che possono andare dagli elettrodomestici "intelligenti", alla domotica, ai sistemi di comunicazione e sicurezza, ai decoder per la TV satellitare e digitale, alla strumentazione, all'automazione in campo automobilistico (automotive). La famiglia PIC16 della Microchip Tra i numerosi tipi sul mercato, i microcontrollori prodotti dalla Microchip Technology Inc. americana hanno negli ultimi anni guadagnato ampia diffusione, grazie alla loro concezione innovativa, che li rende molto versatili e relativamente facili da programmare. La CPU dei microcontrollori PIC (probabile acronimo di Programmable Integrated Controller), si distacca dalla struttura di un microprocessore classico, essenzialmente perché è una RISC (Reduced Instruction Set Computing, elaborazione con insieme di istruzioni ridotto), basata su una struttura del tipo Harward (dall'università americana presso cui è stata sviluppata). L'architettura Harvard, a differenza della macchina di Von Neuman classica, ha la memoria di programma e la memoria dati separate e afferenti a bus diversi; da parte sua, la filosofia RISC consiste sostanzialmente nel prevedere poche e semplici istruzioni, tutte della stessa lunghezza e (possibilmente) tutte caratterizzate dallo stesso numero di cicli macchina sia per il fetch (caricamento) che per l'esecuzione. Questa caratteristica (che, come vedremo, è soddisfatta dai PIC, con l'eccezione delle istruzioni di salto), unita alla separazione fisica dei bus lungo cui fluiscono istruzioni e dati, permette di ottenere una sovrapposizione (PipeLining, "incanalamento") della fase di fetch di un'istruzione con quella di esecuzione della precedente "senza buchi" e quindi molto più efficiente 1

in termini di velocità di quanto sia possibile in una CPU tradizionale, in cui le istruzioni hanno lunghezza diversa e quindi tempi di fetch diversi. Una ulteriore velocizzazione è dovuta al fatto che la riduzione del numero di istruzioni, unita a quella dei tipi di indirizzamento, semplifica l'hardware per la loro decodifica. Nei PIC, un'istruzione richiede quattro cicli di clock (un cosiddetto ciclo macchina T CY, che è l'unità temporale su cui si svolgono le attività del microcontrollore) per essere caricata e decodificata, e un altro ciclo macchina per essere eseguita; grazie però al pipelining, mentre l'istruzione viene caricata, va in esecuzione l'istruzione precedente e, mentre l istruzione corrente viene eseguita, viene caricata la successiva (Figura 1) Figura 1 Ciclo di clock e ciclo macchina (OSC1 è il segnale di clock. Il ciclo macchina T CY comprende quattro periodi di clock Q1, Q2, Q3, Q4. PC sta per program counter, il contatore che contiene in ogni momento l'indirizzo della prossima istuzione da catturare ed eseguire). Il meccanismo di pipeling "perde un colpo" solo quando l'istruzione è di salto (Figura 2): Figura 2 Flusso "Pipeline" delle istruzioni Infatti l'esecuzione un'istruzione di questo tipo (Execute 3 nell'esempio di Figura 2) interrompe l'ordine lineare del programma e l'istruzione successiva, catturata nello stesso ciclo macchina (Fetch 4 in Figura 2), deve essere tolta ("Flush") dalla coda di pipelining senza essere eseguita. 2

In questo modo ogni istruzione richiede un ciclo macchina (4 cicli di clock) per essere eseguita, con l'eccezione dei salti che richiedono due cicli e della prima istruzione, che ne richiede comunque uno in più (Il 1 fetch non corrispondente a nessuna esecuzione). I microcontrollori PIC si dividono in cinque famiglie. Le prime tre sono a 8 bit (parallelismo dei dati) e si dividono fra loro per il formato e il numero (e quindi la flessibilità) delle istruzioni famiglia di base (Base-Line PIC10 famiglia medio livello (Mid-Range PIC12, PIC16) famiglia alto livello (High-End PIC18) con istruzioni a 12 bit con istruzioni a 14 bit con istruzioni a 16 bit ciascuna di queste tre famiglie mantiene la compatibilità software verso il basso, a livello di linguaggio di programmazione intermedio (assembly). All'interno della singola famiglia il set di istruzioni è lo stesso: i vari modelli si distinguono solo per quantità di memoria RAM e programma (e tecnologia di quest'ultima) velocità e dotazione di periferiche (che però, come vedremo, non richiedono istruzioni specifiche). Esistono poi due altre famiglie di microcontrollori a 16 bit di parallelismo dati, una costituita da microprocontrollori d'uso generale e alte prestazioni, l'altra con capacità DSP (Digital Signal Processing, elaborazione digitale dei segnali). nel 2007 è stata introdotta un'ulteriore famiglia a 32 bit di parallelismo dati. IL PIC 16F877A E' un microcontrollore recente, che si situa all'estremità alta della famiglia Mid-Range. Lo prendiamo come esempio perché è molto usato, estremamente versatile, dotato di numerosi periferiche di I/O, utilizzato in molti sistemi didattici e tool di sviluppo facilmente reperibili e poco costosi, che utilizzano la sua possibilità di lavorare in autoemulazione e in circuit-debugging (vedremo poi), senza la necessità di costosi emulatori dedicati. E' anche riprogrammabile in circuito. In Figura 3 è riportato il pinout, che è lo stesso del PIC16F874A (che ha meno memoria, sia di programma che dati). E' evidente che numerosi piedini assolvono a funzioni diverse. 3

Figura 3 Piedinatura PIC16F877A 4

Riportiamo qui le caratteristiche principali del PIC16F877A (per l'elenco completo vedere i datasheet del costruttore): Architettura RISC/Harward (abbiamo già visto cosa vuol dire). Parallelismo dati di 8 bit. Set di sole 35 istruzioni di una singola parola di 14 bit, tutte di un solo ciclo macchina (200 ns alla massima frequenza di clock di 20 MHz), tranne le istruzioni di salto che ne richiedono 2. Il set d'istruzioni è lo stesso di tutta la famiglia Mid-Range PIC16. Tecnologia CMOS, TTL compatibile quando alimentato a 5V (ma la tensione di alimentazione può andare da 2.0 a 5.5 V). Basso consumo: < 0.6 ma a 4 MHz di clock (senza carichi esterni), < 1 µa in stand-by. Alta corrente in uscita: 25 ma sink/source. Interfaccia JTAG a 4 fili per la programmazione. Se i tre piedini d interfaccia (il quarto è la massa) non devono essere usati per altri scopi, il microcontrollore può essere riprogrammato in circuito, e funzionare anche in autoemulazione (cioè funzionare sotto il controllo di un software in ambiente Windows, che permette di intervenire sui registri del microcontrollore, fissare un breakpoint, ecc.). Frequenza di clock dalla continua (esclusa!) a 20 MHz, con varie opzioni di generazione (gruppo RC, cristallo di quarzo, risuonatore ceramico) selezionabili in fase di programmazione. Memoria di programma FLASH di 8192 x 14 bit (quindi fino a 8192 istruzioni, 8 k). 368 byte di RAM, organizzati come registri a 8 bit, alcuni dei quali specializzati. 256 byte di memoria dati non volatile EEPROM (scrivibile, con tempi dell'ordine dei ms, durante il normale funzionamento da programma. Stack hardware (cioè separato dalle aree di programma e dati e non accessibile al programmatore) profondo 8 livelli (si possono "nidificare" fino a 8 chiamate a subroutine una dentro l'altra). 33 linee di I/O digitale programmabili indipendentemente e organizzate in 5 porte. 3 timer/contatori, due a 8 bit uno a 16. 1 convertitore A/D a 10 bit di risoluzione e 8 ingressi multiplexati. 1 USART (porta seriale asincrona standard). 1 porta seriale sincrona standard SSP e I 2 C. 1 porta parallela slave con segnali di abilitazione e read/write esterni. 2 comparatori analogici con sorgente di tensione di riferimento interna. 2 moduli Compare/Capture/PWM, per generare eventi (interrupt) su un conteggio prestabilito del timer a 16 bit, per leggere il timer su un evento esterno, per generare, sempre in connessione con il timer a 16 bit, segnali PWM per controlli di potenza. 14 possibili sorgenti di interrupt. Presenza di un WatchDog Timer (WDT, temporizzatore cane da guardia ), attivabile in fase di programmazione, che genera un reset ogni circa 18 ms (espandibili a oltre 2 s con un prescaler). Questa ripartenza ciclica del programma è utile per sbloccare il microcontrollore da eventuali situazioni critiche o di blocco, in cui possa finire a causa di particolari condizioni esterne (di input dal processo controllato). Possibilità da programma di mandare il microcontrollore in uno stato di SLEEP (stand-by), in cui l esecuzione del programma è sospesa e il consumo ridotto. Dallo SLEEP il PIC esce su reset esterno (livello basso su MCLR ), su interrupt o reset del WDT. Possibilità di proteggere il codice di programma contro la lettura, molto importante nelle applicazioni commerciali. 5

In Figura 4 è riportato lo schema a blocchi interno: Figura 4 Organizzazione interna del PIC16F877A Notare la separazione, tipica dell architettura Harward, fra la memoria di programma (Flash Program Memory) e la memoria dati (RAM File Registers, Registri d Archivio in RAM), nonché fra Data Bus a 8 bit e Program Bus (Bus Istruzioni a 14). Al solito, una delle caratteristiche principali di un microprocessore (e di un microcontrollore) è la sua dotazione di registri. Il nostro amico ne ha 368 di uso generale, 55 specializzati, più un accumulatore o registro di lavoro (Working Register W), che intrattiene una relazione speciale con l ALU (Unità Aritmetico-Logica), nel senso che è operando e possibile destinazione di tutte le operazioni aritmetico-logiche. Tutti questi registri sono a 8 bit (anche quelli implicati negli indirizzamenti, vedremo poi come è possibile accedere a tutta la memoria di programma, che richiede 13 bit d indirizzo: 8192 = 2 13 ). 6

Con l eccezione di W, che non ha indirizzo e a cui le istruzioni si riferiscono implicitamente, tutti gli altri registri (i cosiddetti RAM File Register, registri RAM di archivio), sono individuati da un indirizzo a 9 bit da 0 (000h) a 511 (1FFh) e sono suddivisi in 4 blocchi contigui di 128 registri ciascuno: Blocco 0 da 000h a 07Fh Blocco 1 da 080h a 0FFh Blocco 2 da 100h a 17Fh Blocco 3 da 180h a 1FFh Poiché 368 registri di uso generale + 55 specializzati fanno un totale di 423 registri e non 4 x 128 = 512, prima di concludere che i conti non tornano è opportuno dare un occhiata alla mappa dei registri d archivio in Figura 5: Figura 5 Mappa dei registri RAM di archivio del PIC16F877A 7

Cominciamo a notare che le locazioni grigie sono in maggioranza non implementate (se lette danno tutti 0, probabilmente servono a futuri sviluppi) o ( solo un paio) riservate. Consideriamo poi le locazioni indicate da un nome: sono i 55 registri specializzati, che servono alla gestione della CPU e dei periferici. Molti nomi però si ripetono: per esempio il registro STATUS (registro di stato) compare nella stessa posizione in tutti e 4 i blocchi. Non significa che questi registri sono costituiti da più byte: sono sempre registri a 8 bit, ma sono mappati su più indirizzi: ad esempio, si può accedere al registro di stato STATUS dall indirizzo 003h (blocco 0), dall indirizzo 083h (blocco 1), da quello 103h (blocco 2) e da quello 183h (blocco 3): notate che sono indirizzi che differiscono fra loro solo per i due bit alti (di ordine 8 e 7), dei 9 che li costituiscono. L indirizzamento della memoria dati 1 La particolare organizzazione dei registri d archivio pone qualche problema nel loro accesso. Consideriamo ad esempio l istruzione MOVWF, che copia il registro di lavoro in un registro di archivio. Ad esempio: MOVWF 0x12A ; copia W nel registro di indirizzo 12Ah, che sta nel blocco 2 (Per i particolari, vedere la sezione sulle istruzioni e quella sulla programmazione; tenere presente che il prefisso 0x in assembly indica un numero esadecimale). Se guardiamo il codice operativo (codice macchina a 14 bit) di MOVWF (Figura 20), vediamo che è 0000001fffffff, dove fffffff indicano i sette bit di indirizzo del registro d archivio di destinazione. Se guardiamo i codici delle altre istruzioni che accedono a registri di archivio, la cosa si ripete: nel codice ci sono solo sette bit per specificare il registro. Ma 12Ah = 100101010b, ci sono due bit in più che nell assemblaggio (traduzione in linguaggio macchina) vanno persi: il codice operativo di MOVWF 0x12A diventa infatti: 00000010101010b dove i bit ombreggiati sono i sette bassi di 12Ah 2. 1 Da qui in avanti per comprendere quanto segue è necessario avere qualche nozione sulle istruzioni del PIC e sulla sua programmazione. Tuttavia, per non interrompere il discorso sulla struttura del microcontrollore, rimandiamo ogni volta che sia necessario ai paragrafi Il set di istruzioni e Descrizione delle istruzioni. Il lettore non si faccia problemi a saltare avanti e indietro: non sempre un percorso lineare è il modo migliore per comprendere un argomento (relativamente) complesso. 2 Lo stesso problema c è anche se usiamo nomi simbolici: se ad esempio abbiamo definito BLOB come 12Ah, MOVFW BLOB in linguaggio macchina sarà ancora 00000010101010b, anche se BLOB = 12Ah = 100101010b. 8

Come fa allora la CPU a riferirsi al registro giusto? Il meccanismo, piuttosto complicato, è illustrato Figura 6 Figura 6 Indirizzamento diretto/indiretto Cominciamo con il caso dell indirizzamento diretto, in cui nell istruzione c è l indirizzo del registro operando (è quello appena visto): i 7 bit bassi (6 0) dell indirizzo vengono presi dal codice operativo (opcode), i due alti (8-9) sono i due bit specializzati RP1:RP0 del registro STATUS (rispettivamente i bit 6 e 5). In altri termini RP1:RP0 selezionano il banco RAM attivo (00 banco 0, 01 banco 1, 10 banco 2, 11 banco3). Ma attenzione: RP1:RP0 non vengono settati automaticamente: la loro gestione è a carico del programmatore, che deve ricordarsi di alzarli/abbassarli a seconda in che banco stia il registro cui vuole accedere. Nel nostro esempio, la sequenza d istruzioni potrebbe essere: BSF STATUS,RP1 ; Alza RP1 BCF STATUS,RP0 ; Abbassa RP0 MOVWF 0x1AE ; Esegui l istruzione Dove BSF, BCF sono istruzioni che rispettivamente alzano e abbassano un bit specificato di un registro specificato (vedi paragrafo sulle istruzioni); RP1 e RP0 sono definiti come costanti rispettivamente uguali a 6 e a 5 nel file.inc (vedi programmazione). l'istruzione BCF STATUS,RP0 può essere inutile, se RP0 non è stato alzato prima: RP1:RP0 sono bassi al reset e all accensione (cioè il banco di default e lo 0). 9

In linea di massima, dopo aver avuto accesso ai banchi RAM 1 3, conviene ritornare in banco 0, ove ci sono i registri di solito più usati: BCF STATUS,RP1 ; Torna al BCF STATUS,RP0 ; banco 0 Un meccanismo simile vale per l indirizzamento indiretto, cioè per le istruzioni in cui l indirizzo del registro operando è variabile, e contenuto in un altro registro. Nei PIC, per rispettare l esigenza dell architettura RISC che tutte le istruzioni abbiano lo stesso formato, non ci sono istruzioni specifiche a indirizzamento indiretto: esiste un registro indice FSR (mappato su tutti 1 4 blocchi agli indirizzi 004h, 084h, 104h, 184h) in cui va caricato l indirizzo del registro su cui si vuole operare (o meglio, gli 8 bit bassi dei 9 che costituiscono l indirizzo). Una qualunque istruzione che opera sui registri d archivio può operare sul registro puntato da FSR riferendosi al registro fittizio INDF (indirizzi 000h, 080h, 100h, 180h); se però il registro puntato è in uno dei due blocchi superiori (cioè ha 1 nel nono bit d indirizzo), il nono bit, che non ci sta in FSR, va messo nel bit IRP (MSB del registro STATUS): IRP va cioè alzato (vedi ancora Figura 6). Ad esempio, a sequenza d istruzioni (vedi Set di istruzioni): MOVLW PORTB ; Carica in W l indirizzo POTB MOVWF FSR ; Passalo in SFR MOVLW 255 ; Carica in W il dato 255 MOVWF INDF ; Passalo a INDF (in realtà a PORTB) carica 255 in PORTB, se IRP è basso (default); infatti PORTB è nel blocco 1, e pertanto il MSB del suo indirizzo è 0. Ma se vogliamo caricare lo stesso dato in EEDATA, che sta nel blocco 2 (MSB di indirizzo = 1) dobbiamo scrivere: BSF STATUS, IRP MOVLW EEDATA ; In W e poi in SFR solo gli 8 bit bassi MOVWF FSR ; dell indirizzo EEDATA MOVLW 255 MOVWF INDF BCF STATUS, IRP Il puntamento delle istruzioni Anche il meccanismo di puntamento delle istruzioni richiede qualche chiarimento: il Program Counter (PC) è a 13 bit (2 13 = 8192 locazioni di programma). La parte bassa (8 bit) di PC è PCL, che è un registro accessibile sia in lettura che scrittura, come d altra parte tutti i registri d archivio, specializzati o no. PCL è mappato in tutti i quattro blocchi, rispettivamente agli indirizzi 002h, 082h, 102h, 182h. La parte alta di PC (5 bit), non è direttamente accessibile, ma si possono modificare indirettamente attraverso il registro PCLATH, anch esso mappato sui quattro blocchi (00Ah, 08Ah, 10Ah, 18Ah). PCLATH è a 8 bit, ma solo i 5 più bassi sono scrivibili e hanno significato. Quando un istruzione modifica PCL (ha cioè PCL come destinazione), i 5 bit alti di PC vengono copiati dai 5 bit bassi di PCLATH (figura 7, immagine in alto): 10

Figura 7 Il caricamento del Program Counter in differenti situazioni Quando invece viene eseguita un salto incondizionato GOTO ADDR, o una chiamata a subroutine CALL ADDR, dell indirizzo ADDR (di 13 bit) nel codice operativo dell operazione trovano posto solo gli 11 bit inferiori. Ad esempio, GOTO ha codice 101kkkkkkkkkkk, dove gli 11 k rappresentano appunto il posto per tali bit. I due bit mancanti gli arrivano dai bit <4:3> di PCLATH (Figura 7, immagine in basso); gli altri bit di PCLATH vengono ignorati. Poiché al reset/accensione PCLATH assume il valore 0, non c è bisogno di ricordarsi di lui se i salti sono nella 1 a pagina di 2 11 = 2048 locazioni di memoria (indirizzi 0000h 07FFh). Se invece si deve saltare in un altra pagina, bisogna caricarlo con i bit alti dell indirizzo: MOVLW HIGH ADDRR ; HIGH è una direttiva che estrae il byte alto di ADDR MOVWF PCLTH ; Metti il byte alto di ADDR in PCLATH (i bit <12.11> ; di ADDR vanno nei bit <4:3> di PCLATH) GOTO ADDR ; In questa istruzione vengono considerati gli 11 bit ; bassi di ADDR. Il registro di stato Nella tabella 2.1 del Datasheet è riportata la descrizione sintetica dei 55 registri specializzati, insieme con la condizione che assumono al reset. La maggior parte di questi registri è connessa alla gestione dei periferici (vedi oltre). Tra gli altri, un ruolo fondamentale lo ricopre il registro di sato STATUS (Figura 8): Figura 8 Il registro di stato (R/W sta per bit leggibile/scrivibile, R per bit di sola lettura. I valori segnati sono quelli al reset di accensione; x indica un valore imprevedibile) 11

Il registro STATUS, che è accessibile da tutti i quattro banchi RAM (indirizzi 003h, 083h, 103h, 183h), è costituito da 8 bit (flag) che vengono influenzati dal risultato di operazioni o da particolari eventi, e altri che concorrono, come già visto, all indirizzamento dei registri d archivio. Ecco il loro significato, a partire da LSB: - C (bit 0) Flag di carry: segnala un riporto oltre agli 8 bit (overflow) nelle operazioni di somma. Vedi scheda 1 per il suo comportamento nelle sottrazioni (che non coincide con quello di un flag di prestito, come ci si aspetterebbe). - DC (bit 1) Flag di Digit Carry: segnala un riporto dal nibble (4 bit) basso a quello alto in un operazione di somma. - Z (bit 2) Flag di zero: va alto se il risultato dell operazione logico/aritmetica precedente è 0, basso altrimenti. E influenzato anche da alcune operazioni di trasferimento (vedi descrizione delle istruzioni). - PD (bit 3) Flag di Power Down (riduzione di potenza, standby): va a 1 (inattivo) durante la temporizzazione di Power-Up o per un istruzione CLRWDT (azzeramento del watchdog timer); viene portato a 0 (attivo). dall istruzione di SLEEP. - TO (bit 4) Flag di Time Out: va a 1 (inattivo) durante la temporizzazione di Power-Up o per un istruzione CLRWDT: viene attivato (0) dal time-out del watchdog timer. In sostanza, PD e TO permettono, dalle loro combinazioni, di individuare se un reinizio di programma (reset) è stato determinato da un normale reset hardware su MCLR, se invece si tratta di un uscita dal modo SLEEP, o di un time-out del watchdog timer, o ancora di una combinazione di queste condizioni. Notare che PD e TO sono gli unici bit di STATUs che possono solo essere letti, ma non scritti. - RP1, RP0 (bit 6 e 5) Flag di selezione della pagina (blocco) RAM: selezionano il blocco di registri d archivio nell indirizzamento diretto, secondo lo schema: <RP1:RP0> Blocco indirizzato Indirizzi 00 Blocco 0 000h 07Fh 01 Blocco 1 080h 0FFh 10 Blocco 2 100h 17Fh 11 Blocco 3 180h 1FFh (vedi sopra, Indirizzamento memoria dati). - IRP (bit 7) Flag di selezione pagine (blocchi RAM) nell indirizzamento indiretto. 0 seleziona i blocchi 0:1 (indirizzi 000h 0FFh), 1 i blocchi 2:3 (indirizzi 100h 1FFh). Vedi sopra, Puntamento delle istruzioni. E importante notare che STAUS, come altri registri di servizio, è accessibile indipendentemente dal blocco RAM selezionato, grazie al fatto di essere mappato su quattro indirizzi che differiscono solo per i due bit superiori (quelli appunto di selezione blocco). Questo è essenziale per poter arrivare a RP1:RP0: se accedere a STATUS richiedesse il loro settaggio, che si fa attraverso STATUS 12

La gestione degli interrupt nel PIC16F877A Il PIC16F877A ha 3 possibili sorgenti di interrupt, comuni a tutti i microcontrollori della famiglia PIC16, più tutti quelli che possono essere generati dai periferici specifici dell 877. Quelle comuni a tutta la famiglia sono: Interrupt esterni dal pin RB0/INT. Interrupt su cambiamento di livello su almeno uno degli ingressi RB7:RB4. Interrupt su overflow del counter/timer TMR0. Possono poi generare interrupt il convertitore A/D, la porta seriale, i timer 1 e 2, i moduli Compare/Capture ecc., per un totale complessivo di 14 sorgenti. Non esistono interrupt non mascherabili: tutti gli interrupt sono filtrati da un flag di abilitazione generale degli interrupt GIE (bit 7 del registro INTCON), che di default è basso (interrupt disabilitati). La logica degli eventi legati a un interrupt è la seguente: ogni periferica che può chiedere interruzione ha un flag d interruzione che si alza quando si verificano le condizioni previste per l interrupt; la CPU testa i flag d interruzione dei periferici ogni ciclo Q1 del clock, cioè all inizio del ciclo macchina(vedi Figura 9): Figura 9 Temporizzazione degli interrupt (esempio dell interrupt esterno su RB0/INT) Se ne trova uno alto (e se è quel periferico è abilitato a generare interrupt), l indirizzo di rientro viene salvato sullo stack hardware, e il PC caricato con 0004h, locazione dove di conseguenza la CPU salta, con un ritardo (tempo di latenza) di 2 3 cicli macchina. In 0004h deve iniziare la routine di gestione, o un salto alla routine di gestione. 13

Il vettore d interruzione è pertanto unico: è il software che può (di solito deve) individuare quale periferico tra quelli abilitati ha chiesto interrupt interrogando (polling) i loro flag d interruzione. Quando il PIC risponde a un interruzione, ulteriori interrupt vengono disabilitati dall abbassamento automatico di GIE. E possibile, anche se non molto raccomandabile, permettere la nidificazione degli interrupt, rialzando GIE (che è scrivibile come tutti i flag di INTCON) all interno della routine di gestione. Al rientro dall interruzione il flag GIE si rialza automaticamente, riabilitando le interruzioni. E assolutamente importante ricordare che il flag d interruzione della periferica non si riabbassa automaticamente: poiché il flag alzato che causa interrupt, ci si deve ricordare di abbassarlo all interno della routine di gestione di solito alla fine della routine per impedire che al rientro l interrupt riparta immediatamente, innescando un loop infinito. Il meccanismo degli interrupt non è semplicissimo da gestire; per capirlo meglio si può vedere la Figura 10, che lo traduce in uno schema logico: Figura 10 Logica di interruzione (nell 877A manca la coppia di flag GPIF/GPIE) La richiesta di interrupt alla CPU è un livello alto all uscita dell ultimo AND a destra, da cui si vede che è inibita da GIE = 0 14

Ogni periferico ha un flag d interruzione, che si alza comunque sempre quando si verificano le condizioni che potrebbero chiedere interrupt, e un flag di abilitazione specifico. A titolo d esempio, consideriamo gli interrupt dalla linea RB0/INT: il flag d interruzione è INTF e quello d abilitazione è INTE. Dallo schema si vede che perché la CPU senta l innalzamento di INTF, non basta che sia alto GIE, ma deve essere alto anche INTE: i flag di abilitazione specifici dei periferici permettono quindi di selezionare quali di essi possono chiedere interrupt. La richiesta d interrupt da parte dei periferici specifici dell 877A è generata dalla combinazione ANDs OR a sinistra, e si vede che perché tale richiesta passi è necessaria un ulteriore abilitazione rappresentata dal flag PEIE (PEripheral Interrupt Enable). Nel registro principale collegato agli interrupt INTCON ci sono i flag di interrupt e abilitazione interrupt delle tre sorgenti di base, oltre a GIE e PEIE (Figura 11). Figura 11 Di INTF e INTE abbiamo già detto. TMR0IF/TMR0IE è la coppia flag d interrupt/flag d abilitazione interrupt relativa al Timer 0, RBIF/RBIE quella relativa all interrupt da cambiamento su RB7:RB4. Le coppie relative agli altri periferici sono sparse in altri registri specializzati. Vedere la documentazione relativa ai singoli periferici. Notare che anche INTCON è mappato in tutti i banchi di memoria. Riassumendo, la programmazione degli interrupt richiede i seguenti passi: In fase di inizializzazione: 1. Alzare il flag di abilitazione interruzione della periferica e, se la periferica è esterna al core, anche PEIE 2. Se non si è sicuri dello stato del flag d interruzione della periferica, abbassarlo per sicurezza 3. Alzare il flag di abilitazione generale GIE Nella routine d interruzione, che deve iniziare in 0x0004: 1. Salvare STATUS e W in una coppia di registri opportunamente definiti 2. Se le sorgenti d interruzione possono essere più d una, controllare i flag d interruzione per stabilire quale periferico ha chiamato, ed eseguire di conseguenza le operazioni richieste dalla gestione del periferico stesso (se il periferico che può chiamare interruzione è uno solo, il controllo sui flag necessario) 3. Abbassare il flag d interruzione del perififerico chiamante 4. Recuperare STATUS e W 5. Rientrare dall interrupt (istruzione RETFIE) Ecco un esempio abbastanza generale e completo di gestore di interrupt, che può essere facilmente adattato ad esigenze specifiche. Si suppone che possano chiamare interrupt il Timer 0 (flag d interruzione TMR0IF) e l ingresso RB0/INT (flag d interruzione INTF). Le routine di servizio dei due interrupt sono rispettivamente sbr_1 e sbr_2 (che devono terminare con l istruzione di rientro 15

da subroutine RETURN). Ricordare che il tutto deve essere allocato in 0x004 (mediante una direttiva org 0x004): interr MOVWF W_TEMP ; Salva w SWAPF STATUS,W ; e lo stato MOVWF STATUS_TEMP ; (il perché di questa complicazione lo si ; capisce al rientro) BTFSC INTCON,TMR0IF ; Se è interrupt da Timer 0 CALL sbr_1 ; chiama sbr_1 e rientra BTFSC INTCON,INTF ; Se è (anche) interrupt da RB0/INT CALL sbr_2 ; chiama sbr_2 e rientra BCF INTCON,TMR0IF ; Abbassa i BCF INTCON,INTF ; flag d'interruzione (in realtà solo ; uno sarà di solito alto) SWAPF STATUS_TEMP,W ; Recupera MOVWF STATUS ; lo stato SWAPF W_TEMP,F ; e poi W (con una finezza per evitare di ; alterare il flag Z, SWAPF W_TEMP,W ; che è influenzato da movf, ma non da swapf) RETFIE ; Ritorna dall'interrupt Al solito fare riferimento a Descrizione delle istruzioni per i particolari. Ricordare in ogni caso che BTFSC scavalca l istruzione successiva se il bit specificato del registro specificato è basso, e che CALL è la chiamata a subroutine. Così il segmento di programma BTFSC INTCON,TMR0IF CALL sbr_1 scavalca l istruzione CALL sbr_1 se il flag TMR0IF del registro INTCON è basso, cioè Timer 0 non ha chiamato interrupt; se invece il flag è alto, cioè è Timer 0 ad aver chiamato, l istruzione di chiamata viene eseguita e la CPU salta ad eseguire sbr_1. Notare che il sistema permette di gestire interrupt che si sovrappongano, stabilendo fra essi una scala di priorità: se, mentre la CPU sta rispondendo all interrupt ad Timer 0, si verifica la condizione per un interrupt sull ingresso RB0/INT, il flag INTF si alza (indipendentemente da ogni abilitazione), di modo che, al rientro da sbr_1, il test BTFSC INTCON, INTF lo trova alto e chiama sbr_2 3. Notare che l inverso non avviene: se, durante l esecuzione di sbr_2 in risposta all interrupt da RB0/INT, si verifica una richiesta da Timer 0, questa viene ignorata, perché TMR0IF non viene più interrogato. Se si vuole escludere la possibilità di sovrapposizione, si può modificare il gestore così: interr MOVWF W_TEMP ; Salva w SWAPF STATUS,W ; e lo stato MOVWF STATUS_TEMP ; (il perché di questa complicazione lo si ; capisce al rientro) BTFSC INTCON,TMR0IF ; Se è interrupt da Timer 0 GOTO sbr_1 ; chiama sbr_1 BTFSC INTCON,INTF ; Se è interrupt da RB0/INT GOTO sbr_2 ; chiama sbr_2 ret BCF INTCON,TMR0IF ; Qui al rientro da sbr_1 o sbr_2: abbassa i BCF INTCON,INTF ; flag d'interruzione (in realtà solo ; uno sarà di solito alto) SWAPF STATUS_TEMP,W ; Recupera MOVWF STATUS ; lo stato SWAPF W_TEMP,F ; e poi W (con una finezza per evitare di ; alterare il flag Z, SWAPF W_TEMP,W ; che è influenzato da movf, ma non da swapf) RETFIE ; Ritorna dall'interrupt 3 Ciò non è in contraddizione con il fatto che GIE è basso e inibisce uleriori interrupt: la CPU è già in interrupt, quando legge il secondo flag d interruzione 16

Qui sbr_1 e sbr_2 vengono chiamate da un salto incondizionato GOTO, e devono terminare con GOTO ret, in modo da rientrare entrambe nello stesso punto, senza la possibilità che vengano eseguite entrambe. Una spiegazione a parte richiede lo strano modo di salvare W e STATUS, e di recuperarli al rientro. Anzitutto, i registri W_TEMP e STATUS_TEMP è bene che vadano definiti nella fascia di indirizzi 0x70 0x7F, che si specchia in tutti i quattro blocchi RAM, in modo da non doverci preoccupare di che banco è indirizzato quando si verifica l interrupt. In secondo luogo, la soluzione che parrebbe ovvia: ; ; ; ; MOVWF W_TEMP MOVF STATUS,W MOVWF STATUS_TEMP ; Salvataggio MOVF STATUS_TEMP,W ; Recupero MOVWF STATUS MOVF W_TEMP,W non va bene, perché le istruzioni MOVF STATUS_TEMP e W MOVF W_TEMP,W toccano il flag di zero (vedi Descrizione delle istruzioni) e quindi possono alterare lo stato ripristinato (il flag di zero fa parte di STATUS). L unica istruzione che può copiare un registro RAM in W senza alterare i flag è SWAP (con destinazione W), che però lo fa invertendo i nibble (blocchi di 4 bit) alto e basso: basta allora usarla complessivamente due volte per ogni salvataggio/recupero. La sequenza corretta è allora 4 : ; ; ; ; MOVWF W_TEMP SWAPF STATUS,W MOVWF STATUS_TEMP ; Salvataggio SWAPF STATUS_TEMP,W ; Recupero MOVWF STATUS SWAPF W_TEMP,F SWAPF W_TEMP,W Un ultima precauzione riguarda la disabilitazione generale degli interrupt da programma: la soluzione ovvia BCF INTCON,GIE è rischiosa; se interviene un interrupt durante l esecuzione di questa istruzione, questa viene terminata prima del salto a 0x0004. A questo punto GIE è basso, ma la CPU ha già accettato l interrupt (nella prima fase di T CY ), per cui salta alla routine di gestione, al rientro della quale GIE viene alzato di nuovo. Si va sul sicuro controllando che effettivamente GIE sia andato basso ed eventualmente riprovandoci: riprova BCF INTCON,GIE BTFSC INTCON,GIE GOTO riprova Le porte di I/O digitale 5 4 Se nel programma si tocca PCLATH (perché si deve caricare nel Program Counter un indirizzo > FFh o saltare oltre l'indirizzo 7FFh, vedi paragrafo Il puntamento delle istruzioni) bisogna ricordarsi di salvare anche questo registro (e poi ripristinarlo). Altrimenti, nel corso della gestione dell'interrupt o al ritorno da esso, possono verificarsi salti errati e imprevedibili. Vedere a titolo d'esempio le macro pop e push nel programma PWM_2.asm. 5 La traduzione corretta dell inglese PORT sarebbe PORTO, e non PORTA. Ma qui ci conformiamo alla terminologia più diffusa. 17

Il PIC16F877A ha 5 porte di I/O digitale, indicate con PORTA, PORTB, PORTC, PORTD, PORTE, che costituiscono le connessioni del microcontrollore con il mondo esterno. PORTB, PORTC e PORTD sono dotate di 8 linee di I/O, PORTA di 6 e PORTE solo di 3, per un totale di 33 linee. Tutte le linee sono programmabili, singolarmente e indipendentemente, come input o come output e possono essere usate per scopi generici di I/O. Molte di loro sono però in comproprietà con i periferici specializzati (porta seriale, convertitore A/D, comparatori, timer ecc., vedi sui datasheet la documentazione dei vari periferici, nonché quella sui port di I/O). Tre linee di PORTB servono in programmazione (Vedi paragrafo sulla Programmazione del PIC). Alcune linee hanno particolarità elettriche specifiche (pull-up interno, 3-state, capacità analogiche ecc); tutte però possono comunque essere utilizzate in modalità TTL-compatibile con correnti di sink/source relativamente alte (25 ma) 6. Le porte sono viste come registri d archivio RAM (figura 5), i cui bit corrispondono alle linee di I/O (nel caso di PORTA e PORTE corrispondono alle linee esterne rispettivamente i 6 e 3 bit più bassi, gli altri non hanno significato). Sulle porte sono possibili due operazioni: Scrittura La scrittura di un byte su una porta ha l effetto di emettere i livelli corrispondenti in uscita sulla linee dichiarate in output (a un bit 1 corrisponde un livello alto, a un bit 0 quello basso), mentre non ha effetto sulle linee dichiarate in input. Lettura La lettura di un byte da una porta legge i livelli in ingresso alle linee dichiarate in input ( livello alto = 1, livello basso = 0), e i livelli scritti precedentemente su quelle dichiarate in output (queste ultime sono dotate di latch, di modo che le uscite permangono sull ultimo valore scritto, fino a una nuova scrittura). Per dichiarare le linee di una porta in ingresso o in uscita, è necessario scrivere una maschera di un byte in un registro di controllo corrispondente. I bit posti a 1 corrispondono a linee in ingresso, quelli posti a 0 a linee in uscita. Ogni porta PORTx ( x = A, B, C, D o E) ha un registro di controllo associato TRISx. Le porte sono mappate nel blocco 0 di registri RAM, i registri di controllo le affiancano nel blocco 1: ad esempio, PORTA ha indirizzo 005h = 000000000101b e il suo registro di controllo TRISB è in 085h = 000010000101b (figura 5). Notare che PORT B e TRISB hanno una seconda mappatura, rispettivamente nel blocco 2 e nel blocco 4. Ad esempio, la sequenza di operazioni: BSF STATUS,RP0 ; TRISD è in banco 1 MOVLW 1111000 b MOVWF TRISD BCF STATUS,RP0 ; Torna in banco 0 manda le quattro linee superiori di PORTD (RD7 RD4) in input, e le quattro inferiori (RD3 RD0) in input. Di default (all accensione o dopo un reset), tutte le linee di tutte le porte sono in input. La direzione delle linee può essere riprogrammata in ogni momento nel 6 Le linee RA0, RA1, RA2, RA3, RA5 di PORTA e tutte le tre linee di PORTE sono di default ingressi analogici. Per usarle come I/O digitali bisogna configurare opportunamente i bit PCFG3:PGFG0 del registro ADCON1 (vedi fig. 18 e successiva tabella, paragrafo sul convertitore AD). Ricordare che AN7, AN6, AN5 corrispondono a RE2, RE1, RE0 e AN4, AN3, AN2, AN1, AN0 a RA5, RA3, RA2, RA1, RA0. 18

corso del programma; è un opportuna avvertenza programmare (lasciare) in input le linee non utilizzate, per evitare il rischio di cortocircuiti accidentali 7. A parte il fatto che, come si è detto, numerose linee di I/O sono multiplexate sui periferici interni, le principali differenze fra le varie linee sono: RA4 (linea 4 di PORTA) in ingresso è dotata di trigger di Schmitt, in uscita è opendrain (sullo 0 è connessa a massa, ma su 1 è aperta). Le linee di PORTB in ingresso possono avere un pull-up interno, attivabile abbassando il bit RBPU (il 7) del registro di configurazione OPTION_REG (che è mappato nei blocchi 1 e 3, rispettivamente agli indirizzi 081h e 181h, vedi più avanti figura 13). Di default i pull-up sono disabilitati. RB0 (linea 0 di PORTB), quando usata come ingresso di interrupt (INT), e dotata di trigger di Schmitt. Le linee di PORTD in ingresso sono dotate di trigger di Schmitt (ma NON quando PORTD viene usata come Parallel Slave Port, porta parallela asservita). Le tre linee di PORTD sono anch'esse di tipo trigger, quando lavorano in input. Le periferiche Qui di seguito diamo una descrizione sintetica delle periferiche più importanti del PIC16F877A. Per ulteriori chiarimenti vedere i listati dei programmi d'esempio. Iniziamo da una periferica che fa parte del "core" del microcontrollore, e che è presente in tutti i microcontrollori della famiglia PIC16: Il Timer 0 E' un contatore in avanti a 8 bit, che può essere scritto e letto in ogni momento, dotato di prescaler programmabile via software. Il clock può essere interno (Fosc/4, cioè stesso periodo del ciclo macchina T CY ) o esterno (pin RA4/T0CKI). Può generare interrupt nell'overflow da FFh a 00h. Con il clock interno può servire da temporizzatore (timer), con quello esterno funziona da contatore (counter) di eventi. Lo schema a blocchi semplificato del complesso Timer 0/Prescaler è riportato in figura 12. 7 Se si va a vedere la struttura interna di una linea di I/O (riferirsi ai datasheets), ci si accorge che i latch di uscita sono a monte dei gate comandati dai registri TRIS: ciò significa che un PORT può essere scritto anche se non è orientato (totalmente o parzialmente) in uscita; quando eventualmente il corrispondente registro TRIS viene riprogrammato, i valori memorizzati nel latch escono sulle linee che vengono mandate in output. Questo vuol dire tra l'altro che una linea di output può essere mandata in 3-state riprogrammandola in input, senza che ciò comporti la perdita del dato in uscita, che "ricompare" quando la linea viene riportata in output. Tuttavia, va fatta attenzione all'uso sui PORT delle istruzioni che leggono-modificano-scrivono, come quelle logico/aritmetiche con destinazione il registro stesso (ad es. ANDWF PORTB,F) o quelle che modificano singoli bit (BCF e BSF). Supponiamo ad esempio di utilizzare BSF per settare una linea di uscita di un PORT: il problema è che la CPU prima legge il byte dal PORT, poi cambia il bit indicato, poi riscrive sul PORT il byte modificato; ma in corrispondenza delle linee dichiarate in ingresso i bit letti sono quelli che arrivano dall'esterno, e sono questi che vengono poi riscritti nel latch d'uscita, eventualmente modificando i valori precedenti. Se la programmazione delle linee non cambia, non c'è problema; ma se una linea che prima era in ingresso viene portata in uscita, il valore che su essa compare è imprevedibile. Evitare quindi di usare questo tipo d'istuzioni su PORT che debbono cambiare direzione nel corso dell'esecuzione del programma! 19

Figura 12 Schema semplificato del Timer 0 Il registro di conteggio è TMR0 (mappato agli indirizzi 001h e 101h) e, come si è detto, e di lettura/scrittura; la configurazione del contatore/timer dipende dai bit <0:5> del registro di opzioni OPTION_REG (figura 13): Figura 13 Registro OPTION_REG (indirizzi 081h, 181h) TOCS seleziona la sorgente di clock: 0 = clock interno sul ciclo-macchina, 1 = clock esterno da RA4/T0CKI (default). TOSE seleziona il fronte attivo del clock esterno: 0 = discesa, 1 = salita (default). PSA attribuisce il prescaler al Timer 0 (1) o al Watchdog Timer (1, default). PS2, PS1, PS0 selezionano il rapporto di divisione del prescaler, secondo la tabella qui sotto riportata. 8 Tabella 1 Rapporto divisione prescaler TMR0 (e WDT) (i bit PS2:PS0 sono i bit 2:0 del registro OPTION_REG) PS2:PS0 Rapporto div. TMR0 Rapporto div. WDT 000 1:2 1:1 001 1:4 1:2 010 1:8 1:4 011 1:16 1:8 100 1:32 1:16 101 1:64 1:32 110 1:128 1:64 111 1:256 1:128 8 Gli altri due bit di OPTION_REG sono RPBU, che è il flag di pull-up su PORTB (di cui si è già parlato) e INTDEG, che seleziona il fronte di salita/discesa per l'interrupt da RB0/INT (vedi esempi di programmazione). 20

Notare da figura 13 che,quando il prescaler è attribuito al Watchdog Timer, il Timer 0 prende direttamente il clock dalla sorgente (interna o esterna), con un rapporto di divisione che è quindi 1:1. Di per sé, il Timer 0 è sempre abilitato, di default sul clock esterno: si disabilita solo quando la CPU va in sleep. Al timer sono associati anche due flag del registro di configurazione interrupt INTCON, di cui abbiamo già parlato (vedi figura 11): TMR0IF che è il flag d'interruzione che si alza quando il timer "trabocca" (overflow) da 256 (FFh) a 0. Come tutti i flag d'interruzione non si riabbassa da solo, e va resettato prima di ritornare dall'interruzione. TMR0IE che abilita la richiesta d'interruzione da parte del timer (ma non blocca TMR0IF, che si alza comunque all'overflow, rivedere il paragrafo sulla Gestione degli interrupt). Lo stato attuale del conteggio può essere letto da TMR0 in qualunque momento. TMR0 può anche essere scritto,cioè inizializzato a un valore, ma si deve tener presente che una scrittura inibisce l'incremento di contatore e prescaler di due cicli macchina T CY ; inoltre, se il prescaler è assegnato al Timer 0, una scrittura lo azzera. Ad esempio, se il prescaler (assegnato al timer) è su un rapporto di divisione 1:2 e il clock è interno, una scrittura su TMR0 ritarda l'incremento del contatore di 4 T CY (due di inibizione e due di incremento del prescaler, che riparte da 0). In sostanza, dopo la scrittura di N, con un rapporto r impostato nel prescaler, l'overflow è il conseguente innalzamento di T0IF avviene dopo a un tempo. Il ritardo massimo (con il prescaler su 256:1) è comunque 256 256 = 65536 cicli macchina T CY (63.536 ms con un quarzo da 4 Mhz). Quando il viene usato il clock esterno (funzione di conteggio), senza prescaler deve essere rispettato un tempo minimo di durata dell'impulso (sia per la fase alta che per quella bassa) di 0.5T 20 ns ; con il prescaler questo tempo può scendere a 10ns, ma il periodo deve in ogni caso essere almeno N fattore di prescaling ( 2 256 ). Per i particolari vedere i datasheet. CY T CY 40 ns, con N Il Timer 1 E' un timer/contatore a 16 bit, il cui conteggio è allocato in due registri TMR1H (byte alto, indirizzo 00Eh) e TMR1L (byte basso, indirizzo 00Fh). Il conteggio va da 0000h a FFFFh (up) e riparte da 0000h. L'overflow è segnalato dall'innalzarsi del flag d'interruzione TMR1IF, bit 0 del registro PIR1 (00Ch). La richiesta di interrupt è "filtrata" attraverso il flag di abilitazione specifico TMR1IE (bit 0 di PIE1, 08Ch) e quello PEIE di abilitazione interrupt dai periferici esterni al core (che sta in INTCON), nonché ovviamente da GIE. Valgono peraltro tutte modalità relative agli interrupt e alla loro gestione (vedi paragrafo relativo). Timer 1 può funzionare da temporizzzatore (con il ciclo macchina T CY usato come clock, o come contatore su un clock esterno(fronti di salita su RC0/T1OSO/T1CKI); nella modalità contatore può anche funzionare come temporizzatore con il clock (asincrono rispetto al clock di sistema) dato da un quarzo connesso fra RC0/T1OSO/T1CKI e RC1/T1OSI/CCP2. Timer 1 è dotato di un prescaler con rapporto di divisione da 1:1 a 1:8; a titolo d'esempio, con una frequenza di clock tipica di 4 Mhz e quindi T CY = 1µs, il prescaler al massimo e il conteggio che parte da 0 si ottiene un ritardo massimo di 8 2 16 µs = 524288 µs (poco più di mezzo secondo). Il timer può essere resettato dai moduli CCP (vedi) ed è controllato in programmazione dal registro T1CON (figura 14): 21

Qui di seguito la funzione dei singoli bit: Figura 14 Registro di controllo del Timer 1 T1CKPS1, T1CKPS0 fissano il rapporto di divisione del prescaler: 00 1 (default) 01 2 10 4 11 8 T1OSCEN T1SYNC TMR1CS abilita (1)/disabilita (0, default) l'oscillatore a quarzo esterno. controlla la sincronizzazione del clock esterno su quello di sistema (vedi più sotto). Ignorato se TMR1CS = 0. seleziona la sorgente di clock esterna (1) o interna su F OSC /4 (0, default). TMR1ON portato a 1 abilita il Timer 1. Di default il timer è disabilitato (0). L'uso di Timer 1 è immediato come temporizzatore: la lettura/scrittura dei registri di conteggio può avvenire in qualunque momento, perché il conteggio (come l'operazione stessa) è sincronizzato su T CY. Lo stesso accade nell'uso come contatore se T1SYNC 0, perché l'incremento del conteggio è determinato dal fronte di salita del segnale esterno, ma viene ritardato in modo da essere sincrono con T CY (ciò significa che l'impulso di clock esterno deve durare più di 2T CY ). Con il clock esterno e in modo asincrono T1SYNC 1 le cose si complicano, perché tra l'accesso al registro TMR1H e quello atmr1l il contatore può andare in overflow. La cosa più semplice è fermare temporaneamente il conteggio abbassando TMR1ON immediatamente prima dell'accesso e rialzandolo subito dopo. Naturalmente ciò introduce un ritardo nel conteggio di almeno 4T CY (due per l'accesso ai registri è due per l'abilitazione/disabilitazione). Il modo di conteggio asincrono su clock esterno è l'unico in cui Timer 1 funziona anche nello stato di sleep. Il Timer 2 E' un timer che conta i cicli macchina T CY all'indietro (down counter). Al solito il registro di conteggio (TMR1, indirizzo 011h) è leggibile/scrivibile, ma la caratteristica più importante del timer è quella di essere dotato di un registro a 8 bit di periodo (PR2, indirizzo 092h), anch'esso leggibile/scrivibile. Quando il conteggio in TMR1 eguaglia il contenuto di PER2, un comparatore genera un segnale di reset per il contatore, che riparte da 0 (vedi figura 15): 22

Figura 15 Struttura di Timer 2 Quindi PR2 determina in effetti il periodo del ciclo di conteggio: dopo r POST cicli di conteggio, dove r POST, con 1 r POST 16, è il valore del rapporto di divisione di un postcaler (vedi sempre figura 15), si alza il flag di interrupt TRM2IF (che è soggetto alle solite condizioni sui flag di interrupt). Il timer è anche dotato di un prescaler che divide il clock (ciclo macchina) per un rapporto di divisione. Il reset del timer (TMR2 Ouput in figura 15), che non è accessibile all'esterno, può essere scelto via software come base dei tempi del modulo SSP (Synchronous Serial Port, Porta Seriale sincrona). Timer 2 è controllato dal registro T2CON (figura 16) Figura 16 Registro di controllo del Timer 2 TOUTPS3:TOUTPS0 selezionano il rapporto di divisione del postscaler d'uscita: il rapporto (da 1 a 16) è il valore espresso in binario dai quattro bit, aumentato di 1. Default 1. TMR2ON abilita (1) / disabilita (0, default) il timer. T2CKPS01, T2CKPS0 selezionano il rapporto di divisione del prescaler: 00 1 01 4 1x 16 Il flag d'interruzione TMR2IF e il rispettivo flag di abilitazione TMR2EI stanno rispettivamente nei registri PIR1 e PIE1, gli stessi dei flag di Timer 1. 23

Il convertitore A/D Il convertitore analogico/digitale del PIC16F877A è un convertitore ad approssimazioni successive con risoluzione a 10 bit (1 parte su 1024) e 8 ingressi multiplexati. Il suo range d'ingresso è fissato da due tensioni di riferimento V REF+ e V REF-, che possono essere fissate dall'esterno nel range V SS V REF- < V REF+ V DD (sacrifivando uno o due ingressi), o posti internamente a V REF+ = V DD e V REF- = V SS (di solito V REF- = V SS = 0V). In ogni caso la relazione fra la tensione in ingresso V i e il dato numerico a 10 bit N restituito dal convertitore è 9 : V i V REF V 1024 REF N V REF il convertitore è dotato di circuito di track & hold interno. Il risultato della conversione viene posto in una coppia di registri ADRESH (byte alto, indirizzo 01Eh ) e ADRESL (byte basso, indirizzo 09Eh). Al convertitore sono associati anche due registri di controllo ADCON0 (indirizzo 01Fh) e ADCON1 (indirizzo 09Fh), i cui bit configurano le varie opzioni del convertitore e gestiscono l'inizio e la fine della conversione (figura 17 e figura 18): Figura 17 Registro ADCON0 ADCS2:ADCS0 selezionano il clock del convertitore (ADCS2 è in ADCON1, vedi oltre): 000 = F OSC /2 (F OSC frequenza clock di sistema) 001 = F OSC /8 010 = F OSC /32 011 = F RC (oscillatore resistenza/capacità interno al modulo AD) 100 = F OSC /4 101 = F OSC /16 110 = F OSC /64 111 = F RC (come 011) La frequenza prescelta determina il tempo di conversione, che è 12T AD, dove T AD è il periodo del clock del convertitore. D'altra parte T AD deve essere almeno 1.6 µs; ad esempio, con un clock di sistema di F OSC = 4 MHz, F OSC /8 = 500 khz va bene, perché dà T AD = 1/(500 khz) = 2 µs, ma F OSC /2 = 1 MHz no, perché T AD = 1µs. Il clock a oscillatore RC interno (F RC ) dà un T AD variabile da 2 µs a 6µs, tipico di 4 µs. La sua scelta permette di far funzionare il convertitore con la CPU in stato di sleep, che è una soluzione ottimale per la precisione della conversione, perché sono "spente" le sorgenti di transienti e quindi di possibili disturbi (clock di sistema, timer, ecc.). CHS2:CHS0 selezionano il canale d'ingresso da convertire: 000 = canale 0 (RA0/AN0) 001 = canale 1 (RA1/AN1) 010 = canale 2 (RA2/AN2) 011 = canale 3 (RA3/AN3) 100 = canale 4 (RA5/AN4) 9 Notare che il fondo scala V REF+ non è raggiungibile, perché il valore massimo possibile per N è 1023. 24