编写可靠的Windows CE代码

编写可靠的本机代码


2003年1月

Nat Frampton(请参阅 Nat 的 eMVP 传记)Real Time Development Corp.
Microsoft Embedded MVP 主管

适用于:
    Microsoft(r) Windows(r) CE .NET
    Microsoft(r) eMbedded Visual C++(r)
    Microsoft(r) Windows(r) .NET Framework 精简版

摘要:通过开发一个显示和实时控制 MFC 直方图的应用程序,来学习 eMbedded Visual C++ 环境的开发过程和功能,同时还将学习 Visual C++ 本机代码和 .NET Framework 精简版的各种强大功能。

下载本文讨论的示例应用程序的源代码。(请注意,在示例文件中,程序员的注释使用的是英文,本文中将其译为中文是为了便于读者理解。)

目录

  • 概述
  • 本机与 .NET Framework 精简版开发
  • 中断体系结构
  • 应用程序中断处理
  • 应用程序体系结构
  • 控制器开发 - DemoControl.exe
  • 显示代码 - DemoView.exe
  • 小结

概述

对于 Microsoft(r) Windows(r) CE .NET Embedded 的开发人员来说,有多种开发方法可以选择。本机 Microsoft eMbedded Visual C++(r) 和新的 .NET Framework 精简版提供了两种截然不同的应用程序开发方法。以 eMbedded Visual C++ 4.0 来开发本机代码,仍然是最适合用来开发高速代码的开发环境,而且它开发的代码能够充分利用 Windows CE .NET 平台的实时功能。本次学习通过开发一个显示和实时控制 MFC 直方图的应用程序,来探索 eMbedded Visual C++ 环境的开发过程和功能,还将比较 Visual C++ 本机代码与 .NET Framework 精简版。控制应用程序开发将利用 Microsoft Win32 线程和同步对象、中断接口、共享内存接口技术以及实时优先级策略;MFC 应用程序设计的主题包括显示开发、实时数据接口技术和直方图库集成。两种应用程序的开发都是从头开始的,只利用了很少几个源代码格式的可用库。

本机与 .NET Framework 精简版开发

选择本机开发还是选择 .NET Framework 精简版开发是一个非常复杂的问题,需要综合分析应用程序工具、平台要求和开发专家的意见,才能明确选择标准。下一节为这种选择提供了高级指南,引导您进入开发过程。

应用程序开发工具选择

对于 Microsoft Windows CE .NET Embedded 开发人员来说,有许多应用程序开发工具可供选择,而在 Windows CE .NET 中集成 .NET Framework 精简版进一步扩大了选择范围。

Platform Builder(平台生成器)是开发操作系统、驱动程序和服务的首选工具。它充当嵌入式平台开发程序时,能够紧密集成操作系统配置、驱动程序开发、内核级调试以及其他许多此处未列举的功能。但 Platform Builder(平台生成器)不适合用来开发大型应用程序。

本机应用程序是直接为特定处理器编译的应用程序。从 Microsoft Visual C++ 的 Build(生成)菜单中选择 Build MyApp.exe(生成 MyApp.exe)时,编译器将创建一个本机应用程序。在引入 Framework 精简版之前,Microsoft eMbedded Visual C++ 和 Microsoft Visual Basic(r) 一直是所有 Windows CE 应用程序开发的基础。在 Visual C++ 中,有两个主要选项可用于应用程序开发。Windows CE .NET 是围绕 Microsoft Win32(r) API 构建的,而 Win32 API 可以在本机应用程序中通过编程方式直接使用。由于存在重复的应用程序开发过程,所以出现了一系列封装了大多数常见任务的类库,这些类库称为 Microsoft 基础类 (MFC) 库。eMbedded Visual C++ 允许嵌入式开发人员利用 MFC 库,为可视应用程序提供图形化开发环境。

随着 Framework 精简版的引入,现在可以在 Visual Studio .NET 环境中开发应用程序,使之同时适用于桌面和 Windows CE .NET 公共语言运行库 (CLR)。从而允许对 Windows CE .NET 进行 Visual Basic .NET 和 C# .NET 开发。

开发 API 选择

Window CE .NET 开发人员可以选择本机 Win32、本机 MFC 或 Framework 精简版应用程序开发。下表概要介绍了这些选择:

  • 空间 - 介绍预期的平台要求
  • 优势与劣势 - 列举基本的选择

表 1:空间

空间要求Windows CE .NETWindows XP
Win32--
MFC280 KB1.25 MB
.NET Framework1.5 MB34 MB

表 2:优势与劣势

API优势劣势
Microsoft Win32

(C / C++)

  • 最小和最快的 .exe 文件和 DLL

    不需要其他运行时,Windows CE .NET 就是运行时。

  • 最低的内存开销
  • 对于设备驱动程序、控制面板小程序和 shell 扩展是必需的
  • 应用程序/驱动程序开发人员负责清理对象,使得此 API 很容易出现内存泄露。
  • 低级 API - 可能难以掌握。
  • 面向过程而不是面向对象的 API。
MFC

