Assembly Avanzato con MASM

Capitolo 4: La memoria CMOS e il Real Time Clock


Quando accendiamo un vecchio PC di classe XT, possiamo notare che l'orologio/calendario viene fatto partire dalle ore 00:00:00 del 01/01/1980; tale inizializzazione viene effettuata dal DOS ed è basata su un preciso standard seguito dal SO.
Ogni SO utilizza, infatti, una data di riferimento che prende il nome di epoch time; una qualunque data precedente a quella di riferimento viene considerata non valida dallo stesso SO.
La Figura 4.1 illustra l'epoch time di alcuni SO. Sui PC di classe XT possiamo aggiornare l'orologio/calendario ai valori correnti attraverso i due comandi TIME e DATE forniti dal DOS; provando, però, a riavviare il PC, possiamo constatare che le nostre impostazioni vengono perse e l'orologio/calendario riparte nuovamente dalle 00:00:00 del 01/01/1980!
Ciò accade in quanto, sui PC di classe XT, l'orologio/calendario non è un vero dispositivo hardware capace di aggiornare la data e l'ora anche a computer spento; si tratta di una semplice simulazione software, gestita dal DOS, che costringe l'utente ad aggiornare manualmente le impostazioni standard ad ogni riavvio!

Questo problema è stato risolto con l'avvento dei PC di classe AT; a tale proposito, la IBM ha scelto un dispositivo che, in origine, era rappresentato dal famoso chip Motorola MC146818A RTC + RAM.
Attualmente vengono utilizzati anche dei chip alternativi, del tutto compatibili con il MC146818A; si possono citare, ad esempio, il Texas Instruments bq4285 e il Dallas Semiconductors DS12885.

4.1 Funzionamento del dispositivo RTC + RAM

Nel seguito del capitolo, con il termine RTC + RAM verrà indicato uno dei tanti dispositivi dell'ultima generazione che sostituiscono il MC146818A; quando sarà necessario, verranno illustrate le eventuali differenze con lo stesso MC146818A.

La Figura 4.2 illustra lo schema a blocchi semplificato di un dispositivo RTC + RAM. Agli ingressi X1 e X2 viene collegato un quarzo che "vibra" alla frequenza di 32768 kHz; da questa vibrazione, il dispositivo Oscillatore ricava una frequenza perfettamente stabile, che verrà utilizzata come segnale periodico di riferimento.

