Main Site Documentation

FEZ Touch Driver Ver 2.1


#1

I’ve updated the FEZ Touch driver to include support for:

[ulist]
Landscape and Portrait modes
Fixed and Proportional Fonts
Display mode (On, Dim, or Off)
[/ulist]

I’ve also created a FEZ Touch Font Generator application for creating custom fixed and proportional fonts for use with the new driver.

The Font Generator and Driver were updated to fix a bug and to add compressed font bitmaps.

The new driver and the font generator application can be found on TinyCLR Code at:

http://code.tinyclr.com/project/363/fez-touch-driver-ver-21/

http://code.tinyclr.com/project/364/fez-touch-font-generator/


#2

Interesting. Thanks!


#3

Will be very useful to a lot of users, simply great and thanks for sharing.


#4

+1


#5

Jasdev, thank you for your interesting ui-interface!
I downloaded your code, but got a lot of errors, if I try to compile it.
Please could you make a c#-project package (for me a Panda II)?

Btw: I thought a little on the same subject: perhaps it would improve the speed if you use the last bit of each color to determine in which control the user clicked. So you have 23 different identificators for your controls.
with other words: the click-event determines the RGB-value of the clicked pixel (via x,y coord) by reading the pixel out of the driver pixel map and uses the last bit of each color to determine, which control was clicked. Offcourse you must change the last 3 bits of the 3 colors while you generate the controls. I think the change in color of the last bit, (perhaps the last 2 bits if you want 4
3= 64 controls) is below the visibility.
Its a bit difficult to explain, but I thing the use of this existing bitmap in the driver will accelerate the identification of the control in the click event. (But as you see above: I was not able to run your testprog. So perhaps there are no performance problems and you can forget this idea.


#6

Thank you Andreas.

The zip file contains a complete Panda II project for Visual Studio. Have you tried that?
I’m at work right now, so I can’t test it, but if you’re still having problems I will download it later and try it on my computer again.


#7

are you shure? I found in the zip file only the help file (chm) with a broken link. Perhaps I looked on the wrong place?


#8

I just checked, and it does include the entire project. Please try again.


#9

jasdev sorry! you are right! I mixed several drivers. Thank you.


#10

No problem. Tonight I’ll look into your suggestions for speeding up the driver. Thank you.


#11

I am sure you saw this coming?

Landscape is a great addition, But, I would like the origin in the diagonally opposite corner (upside down landscape). Has to do with how I would like to mount the lcd in a case, in relation to other additional components, simpler wiring loom etc.

According to the ILI9325 data sheet, if i read it right, setting register 0h03 to 0h0298 would give that orientation. This would be AM bit to 1, ID0 bit to 1, ID1 bit to 0, leaving other bits set as you have them for landscape.

There are numerous other references to ‘lcdorientation’ in the code. Any hints on where to start the thought process to flip landscape over?


#12

Hi RDH,

Yes, I think you are correct. You’ll notice that I also set the HWM bit to ‘1’, because it said “high speed” mode, but I’m not sure it acutally does anything. At least I’m not sure how to use it :slight_smile:


#13

Andreas,

Your suggested change would possibly speed up the touch detection, but that isn’t really a problem. It is very fast already. Drawing is slow. I’m not sure why. I have to investigate further.


#14

Jasdev
I played a little with your code and the UI-Control. if I use a Interrupt driven event-fireing I reduce the programm init of the UI-Control from 23s to 19s. As far as my interrupt routine will run without errors I post it here.
But you are right, the bottleneck is the drawing. I suppose, if one send images instead of setting pixels it would be faster.


#15

Andreas, that sounds interesting. Please post your changes when you get them working. Thanks.


#16

Changed my program over to use the 2.1 driver. Set up to use landscape and a 10 pt courier font.
The text writes to the screen much faster than the original drivers. The larger font doesn’t put as much strain on the bifocals?

I use the fez-touch-ui-controls, had to change all the draw text lines.

added the font to the variables in the call
moved the string from beginning to after the x, y position, different position than the original
referenced the font width, height where needed.

I did try changing the write to 0h03 to 298, it does flip the display over, but, the text writes from right to left, top and bottom are backwards, also all text chars are backwards. Have not taken the time to figure out how to flip the x and y coords to get it to look right. The FEZ monkey img looks right tho???

Great upgrade!!!


#17

jasdev
here are the changed snippeds for the[italic] irq driven touch events[/italic]. Now the thread.polls only between touch down and touch up

            private static SPI spi;
            public static InterruptPort touchIRQ;
            private static bool terminateTouchThread;
            private static bool bIRQrunning=false;
[...]
            private void touch_OnInterrupt(uint iPort, uint iState, DateTime time)
            {
                terminateTouchThread = (iState == 1);   //   0: down 1:up
                if (!bIRQrunning)
                {
                    touchThread = new Thread(TouchThread);
                    touchThread.Priority = ThreadPriority.AboveNormal;
                    touchThread.Start();
                }
            }
            private void TouchThread()
            {
                if (bIRQrunning) return;
                bIRQrunning = true;
                int x = 0;
                int y = 0;
                getTouchCoord(ref x, ref y);
                FireTouchDownEvent(x, y);

                while (terminateTouchThread == false)
                {
                    Thread.Sleep(TOUCH_SAMPLING_TIME);
                    getTouchCoord(ref x, ref y);
                    this.FireTouchMoveEvent(x, y);
                }
                getTouchCoord(ref x, ref y);
                this.FireTouchUpEvent(x, y);
                bIRQrunning = false;
            }

            static byte[] writeBuffer = new byte[] { 0, 0, 0, 0 };
            static byte[] readBuffer = new byte[2];
            private const int TOUCHXMAX = 3385;
            private const int TOUCHXMIN = 620;
            private const int TOUCHYMIN = 800;
            private const int TOUCHYMAX = 3500;
            //private int xMin=5000;
            //private int xMax = 0;
            //private int yMin = 5000;
            //private int yMax = 0;
            private void getTouchCoord(ref int x, ref int y)
            {
                touchIRQ.OnInterrupt -= new NativeEventHandler(touch_OnInterrupt);
                writeBuffer[0] = 0x90;
                spi.WriteRead(writeBuffer, readBuffer, 1);
                y = readBuffer[0];
                y <<= 8;
                y |= readBuffer[1];
                y >>= 3;

                writeBuffer[0] = 0xD0;
                spi.WriteRead(writeBuffer, readBuffer, 1);
                touchIRQ.OnInterrupt += new NativeEventHandler(touch_OnInterrupt);
                x = readBuffer[0];
                x <<= 8;
                x |= readBuffer[1];
                x >>= 3;
                //if (x != 0 && x != 4095 && y != 0 && y != 4095) //get extrema
                //{
                //    if (x < xMin) { xMin = x; Debug.Print("xmin=" + xMin.ToString()); }
                //    if (x > xMax) { xMax = x; Debug.Print("xmax=" + xMax.ToString()); }
                //    if (y < yMin) { yMin = y; Debug.Print("ymin=" + yMin.ToString()); }
                //    if (y > yMax) { yMax = y; Debug.Print("ymax=" + yMax.ToString()); }
                //}
                // calibrate 
 
                x = (TOUCHXMAX - x) * (SCREEN_WIDTH - 1) / (TOUCHXMAX-TOUCHXMIN); 
                y = (TOUCHYMAX - y) * (SCREEN_HEIGHT - 1) / (TOUCHYMAX-TOUCHYMIN);
                y = y > SCREEN_HEIGHT ? SCREEN_HEIGHT : y; y = y < 0 ? 0 : y;
                x = x > SCREEN_WIDTH ? SCREEN_WIDTH : x; x = x < 0 ? 0 : x;
             }


#18

jasdev
if I use your font-generator (looks fine!!!) I get

    #### Exception System.ArgumentOutOfRangeException - CLR_E_OUT_OF_RANGE (1) ####
    #### Message: 
    #### System.String::get_Chars [IP: 0000] ####
    #### GHIElectronics.NETMF.FEZ.FEZ_Components+FEZTouch+Font::get_Item [IP: 003a] ####
    #### GHIElectronics.NETMF.FEZ.FEZ_Components+FEZTouch+Font::GetTextWidth [IP: 0019] ####
    #### FEZ_Touch_2.Program::DisplayGraphics [IP: 0016] ####
    #### FEZ_Touch_2.Program::Main [IP: 0008] ####

at

         ushort charWidth = (ushort)(this.charDescriptors[descriptorIndex] & bitMask);

Did I something wrong?


#19

does anyone know, how to get the color of a pixel from the display.
ie. the invers of setPixel(x,y,color)
I mean a function long getPixelColor(x,y) which returns the RGB-Value of the Pixel at x,y
(With such function the touchEvents can deliver also a signature of the control, which fired the event. If you change the colors of your several controls slightly, you can identify them by color.)


#20

I’ve updated the driver to Version 2.2, which includes a LandscapeInverse mode:


/*
Copyright 2011 GHI Electronics LLC
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 
You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 
either express or implied. See the License for the specific language governing permissions and limitations under the License. 
*/

using System;
using System.Threading;

using Microsoft.SPOT.Hardware;

using GHIElectronics.NETMF.Hardware;


namespace GHIElectronics.NETMF.FEZ
{
    public static partial class FEZ_Components
    {
        public class FEZTouch : IDisposable
        {
            #region Enumerated Values

            public enum Orientation
            {
                Portrait = 0,
                Landscape = 1,
				LandscapeInverse = 2
            }

            public enum Color : ushort
            {
                White = 0xFFFF,
                Black = 0x0000,
                Red = (0xFF >> 3) | ((0 & 0xFC) << 3) | ((0 & 0xF8) << 8),
                Blue = (0 >> 3) | ((0 & 0xFC) << 3) | ((0xFF & 0xF8) << 8),
				Green = (0 >> 3) | ((0xFF & 0xFC) << 3) | ((0 & 0xF8) << 8),
				Cyan = (0 >> 3) | ((0xFF & 0xFC) << 3) | ((0xFF & 0xF8) << 8),
                Gray = (0x80 >> 3) | ((0x80 & 0xFC) << 3) | ((0x80 & 0xF8) << 8),
				Magneta = (0xFF >> 3) | ((0 & 0xFC) << 3) | ((0xFF & 0xF8) << 8),
				Yellow = (0xFF >> 3) | ((0xFF & 0xFC) << 3) | ((0 & 0xF8) << 8),
            }

			public enum PowerMode
			{
				Normal = 0,
				Sleep = 1,
				StandBy = 2,
				DeepStandBy = 3
			}

			public enum DisplayMode
			{
				Normal = 0,
				Dim = 1,
				Off = 2
			}

			#endregion


			#region Events

			public delegate void TouchEventHandler(int x, int y);

			public event TouchEventHandler TouchDownEvent = delegate { };
			public event TouchEventHandler TouchMoveEvent = delegate { };
			public event TouchEventHandler TouchUpEvent = delegate { };

			#endregion


			#region Constructors

            protected FEZTouch()
            {
                // not allowed to call this constructor
			}

            public FEZTouch(LCDConfiguration lcdConfig)
            {
                this.InitLCD(lcdConfig);
            }

            public FEZTouch(LCDConfiguration lcdConfig, TouchConfiguration touchConfig)
            {
				this.InitLCD(lcdConfig);
                this.InitTouch(touchConfig);
            }

            #endregion


            #region Destructors

            ~FEZTouch()
            {
                this.Dispose();
            }

            public void Dispose()
            {
                if (this.disposed == false)
                {
                    this.disposed = true;

                    if (this.spi != null)
                    {
                        this.terminateTouchThread = true;
                        this.touchThread.Join();

                        this.spi.Dispose();
                        this.touchIRQ.Dispose();
                    }

                    this.TouchDownEvent = null;
					this.TouchMoveEvent = null;
					this.TouchUpEvent = null;

                    this.parallelPort.Dispose();
                    this.lcdReset.Dispose();
                    this.lcdChipSelect.Dispose();
                    this.lcdRegSelect.Dispose();
                    this.lcdBackLight.Dispose();
                }
            }

            #endregion


            #region Properties

            public int ScreenWidth
            {
                get { return (this.lcdOrientation == Orientation.Portrait ? 240 : 320); }
            }

            public int ScreenHeight
            {
                get { return (this.lcdOrientation == Orientation.Portrait ? 320 : 240); }
            }

			#endregion


			#region Control Methods

			private void InitLCD(LCDConfiguration lcdConfig)
			{
				// save config values
				this.parallelPort = new ParallelPort(lcdConfig.DataPins, lcdConfig.WritePin, lcdConfig.ReadPin);
				this.lcdReset = new OutputPort(lcdConfig.Reset, true);
				this.lcdChipSelect = new OutputPort(lcdConfig.ChipSelect, true);
				this.lcdRegSelect = new OutputPort(lcdConfig.RS, true);
				this.lcdBackLight = new OutputCompare(lcdConfig.BackLight, true, 2);
				this.lcdOrientation = lcdConfig.LCDOrientation;

				// set initial power and display modes
				this.displayMode = DisplayMode.Normal;
				this.backlightLevel = 100;

				// toggle reset pin
				this.lcdReset.Write(true);
				Thread.Sleep(5);
				this.lcdReset.Write(false);
				Thread.Sleep(5);
				this.lcdReset.Write(true);
				Thread.Sleep(5);

				this.lcdChipSelect.Write(false);

				//************* Start Initial Sequence **********//
				this.WriteRegister(0x01, 0x0100); // set SS and SM bit
				this.WriteRegister(0x02, 0x0200); // set 1 line inversion
				switch (this.lcdOrientation)
				{
					case Orientation.Portrait:
						// AM		=	0
						// ID1-ID0	=	10
						// ORG		=	1
						// HWM		=	1
						this.WriteRegister(0x03, 0x0230);
						break;
					case Orientation.Landscape:
						// AM		=	1
						// ID1-ID0	=	11
						// ORG		=	0
						// HWM		=	1
						this.WriteRegister(0x03, 0x02A8);
						break;
					case Orientation.LandscapeInverse:
						// AM		=	1
						// ID1-ID0	=	01
						// ORG		=	0
						// HWM		=	1
						this.WriteRegister(0x03, 0x0298);
						break;
				}
				this.WriteRegister(0x04, 0x0000); // Resize register
				this.WriteRegister(0x08, 0x0207); // set the back porch and front porch
				this.WriteRegister(0x09, 0x0000); // set non-display area refresh cycle ISC[3:0]
				this.WriteRegister(0x0A, 0x0000); // FMARK function
				this.WriteRegister(0x0C, 0x0000); // RGB interface setting
				this.WriteRegister(0x0D, 0x0000); // Frame marker Position
				this.WriteRegister(0x0F, 0x0000); // RGB interface polarity

				//*************Power On sequence ****************//
				this.WriteRegister(0x10, 0x0000); // SAP, BT[3:0], AP, DSTB, SLP, STB
				this.WriteRegister(0x11, 0x0007); // DC1[2:0], DC0[2:0], VC[2:0]
				this.WriteRegister(0x12, 0x0000); // VREG1OUT voltage
				this.WriteRegister(0x13, 0x0000); // VDV[4:0] for VCOM amplitude
				this.WriteRegister(0x07, 0x0001);
				Thread.Sleep(200); // Dis-charge capacitor power voltage
				this.WriteRegister(0x10, 0x1690); // SAP, BT[3:0], AP, DSTB, SLP, STB
				this.WriteRegister(0x11, 0x0227); // Set DC1[2:0], DC0[2:0], VC[2:0]
				Thread.Sleep(50); // Delay 50ms
				this.WriteRegister(0x12, 0x000D); // 0012
				Thread.Sleep(50); // Delay 50ms
				this.WriteRegister(0x13, 0x1200); // VDV[4:0] for VCOM amplitude
				this.WriteRegister(0x29, 0x000A); // 04 VCM[5:0] for VCOMH
				this.WriteRegister(0x2B, 0x000D); // Set Frame Rate
				Thread.Sleep(50); // delay 50ms
				this.WriteRegister(0x20, 0x0000); // GRAM horizontal Address
				this.WriteRegister(0x21, 0x0000); // GRAM Vertical Address

				// ----------- Adjust the Gamma Curve ----------//
				this.WriteRegister(0x30, 0x0000);
				this.WriteRegister(0x31, 0x0404);
				this.WriteRegister(0x32, 0x0003);
				this.WriteRegister(0x35, 0x0405);
				this.WriteRegister(0x36, 0x0808);
				this.WriteRegister(0x37, 0x0407);
				this.WriteRegister(0x38, 0x0303);
				this.WriteRegister(0x39, 0x0707);
				this.WriteRegister(0x3C, 0x0504);
				this.WriteRegister(0x3D, 0x0808);

				//------------------ Set GRAM area ---------------//
				this.WriteRegister(0x50, 0x0000); // Horizontal GRAM Start Address
				this.WriteRegister(0x51, 0x00EF); // Horizontal GRAM End Address
				this.WriteRegister(0x52, 0x0000); // Vertical GRAM Start Address
				this.WriteRegister(0x53, 0x013F); // Vertical GRAM Start Address
				this.WriteRegister(0x60, 0xA700); // Gate Scan Line
				this.WriteRegister(0x61, 0x0001); // NDL, VLE, REV
				this.WriteRegister(0x6A, 0x0000); // set scrolling line

				//-------------- Partial Display Control ---------//
				this.WriteRegister(0x80, 0x0000);
				this.WriteRegister(0x81, 0x0000);
				this.WriteRegister(0x82, 0x0000);
				this.WriteRegister(0x83, 0x0000);
				this.WriteRegister(0x84, 0x0000);
				this.WriteRegister(0x85, 0x0000);

				//-------------- Panel Control -------------------//
				this.WriteRegister(0x90, 0x0010);
				this.WriteRegister(0x92, 0x0000);
				this.WriteRegister(0x07, 0x0133); // 262K color and display ON

				this.lcdChipSelect.Write(true);
			}

			private void InitTouch(TouchConfiguration touchConfig)
			{
				this.spi = new SPI(new SPI.Configuration(touchConfig.ChipSelect, false, 1, 1, false, true, 2000, touchConfig.Channel));
				this.touchIRQ = new InputPort(touchConfig.TouchIRQ, false, Port.ResistorMode.Disabled);
				this.terminateTouchThread = false;
				this.touchThread = new Thread(this.TouchThread);
				this.touchThread.Priority = ThreadPriority.Highest;
				this.touchThread.Start();
			}

			/*
			public void SetPowerMode(PowerMode powerMode)
			{
				if (this.powerMode != powerMode)
				{
					// save power mode
					this.powerMode = powerMode;

					// set new power mode
					this.lcdChipSelect.Write(false);

					switch (this.powerMode)
					{
						case PowerMode.Normal:
							this.WriteRegister(0x10, 0x1690);
							break;
						case PowerMode.Sleep:
							this.WriteRegister(0x10, 0x1691);
							break;
						case PowerMode.StandBy:
							this.WriteRegister(0x10, 0x1692);
							break;
						case PowerMode.DeepStandBy:
							this.WriteRegister(0x10, 0x1694);
							break;
					}

					this.lcdChipSelect.Write(true);
				}
			}
			*/

			public void SetDisplayMode(DisplayMode displayMode)
			{
				if (this.displayMode != displayMode)
				{
					// save display mode
					this.displayMode = displayMode;

					// set new display mode
					this.lcdChipSelect.Write(false);

					switch (this.displayMode)
					{
						case DisplayMode.Normal:
							this.SetBackLightLevel(100);
							this.WriteRegister(0x07, 0x0133);
							break;
						case DisplayMode.Dim:
							this.SetBackLightLevel(5);
							this.WriteRegister(0x07, 0x0133);
							break;
						case DisplayMode.Off:
							this.SetBackLightLevel(0);
							this.WriteRegister(0x07, 0x0131);
							break;
					}

					this.lcdChipSelect.Write(true);
				}
			}

			public void SetBackLightLevel(int backlightLevel)
			{
				// check for valid parameters
				if (backlightLevel < 0 || backlightLevel > 100)
				{
					throw new ArgumentException();
				}

				if (this.backlightLevel != backlightLevel)
				{
					// save back light level
					this.backlightLevel = backlightLevel;

					// set new back light level
					if (this.backlightLevel == 0)
					{
						this.lcdBackLight.Set(false);
					}
					else if (this.backlightLevel == 100)
					{
						this.lcdBackLight.Set(true);
					}
					else
					{
						const int PERIOD = 10000;
						int highTime = PERIOD * this.backlightLevel / 100;
						this.lcdBackLight.Set(true, new uint[2] { (uint)highTime, (uint)(PERIOD - highTime) }, 0, 2, true);
					}
				}
			}

			#endregion


			#region Drawing Methods

			public void ClearScreen()
            {
                this.FillRectangle(0, 0, this.ScreenWidth, this.ScreenHeight, FEZ_Components.FEZTouch.Color.Black);  // fill screen with black
            }

            public Color ColorFromRGB(byte red, byte green, byte blue)
            {
                return (Color)((red >> 3) | ((green & 0xFC) << 3) | ((blue & 0xF8) << 8));
            }

			private void SetPixelAddress(int x, int y)
			{
				this.WriteRegister(0x20, (ushort)x);
				this.WriteRegister(0x21, (ushort)y);
			}

			private void SetDrawingWindow(int x, int y, int width, int height)
			{
				// pixel address
				this.SetPixelAddress(x, y);

				// window
				this.WriteRegister(0x50, (ushort)x);
				this.WriteRegister(0x52, (ushort)y);
				this.WriteRegister(0x51, (ushort)(x + width - 1));
				this.WriteRegister(0x53, (ushort)(y + height - 1));
			}

            public void SetPixel(int x, int y, Color color)
            {
                if (x < 0 || y < 0 || x >= this.ScreenWidth || y >= this.ScreenHeight)
                {
                    throw new ArgumentException("SetPixel: Invalid parameter values.");
                }

                this.lcdChipSelect.Write(false);

                this.buffer[0] = (byte)((int)color >> 8);
                this.buffer[1] = (byte)(color);

                this.SetDrawingWindow(x, y, 1, 1);

                this.SetRegister(REGISTER_WRITE_GRAM);
                this.parallelPort.Write(this.buffer, 0, 2);

                this.lcdChipSelect.Write(true);
            }

            public void DrawLine(int xStart, int yStart, int xEnd, int yEnd, Color color)
            {
				if (xStart < 0 || yStart < 0 || xStart >= this.ScreenWidth || yStart >= this.ScreenHeight)
                {
                    throw new ArgumentException("DrawLine: Invalid start position values.");
                }

				if (xEnd < 0 || yEnd < 0 || xEnd >= this.ScreenWidth || yEnd >= this.ScreenHeight)
                {
					throw new ArgumentException("DrawLine: Invalid end position values.");
				}

				// translate coordinates based on display orientation
				int x0 = 0;
				int y0 = 0;
				int x1 = 0;
				int y1 = 0;
				switch (this.lcdOrientation)
				{
					case Orientation.Portrait:
						x0 = xStart;
						y0 = yStart;
						x1 = xEnd;
						y1 = yEnd;
						break;
					case Orientation.Landscape:
						x0 = SCREEN_WIDTH - yStart - 1;
						y0 = xStart;
						x1 = SCREEN_WIDTH - yEnd - 1;
						y1 = xEnd;
						break;
					case Orientation.LandscapeInverse:
						x0 = yStart;
						y0 = SCREEN_HEIGHT - xStart - 1;
						x1 = yEnd;
						y1 = SCREEN_HEIGHT - xEnd - 1;
						break;
				}

				this.lcdChipSelect.Write(false);

                this.buffer[0] = (byte)((int)color >> 8);
                this.buffer[1] = (byte)(color);

                int dy = y1 - y0;
                int dx = x1 - x0;

                float m = 0;
                int b = 0;

                if (dx != 0)
                {
                    m = ((float)(dy)) / (dx);
                    b = y0 - (int)(m * x0);
                }

				if (global::System.Math.Abs(dx) >= global::System.Math.Abs(dy))
                {
					if (x0 > x1)
					{
                        this.Swap(ref x0, ref x1);
                        this.Swap(ref y0, ref y1);
                    }

                    while (x0 <= x1)
                    {
						this.SetDrawingWindow(x0, y0, 1, 1);

                        this.SetRegister(REGISTER_WRITE_GRAM);
                        this.parallelPort.Write(this.buffer, 0, 2);

                        x0++;

                        if (x0 <= x1)
                        {
                            y0 = (int)(m * x0) + b;
                        }
                    }
                }
                else
                {
					if (y0 > y1)
                    {
                        this.Swap(ref x0, ref x1);
                        this.Swap(ref y0, ref y1);
                    }

                    while (y0 <= y1)
                    {
						this.SetDrawingWindow(x0, y0, 1, 1);

                        this.SetRegister(REGISTER_WRITE_GRAM);
                        this.parallelPort.Write(this.buffer, 0, 2);

                        y0++;

                        if (y0 <= y1)
                        {
							if (dx != 0)
							{
								x0 = (int)((float)(y0 - b) / m);
							}
                        }
                    }
                }

                this.lcdChipSelect.Write(true);
            }

            public void FillRectangle(int xPos, int yPos, int rectWidth, int rectHeight, Color color)
            {
				// validate parameter values
                if (xPos < 0 || yPos < 0 || (xPos + rectWidth) > this.ScreenWidth || (yPos + rectHeight) > this.ScreenHeight)
                {
                    throw new ArgumentException("FillRectangle: Invalid parameter values.");
                }

				// translate coordinates based on display orientation
				int x = 0;
				int y = 0;
				int width = 0;
				int height = 0;
				switch (this.lcdOrientation)
				{
					case Orientation.Portrait:
						x = xPos;
						y = yPos;
						width = rectWidth;
						height = rectHeight;
						break;
					case Orientation.Landscape:
						x = SCREEN_WIDTH - yPos - rectHeight;
						y = xPos;
						width = rectHeight;
						height = rectWidth;
						break;
					case Orientation.LandscapeInverse:
						x = yPos;
						y = SCREEN_HEIGHT - xPos - rectWidth;
						width = rectHeight;
						height = rectWidth;
						break;
				}

                this.lcdChipSelect.Write(false);

                int pixelCount = width * height;
                int bufferPixels = this.buffer.Length / 2; // every pixel is 2 bytes
                byte h = (byte)((int)color >> 8);
                byte l = (byte)(color);

                // fill buffer
                for (int i = 0; i < this.buffer.Length; i = i + 2)
                {
                    this.buffer[i] = h;
                    this.buffer[i + 1] = l;
                }

                this.SetDrawingWindow(x, y, width, height);

                this.SetRegister(REGISTER_WRITE_GRAM);

                int loops = pixelCount / bufferPixels;

                for (int i = 0; i < loops; i++)
                {
                    this.parallelPort.Write(this.buffer, 0, this.buffer.Length);
                }

                int pixelsLeft = pixelCount % bufferPixels;
                if (pixelsLeft > 0)
                {
                    // every pixel is 2 bytes
                    this.parallelPort.Write(this.buffer, 0, pixelsLeft * 2);
                }

                this.lcdChipSelect.Write(true);
            }

            public void DrawImage(int xPos, int yPos, Image image)
            {
				// validate parameter values
				if (xPos < 0 || yPos < 0 || (xPos + image.Width) > this.ScreenWidth || (yPos + image.Height) > this.ScreenHeight)
				{
					throw new ArgumentException("DrawImage: Invalid parameter values.");
				}

				// translate coordinates based on display orientation
				int x = 0;
				int y = 0;
				int width = 0;
				int height = 0;
				switch (this.lcdOrientation)
				{
					case Orientation.Portrait:
						x = xPos;
						y = yPos;
						width = image.Width;
						height = image.Height;
						break;
					case Orientation.Landscape:
						x = SCREEN_WIDTH - yPos - image.Height;
						y = xPos;
						width = image.Height;
						height = image.Width;
						break;
					case Orientation.LandscapeInverse:
						x = yPos;
						y = SCREEN_HEIGHT - xPos - image.Width;
						width = image.Height;
						height = image.Width;
						break;
				}

				this.lcdChipSelect.Write(false);
                this.SetDrawingWindow(x, y, width, height);
                this.SetRegister(REGISTER_WRITE_GRAM);
                this.parallelPort.Write(image.ImageBytes, Image.IMG_PIXELS_INDEX, image.ImageBytes.Length - Image.IMG_PIXELS_INDEX);
                this.lcdChipSelect.Write(true);
            }

			public void DrawString(int xPos, int yPos, string text, Color fgColor, Color bgColor, Font font)
			{
				// validate parameter values
				if (xPos < 0 || yPos < 0)
				{
					throw new ArgumentException("DrawString: Screen position (x,y) is invalid.");
				}

				if ((xPos + font.GetTextWidth(text)) > this.ScreenWidth || (yPos + font.Height) > this.ScreenHeight)
				{
					throw new ArgumentException("DrawString: Text string is too wide or too high to fit on the screen.");
				}

				// translate coordinates based on display orientation
				int x = 0;
				int y = 0;
				int width = 0;
				int height = 0;
				switch (this.lcdOrientation)
				{
					case Orientation.Portrait:
						x = xPos;
						y = yPos;
						break;
					case Orientation.Landscape:
						x = SCREEN_WIDTH - yPos - font.Height;
						y = xPos;
						break;
					case Orientation.LandscapeInverse:
						x = yPos;
						y = SCREEN_HEIGHT - xPos - font.AverageWidth;
						break;
				}

				// split colors into bytes
				byte fgColorHigh = (byte)((int)fgColor >> 8);
				byte fgColorLow = (byte)(fgColor);
				byte bgColorHigh = (byte)((int)bgColor >> 8);
				byte bgColorLow = (byte)(bgColor);

				// draw each character
				char currentChar = ' ';
				CharInfo charInfo = null;
				int bytesInBuffer = 0;
				for (int charIndex = 0; charIndex < text.Length; charIndex++)
				{
					// get character
					currentChar = text[charIndex];

					// get character info
					charInfo = font[currentChar];

					// translate width and height based on display orientation
					switch (this.lcdOrientation)
					{
						case Orientation.Portrait:
							width = charInfo.Width;
							height = charInfo.Height;
							break;
						case Orientation.Landscape:
							width = charInfo.Height;
							height = charInfo.Width;
							break;
						case Orientation.LandscapeInverse:
							width = charInfo.Height;
							height = charInfo.Width;
							break;
					}

					// get character pixels
					bytesInBuffer = font.FillBitmapBuffer(charInfo, this.buffer, fgColorLow, fgColorHigh, bgColorLow, bgColorHigh);

					// output character pixels
					this.lcdChipSelect.Write(false);
					this.SetDrawingWindow(x, y, width, height);
					this.SetRegister(REGISTER_WRITE_GRAM);
					this.parallelPort.Write(this.buffer, 0, bytesInBuffer);
					this.lcdChipSelect.Write(true);

					// update x position
					switch (this.lcdOrientation)
					{
						case Orientation.Portrait:
							x += width;
							break;
						case Orientation.Landscape:
							y += height;
							break;
						case Orientation.LandscapeInverse:
							y -= height;
							break;
					}
				}
			}

			#endregion


			#region Interface Methods

			private void Swap(ref int a1, ref int a2)
            {
                int temp = a1;
                a1 = a2;
                a2 = temp;
            }

            private void SetRegister(byte register)
            {
                 this.lcdRegSelect.Write(false);

                this.regBuffer[0] = 0;
				this.regBuffer[1] = register;
                this.parallelPort.Write(this.regBuffer, 0, 2);

                 this.lcdRegSelect.Write(true);
            }

			private void WriteRegister(byte register, ushort value)
            {
				this.SetRegister(register);

                this.regBuffer[0] = (byte)(value >> 8);
                this.regBuffer[1] = (byte)(value);
                this.parallelPort.Write(regBuffer, 0, 2);
            }

            #endregion


            #region Touch Thread & Methods

            private void TouchThread()
            {
                int lastX = 0;
                int lastY = 0;
                bool lastStatus = false; // true means there are touches

                int x;
                int y;
                bool status;

                byte[] writeBuffer = new byte[] { 0, 0, 0, 0 };
                byte[] readBuffer = new byte[2];

                while (this.terminateTouchThread == false)
                {
                    Thread.Sleep(TOUCH_SAMPLING_TIME);

                    status = !this.touchIRQ.Read();

                    if (status == true)
                    {
                        writeBuffer[0] = 0x90;
                        this.spi.WriteRead(writeBuffer, readBuffer, 1);
                        y = readBuffer[0];
                        y <<= 8;
                        y |= readBuffer[1];
                        y >>= 3;

                        writeBuffer[0] = 0xD0;
                        this.spi.WriteRead(writeBuffer, readBuffer, 1);
                        x = readBuffer[0];
                        x <<= 8;
                        x |= readBuffer[1];
                        x >>= 3;

                        // calibrate 
                        if (x > 3750)
                            x = 3750;
                        else if (x < 280)
                            x = 280;

                        if (y > 3850)
                            y = 3850;
                        else if (y < 450)
                            y = 450;

                        x = (3750 - x) * (SCREEN_WIDTH - 1) / (3750 - 280);
                        y = (3850 - y) * (SCREEN_HEIGHT - 1) / (3850 - 450);

                        if (lastStatus == false)
                        {
							this.FireTouchDownEvent(x, y);

                            lastStatus = true;
                            lastX = x;
                            lastY = y;
                        }
                        else
                        {
                            // filter small changes
                            if (global::System.Math.Abs(x - lastX) > 5 || global::System.Math.Abs(y - lastY) > 5)
                            {
								this.FireTouchMoveEvent(x, y);
                                lastX = x;
                                lastY = y;
                            }
                        }
                    }
                    else if (lastStatus == true)
                    {
						this.FireTouchUpEvent(lastX, lastY);
                        lastStatus = false;
                    }
                }
            }

			private void FireTouchDownEvent(int xPos, int yPos)
			{
				if (this.TouchDownEvent != null)
				{
					// translate coordinates based on display orientation
					int x = 0;
					int y = 0;
					switch (this.lcdOrientation)
					{
						case Orientation.Portrait:
							x = xPos;
							y = yPos;
							break;
						case Orientation.Landscape:
							x = yPos;
							y = SCREEN_WIDTH - xPos - 1;
							break;
						case Orientation.LandscapeInverse:
							x = SCREEN_HEIGHT - yPos - 1;
							y = xPos;
							break;
					}

					// fire the event
					this.TouchDownEvent(x, y);
				}
			}

			private void FireTouchMoveEvent(int xPos, int yPos)
			{
				if (this.TouchMoveEvent != null)
				{
					// translate coordinates based on display orientation
					int x = 0;
					int y = 0;
					switch (this.lcdOrientation)
					{
						case Orientation.Portrait:
							x = xPos;
							y = yPos;
							break;
						case Orientation.Landscape:
							x = yPos;
							y = SCREEN_WIDTH - xPos - 1;
							break;
						case Orientation.LandscapeInverse:
							x = SCREEN_HEIGHT - yPos - 1;
							y = xPos;
							break;
					}

					// fire the event
					this.TouchMoveEvent(x, y);
				}
			}

			private void FireTouchUpEvent(int xPos, int yPos)
			{
				if (this.TouchUpEvent != null)
				{
					// translate coordinates based on display orientation
					int x = 0;
					int y = 0;
					switch (this.lcdOrientation)
					{
						case Orientation.Portrait:
							x = xPos;
							y = yPos;
							break;
						case Orientation.Landscape:
							x = yPos;
							y = SCREEN_WIDTH - xPos - 1;
							break;
						case Orientation.LandscapeInverse:
							x = SCREEN_HEIGHT - yPos - 1;
							y = xPos;
							break;
					}

					// fire the event
					this.TouchUpEvent(x, y);
				}
			}

            #endregion


            #region Font Classes

            public class Font
            {
                // CONSTRUCTORS
                protected Font()
                {
                    // this constructor is not for external access
                    this.avgWidth = 0;
                    this.maxWidth = 0;
                    this.height = 0;
                    this.startChar = '\x00';
                    this.endChar = '\x00';
                    this.charDescriptors = null;
                    this.charBitmaps = null;
                }


                // PROPERTIES
                public ushort AverageWidth
                {
                    get { return this.avgWidth; }
                }

				public ushort MaxWidth
                {
                    get { return this.maxWidth; }
                }

				public ushort Height
                {
                    get { return this.height; }
                }

				public CharInfo this[char newChar]
				{
					get
					{
						// validate parameter
						if (newChar < this.startChar || newChar > this.endChar)
						{
							throw new ArgumentException("Font class: character not found.");
						}

						ushort bitMask = 0x7FFF;
						int descriptorIndex = (newChar - this.startChar) * constSizeOfCharDescriptor;
						ushort charWidth = (ushort)(this.charDescriptors[descriptorIndex] & bitMask);
						ushort charHeight = (ushort)(this.charDescriptors[descriptorIndex + 1] & bitMask);
						ushort bitmapStartIndex = (ushort)(this.charDescriptors[descriptorIndex + 2] & bitMask);
						ushort bitmapLength = (ushort)(this.charDescriptors[descriptorIndex + 3] & bitMask);

						return new CharInfo(newChar, charWidth, charHeight, bitmapStartIndex, bitmapLength);
					}
				}


				// METHODS
				public int GetTextWidth(string text)
				{
					int totalPixels = 0;

					foreach (char character in text)
					{
						totalPixels += this[character].Width;
					}

					return totalPixels;

				}

				public int FillBitmapBuffer(CharInfo charInfo, byte[] buffer, byte fgColorLow, byte fgColorHigh, byte bgColorLow, byte bgColorHigh)
				{
					// check the buffer size
					int charBytesReq = charInfo.Width * charInfo.Height * 2;
					int totalBytesReq = charInfo.Width * this.height * 2;
					if (buffer.Length < totalBytesReq)
					{
						throw new ArgumentException("Buffer length is not large enough for this character.");
					}

					// fill the buffer
					ushort bitmapValue = 0;
					int bufferIndex = 0;
					ushort bitmapIndex = charInfo.BitmapStartIndex;
					for (int i = 0; i < charInfo.BitmapLength; i++, bitmapIndex++)
					{
						bitmapValue = (ushort)this.charBitmaps[bitmapIndex];
						for (ushort bitMask = (ushort)0x0001; bitMask < (ushort)0x8000; )
						{
							// add pixel color
							if ((bitmapValue & bitMask) == bitMask)
							{
								// add foreground color
								buffer[bufferIndex] = fgColorHigh;
								buffer[bufferIndex + 1] = fgColorLow;
							}
							else
							{
								// add background color
								buffer[bufferIndex] = bgColorHigh;
								buffer[bufferIndex + 1] = bgColorLow;
							}

							// adjust bitMask, increment pixelsRead, and increment bufferIndex
							bitMask = (ushort)(bitMask << 1);
							bufferIndex += 2;

							// check if we are done
							if (bufferIndex >= charBytesReq)
							{
								break;
							}
						}
					}

					// add blank rows
					while (bufferIndex < totalBytesReq)
					{
						// add background color
						buffer[bufferIndex] = bgColorHigh;
						buffer[bufferIndex + 1] = bgColorLow;
						bufferIndex += 2;
					}

					// return num bytes in buffer
					return bufferIndex;
				}


                // MEMBER FIELDS
                protected ushort avgWidth;
                protected ushort maxWidth;
                protected ushort height;
                protected char startChar;
                protected char endChar;
                protected string charDescriptors;
                protected string charBitmaps;
				protected const int constSizeOfCharDescriptor = 4;	// number of chars in each descriptor
            }

			public class CharInfo
			{
				// CONSTRUCTORS
				public CharInfo(char character, ushort charWidth, ushort charHeight, ushort bitmapStartIndex, ushort bitmapLength)
				{
					this.character = character;
					this.charWidth = charWidth;
					this.charHeight = charHeight;
					this.bitmapStartIndex = bitmapStartIndex;
					this.bitmapLength = bitmapLength;
				}

				// PROPERTIES
				public char Character
				{
					get { return this.character; }
				}

				public ushort Width
				{
					get { return this.charWidth; }
				}

				public ushort Height
				{
					get { return this.charHeight; }
				}

				public ushort BitmapStartIndex
				{
					get { return this.bitmapStartIndex; }
				}

				public ushort BitmapLength
				{
					get { return this.bitmapLength; }
				}


				// MEMBER FIELDS
				private char character;
				private ushort charWidth;
				private ushort charHeight;
				private ushort bitmapStartIndex;
				private ushort bitmapLength;
			}

            #endregion


            #region Image Class

            public class Image
            {
                // CONSTRUCTORS
                public Image(byte[] imgBytes)
                {
					if (Utility.ExtractValueFromArray(imgBytes, 0, 4) != SIGNATURE)
					{
						throw new ArgumentException("Image Class: Signature bytes not found.");
					}

                    int width = (int)Utility.ExtractValueFromArray(imgBytes, 4, 2);
                    int height = (int)Utility.ExtractValueFromArray(imgBytes, 6, 2);

					if (width * height * 2 + 8 != imgBytes.Length)
					{
						throw new ArgumentException("Image class: Width and height do not match size of byte array.");
					}

					this.ImageBytes = imgBytes;
                    this.Width = width;
                    this.Height = height;
                }

                // PROPERTIES
                public readonly int Width;
                public readonly int Height;
                public const uint SIGNATURE = 0x354A82B8;
                public const int IMG_PIXELS_INDEX = 8;
                public byte[] ImageBytes;
            }

            #endregion


            #region LCD Configuration Class

            public class LCDConfiguration
            {
                // CONSTRUCTORS
                public LCDConfiguration(FEZ_Pin.Digital reset,
                    FEZ_Pin.Digital chipSelect,
                    FEZ_Pin.Digital RS,
                    FEZ_Pin.Digital lcdBackLight,
                    FEZ_Pin.Digital[] dataPins,
                    FEZ_Pin.Digital writePin,
                    FEZ_Pin.Digital readPin,
                    Orientation lcdOrientation)
                {
                    this.DataPins = new Cpu.Pin[8];

					for (int i = 0; i < 8; i++)
					{
						this.DataPins[i] = (Cpu.Pin)dataPins[i];
					}

                    this.WritePin = (Cpu.Pin)writePin;
                    this.ReadPin = (Cpu.Pin)readPin;
                    this.ChipSelect = (Cpu.Pin)chipSelect;
                    this.Reset = (Cpu.Pin)reset;
                    this.RS = (Cpu.Pin)RS;
                    this.BackLight = (Cpu.Pin)lcdBackLight;
                    this.LCDOrientation = lcdOrientation;
                }

                // PROPERTIES
                public Cpu.Pin[] DataPins;
                public Cpu.Pin WritePin;
                public Cpu.Pin ReadPin;
                public Cpu.Pin ChipSelect;
                public Cpu.Pin Reset;
                public Cpu.Pin RS;
                public Cpu.Pin BackLight;
                public Orientation LCDOrientation;
            }

            #endregion


            #region Touch Configuration Class

            public class TouchConfiguration
            {
                public SPI.SPI_module Channel;
                public Cpu.Pin ChipSelect;
                public Cpu.Pin TouchIRQ;

                public TouchConfiguration(SPI.SPI_module channel, FEZ_Pin.Digital chipSelect, FEZ_Pin.Digital touchIRQ)
                {
                    this.Channel = channel;
                    this.ChipSelect = (Cpu.Pin)chipSelect;
                    this.TouchIRQ = (Cpu.Pin)touchIRQ;
                }
            }

            #endregion


            #region Member Fields

			// constants
			private const int SCREEN_WIDTH = 240;
			private const int SCREEN_HEIGHT = 320;
            private const int BUFFER_SIZE = 2024;
			private const byte REGISTER_WRITE_GRAM = 0x22;
			private const int TOUCH_SAMPLING_TIME = 10;

            private bool disposed = false;
            private byte[] buffer = new byte[BUFFER_SIZE];
            private byte[] regBuffer = new byte[2];

            // lcd configuration
            private ParallelPort parallelPort;
            private OutputPort lcdReset;
            private OutputPort lcdChipSelect;
            private OutputPort lcdRegSelect;
            private OutputCompare lcdBackLight;
            private Orientation lcdOrientation;

            // touch configuration
            private SPI spi;
            private InputPort touchIRQ;
            private bool terminateTouchThread;
            private Thread touchThread;

			// display mode
			private DisplayMode displayMode;
			private int backlightLevel;

            #endregion
        }
    }
}