(C++)

  • 面向对象。继承、封装、多态(也称为功能重载)。

    良好的工具支持和向导

  • 容器类支持数组、列表、对象映射,并能简化数据处理。
  • 类型安全。
  • Embedded Visual 工具附带完整的 MFC 源代码。
  • 对象清理是半自动的,因此与 Win32 相比不易于出现内存泄露,但由于 MFC 是对 Win32 的简单包装,所以仍然易受攻击。
  • 运行时大小 ~ 500 KB(MFC 和 OLECE)
.NET Framework

(C# 和 Microsoft Visual Basic .NET)

  • 精心设计的编程界面。

    优秀的工具支持 - Forms Designer(窗体设计器)。

  • 面向对象。继承、封装、多态(也称为功能重载)。
  • 容器类支持数组、列表、散列表、字典和堆栈。
  • 类型安全。
  • 命名空间。
  • 自动进行内存回收,可以消除内存泄露。
  • 便携式计算机指令集 MSIL / CIL,提供二进制可移植的可执行(.exe 和 .dll)文件。
  • 可以便捷地编写 Web 服务客户端。
  • 很好地支持处理 XML
  • 运行时大小 ~ 1.5 MB。
  • 在托管代码和非托管代码之间的调用开销很高。COM 互操作性不够灵活。要求编写需要调用 COM 接口函数的 Win32 包装程序。
  • 源代码不可用。
  • 要求基于显示器的平台。

要在资源有限的平台上开发高速控制应用程序,显然应该选择本机 Win32 进行控制器的开发,选择本机 MFC 进行可视化开发。在许多情况下,开发环境的选择可能很简单,但在另外一些情况下,可能会很复杂。

中断体系结构

以下插图是 Windows CE .NET 的中断体系结构的应用程序视图,说明了中断期间的硬件、内核、OAL 和线程的交互操作。

图 1:Windows CE .NET 中断

图中以向右的方向表示时间的推移。最底层是硬件的状态;上一层是 Windows CE OEM 适配层 (OAL),描述板卡支持程序包 (BSP) 的任务;最顶层代表服务和中断所需的应用程序或驱动程序线程交互操作。

活动从图表左侧部分以直线表示的中断开始。首先生成异常,导致内核中断服务例程 (ISR) 矢量被加载到处理器中。内核 ISR 与硬件交互,禁用所有处理器上的所有优先级相等和较低的中断(ARM 和 Strong ARM 体系结构除外)。然后,内核推进到已为该中断注册的 OAL ISR。接着 OAL ISR 检查该硬件以确定其是否导致了中断。这一操作通常是通过检查中断状态寄存器完成的。如果是该硬件导致了中断,ISR 将返回该硬件的 SYSID。

一旦 ISR 完成检查,内核将重新启用处理器上除已标识的中断之外的所有中断。然后,内核通知与 SYSID 值关联的事件。

如果驱动程序或应用程序的中断服务线程 (IST) 是要运行的线程中优先级最高的,则接下来就会运行该线程。IST 将与相关设备通信,并从设备中读取所有必要的数据,完成其中断交互操作。然后,IST 用关联的 SYSID 值来调用 InterruptDone( ),通知其已完成运行。

内核接收到 SYSID 值的 InterruptDone 时,将重新启用指定的中断。这时可以开始接收该设备的另一个中断。

这是对 Windows CE .NET 内部活动的中断序列的一个快速浏览。插图代表了一次中断期间的交互操作,但没有显示 Windows CE .NET 的共享中断功能。有关这项功能的详细信息,请参阅 Interrupt Architecture in Windows CE .NET(英文)。

应用程序中断处理

处理应用程序或驱动程序的中断需要两个步骤。首先,中断必须使用关联的事件进行初始化。其次,IST 必须等待响应内核中断的中断事件。

中断初始化

以下示例代码将设置 IST 并将 IST 与特定的中断相关联。初始化中断的关键步骤包括:

  • 创建事件
  • 获取 IRO 的系统中断号
  • 创建挂起的中断服务线程 (IST)
  • 调用 InterruptInitialize 以创建 IRQ 与事件的关联
创建未挂起的 IST 可能会导致 InterruptInitialize 失败,因为该事件已经处于等待状态。
  • 将线程优先级设置为相应的优先级
  • 恢复 IST
Void SetupInterrupt( void )
{
// 创建事件
//
g_hevInterrupt = CreateEvent(NULL, FALSE, FALSE, NULL);
if (g_hevInterrupt == NULL) 
{
      RETAILMSG(1, (TEXT("DEMO: Event creation failed!!!/r/n")));
      return;
}


// 使 OAL 将 IRQ 转换成系统 IRQ
//
fRetVal      = KernelIoControl( IOCTL_HAL_TRANSLATE_IRQ, 
                        &dwIrq,
                        sizeof( dwIrq ),
                        &g_dwSysInt,
                        sizeof( g_dwSysInt ),
                        NULL );


// 创建等待信号的线程
//
g_fRun   = TRUE;
g_htIST         = CreateThread(NULL,   // 安全性
               0,       // 没有堆大小
               ThreadIST,   // 中断线程
               NULL,      // 没有参数
               CREATE_SUSPENDED, // 创建挂起的线程
               &dwThreadID   // 线程 ID
               );


// 设置线程的优先级 - 随意选择了 5
//
m_nISTPriority = 5;
if( !CeSetThreadPriority( g_htIST, m_nISTPriority ))
{
      RETAILMSG(1,(TEXT("DEMO: Failed setting Thread Priority./r/n")));
      return;
}


// 初始化中断
//
if ( !InterruptInitialize(g_dwSysInt,g_hevInterrupt,NULL,0) ) 
{
      RETAILMSG (1, (TEXT("DEMO: InterruptInitialize failed!!!/r/n")));
      return;
}

// 使线程启动
//
ResumeThread( g_htIST );

}

需要特别注意的是,调用 InterruptInitialize 仅获取 SYSINTR 值和事件。内核不知道或者说也不关心将要等待事件的线程。这样一来,就可以建立多种应用程序和驱动程序体系结构。应用程序的简单主循环可以初始化中断,然后立即等待事件。一个中断只能与一个事件关联,并且调用 WaitForMultipleObjects 的过程中不能使用该事件。我们将会看到一个简单的线程为中断服务。这是大多数实现方案中的标准解决方法。

应用程序中断服务例程:

以下是中断服务线程 (IST) 的示例代码。此 IST 中断处理线程的关键组件包括:

  • 等待中断事件
  • 确认有一个来自操作系统的事件
  • 在尽可能短的时间内处理中断
  • 调用 InterruptDone()
在调用 InterruptDone 之前,操作系统不会提供有关此 IRQ 的另一个中断。
  • 再次等待中断事件
DWORD   WINAPI   ThreadIST( LPVOID lpvParam )
{
   DWORD   dwStatus;

   // 始终检查运行标志
   //
   while( g_fRun )
   {
      dwStatus   = WaitForSingleObject(g_hevInterrupt, INFINITE);

      // 确保拥有对象
      //
      if( dwStatus == WAIT_OBJECT_0 )
      {
         // 在此处理中断
         //
         g_dwInterruptCount ++;

         // 完成中断
         //
         InterruptDone( g_dwSysInt );

      }
   }

   return 0;
}

优先级

初始化代码中的关键 Win32 API 调用是对 CeSetThreadPriority 的调用。此函数接受两个参数。第一个参数是线程句柄,第二个值介于 0-255 之间,用于描述所需的优先级。选择使用哪个线程优先级非常关键,而能够以图表表现应用程序优先级的使用,也有助于确保适当的性能。优先级从 0 至 247 的线程(0 表示最高优先级)是实时线程优先级,需要调用 CeSetThreadPriority 来访问。一般线程优先级介于 248-255 之间,要使用 SetThreadPriority 进行访问。下表提供了 Windows CE .NET 标准优先级实现的快速指南。

表 3:实时线程优先级:CeSetThreadPriority

优先级组件
0-19开放 - 高于驱动程序的实时
20Permedia 垂直折返
21-98开放 - 高于驱动程序的实时
99电源管理恢复线程
100-108USB OHCI UHCI、串行
109-129Irsir1、NDIS、触摸板
130KITL
131VMini
132CxPort
133-144开放 - 设备驱动程序
145PS2 键盘
146-147开放 - 设备驱动程序
148IRComm
149开放 - 设备驱动程序
150TAPI
151-152开放 - 设备驱动程序
153-247开放 - 低于驱动程序的实时

表 4:一般线程优先级:SetThreadPriority

优先级 组件
248电源管理
249WaveDev、TVIA5000、鼠标、PnP、电源
250WaveAPI
251电源管理器电池线程
252-255开放

一般来说,最先需要决定的是要确定关键线程是否需要驱动程序。如果关键线程需要驱动程序才能正常工作,而将它的优先级设定为高于驱动程序的优先级,则很难获得好的性能。总之,时间关键型应用程序需要放在“高于驱动程序类别的实时”类别中,优先级范围为 0-98。

应用程序体系结构

同时使用 DemoControl 和 View 应用程序,可以提供对平台硬件的中断状态的控制和显示功能。在此演示中使用的是来自 NML 的基于 SH4 的平台。演示平台包含一个连接到中断的简单按钮。按钮状态的改变将触发操作系统的中断。每次硬件开关的释放和按下状态变化都会收到中断。收到第一个中断时,DemoControl 应用程序开始每 5 毫秒计数一次,并递增 8 个内存容器之一的计数。每次按下按钮,当前的容器索引都会递增一。如果容器索引达到 8,则重置为 0。DemoView 应用程序的直方图将自动调整大小以显示 8 个容器中每一个的相对计数。直方图最终显示的是每个容器位置所花费的时间量。DemoView 还显示了平均容器计数、总容器计数和当前容器编号。

高速信息的显示是典型的实时接口题。如果实时控制器在绘制过程中更新容器计数,则显示屏显示的容器计数、平均数和总数可能有一部分不正确。对实时应用程序而言,在一个快照中获取所有数据非常关键,这样才能保持各个项的完全一致。这也被称为暂时一致性。为了在一个快照内获取数据(即暂时保持一致),最常用的方法是与实际控制线程同步,以便在需要时发送此快照。

演示策略使用了 Win32 中最常用的两个同步对象:命名事件和内存映射文件。事件提供了一种机制,使一个应用程序能够通知另一个应用程序它已达到某种状态。应用程序在得到通知以前可以一直等待这些事件。因为这些事件已命名,所以可以在应用程序之间引用。内存映射文件(按名称引用)提供了一种方法,使两个独立的应用程序可以查看同一个内存区域。

同步策略

以下插图概要描述了同步策略。

图 2:同步策略

同步按以下步骤循环进行:

  1. DemoView 应用程序设置复制事件,请求将新的统计信息复制到演示内存区域,然后等待完成事件。
  2. DemoControl 应用程序检查每个控制系统是否已设置复制事件。如果已设置,则转至步骤 3;否则将不断循环。
  3. DemoControl 更新演示内存区域中的数据。
  4. DemoControl 设置完成事件。
  5. 等待完成事件之后,DemoView 将被唤醒,重画屏幕,休眠 100 毫秒,然后返回步骤 1。

最小化控制线程的交互操作和要求具有最高的优先级。控制器在检查复制事件的状态时或者在将数据复制到内存区域时花费的时间很少。您可能会问,是否应该继续执行并在每个控制循环中复制内存。这样做的问题是 DemoView 可能正在将数据复制到其本地内存中,最终导致不一致的快照。

演示控制体系结构

为了支持同步和控制器要求,将实现以下的 DemoControl 体系结构:

图 3:演示控制体系结构

DemoControl 应用程序使用两个主要线程:IST 和主要控制线程。

一个简单的中断服务线程 (IST) 将响应按下按钮引起的中断。IST 将确保该中断表示的是按钮释放的状态。然后,IST 将增加容器索引并使 LED 闪烁。

控制线程将循环检查表示关闭的完成标志。如果未设置完成标志,控制线程将增加当前容器索引的容器值,然后计算当前的统计数据。控制线程将检查来自 DemoView 的复制请求事件,如果已设置,则将数据复制到演示内存区域。然后控制线程将设置完成事件,休眠 5 毫秒并重复循环。

中断服务线程必须尽快发挥作用而不应该被控制线程停滞,其优先级应该设置为 50。演示控制线程则不依赖于任何驱动程序,因此优先级为 60。

控制器开发 - DemoControl.exe

目录结构

建议您创建如下目录结构:

/DEMOTop 目录,可在任意位置

/DEMO/DemoControlPlace,用于 DemoControl 项目

/DEMO/DemoViewPlace,用于 DemoView 项目

/DEMO/LibPre,用于现有的 SharedMemory 和历史记录库

/DEMO/IncPre,用于现有的 DemoMemory.h 文件

应用程序设置

DemoControl.exe 是一个 Win32 应用程序。创建 Win32 应用程序包括两个步骤。先要选择一个新的 WCE 应用程序,然后要选择简单的 Windows CE .NET 应用程序,如下所示。

图 4:新建 WCE 应用程序对话框

要创建应用程序,请选择 WCE Application(WCE 应用程序),然后单击 OK(确定)。

图 5:WCE Application(WCE 应用程序)对话框

一些库和内存定义已经创建。创建内存映射文件的封装类包含在 SharedMemory.cpp 和 SharedMemory.h 文件中。演示内存区域在 DemoMemory.h 中定义。在您的项目中添加这三个文件,即产生以下 DemControl 文件列表。

图 6:DemControl 文件列表

库和包含文件分别位于 Include(包含文件)和 Lib(库)子目录下。将这些项映射到 Project Settings(项目设置)对话框的包含目录搜索路径。

图 7:Project Settings(项目设置)对话框中的库和包含文件

为了访问平台的硬件,请利用 CEDDK 库并将其添加到 Project Settings(项目设置)对话框的 Object Modules(对象模块)列表中。

图 8:添加到 Project Settings(项目设置)对话框的 Object Modules(对象模块)列表

演示内存区域 - DemoMemory.h

两个应用程序的全局内存结构已在 DemoMemory.h 中定义。DEMO_MEMORY_STRUCTURE 包含:

   nButton   Current Button Index from 0 -7
   ulButtonCount   Array of 8 Bins worth of Tick Counts
   ulAverage   Average Bin Value
   ulTotal   Total Tick Counts
   ulNumInts   Number of Interrupts
   fFinished   Terminate Controller Flag

// 新建文件 DemoMemory.h
//

// 定义
//
#define   DEMO_MEMORY_NAME         _T("DEMO_MEMORY")
#define   DEMO_COPY_EVENT_NAME         _T("DEMO_COPY")
#define   DEMO_COPY_FINISHED_EVENT_NAME      _T("DEMO_COPY_FINISHED")
#define   DEMO_NUM_BINS            8
#define   DEMO_IST_PRIORITY         50
#define   DEMO_CONTROL_PRIORITY         60

// 全局内存结构
//
typedef struct
{
   int      nButton;
   ULONG      ulButtonCount[ DEMO_NUM_BINS ];   
   ULONG      ulAverage;
   ULONG      ulTotal;
   ULONG      ulNumInts;

   BOOL      fFinished;

}DEMO_MEMORY_STRUCT,*DEMO_MEMORY_PTR;

“初始化”和“控制代码”节中的其余代码将取代由 eMbedded Visual C++ 生成的 DemoControl.cpp 默认代码。

初始化

// DemoControl.cpp:定义应用程序的入口点。
//

#include "stdafx.h"
#include "SharedMemory.h"
#include "DemoMemory.h"

#include <pkfuncs.h>
#include "celog.h"
#include "ceddk.h"
#include "Platform.h"

// 全局内存
//
CSharedMemory*          g_SM;
DEMO_MEMORY_PTR         g_pDM;
DEMO_MEMORY_STRUCT      g_CM;

HANDLE            g_hevInterrupt;
HANDLE            g_htIST;
DWORD             g_dwSysInt;      
HANDLE            g_htControl;
HANDLE            g_hevCopyRequest;
HANDLE            g_hevCopyFinished;

PVBYTE            g_pLEDPORT;
PVBYTE            g_pButtonPort;

// 定义
//
#define BUTTON_MASK     0x20000000
#define LED_ON          0x0C
#define LED_OFF         0x00

// 原型
//
DWORD             SetupMemory    ( void );
DWORD             SetupInterrupt ( void );
DWORD             SetupControl   ( void );
DWORD             LED            ( BYTE ucPort, BOOL fState  );
DWORD WINAPI      ThreadIST      ( LPVOID lpvParam );
DWORD WINAPI      ThreadControl  ( LPVOID lpvParam );

// 主要
//
int WINAPI WinMain(   HINSTANCE hInstance,
                      HINSTANCE hPrevInstance,
                      LPTSTR    lpCmdLine,
                      int       nCmdShow)
{
   DWORD   dwRet;

   dwRet = SetupMemory();
   if( dwRet )   return dwRet;
   dwRet = SetupInterrupt();
   if( dwRet )   return dwRet;
   dwRet = SetupControl();
   if( dwRet )   return dwRet;


   // 更新局部内存
   //
   memcpy( (PVOID)&g_CM, (PVOID)g_pDM, sizeof( DEMO_MEMORY_STRUCT ) ); 

   // 使线程开始执行
   //
   ResumeThread( g_htIST );
   ResumeThread( g_htControl );


   while( !g_pDM->fFinished )
   {
      Sleep( 500 );
   }

   return 0;
}

DWORD   SetupMemory( void )
{
   // 创建共享内存区域
   //
   g_SM   = new CSharedMemory( sizeof( DEMO_MEMORY_STRUCT ), DEMO_MEMORY_NAME );
   if( !g_SM ) return 1;

   // 从共享内存中获取内存
   //
   g_pDM = (DEMO_MEMORY_PTR)g_SM->GetMemory();
   if( !g_pDM ) return 2;

   // 清除内存
   //
   g_pDM->fFinished = FALSE;


   // 使用 CEDDK 函数来映射 LED 和按钮寄存器
   //
   LARGE_INTEGER      liAddress;
   liAddress.LowPart   = NET_LED_BASE;
   liAddress.HighPart   = 0;

   g_pLEDPORT = (PVBYTE) MmMapIoSpace ( liAddress, 0x10, FALSE );
   if( !g_pLEDPORT  ) return 3;

   liAddress.LowPart   = PCTRA;
   liAddress.HighPart   = 0;

   g_pButtonPort = (PVBYTE) MmMapIoSpace ( liAddress, 0x4, FALSE );
   if( !g_pButtonPort  ) return 4;
   return 0;
}

DWORD   SetupInterrupt( void )
   g_htIST         = CreateThread(  NULL,         // CE 安全性
                    0,          // 默认大小
                    ThreadIST,      // IST      
                       NULL,         // 没有参数
                    CREATE_SUSPENDED,   // 挂起
                    &dwThreadID      // 线程 ID
                           );
   if( !g_htIST )return 12;

   // 将线程优先级设置为实时
   //
   if( !CeSetThreadPriority( g_htIST, DEMO_IST_PRIORITY ))return 13;


   // 初始化中断
   //
   if ( !InterruptInitialize(g_dwSysInt,g_hevInterrupt,NULL,0) )return 14;

   return 0;
}

