Writing Reliable Native Code

Windows CE .NET
 

Nat Frampton
Microsoft Embedded MVP
President, Real Time Development Corp.

January 2003

Applies to:
    Microsoft® Windows® CE .NET
    Microsoft® eMbedded Visual C++®
    Microsoft® .NET Compact Framework

Summary: Learn about the development process and capabilities of the eMbedded Visual C++ environment by developing an MFC histogram display and real-time control application. You will also learn the various strengths of Visual C++ native code and the .NET Compact Framework.

Download the source code for the sample applications discussed in this article.


Contents

Overview
Native vs. .NET Compact Framework Development
Interrupt Architecture
Application Interrupt Handling
Application Architecture
Controller Development - DemoControl.exe
Visualization Code - DemoView.exe
Conclusion

Overview

There are a variety of development choices for the Microsoft® Windows® CE .NET Embedded developer. Native Microsoft eMbedded Visual C++® and the new .NET Compact Framework provide two distinct paths to application development. Native code developed in eMbedded Visual C++ 4.0 will remain the most appropriate environment for developing high-speed code that takes advantage of the real-time capabilities of a Windows CE .NET platform. This session explores, through the development of an MFC histogram display and real-time control application, the development process and capabilities of the eMbedded Visual C++ environment. Visual C++ native code versus .NET Compact Framework issues are explored. The control application development will leverage Microsoft Win32 thread and synchronization objects, interrupt interfacing, shared memory interface techniques, and real-time priority strategies. MFC application topics will include display development, real-time data interfacing techniques and histogram library integration. Both applications will be developed from scratch, leveraging a few libraries available in source code format.

Native vs. .NET Compact Framework Development

The selection of native versus .NET Compact Framework is a very complex issue. Application tools, platform requirements, and development expertise work together to provide the selection criteria. The following section provides high-level guidance in this choice to provide an introduction to the process

Application Development Tool Choices

There are many application development tools available to the Microsoft Windows CE .NET Embedded developer. The integration of the .NET Compact Framework into Windows CE .NET has extended these choices.

Platform Builder is the obvious choice for operating system, driver, and service development. It serves the embedded platform developer, providing a tightly integrated for OS configuration, driver development, kernel level debugging, and many other features, just to name a few. Platform Builder is not intended as an environment for large-scale application development.

Native applications are applications that have been compiled directly for a particular processor. When you select Build MyApp.exe from the Microsoft Visual C++ build menu, the compiler creates a native application. Microsoft eMbedded Visual C++ and Microsoft Visual Basic® have served as the base for all Windows CE applications development up to the introduction of the Compact Framework. Within Visual C++, there are two main choices for application development. Windows CE .NET is architected around the Microsoft Win32® APIs. The Win32 APIs can be programmed directly from native applications. The repetitive application development process resulted in the creation of a set of class libraries encapsulating most of the mundane tasks. These class libraries are called the Microsoft Foundation Class (MFC) libraries. eMbedded Visual C++ allows embedded developers to leverage the MFC libraries, providing a graphical development environment for visual applications.

With the introduction of the Compact Framework, applications can now be developed within the Visual Studio .NET environment, targeting both the desktop and Windows CE .NET Common Language Runtimes (CLRs). This allows Visual Basic .NET and C# .NET development for Windows CE .NET.

Development API Choices

The Window CE .NET developer can choose from native Win32, native MFC, or Compact Framework application development. The following tables provide a simple overview of the choices:

  • Footprint—provides expected platform requirements
  • Strengths and weaknesses—enumerates the basic selection choices

Table 1. Footprint

Footprint Requirement Windows CE .NET Windows XP
Win32 - -
MFC 280 KB 1.25 MB
.NET Framework 1.5 MB 34 MB

Table 2. Strengths and weaknesses

API Strengths Weaknesses
Microsoft Win32