Il dispositivo Divisore di frequenza, permette di dividere la frequenza del segnale periodico di riferimento; in questo modo, si ottengono segnali periodici a frequenze sottomultiple di quella di riferimento, utilizzabili per gestire le funzionalità SQW, INT e Data/Ora del dispositivo RTC + RAM.
Il dispositivo Generatore di onda quadra permette di generare segnali ad onda quadra (sull'uscita SQW) la cui frequenza può essere scelta dal programmatore; generalmente, l'uscita SQW rimane inutilizzata sui PC.
Il dispositivo Generatore di interrupt permette la generazione di interruzioni hardware sull'uscita INT; è possibile programmare le interruzioni in modo che esse si verifichino in seguito ad un preciso evento, oppure ad intervalli regolari di tempo.
Il dispositivo Aggiornamento data/ora riceve dal Divisore di frequenza un segnale periodico a 1 Hz (1 ciclo al secondo) attraverso il quale viene continuamente aggiornato l'orologio/calendario interno; in sostanza, ad intervalli di 1 secondo, viene effettuato l'aggiornamento di svariate informazioni che comprendono: ore, minuti, secondi, giorno, mese, anno, etc.

Tutte le informazioni relative all'orologio/calendario, vengono sistemate in una apposita memoria RAM realizzata in tecnologia CMOS; in Figura 4.2, l'area riservata all'orologio/calendario è quella colorata in verde (User Buffer) ed ha una capienza di 14 byte.
Sono presenti anche ulteriori 114 byte (Storage Registers) destinati alla memorizzazione di informazioni relative alla configurazione hardware del PC (area colorata in rosso in Figura 4.2); a tale proposito, si veda anche il paragrafo 2.4 del Capitolo 2, sezione Assembly Avanzato.

Complessivamente, la RAM-CMOS del dispositivo di Figura 4.2 ha una capienza pari a 14+114=128 byte; nel dispositivo originario MC146818A, invece, la RAM-CMOS aveva una capienza pari a 14+50=64 byte.

A computer spento, la conservazione delle informazioni descritte in precedenza e l'aggiornamento dell'orologio/calendario, vengono garantiti da una batteria ricaricabile che alimenta continuamente il dispositivo RTC + RAM; tale batteria risulta collegata ai pin +3V e Gnd di Figura 4.2.

4.2 Struttura dell'area User Buffer della RAM-CMOS

L'area User Buffer della RAM-CMOS ha una capienza di 14 byte e risulta suddivisa in 14 locazioni da 1 byte ciascuna; la Figura 4.3 illustra le informazioni contenute in queste 14 locazioni. Ad intervalli di 1 secondo, il dispositivo Aggiornamento data/ora incrementa di 1 il contenuto del campo Secondi; se il campo Secondi raggiunge il valore 60, viene azzerato e viene incrementato di 1 il campo Minuti.
Se il campo Minuti raggiunge il valore 60, viene azzerato e viene incrementato di 1 il campo Ore; se il campo Ore raggiunge il valore 24, viene azzerato e vengono incrementati di 1 i campi Giorno della Settimana e Giorno del Mese ... e così via.

Il campo Giorno della Settimana varia da 1 a 7 con il valore 1 che indica la Domenica; il campo Giorno del Mese varia da 1 a 31 (il dispositivo RTC + RAM gestisce automaticamente le diverse lunghezze dei mesi e l'aggiunta di un giorno al mese di Febbraio negli anni bisestili).
Il campo Mese varia da 1 a 12; il campo Anno varia da 00 a 99 e indica solamente le due cifre meno significative del valore a 4 cifre che rappresenta l'anno.

In Figura 4.3 possiamo notare anche la presenza dei tre campi Allarme Secondi, Allarme Minuti e Allarme Ore; come vedremo più avanti, questi tre campi permettono di generare un evento (allarme) non appena si verifica la condizione:
Ore:Minuti:Secondi = Allarme Ore:Allarme Minuti:Allarme Secondi
Gli ultimi 4 byte dell'area User Buffer sono riservati ai cosiddetti Control/Status Registers (registri di stato e di controllo); come si intuisce dal nome, questi 4 registri sono molto importanti in quanto permettono all'utente di controllare (programmare) il dispositivo RTC + RAM. Gli stessi registri forniscono anche informazioni complete su tutto ciò che accade nel dispositivo; analizziamoli quindi in dettaglio.

4.2.1 Registro A

La Figura 4.4 illustra la struttura assunta dal Registro A. I bit RS0, RS1, RS2 e RS3 sono accessibili in lettura/scrittura e permettono di impostare, sia la frequenza del segnale ad onda quadra generato sull'uscita SQW, sia l'intervallo di tempo che intercorre tra una interruzione e quella successiva generate sull'uscita INT (per un tipo particolare di "interruzione periodica", illustrata più avanti); con 4 bit possiamo gestire sino a 24=16 impostazioni differenti le cui caratteristiche vengono illustrate in Figura 4.5. In fase di avvio del PC, il BIOS imposta questi 4 bit al valore 0110b; il valore 0000b disabilita l'output del Divisore di frequenza. Il bit UIP (Update cycle In Progress) è accessibile in sola lettura e indica se è in atto o meno la fase di aggiornamento dell'orologio/calendario; se UIP=1, l'orologio/calendario è in fase di aggiornamento, mentre se UIP=0, tale fase è terminata.
Se vogliamo accedere in lettura/scrittura all'orologio/calendario, dobbiamo prima attendere che si abbia UIP=0; in caso contrario, potremmo anche ottenere risultati errati!

4.2.2 Registro B

La Figura 4.6 illustra la struttura assunta dal Registro B. Il bit DSE (Daylight Saving Enable) permette di abilitare (1) o disabilitare (0) il passaggio automatico dall'ora solare all'ora legale e viceversa; se DSE viene posto a 1, durante l'anno si verificano i seguenti due eventi: Le convenzioni relative all'ora solare/legale non sono uguali per tutti i Paesi; purtroppo, però, le impostazioni del dispositivo RTC + RAM non sono modificabili. Proprio per questo motivo, la gestione del cambiamento dell'ora solare/legale viene lasciata al SO.

L'impostazione predefinita è DSE=0 (nessun cambio automatico dell'ora).

Il bit HF (Hour Format) permette di impostare l'orologio di 12 ore o di 24 ore; se HF=0, il campo Ore varia da 0 a 11 (AM e PM), mentre se HF=1, il campo Ore varia da 0 a 23.

L'impostazione predefinita è HF=1 (orologio di 24 ore).

Il bit DF (Data Format) permette di impostare il formato numerico usato per la rappresentazione della data e dell'ora; se DF=1, l'orologio/calendario viene rappresentato con numeri in formato binario (ad esempio, le 05:28:49 del 12/07/01), mentre se DF=0, l'orologio/calendario viene rappresentato con numeri in formato BCD (ad esempio, le 05h:28h:49h del 12h/07h/01h).

L'impostazione predefinita è DF=0 (numeri in formato BCD).

Il bit SQWE (SQuare Wave Enable) permette di abilitare (1) o disabilitare (0) l'uscita SQW del Generatore di onda quadra; come abbiamo visto in precedenza, la frequenza del segnale ad onda quadra può essere impostata attraverso i bit RS0, RS1, RS2 e RS3 del Registro A.
La Figura 4.7 illustra la forma di un tipico segnale ad onda quadra. L'impostazione predefinita è SQWE=0 (uscita SQW disabilitata).

Il bit UIE (Update cycle Interrupt Enable) permette di abilitare (1) o disabilitare (0) la generazione di un impulso INT al termine di ogni ciclo di aggiornamento dell'orologio/calendario; ricordando che la frequenza di aggiornamento dell'orologio/calendario è pari a 1 Hz, possiamo affermare che ponendo UIE=1 otteniamo la generazione di un impulso INT ogni secondo.
Il bit UIE si rivela molto utile per sapere quando possiamo accedere in lettura/scrittura all'orologio/calendario senza trovarci nel bel mezzo di un ciclo di aggiornamento; a tale proposito, dobbiamo porre UIE=1 e installare una nostra ISR che intercetta l'impulso INT. Quando la ISR riceve il controllo, siamo sicuri che la fase di aggiornamento dell'orologio/calendario è appena terminata; da quel momento, abbiamo a disposizione al massimo 999 ms per accedere in sicurezza alle informazioni relative all'orologio/calendario.

L'impostazione predefinita è UIE=0 (nessun impulso INT al termine del ciclo di aggiornamento dell'orologio/calendario).