DWORD   SetupControl( void )
{
   DWORD   dwThreadID;

   // 创建事件
   //
   g_hevCopyRequest = CreateEvent(NULL, FALSE, FALSE, 
                  DEMO_COPY_EVENT_NAME );
   if(!g_hevCopyRequest) return 20;
   g_hevCopyFinished = CreateEvent(NULL, FALSE, FALSE, 
                   DEMO_COPY_FINISHED_EVENT_NAME );
   if(!g_hevCopyFinished) return 20;

   
   // 创建高优先级线程
   //
   g_htControl         = CreateThread(   NULL,   
                        0,             
                        ThreadControl,
                          NULL,      
                        CREATE_SUSPENDED,
                        &dwThreadID );
   if( !g_htControl ) return 20;

   // 将线程优先级设置为实时
   //
   if( !CeSetThreadPriority( g_htControl, DEMO_CONTROL_PRIORITY )) return 21;

   return 0;
}


DWORD   LED( BYTE ucPort, BOOL fState  )
{
   if( !g_pLEDPORT  )return 0;

   if( fState ) WRITE_REGISTER_UCHAR( g_pLEDPORT, LED_ON | ucPort );  // 开
   else WRITE_REGISTER_UCHAR( g_pLEDPORT, LED_OFF | ucPort ); // 关

   return 0;
}