(C / C++)

  • Smallest and fastest .exe files and DLLs

    No runtime required; Windows CE .NET is the runtime.

  • Lowest memory overhead
  • Required for device drivers, control panel applets, shell extensions
  • Object cleanup is the responsibility of the application/driver programmer, making this API prone to memory leaks.
  • Low-level API — can be hard to learn.
  • Procedure-oriented API, not object-oriented.
MFC

(C++)

  • Object-oriented. Inheritance, Encapsulation, Polymorphism, also called function overloading.

    Good tool support & wizards

  • Container classes support arrays, lists, object maps and simplify data handling.
  • Type safety.
  • Complete MFC source code ships with Embedded Visual Tools.
  • Object cleanup only semi-automatic, therefore less prone to memory leaks than Win32, but still somewhat vulnerable since MFC is thin wrapper on top of Win32
  • Size of runtime ~ 500 KB (MFC & OLECE)
.NET Framework

(C# and Microsoft Visual Basic .NET)

  • Well-designed programming interface.

    Great tool support — Forms Designer.

  • Object-oriented. Inheritance, Encapsulation, Polymorphism, also called function overloading.
  • Container classes support arrays, lists, hash tables, dictionaries, and stacks.
  • Type safety.
  • Namespaces.
  • Automatic garbage collection eliminates memory leaks.
  • Portable machine instruction set, MSIL / CIL, provides binary portable of executable (.exe & .dll) files.
  • Web service clients are quick and easy to write.
  • Great support for handling XML
  • Size of runtime ~ 1.5 MB.
  • Overhead of calls between managed and unmanaged code is high. COM Interoperability somewhat clumsy. Requires writing Win32 wrappers that call the COM interface functions.
  • Source code is not available.
  • Requires display-based platform.

In the case of a high-speed control application on a resource-constrained platform, the selection of native Win32 for the controller and native MFC for the visualization are obvious choices. In many cases, the choice of development environment can be simple; in other cases, it may be very complex.

Interrupt Architecture

The following illustration provides an application view of Windows CE .NET's interrupt architecture, with a representation of the hardware, kernel, OAL, and thread interactions during an interrupt.

Figure 1. Windows CE .NET interrupts

Time increases to the right of the illustration. The bottom layer of the illustration is the state of the hardware. The next layer is the Windows CE OEM adaptation layer (OAL), which describes the board support package (BSP) responsibilities. The top layer represents the application or driver thread interactions needed to service and interrupt.

The activity starts with an interrupt represented by the line at the left section of the chart. An exception is generated, causing the kernel interrupt service routine (ISR) vector to be loaded onto the processor. The kernel ISR interacts with the hardware, disabling all equal and lower priority interrupts on all processors except for the ARM and Strong ARM architectures. The kernel then vectors to the OAL ISR that has been registered for that particular interrupt. The OAL ISR can then examine the hardware to determine whether it has caused the interrupt. This is usually done by checking an interrupt state register. If the hardware has caused the interrupt the ISR return the SYSID of that hardware.

Upon completion of the ISR, the kernel re-enables all interrupts on the processor except for the identified interrupt. The kernel then signals the event that has been associated with the SYSID value.

The Interrupt Service Thread (IST) of a driver or application is then able to run, assuming that it is the highest priority thread that is ready to run. The IST communicates with the associated device, and reads any necessary data from the device, completing its interrupt interaction. The IST then signals its completion with a call to InterruptDone( ) with the associated SYSID value.

The kernel, upon receiving the InterruptDone for the SYSID value, re-enables the designated interrupt. This is the first point at which another interrupt for this device can be received.

This is a quick look through the interrupt sequence of activities inside Windows CE .NET. The illustration represents the interactions during a single interrupt and does not show the shared interrupts feature of Windows CE .NET. For more information about this, please refer to the Interrupt Architecture in Windows CE .NET.

Application Interrupt Handling

Dealing with interrupts from either an application or driver requires a two-step process. First, the interrupt must be initialized with an associated event. Second, the IST must wait for the interrupt event in response to interrupts from the kernel.

Interrupt Initialization

The following is example code for setting up and associating an IST with a particular interrupt. The key steps in initializing an interrupt are:

  • Creating an event
  • Getting the System Interrupt number for your IRQ
  • Creating an interrupt service thread (IST) that is suspended
  • Calling InterruptInitialize to create an association of the IRQ->Event
Creating an IST that is not suspended may cause InterruptInitialize to fail because the event is already being waited on.
  • Setting the thread priority to the appropriate priority
  • Resuming the IST
Void SetupInterrupt( void )
{
// Create an event
//
g_hevInterrupt = CreateEvent(NULL, FALSE, FALSE, NULL);
if (g_hevInterrupt == NULL) 
{
      RETAILMSG(1, (TEXT("DEMO: Event creation failed!!!\r\n")));
      return;
}


// Have the OAL Translate the IRQ to a system irq
//
fRetVal      = KernelIoControl( IOCTL_HAL_TRANSLATE_IRQ, 
                        &dwIrq,
                        sizeof( dwIrq ),
                        &g_dwSysInt,
                        sizeof( g_dwSysInt ),
                        NULL );


// Create a thread that waits for signaling
//
g_fRun   = TRUE;
g_htIST         = CreateThread(NULL,   // Security
               0,       // No Stack Size
               ThreadIST,   // Interrupt Thread                        NULL,      // No Parameters
               CREATE_SUSPENDED, // Create Suspended
               &dwThreadID   // Thread Id
               );


// Set the thread priority - arbitrarily 5
//
m_nISTPriority = 5;
if( !CeSetThreadPriority( g_htIST, m_nISTPriority ))
{
      RETAILMSG(1,(TEXT("DEMO: Failed setting Thread Priority.\r\n")));
      return;
}


// Initialize the interrupt
//
if ( !InterruptInitialize(g_dwSysInt,g_hevInterrupt,NULL,0) ) 
{
      RETAILMSG (1, (TEXT("DEMO: InterruptInitialize failed!!!\r\n")));
      return;
}

// Get the thread started
//
ResumeThread( g_htIST );

}

It is important to note that the call to InterruptInitialize takes only the SYSINTR value and the event. The kernel does not know or care about the thread that will be waiting for the event. This allows for a variety of application and driver architectures. A simple main loop of an application could initialize an interrupt and then immediately wait for the event. Only a single event can be associated with an interrupt. The event can not be used in a call to WaitForMultipleObjects. We will look at a simple thread to service the interrupt. This is the standard solution for most implementations.

Application Interrupt Service Routine:

Example code for an Interrupt Service Thread (IST) is presented below. The key components to this IST interrupt handling thread are:

  • Waiting for the interrupt event
  • Confirming that we have a pulsed event from the OS
  • Handling the interrupt in the shortest time possible
  • Calling InterruptDone()
The OS will not provide another interrupt on this IRQ until InterruptDone is called.
  • Waiting for the interrupt event again
DWORD   WINAPI   ThreadIST( LPVOID lpvParam )
{
   DWORD   dwStatus;

   // Always chec the running flag
   //
   while( g_fRun )
   {
      dwStatus   = WaitForSingleObject(g_hevInterrupt, INFINITE);

      // Make sure we have the object
      //
      if( dwStatus == WAIT_OBJECT_0 )
      {
         // Handle Interrupt Here
         //
         g_dwInterruptCount ++;

         // Finish the interrupt
         //
         InterruptDone( g_dwSysInt );

      }
   }

   return 0;
}

Priorities

A key Win32 API call in the initialization code was a call to CeSetThreadPriority. This function takes two parameters. The first parameter is the thread handle, and the second value, from 0-255, describes the desired priority level. The choice of which thread priorities to use is very critical. It is important to be able to diagram your application's use of priorities to ensure proper performance. The thread priorities from 0-247, with 0 being the highest priority, are the real-time thread priorities requiring a call to CeSetThreadPriority to access them. The normal thread priorities from 248-255 are access with SetThreadPriority. The following table provides a quick guide to Windows CE .NET standard priority implementations.

Table 3. Real-time thread priorities: CeSetThreadPriority

Priority Component
0-19 Open—Real Time Above Drivers
20 Permedia Vertical Retrace
21-98 Open—Real Time Above Drivers
99 Power management Resume Thread
100-108 USB OHCI UHCI, Serial
109-129 Irsir1, NDIS, Touch
130 KITL
131 VMini
132 CxPort
133-144 Open—Device Drivers
145 PS2 Keyboard
146-147 Open—Device Drivers
148 IRComm
149 Open—Device Drivers
150 TAPI
151-152 Open—Device Drivers
153-247 Open—Real Time Below Drivers

Table 4. Normal thread priorities: SetThreadPriority

Priority Component
248 Power Management
249 WaveDev, TVIA5000, Mouse, PnP ,Power
250 WaveAPI
251 Power Manager Battery Thread
252-255 Open

The general first decision point is to determine if your critical thread requires any drivers. If the critical thread requires drivers to function, then placing it at a priority above the driver's priority will not create performance gains. In general, time-critical applications are places in the "Real-Time Above Drivers Category" ranging from 0-98.

Application Architecture

The DemoControl and View applications work together to provide both control and visualization of the interrupt state of our platforms hardware. An SH4-based platform from NMI will be leveraged for this demonstration. The demonstration platform contains a simple button that is attached to an interrupt. An operating system interrupt is fired on a change of state of the button. There is an interrupt received for both the up and down transition of the hardware switch. The DemoControl application, upon receiving its first interrupt, starts counting every 5 milliseconds (ms) and increments the count in one of 8 memory bins. Each time the button is pressed, the current bin index is incremented by one. When the bin index reaches 8, it is reset to 0. The histogram of the DemoView application automatically resizes to show the relative counts of each of the 8 bins. In effect, the histogram shows the amount of time spent at each bin location. The DemoView also displays the average bin count, total bin counts, and the current bin number.

The display of high-speed information is a classic real-time interfacing problem. If the real-time controller updates the bin counts in the middle of drawing, the display may show bin counts, averages, and totals that may be partially incorrect. It is critical in real-time applications to get all the data in a single snapshot, so that the individual items are all consistent. This is referred to as temporal consistency. The most common way of getting the data in a single snapshot, or temporally consistent, is to synchronize with the actual control thread to deliver this snapshot when needed.

The demonstration strategy involves leveraging two of the most commonly-used synchronization objects in Win32: named events and memory mapped files. The event provides a mechanism for one application to signal another application that it has reached a certain state. Applications can wait for these events until it is signalled. Because these events can be named, they can be referenced between applications. Memory mapped files, referenced by name, provide a way for two separate applications two view a single area of memory.

Synchronization Strategy

The following illustration outlines the synchronization strategy.

Figure 2. Synchronization strategy

The synchronization happens cyclically with the following steps:

  1. DemoView application sets the Copy Event to request a fresh copy of the statistics into Demo Memory Area and then waits for the Finished Event.
  2. The DemoControl application checks every control system to see if the Copy Event has been set. If it has been set, it goes to Step 3; otherwise, it continues cycling.
  3. DemoControl updates the data in the Demo Memory Area.
  4. DemoControl sets the Finished Event.
  5. DemoView is woken back up, after waiting for the Finished Event, redraws the screen, sleeps for 100 ms, and then returns to step 1.

Minimizing interactions and requirements on the control threads is of highest priority. The controller spends very little time on either checking on the state of the Copy Event or copying the data to the memory area. You might ask if you should just go ahead and copy the memory every control cycle. The problem with this is that DemoView may be in the middle of copying the data out to its local memory and then end up with an inconsistent snapshot.

Demo Control Architecture

In order to support the synchronization and controller requirements, the following DemoControl architecture will be implemented:

Figure 3. Demo control architecture

The DemoControl application uses two main threads: the IST and the main control thread.

A simple Interrupt Service Thread (IST) will respond to a push button interrupt. The IST will ensure that interrupt is for the button up state. The IST will then increment the bin index and flash the LED.

The control thread will loop check for a finished flag indicating shutdown. If the finished flag is not set, the control thread will increment the bin value for the current bin index. The control thread then calculates the current statistics. The control thread checks for the copy request event from DemoView. If it is set, it copies the data to the Demo Memory Area. The control thread then sets the finished event, sleeps for 5 ms and repeats the loop.

The interrupt service thread must be serviced as quickly as possible, and should not be held up by the Control thread, and will be placed at priority 50. The demo control thread does not depend on any drivers and will therefore be placed at priority 60.

Controller Development - DemoControl.exe

Directory Structure

It is recommended that you create the following directory structure:

\DEMOTop Directory anywhere you want

\DEMO\DemoControlPlace for DemoControl Project

\DEMO\DemoViewPlace for DemoView Project

\DEMO\LibPre Existing SharedMemory and History Libraries

\DEMO\IncPre Existing DemoMemory.h File

Application Setup

DemoControl.exe is a Win32 application. Creating a Win32 application is a two-step process. You will select a new WCE application and ask for a simple Windows CE .NET application as shown below.

Figure 4. New WCE application dialog box

To create the application, select WCE Application and then click OK.

Figure 5. WCE Application dialog box

Some libraries and memory definitions have already been created. A class that encapsulated creating a memory mapped file is contained in the SharedMemory.cpp & SharedMemory.h files. The Demo Memory Area is defined in DemoMemory.h. Add these three files to your project, which will result in the following DemControl files list.

Figure 6. DemControl files list

The libraries and include files are placed into an Include and Lib sub directory. Map these into the include directory search path in the Project Settings dialog box.

Figure 7. Libraries and include files in the project settings dialog box

In order to access the platform hardware, leverage the CEDDK library and add that to the Object Modules list in the Project Settings dialog box.

Figure 8. Adding to the Object Modules list in the Project Settings dialog box

Demo Memory Area—DemoMemory.h

The global memory structure for both applications has been defined in DemoMemory.h. The DEMO_MEMORY_STRUCTURE contains:

   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

// New File DemoMemory.h
//

// Defines
//
#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

// Global Memory Structure
//
typedef struct
{
   int      nButton;
   ULONG      ulButtonCount[ DEMO_NUM_BINS ];   
   ULONG      ulAverage;
   ULONG      ulTotal;
   ULONG      ulNumInts;

   BOOL      fFinished;

}DEMO_MEMORY_STRUCT,*DEMO_MEMORY_PTR;

The remainder of the code in the Initialization and Control Code sections will replace the DemoControl.cpp default code generated by eMbedded Visual C++.

Initialization

// DemoControl.cpp : Defines the entry point for the application.
//

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

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

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

// Defines
//
#define BUTTON_MASK     0x20000000
#define LED_ON          0x0C
#define LED_OFF         0x00

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

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


   // Update the local memory
   //
   memcpy( (PVOID)&g_CM, (PVOID)g_pDM, sizeof( DEMO_MEMORY_STRUCT ) ); 

   // Get the Threads going
   //
   ResumeThread( g_htIST );
   ResumeThread( g_htControl );


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

   return 0;
}

