Real-Time Clock (Windows Embedded CE 6.0)

1/6/2010

You can minimize the impact of accessing the real-time clock (RTC) in any of the following ways:

  • Replace the hardware RTC with a software RTC.
  • Eliminate calls to the RTC.
  • Access the clock at optimal times.

In standard x86 bus architecture, where the complementary metal oxide semiconductor (CMOS) system clock houses the RTC and the system clock resides on the bus, the RTC accesses the bus several times. In doing so, the bus can be locked for several microseconds, blocking it from other activities. This translates into higher latencies for interrup service routines (ISRs) and longer times to fully service an interrupt request (IRQ). To avoid this, consider replacing the hardware RTC with a software RTC. On a CEPC hardware platform, you must also eliminate the calls to the RTC when GetSystemTime is called, and access the clock at optimal times to ensure that a large drift is not introduced.

The following code example shows how to replace the hardware RTC with a software RTC:

#include <windows.h>
#include <nkintr.h>
#include "pc.h"
#include "timer.h"
// NOTE: A problem has been found with some chipsets such that setting the time to 23:59:59 on the 29th or 30th day of a month that has less than 31 days causes the clock to roll over incorrectly. Uncomment the following line to fix this problem. However, be aware that the fix consists of responding to calls that set the time to HH:MM:59 by instead setting the time to HH:MM:58.
#define HARDWARE_TIME_SET_PROBLEM 1 __int64 RealTimeBias;
unsigned long AlarmTime[2];
SYSTEMTIME RTC_AlarmTime;
CRITICAL_SECTION RTC_critsect;
BOOL fRTCInit;
unsigned long volatile CurMSecHigh;
// RTC without CMOS Global Variables
BOOL rtInited = FALSE;
FILETIME rtStartFileTime;
FILETIME rtDeltaTime;
FILETIME rtCurFileTime;
FILETIME rtLastFileTime;
DWORD rtLastCurMSec;
extern DWORD CurMSec;
// Prototypes for RTC without CMOS
BOOL RT_Init_RealTime ( void );
BOOL RT_Bare_GetRealTime (LPSYSTEMTIME lpst);
BOOL RT_Bare_SetRealTime (LPSYSTEMTIME lpst);
void add64_64_64 (const FILETIME *lpnum1, 
                        LPFILETIME lpnum2, 
                        LPFILETIME lpres);
void CMOS_Write( BYTE offset, BYTE value )
{
   BYTE cAddr;
   // Remember, only the low-order 5 bits in the address register are changed.
   cAddr = _inp( CMOS_ADDR );
   _outp( CMOS_ADDR, (cAddr & RTC_ADDR_MASK) | offset );
   _outp( CMOS_DATA, value );
   // NOTE : If bytes 16-45 are ever updated, the checksum should be recalculated and stored. However, because none of those bytes are currently used, it will not be addressed here.
   DEBUGCHK(offset < 16);
}
BYTE CMOS_Read( BYTE offset )
   {
      BYTE cAddr, cResult;
      // Remember, only the low-order 5 bits in the address register are changed.
      cAddr = _inp( CMOS_ADDR );
      _outp( CMOS_ADDR, (cAddr & RTC_ADDR_MASK) | offset );    
      cResult = _inp( CMOS_DATA );
      return (cResult);
   }
BOOL IsTimeEqual(LPSYSTEMTIME lpst1, LPSYSTEMTIME lpst2)
   {
      if (lpst1->wYear != lpst2->wYear)
         return(FALSE);
      if (lpst1->wMonth != lpst2->wMonth)
         return(FALSE);
      if (lpst1->wDayOfWeek != lpst2->wDayOfWeek)
         return(FALSE);
      if (lpst1->wDay != lpst2->wDay)
         return(FALSE);
      if (lpst1->wHour != lpst2->wHour)
         return(FALSE);
      if (lpst1->wMinute != lpst2->wMinute)
         return(FALSE);
      if (lpst1->wSecond != lpst2->wSecond)
         return(FALSE);
      return (TRUE);
   }
   // NOTE : The RTC routines are not thread safe. But the ether debug functions do not want me to change interrupts, and so on. So the Bare_ functions is provided for callers who run with interrupts off, and so on. Then the real functions call into the bare functions after turning off interrupts.