Il bit AIE (Alarm Interrupt Enable) permette di abilitare (1) o disabilitare (0) la generazione di un impulso INT nel momento esatto in cui si verifica la condizione:
Ore:Minuti:Secondi = Allarme Ore:Allarme Minuti:Allarme Secondi
Il bit AIE ci permette quindi di svolgere un determinato compito allo scoccare di una precisa ora del giorno; a tale proposito, dobbiamo porre AIE=1, impostare i campi Allarme Ore, Allarme Minuti, Allarme Secondi e installare una nostra ISR che intercetta l'impulso INT.

L'impostazione predefinita è AIE=0 (nessun impulso INT al verificarsi della condizione di allarme).

Il bit PIE (Periodic Interrupt Enable) permette di abilitare (1) o disabilitare (0) la generazione di un impulso INT ad intervalli regolari di tempo (da cui il nome "periodic interrupt"); se PIE=1, viene generata una sequenza continua di impulsi INT ad intervalli di tempo che possiamo impostare attraverso i bit RS0, RS1, RS2, RS3 del Registro A.

L'impostazione predefinita è PIE=0 (nessun impulso INT periodico).

Il bit UTI (Update Transfer Inhibit) permette di abilitare (0) o disabilitare (1) la memorizzazione nella CMOS degli aggiornamenti relativi all'orologio/calendario; in sostanza, se poniamo UTI=1, l'orologio/calendario viene regolarmente aggiornato una volta al secondo, ma tali aggiornamenti non vengono memorizzati nell'area User Buffer della CMOS.
Ponendo UTI=1 (aggiornamento inibito), si ottiene automaticamente UIE=0 (nessun impulso INT automatico al termine del ciclo di aggiornamento).