DWORD   SetupMemory( void )
{
   // Create the Shared Memory Area
   //
   g_SM   = new CSharedMemory( sizeof( DEMO_MEMORY_STRUCT ), DEMO_MEMORY_NAME );
   if( !g_SM ) return 1;

   // Get the memory from the shared memory
   //
   g_pDM = (DEMO_MEMORY_PTR)g_SM->GetMemory();
   if( !g_pDM ) return 2;

   // Clear the memory
   //
   g_pDM->fFinished = FALSE;


   // Map the LED and Button registers using the CEDDK function
   //
   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 Security
                    0,          // Default Size
                    ThreadIST,      // IST      
                       NULL,         // No Parameters
                    CREATE_SUSPENDED,   // Suspended
                    &dwThreadID      // Thread Id
                           );
   if( !g_htIST )return 12;

   // Set the thread priority to real time
   //
   if( !CeSetThreadPriority( g_htIST, DEMO_IST_PRIORITY ))return 13;


   // Initialize the interrupt
   //
   if ( !InterruptInitialize(g_dwSysInt,g_hevInterrupt,NULL,0) )return 14;

   return 0;
}

DWORD   SetupControl( void )
{
   DWORD   dwThreadID;

   // Create the events
   //
   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;

   
   // Create the High Priority Thread
   //
   g_htControl         = CreateThread(   NULL,   
                        0,             
                        ThreadControl,
                          NULL,      
                        CREATE_SUSPENDED,
                        &dwThreadID );
   if( !g_htControl ) return 20;

   // Set the thread priority to real time
   //
   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 );  // On
   else WRITE_REGISTER_UCHAR( g_pLEDPORT, LED_OFF | ucPort ); // Off

   return 0;
}

