技术开发 频道

Windows CE .NET平台上实现坚固的计时器

 【IT168技术文档】Windows CE.NET 确实通过 QueryPerformanceCounter API 为高分辨率计时器提供了现成的解决方案。如果您必须延迟一小段时间,则使用该 API 非常有效,但如果您希望等待一小段时间,又该如何呢?延迟和等待之间的差别在于:延迟比等待消耗更多的 CPU 时间。等待意味着系统中的其他(优先级较低或相等的)线程可以在等待期间执行。

 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);

 }

 }

 以最高优先级(优先级 0)运行上述代码将在延迟期间阻塞整个 OS。

 // !!! PSEUDO CODE !!!

 HANDLE hTimer = CreateHighResolutionTimer();

 SetHighResolutionTimeout(hTimer, GetHighResolutionTimer() + DELAY);

 WaitForSingleObject(hTimer, INFINITE);

 当我们在优先级 0 运行第二个示例时,线程会在等待期间释放 CPU。因此,在这些小段时间内,其他线程可以取得 CPU 的拥有权并完成它们的工作。遗憾的是,在 Windows CE .NET 中未实现上述 HighResolutionTimer API。

 休眠分辨率

 如果您已经阅读本文档的简介,则可能会认为它含有一个打字错误:

 “Windows CE (...) 具有 1 ms 的内部计时器信号分辨率。对于大多数项目而言,2 ms 的精确度就足够了,(...)”。

 如果 CE 具有 1 ms 的分辨率,则您可能认为那也是您可以等待的最小时间。可惜的是,事实并非如此,因为如果我们在系统计时器滴答(重新安排时间滴答)10 μs 之后发出“Sleep(1),”,则休眠计数器会在下一次滴答时启动,并且在接下来的那次滴答时终止。这使我们获得了 1.90 ms 的休眠,而不是预期的 1 ms。一般说来,Sleep(N) 将休眠 N 到 (N+1) ms。

 硬件解决方案

 PC 硬件体系结构只提供 1 个计时器,它在物理上连接到某根中断线,并且该计时器已经由 Windows CE 内核使用。CE 内核对该计时器进行编程,使其每毫秒生成一个中断,并且将该中断主要用于线程计划程序和其他一些功能。如果 PC 体系结构能够加入一些备用的中断计时器,则我们在应用 x86 CEPC 的时候就不会如此困难。当然,您可以在 ISA 或 PCI 总线上的某个位置添加一个简单的可编程计时器芯片,但为什么不尝试用软件实现高分辨率计时器呢?

 对计时器进行重新编程

 生成硬实时 1 ms 中断的唯一方法是对 PIT(可编程间隔计时器,在 PC 硬件中通常为 82C54 或衍生物)进行重新编程,使其快于 1ms。OAL 中的分析代码使用了类似的技术(参见 OEMProfileTimerEnable)。Windows CE 用于对 PIT 进行编程的代码位于 OAL(OEM 适应层)中。OAL 源代码文件位于 \WINCE410\PUBLIC\COMMON\OAK\CSP\I486\OAL1 中。Windows CE 使用 timer.c 内部的 InitClock 函数对 PIT 进行编程:

 //

 // Setup Timer0 to fire every TICK_RATE mS and generate

 // interrupt

 //

 SetTimer0(TIMER_COUNT);

 PICEnableInterrupt(INTR_TIMER0, TRUE);

 dwReschedPeriod = TIMER_COUNT;

 1. 我使用原始路径来指向 OAL 源代码,当然您应该将 OAL 代码从 PUBLIC 树移动到您自己的 BSP,并且在那里修改它。绝对不要在 PUBLIC 树中修改任何代码;Microsoft 可能使用 QFE 更新它。

 创建 1 ms 中断的最简便方法是将中断速度加倍并切换该行为。该行为是在主中断服务例程(将在下面对其进行讨论)中编码的。

 要使计时器中断的速度加倍,可以用 TIMER_COUNT / 2 加载该计时器,如下所示:

 //

 // 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;

 现在,timer0 中断将每 500 微秒 (0.5 ms) 发生一次。

 我已经在修改过的代码前后添加了 #ifdefs,以使其能够稍微容易一些返回到原始 CE 代码。

 修改 ISR

 主 ISR 位于 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    }

0
相关文章