L'impostazione predefinita è UTI=0 (aggiornamenti salvati nella CMOS).

4.2.3 Registro C

La Figura 4.8 illustra la struttura assunta dal Registro C. I 4 bit meno significativi del Registro C sono riservati e devono valere 0000b.

Il bit UF (Update event Flag) è accessibile in sola lettura e permette di sapere se il ciclo di aggiornamento dell'orologio/calendario è terminato; infatti, questo bit vale normalmente 0 e viene posto automaticamente a 1 al termine del ciclo di aggiornamento.
UF fornisce quindi un metodo alternativo (a UIE) per sapere quando possiamo accedere in sicurezza alle informazioni relative all'orologio/calendario; a tale proposito, non dobbiamo fare altro che monitorare continuamente il valore assunto dallo stesso UF.
La modifica di UF è automatica ed è indipendente dal valore assunto dal bit UIE (Update cycle Interrupt Enable); inoltre, una operazione di lettura del Registro C provoca l'azzeramento del bit UF.

Il bit AF (Alarm event Flag) è accessibile in sola lettura e permette di sapere se si è verificata la condizione:
Ore:Minuti:Secondi = Allarme Ore:Allarme Minuti:Allarme Secondi
Infatti, questo bit vale normalmente 0 e viene posto automaticamente a 1 quando si verifica la suddetta condizione.
La modifica di AF è automatica ed è indipendente dal valore assunto dal bit AIE (Alarm Interrupt Enable); inoltre, una operazione di lettura del Registro C provoca l'azzeramento del bit AF.

Il bit PF (Periodic event Flag) è accessibile in sola lettura e permette di sapere se è trascorso l'intervallo di tempo necessario per la generazione di una interruzione periodica (in base alle impostazioni illustrate in Figura 4.5); infatti, questo bit vale normalmente 0 e viene posto automaticamente a 1 ogni volta che trascorre il suddetto intervallo di tempo.
La modifica di PF è automatica ed è indipendente dal valore assunto dal bit PIE (Periodic Interrupt Enable); inoltre, una operazione di lettura del Registro C provoca l'azzeramento del bit PF.

In base a quanto è stato appena esposto, i tre bit UF, AF e PF vengono modificati automaticamente senza alcuna relazione con lo stato dei tre bit UIE, AIE e PIE; se, invece, siamo interessati proprio a tale relazione, possiamo servirci del bit INTF.
Il bit INTF (INTerrupt request Flag) è accessibile in sola lettura e viene posto automaticamente a 1 solamente quando si verifica una delle seguenti condizioni: Supponiamo, ad esempio, di aver posto UIE=1 (Update cycle Interrupt Enable); in questo modo, stiamo richiedendo la generazione di un impulso INT ogni volta che termina il ciclo di aggiornamento dell'orologio/calendario.
Non appena tale ciclo termina, si ottiene automaticamente UF=1; solo in questo caso si ha anche INTF=1. Se, invece, avessimo posto UIE=0, al termine del ciclo di aggiornamento avremmo ottenuto UF=1 e INTF=0!
La modifica di INTF è automatica; inoltre, una operazione di lettura del Registro C provoca l'azzeramento del bit INTF.

4.2.4 Registro D

La Figura 4.9 illustra la struttura assunta dal Registro D. I 7 bit meno significativi del Registro D sono riservati e devono valere 0000000b.

Il bit VRT (Valid Ram and Time) è accessibile in sola lettura e permette di sapere se la batteria di alimentazione ha una carica sufficiente per garantire la correttezza delle informazioni presenti nel dispositivo RTC + RAM; solamente quando VRT=1 (batteria sufficientemente carica) tali informazioni possono essere considerate attendibili.

4.2.5 Gestione dei diversi tipi di interruzione