The key steps in initializing the controller from WinMain are:

  • Call SetupMemory:
    • Creates a new memory mapped file of the demo memory area.
    • Maps in through the CEDDK.lib the input/output (I/O) space for the LEDs and buttons.
  • Call SetupInterrupt:
    • An Interrupt Event and IST are created and InterruptInitialize is called to hook up the interrupt.
    • Set the IST priority to 50.
  • Call SetupControl:
    • Create or attach to a named event for the Copy Request.
    • Create or attach to a named event for the Copy Finished.
    • Create the Control Thread.
    • Set the control thread priority to 60.
  • Copy in the global state of the Demo Memory.
  • Resume both the IST and control threads.
  • Wait for the fFinished flag to be set.

Control Code

Interrupt Thread

The ThreadIST follows the simple example from above and adds hardware specific code to handle the button and button index.

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

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

      // Check to see if we are finished
      //
      if(g_pDM->fFinished ) return 0;

      // Make sure we have the object
      //
      if( dwStatus == WAIT_OBJECT_0 )
      {
         // Only look for button ups. Get get an interrupt on up and down
         //
         if (!( READ_REGISTER_ULONG(g_pButtonPort) & BUTTON_MASK))
         {
            // Increment the Button
            //
            g_CM.ulNumInts++;
            if( !fFirstTime )
            {
               g_CM.nButton ++;
               if( g_CM.nButton == DEMO_NUM_BINS ) 
                   g_CM.nButton = 0;
            }
            else
               fFirstTime = FALSE;


            // Store our count out the the CELOG
            //
            CELOGDATA(   TRUE, 
                       CELID_RAW_LONG, 
                     &g_CM.ulNumInts, (WORD) (sizeof(DWORD)), 
                      1, CELZONE_MISC);

            // Flash the LED
            LED( 0, fState );
            fState   = !fState;

         }

         // Finish the interrupt
         //
         InterruptDone( g_dwSysInt );

      }
   }

   return 0;
}

