1080p HD TV on a Gadgeteer system

Oh, and ‘orange’ sounds like ‘gullible’ if you say it slowly…

In this post, I am not going to show you how to do HDTV on a Gadgeteer system. I am, however, going to do some stuff with the Sytech LCD touch panel, and show you how to do some simple (and rather rubbish, if truth be told) user interface interactions for your Gadgeteer projects.

The .NET Micro Framework supports pixel displays like LCDs in two ways. You can do GDI-like operations to draw directly on the display using the Bitmap class, or you can do somewhat more clever stuff using a trimmed-down version of the WPF libraries. We’ll do a bit of both. Firstly, you need to set up the hardware. I’m using a Fez Hydra main board, but you could use a Nano or a Spider. The display is a Sytech 4.3″ LCD touch panel. You could use a different (read: cheaper) LCD, but you might have to adjust various sizes in the code to make the program fit the display. the LCD requires four connectors to the main board: three for data and control, and one to retrieve input from the touch panel. The wiring is straightforward:

P1020250

Until, that is, you actually want to see the front of the display, the right way up, and then it suddenly isn’t as neat. Never mind, all the wires are hidden behind the display so I can’t see them:

P1020251

No more components are required at the moment (except the red power/USB board, which I hope goes without saying), so it’s time to go to Visual Studio and add the same components to the designer. Adding them and using auto-connect means that you get this situation:

gadgeteer_8-small

Now we can turn to the software.

Drawing stuff on the LCD

‘Simple’ drawing on the LCD is a three-step process:

  1. create a Bitmap object
  2. use methods of the Bitmap class to draw lines, circles, text and so on on the Bitmap
  3. use Bitmap‘s Flush() method to copy the bitmap to the display

Sounds a bit clunky, but it’s really not that bad. Here’s a simple example. Put this code in the ProgramStarted() method of your program:

Bitmap bmp = new Bitmap((int)lcdTouch.Width, (int)lcdTouch.Height);
bmp.DrawEllipse(Colors.Red, 50, 50, 25, 25);
bmp.Flush();

When you run it, you’ll see a black screen with a red circle on it. The only point needing any note at all here is the use of the constructor for the bitmap. In order to flush it to the display, it needs to be the same size as the display. That’s why we create it with the width and height properties of the lcdTouch object. We can draw lines, too:

bmp = new Bitmap((int)lcdTouch.Width, (int)lcdTouch.Height);
bmp.DrawEllipse(Colors.Red, 50, 50, 25, 25);
bmp.DrawLine(Colors.Red, 1, 0, 100, 100, 100);
bmp.DrawLine(Colors.Red, 1, 50, 75, 50, 175);
bmp.DrawLine(Colors.Red, 1, 50, 175, 0, 250);
bmp.DrawLine(Colors.Red, 1, 50, 175, 100, 250);
bmp.Flush();

There’s a problem with what we’ve done so far. All the code that does the drawing is in the ProgramStarted() method. This is ok for simple, one-off initialisation methods, but it’s not good design to put code which takes any significant length of time to run in there, because it’s not supposed to block the system. In any case it’s only ever called once, so if we want to do any animation or change the display we need to put the code elsewhere. To demonstrate this, let’s modify our program so that instead of drawing a static shape, it sshows a ball bouncing around. Animation is simple in principle: you delete the current drawing and create a new drawing slightly differeny. You do this many times a second, and the eye is fooled into seeing something moving on the screen. To implement this, we need three things: a method which draws our ball on the screen at a specific location, a means of modifying the location, and something which will keep doing this regularly. Let’s start with the drawing part:

public partial class Program
{
  Bitmap bmp;
  int x, y;

  void ProgramStarted()
  {
    bmp = new Bitmap((int)lcdTouch.Width, (int)lcdTouch.Height);
    x = 50;
    y = 50;
    DrawBall(x, y);
  }

  void DrawBall(int x, int y)
  {
     bmp.Clear();
     bmp.DrawEllipse(Colors.Green, x, y, 25, 25);
     bmp.Flush();
  }
}

There are a couple of things to note here: firstly, the Bitmap object is now defined at the class level, so we can use it over and over again. Secondly, it clears the bitmap before drawing the circle. If it didn’t do this, repeated calls with different coordinates would end up with many balls on the screen. Simply clearing the whole bitmap is not really very good practice, especially if the things to be drawn are complex, but it will do for now. When you run this program, you will see a green circle, resolutely not moving. To animate it, we need to add a timer, which will cause DrawBall() to be exectuted at regular intervals with changing values of x and y.

