Freigeben über


Building a WPF Emulator Part III

OK, so I promised we’d look at the emulator engine in this post. The design idea for the emulator places all of the device specific configuration of the emulation in code. This is in contrast to the default  “generic” Microsoft Emulator that ships with the SDK, which is configured by XML files. That’s OK for a basic general solution but leads to confusion and problems when emulating a specific set of hardware. What happens if a user deletes or edits the XML configuration file? Thus, for emulating a real device I recommend that all configuration is provided in the code instead of using the configuration files. (It’s also easier to do, especially with the support framework classes I’ve put together for this series of posts).

In the case of the KIOSK there are a number of hardware specific aspects that need to be accounted for. There are 3 LEDS for the magstrip reader to toggle in sequence to indicate a sweeping motion. Another LED for the NFC reader to blink indicating the reader is active and ready for a card. There is the touch support, the LCD display and the magstrip and NFC readers themselves (as custom interop). Last but not least is the block storage support for ExtendedWeakReferences used to save the touch screen calibration.

The engine looks like this:

 using Microsoft.SPOT.Hardware;
 using Microsoft.SPOT.Emulator;
 using Microsoft.SPOT.Emulator.Wpf;
 using Microsoft.SPOT.Emulator.Gpio;
 using Microsoft.SPOT.Emulator.TouchPanel;
 using Microsoft.SPOT.Emulator.BlockStorage;
  
 namespace KioskEmulator
 {
     public class KioskEmulatorEngine : Emulator2
     {
         static class Pins
         {
             public const Cpu.Pin Select = (Cpu.Pin)3;
             public const Cpu.Pin LED1 = (Cpu.Pin)48;
             public const Cpu.Pin LED2 = (Cpu.Pin)47;
             public const Cpu.Pin LED3 = (Cpu.Pin)45;
             public const Cpu.Pin RFIDLED = (Cpu.Pin)40;
  
             public const Cpu.Pin MagIrq = (Cpu.Pin)41;
             public const Cpu.Pin NfcIrq = (Cpu.Pin)42;
  
             public const Cpu.Pin Touch = TouchGpioPort.DefaultTouchPin;
         }
  
         public KioskEmulatorEngine( IEmulator Hal )
             : base( Hal )
         {
         }
  
         /// <summary>Registers default components and settings for this emulator</summary>
         protected override void LoadDefaultComponents( )
         {
             base.LoadDefaultComponents( );
  
             LED1 = RegisterPin( "LED1", Pins.LED1 );
             LED2 = RegisterPin( "LED2", Pins.LED2 );
             LED3 = RegisterPin( "LED3", Pins.LED3 );
             RFIDLED = RegisterPin( "RFIDLED", Pins.RFIDLED );
             Select = RegisterPin( "Select", Pins.Select, VirtualKey.Select );
             MagIrq = RegisterPin( "MagIrq", Pins.MagIrq );
             NfcIrq = RegisterPin( "NfcIrq", Pins.NfcIrq );
  
             RegisterLcd( 320, 240, 16, Pins.Touch );
  
             RegisterBlockStorage( );
  
             base.TimingServices.SystemClockFrequency = 16000000;
             base.RamManager.Size = 8 * 1024 * 1024;
             base.GpioPorts.MaxPorts = 129;
         }
  
         private void RegisterBlockStorage( )
         {
             EmulatorBlockStorageDevice bs = new EmulatorBlockStorageDevice( );
             bs.ComponentId = "EWRFlash";
             bs.PersistanceFilename = "EWR.Dat";
             bs.MaxSectorWriteTime = 500;
             bs.MaxBlockEraseTime = 2000;
  
             // 1 region with 6 blocks for EWR storage only
             bs.Regions = new Region[ 1 ];
             bs.Regions[ 0 ] = new Region( 1, 64 * 1024, new Block[ 6 ] );
             bs.Regions[ 0 ].Blocks[ 0 ] = new Block( BlockStatus.Usage_Storage_A );
             bs.Regions[ 0 ].Blocks[ 1 ] = new Block( BlockStatus.Usage_Storage_A );
             bs.Regions[ 0 ].Blocks[ 2 ] = new Block( BlockStatus.Usage_Storage_A );
             bs.Regions[ 0 ].Blocks[ 3 ] = new Block( BlockStatus.Usage_Storage_B );
             bs.Regions[ 0 ].Blocks[ 4 ] = new Block( BlockStatus.Usage_Storage_B );
             bs.Regions[ 0 ].Blocks[ 5 ] = new Block( BlockStatus.Usage_Storage_B );
  
             RegisterComponent( bs );
         }
  
         #region GPIO Pin Accessors
         public GpioPort Select  { get; protected set; }
         public GpioPort LED1    { get; protected set; }
         public GpioPort LED2    { get; protected set; }
         public GpioPort LED3    { get; protected set; }
         public GpioPort RFIDLED { get; protected set; }
         public GpioPort MagIrq  { get; protected set; }
         public GpioPort NfcIrq  { get; protected set; }
         #endregion
     }
 }
 

 