The applications-specific steps to this IST are:

  • Upon receiving the interrupt, the IST checks to see whether there is a Button Up.
  • Increments the number of interrupts and clears the first time flag.
  • Reports the number of interrupts to CELOG.
  • Toggles the LED state.
  • Reporting InterruptDone.

Control Thread

The main controller thread can be seen below.

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

   while( !g_pDM->fFinished )
   {

      // Increment the current Bin Bucket
      //
      if( g_CM.ulNumInts )
      {
         g_CM.ulTotal ++;
         g_CM.ulButtonCount[ g_CM.nButton ] ++;
         g_CM.ulAverage      = g_CM.ulTotal / DEMO_NUM_BINS;
      }

      // Get Request to update
      //
      result = WaitForSingleObject( g_hevCopyRequest, 0 );

      // See if signaled
      //
      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 ++;
         }

         // Were done
         //
         SetEvent( g_hevCopyFinished );
      }

      Sleep( 5 );
   }

   return 0;
}

The key steps performed by the ThreadControl are:

  1. Checking the finished flag each cycle.
  2. Incrementing the bin count pointed to by the current button.
  3. Calculating the average.
  4. Checking to see if the Copy Request Event is set. If it is, the following steps are performed:
    • Copies the average, number of interupts, total interrupts, and button bndex to the Demo Memory Area.
    • Copies the bin counts to the Demo Memory Area.
    • Sets the Copy Finished Event.
  5. Sleeping 5 ms and repeating the preceding steps.