WinMain 初始化控制器的关键步骤包括:

  • 调用 SetupMemory:
    • 创建演示内存区域的新内存映射文件。
    • 通过 CEDDK.lib 映射 LED 和按钮的输入/输出 (I/O) 空间。
  • 调用 SetupInterrupt:
    • 创建中断事件和 IST,并且调用 InterruptInitialize 以挂起中断。
    • 将 IST 优先级设置为 50
  • 调用 SetupControl:
    • 创建或连接到复制请求事件。
    • 创建或连接到复制完成事件。
    • 创建控制线程。
    • 将控制线程的优先级设置为 60
  • 在演示内存的全局状态下复制。
  • 恢复 IST 和控制线程。
  • 等待设置 fFinished 标志。

控制代码

中断线程

ThreadIST 遵循上面的简单示例,并添加了针对硬件的代码以处理按钮和按钮索引。

DWORD   WINAPI   ThreadIST( LPVOID lpvParam )
{
   DWORD   dwStatus;
   BOOL   fState = TRUE;
   BOOL   fFirstTime = TRUE;

   while( !g_pDM->fFinished )
   {
      dwStatus   = WaitForSingleObject(g_hevInterrupt, INFINITE);

      // 检查是否已经完成
      //
      if(g_pDM->fFinished ) return 0;

      // 确保拥有对象
      //
      if( dwStatus == WAIT_OBJECT_0 )
      {
         // 仅检查按钮释放。在释放和按下时获取中断
         //
         if (!( READ_REGISTER_ULONG(g_pButtonPort) & BUTTON_MASK))
         {
            // 递增按钮
            //
            g_CM.ulNumInts++;
            if( !fFirstTime )
            {
               g_CM.nButton ++;
               if( g_CM.nButton == DEMO_NUM_BINS ) 
                   g_CM.nButton = 0;
            }
            else
               fFirstTime = FALSE;


            // 在 CELOG 外存储计数
            //
            CELOGDATA(   TRUE, 
                       CELID_RAW_LONG, 
                     &g_CM.ulNumInts, (WORD) (sizeof(DWORD)), 
                      1, CELZONE_MISC);

            // 闪烁 LED
            LED( 0, fState );
            fState   = !fState;

         }

         // 完成中断
         //
         InterruptDone( g_dwSysInt );

      }
   }

   return 0;
}

