织梦CMS - 轻松建站从此开始!

罗索

如何实现坚固的 Windows CE 计时器

jackyhwei 发布于 2009-12-23 17:54 点击:次 
Windows CE .NET 是来自 Microsoft 的嵌入式组件化 OS,它具有 1 微秒 (ms) 的内部计时器信号分辨率。对于大多数项目而言,2 ms 的精确度就足够了,但某些项目却需要较高分辨率的非阻塞计时器。CE API 没有提供此类现成功能,但通过对 OAL 加以略微修改,我们可以获得分
TAG:

Windows CE .NET 是来自 Microsoft 的嵌入式组件化 OS,它具有 1 微秒 (ms) 的内部计时器信号分辨率。对于大多数项目而言,2 ms 的精确度就足够了,但某些项目却需要较高分辨率的非阻塞计时器。CE API 没有提供此类现成功能,但通过对 OAL 加以略微修改,我们可以获得分辨率高于 2ms 的坚固的非阻塞计时器。

QueryPerformanceCounter
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 }
所有硬件中断都被映射到该 ISR 并在该 ISR 中进行处理。线 018 得到当前的中断号。线 020 和 088 分别处理计时器 0 中断和 RTC(实时时钟)中断。如果该中断是其他某种中断,则线 096 会执行快速验证,调用任何链化的 ISR(参见 MSDN 中的函数 NKCallIntChain),将中断号转换为 SYSINTR_ 值,禁用该中断,并且最后在 ulRet 中返回 SYSINTR_ 值。如果找不到 SYSINTR_ 映射的 Irq,则用 SYSINTR_NOP 填充 ulRet。任何已注册的 IST(中断服务线程)事件都按照 ISR 的 SYSINTR_ 返回值进行设置。通过调用 InterruptInitialize 来注册 IST:

InterruptInitialize(SYSINTR_SOFT1MS, hEvent, NULL, 0);
在上述函数中,事件 hEvent 被映射到 ISR 返回值 SYSINTR_SOFT1MS。

最后,ISR 通过向可编程中断控制器写 EOI(中断结束)值 (0x20),通知它中断已被处理。如果中断号大于 7,则必须首先通知第二个 PIC(两个 PIC 控制器通过中断线 2 级联)。

因为我们调整了计时器频率,所以我们还必须调整上述 ISR,原因是现在 ISR 通常被调用两次,因此计划程序也工作两倍的次数(对于每个线程,计划次数被除以 2)。

首先,我们必须声明一个静态布尔值,以便能够在 timer0 中断发生时切换 ISR 行为:

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
我们必须只为 timer0 中断切换该行为:

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
现在,发生 timer0 中断时的行为在“运行正常的 CE ISR 代码”和“返回 SYSINTR_SOFT1MS”之间切换。我们现在可以通过 SYSINTR_SOFT1MS 值使用 InterruptInitialize,以便将某个事件绑定到 timer0 中断。然后,该事件将每 1 ms 产生一次。

修改 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
只要您按照下面的说明修改 OEMInterruptEnable 函数,就可以随便使用任何基于 SYSINTR_FIRMWARE 的值(就像 SYSINTR_FIRMWARE+20 一样)。

修改 OEMInterruptEnable 函数
我们还必须更改 cfwpc.c 内部的 OEMInterruptEnable 函数,以确保对于我们的 timer0 中断,该函数总是成功。如果我们不这样做,则对于 SYSINTR_SOFT1MS 中断,InterruptInitialize 函数将失败。将下面的代码行添加到该函数:

#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
生成平台
因为我们更改了一些内核代码,所以必须对内核进行完整的生成,包括重新生成依赖项树。首先,保存所有已更改的文件,然后在 Platform Builder 的“Tools”菜单中选择“Options”,并单击“Build”选项卡。现在,确保“Enable Deptree Build”被选中。此时,就可以通过单击“Build”菜单中的“Rebuild Platform”重新生成整个平台了。完成所有工作以后,请从“Tools”->“Options”菜单的“Build”选项卡中取消选中“Enable Deptree Build”。


关于作者
Michel Verhagen 自 2000 年以来一直担任 PTS Software bv 的 Windows CE.NET 顾问,致力于为荷兰的客户生成用于工业设备的复杂的 Windows CE 平台和设备驱动程序。因此,他是荷兰仅有的几名专门研究 Windows CE.NET 的开发人员之一,并且是荷兰唯一的 eMVP。过去,他已经参与了对 Windows CE 3.0 的实时行为的评估。最近,Michel 已经混合使用托管代码和非托管代码并结合使用 Windows CE.NET 4.1,对 .NET Compact Framework 的实时行为进行了评估。有关该主题的白皮书最近已被 Microsoft 授予 2003 年度技术卓越奖。当您需要 Michel 的专业知识时,您总是可以在 Microsoft 嵌入式新闻组之一中向他求教。如果 Michel 没有及时答复,那么他很可能正在驾着自己的滑翔伞翱翔于云端。

(Michel Verhagen )
本站文章除注明转载外,均为本站原创或编译欢迎任何形式的转载,但请务必注明出处,尊重他人劳动,同学习共成长。转载请注明:文章转载自:罗索实验室 [http://www.rosoo.net/a/200912/8175.html]
本文出处:MSDN.Microsoft.Com 作者:Michel Verhagen
顶一下
(0)
0%
踩一下
(0)
0%
------分隔线----------------------------
发表评论
请自觉遵守互联网相关的政策法规,严禁发布色情、暴力、反动的言论。
评价:
表情:
用户名: 验证码:点击我更换图片
栏目列表
将本文分享到微信
织梦二维码生成器
推荐内容