Note   The DemoControl application can now be built and executed. Without the DemoView, you will be able to see the LEDs toggling on the NMI Platform based on button presses.

Visualization Code - DemoView.exe

Application Setup

DemoView.exe is an MFC application. Creating an MFC application is a two-step process. Select the New WCE MFC AppWizard (exe) dialog box, and then click OK, as shown in the following illustration.

Figure 9. New project dialog box

Click OK.

Figure 10. Appwizard dialog box

Click Finish.

Some libraries and memory definitions have already been created. A class that encapsulated creating a memory mapped file is contained in the SharedMemory.cpp & .h files. The demo memory area is defined in DemoMemory.h. A histogram class library is contained in HistoryLib.cpp and .h that add these five files to your project, resulting in the following DemoView files list.

Figure 11. DemoView files list

The libraries and include files are placed into an Include and Lib sub directory. Map these into the include directory search path in the Project Settings dialog box.

Figure 12. Project Settings dialog box

Dialog Setup

Open the IDD_DEMOVIEW_DIALOG dialog box and place the following items resulting in the following illustration. The DemoView dialog box should be in the range of 330 x 190 pixels.

Table 5. Dialog setup items

Item Resource ID Properties
Average Edit Box IDC_AVG_EDIT Disabled, Visible
Total Counts IDC_TOTAL_EDIT Disabled, Visible
Number of Interrupts IDC_NUM_INTS_EDIT Disabled, Visible
Stop Button IDC_STOP_BUTTON Caption—Stop

