Real-Time Clock (Windows CE 5.0)
You can minimize the impact of accessing the real-time clock (RTC) by replacing the hardware RTC with a software RTC, eliminating calls to the RTC, or accessing the clock at optimal times. In standard x86 bus architecture, where the CMOS houses the RTC and the CMOS 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 ISRs and longer times to fully service an 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 sample 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 the 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"),
lpst->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 2us (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 2us (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
System Bus | Peripheral Access Considerations
Send Feedback on this topic to the authors