此 IST 的应用程序特有的步骤包括:

  • 收到中断时,IST 检查是否存在按钮释放事件。
  • 增加中断数目并清除第一次的标志。
  • CELOG 报告中断数目。
  • 切换 LED 状态。
  • 报告 InterruptDone

控制线程

下面是主要的控制器线程。

DWORD   WINAPI   ThreadControl( LPVOID lpvParam )
{
   int      i;
   DWORD   result;

   while( !g_pDM->fFinished )
   {

      // 递增当前的容器位置
      //
      if( g_CM.ulNumInts )
      {
         g_CM.ulTotal ++;
         g_CM.ulButtonCount[ g_CM.nButton ] ++;
         g_CM.ulAverage      = g_CM.ulTotal / DEMO_NUM_BINS;
      }

      // 获取要更新的请求
      //
      result = WaitForSingleObject( g_hevCopyRequest, 0 );

      // 检查是否发出了信号
      //
      if( result == WAIT_OBJECT_0 ) 
      {
         g_pDM->ulAverage      = g_CM.ulAverage;
         g_pDM->ulNumInts      = g_CM.ulNumInts;
         g_pDM->ulTotal         = g_CM.ulTotal;
         g_pDM->nButton         = g_CM.nButton;

         i = 0;
         while( i < DEMO_NUM_BINS )
         {
            g_pDM->ulButtonCount[i]   = g_CM.ulButtonCount[i];
            i ++;
         }

         // 已经完成
         //
         SetEvent( g_hevCopyFinished );
      }

      Sleep( 5 );
   }

   return 0;
}