Figure 13. DemoView dialog box

Double-click the Stop button to create an OnStopButton message in the dialog box. In the MFC ClassWizard dialog box, add the following member variables to the class CdemoViewDlg:

Figure 14. MFC ClassWizard dialog box

In the MFC ClassWizard dialog box, add WM_TIMER and WM_PAINT message handlers to the dialog box.

Figure 15. Adding WM_TIMER and WM_PAINT message handlers

Dialog Header- DemoViewDlg.h

Add the following bold definitions to the protected definitions in DemoViewDlg.h under the definitions for m_hIcon. They add member variables for the history library, the plot RECT, and the statistics.

// Implementation
protected:
   HICON         m_hIcon;

   CHistoryLib      m_HistoryLib;
   CRect         m_PlotRect;

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

The remainder of the code in the Dialog Initialization and Dialog Message Handlers sections will replace the DemoViewDlg.cpp default code generated by eMbedded Visual C++.

Dialog Initialization

The dialog initialization is demonstrated in the following code example:

// DemoViewDlg.cpp : implementation file
//

#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 dialog

CDemoViewDlg::CDemoViewDlg(CWnd* pParent /*=NULL*/)
   : CDialog(CDemoViewDlg::IDD, pParent)
{
   //{{AFX_DATA_INIT(CDemoViewDlg)
   //}}AFX_DATA_INIT
   // Note that LoadIcon does not require a subsequent DestroyIcon in Win32
   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 message handlers

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

   // Set the icon for this dialog. The framework does this automatically
   //  when the application's main window is not a dialog
   SetIcon(m_hIcon, TRUE);         // Set big icon
   SetIcon(m_hIcon, FALSE);      // Set small icon
   
   CenterWindow(GetDesktopWindow());   // center to the hpc screen

   // Setup the Histogram
   //
   GetClientRect( &m_PlotRect );
   m_PlotRect    = m_PlotRect;
   m_PlotRect.right   -= 10;
   m_PlotRect.bottom   -= 70;
   m_HistoryLib.Init(   HistoryLeft,         // Type
            &m_PlotRect,         // Area
            _T("Button-Histogram"),   // Title
            _T("(Button)" ),      // H Title
            _T("(Counts)" ),      // V Title
            0.0,            // Left 
            8,            // Bins
            1.0            // Bin Size
            );

   // Setup the Edits
   //
   m_ulLastAverage   = 0;
   m_ulLastTotal      = 0;
   m_ulLastNumInts   = 0;


   // Create the Shared Memory Area
   //
   g_SM   = new CSharedMemory( sizeof( DEMO_MEMORY_STRUCT ), DEMO_MEMORY_NAME );
   if( !g_SM ) return FALSE;

   // Get the memory from the shared memory
   //
   g_pDM = (DEMO_MEMORY_PTR)g_SM->GetMemory();
   if( !g_pDM ) return FALSE;


   // Get the Events
   //
   // Create the events
   //
   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;



   // Get the Timer Going
   //
   SetTimer( 10000, 200, NULL );
   
   return TRUE;  // return TRUE  unless you set the focus to a control
}