BOOL Bare_GetRealTime(LPSYSTEMTIME lpst)
   {
      SYSTEMTIME st;
      LPSYSTEMTIME lpst1 = &st, lpst2 = lpst, lptmp;
      lpst1->wSecond = 61; // initialize to an invalid value 
      lpst2->wSecond = 62; // initialize to an invalid value 
      do
         {
            // exchange lpst1 and lpst2
            lptmp = lpst1;
            lpst1 = lpst2;
            lpst2 = lptmp;
            // Wait until not updating.
            while (CMOS_Read(RTC_STATUS_A) & RTC_SRA_UIP);
            // Read all values.
            lpst1->wYear = CMOS_Read(RTC_YEAR);
            lpst1->wMonth = CMOS_Read(RTC_MONTH); 
            lpst1->wDayOfWeek = CMOS_Read(RTC_DO_WEEK);
            lpst1->wDay = CMOS_Read(RTC_DO_MONTH);
            lpst1->wHour = CMOS_Read(RTC_HOUR); 
            lpst1->wMinute = CMOS_Read(RTC_MINUTE); 
            lpst1->wSecond = CMOS_Read(RTC_SECOND); 
         }
      while (!IsTimeEqual (lpst1, lpst2));
         lpst->wMilliseconds = 0; // Not sure how you would get this
      if (!(CMOS_Read (RTC_STATUS_B) & RTC_SRB_DM)) {
         // Values returned in BCD.
         lpst->wSecond = DECODE_BCD(lpst->wSecond);
         lpst->wMinute = DECODE_BCD(lpst->wMinute);
         lpst->wHour   = DECODE_BCD(lpst->wHour);
         lpst->wDay    = DECODE_BCD(lpst->wDay);
         lpst->wDayOfWeek = DECODE_BCD(lpst->wDayOfWeek);
         lpst->wMonth  = DECODE_BCD(lpst->wMonth);
         lpst->wYear   = DECODE_BCD(lpst->wYear);
      }
   // OK - PC RTC returns 1998 as 98.
   lpst->wYear += (lpst->wYear > 70)? 1900 : 2000;
   #ifdef NOTDEF NKDbgPrintfW(TEXT("\r\nReal Time %d, %d, %d, %d, %d, %d, %d, %d\r\n"),
                 lpst->wYear,
                 lpst->wMonth,
                 lpst->wDayOfWeek,
                 lpst->wDay,
                 lpst->wHour,
                 lpst->wMinute,
                 lpst->wSecond,
                 lpst->wMilliseconds );
   #endif
      return (TRUE);
   }
   BOOL Bare_SetRealTime(LPSYSTEMTIME lpst)
      {
         BYTE cStatusRegA, cStatusRegB, Year;
         #ifdef NOTDEF NKDbgPrintfW(TEXT("\r\nSet Real Time %d, %d, %d, %d, %d, %d, %d, %d\r\n"),
                         pst->wYear,
                         lpst->wMonth,
                         lpst->wDayOfWeek,
                         lpst->wDay,
                         lpst->wHour,
                         lpst->wMinute,
                         lpst->wSecond,
                         lpst->wMilliseconds );
         #endif
      Year = lpst->wYear % 100;
   #ifdef HARDWARE_TIME_SET_PROBLEM
      if (lpst->wSecond == 59)
         {
            lpst->wSecond = 58;
         }
   #endif
   // Read the update in progress bit, wait for it to be clear. This bit will be set once per second for about 2 us. (Undoc. PC, page 897)
   do
      {
         cStatusRegA = CMOS_Read( RTC_STATUS_A);
      }
   while ( cStatusRegA & RTC_SRA_UIP );
      // Disable updates while the values are changed
      cStatusRegB = CMOS_Read( RTC_STATUS_B );
      cStatusRegB |= RTC_SRB_UPDT;
      CMOS_Write( RTC_STATUS_B, cStatusRegB );
   if ( !(cStatusRegB & RTC_SRB_DM) )
      {
         // BCD Mode
         CMOS_Write( RTC_YEAR, (BYTE)(CREATE_BCD(Year))); 
         CMOS_Write( RTC_MONTH, (BYTE)(CREATE_BCD(lpst->wMonth))); 
         CMOS_Write( RTC_DO_WEEK, (BYTE)(CREATE_BCD(lpst->wDayOfWeek))); 
         CMOS_Write( RTC_DO_MONTH, (BYTE)(CREATE_BCD(lpst->wDay))); 
         CMOS_Write( RTC_HOUR,     (BYTE)(CREATE_BCD(lpst->wHour))); 
         CMOS_Write( RTC_MINUTE,   (BYTE)(CREATE_BCD(lpst->wMinute))); 
         CMOS_Write( RTC_SECOND,   (BYTE)(CREATE_BCD(lpst->wSecond))); 
         // Not sure how you can do lpst->wMilliseconds;
      }
   else
      {
         // Binary mode
         CMOS_Write( RTC_YEAR, (UCHAR)Year); 
         CMOS_Write( RTC_MONTH, (UCHAR)lpst->wMonth); 
         CMOS_Write( RTC_DO_WEEK, (UCHAR)lpst->wDayOfWeek); 
         CMOS_Write( RTC_DO_MONTH, (UCHAR)lpst->wDay); 
         CMOS_Write( RTC_HOUR, (UCHAR)lpst->wHour); 
         CMOS_Write( RTC_MINUTE, (UCHAR)lpst->wMinute); 
         CMOS_Write( RTC_SECOND, (UCHAR)lpst->wSecond); 
         // Not sure how you can do lpst->wMilliseconds;
      }
   // Reenable updates
   cStatusRegB &= ~RTC_SRB_UPDT;
   CMOS_Write( RTC_STATUS_B, cStatusRegB );
   return (TRUE);
   }