ThreadControl 执行的关键步骤包括:

  1. 检查每个循环的完成标志。
  2. 根据当前按钮递增指定的容器计数。
  3. 计算平均数。
  4. 检查是否已设置复制请求事件。如果已设置,则执行以下步骤:
    • 将平均数、中断数、总中断数和按钮索引复制到演示内存区域。
    • 将容器计数复制到演示内存区域。
    • 设置复制完成事件。
  5. 休眠 5 毫秒,然后重复上述步骤。
注意:现在可以生成并执行 DemoControl 应用程序。不使用 DemoView,您也可以看到 NMI 平台上的 LED 根据按钮的按下状态进行切换。

显示代码 - DemoView.exe

应用程序设置

DemoView.exe 是一个 MFC 应用程序。创建 MFC 应用程序包括两个步骤。选择 WCE MFC AppWizard (exe)(WCE MFC 应用程序向导 [exe])对话框,然后单击 OK(确定),如下图所示。

图 9:New(新建)对话框

单击 OK(确定)。

图 10:New WCE MFC AppWizard (exe)(新建 WCE MFC 应用程序向导 [exe])对话框

单击 Finish(完成)。

一些库和内存定义已经创建。创建内存映射文件的封装类包含在 SharedMemory.cpp & .h 文件中。演示内存区域在 DemoMemory.h 中定义。直方图类库包含在 HistoryLib.cpp & .h 中。在您的项目中添加这五个文件,即产生以下 DemoView 文件列表。