To make the animation work, we are going to use a Timer, which is defined in the Gadgeteer namespace. This will run in the background, and fire off an event at an interval of our choosing. By updating the display every time the event fires, we get animation. The modified code is shown below. the timer is defined at the class level, and initialised in the ProgramStarted() method. It’s set to fire off every 10 milliseconds, which makes for a nice smooth animation. When the timer fires, it calls the animationTimer_Tick() method. This method checks to see if the circle is about to leave the screen area, and if so reverses its direction of travel, then updates the circle’s position. Finally, it calls the DrawBall() method. Run this, and you should see a green circle bouncing around the LCD.

public partial class Program { Bitmap bmp; int x, y; int x_increment, y_increment; GT.Timer animationTimer; void ProgramStarted() { bmp = new Bitmap((int)lcdTouch.Width, (int)lcdTouch.Height); x = 50; y = 50; x_increment = 2; y_increment = 1; animationTimer = new GT.Timer(10); animationTimer.Tick+=new GT.Timer.TickEventHandler(animationTimer_Tick); animationTimer.Start(); } void animationTimer_Tick(GT.Timer timer) { if ((x > lcdTouch.Width - 25) || (x < 25)) x_increment = -x_increment; if ((y > lcdTouch.Height - 25) || (y < 25)) y_increment = -y_increment; x += x_increment; y += y_increment; bmp.Clear(); DrawBall(x,y); bmp.Flush(); } void DrawBall(int x, int y) { bmp.DrawEllipse(Colors.Green, x, y, 25, 25); } }

Bitmap has other simple drawing methods, of course. In addition to drawing ellipses and lines, you can draw other primitives such as rectangles and images. One of the more useful functions is the ability to draw text. The DrawText method takes as parameters a string to draw, a font, a colour and screen coordinates. The only even slightly tricky part about this is the font. The Micro Framework does not support the TrueType fonts we are used to using with the full framework. Instead it has its own .tinyfnt format, which maintains a small bitmap for each character. You can create tiny fonts from TrueType ones if you want to (there is a program called TFConvert.exe in the /tools folder of your Micro Framework installation), but you should have two already created and installed. These fonts (‘NinaB’ and ‘small’) will appear in the Resources folder of your project in Visual Studio. To use one of these fonts, we have to load it at run time from the project’s resources. We’ll define a new Font object at the class level, so we can use it anywhere, and we’ll initialise it in the ProgramStarted() Method. Then we’ll add a new method, DrawText() which will put some text on the screen showing the location of the ball. this method will be called just before DrawBall()

Add the font declaration, just below the bitmap declaration:

Font demoFont;

In the ProgramStarted() method, add the font initialisation just after the bitmap initialisation:

demoFont = Resources.GetFont(Resources.FontResources.NinaB);

In the animationTimer_Tick method, add a call to DrawText() to show the current location of the green circle:

DrawText(x.ToString() + "," + y.ToString(), 20, 20);

And finally add the DrawText() method itself:

void DrawText(string text,int x, int y)
{
  bmp.DrawText(text, demoFont, Colors.Red, x, y);
}

Run this, and you will get a bouncing green circle with red text showing its current coordinates, constantly updated.  It’s not a hugely remarkable (or indeed useful) application, but I think it shows that the Gadgeteer kit is capable of processing fast enough to do simple animations. 

Simple drawing on the LCD panel is one thing, but there are two other things we should investigate.  Firstly, this particular display is a touch screen.  We should be able to get input from it.  Secondly, I mentioned earlier that you can use WPF to draw on the display, and I have not forgotten that.  These two things are actually related, so we’ll deal with them both at once.

The LCDTouch class used to represent the display is descended from Gadgeteer.Module.DisplayModule.  This base class has a member called WPFWindow, which is a read-only reference to a WPF Window object.  You can use this in much the same way as you can use a WPF Window object in a standard Windows application (although the number of WPF objects supported in the Micro Framework is limited).  You can add StackPanels, TextBoxes and so on to the window, and then to update what is shown on the display you can simply change their properties and let WPF sort out the redrawing.  Let’s recreate our bouncing ball application using WPF.  To do this, we’ll need to create a Text control and an Ellipse control, then we will add them to a Canvas control, and add the Canvas to the Window.  We can then change the contents of the Text control and the position of the Ellipse control from within the animationTimer_Tick() method, and all will be as it was before.  Before we can use the Ellipse control, we need to add a reference to the Microsoft.SPOT.Presentation.Shapes namespace:

using Microsoft.SPOT.Presentation.Shapes;

Then we can add the declarations of the UI components we will be using:

Window displayWindow; Text textCoords;

Ellipse greenCircle;

Now in the ProgramStarted() method, remove all references to the Bitmap object we previously used, and add the following initialisation code:

displayWindow = lcdTouch.WPFWindow;

Canvas c = new Canvas();
displayWindow.Child = c;

textCoords = new Text(demoFont, "");
textCoords.ForeColor = Colors.Red;
c.Children.Add(textCoords);
Canvas.SetLeft(textCoords, 20);
Canvas.SetTop(textCoords, 20);

greenCircle = new Ellipse(25, 25);
greenCircle.Stroke = new Pen(Colors.Green);
c.Children.Add(greenCircle);

We are using a Canvas object, because we want to show two things (the text and the circle) on screen at once, in positions that we define and change.  The Window itself can only have one child object, so the canvas becomes the Window‘s child, and the Ellipse and the Text objects are children of the Canvas.  We can then set their positions using the static methods Canvas.SetLeft() and Canvas.SetTop().  All that remains is to modify the animationTimer_Tick() method to update our new objects, and we are done:

 

void  animationTimer_Tick(GT.Timer timer)
{
  if ((x > lcdTouch.Width - 50) || (x < 0)) 
    x_increment = -x_increment;
  if ((y > lcdTouch.Height - 50) || (y < 0)) 
    y_increment = -y_increment;
  x += x_increment;
  y += y_increment;

  textCoords.TextContent=x.ToString() + "," + y.ToString();
  Canvas.SetLeft(greenCircle, x);
  Canvas.SetTop(greenCircle, y);
}

Notice how we no longer need the DrawText() or DrawBall() methods.  To update the display, we just change the properties of the objects, and WPF does the rest. Run the application, and you should see much the same display as you saw with the previous version (except that the screen background will be white rather than black, because that’s WPF’s default colour). You may also notice that the animation of the circle seems a little more jerky. That’s not really surprising – the WPF framework has quite a bit more work to do maintaining the display based on object properties, so it’s not as fast for pure animation. Against this loss of speed, you can balance the simplicity of simple being able to update object properties to change the display.

finally, we can get on the using the touch features of the display. The Window class exposes six events related to the touch panel. The simple ones are TouchDown, TouchMove and TouchUp. Slightly more complex are TouchGestureStart, TouchGestureChanged and TouchGestureEnd. We’ll use the easy ones to begin with. TouchDown is triggered every time you press on the touch panel.  To make life easier, add the following  references to your program (if they are not already there).

using Microsoft.SPOT.Touch;
using Microsoft.SPOT.Input;

  Now, in the ProgramStarted() method, subscribe to the TouchDown event  

displayWindow.TouchDown +=

new TouchEventHandler(displayWindow_TouchDown);

And create a new handler as follows:

void displayWindow_TouchDown(object sender, TouchEventArgs e)
{
  x = e.Touches[0].X;
  y = e.Touches[0].Y;
}

Modern touch displays often support multi-touch.  That is, you can press on the screen in several places at once, and the system keeps track of all (or at least some) of these separate contacts.  To support this, the TouchEventArgs object that TouchDown sends contains an array of objects of type TouchInput, rather than just a single position.  We are only going to worry about a single touch, so we can just examine the first object in the array and take its screen coordinates.  Setting the x and y variables to these means that the moving circle on the screen will jump to the touch point.

It’s perhaps slightly surprising that the MicroFramework version of WPF does not have a button control (it does have hardware Button controls, of course), but all the WPF controls like Ellipse and Rectangle support the TouchDown and TouchUp methods, so it’s pretty easy to create your own if required.

That’s all for this post.  Next time, we’ll use more input devices and perhaps hack some non-Gadgeteer hardware to allow it to be controlled by the MicroFramework device.


	

Leave a Reply

Your email address will not be published. Required fields are marked *