Slavik
驱动牛犊
驱动牛犊
  • 注册日期2004-06-08
  • 最后登录2004-08-16
  • 粉丝0
  • 关注0
  • 积分0分
  • 威望0点
  • 贡献值0点
  • 好评度0点
  • 原创分0分
  • 专家分0分
阅读:2708回复:6

如何在wince下使用时间中断

楼主#
更多 发布于:2004-06-11 17:44
请问如何在wince下使用时间中断?
我现在需要对一个脉冲计数,Timer精度达不到要求。请问如果将一个函数用时间中断启动?
yejp
驱动牛犊
驱动牛犊
  • 注册日期2001-12-18
  • 最后登录2007-12-28
  • 粉丝0
  • 关注0
  • 积分20分
  • 威望2点
  • 贡献值0点
  • 好评度2点
  • 原创分0分
  • 专家分0分
沙发#
发布于:2004-06-15 17:27
http://msdn.microsoft.com/library/default.asp?url=/nhp/Default.asp?contentid=28000437

微软的文章,你自己看看吧!
Slavik
驱动牛犊
驱动牛犊
  • 注册日期2004-06-08
  • 最后登录2004-08-16
  • 粉丝0
  • 关注0
  • 积分0分
  • 威望0点
  • 贡献值0点
  • 好评度0点
  • 原创分0分
  • 专家分0分
板凳#
发布于:2004-06-15 22:57
看不到,什么都没有,只有一个目录
aaab01
驱动牛犊
驱动牛犊
  • 注册日期2002-03-12
  • 最后登录2008-07-31
  • 粉丝0
  • 关注0
  • 积分0分
  • 威望0点
  • 贡献值0点
  • 好评度0点
  • 原创分0分
  • 专家分0分
地板#
发布于:2004-06-16 22:07
要占用一个硬件定时器,先设定好硬件定时器,然后再挂中断到这个定时器上。
Slavik
驱动牛犊
驱动牛犊
  • 注册日期2004-06-08
  • 最后登录2004-08-16
  • 粉丝0
  • 关注0
  • 积分0分
  • 威望0点
  • 贡献值0点
  • 好评度0点
  • 原创分0分
  • 专家分0分
地下室#
发布于:2004-06-17 06:39
能说得具体点吗?
yejp
驱动牛犊
驱动牛犊
  • 注册日期2001-12-18
  • 最后登录2007-12-28
  • 粉丝0
  • 关注0
  • 积分20分
  • 威望2点
  • 贡献值0点
  • 好评度2点
  • 原创分0分
  • 专家分0分
5楼#
发布于:2004-06-17 09:01
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.

// !!! PSEUDO CODE !!!
HANDLE hTimer = CreateHighResolutionTimer();
SetHighResolutionTimeout(hTimer, GetHighResolutionTimer() + DELAY);
WaitForSingleObject(hTimer, INFINITE);

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 &micro;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:

   //
   // Setup Timer0 to fire every TICK_RATE mS and generate
   // interrupt
   //
   SetTimer0(TIMER_COUNT);

   PICEnableInterrupt(INTR_TIMER0, TRUE);

   dwReschedPeriod = TIMER_COUNT;

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:

   //
   // Setup Timer0 to fire every TICK_RATE mS and generate
   // interrupt
   //
   // Twice as fast for software 1ms timer
#define USE_SOFT_1MS
#ifdef USE_SOFT_1MS
   SetTimer0(TIMER_COUNT / 2);
#else
   SetTimer0(TIMER_COUNT);
#endif
   PICEnableInterrupt(INTR_TIMER0, TRUE);

   dwReschedPeriod = TIMER_COUNT;

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:

InterruptInitialize(SYSINTR_SOFT1MS, hEvent, NULL, 0);

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:

001   ULONG PeRPISR(void)
002   {
003      ULONG ulRet = SYSINTR_NOP;
004      UCHAR ucCurrentInterrupt;
         #define USE_SOFT_1MS
         #ifdef USE_SOFT_1MS
            static BOOL bToggle = FALSE;
         #endif
005  
006      if (fIntrTime)
007      {
            // Append rest of code here

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
Before we can use the SYSINTR_SOFT1MS value we have to define it in oalintr.h, which resides in \WINCE410\PUBLIC\COMMON\OAK\CSP\I486\INC, like this:
#define USE_SOFT_1MS
#ifdef USE_SOFT_1MS
#define SYSINTR_SOFT1MS       (SYSINTR_FIRMWARE+6)
#endif

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:

#define USE_SOFT_1MS
#ifdef USE_SOFT_1MS
if (idInt == SYSINTR_SOFT1MS)
{
   DEBUGMSG (1, (TEXT("Accepting the soft 1ms interrupt enable request.\r\n")));
   return (TRUE);
}
#endif

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:

www.pts.nl

www.dotnetfordevices.nl

www.microsoft.com/windows/embedded/ce.net/default.asp

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
&micro;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
Slavik
驱动牛犊
驱动牛犊
  • 注册日期2004-06-08
  • 最后登录2004-08-16
  • 粉丝0
  • 关注0
  • 积分0分
  • 威望0点
  • 贡献值0点
  • 好评度0点
  • 原创分0分
  • 专家分0分
6楼#
发布于:2004-06-17 15:11
谢谢
游客

返回顶部