Not a lot to it really the engine is derived from the Emulator2 base class that provides a number of helpful methods for registering GPIO pins and the LCD.The static Pins class provides constant pin values of the proper Cpu.Pin type (avoids casting in lot’s of places) These are all set to match what the actual hardware uses so the application code will work correctly. The accepts an IEmulator interface for custom emulator CLR+HAL implementations. This allows a custom emulator such as this to use a device specific emulator CLR+HAL and is typically used for adding interop extensions. This extensibility can also provide an emulator CLR implementation that exactly matches the features (APIs and classes supported) in the firmware of a real device. (E.g. a device with no actual display could have a customized CLR implementation that won’t support any of the graphics classes and methods so you get exact behavior of the real device.)

The device emulated by KioskEmulatorEngine has a set of hardware that must be configured for the emulation to function correctly. This is done through the overloaded LoadDefaultComponents() method. LoadDefaultComponents() is called by the Microsoft.SPOT.Emulator.Emulator class during initialization and allows a derived class to register EmulatorComponents in the system. This implementation registers several GPIO Pins using the extension methods provided by Emulator2. Each pin gets a name, that is the component ID for the underlying EmulatorCompontent, and the pin number. The RegisterPin function will create a new GpioPort instance, register it and return the new port. The port is then stored for later re-use. Storing the GpioPort saves looking it up again via the FindComponentByID or through the GpioPorts collection. It also allows use of the port before the emulator reaches the Initialized state as the GpioPorts collection is not filled in with all the registered ports until just before transitioning to the initialized state. If  you try to use the collection before reaching the initialized state you get an exception since it is still empty.

After registering GPIOs the LoadDefaultComponents() registers the LCD with screen dimensions of 320x240x16 and enabling Touch on the previously defined touch pin number. The LCD sizes are in actual device pixels. This is important as the WPF UI rendering system works in terms of real world sizes and not pixels. If you have a 96dpi monitor things end up as 1:1 that we’ll see more detail on with the touch support in the engineer’s main window. Once we move to the nicer designer’s window that matches the actual size of the device on the screen. We’ll see that the total number of pixels for display area is significantly larger than the actual number of pixels on the device. Scaling is applied to make it show up as it would on the real system. This has interesting implications for touch. in the engineers view it normally ends up as 1:1 per pixel (or close enough). Thus no calibration is needed, however once you shift to the designer’s nice UI things don’t match up per pixel and you must have calibration to get touch to work. This is true of the real device so makes things a bit more like the real thing and keeps you from forgetting an important step. (Don’t worry I’ve generalized the whole calibration process into a reusable assembly. Yes, you guessed it, that’s another post for another time…)

After setting up the LCD and touch components LoadDefaultComponents registers the block storage devices for Extended Weak references. For the emulator the storage data is stored in a file that, in this case, is called EWR.DAT. The storage has one region with 6 blocks of 64K each. The regions are split into two for 3 STROAGE_A and 3 STORAGE_B (used as a “ping pong” style transacted write memory) to match what the physical device has for storage.

After initializing the storage the LoadDefaultComponents() method will set up the system clock frequency, the RAM size and maximum number of GPIO ports.

The registered GpiPorts created and saved as public properties so that the additional code can refer to the ports as named properties instead of pin numbers or forcing code to look up the pins somehow. Each port property is publicly read only but writeable by the KioskEmulatorEngine instance. (These are set in LoadDefaultComponents() as previously discussed)

That’s all there is to it. Focused, to the point and all specific to the hardware being emulated without any additional clutter. In my next post we’ll look at the Emulator2 base class to see what else is going on with that class to take advantage of. So, as they used to say on The Muppet Show, “Tune in next time and hear Dr. Bob say…”