图 11:DemoView 文件列表

库和包含文件分别位于 Include(包含文件)和 Lib(库)子目录下。将这些项映射到 Project Settings(项目设置)对话框的包含目录搜索路径。

图 12:Project Settings(项目设置)对话框

对话框设置

打开 IDD_DEMOVIEW_DIALOG 对话框,放置以下项,使对话框如下图所示。DemoView 对话框的大小应该在 330 x 190 像素范围内。

表 5:对话框设置项

资源 ID属性
平均数编辑框IDC_AVG_EDIT禁用,可视
总计数IDC_TOTAL_EDIT禁用,可视
中断数IDC_NUM_INTS_EDIT禁用,可视
“停止”按钮IDC_STOP_BUTTON说明文字 - 停止

图 13:DemoView 对话框

双击对话框中的“停止”按钮,创建 OnStopButton 消息。在 MFC ClassWizard(MFC 类向导)对话框中向类 CdemoViewDlg 添加以下成员变量:

图 14:MFC ClassWizard(MFC 类向导)对话框

MFC ClassWizard(MFC 类向导)对话框中,将 WM_TIMERWM_PAINT 消息处理程序添加到对话框。

图 15:添加 WM_TIMER 和 WM_PAINT 消息处理程序

对话框标题 - DemoViewDlg.h

将以下粗体定义添加到 DemoViewDlg.h 中受保护的 m_hIcon 定义之下。它们将添加历史记录库、绘图 RECT 和统计信息等成员变量。

// 实现
protected:
   HICON         m_hIcon;

   CHistoryLib      m_HistoryLib;
   CRect         m_PlotRect;

   ULONG         m_ulLastAverage;
   ULONG         m_ulLastTotal;
   ULONG         m_ulLastNumInts;
   CString         m_Output;

“对话框”、“初始化”和“对话框详细处理程序”各节中的其余代码将取代由 eMbedded Visual C++ 生成的 DemoViewDlg.cpp 默认代码。

对话框初始化

以下代码示例演示了对话框的初始化过程:

// DemoViewDlg.cpp:实现文件
//

#include "stdafx.h"
#include "DemoView.h"
#include "DemoViewDlg.h"
#include "HistoryLib.h"
#include "DemoMemory.h"
#include "SharedMemory.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

CSharedMemory*      g_SM;
DEMO_MEMORY_PTR   g_pDM;
HANDLE         g_hevCopyRequest;
HANDLE         g_hevCopyFinished;


/
// CDemoViewDlg 对话框

CDemoViewDlg::CDemoViewDlg(CWnd* pParent /*=NULL*/)
   : CDialog(CDemoViewDlg::IDD, pParent)
{
   //{{AFX_DATA_INIT(CDemoViewDlg)
   //}}AFX_DATA_INIT
   // 请注意,LoadIcon 不需要 Win32 中后续的 DestroyIcon
   m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

void CDemoViewDlg::DoDataExchange(CDataExchange* pDX)
{
   CDialog::DoDataExchange(pDX);
   //{{AFX_DATA_MAP(CDemoViewDlg)
   DDX_Control(pDX, IDC_NUM_INTS_EDIT, m_NumIntsEdit);
   DDX_Control(pDX, IDC_TOTAL_EDIT, m_TotalEdit);
   DDX_Control(pDX, IDC_AVG_EDIT, m_AvgEdit);
   //}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CDemoViewDlg, CDialog)
   //{{AFX_MSG_MAP(CDemoViewDlg)
   ON_WM_PAINT()
   ON_WM_TIMER()
   ON_BN_CLICKED(IDC_STOP_BUTTON, OnStopButton)
   //}}AFX_MSG_MAP
END_MESSAGE_MAP()

/

// CDemoViewDlg 消息处理程序

BOOL CDemoViewDlg::OnInitDialog()
{
   CDialog::OnInitDialog();

   // 设置此对话框的图标。当应用程序的主窗口不是对话框时,
   //  框架不会自动完成此操作
   SetIcon(m_hIcon, TRUE);         // 设置大图标
   SetIcon(m_hIcon, FALSE);      // 设置小图标
   
   CenterWindow(GetDesktopWindow());   // 在 hpc 屏幕中居中显示

   // 设置直方图
   //
   GetClientRect( &m_PlotRect );
   m_PlotRect    = m_PlotRect;
   m_PlotRect.right   -= 10;
   m_PlotRect.bottom   -= 70;
   m_HistoryLib.Init(   HistoryLeft,         // 类型
            &m_PlotRect,         // 区域
            _T("Button-Histogram"),   // 标题
            _T("(Button)" ),      // 水平标题
            _T("(Counts)" ),      // 垂直标题
            0.0,            // 左 
            8,            // 容器数
            1.0            // 容器大小
            );

   // 设置编辑操作
   //
   m_ulLastAverage   = 0;
   m_ulLastTotal      = 0;
   m_ulLastNumInts   = 0;


   // 创建共享内存区域
   //
   g_SM   = new CSharedMemory( sizeof( DEMO_MEMORY_STRUCT ), DEMO_MEMORY_NAME );
   if( !g_SM ) return FALSE;

   // 从共享内存中获取内存
   //
   g_pDM = (DEMO_MEMORY_PTR)g_SM->GetMemory();
   if( !g_pDM ) return FALSE;


   // 获取事件
   //
   // 创建事件
   //
   g_hevCopyRequest = CreateEvent(NULL, FALSE, FALSE,
                    DEMO_COPY_EVENT_NAME );
   if(!g_hevCopyRequest) return FALSE;
   g_hevCopyFinished = CreateEvent(NULL, FALSE, FALSE,
                      DEMO_COPY_FINISHED_EVENT_NAME );
   if(!g_hevCopyFinished) return FALSE;



   // 使定时器开始执行
   //
   SetTimer( 10000, 200, NULL );
   
   return TRUE;  // 除非将焦点设置到某个控件上,否则返回 TRUE
}

对话框初始化的关键步骤包含在 OnInitDialog 消息处理程序中:

  • 计算直方图的绘图矩形
  • 初始化 m_HistoryLib
  • 创建演示内存区域的新内存映射文件
  • 创建或连接到复制请求事件
  • 创建或连接到复制完成事件
  • 在 200 毫秒时启动计时器以更新显示

对话框消息处理程序

以下消息处理程序 OnPaintOnTimerOnStopButton 将完成全部工作。

void CDemoViewDlg::OnPaint() 
{
   CPaintDC dc(this); // 绘图设备上下文
   
   m_HistoryLib.PlotBackground( &dc );
   
   // 不要调用 CDialog::OnPaint() 来绘制消息
}

void CDemoViewDlg::OnTimer(UINT nIDEvent) 
{
   static   int i = 0;

   // 更新显示
   //
   CDC*   dc;
   dc   = GetDC();


   // 请求控制器信息,并等待其完成
   //
   SetEvent( g_hevCopyRequest );
   WaitForSingleObject( g_hevCopyFinished, 500 );
   
   // 更新直方图
   //
   m_HistoryLib.SetBinData( DEMO_NUM_BINS, &g_pDM->ulButtonCount[0] );
   m_HistoryLib.Update( dc );
   
   // 更新编辑操作
   //
   if( g_pDM->ulAverage != m_ulLastAverage )
   {
      m_Output.Format( _T("%ld"),  g_pDM->ulAverage );
      m_AvgEdit.SetWindowText( m_Output );
      m_ulLastAverage = g_pDM->ulAverage;
   }

   if( g_pDM->ulTotal != m_ulLastTotal )
   {
      m_Output.Format( _T("%ld"),  g_pDM->ulTotal );
      m_TotalEdit.SetWindowText( m_Output );
      m_ulLastTotal   = g_pDM->ulTotal;
   }

   if( g_pDM->ulNumInts != m_ulLastNumInts )
   {
      m_Output.Format( _T("%ld"),  g_pDM->ulNumInts );
      m_NumIntsEdit.SetWindowText( m_Output );
      m_ulLastNumInts = g_pDM->ulNumInts;
   }

   // 返回此 dc!
   ReleaseDC( dc );
   CDialog::OnTimer(nIDEvent);
}

void CDemoViewDlg::OnStopButton() 
{

   g_pDM->fFinished = TRUE;
}

消息处理程序的关键步骤包括:

  • OnPaint
    • 绘制直方图库的背景。
  • OnTimer
  1. 获取当前绘图设备的上下文。
  2. 设置复制请求事件。
  3. 等待来自控制器的复制完成事件,并在 500 ms 时设为超时以确保其不被锁定。
  4. 设置来自演示内存区域的直方图库的容器数据。
  5. 使直方图库更新显示。
  6. 更新对平均中断数、总中断数和中断数的编辑结果,如果这些数字与上次刷新时不同。
  7. 释放当前绘图设备的上下文。
  8. 返回。
注意:现在可以生成和执行 DemoView 应用程序。要测试控制器,请按硬件平台上的按钮。直方图容器基本上可以测量每次按钮按下所用的时间。如果有必要,直方图库将自动调整自身的大小。

小结

Windows CE .NET Embedded 开发人员可以选择多种开发工具。本机应用程序和 .NET Framework 精简版在应用程序开发中都占有一席之地。综合考虑性能、空间以及代码的可移植性等因素可以帮助您确定选择哪个工具。您可以在 eMbedded Visual C++ 中快速开发复杂的应用程序,eMbedded Visual C++ 允许对 Windows CE .NET 本机的各种 Win32 功能进行高性能访问。Windows CE .NET 为各种应用程序体系结构提供了丰富的实时环境。eMbedded Visual C++ 是开发功能强大的应用程序的得力工具。

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页