They key steps in to the Dialog Initialization are contained in the OnInitDialog Message handler:

  • Calculating the plot rectangle for the histogram
  • Initializing the m_HistoryLib library
  • Creates a new memory mapped file of the Demo Memory Area
  • Create or attach to a named event for the Copy Request
  • Create or attach to a named event for the Copy Finished
  • Start a Timer at 200 ms to update the display

Dialog Message Handlers

The following message handlers OnPaint, OnTimer and OnStopButton do all the work.

void CDemoViewDlg::OnPaint() 
{
   CPaintDC dc(this); // device context for painting
   
   m_HistoryLib.PlotBackground( &dc );
   
   // Do not call CDialog::OnPaint() for painting messages
}

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

   // Update the display
   //
   CDC*   dc;
   dc   = GetDC();


   // Request the controller info and wait until its done
   //
   SetEvent( g_hevCopyRequest );
   WaitForSingleObject( g_hevCopyFinished, 500 );
   
   // Update the histogram
   //
   m_HistoryLib.SetBinData( DEMO_NUM_BINS, &g_pDM->ulButtonCount[0] );
   m_HistoryLib.Update( dc );
   
   // Update the Edits
   //
   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;
   }

   // Give this dc back!!!
   ReleaseDC( dc );
   CDialog::OnTimer(nIDEvent);
}

void CDemoViewDlg::OnStopButton() 
{

   g_pDM->fFinished = TRUE;
}

The key steps to the message handlers are:

  • OnPaint
    • Plot the background of the histogram library.
  • OnTimer
  1. Get the current drawing device context.
  2. Set the Copy Request event.
  3. Wait for the Copy Finished event to come from the controller and time out at 500 ms to ensure that it does not lock up.
  4. Set the Bin Data for the histogram library from the Demo Memory Area.
  5. Have the histogram library update the display.
  6. Update the Average, Total, and Number of Interrupts edits if they are different from the last redraw.
  7. Release the current drawing device context.
  8. Return.
Note   The DemoView application can now be built and executed. To exercise the controller, press the button on the hardware platform. The histogram bins basically measure the time you spend on each button push. The histogram library will automatically resize itself if necessary.

Conclusion

There are several development tool choices for the Windows CE .NET Embedded developer. Both native applications and the .NET Compact Framework have their place in application development. Factors such as performance, footprint and portability of code all come together to help guide the decision on which one to choose. Complex applications can be rapidly developed in eMbedded Visual C++, which allows for high performance access to the Win32 features native to Windows CE .NET. Windows CE .NET provides a rich real-time environment for a variety of application architectures. eMbedded Visual C++ is a powerful tool for full-strength applications.

For More Information

Windows CE .NET Home page

eMbedded Visual C++ 4.0 Web site

Microsoft .NET Compact Framework Web site

Free Downloads:

Microsoft Windows CE .NET Emulation Edition

eMbedded Visual C++ 4.0

Microsoft .NET Compact Framework Update to Platform Builder 4.1

Show: