text: Implementing Rock-Solid Windows CE Timers On Windows CE .NET 4.1 Platforms
  Michel Verhagen,
  Microsoft Windows Embedded MVP
  PTS Software, The Netherlands
  July 2003
  Applies to:
  Microsoft® Windows® CE .NET 4.1
  Abstract
  This paper provides guidelines for system developers to implement rock-solid CE timers on Windows CE platforms using a software-only solution.
  Contents
  Abstract
  Introduction
  QueryPerformanceCounter
  Re-Programming the Timer
  About the Author
  Acronyms and Terms
  Introduction
  Windows CE .NET, the embedded componentized OS from Microsoft, has an internal timer tick resolution of 1 microsecond (ms). For most projects, 2 ms accuracy is enough, but some projects just need a higher resolution non-blocking timer. The CE API does not provide such functionality out of the box, but by modifying the OAL a little bit, we can get rock-solid non-blocking timers with resolutions higher than 2ms.
  QueryPerformanceCounter
  Windows CE. NET does supply an out-of-the-box solution for high-resolution timers by means of the QueryPerformanceCounter API. This API is great if you have to delay for a small period of time, but what if you want to wait for a small period of time? The difference between delaying and waiting is that delaying is much more CPU consuming than waiting. Waiting implies that other (lower or equal priority) threads in the system can execute during the wait.
  LARGE_INTEGER liDelay; // Query number of ticks per second if (QueryPerformanceFrequency(&liDelay)) { // 1ms delay liDelay.QuadPart /= 1000; LARGE_INTEGER liTimeOut; // Get current ticks if (QueryPerformanceCounter(&liTimeOut)) { // Create timeout value liTimeOut.QuadPart += liDelay.QuadPart; LARGE_INTEGER liCurrent; do { // Get current ticks QueryPerformanceCounter(&liCurrent); // Delay until timeout } while (liCurrent.QuadPart<liTimeOut.QuadPart); } }
  Running the code shown above at the highest priority (priority 0) will block the entire OS during the delay time.
  When we run the second example at priority 0, the thread frees the CPU during the wait. Therefore, during these small periods of time, other threads can take ownership of the CPU and do their work. Unfortunately, the above HighResolutionTimer API's are not implemented in Windows CE .NET.
  Sleep resolution
  If you've read the introduction of this document, you might think there is a typo in it:
  "Windows CE (...) has an internal timer tick resolution of 1 ms. For most projects 2 ms accuracy is enough, (...)".
  If CE has a resolution of 1 ms, you would expect that's also the smallest time you can wait. Unfortunately, that's not the case because if we issue a Sleep(1), 10 µs after the system timer tick (the reschedule tick), the sleep counter starts at the next tick and will end on the following tick. This gives us a sleep of 1.90 ms, and not the 1 ms as expected. Generally speaking, a Sleep(N) will sleep somewhere in between N and (N+1) ms.
  Hardware solution
  The PC hardware architecture provides only 1 timer, which is physically connected to an interrupt line, and this timer is already in use by the Windows CE kernel. The CE kernel programs the timer to generate an interrupt every millisecond, and uses this interrupt primarily for the thread scheduler and some other functions. Our lives in x86 CEPC land wouldn't be so difficult if the PC architecture would incorporate some spare interrupt timers. Of course, you could add a simple programmable timer chip somewhere on the ISA or PCI bus, but why not try to accomplish high-resolution timers in software?
  Re-Programming the Timer
  The only way to generate a hard real-time 1 ms interrupt is to reprogram the PIT (Programmable Interval Timer, in PC hardware usually an 82C54 or derivate) faster than 1ms. A similar technique is used by the profiling code in the OAL (see OEMProfileTimerEnable). The code Windows CE uses to program the PIT is located in the OAL (OEM Adaptation Layer). The OAL source code files reside in \WINCE410\PUBLIC\COMMON\OAK\CSP\I486\OAL1. Windows CE uses the InitClock function inside timer.c to program the PIT:
  1. I use the original paths to point to OAL source code, but of course you should move the OAL code from the PUBLIC tree to your own BSP and modify it there. Never modify any code in the PUBLIC tree; Microsoft might update it using a QFE!
  The easiest way to create the 1 ms interrupt is to double the interrupt speed and toggle the behavior. The behavior is coded in the main Interrupt Service Routine, which will be discussed below.
  To double the speed of the timer interrupts, load the timer with TIMER_COUNT / 2, like this:
  Now, timer0 interrupts will occur every 500 microseconds (0.5 ms).
  I've added the #ifdefs around the modified code to make it slightly easier to go back to the original CE code.
  Modifying the ISR
  The main ISR is located inside fwpc.c:
  001 ULONG PeRPISR(void) 002 { 003 ULONG ulRet = SYSINTR_NOP; 004 UCHAR ucCurrentInterrupt; 005 006 if (fIntrTime) 007 { 008 // 009 // We're doing interrupt timing. Get Time to ISR. 010 // 011 #ifdef EXTERNAL_VERIFY 012 _outp((USHORT)0x80, 0xE1); 013 #endif 014 dwIntrTimeIsr1 = _PerfCountSinceTick(); 015 dwIntrTimeNumInts++; 016 } 017 018 ucCurrentInterrupt = PICGetCurrentInterrupt(); 019 020 if (ucCurrentInterrupt == INTR_TIMER0) 021 { 022 if (PProfileInterrupt) 023 { 024 ulRet= PProfileInterrupt(); 025 } 026 else 027 { 028 #ifdef SYSTIMERLED 029 static BYTE bTick; 030 _outp((USHORT)0x80, bTick++); 031 #endif 032 033 CurMSec += SYSTEM_TICK_MS; 034 #if (CE_MAJOR_VER == 0x0003) 035 DiffMSec += SYSTEM_TICK_MS; 036 #endif 037 CurTicks.QuadPart += TIMER_COUNT; 038 039 if (fIntrTime) 040 { 041 // 042 // We're doing interrupt timing. Every nth tick is a 043 // SYSINTR_TIMING. 044 // 045 dwIntrTimeCountdown--; 046 047 if (dwIntrTimeCountdown == 0) 048 { 049 dwIntrTimeCountdown = dwIntrTimeCountdownRef; 050 dwIntrTimeNumInts = 0; 051 #ifdef EXTERNAL_VERIFY 052 _outp((USHORT)0x80, 0xE2); 053 #endif 054 dwIntrTimeIsr2 = _PerfCountSinceTick(); 055 ulRet = SYSINTR_TIMING; 056 } 057 else 058 { 059 #if (CE_MAJOR_VER == 0x0003) 060 if (ticksleft || (dwSleepMin && (dwSleepMin <= DiffMSec)) 061 || (dwPreempt && (dwPreempt <= DiffMSec))) 062 #else 063 if ((int) (CurMSec - dwReschedTime) >= 0) 064 #endif 065 ulRet = SYSINTR_RESCHED; 066 } 067 } 068 else 069 { 070 #if (CE_MAJOR_VER == 0x0003) 071 if (ticksleft || (dwSleepMin && (dwSleepMin <= DiffMSec)) || 072 (dwPreempt && (dwPreempt <= DiffMSec))) 073 #else 074 if ((int) (CurMSec - dwReschedTime) >= 0) 075 #endif 076 ulRet = SYSINTR_RESCHED; 077 } 078 } 079 080 // 081 // Check if a reboot was requested. 082 // 083 if (dwRebootAddress) 084 { 085 RebootHandler(); 086 } 087 } 088 else if (ucCurrentInterrupt == INTR_RTC) 089 { 090 UCHAR cStatusC; 091 // Check to see if this was an alarm interrupt 092 cStatusC = CMOS_Read( RTC_STATUS_C); 093 if((cStatusC & (RTC_SRC_IRQ|RTC_SRC_US)) == (RTC_SRC_IRQ|RTC_SRC_US)) 094 ulRet = SYSINTR_RTC_ALARM; 095 } 096 else if (ucCurrentInterrupt <= INTR_MAXIMUM) 097 { 098 // We have a physical interrupt ID, but want to return a SYSINTR_ID 099 100 // Call interrupt chain to see if any installed ISRs handle this 101 // interrupt 102 ulRet = NKCallIntChain(ucCurrentInterrupt); 103 104 if (ulRet == SYSINTR_CHAIN) 105 { 106 ulRet = OEMTranslateIrq(ucCurrentInterrupt); 107 if (ulRet != -1) 108 PICEnableInterrupt(ucCurrentInterrupt, FALSE); 109 else 110 ulRet = SYSINTR_NOP; 111 } 112 else 113 { 114 PICEnableInterrupt(ucCurrentInterrupt, FALSE); 115 } 116 } 117 if (ucCurrentInterrupt > 7 || ucCurrentInterrupt == -2) 118 { 119 __asm 120 { 121 mov al, 020h ; Nonspecific EOI 122 out 0A0h, al 123 } 124 } 125 __asm 126 { 127 mov al, 020h ; Nonspecific EOI 128 out 020h, al 129 } 130 return ulRet; 131 }
  All hardware interrupts are mapped to and handled in this ISR. Line 018 gets the current interrupt number. Line 020 and 088 handle the timer 0 interrupt and the RTC (Real Time Clock) interrupt respectively. If the interrupt is some other interrupt, line 096 does a quick validation, calls any chained ISRs (see function NKCallIntChain in the MSDN), translates the interrupt number into a SYSINTR_ value, disables the interrupt and finally returns the SYSINTR_ value in ulRet. If the Irq to SYSINTR_ mapping could not be found, ulRet is filled with SYSINTR_NOP. Any registered IST (Interrupt Service Thread) event is set according to the SYSINTR_ return value of the ISR. An IST is registered by calling InterruptInitialize:
  In the above function, the event hEvent is mapped to the ISR return value SYSINTR_SOFT1MS.
  Finally the ISR let's the programmable interrupt controller know the interrupt is handled by writing the EOI (End Of Interrupt) value (0x20) to it. If the interrupt number is bigger then 7, the second PIC has to be notified first (the two PIC controllers are cascaded through interrupt line 2).
  Since we adjusted the timer frequency, we also have to adjust the above ISR, because now, the ISR is called twice as often as normal, and thus the scheduler is also working double times (scheduled times are divided by 2 per thread).
  First of all, we have to declare a static Boolean, to be able to toggle the ISR behavior when a timer0 interrupt occurs:
  We have to toggle the behavior for the timer0 interrupt only:
  020 if (ucCurrentInterrupt == INTR_TIMER0) 021 { #ifdef USE_SOFT_1MS bToggle = !bToggle; // Toggle value if (bToggle) { #endif 022 if (PProfileInterrupt) 023 { 024 ulRet= PProfileInterrupt(); 025 } 026 else 027 { // Lines 028 to 077 are unchanged, and not showed here to save // the rainforest... 078 } 079 080 // 081 // Check if a reboot was requested. 082 // 083 if (dwRebootAddress) 084 { 085 RebootHandler(); 086 } #ifdef USE_SOFT_1MS } else { ulRet = SYSINTR_SOFT1MS; } #endif 087 } 088 else if (ucCurrentInterrupt == INTR_RTC) 089 { // Append rest of code here
  The behavior when a timer0 interrupt occurs now toggles between 'running normal CE ISR code' and 'returning SYSINTR_SOFT1MS'. We can now use InterruptInitialize with the SYSINTR_SOFT1MS value to bind an event to the timer0 interrupt. This event will then be pulsed every 1 ms.
  Modifying oalintr.h
  You are free to use any SYSINTR_FIRMWARE based value (like (SYSINTR_FIRMWARE+20), as long as you modify the OEMInterruptEnable function as described below.
  Modifying the OEMInterruptEnable function
  We also have to change the OEMInterruptEnable function inside cfwpc.c to make sure this function always succeeds for our timer0 interrupt. If we don't do this, the InterruptInitialize function will fail for the SYSINTR_SOFT1MS interrupt. Add the following lines to the function:
  Building the platform
  Because we changed some kernel code, we have to do a complete build of the kernel, including a rebuild of the dependency tree. First save all changed files, then choose Options in the Tools menu of the Platform Builder, and click on the Build tab. Now make sure Enable Deptree Build is selected. You can now start rebuilding the entire platform by clicking Rebuild Platform from the Build menu. When everything is done, deselect Enable Deptree Build from the Build tab of the Tools->Options menu.
  About the Author
  Michel Verhagen has been a Windows CE. NET consultant for PTS Software bv since 2000, specializing in building complex Windows CE platforms and device drivers for industrial appliances for customers in the Netherlands. As such he is one of the few Dutch developers specializing in Windows CE.NET and the only eMVP in the Netherlands. In the past he has been involved in evaluating Windows CE 3.0 as far as real-time behavior is concerned. Recently Michel has evaluated the real-time behavior of the .NET Compact Framework, using a mix of managed- and unmanaged code in combination with Windows CE.NET 4.1. The whitepaper about this subject has recently been awarded by Microsoft with the Technical Excellence Award 2003. When you need Michel's expertise, you can always count on him in one of the Microsoft embedded newsgroups. When Michel is not responding in real-time, he can most likely be found at cloudbase under his paraglider.
  Additional Resources:
  Feedback:
  To provide feedback about this whitepaper, please send e-mail to michel.verhagen@pts.nl
  For Additional Information
  For more information about Windows CE .NET, see the Windows CE .NET home page.
  For online documentation and context-sensitive Help included with Windows CE .NET, see Windows CE .NET product documentation.
  Acronyms and Terms
  µs microsecond (1 / 1000000 second)
  API Application Programming Interface
  CPU Central Processing Unit
  EOI End Of Interrupt
  ISA Industry Standard Architecture
  ISR Interrupt Service Routine
  IST Interrupt Service Thread
  ms millisecond (1 / 1000 second)
  OAK OEM Adaptation Kit
  OAL OEM Abstraction Layer
  OS Operating System
  PC Personal Computer
  PCI Peripheral Component Interconnect
  PIC Programmable Interrupt Controller
  PIT Programmable Interval Timer
  RTC Real Time Clock