Dalle considerazioni appena esposte risulta che il dispositivo RTC + RAM è in grado di generare tre tipi differenti di interruzione e cioè: Per gestire ciascun tipo di interrupt abbiamo a disposizione due metodi; il primo metodo è quello classico della ISR, mentre il secondo consiste nel "polling" del Registro C.

Il metodo della ISR è più impegnativo ma garantisce la massima efficienza possibile; per sfruttare tale metodo dobbiamo installare una nostra ISR in memoria, effettuare eventuali impostazioni sul dispositivo RTC + RAM e abilitare l'opportuno bit (UIE, AIE o PIE) nel Registro B.
Supponiamo, ad esempio, di voler richiedere la generazione di una interruzione periodica ogni 125 ms; a tale proposito, dopo aver installato in memoria la nostra ISR dobbiamo impostare il valore 1101b nei bit RS del Registro A e porre infine PIE=1 nel Registro B.

La ISR, ogni volta che riceve il controllo, deve svolgere un compito importantissimo che consiste nell'accedere in lettura al Registro C; come è stato spiegato in precedenza, tale operazione permette di azzerare tutti i bit del Registro C. Se non si svolge questo lavoro, le interruzioni rimangono disabilitate!

Il metodo del polling è più facile da applicare in quanto richiede un semplice sondaggio continuo sullo stato dell'opportuno bit (UF, AF o PF) del Registro C; l'unico difetto di questo metodo è rappresentato dalla scarsa efficienza dovuta alle continue operazioni di lettura del Registro C che impegnano pesantemente la CPU.

4.3 Struttura dell'area Storage Registers della RAM-CMOS

I 114 byte della CMOS, successivi ai 14 byte dell'area User Buffer, sono riservati alla memorizzazione di numerose informazioni relative alla configurazione hardware del PC; tali informazioni sono di competenza esclusiva del BIOS e non devono essere assolutamente modificate dal programmatore attraverso l'accesso diretto in scrittura alla CMOS stessa!

Si tenga anche presente che l'ordine con il quale risultano memorizzate le varie informazioni nella CMOS può differire in base alla marca del BIOS e/o al modello di PC; la Figura 4.10 illustra alcuni campi rilevanti che si possono trovare nella CMOS di un tipico PC "IBM compatibile" (ovviamente, gli offset partono da 0Eh in quanto sono successivi all'area User Buffer). Se si ha la necessità di accedere in lettura a queste informazioni, si può ricorrere ad un metodo sicuro che consiste nel servirsi dei numerosi servizi offerti dal BIOS; a tale proposito, si consiglia di consultare il manuale utente del proprio BIOS.

Osservando la Figura 4.10 possiamo notare, all'offset 32h della CMOS, la presenza di un "curioso" byte denominato Century (secolo); tale byte indica il secolo corrente e viene unito al campo Anno di Figura 4.3 per ottenere la rappresentazione a 4 cifre dell'anno corrente.

Ovviamente, nasce subito una domanda: cosa ci fa il campo Century in quella strana posizione?

La risposta a questa domanda non può essere riassunta in poche parole in quanto comporta implicazioni di vario genere; come vedremo più avanti, una di queste implicazioni chiama addirittura in causa il famoso (o fantomatico) millennium bug!

Per chiarire la situazione bisogna partire dal fatto che, all'avvio del PC, il BIOS sfrutta il dispositivo RTC + RAM per inizializzare un proprio sistema autonomo di gestione della data e dell'ora; anche i SO utilizzano un proprio sistema autonomo di gestione della data e dell'ora, che viene inizializzato attraverso la lettura delle informazioni fornite dal BIOS o dallo stesso dispositivo RTC + RAM.
Il perché di questa situazione è legato a motivi prestazionali; se il BIOS e il SO dovessero aggiornare continuamente il loro orologio/calendario attraverso la lettura del dispositivo RTC + RAM, si andrebbe incontro ad un serio rallentamento generale del sistema!
Il problema viene allora risolto facendo in modo che il dispositivo RTC + RAM, il BIOS e il SO aggiornino separatamente e autonomamente i rispettivi orologi/calendari; ciò giustifica il fatto che si possa riscontrare una leggera divergenza tra i vari orologi (soprattutto per quanto riguarda il campo Secondi)!

Come sappiamo, il dispositivo originario RTC + RAM usato per equipaggiare i PC dei primi anni 80 fu il Motorola MC146818A; stiamo parlando quindi di vicende relative al XIX secolo.
Per gestire l'anno corrente i progettisti della Motorola misero a disposizione il campo Anno; come è stato già spiegato, tale campo è destinato a contenere solamente le 2 cifre meno significative dell'anno corrente.
Nelle intenzioni di quei progettisti, i SO dell'epoca avrebbero potuto ricavare in modo semplicissimo la rappresentazione a 4 cifre dell'anno corrente; a tale proposito, non dovevano fare altro che leggere le 2 cifre (ad esempio, 87) del campo Anno e metterci un 19 davanti in modo da ottenere 1987.
Inoltre, nessuno riteneva, all'epoca, che quei PC potessero raggiungere e superare la soglia del XX secolo; si pensava cioè che nel frattempo tali PC sarebbero stati sostituiti da hardware più evoluto, capace di affrontare in modo adeguato anche eventuali problemi legati all'orologio/calendario.
In ogni caso, la IBM pensò di cautelarsi sistemando il byte Century nella CMOS; non essendoci spazio disponibile nell'area User Buffer, venne scelto arbitrariamente l'offset 32h. In tale posizione venne messo il valore 19h (secolo in formato BCD) in attesa di "ulteriori sviluppi".

Contrariamente alle previsioni, una enorme quantità di vecchi computer, dotati di dispositivo RTC + RAM, ha raggiunto e superato la soglia del XX secolo; ciò ha determinato un problema generalmente conosciuto con il nome di millennium bug!

4.3.1 Il millennium bug

Cosa succede quando un generico orologio/calendario supera le ore 23:59:59 del 31/12/1999?

Al successivo ciclo di aggiornamento dovrebbero scoccare le ore 00:00:00 del 01/01/2000; con i vecchi dispositivi RTC + RAM le cose non vanno però così!

Alle ore 23:59:59 del 31/12/1999, il campo Anno contiene il valore 99; al successivo ciclo di aggiornamento, si passa alle ore 00:00:00 del 01/01/2000, con il campo Anno che diventa quindi 00.
Il campo Century non fa parte dell'orologio/calendario e quindi non subisce alcun aggiornamento automatico (da 19 a 20); si tratta, infatti, di una semplice locazione da 1 byte inserita arbitrariamente dalla IBM all'offset 32h della CMOS.
A questo punto, tutto passa nelle mani del SO; più precisamente, tutto dipende dalla consapevolezza che il SO ha del problema appena illustrato.

I vecchi SO, come il DOS, per inizializzare il proprio sistema di gestione della data e dell'ora, leggono le 2 cifre del campo Anno e continuano a metterci davanti un 19 (eventualmente letto dal campo Century); nel caso dell'anno 2000, quindi, mettendo un 19 davanti a 00 si ottiene 1900!
Il DOS non accetta questa data e la adegua al valore minimo possibile (legato all'epoch time) pari a 1980; come se non bastasse, lo stesso DOS non si preoccupa di salvare le nuove impostazioni nel dispositivo RTC + RAM il quale continua quindi ad indicare Anno=00h e Century=19h!
La conseguenza è che al successivo riavvio del PC si verifica nuovamente il problema appena illustrato; tutto ciò va avanti (se non si provvede ad aggiornare il dispositivo RTC + RAM) per 80 anni, finché cioè il campo Anno non raggiunge il valore 80!

Purtroppo, esistono anche particolari BIOS o SO che si rifiutano di avviarsi in presenza di una data non valida; questa situazione diventa particolarmente pericolosa nel caso di computer destinati a gestire situazioni molto delicate (ad esempio, apparati militari, strumenti elettromedicali, etc).

Diversi dispositivi RTC + RAM successivi al MC146818A (ad esempio, il Dallas Semiconductors DS12885) affrontano il problema della data provvedendo ad aggiornare automaticamente anche il campo Century; in sostanza, il campo Anno viene azzerato ogni volta che supera il valore 99 e viene incrementato di 1 il campo Century.
Naturalmente, è fondamentale che il SO venga progettato in modo da ricavare la rappresentazione a 4 cifre dell'anno corrente attraverso i campi Century e Anno del dispositivo RTC + RAM; i SO dell'ultima generazione seguono proprio questa strada.

4.4 Programmazione del dispositivo RTC + RAM

La programmazione del dispositivo RTC + RAM avviene in modo semplicissimo; infatti, ciascuna operazione di I/O richiede le seguenti due fasi: Per svolgere queste due fasi sono disponibili due apposite porte; la Figura 4.11 illustra tutti i dettagli. In sostanza, attraverso la porta 70h specifichiamo a quale offset vogliamo accedere (vedere Figura 4.3 e Figura 4.10); attraverso poi la porta 71h effettuiamo l'accesso, in lettura o in scrittura, all'offset specificato in precedenza.

Naturalmente, il programmatore deve prestare molta attenzione al fatto che, come abbiamo già visto, alcune locazioni sono accessibili in sola lettura; inoltre, è necessario ribadire che è pericolosissimo modificare le informazioni presenti nell'area Storage Registers della CMOS!

4.5 Un esempio pratico: orologio/calendario + allarme

Il dispositivo RTC + RAM può essere interamente programmato attraverso i servizi offerti dalla INT 1Ah del BIOS; a tale proposito, si consiglia di consultare il manuale utente del proprio BIOS.

Nel nostro caso, anziché ricorrere al BIOS, seguiamo la strada dell'accesso diretto al dispositivo RTC + RAM scrivendo un programma che mostra un orologio/calendario completo di ore, minuti, secondi, giorno della settimana, giorno del mese, mese e anno; inoltre, impostiamo un allarme da attivare attraverso l'alarm interrupt.
Nella Figura 3.11 del Capitolo 3 possiamo notare che il dispositivo RTC + RAM invia le proprie IRQ alla linea IR0 del PIC Slave; si tratta quindi di una IRQ8 a cui il PIC associa una INT 70h. Il nostro programma deve quindi installare una propria ISR destinata ad intercettare la INT 70h; la Figura 4.12 mostra il listato Assembly. Il primo compito svolto dal programma consiste nell'installazione della nuova ISR per la INT 70h; il procedimento da seguire è già stato abbondantemente descritto nel precedente capitolo.

Dopo l'installazione della ISR, procediamo con l'impostazione dei campi Allarme Ore, Allarme Minuti e Allarme Secondi; a tale proposito, il programmatore deve servirsi delle costanti simboliche ALL_ORE, ALL_MIN e ALL_SEC (ovviamente, in fase di "collaudo" del programma è consigliabile impostare un'ora di allarme molto vicina all'ora corrente in modo da non dover attendere per troppo tempo).
Si noti che prima di inizializzare i campi relativi all'allarme, viene chiamata la procedura wait_uip0 per attendere che il bit UIP (Update cycle In Progress) si sia portato a zero.

Una volta che l'ora di allarme è stata impostata, dobbiamo attivare il bit AIE (Alarm Interrupt Enable) del Registro B; questo passo è necessario in quanto abbiamo deciso di utilizzare il classico metodo della ISR.

A questo punto parte il loop principale del programma; al suo interno sono presenti le istruzioni necessarie per mostrare tutte le informazioni relative all'orologio/calendario.
All'inizio di ogni loop attendiamo, come al solito, che il bit UIP si sia portato a zero; è importante ribadire che se non si segue questo procedimento, c'è il serio rischio di ottenere risultati privi di senso (a titolo di curiosità, si può provare a commentare la chiamata a wait_uip0 per vedere ciò che succede)!

Mentre il loop è in esecuzione, si verifica la condizione di allarme; a quel punto, il dispositivo RTC + RAM genera un Alarm Interrupt che raggiunge il PIC Slave. Lo stesso PIC Slave informa la CPU che bisogna chiamare la INT 70h.
Tale chiamata viene intercettata dalla nostra ISR la quale provvede ad assegnare il valore 1 alla variabile alarm_flag; si tratta della condizione che provoca la terminazione del loop principale.
La ISR svolge anche un compito importante che consiste nell'accesso in lettura al Registro C; come sappiamo, tale accesso è necessario per ripristinare lo stato dei vari flags presenti nello stesso registro.
Prima di terminare, la ISR invia il segnale EOI; come abbiamo visto nel precedente capitolo, per le IRQ che arrivano al PIC Slave bisogna mandare un EOI allo stesso PIC Slave e un EOI al PIC Master!

Una volta che il loop principale del programma è terminato, si procede al ripristino della vecchia ISR relativa alla INT 70h; in questo modo, il nostro programma termina riportando lo stato del computer alla situazione originaria.

4.5.1 Vettori di puntatori

L'esempio di Figura 4.12 ci offre l'opportunità di analizzare il caso dei "vettori di puntatori"; si tratta comunque di un argomento già affrontato in dettaglio nella sezione Assembly Base.

Un vettore di puntatori o "vettore di indirizzi" è un vettore i cui elementi sono tutti degli indirizzi; ogni elemento del vettore contiene quindi l'indirizzo di una qualche informazione in memoria.
Consideriamo, ad esempio, la stringa str_giorni; questa stringa contiene una sequenza di stringhe C, consecutive e contigue. A sua volta, ciascuna stringa C contiene il nome di uno dei giorni della settimana; come si vede allora nel listato di Figura 4.12, possiamo affermare che: A questo punto, definiamo il vettore vpt_giorni il cui scopo è contenere gli indirizzi (offset) di ciascuna delle precedenti stringhe C; si noti che il primo elemento di vpt_giorni è 0 e non viene utilizzato in quanto, nel dispositivo RTC + RAM, i giorni della settimana partono dall'indice 1.
In sostanza, la variabile vpt_giorni è un vettore di WORD in quanto ogni suo elemento contiene un offset a 16 bit. Di conseguenza, l'istruzione:
mov di, vpt_giorni
trasferisce in DI l'indirizzo di memoria della prima WORD di vpt_giorni; invece:
mov di, [vpt_giorni]
trasferisce in DI il contenuto della prima WORD di vpt_giorni e cioè, 0000h.

L'istruzione:
mov di, vpt_giorni+2
trasferisce in DI l'indirizzo di memoria della seconda WORD di vpt_giorni; invece:
mov di, [vpt_giorni+2]
trasferisce in DI il contenuto della seconda WORD di vpt_giorni e cioè, str_giorni+0; si tratta quindi dell'indirizzo di memoria da cui inizia la stringa "Domenica ", 0.
Ne consegue che:
mov al, [di]
che equivale a
mov al, [str_giorni+0]
trasferisce in AL il contenuto del primo BYTE di str_giorni+0 e cioè, 'D'.

L'istruzione:
mov di, vpt_giorni+4
trasferisce in DI l'indirizzo di memoria della terza WORD di vpt_giorni; invece:
mov di, [vpt_giorni+4]
trasferisce in DI il contenuto della terza WORD di vpt_giorni e cioè, str_giorni+10; si tratta quindi dell'indirizzo di memoria da cui inizia la stringa "Lunedi   ", 0.
Ne consegue che:
mov al, [di]
che equivale a
mov al, [str_giorni+10]
trasferisce in AL il contenuto del primo BYTE di str_giorni+10 e cioè, 'L'.

Analoghe considerazioni per tutte le altre WORD di vpt_giorni.

Si consiglia di scrivere un programma per verificare in pratica i concetti appena esposti.

La lettura del campo Giorno della Settimana dal dispositivo RTC + RAM ci fornisce un valore compreso tra 1 e 7; tale valore viene caricato in BX e moltiplicato per 2 in quanto ogni elemento di vpt_giorni occupa 2 byte. A questo punto, [vpt_giorni+BX] contiene l'offset da cui inizia in memoria la stringa C con il nome del giorno corrente.
Naturalmente, le considerazioni appena esposte si applicano anche al caso del vettore vpt_mesi.

Bibliografia

Motorola Semiconductors - MC146818A Real Time Clock plus RAM
(501896_DS.pdf)

Dallas Semiconductors - Real Time Clock
(DS12885-DS12C887A.pdf)

Texas Instruments - bq4285 Real Time Clock with NVRAM Control
(bq4285.pdf)