- Elettronica Open Source - https://it.emcelettronica.com -

Il Real Time Clock dei micro i.MX25

In molte applicazioni in tempo reale, la misura del tempo corrente è aspetto essenziale per il funzionamento del sistema. In linea di principio, una funzionalità di questo tipo può essere generata mediante una routine software, sulla base di interruzioni periodiche generate da un timer [1].

Questo richiede tuttavia un impegno della CPU nell’aggiornamento del tempo; inoltre impone che il sistema sia costantemente alimentato. Per questo motivo, più frequentemente si preferisce una soluzione hardware basata su Real-Time Clock (RTC), veri e propri orologi elettronici. Essi sono caratterizzati da una maggiore risoluzione nella misura del tempo; presentano una interfaccia standard verso la CPU [2] e bassa dissipazione di potenza, così da poter essere alimentati anche mediante batteria tampone. In questo modo, l’RTC può continuare a funzionare anche se la CPU viene forzata in uno stato di power-down nelle applicazioni a risparmio energetico. Quasi tutti i principali produttori di componenti elettronici (come Intersil, Maxim, Texas Instrument, STMicroelectronics ) dispongono, a catalogo, di RTC e questi dispositivi sono ormai disponibili come periferiche integrate anche in molti microcontrollori di fascia più alta. In quest’articolo vogliamo attirare l’attenzione sulle caratteristiche e sull’utilizzo del Real-Time Clock di cui dispongono i microprocessori della famiglia i.Mx25 di Freescale/NXP. Basati su CPU ARM9 con frequenza di clock fino a 400 MHz, bassa dissipazione di potenza e connettività a diversi livelli, tali processori rappresentano una soluzione ideale per molte applicazioni embedded nel settore industriale od in ambito automotive.

CARATTERISTICHE DELL’RTC

L’RTC dei processori iMX25 è un timer a 47 bit con clock a 32.768 KHz che assicura una risoluzione nella misura del tempo di circa 30 microsecondi. Il contatore di secondi è a 32-bit garantendo così un intervallo di misura di oltre 100 anni. Il clock può essere fornito mediante sorgente esterna o oscillatore integrato. L’RTC supporta la programmazione di un istante di allarme, al momento del quale attiva una interruzione verso la CPU; questa può essere usata, ad esempio, per risvegliare il sistema da uno stato di power-down o come base dei tempi sincronizzata per l’esecuzione di procedure programmate. L’RTC dispone di una sezione di alimentazione dedicata che consente di commutare automaticamente tra la linea principale ed un ingresso di back-up, connesso, ad esempio, ad un batteria tampone. Quando la tensione d’ingresso scende al di sotto di 1.0V (± 50 mV) l’RTC commuta automaticamente sulla linea di backup; torna invece ad essere alimentato dalla linea principale quando sono ristabilite le condizioni nominali (1.2V ± 50 mV). In alimentazione a batteria, se abilitato, l’RTC continua ad aggiornare correttamente il tempo proprio. L’assorbimento di corrente in queste condizioni è inferiore ai 15 microampere, così che una batteria tampone di quelle tipicamente utilizzate per tali applicazioni può durare anche diversi anni. Inoltre, qualora necessario, l’RTC rende disponibile un registro a 32-bit, anch’esso alimentabile mediante la linea ausiliaria, il cui contenuto continua ad essere valido anche quando il sistema viene forzato in stand-by. La CPU può utilizzare tale registro per memorizzare informazioni di stato da recuperare al risveglio dal periodo di ibernazione. L’RTC fa parte del modulo DryIce dei processori i.MX25, che oltre ad integrare la periferica rende disponibile un meccanismo di memorizzazione di chiavi di cifratura e di rilevamento di tentativi di sabotaggio (tampering) del dispositivo. Nel listato 1 è mostrato un semplice esempio di utilizzo dell’RTC nella modalità non-secure.

#define reg32_read(addr) *(unsigned int *)((addr))
#define reg32_write(addr,val) *((unsigned int *)(addr)) = (val)
#define WDOG_WCR (0x53FDC000) // DRYICE - RTC register defines
#define DRYICE_RTCMR (0x53FFC000) // RTC Time Counter MSB Register
#define DRYICE_RTCLR (0x53FFC004) // RTC Time Counter LSB Register
#define DRYICE_RCAMR (0x53FFC008) // RTC Clock Alarm MSB Register
#define DRYICE_RCALR (0x53FFC00C) // RTC Clock Alarm LSB Register
#define DRYICE_DCR (0x53FFC010) // DryIce Control Register
#define DRYICE_DSR (0x53FFC014) // DryIce Status Register
#define DRYICE_DIER (0x53FFC018) // DryIce Interrupt Enable Register
#define DRYICE_DGPR (0x53FFC03C) // DryIce General Purpose Register
void reg32_dryice32kdomain_write(unsigned int addr, unsigned int wdata) {
unsigned int masked_wdata, rdata_tmp;
unsigned int dryice_status;
reg32_write(addr, wdata);
while ((reg32_read(DRYICE_DSR) & 0x200) != 0x200);
while ((reg32_read(DRYICE_DSR) & 0x100) != 0x100);
reg32_read(DRYICE_DSR);
}
Listato 1

PROGRAMMAZIONE DELL’RTC

Una delle prime cose da tenere presente nell’utilizzo dell’RTC è che i registri del DryIce (e quindi dell’RTC – ad eccezione dei registri di Interrupt Enable e Status) funzionano alla frequenza di clock dell’oscillatore a 32.768 KHz; quindi ogni accesso in scrittura ad essi richiede un ciclo di tale clock, di frequenza inferiore a quella dell’oscillatore della CPU (fino a 400 MHz). Per fortuna la CPU non è in stallo durante l’esecuzione dell’operazione. All’interno del registro di stato DryIce Status Register (DSR) del DryIce, le flag WBF (Write Busy Flag), WCF (Write Complete Flag) e WNF (Write Next Flag) indicano, rispettivamente, che un’operazione di scrittura è in corso, che è stata completata e che una successiva operazione può essere eseguita. La flag WEF (Write Error Flag) segnala invece che l’operazione di scrittura è stata cancellata, come conseguenza, ad esempio, di un tentativo di accesso al registro mentre il DryIce è isolato oppure durante il periodo di reset dopo il power-on del dispositivo (Power-On Reset). Il listato 1 (da [3]) mostra una semplice implementazione di una funzione di scrittura dei registri del DryIce che tiene conto di queste limitazioni; dopo la scrittura la CPU esegue un ciclo di polling del registro di stato del DryIce per verificare le flag discusse in precedenza. Una soluzione diversa, che libera la CPU dall’esecuzione del ciclo di polling, può essere basata sulla abilitazione della generazione di un interrupt al termine del ciclo di scrittura. Il registro DryIce Interrupt Enable Register (DIER) consente di abilitare indipendentemente la generazione dell’interrupt in risposta agli eventi segnalati dalle tre flag WBF, WCF e WNF. Un’implementazione di questo tipo si trova, ad esempio, all’interno del driver per Linux che viene distribuito da Freescale unitamente al sistema di sviluppo iMX25 Product Development Kit (vedi [4]). Un altro aspetto da considerare nell’utilizzo dell’RTC è che i registri del DryIce possono essere letti da qualunque software ma, se il bit Non-Secure Access nel registro non è settato, possono essere scritti soltanto da software ‘secure’. Quindi per gestire l’RTC in applicazioni non-secure dovremmo avere cura di configurare opportunamente questo controllo. Una generica applicazione di questo tipo dovrà contenere allora le istruzioni riportate nel listato 2.

void main(void){
int i;
*(unsigned short *)(WDOG_WCR) = 0x0030; // Disable WDOG
reg32_dryice32kdomain_write(DRYICE_DCR,0x8000); //disable security to allow any register writes
reg32_dryice32kdomain_write(DRYICE_DSR,0x03); // clear any security errors
reg32_dryice32kdomain_write(DRYICE_RTCMR,0x00); // need to write to the RTCMR to get RTC started
reg32_dryice32kdomain_write(DRYICE_DCR,0x8008); // enable the RTC
while(1)
{
                                              /* put here user application*/
}
}
Listato 2

Il listato 3 mostra infine, come può essere scritta una funzione che imposta un allarme (per semplicità vengono configurati solo i 32 bit del timer dell’RTC corrispondenti ai secondi) abilitando la generazione dell’interrupt in corrispondenza dell’istante configurato.

static int dryice_rtc_set_alarm(unsigned long alarm_time, int alarm_enabled)
{
       unsigned long now;
       unsigned long alarm_time;
       /* don’t allow setting alarm in the past */
       now =reg32_read(DRYICE_RTCMR);

       if (alarm_time < now)
       return -1;

       /* write the new alarm time */
       reg32_dryice32kdomain_write(DRYICE_RTCMR, alarm_time);

      /* enable the interrupt */
      if (alarm_enabled)
      reg32_dryice32kdomain_write(DRYICE_DIER, 0x0080);
      else
      reg32_dryice32kdomain_write(DRYICE_DIER, 0x0000);

      return 0;
}
Listato 3