Freigeben über


Building a WPF Emulator Part IV

Part IV? already… this is starting to get like one of those scary movies series of the late 70’s and early 80’s… or Star Trek movies… (Hopefully the even numbered and the odd numbered posts are good!)

So far I’ve covered the code from the viewpoint of a custom emulator using the additional support classes I’ve created. Now it’s time to have a look at what’s going on behind the curtain of the Emulator2 class. In trying to support WPF I wanted to leverage the MVVM pattern to isolate the UI from the core of the application code. This meant either re-writing the emulator engine to support data binding or to put all the Data Binding in the ViewModel. The MVVM pattern was developed assuming a model that was unchangeable and the ViewModel wraps the model class with support for data binding and commands for the UI to use. (Kind of like an instance of the “Adapter” pattern). So I created a common ViewModel class for emulators that custom emulators cad derive from. In the process of getting that sorted out I realized it wasn’t going to be enough. The emulator uses a multi-stage initialization process and some parts of the emulator are only valid after a given stage of init , while others are only valid in the early configuration stage. I needed a way to have the ViewModel interact with the engine (running on a different thread) to complete the initialization of the UI binding at the correct stage of initialization. The solution was to create a new class (Emulator2) derived from the standard emulator class and add a set of events for the important stages of initialization. The class looks like this:

     /// <summary>Extension of standard Emulator to provide events for key state changes</summary>
     /// <remarks>The standard <see cref="emulator" /> and derived classes go through
     /// several stages of initialization. However, external classes, like a view model
     /// don't get to participate in the "notification" of state changes. Thus it is difficult
     /// for a view model to know when it is safe to attach to the emulators LCD changed event.
     /// <para>This class is designed to add events to the standard emulator for the Initialize
     /// and Uninitialize states and to simplify registration of GPIO pins and LCD display components</para>
     /// </remarks>
     public class Emulator2 : Emulator
     {
         /// <summary>Creates a new instance of an <see cref="Emulator2"/></summary>
         public Emulator2( )
         {
         }
  
         /// <summary>Creates a new instance of an <see cref="Emulator2"/></summary>
         /// <param name="Hal">Custom emulator HAL built from the .NET Micro Framework Porting kit</param>
         /// <remarks>
         /// This overload of the constructor allows setting the HAL/CLR implementation for the emulator.
         /// You can create custom HAL/CLR configurations using the .NET Micro Framework Porting kit.
         /// </remarks>
         public Emulator2(IEmulator Hal)
             : base( Hal )
         {
         }
  
         #region Emulator Thread
         /// <summary>Start the emulator engine on a new thread</summary>
         /// <remarks>Uses Environment.GetCommandLineArguments()</remarks>
         public void StartEmulator( )
         {
             new Thread( Start ).Start( );
         }
  
         /// <summary>Start the emulator engine on a new thread</summary>
         /// <param name="Args">Command line arguments</param>
         public void StartEmulator( string[ ] Args )
         {
             new Thread( Start ).Start( Args );
         }
         #endregion
  
         #region Events
         /// <summary>Event raised when the emulator has reached the Initialize state</summary>
         /// <remarks>
         /// This event is raised after the emulator is initialized but before it begins executing
         /// code. All of the EmulatorComponentCollections are fully wired up at this point. This
         /// event is normally used to trigger application code to attach to events fired by the
         /// other emulator components, such as the LCD or a GPIO pin.
         /// </remarks>
         public event EventHandler Initialize = delegate { };
  
         /// <summary>Event raised when the emulator is shutting down</summary>
         /// <remarks>
         /// This event indicates the emulator is shutting down and receivers should
         /// detach events and handle any other cleanup work needed.
         /// </remarks>
         public event EventHandler Uninitialize = delegate { };
  
         /// <summary>Triggers the <see cref="Initialize"/> event</summary>
         public override void InitializeComponent( )
         {
             base.InitializeComponent( );
             Initialize(this, null);
         }
  
         /// <summary>Triggers the <see cref="Uninitialize"/> event</summary>
         public override void UninitializeComponent()
         {
             base.UninitializeComponent( );
             Uninitialize( this, null );
         }
         #endregion
  
+ LCD/Touch
  
+ GPIO Pins 
  
     }

There’s more to it than shown here (I’ll show the LCD/Touch and GPIO Pins details in a bit) The constructors mirror that of the base class so that it works just like the original. There’s a pair of StartEmulator() methods that mirror the base class Start() method but start the emulator in a new thread. This is needed since the UI will run on the applications main thread. The events needed are Initialize and Uninitialize. Initialize is fired after everything is done just before the emulator starts processing IL instructions for the emulated application. At this point all of the Emulator Components are created and valid as are the various component collections, such as the GPIO ports, SPI bus and I2C Bus devices, etc… Thus if the ViewModel needs to connect to events or data from those components it is safe to do so when this event is fired. This covers the primary reason for the existence of the Emulator2 class. Since I had it I decided it was worth adding some more useful component registration “helpers” to handle some of the repetitive grunt work of registering components.

The first set of component registration helpers we’ll look at is the LCD/Touch support. The code looks like this:

         #region LCD/Touch
         /// <summary>Touch GPIO port for this emulator</summary>
         /// <remarks>
         /// This is normally set via a call to <see cref="RegisterLCD"/> in an override
         /// of the <see cref="LoadDefaultComponents"/> method. The UI must call the methods
         /// of the TouchGpioPort in order to generate touch events for the emulated system.
         /// </remarks>
         public TouchGpioPort TouchPort { get; protected set; }
  
         /// <summary>Registers an LCD component in the system</summary>
         /// <param name="PixelWidth">Width, in pixels, for the LCD</param>
         /// <param name="PixelHeight">Height, in pixels, for the LCD</param>
         /// <param name="Bpp">Bits per pixel setting for the LCD</param>
         /// <param name="TouchPin">Touch IRQ pin for touch support or GPIO_NONE</param>
         /// <remarks>
         /// Creates and registers an LCD Display emulator component. If TouchPin is a
         /// valid GPIO pin value <see cref="TouchPort"/> is created and registered to
         /// enable touch capabilities. 
         /// </para>NOTE: This should only be called from an overload of the
         /// <see cref="LoadDefaultComponents()"/> Method</para>
         /// </remarks>
         protected void RegisterLcd( int PixelWidth, int PixelHeight, int Bpp, Cpu.Pin TouchPin )
         {
             if( this.State != EmulatorState.Configuration )
                 throw new InvalidOperationException("Cannot register LCDs unless emulator is in the Configuration state" );
  
             LcdDisplay lcd = new LcdDisplay( );
             lcd.Width = PixelWidth;
             lcd.Height = PixelHeight;
             lcd.BitsPerPixel = Bpp;
             RegisterComponent( lcd );
  
             if( TouchPin != Cpu.Pin.GPIO_NONE )
             {
                 this.TouchPort = new TouchGpioPort( TouchPin );
                 RegisterComponent( this.TouchPort );
             }
         }
         #endregion

Component registration can only occur in the configuration state so this should be called from an overload of the LoadDefaultComponents method. The Touch port is exposed from the emulator engine so that the ViewModel can generate touch input to the emulation engine. (We’ll look into the whole View model thing in more detail in a later post.) This method just creates and registers a new LCD component and if a TouchPin is provided it creates and registers the touch port as well.

Registering GPIO ports is also covered in the Emulator2 helpers. There are 3 overloads of the RegisterPin() method for various uses in an emulated device.

 

         #region GPIO Pins
         /// <summary>Registers a GPIO pin in the system</summary>
         /// <param name="Pin">Pin number</param>
         /// <param name="Name">Name for the pin</param>
         /// <remarks>
         /// <para>Useful method for registering a pin</para>
         /// </para>NOTE: This should only be called from an overload of the
         /// <see cref="LoadDefaultComponents()"/> Method</para>
         /// </remarks>
         protected GpioPort RegisterPin( string Name, Cpu.Pin Pin )
         {
             return RegisterPin( Name, Pin, GpioPortMode.InputOutputPort, GpioPortMode.InputOutputPort );
         }
  
         /// <summary>Registers a GPIO pin in the system</summary>
         /// <param name="Pin">Pin number</param>
         /// <param name="Name">Name for the pin</param>
         /// <param name="Key">Virtual Key to attach to the pin</param>
         /// <remarks>
         /// <para>Useful method for registering a pin that acts as a button</para>
         /// </para>NOTE: This should only be called from an overload of the
         /// <see cref="LoadDefaultComponents()"/> Method</para>
         /// </remarks>
         protected GpioPort RegisterPin(string Name, Cpu.Pin Pin, VirtualKey Key)
         {
             GpioPort retVal = RegisterPin( Name, Pin );
             retVal.VirtualKey = Key;
             return retVal;
         }
  
         /// <summary>Registers a GPIO pin in the system</summary>
         /// <param name="Name">Name for the pin</param>
         /// <param name="Pin">Pin number</param>
         /// <param name="ExpectedMode">Expected mode for the pin</param>
         /// <param name="AllowedMode">Allowed modes for the pin</param>
         /// <remarks>
         /// <para>Useful method for registering a pin</para>
         /// </para>NOTE: This should only be called from an overload of the
         /// <see cref="LoadDefaultComponents()"/> Method</para>
         /// </remarks>
         protected GpioPort RegisterPin( string Name, Cpu.Pin Pin, GpioPortMode ExpectedMode, GpioPortMode AllowedMode )
         {
             if( this.State != EmulatorState.Configuration )
                 throw new InvalidOperationException(
                              "Cannot register GPIO Pins unless emulator is in the Configuration state" );
  
             GpioPort port = new GpioPort( );
             port.Pin = Pin;
             port.ModesAllowed = AllowedMode;
             port.ModesExpected = ExpectedMode;
             port.ComponentId = Name;
             RegisterComponent( port );
             return port;
         }
         #endregion

These methods are pretty straight forward and simplify some of the more tedious coding needed for registering pins.

So there’s the whole Emulator2 class with events, LCD, Touch and GPIO pin registration helper methods. Next post we’ll look at the XAML code for the engineers view and see how that ties back to the data binding in the ViewModel, then we’ll hit the ViewMode that acts as the glue to bring the XAML UI and the underlying engine together. “Tune in next week, same Bat time, same Bat channel…”