BOOL Bare_SetAlarmTime(LPSYSTEMTIME lpst)
   {
      BYTE cStatusRegA, cStatusRegB, Year;
      #ifdef NOTDEF
         NKDbgPrintfW(TEXT("\r\nSet Alarm Time %d, %d, %d, %d, %d, %d, %d, %d\r\n"),
             lpst->wYear,
             lpst->wMonth,
             lpst->wDayOfWeek,
             lpst->wDay,
             lpst->wHour,
             lpst->wMinute,
             lpst->wSecond,
             lpst->wMilliseconds );
      #endif
   // NOTE : Because our alarm only has a 1-day rollover, the full alarm time needs to be stored and compared on alarm interrupts to see if it is at the correct day.
   RTC_AlarmTime = *lpst;
   Year = lpst->wYear % 100;
   // Read the update in progress bit, wait for it to be clear. This bit  will be set once per second for about 2 us. (Undoc. PC, page 897)
   do
      {
         cStatusRegA = CMOS_Read( RTC_STATUS_A);
      }
   while ( cStatusRegA & RTC_SRA_UIP );
      // Disable updates while the values are changed
      cStatusRegB = CMOS_Read( RTC_STATUS_B );
      cStatusRegB |= RTC_SRB_UPDT;
      CMOS_Write( RTC_STATUS_B, cStatusRegB );
      if ( !(cStatusRegB & RTC_SRB_DM) )
         {
            // BCD Mode
            CMOS_Write( RTC_ALRM_HOUR, (BYTE)(CREATE_BCD(lpst->wHour)));
            CMOS_Write( RTC_ALRM_MINUTE, (BYTE)(CREATE_BCD(lpst->wMinute)));
            CMOS_Write( RTC_ALRM_SECOND, (BYTE)(CREATE_BCD(lpst->wSecond)));
         }
      else
         {
            // Binary mode
            CMOS_Write( RTC_ALRM_HOUR, (UCHAR)lpst->wHour);
            CMOS_Write( RTC_ALRM_MINUTE, (UCHAR)lpst->wMinute);
            CMOS_Write( RTC_ALRM_SECOND, (UCHAR)lpst->wSecond);
         }
      // Enable alarm interrupt and reenable updates.
      cStatusRegB = (cStatusRegB | RTC_SRB_AI) & ~RTC_SRB_UPDT;
      CMOS_Write( RTC_STATUS_B, cStatusRegB );
      return( TRUE );
   }
BOOL OEMGetRealTime(LPSYSTEMTIME lpst)
   {
      BOOL RetVal;
      if (!fRTCInit)
         {
            InitializeCriticalSection(&RTC_critsect);
            // RTC without CMOS
            if( !rtInited )RT_Init_RealTime();
               fRTCInit = TRUE;
         }
      EnterCriticalSection(&RTC_critsect);
      if( rtInited )
         {
            //RTC without CMOS
            RetVal = RT_Bare_GetRealTime( lpst );
         }
      else
         {
            RetVal = Bare_GetRealTime(lpst);
         }
         LeaveCriticalSection(&RTC_critsect);
      return RetVal;
   }
BOOL OEMSetRealTime(LPSYSTEMTIME lpst)
   {
      BOOL RetVal;
      if (!fRTCInit)
         {
            InitializeCriticalSection(&RTC_critsect);
            if( !rtInited )RT_Init_RealTime();
               fRTCInit = TRUE;
         }
      EnterCriticalSection(&RTC_critsect);
      if( rtInited )
         {
            RetVal = RT_Bare_SetRealTime(lpst);
         }
      else
         {
            RetVal = Bare_SetRealTime(lpst);
         }
      LeaveCriticalSection(&RTC_critsect);
      return RetVal;
   }
BOOL OEMSetAlarmTime(LPSYSTEMTIME lpst)
   {
      BOOL RetVal;
      if (!fRTCInit)
         {
            InitializeCriticalSection(&RTC_critsect);
            fRTCInit = TRUE;
         }
      EnterCriticalSection(&RTC_critsect);
      RetVal = Bare_SetAlarmTime(lpst);
      LeaveCriticalSection(&RTC_critsect);
      return RetVal;
   }
// The following function attempts to eliminate the dependency on the CMOS for the clock times. A startup value is taken from the CMOS with all later times being returned as the Tick Count plus this initial value. Roll over is accounted for. If the RT system has not been initialized, the legacy Bare_GetRealTime is called.
BOOL RT_Bare_GetRealTime(LPSYSTEMTIME lpst)
   {
      DWORD delta;
      // Calculate the current diference
      if( CurMSec >= rtLastCurMSec )
         {
            delta = CurMSec - rtLastCurMSec;
         }
      else
         {
            // The counter had to be wrapped
            delta = CurMSec + (ULONG_MAX - rtLastCurMSec);
         }
      rtDeltaTime.dwHighDateTime = 0;
      rtDeltaTime.dwLowDateTime = delta * 10000;
      // Add the delta to the last.
      add64_64_64(  &rtLastFileTime , & rtDeltaTime, & rtCurFileTime );
      // Add the current time difference in & record the values.
      rtLastFileTime.dwLowDateTime = rtCurFileTime.dwLowDateTime;
      rtLastFileTime.dwHighDateTime = rtCurFileTime.dwHighDateTime;
      rtLastCurMSec = CurMSec;
      // Get the answer in a system-time format.
      if(KFileTimeToSystemTime( &rtCurFileTime, lpst ) )
         {
            return TRUE;
         }
      else
         {
            // Failed and what time is it?
            return FALSE;
         }
      }
   // The following functions Converts the system time to the rtLastFileTime which stores the last time called. Zero the MS delta counter by setting the rtLastCurMSec to the CurMSec.
   BOOL RT_Bare_SetRealTime(LPSYSTEMTIME lpst)
      {
         if( KSystemTimeToFileTime(lpst, &rtLastFileTime ) )
            {
               rtLastCurMSec = CurMSec;
               return TRUE;
            }
         return FALSE;
      }
   // The following function initializes the RT area in an attempt to eliminate the run time dependency of the OS on the CMOS. The X86 CMOS is very slow and can take up to 1ms to get the full CMOS clock. This function get the start time from the CMOS and initializes the local variables that the RT functions need.
   BOOL RT_Init_RealTime( void )
      {
         SYSTEMTIME sysTime;
         RETAILMSG(1,(TEXT("\r\n\n******Called RT_Init_GetRealTime\r\n\n")));
         // Get the curren time and store it.
         if( !Bare_GetRealTime( &sysTime ))
            {
               // Are not able to get the time?
               return FALSE;
            }
         // Convert the file time structure.
         if( !KSystemTimeToFileTime( &sysTime, &rtCurFileTime ) )
            {
               return FALSE;
            }
      // Store Start and Current Versions of the File Time and Large Ints.
      rtLastCurMSec = (DWORD)CurMSec;
      rtStartFileTime.dwLowDateTime = rtCurFileTime.dwLowDateTime;
      rtStartFileTime.dwHighDateTime = rtCurFileTime.dwHighDateTime;
      rtLastFileTime.dwLowDateTime = rtCurFileTime.dwLowDateTime;
      rtLastFileTime.dwHighDateTime = rtCurFileTime.dwHighDateTime;
      rtInited = TRUE;
      return TRUE;
   }
void add64_64_64(const FILETIME *lpnum1, LPFILETIME lpnum2, LPFILETIME, lpres)
   {
      __int64 num1, num2;
      num1 = (((__int64)lpnum1->dwHighDateTime)<<32)+ (__int64)lpnum1->dwLowDateTime;
      num2 = (((__int64)lpnum2->dwHighDateTime)<<32)+ (__int64)lpnum2->dwLowDateTime;
      num1 += num2;
      lpres->dwHighDateTime = (DWORD)(num1>>32);
      lpres->dwLowDateTime = (DWORD)(num1&0xffffffff);
   }
}

See Also

Concepts

Peripheral Access Considerations
System Bus